Articolo tecnico

Portare l’assembly inline Delphi _ftol alle build a 64 bit

· Programmazione Delphi

Il vecchio codice Delphi a volte includeva un piccolo helper _ftol per convertire un valore in virgola mobile in un intero. Nei progetti a 32 bit era comune scrivere questo tipo di helper con assembly inline, soprattutto in codice grafico e PDF ottimizzato per compilatori più vecchi.

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;

Questo approccio si rompe quando la stessa unità viene compilata per un target a 64 bit. Il compilatore Delphi a 64 bit segnala E1025 Unsupported language feature: 'ASM' perché DCC64 non supporta Pascal misto ad assembly inline dentro normali routine Pascal.

Perché il compilatore a 64 bit lo rifiuta

DCC64 può assemblare routine assembly a 64 bit, ma la routine deve essere scritta come routine assembler completa tenendo conto della convenzione di chiamata a 64 bit. Non puoi copiare assembly inline a 32 bit nel corpo di una funzione Pascal e aspettarti che il compilatore traduca uso dei registri, layout dello stack o comportamento x87.

Una versione assembler completa è possibile, e codice come il seguente mostra il tipo di gestione della control word che può servire quando il valore è già sullo stack FPU.

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;

Preferire Pascal quando l’obiettivo è solo troncare

Per la maggior parte del codice applicativo, la routine assembler è inutile. Se il requisito reale è troncare un Double verso zero e restituire un intero, la versione chiara e portabile è semplicemente:

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

Trunc esprime direttamente l’intento, compila per target a 32 e 64 bit ed evita il costo di manutenzione di assembly specifico dell’architettura. Inoltre è più facile per i futuri maintainer ragionare su controlli di range e comportamento di overflow.

Guida alla migrazione

  • Usa l’implementazione Pascal quando controlli il punto di chiamata e ti serve solo la conversione numerica.
  • Mantieni un export assembler solo quando la compatibilità binaria richiede uno specifico simbolo e una specifica convenzione di chiamata.
  • Non copiare codice di registri a 32 bit in una routine a 64 bit senza rivedere convenzione di chiamata e larghezza dei dati.
  • Testa casi limite come valori negativi, valori grandi e valori fuori dall’intervallo dell’intero di destinazione.

Considerazioni su range e comportamento

Sostituire assembler con Trunc va comunque trattato come un cambio di comportamento che merita test. Conferma come deve comportarsi il codice con numeri negativi, frazioni vicine a un limite intero e valori fuori dall’intervallo di Integer. Il vecchio assembler poteva dipendere dallo stato della control word FPU, mentre la versione Pascal segue l’implementazione RTL del compilatore di destinazione.

Se l’helper si trova dentro un componente PDF o grafico, testa output con testo ruotato, coordinate scalate e comandi di disegno trasformati. Queste aree hanno più probabilità di rivelare sottili differenze nella conversione da floating point a intero rispetto a esempi semplici.

Percorso di migrazione consigliato

Per prima cosa, sostituisci gli helper privati di conversione con codice Pascal chiaro dove possibile. Secondo, mantieni l’implementazione originale a 32 bit solo dietro una condizione a 32 bit se è ancora necessaria. Terzo, aggiungi un ramo separato a 64 bit solo quando un requisito misurato di prestazioni o compatibilità binaria lo giustifica. Così il percorso portabile resta semplice e quello specifico dell’architettura diventa l’eccezione invece del default.