技術文章

將 Delphi _ftol 內聯彙編遷移到 64 位構建

· Delphi 程式設計

較舊的 Delphi 程式碼有時會包含一個小型 _ftol 輔助函式,用於把浮點值轉換為整數。在 32 位專案中,這類輔助函式常用內聯彙編實現,尤其常見於為舊編譯器調優過的圖形和 PDF 程式碼。

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;

同一個單元編譯為 64 位目標時,這種寫法會失敗。Delphi 64 位編譯器會報告 E1025 Unsupported language feature: 'ASM',因為 DCC64 不支援在普通 Pascal 例程內部混合 Pascal 和內聯彙編。

為什麼 64 位編譯器會拒絕

DCC64 可以彙編 64 位彙編例程,但例程必須作為完整的 assembler routine 編寫,並遵守 64 位呼叫約定。不能把 32 位內聯彙編複製到 Pascal 函式體裡,指望編譯器自動轉換暫存器使用、棧佈局或 x87 行為。

完整彙編版本是可能的,下面的程式碼展示了當值已經位於 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;

只需要截斷時優先使用 Pascal

對大多數應用程式碼來說,彙編例程並不必要。如果真實需求只是把 Double 向零截斷並返回整數值,清晰且可移植的版本就是:

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

Trunc 直接表達意圖,可同時編譯到 32 位和 64 位目標,並避免維護架構相關彙編的成本。後續維護者也更容易理解範圍檢查和溢位行為。

範圍和行為注意事項

Trunc 替換匯編仍應被視為需要測試的行為變更。請確認負數、接近整數邊界的小數,以及超出 Integer 範圍的值應該如何處理。舊彙編可能依賴 FPU 控制字狀態,而 Pascal 版本遵循目標編譯器的 RTL 實現。

如果該輔助函式位於 PDF 或圖形元件中,請測試旋轉文字、縮放座標和變換後的繪製命令。這些區域比簡單示例更容易暴露浮點到整數轉換的細微差異。

推薦遷移路徑

  • 如果只需要數值轉換,優先使用清晰的 Pascal 實現。
  • 只有在二進位制相容性需要特定符號和呼叫約定時,才保留彙編匯出。
  • 不要在沒有審查呼叫約定和資料寬度的情況下複製 32 位暫存器程式碼。
  • 測試負數、大數以及超出目標整數範圍的值。