技術記事

Delphi の _ftol と 64 ビット Inline ASM の変更

古い 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 ビット アセンブリ ルーチンをアセンブルできますが、そのルーチンは 64 ビット呼び出し規約を意識した完全なアセンブラ ルーチンとして書く必要があります。32 ビットのインライン アセンブラを Pascal 関数本体へコピーして、レジスタ使用、スタック レイアウト、x87 の動作をコンパイラが変換してくれるとは期待できません。

完全なアセンブラ版は可能であり、次のようなコードは、値がすでに FPU スタック上にある場合に必要になる可能性がある control-word 処理の種類を示しています。

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 を 0 方向へ切り捨てて整数値を返すことだけなら、明確で移植性のある版は単純に次のとおりです。

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

Trunc は意図を直接表し、32 ビットと 64 ビットの両方のターゲットでコンパイルでき、アーキテクチャ固有アセンブラの保守コストを避けられます。また、来の保守担当者が範囲チェックやオーバーフロー動作を判断しやすくなります。

移行ガイド

  • 呼び出し側を制御でき、必要なのが数値変換だけの場合は Pascal 実装を使います。
  • 特定のシンボルと呼び出し規約がバイナリ互換性に必要な場合に限り、アセンブラ エクスポートを残します。
  • 呼び出し規約とデータ幅を確認せずに、32 ビットのレジスタ コードを 64 ビット ルーチンへコピーしないでください。
  • 負の値、大きな値、対象整数範囲外の値などの境界ケースをテストします。

範囲と動作に関する考慮

アセンブラを Trunc へ置き換える場合も、テストすべき動作変更として扱う必要があります。負数、整数境界付近の小数、 Integerの範囲外の値について、コードがどう振る舞うべきかを確認してください。古いアセンブラは FPU control-word 状態に依存していた可能性があり、Pascal 版は対象コンパイラの RTL 実装に従います。

ヘルパーが PDF またはグラフィックス コンポーネント内にある場合は、回転テキスト、拡大縮小された座標、変換された描画コマンドを使う出力をテストしてください。こうした領域は、単純な例よりも浮動小数点から整数への変換差を露呈しやすいです。

推奨される移行手順

まず、可能な場所では private な変換ヘルパーを明確な Pascal コードへ置き換えます。次に、必要な場合に限り元の 32 ビット実装を 32 ビット条件付きの背後に残します。最後に、測定済みの性能要件またはバイナリ互換性要件がある場合だけ、別の 64 ビット ブランチを追加します。これにより移植可能な経路を単純に保ち、アーキテクチャ固有の経路を既定ではなく例外にできます。