Pôvodný idióm _ftol v 32-bitovom Delphi vyzerá ako šikovný jednoriadkový príkaz: obal (wrapper) funkcie v Pascale, ktorý prejde do inline assembleru, aby manipuloval s riadiacim slovom FPU x87, skrátil hodnotu v zásobníku FPU a vybral výsledok. Pod DCC32 sa dlho skompilovával bez problémov, čo je presne ten dôvod, prečo skončil v toľkých starších grafických a PDF unitách bez toho, aby to niekto spochybňoval
Prepnite cieľ zostavenia na 64-bitový a prekladač sa ukončí s chybou E1025 Unsupported language feature: 'ASM'. Táto chyba nie je varovaním o kompatibilite. Znamená to, že DCC64 rutinu vôbec neskompiluje bez ohľadu na to, ako dobre ten assembler fungoval predtým
32-bitový originál zvyčajne vyzeral nejako takto:
function _ftol(f: Double): Integer; cdecl;
begin
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
end;
Tento blok asm vo vnútri tela begin...end v Pascale je presne to, čo DCC64 odmieta. Dva prekladače majú odlišné pravidlá o tom, kde je assembler povolený, a na hranici záleží
Prečo DCC64 kreslí čiaru inak
DCC32 povoľuje inline assembler v bežných rutinách Pascalu. Prekladač pozná 32-bitovú konvenciu volania a dokáže usúdiť, kde žijú lokálne premenné a parametre, takže toleruje fragmenty assembleru, ktoré siahajú do rámca zásobníka podľa mena. DCC64 zaujíma prísnejšiu pozíciu: assembler musí byť vo vyhradenej funkcii assembleru, v takej, kde celé telo je v assembleri a konvencia volania sa spravuje explicitne. Zmiešaný kód Pascal-plus-asm nie je podporovaný vôbec
Základný dôvod je architektonický. V 64-bitovej konvencii volania Windows (Microsoft ABI) prichádzajú prvé štyri parametre v RCX, RDX, R8 a R9 pre celočíselné typy alebo v XMM0 až XMM3 pre pohyblivú rádovú čiarku. Pri bežnom odovzdávaní parametrov nie je zapojený x87 FPU; x87 je technicky k dispozícii, ale ABI ho na prenos argumentov nepoužíva. Assembler, ktorý predpokladá, že hodnota je „na zásobníku FPU“, uvažuje o stave, ktorý 64-bitové ABI nikdy nevytvára
Takže starý fragment nemá len problém so syntaxou. Aj keby ho DCC64 prijal, predpoklady o registroch by boli nesprávne
Písanie správnej verzie pre 64-bitový assembler
Keď skutočne potrebujete exportovať symbol _ftol s konvenciou cdecl pre binárnu kompatibilitu, funkcia musí byť napísaná ako čisto assemblerová rutina. V 64-bitovom ABI dorazí parameter Double v XMM0 a celočíselný výsledok musí byť pri návrate v RAX. Direktíva .NOFRAME hovorí DCC64, že rutina spravuje svoj vlastný zásobník, čo je vhodné pre koncovú (leaf) funkciu, ktorá je takáto krátka:
function _ftol: Integer; cdecl;
// Hodnota Double sa očakáva v XMM0 podľa 64-bitového ABI
asm
.NOFRAME
cvttsd2si rax, xmm0 // zrezať na celé číslo, výsledok v rax
end;
CVTTSD2SI je inštrukcia SSE2 pre konverziu čísla s pohyblivou rádovou čiarkou s dvojitou presnosťou na celé číslo so znamienkom so zrezaním k nule, čo je presne to, čo by malo _ftol urobiť. Je to jedna inštrukcia, preberá parameter priamo tam, kde ho ABI zanechalo, a umiestni výsledok tam, kde ho ABI očakáva. Nie je potrebné žiadne žonglovanie s riadiacim slovom FPU
Všimnite si, že ak vstup prekročí rozsah 32-bitového celého čísla so znamienkom, CVTTSD2SI vráti neurčitú hodnotu celého čísla ($80000000). Ide o rovnaké správanie ako v prípade x87 fistp pre vstup mimo rozsahu. To, či vaši volajúci môžu produkovať takéto hodnoty, stojí za potvrdenie predtým, ako vyhlásite migráciu za ukončenú
Keď je Trunc lepšou odpoveďou
Vyššie uvedenú verziu v assembleri sa oplatí písať len vtedy, keď máte skutočnú požiadavku na binárnu kompatibilitu: nejaký externý volajúci očakáva symbol _ftol so špecifickou konvenciou volania a týchto volajúcich nemôžete zmeniť. Táto situácia je neobvyklá. Väčšinu času bol _ftol privátnym pomocníkom (private helper) používaným len v rámci tej istej unity a na jeho mene alebo konvencii vôbec nezáležalo
V takom prípade ho nahraďte čistým Pascalom:
function _ftol(f: Double): Integer; cdecl;
begin
Result := Trunc(f);
end;
Trunc orezáva smerom k nule, čo zodpovedá tomu, čo _ftol robilo s riadiacim slovom x87 nastaveným na režim zrezania. Na DCC32 a DCC64 sa skompiluje bez úprav. Prekladač vygeneruje pre každý cieľ zodpovedajúcu inštrukciu: na platforme x64 zvyčajne v každom prípade emituje CVTTSD2SI, teda rovnakú inštrukciu ako ručne písaná verzia. Dostanete rovnaké správanie, žiadne podmienky pre platformu (platform conditionals) a žiadny assembler na údržbu
Jeden sémantický rozdiel, ktorý stojí za kontrolu: Trunc vyvolá v predvolenej konfigurácii Delphi výnimku EInvalidOp, keď je vstupná hodnota NaN alebo nekonečno. x87 fistp v pôvodnom kóde len zapísal bitový vzor bez toho, aby niečo vyvolal. Ak váš kód odovzdáva do tejto funkcie neobvyklé hodnoty s pohyblivou rádovou čiarkou a staré správanie bolo tiché, ošetrite to pomocou IsNaN a IsInfinite z jednotky (unitu) Math pred zavolaním Trunc
Podmienený preklad (conditional compilation), keď oba ciele zostávajú aktívne
Niektoré projekty musia pokračovať v distribúcii oboch, 32-bitových a 64-bitových binárok. Ak sa pôvodná verzia v assembleri musí zachovať pre 32-bitovú platformu a pre 64-bitovú je k dispozícii nová implementácia, použite podmienku CPUX64:
function _ftol(f: Double): Integer; cdecl;
begin
{$IFDEF CPUX64}
Result := Trunc(f);
{$ELSE}
// 32-bitová cesta: DCC32 akceptuje inline asm
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
{$ENDIF}
end;
Je to minimálna mechanická oprava a oplatí sa k nej pristupovať ako k dočasnej. Báza kódu, ktorá nesie assembler špecifický pre architektúru v pomocníkovi, ktorého jediným účelom je zrezanie čísla s pohyblivou rádovou čiarkou na celé číslo (float-to-integer), nesie zbytočný dlh. 32-bitová vetva môže úplne zmiznúť, akonáhle potvrdíte, že nič nezávisí od vedľajších účinkov FPU v starej implementácii
Ak sa funkcia objavuje v komponente, ktorý sa používa naprieč viacerými unitami, pred rozhodnutím o migrácii vyhľadajte v celej báze kódu _ftol. Symbol s takýmto názvom môže byť deklarovaný na viacerých miestach; linker vyberie jeden a potichu ignoruje ostatné, čo znamená, že môžete opraviť jednu kópiu a stále sa linkovať proti inej, ktorej sa nikto nedotkol