Starszy kod Delphi czasem zawierał mały helper _ftol do konwersji wartości zmiennoprzecinkowej na liczbę całkowitą. W projektach 32-bitowych często pisano taki helper z użyciem inline assemblera, zwłaszcza w kodzie graficznym i PDF dostrajanym pod starsze kompilatory.
|
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; |
To podejście przestaje działać, gdy ta sama jednostka jest kompilowana dla celu 64-bitowego. Kompilator Delphi 64-bit zgłasza E1025 Unsupported language feature: 'ASM', ponieważ DCC64 nie obsługuje mieszania Pascala i inline assembly wewnątrz zwykłych procedur Pascala.
Dlaczego kompilator 64-bitowy to odrzuca
DCC64 może asemblować 64-bitowe procedury assembly, ale procedura musi być napisana jako kompletna procedura assemblera z uwzględnieniem 64-bitowej calling convention. Nie można skopiować 32-bitowego inline assemblera do ciała funkcji Pascala i oczekiwać, że kompilator przetłumaczy użycie rejestrów, układ stosu lub zachowanie x87.
Pełna wersja assemblerowa jest możliwa, a kod taki jak poniżej pokazuje rodzaj obsługi control-word, która może być wymagana, gdy wartość znajduje się już na stosie 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; |
Wybierz Pascal, gdy celem jest tylko obcięcie
W większości kodu aplikacji procedura assemblerowa jest niepotrzebna. Jeśli rzeczywistym wymaganiem jest obcięcie Double w kierunku zera i zwrócenie wartości całkowitej, jasna i przenośna wersja to po prostu:
|
1 2 3 4 |
function _ftol(f: double): integer; cdecl; begin Result := Trunc(f); end; |
Trunc wyraża zamiar bezpośrednio, kompiluje się dla celów 32- i 64-bitowych oraz eliminuje koszt utrzymania assemblera zależnego od architektury. Przyszłym opiekunom łatwiej też rozumieć sprawdzanie zakresu i zachowanie overflow.
Wskazówki migracyjne
- Użyj implementacji w Pascalu, gdy kontrolujesz call site i potrzebujesz tylko konwersji liczbowej.
- Zachowaj eksport assemblerowy tylko wtedy, gdy zgodność binarna wymaga konkretnego symbolu i calling convention.
- Nie kopiuj 32-bitowego kodu rejestrowego do procedury 64-bitowej bez przeglądu calling convention i szerokości danych.
- Testuj przypadki brzegowe, takie jak wartości ujemne, duże wartości i wartości poza zakresem docelowej liczby całkowitej.
Zakres i zachowanie
Zastąpienie assemblera przez Trunc nadal należy traktować jako zmianę zachowania wymagającą testów. Potwierdź, jak kod ma obsługiwać liczby ujemne, ułamki blisko granicy liczby całkowitej i wartości poza zakresem Integer. Stary assembler mógł zależeć od stanu FPU control-word, a wersja Pascalowa podąża za implementacją RTL dla docelowego kompilatora.
Jeśli helper znajduje się w komponencie PDF lub graficznym, testuj wyjście używające obróconego tekstu, skalowanych współrzędnych i transformowanych poleceń rysowania. Te obszary częściej ujawniają subtelne różnice konwersji float-to-integer niż proste przykłady.
Zalecana ścieżka migracji
Najpierw zastąp prywatne helpery konwersji jasnym kodem Pascala tam, gdzie to możliwe. Następnie zachowaj oryginalną implementację 32-bitową tylko za warunkiem 32-bitowym, jeśli nadal jest potrzebna. Dodaj osobną gałąź 64-bitową tylko wtedy, gdy uzasadnia to zmierzony wymóg wydajności lub zgodności binarnej. Dzięki temu ścieżka przenośna pozostaje prosta, a ścieżka zależna od architektury jest wyjątkiem, nie domyślnym rozwiązaniem.