Загрузка регистра xmm с двумя UInt64, которые находятся в массиве с указателем - PullRequest
0 голосов
/ 27 ноября 2018

Я пытаюсь загрузить 128-битный регистр xmm с двумя UInt64 целыми числами в Delphi (XE6).

Фон

Регистр XMM 128-битный,и может быть загружен несколькими независимыми целыми числами.Затем процессор может добавить эти несколько целых чисел параллельно.

Например, вы можете загрузить xmm0 и xmm1 с четырьмя UInt32s каждый, а затем процессор может добавить все четыре пары одновременно.

xmm0: $00001000 $00000100 $00000010 $00000001
          +         +         +         +      
xmm1: $00002000 $00000200 $00000020 $00000002
          =         =         =         =
xmm0: $00003000 $00000300 $00000030 $00000003

После загрузки xmm0 и xmm0 вы выполняете сложение четырех пар, используя:

paddd xmm0, xmm1    //Add packed 32-bit integers (i.e. xmm0 := xmm0 + xmm1)

Вы также можете сделать это, используя 8 x 16-битных целых чисел:

xmm0: $001F $0013 $000C $0007 $0005 $0003 $0002 $0001
        +     +     +     +     +     +     +     + 
xmm1: $0032 $001F $0013 $000C $0007 $0005 $0003 $0002
        =     =     =     =     =     =     =     = 
xmm0: $0051 $0032 $001F $0013 $000C $0007 $0005 $0003

С инструкцией

paddw xmm0, xmm1  //Add packed 16-bit integers

Теперь для 64-разрядных целых чисел

Чтобы загрузить два 64-разрядных целых числа в регистр xmm, необходимо использовать либо:

  • movdqu: переместить двойное слово (без выравнивания)
  • movdqa: переместить двойное слово (выровнено)

В этом простом примере мы не будембеспокоиться о том, чтобы наши UInt64 были выровнены, и мы просто будем использовать unaligned версию (movdqu)

Первое, с чем нам придется иметь дело, это то, что Delphi-компилятор знает, что movdqu для загрузки требуется 128-битное что-то - оно загружает double quadwords.

Для этого мы будемсоздать 128-битную структуру, которая также позволяет нам обращаться к двум 64-битным значениям:

TDoubleQuadword = packed record
   v1: UInt64; //value 1
   v2: UInt64; //value 2
end;

И теперь мы можем использовать этот тип в тестовом консольном приложении:

procedure Main;
var
    x, y: TDoubleQuadword;
begin
    //[1,5] + [2,7] = ?
    x.v1 := $0000000000000001;
    x.v2 := $0000000000000005;

    y.v1 := $0000000000000002;
    y.v2 := $0000000000000007;

    asm
        movdqu xmm0, x      //move unaligned double quadwords (xmm0 := x)
        movdqu xmm1, y      //move unaligned double quadwords (xmm1 := y)

        paddq  xmm0, xmm1   //add packed quadword integers    (xmm0 := xmm0 + xmm1)

        movdqu x, xmm0      //move unaligned double quadwords (x := xmm0)

    end;

    WriteLn(IntToStr(x.v1)+', '+IntToSTr(x.v2));
end;

И это работает, распечатывая:

3, 12

Взгляд на приз

С целью достижения выравнивания x и y (, но не является необходимой частью моего вопроса ), допустим, у нас есть указатель на TDoubleQuadword структуру:

TDoubleQuadword = packed record
   v1: UInt64; //value 1
   v2: UInt64; //value 2
end;
PDoubleQuadword = ^TDoubleQuadword;

мы теперь изменим наш гипотетический тест функция для использования PDoubleQuadword:

procedure AlignedStuff;
var
    x, y: PDoubleQuadword;
begin
    x := GetMemory(sizeof(TDoubleQuadword));
    x.v1 := $0000000000000001;
    x.v2 := $0000000000000005;

    y := GetMemory(sizeof(TDoubleQuadword));
    y.v1 := $0000000000000002;
    y.v2 := $0000000000000007;

    asm
        movdqu xmm0, x      //move unaligned double quadwords (xmm0 := x)
        movdqu xmm1, y      //move unaligned double quadwords (xmm1 := y)

        paddq  xmm0, xmm1       //add packed quadword integers    (xmm0 := xmm0 + xmm1)
        movdqu x, xmm0         //move unaligned double quadwords (v1 := xmm0)
    end;

    WriteLn(IntToStr(x.v1)+', '+IntToSTr(x.v2));
end;

Теперь это не компилируется, и имеет смысл, почему:

movdqu xmm0, x      //E2107 Operand size mismatch

Это имеет смысл.Аргумент x должен быть 128-битным, и компилятор знает, что x действительно является только (32-битным) указателем.

Но что это должно быть?

Теперь мы подошли к моему вопросу: что это должно быть?Я случайно пюрею разные вещи на своей клавиатуре, надеясь, что боги компилятора просто примут то, что я имею в виду.Но ничего не работает.

//Don't try to pass the 32-bit pointer itself, pass the thing it points to:
movdqu xmm0, x^     //E2107 Operand size mismatch    

//Try casting it
movdqu xmm0, TDoubleQuadword(x^) //E2105 Inline assembler error

//i've seen people using square brackets to mean "contents of":
movdqu xmm0, [x]     //E2107 Operand size mismatch    

И теперь мы отказываемся от рационального мышления

movdqu xmm0, Pointer(x)
movdqu xmm0, Addr(x^)
movdqu xmm0, [Addr(x^)]
movdqu xmm0, [Pointer(TDoubleQuadword(x))^]

Я получил одну вещь для компиляции:

movdqu xmm0, TDoubleQuadword(x)

Но конечнокоторый загружает в регистр адрес из x, а не значения внутри x.

Так что я сдаюсь.

Complete Minimal Example

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
     TDoubleQuadword = packed record
         v1: UInt64; //value 1
         v2: UInt64; //value 2
     end;
     PDoubleQuadword = ^TDoubleQuadword;

    TVectorUInt64 = array[0..15] of UInt64;
    PVectorUInt64 = ^TVectorUInt64;

 procedure AlignedStuff;
 var
    x, y: PVectorUInt64;
 begin
    x := GetMemory(sizeof(TVectorUInt64));
    //x[0] := ...
    //x[1] := ...
    // ...
    //x[3] := ...
    x[4] := $0000000000000001;
    x[5] := $0000000000000005;

    y := GetMemory(sizeof(TVectorUInt64));
    //y[0] := ...
    //y[1] := ...
    // ...
    //y[3] := ...
    y[4] := $0000000000000002;
    y[5] := $0000000000000007;

    asm
        movdqu xmm0, TDoubleQuadword(x[4])      //move unaligned double quadwords (xmm0 := x)
        movdqu xmm1, TDoubleQuadword(y[4])      //move unaligned double quadwords (xmm1 := y)

        paddq  xmm0, xmm1       //add packed quadword integers    (xmm0 := xmm0 + xmm1)
        movdqu TDoubleQuadword(x[4]), xmm0         //move unaligned double quadwords (v1 := xmm0)
    end;

    WriteLn(IntToStr(x[4])+', '+IntToSTr(x[5]));
 end;

begin
  try
        AlignedStuff;
        Writeln('Press enter to close...');
        Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Указатель?

Причина, по которой вопрос задается об указателях, заключается в том, что:

  • вы не можете использовать переменные стека (Delphi не гарантирует выравнивание переменных стека)
  • вы можете скопировать их в регистр (например, EAX), но затем вы выполняете потерянное копирование и вызов функции
  • у меня уже есть данные, выровненные в памяти

Если я приведу пример кода, который просто включает в себя добавление UInt64s:

TVectorUInt64 = array[0..15] of UInt64;
PVectorUInt64 = ^TVectorUInt64;

var
   v: PVectorUInt64;
begin
   v := GetMemoryAligned(sizeof(TVectorUInt64), 64); //64-byte alignment

   //v is initalized

   for i := 0 to 15 do
   begin
      v[0] := v[0] + v[4];
      v[1] := v[1] + v[5];
      v[2] := v[2] + v[6];
      v[3] := v[3] + v[7];

      //..and some more changes to v0..v3
      //..and some more changes to v12..v15

      v[8]  := v[8]  + v[12];
      v[9]  := v[9]  + v[13];
      v[10] := v[10] + v[14];
      v[11] := v[11] + v[15];

      //...and some more changes to v4..v7

      v[0] := v[0] + v[4];
      v[1] := v[1] + v[5];
      v[2] := v[2] + v[6];
      v[3] := v[3] + v[7];

      //...and some more changes to v0..v3
      //...and some more changes to v12..v15

      v[8]  := v[8]  + v[12];
      v[9]  := v[9]  + v[13];
      v[10] := v[10] + v[14];
      v[11] := v[11] + v[15];

      //...and some more changes to v4..v7

      v[0] := v[0] + v[4];
      v[1] := v[1] + v[5];
      v[2] := v[2] + v[6];
      v[3] := v[3] + v[7];

      //..and some more changes to v0..v3
      //..and some more changes to v12..v15

      v[8]  := v[8]  + v[12];
      v[9]  := v[9]  + v[13];
      v[10] := v[10] + v[14];
      v[11] := v[11] + v[15];

      //...and some more changes to v4..v7

      v[0] := v[0] + v[4];
      v[1] := v[1] + v[5];
      v[2] := v[2] + v[6];
      v[3] := v[3] + v[7];

      //...and some more changes to v0..v3
      //...and some more changes to v12..v15

      v[8]  := v[8]  + v[12];
      v[9]  := v[9]  + v[13];
      v[10] := v[10] + v[14];
      v[11] := v[11] + v[15];

      //...and some more changes to v4..v7
   end;

Концептуально очень легко изменить код на:

      //v[0] := v[0] + v[4];
      //v[1] := v[1] + v[5];
      asm
         movdqu xmm0, v[0]
         movdqu xmm1, v[4]
         paddq xmm0, xmm1
         movdqu v[0], xmm0
      end
      //v[2] := v[2] + v[6];
      //v[3] := v[3] + v[7];
      asm
         movdqu xmm0, v[2]
         movdqu xmm1, v[6]
         paddq xmm0, xmm1
         movdqu v[2], xmm0
      end

      //v[8]  := v[8]  + v[12];
      //v[9]  := v[9]  + v[13];
      asm
         movdqu xmm0, v[8]
         movdqu xmm1, v[12]
         paddq xmm0, xmm1
         movdqu v[8], xmm0
      end
      //v[10] := v[10] + v[14];
      //v[11] := v[11] + v[15];
      asm
         movdqu xmm0, v[10]
         movdqu xmm1, v[14]
         paddq xmm0, xmm1
         movdqu v[10], xmm0
      end

Хитрость заключается в получении Delphiкомпилятор, чтобы принять его.

  • он работает для непосредственных данных
  • он не работает для poИнтер к данным
  • , и вы думаете, что [contentsOfSquareBrackets] будет работать

Bonus Chatter

Использование решения Дэвида (накладных расходов на вызов функций) приводит к повышению производительности-7% (90 МБ / с -> 83 МБ / с пропускной способности алгоритма)

Похоже, что в компиляторе XE6 концептуально можно вызвать:

movdqu xmm0, TPackedQuadword

, ноу компилятора просто нет мозгов, чтобы позволить вам выполнить концептуальный вызов:

movdqu xmm0, PPackedQuadword^

или его моральный эквивалент.

Если это ответ, не бойтесь этого.Примите это и сформулируйте как форму ответа:

* "Компилятор не поддерживает разыменование указателя внутри блока asm. Независимо от того, пытаетесь ли вы сделать это с помощью каретки (^) или квадратных скобок ([...]).Это просто невозможно.

Если это ответ, ответьте.

Если это не так, и компилятор может поддерживать указатели в asm заблокировать, затем опубликовать ответ.

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

Документация для встроенного ассемблера в Delphi не такая полная, как должна быть, и многие функции просто не документированы.Так что я не могу быть уверен в этом, но, насколько мне известно, просто нет поддержки оператора ассемблера, который вы пытаетесь записать, где один операнд является локальной переменной типа указателя.

Я настоятельно рекомендую вам не смешивать код на языке Паскаль и код на ассемблере в одной и той же функции.Это делает очень трудным создание эффективного кода и очень затрудняет управление использованием регистров при перемещении между кодом Pascal и кодом ассемблера в одной и той же функции.

Я лично считаю правилом никогда не смешивать Pascal и inlineассемблер.Всегда пишите чистые ассемблерные функции.Например, для 32-битного кода вы должны написать полную программу, например:

{$APPTYPE CONSOLE}

type
  PDoubleQuadword = ^TDoubleQuadword;
  TDoubleQuadword = record
    v1: UInt64;
    v2: UInt64;
  end;

function AddDoubleQuadword(const dqw1, dqw2: TDoubleQuadword): TDoubleQuadword;
asm
  movdqu xmm0, [eax]
  movdqu xmm1, [edx]
  paddq  xmm0, xmm1
  movdqu [ecx], xmm0
end;

procedure AlignedStuff;
var
  x, y: PDoubleQuadword;
begin
  New(x);
  x.v1 := $0000000000000001;
  x.v2 := $0000000000000005;

  New(y);
  y.v1 := $0000000000000002;
  y.v2 := $0000000000000007;

  x^ := AddDoubleQuadword(x^, y^);

  Writeln(x.v1, ', ', x.v2);
end;

begin
  AlignedStuff;
  Readln;
end.

Эта программа выводит:

3, 12

Или вы можете использовать запись с операторами:

type
  PDoubleQuadword = ^TDoubleQuadword;
  TDoubleQuadword = record
    v1: UInt64;
    v2: UInt64;
    class operator Add(const dqw1, dqw2: TDoubleQuadword): TDoubleQuadword;
  end;

class operator TDoubleQuadword.Add(const dqw1, dqw2: TDoubleQuadword): TDoubleQuadword;
asm
  movdqu xmm0, [eax]
  movdqu xmm1, [edx]
  paddq  xmm0, xmm1
  movdqu [ecx], xmm0
end;

А потом на сайте звонка у вас есть:

x^ := x^ + y^;
0 голосов
/ 27 ноября 2018

Рабочий код:

   asm
        mov eax, x
        mov edx, y
        movdqu xmm0, DQWORD PTR [eax]   //move unaligned double quadwords (xmm0 := x)
        movdqu xmm1, DQWORD PTR [edx]  //move unaligned double quadwords (xmm1 := y)

        paddq  xmm0, xmm1     //add packed quadword integers    (xmm0 := xmm0 + xmm1)
        movdqu DQWORD PTR [eax], xmm0  //move unaligned double quadwords (v1 := xmm0)
    end;

 IntToStr(x.v1)+', '+IntToSTr(x.v2);  prints 3,12
...