7

我试图运行以下内容,

type
  Vector = array [1..4] of Single;

{$CODEALIGN 16}
function add4(const a, b: Vector): Vector; register; assembler;
asm
  movaps xmm0, [a]
  movaps xmm1, [b]
  addps xmm0, xmm1
  movaps [@result], xmm0
end;

据我所知,它会在movaps上出现访问冲突,如果内存位置是 16 对齐,则可以信任movaps 。如果movups没有问题(不需要对齐)。

所以我的问题是,在 Delphi XE3 中,{$CODEALIGN} 在这种情况下似乎不起作用。

编辑

很奇怪...我尝试了以下方法。

program Project3;

{$APPTYPE CONSOLE}

uses
  windows;  // if not using windows, no errors at all

type
  Vector = array [1..4] of Single;

function add4(const a, b: Vector): Vector;
asm
  movaps xmm0, [a]
  movaps xmm1, [b]
  addps xmm0, xmm1
  movaps [@result], xmm0
end;

procedure test();
var
  v1, v2: vector;
begin
  v1[1] := 1;
  v2[1] := 1;
  v1 := add4(v1,v2);  // this works
end;

var
  a, b, c: Vector;

begin
  {$ifndef cpux64}
    {$MESSAGE FATAL 'this example is for x64 target only'}
  {$else}
  test();
  c := add4(a, b); // throw out AV here
  {$endif}
end.

如果没有添加“使用窗口”,一切都很好。如果“使用窗口”,那么它将在c := add4(a, b)抛出异常,但不会在test()中抛出异常。

谁能解释一下?

编辑 这一切对我来说都是有意义的,现在。Delphi XE3 - 64 位的结论是

  1. X64 的堆栈帧设置为 16 字节(根据需要),{$CODEALIGN 16} 将 proc/fun 的代码对齐为 16 字节。
  2. 动态数组位于堆中,可以使用 SetMinimumBlockAlignment(mba16byte) 将其设置为对齐 16
  3. 但是,堆栈变量并不总是 16 字节对齐,例如,如果您在上例中的 v1、v2 之前声明了一个整数 var,例如 test(),则该示例将不起作用
4

3 回答 3

4

你需要你的数据是 16 字节对齐的。这需要一些关心和关注。您可以确保堆分配器对齐到 16 个字节。但是您不能确保编译器将 16 字节对齐堆栈分配的变量,因为您的数组的对齐属性为 4,即其元素的大小。并且在其他结构中声明的任何变量也将具有 4 字节对齐。这是一个很难清除的障碍。

我认为您无法在当前可用的编译器版本中解决您的问题。至少不会,除非您放弃堆栈分配的变量,我猜这些变量太难吞下。使用外部汇编器可能会有一些运气。

于 2013-04-04T20:21:08.553 回答
2

您可以编写自己的内存分配例程,在堆中分配对齐的数据。您可以指定自己的对齐大小(不仅是 16 字节,还有 32 字节、64 字节等等......):

    procedure GetMemAligned(const bits: Integer; const src: Pointer;
      const SrcSize: Integer; out DstAligned, DstUnaligned: Pointer;
      out DstSize: Integer);
    var
      Bytes: NativeInt;
      i: NativeInt;
    begin
      if src <> nil then
      begin
        i := NativeInt(src);
        i := i shr bits;
        i := i shl bits;
        if i = NativeInt(src) then
        begin
          // the source is already aligned, nothing to do
          DstAligned := src;
          DstUnaligned := src;
          DstSize := SrcSize;
          Exit;
        end;
      end;
      Bytes := 1 shl bits;
      DstSize := SrcSize + Bytes;
      GetMem(DstUnaligned, DstSize);
      FillChar(DstUnaligned^, DstSize, 0);
      i := NativeInt(DstUnaligned) + Bytes;
      i := i shr bits;
      i := i shl bits;
      DstAligned := Pointer(i);
      if src <> nil then
        Move(src^, DstAligned^, SrcSize);
    end;

    procedure FreeMemAligned(const src: Pointer; var DstUnaligned: Pointer;
      var DstSize: Integer);
    begin
      if src <> DstUnaligned then
      begin
        if DstUnaligned <> nil then
          FreeMem(DstUnaligned, DstSize);
      end;
      DstUnaligned := nil;
      DstSize := 0;
    end;

然后使用指针和过程作为第三个参数来返回结果。

你也可以使用函数,但不是很明显。

type
  PVector^ = TVector;
  TVector  = packed array [1..4] of Single;

然后以这种方式分配这些对象:

const
   SizeAligned = SizeOf(TVector);
var
   DataUnaligned, DataAligned: Pointer;
   SizeUnaligned: Integer;
   V1: PVector;
begin
  GetMemAligned(4 {align by 4 bits, i.e. by 16 bytes}, nil, SizeAligned, DataAligned, DataUnaligned, SizeUnaligned);
  V1 := DataAligned;
  // now you can work with your vector via V1^ - it is aligned by 16 bytes and stays in the heap

  FreeMemAligned(nil, DataUnaligned, SizeUnaligned);
end;

正如您所指出的,我们已经传递nil给 GetMemAligned 和 FreeMemAligned - 当我们想要对齐现有数据时需要此参数,例如,我们作为函数参数接收的数据。

只需在汇编例程中使用直接寄存器名称而不是参数名称。使用寄存器调用约定时,您不会搞砸任何事情 - 否则您可能会在不知道使用的参数名称只是寄存器的别名的情况下修改寄存器。

在 Win64 下,使用 Microsoft 调用约定,第一个参数始终作为 RCX 传递,第二个 - RDX,第三个 R8,第四个 - R9,其余在堆栈中。函数在 RAX 中返回结果。但是如果一个函数返回一个结构(“记录”)结果,它不会在 RAX 中返回,而是在一个隐式参数中,按地址返回。调用后,您的函数可能会修改以下寄存器:RAX,RCX,RDX,R8,R9,R10,R11。其余的应保留。有关更多详细信息,请参阅https://msdn.microsoft.com/en-us/library/ms235286.aspx

在 Win32 下,使用 Delphi 寄存器调用约定,调用在 EAX 中传递第一个参数,在 EDX 中传递第二个参数,在 ECX 中传递第三个参数,然后在堆栈中传递

下表总结了这些差异:

         64     32
         ---   ---
    1)   rcx   eax
    2)   rdx   edx
    3)   r8    ecx
    4)   r9    stack

因此,您的函数将如下所示(32 位):

procedure add4(const a, b: TVector; out Result: TVector); register; assembler;
asm
  movaps xmm0, [eax]
  movaps xmm1, [edx]
  addps xmm0, xmm1
  movaps [ecx], xmm0
end;

64位以下;

procedure add4(const a, b: TVector; out Result: TVector); register; assembler;
asm
  movaps xmm0, [rcx]
  movaps xmm1, [rdx]
  addps xmm0, xmm1
  movaps [r8], xmm0
end;

顺便说一句,根据微软的说法,64 位调用约定中的浮点参数直接在 XMM 寄存器中传递:第一个在 XMM0 中,第二个在 XMM1 中,第三个在 XMM2 中,第四个在 XMM3 中,然后在堆栈中。所以你可以通过值传递它们,而不是通过引用。

于 2017-07-14T07:48:04.163 回答
1

使用它来使内置内存管理器以 16 字节对齐方式分配:

SetMinimumBlockAlignment(mba16Byte);

此外,据我所知,“寄存器”和“汇编程序”都是冗余指令,因此您可以从代码中跳过这些指令。

--

编辑:您提到这是针对 x64 的。我刚刚在为 x64 编译的 Delphi XE2 中尝试了以下内容,它在这里工作。

program Project3;

type
  Vector = array [1..4] of Single;

function add4(const a, b: Vector): Vector;
asm
  movaps xmm0, [a]
  movaps xmm1, [b]
  addps xmm0, xmm1
  movaps [@result], xmm0
end;

procedure f();
var
  v1,v2 : vector;
begin
  v1[1] := 1;
  v2[1] := 1;
  v1 := add4(v1,v2);
end;

begin
  {$ifndef cpux64}
  {$MESSAGE FATAL 'this example is for x64 target only'}
  {$else}
  f();
  {$endif}
end.
于 2013-04-04T07:38:35.347 回答