2014-10-21 2 views
3

Я портирование 32-битная Delphi Basm кода в 64-битного FPC (Win64 целевого ОС) и удивляюсь, почему следующая команда не компилируется в 64-битный FPC:Почему эта инструкция LEA не компилируется?

{$IFDEF FPC} 
    {$ASMMODE INTEL} 
{$ENDIF} 

procedure DoesNotCompile; 
asm 
     LEA ECX,[ECX + ESI + $265E5A51] 
end; 

// Error: Asm: 16 or 32 Bit references not supported 

возможных обходные пути:

procedure Compiles1; 
asm 
     ADD ECX,ESI 
     ADD ECX,$265E5A51 
end; 

procedure Compiles2; 
asm 
     LEA ECX,[RCX + RSI + $265E5A51] 
end; 

Я просто не понимаю, что не так с 32-разрядной командой LEA в целевом Win64 (она компилирует ОК в 32-разрядном Delphi, поэтому это правильная инструкция CPU).


Optimization примечание:

Следующий код, составленный 64-битного FPC 2.6.2

{$MODE DELPHI} 
    {$ASMMODE INTEL} 

procedure Test; 
asm 
     LEA  ECX,[RCX + RSI + $265E5A51] 
     NOP 
     LEA  RCX,[RCX + RSI + $265E5A51] 
     NOP 
     ADD  ECX,$265E5A51 
     ADD  ECX,ESI 
     NOP 
end; 

генерирует следующий вывод: ассемблер

00000000004013F0 4883ec08     sub $0x8,%rsp 
         project1.lpr:10 LEA  ECX,[RCX + RSI + $265E5A51] 
00000000004013F4 8d8c31515a5e26   lea 0x265e5a51(%rcx,%rsi,1),%ecx 
         project1.lpr:11 NOP 
00000000004013FB 90      nop 
         project1.lpr:12 LEA  RCX,[RCX + RSI + $265E5A51] 
00000000004013FC 488d8c31515a5e26   lea 0x265e5a51(%rcx,%rsi,1),%rcx 
         project1.lpr:13 NOP 
0000000000401404 90      nop 
         project1.lpr:14 ADD  ECX,$265E5A51 
0000000000401405 81c1515a5e26    add $0x265e5a51,%ecx 
         project1.lpr:15 ADD  ECX,ESI 
000000000040140B 01f1      add %esi,%ecx 
         project1.lpr:16 NOP 
000000000040140D 90      nop 
         project1.lpr:17 end; 
000000000040140E 4883c408     add $0x8,%rsp 

и победитель (Длина 7 байт):

LEA  ECX,[RCX + RSI + $265E5A51] 

все 3 альтернативы (включая LEA ECX,[ECX + ESI + $265E5A51], которые не скомпилируются 64-разрядным FPC) имеют длину 8 байтов.

Не уверен, что победитель лучше всего в скорости.

+1

Обходной лучше (сохраняет байт), это все-таки ошибка, конечно – harold

ответ

5

Я считаю это ошибкой в ​​ассемблере FPC. Код asm, который вы представляете, действителен, и в режиме с 64-битным интерфейсом вполне справедливо использовать LEA с 32-разрядными регистрами, как вы это делали. Документы процессоров Intel понятны по этому вопросу. 64-разрядный встроенный ассемблер Delphi принимает этот код.

Для устранения этого вам нужно будет сдать собрать код:

DQ $265e5a510e8c8d67 

По мнению CPU Delphi это выходит как:

 
Project1.dpr.12: DQ $265e5a510e8c8d67 
0000000000424160 678D8C0E515A5E26 lea ecx,[esi+ecx+$265e5a51] 

Я выполнил очень простой сравнительный анализ для сравнить использование 32 и 64-битных операндов и версию с использованием двух ADD.Код выглядит следующим образом:

{$APPTYPE CONSOLE} 

uses 
    System.Diagnostics; 

function BenchWithTwoAdds: Integer; 
asm 
    MOV EDX,ESI 
    XOR EAX,EAX 
    MOV ESI,$98C34 
    MOV ECX,$ffffffff 
@loop: 
    ADD EAX,ESI 
    ADD EAX,$265E5A51 
    DEC ECX 
    CMP ECX,0 
    JNZ @loop 
    MOV ESI,EDX 
end; 

function BenchWith32bitOperands: Integer; 
asm 
    MOV EDX,ESI 
    XOR EAX,EAX 
    MOV ESI,$98C34 
    MOV ECX,$ffffffff 
@loop: 
    LEA EAX,[EAX + ESI + $265E5A51] 
    DEC ECX 
    CMP ECX,0 
    JNZ @loop 
    MOV ESI,EDX 
end; 

{$IFDEF CPUX64} 
function BenchWith64bitOperands: Integer; 
asm 
    MOV EDX,ESI 
    XOR EAX,EAX 
    MOV ESI,$98C34 
    MOV ECX,$ffffffff 
@loop: 
    LEA EAX,[RAX + RSI + $265E5A51] 
    DEC ECX 
    CMP ECX,0 
    JNZ @loop 
    MOV ESI,EDX 
end; 
{$ENDIF} 

var 
    Stopwatch: TStopwatch; 

begin 
{$IFDEF CPUX64} 
    Writeln('64 bit'); 
{$ELSE} 
    Writeln('32 bit'); 
{$ENDIF} 
    Writeln; 

    Writeln('BenchWithTwoAdds'); 
    Stopwatch := TStopwatch.StartNew; 
    Writeln('Value = ', BenchWithTwoAdds); 
    Writeln('Elapsed time = ', Stopwatch.ElapsedMilliseconds); 
    Writeln; 

    Writeln('BenchWith32bitOperands'); 
    Stopwatch := TStopwatch.StartNew; 
    Writeln('Value = ', BenchWith32bitOperands); 
    Writeln('Elapsed time = ', Stopwatch.ElapsedMilliseconds); 
    Writeln; 

{$IFDEF CPUX64} 
    Writeln('BenchWith64bitOperands'); 
    Stopwatch := TStopwatch.StartNew; 
    Writeln('Value = ', BenchWith64bitOperands); 
    Writeln('Elapsed time = ', Stopwatch.ElapsedMilliseconds); 
{$ENDIF} 

    Readln; 
end. 

выход на моем с i5-2300 Intel:

 
32 bit 

BenchWithTwoAdds 
Value = -644343429 
Elapsed time = 2615 

BenchWith32bitOperands 
Value = -644343429 
Elapsed time = 3915 

---------------------- 

64 bit 

BenchWithTwoAdds 
Value = -644343429 
Elapsed time = 2612 

BenchWith32bitOperands 
Value = -644343429 
Elapsed time = 3917 

BenchWith64bitOperands 
Value = -644343429 
Elapsed time = 3918 

Как вы можете видеть, что нет ничего, чтобы сделать выбор между любой из вариантов LEA на основе этого. Различия между их временами находятся в пределах изменчивости измерения. Однако вариант с использованием ADD дважды выигрывает руки.

Некоторые разные результаты с разных машин. Вот вывод на Xeon E5530:

 
64 bit 

BenchWithTwoAdds 
Value = -644343429 
Elapsed time = 3434 

BenchWith32bitOperands 
Value = -644343429 
Elapsed time = 3295 

BenchWith64bitOperands 
Value = -644343429 
Elapsed time = 3279 

А на Xeon E5-4640 v2:

 
64 bit 

BenchWithTwoAdds 
Value = -644343429 
Elapsed time = 4102 

BenchWith32bitOperands 
Value = -644343429 
Elapsed time = 5868 

BenchWith64bitOperands 
Value = -644343429 
Elapsed time = 5868 
+0

Я добавил некоторые тайминги для версии, основанной на двух ADD. Который побеждает всесторонне. –

+0

На каком процессоре? Три-операнд 'lea' наказывается на недавнем оборудовании Intel. – gsg

+1

@gsg Я сделал это. Я думаю, вы правы в том, что современное оборудование не поддерживает три op lea. –

2

Отдельные размеры самих операндов, Компоненты операндов памяти имеют размер по умолчанию. В 64-битном режиме это 64 бит, то есть вы должны использовать 64-разрядные регистры для компонентов операндов памяти, если у вас нет конкретной причины.

x86 ISA позволяет изменять размер для данной команды с префиксным байтом 0x67, но вы, вероятно, не хотите этого делать (и, по-видимому, ваш ассемблер даже не поддерживает его).

Чтобы сделать различие между операндом и операндом компонент немного понятнее:

lea eax, dword ptr [rax + rdx * 4] 

    ^^^ ^^^^^ ^^^     operands: can be any size you like 
        ^^^ ^^^  operand components: usually 64-bit 
+0

Приставка с 0x67 вероятно, именно то, что здесь нужно. –

+0

@DavidHeffernan, это не меняет результат (это 32-битный пункт назначения)? Просто занимает байт пространства AFAIK – harold

+0

@harold Strip префикс 0x67, и у вас есть другая инструкция: 'lea ecx, [rsi + rcx + $ 265e5a51]' –

Смежные вопросы