如何在 Delphi XE 3中使用align-data-move SSE?

i86rm4rw  于 2023-08-04  发布在  其他
关注(0)|答案(4)|浏览(77)

我试着运行以下内容,

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 上的访问违规,据我所知,movaps 可以被信任,如果内存位置是16-align。如果 movups(不需要对齐),它工作没有问题。
所以我的问题是,在 Delphi XE 3中,{$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.


如果没有添加“use windows”,一切都很好。如果'use window',那么它将抛出异常在 c:= add 4(a,B),但不在 test()
谁能解释这一点?

    • Delphi 3 - 64位结论如下
  1. X64的堆栈帧设置为16字节(根据需要),{$CODEALIGN 16}将proc/fun的代码对齐为16字节。
    1.动态数组位于堆中,可以使用SetMinimumBlockAlignment(mba 16 byte)将其设置为对齐16
    1.然而,栈var并不总是16字节对齐的,例如,如果在上面的示例中在v1、v2之前声明了一个整数var,例如。test(),则该示例将不工作
zvms9eto

zvms9eto1#

你需要你的数据是16字节对齐。这需要一些关心和注意。您可以确保堆分配器与16个字节对齐。但是你不能确保编译器将16字节对齐你的堆栈分配的变量,因为你的数组有一个对齐属性4,它的元素的大小。在其他结构中声明的任何变量也将具有4字节对齐。这是一个坚韧的障碍。
我不认为你可以在当前可用的编译器版本中解决你的问题。至少不会,除非你放弃堆栈分配的变量,我猜这是一个太苦的药丸吞下。你可能会有一些运气与外部汇编程序。

vsikbqxv

vsikbqxv2#

您可以编写自己的内存分配例程,在堆中分配对齐的数据。您可以指定自己的对齐大小(不仅是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 -当我们想要对齐现有数据时需要此参数,例如。一个我们已经收到的函数参数,例如。
在汇编程序中使用直接的寄存器名而不是参数名。当使用寄存器调用约定时,您不会把它弄乱-否则您可能会在不知道所使用的参数名只是寄存器的别名的情况下修改寄存器。
在Win 64下,Microsoft调用约定,第一个参数总是传递为RCX,第二个参数是RDX,第三个参数是R8,第四个参数是R9,其余参数是栈。函数返回RAX格式的结果。但是如果函数返回结构(“record”)结果,它不是在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;


顺便说一下,根据Microsoft的说法,64位调用约定中的浮点参数直接传递到XMM寄存器中:第一个在XMM 0中,第二个在XMM 1中,第三个在XMM 2中,第四个在XMM 3中,其余在堆栈中。所以你可以通过值传递它们,而不是通过引用。

e4eetjau

e4eetjau3#

使用此命令使内置内存管理器分配16字节对齐:

SetMinimumBlockAlignment(mba16Byte);

字符串
此外,据我所知,“register”和“assembler”都是冗余指令,因此您可以从代码中跳过它们。

编辑:你提到这是针对x64的。我刚刚尝试了以下在 Delphi XE 2编译为x64和它的工作在这里。

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.

bgtovc5b

bgtovc5b4#

为了确保未打包记录类型中的字段正确对齐,编译器会在对齐为2的字段之前插入一个未使用的字节,并在对齐为4的字段之前插入最多3个未使用的字节(如果需要)。最后,编译器将记录的总大小向上舍入到由任何字段的最大对齐方式指定的字节边界。
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Internal_Data_Formats_(Delphi)#Record_Types
对于下一个记录,如果使用{$ALIGN 16},则有下一个表

type
  TAlignType = Extended;
  TRec = record
    first : byte;
    second: TAlignType;
  end;

 TAlignType      |  Byte    Word    Integer     Int64     Extended
-------------------------------------------------------------------
 Record Align    |    1       2        4          8          16
 Record Size     |    2       4        8          16         32
                 |
 Align bytes     |    0       1        3          7          15
after field first|

字符串

因此,为了使结构对齐到16字节,必须添加一个大于8字节的字段

此示例在x86/x64中运行良好。我添加了扩展类型的哑字段。

{$EXTENDEDCOMPATIBILITY ON} // To use 10 byte size for Win64. Extended type has 8 byte size for Win64 and 10 byte for Win32.
{$ALIGN 16}
type
  Vector = record
    case Integer of
      0: (dw: array[0..4-1]of Single);
      8: (a: Extended); // 10 byte field to use alignment of record field
      // https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Internal_Data_Formats_(Delphi)#Record_Types
  end;

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

procedure test();
var
  dump: Integer; // 4 byte
  a, b: Vector;
begin
  a.dw[0] := 1;
  b.dw[0] := 1;
  a := add4(a, b);  // this works
end;

var
  a, b, c: Vector;
begin
  test();
  c := add4(a, b);
  Readln;
end.

相关问题