Technisch artikel

Delphi _ftol inline assembly naar 64-bit builds porten

· Delphi Programmering

Oudere Delphi-code bevatte soms een kleine _ftol-helper om een floating-pointwaarde naar een integer te converteren. In 32-bit projecten was het gebruikelijk om zo'n helper met inline assembler te schrijven, vooral in grafische en PDF-code die voor oudere compilers was geoptimaliseerd.

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;

Die aanpak breekt wanneer dezelfde unit voor een 64-bit target wordt gecompileerd. De Delphi 64-bit compiler meldt E1025 Unsupported language feature: 'ASM' omdat DCC64 geen gemengde Pascal en inline assembly binnen normale Pascal-routines ondersteunt.

Waarom de 64-bit compiler dit weigert

DCC64 kan 64-bit assembly-routines assembleren, maar de routine moet als volledige assembler-routine worden geschreven met de 64-bit calling convention in gedachten. U kunt 32-bit inline assembler niet in een Pascal-functiebody kopiëren en verwachten dat de compiler registergebruik, stackindeling of x87-gedrag vertaalt.

Een volledige assemblerversie is mogelijk, en code zoals hieronder toont het soort control-word-afhandeling dat nodig kan zijn wanneer de waarde al op de FPU-stack staat.

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;

Geef de voorkeur aan Pascal wanneer alleen truncatie nodig is

Voor de meeste applicatiecode is de assemblerroutine niet nodig. Als de echte eis is om een Double naar nul toe af te kappen en een integerwaarde terug te geven, is de duidelijke en portable versie simpelweg:

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

Trunc drukt de bedoeling direct uit, compileert voor zowel 32-bit als 64-bit targets en vermijdt de onderhoudskosten van architectuurspecifieke assembler. Toekomstige onderhouders kunnen ook eenvoudiger redeneren over range checking en overflowgedrag.

Migratierichtlijnen

  • Gebruik de Pascal-implementatie wanneer u de call site beheert en alleen numerieke conversie nodig hebt.
  • Behoud een assembler-export alleen wanneer binaire compatibiliteit een specifiek symbool en calling convention vereist.
  • Kopieer geen 32-bit registercode naar een 64-bit routine zonder de calling convention en gegevensbreedte te beoordelen.
  • Test randgevallen zoals negatieve waarden, grote waarden en waarden buiten het bereik van het doel-integertype.

Bereik en gedrag

Het vervangen van assembler door Trunc moet nog steeds als een gedragswijziging worden behandeld die tests verdient. Bevestig hoe uw code moet omgaan met negatieve getallen, fracties vlak bij een integergrens en waarden buiten het bereik van Integer. De oude assembler kan afhankelijk zijn geweest van de FPU control-wordstatus, terwijl de Pascal-versie de RTL-implementatie voor de doelcompiler volgt.

Als de helper in een PDF- of grafische component zit, test dan uitvoer met geroteerde tekst, geschaalde coördinaten en getransformeerde tekenopdrachten. Die gebieden tonen subtiele conversieverschillen van float naar integer eerder dan eenvoudige voorbeelden.

Aanbevolen migratiepad

Vervang eerst private conversiehelpers waar mogelijk door duidelijke Pascal-code. Behoud daarna de oorspronkelijke 32-bit implementatie alleen achter een 32-bit conditional als die nog nodig is. Voeg pas een aparte 64-bit branch toe wanneer een gemeten prestatie- of binaire-compatibiliteitseis dat rechtvaardigt. Zo blijft het portable pad eenvoudig en wordt het architectuurspecifieke pad de uitzondering in plaats van de standaard.