Idiom _ftol asli di Delphi 32-bit terlihat seperti satu baris yang cerdas: wrapper fungsi Pascal yang turun ke inline assembly untuk memanipulasi kata kontrol FPU x87, memotong nilai di tumpukan FPU, dan memunculkan hasilnya. Ini berhasil dikompilasi di bawah DCC32 cukup lama, yang persis mengapa ia berakhir di begitu banyak unit grafis dan PDF lama tanpa ada yang mempertanyakannya
Alihkan target build ke 64-bit dan kompiler berhenti dengan E1025 Unsupported language feature: 'ASM'. Kesalahan itu bukan peringatan kompatibilitas. Artinya DCC64 tidak akan mengompilasi rutinitas tersebut sama sekali, terlepas dari seberapa baik assembly bekerja sebelumnya
Versi 32-bit asli biasanya terlihat seperti ini:
function _ftol(f: Double): Integer; cdecl;
begin
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
end;
Blok asm di dalam tubuh begin...end Pascal itulah yang ditolak DCC64. Kedua kompiler memiliki aturan berbeda tentang di mana assembly diizinkan, dan batasnya penting
Mengapa DCC64 menarik garis yang berbeda
DCC32 mengizinkan inline assembly di dalam rutinitas Pascal biasa. Kompiler mengetahui konvensi pemanggilan 32-bit dan dapat memahami di mana variabel lokal dan parameter berada, sehingga ia mentolerir fragmen assembly yang menjangkau ke dalam stack frame berdasarkan nama. DCC64 mengambil posisi yang lebih ketat: assembly harus berada dalam fungsi assembler khusus, di mana seluruh tubuhnya adalah assembly dan konvensi pemanggilan ditangani secara eksplisit. Pascal-plus-asm campuran tidak didukung sama sekali
Alasan mendasarnya adalah arsitektural. Dalam konvensi pemanggilan Windows 64-bit (Microsoft ABI), empat parameter pertama tiba di RCX, RDX, R8, dan R9 untuk tipe integer, atau di XMM0 hingga XMM3 untuk floating-point. Tidak ada keterlibatan FPU x87 dalam penerusan parameter normal; x87 secara teknis tersedia tetapi ABI tidak menggunakannya untuk transport argumen. Assembly yang mengasumsikan nilai "ada di tumpukan FPU" sedang menalar tentang keadaan yang tidak pernah dibuat ABI 64-bit
Jadi fragmen lama itu tidak hanya memiliki masalah sintaks. Bahkan jika DCC64 menerimanya, asumsi register akan salah
Menulis versi assembler 64-bit yang benar
Ketika Anda benar-benar perlu mengekspor simbol _ftol dengan konvensi cdecl untuk kompatibilitas biner, fungsi tersebut harus ditulis sebagai rutinitas assembler murni. Dalam ABI 64-bit, parameter Double tiba di XMM0, dan hasil integer harus berada di RAX saat kembali. Direktif .NOFRAME memberi tahu DCC64 bahwa rutinitas mengelola tumpukannya sendiri, yang sesuai untuk fungsi daun yang pendek ini:
function _ftol: Integer; cdecl;
// Double value expected in XMM0 per 64-bit ABI
asm
.NOFRAME
cvttsd2si rax, xmm0 // truncate-to-integer, result in rax
end;
CVTTSD2SI adalah instruksi SSE2 untuk mengonversi float presisi ganda ke integer bertanda dengan pemotongan menuju nol, yang persis seperti yang seharusnya dilakukan _ftol. Ini adalah satu instruksi, mengambil parameter langsung dari tempat ABI meninggalkannya, dan menempatkan hasil di tempat ABI mengharapkannya. Tidak perlu juggling kata kontrol FPU
Perhatikan bahwa jika input melebihi rentang integer bertanda 32-bit, CVTTSD2SI mengembalikan nilai integer indefinite ($80000000). Itu adalah perilaku yang sama seperti fistp x87 pada input di luar rentang. Apakah pemanggil Anda dapat menghasilkan nilai seperti itu layak dikonfirmasi sebelum mendeklarasikan migrasi selesai
Kapan Trunc adalah jawaban yang lebih baik
Versi assembler di atas hanya layak ditulis ketika Anda memiliki persyaratan kompatibilitas biner yang nyata: beberapa pemanggil eksternal mengharapkan simbol _ftol dengan konvensi pemanggilan tertentu, dan Anda tidak dapat mengubah pemanggil tersebut. Situasi itu tidak umum. Sebagian besar waktu, _ftol adalah helper privat yang hanya digunakan dalam unit yang sama, dan tidak ada ketergantungan eksternal pada nama atau konvensinya
Untuk kasus itu, gantikan dengan Pascal biasa:
function _ftol(f: Double): Integer; cdecl;
begin
Result := Trunc(f);
end;
Trunc memotong menuju nol, yang cocok dengan apa yang dilakukan _ftol dengan kata kontrol x87 yang disetel ke mode pemotongan. Ini dikompilasi pada DCC32 dan DCC64 tanpa modifikasi. Kompiler menghasilkan instruksi yang sesuai untuk setiap target: pada x64 biasanya akan memancarkan CVTTSD2SI juga, instruksi yang sama seperti versi yang ditulis tangan. Anda mendapatkan perilaku identik, tidak ada kondisional platform, dan tidak ada assembly yang perlu dirawat
Satu perbedaan semantik yang perlu diperiksa: Trunc memunculkan exception EInvalidOp dalam konfigurasi default Delphi ketika input adalah NaN atau infinity. fistp x87 dalam kode asli hanya menulis pola bit tanpa memunculkan apa pun. Jika kode Anda memberi nilai floating-point yang tidak biasa ke fungsi ini dan perilaku lama tidak bersuara, lindungi dengan IsNaN dan IsInfinite dari Math sebelum memanggil Trunc
Kompilasi kondisional ketika kedua target tetap aktif
Beberapa proyek harus terus mengirimkan binary 32-bit dan 64-bit. Jika versi assembler asli harus dipertahankan untuk 32-bit dan implementasi baru disediakan untuk 64-bit, gunakan kondisional CPUX64:
function _ftol(f: Double): Integer; cdecl;
begin
{$IFDEF CPUX64}
Result := Trunc(f);
{$ELSE}
// 32-bit path: DCC32 accepts inline asm
asm
lea eax, f
fstp qword ptr [eax]
end;
Result := Trunc(f);
{$ENDIF}
end;
Itu adalah perbaikan mekanis minimal, dan layak diperlakukan sebagai sementara. Codebase yang membawa assembly khusus arsitektur dalam helper yang satu-satunya tujuannya adalah pemotongan float-ke-integer membawa hutang yang tidak perlu. Cabang 32-bit dapat dihapus sepenuhnya setelah Anda mengonfirmasi tidak ada yang bergantung pada efek samping FPU dari implementasi lama
Jika fungsi muncul di komponen yang digunakan di beberapa unit, cari seluruh codebase untuk _ftol sebelum memutuskan cara migrasi. Simbol dengan nama itu dapat dideklarasikan di lebih dari satu tempat; linker memilih satu dan diam-diam mengabaikan yang lain, yang berarti Anda mungkin memperbaiki satu salinan dan masih menautkan ke salinan lain yang belum disentuh