2013-04-04 15 views
7

Я пытался запустить следующую,Как использовать SSE с выравниванием данных в Delphi XE3?

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 выравнивания. Это не проблема, если movups (выравнивание не требуется).

Так что мой вопрос в Delphi XE3, {$ CODEALIGN}, похоже, не работает в этом случае.

EDIT

Очень странно ... Я пробовал следующее.

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().

Кто может это объяснить?

EDIT все это имеет смысл для меня, теперь. выводы для Delphi XE3 - 64-разрядные

  1. кадры стека на X64 установлены в 16 байт (при необходимости), {$ CODEALIGN 16} выравнивает код для Proc/удовольствия до 16 байт.
  2. динамический массив живет в куче, который может быть установлен для выравнивания 16, используя SetMinimumBlockAlignment (mba16byte)
  3. однако, стек вары не всегда 16 байт выровнены, например, если вы объявить целочисленный вар перед v1 , v2 в приведенном выше примере, например test(), пример не будет работать.
+1

'CODEALIGN' выравнивает _code_. Если вы хотите выровнять _data_, вы можете использовать директиву 'ALIGN'. – Michael

+1

Я тоже пытался {$ ALIGN 16}, и он не работает. –

ответ

2

Для того, чтобы ваши данные были выровнены по 16 байт, нужны ваши данные. Это требует некоторой осторожности и внимания. Вы можете убедиться, что распределитель кучи выравнивается до 16 байтов. Но вы не можете убедиться, что компилятор будет по 16 байт выровнять переменные стека, потому что ваш массив имеет свойство выравнивания 4, размер его элементов. И любые переменные, объявленные внутри других структур, также будут иметь 4 байта. Это непростое препятствие.

Я не думаю, что вы можете решить свою проблему в доступных в настоящее время версиях компилятора. По крайней мере, если вы не откажете стек, выделенные переменные, которые, я думаю, будут слишком горькими, чтобы проглотить таблетку.Возможно, вам повезло с внешним ассемблером. U

+0

OK ... проще ли это сделать на C++? –

+1

MS-компилятор не разрешает 64-битный встроенный asm. Я ожидаю, что gcc. И я уверен, что gcc даст вам возможность выравнивать переменные стека. –

+0

Очень странное поведение, пожалуйста, взгляните на мои последние изменения. –

1

Используйте это, чтобы сделать встроенный менеджер памяти выделить с выравниванием 16-байтовый:

SetMinimumBlockAlignment(mba16Byte); 

Кроме того, насколько я знаю, как «регистр» и «Ассемблер» являются избыточные директивы так вы можете пропустить их из своего кода.

-

Edit: Вы упоминаете это для x64. Я просто попробовал следующее в Delphi XE2, скомпилированном для 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. 
+0

Я пробовал ваше решение, и оно не работает. Насколько я могу судить, реализация SetMinimumBlockAlignment не имеет эффектов под 64-битным, у него есть комментарий. {16-байтовое выравнивание требуется под 64-битным.}. –

+0

@DoctorLai См. Мое редактирование. Разве это не работает для вас? –

+0

Нет, он не работает на моем ПК, это исключает исключение доступа. –

0

Вы можете написать свои собственные процедуры выделения памяти, которые выделяют выровненные данные в куче. Вы можете задать свой собственный размер выравнивания (а не только 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 с Дельм регистром вызовов, вызов проходит первый параметр в 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: первый в XMM0, второй в XMM1, третий в XMM2 и четвёртом в xmm3, и отдохнуть в стеке. Поэтому вы можете передавать их по значению, а не по ссылке.