Teknisk artikel

Portera Delphi _ftol inline assembly till 64-bitarsbyggen

· Delphi-programmering

Äldre Delphi-kod innehöll ibland en liten _ftol-hjälpare för att konvertera ett flyttalsvärde till ett heltal. I 32-bitarsprojekt var det vanligt att skriva en sådan hjälpare med inline assembler, särskilt i grafik- och PDF-kod som hade trimmats för äldre kompilatorer.

1
2
3
4
5
6
7
8
function _ftol( f: double) : Integer; cdecl;
begin
  asm
    lea   eax, f
    fstp  qword ptr [eax]
  end;
  result := Trunc(f);
end;

Den metoden bryts när samma enhet kompileras för ett 64-bitarsmål. Delphi 64-bitarskompilatorn rapporterar E1025 Unsupported language feature: 'ASM' eftersom DCC64 inte stöder blandad Pascal och inline assembly inuti vanliga Pascal-rutiner.

Varför 64-bitarskompilatorn avvisar det

DCC64 kan assemblera 64-bitars assembly-rutiner, men rutinen måste skrivas som en komplett assembler-rutin med 64-bitars calling convention i åtanke. Du kan inte kopiera 32-bitars inline assembler till en Pascal-funktionskropp och förvänta dig att kompilatorn översätter registeranvändning, stacklayout eller x87-beteende.

En fullständig assemblerversion är möjlig, och kod som följande visar vilken typ av control-word-hantering som kan krävas när värdet redan ligger på FPU-stacken.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function _ftol: Integer; cdecl;
// Assumes double value is in FPU stack on entry
// Make a truncation to integer and put it into function result
var
  TmpVal: Int64;
  SaveCW, ScratchCW: word;
 
asm
  .NOFRAME
  fnstcw word ptr [SaveCW]
  fnstcw word ptr [ScratchCW]
  or word ptr [ScratchCW], 0F00h  ;// trunc toward zero, full precision
  fldcw word ptr [ScratchCW]
  fistp qword ptr [TmpVal]
  fldcw word ptr [SaveCW]
  mov rax, TmpVal
end;

Föredra Pascal när målet bara är trunkering

För den mesta applikationskoden är assembler-rutinen onödig. Om det verkliga kravet är att trunkera en Double mot noll och returnera ett heltalsvärde är den tydliga och portabla versionen helt enkelt:

1
2
3
4
function _ftol(f: double): integer; cdecl;
begin
  Result := Trunc(f);
end;

Trunc uttrycker avsikten direkt, kompilerar för både 32-bitars- och 64-bitarsmål och undviker underhållskostnaden för arkitekturspecifik assembler. Det är också lättare för framtida underhållare att resonera om range checking och overflow-beteende.

Migreringsråd

  • Använd Pascal-implementeringen när du kontrollerar call site och bara behöver numerisk konvertering.
  • Behåll en assembler-export endast när binär kompatibilitet kräver en specifik symbol och calling convention.
  • Kopiera inte 32-bitars registerkod till en 64-bitarsrutin utan att granska calling convention och databredd.
  • Testa randfall som negativa värden, stora värden och värden utanför målets heltalsintervall.

Intervall och beteende

Att ersätta assembler med Trunc bör fortfarande behandlas som en beteendeförändring som kräver tester. Bekräfta hur koden ska hantera negativa tal, bråk nära en heltalsgräns och värden utanför intervallet för Integer. Den gamla assemblern kan ha berott på FPU control-word-tillstånd, medan Pascal-versionen följer RTL-implementeringen för målkompilatorn.

Om hjälparen sitter i en PDF- eller grafikkomponent, testa utdata som använder roterad text, skalade koordinater och transformerade ritkommandon. De områdena visar subtila skillnader i float-till-integer-konvertering oftare än enkla exempel.

Rekommenderad migreringsväg

Ersätt först privata konverteringshjälpare med tydlig Pascal-kod där det är möjligt. Behåll sedan den ursprungliga 32-bitarsimplementeringen endast bakom ett 32-bitarsvillkor om den fortfarande behövs. Lägg till en separat 64-bitarsgren bara när ett uppmätt prestanda- eller binärkompatibilitetskrav motiverar det. Det håller den portabla vägen enkel och gör den arkitekturspecifika vägen till ett undantag snarare än standard.