Intel SDMの誤植を直してもらった

Intelはコンピューターの頭脳、CPUを作っている会社です。CPUで動作するプログラムを書くためには、プログラムを低レベルな命令の列に変換し、その命令の列を特定の規則に従って変換したバイナリー(機械語)を用意しなければなりません。IntelのCPUが提供する命令や機械語について、Intelが用意しているマニュアルがIntel 64 and IA-32 Architectures Software Developer’s Manual、略してIntel SDMです。

Intel SDMの最新版は「Intel® 64 and IA-32 Architectures Software Developer Manuals」から入手できます。全部合わせて5000ページ以上あります。

Intel SDMも人間が書いているマニュアルなので、時々誤植等があります。容易にそれとわかる誤植なら可愛いものですが、時にはわかりにくいものもあります。

December 2024の版における、CVTSS2SI命令の説明を見てみましょう。これは単精度浮動小数点数(Scalar Single-precision)を符号付き整数(Signed Integer)に変換する命令です。

Description
Converts a single precision floating-point value in the source operand (the second operand) to a signed integer in the destination operand (the first operand). The source operand can be an XMM register or a memory location. The destination operand is a general-purpose register. When the source operand is an XMM register, the single precision floating-point value is contained in the low doubleword of the register.
When a conversion is inexact, the value returned is rounded according to the rounding control bits in the MXCSR register or the embedded rounding control bits. If a converted result cannot be represented in the destination format, the floating-point invalid exception is raised, and if this exception is masked, the indefinite integer value (2w-1, where w represents the number of bits in the destination format) is returned.

CVTSS2SI, Intel® 64 and IA-32 Architectures Software Developer’s Manual, December 2024

おかしいところに気づけたでしょうか?気づけた人がいたとしたら、その人は「x86における浮動小数点数の常識」というものをよく理解している人だと思います。

クイズでもないので答えを書くと、「丸めによって整数値に変換した値を、結果の整数型(32ビットまたは64ビット)で表現できなかった場合で、しかも例外が抑制された場合」に返すビットパターンが間違っています。December 2024版では2w-1とありますが、2w-1が正しいのです。

x86では浮動小数点数を整数に変換できなかった場合は整数不定値(indefinite integer)と呼ばれる値を返すことが多く、それが符号付きだと 0x80000000 のような値(2w-1;符号付きなので値としては負ですが、ここではmod 2wで考えることにします)で、符号なしだと 0xFFFFFFFF のような値(2w-1)なんですね。

他のページを見ると、表現できなかった場合の値を2w-1とする説明も見つかるので、やはり誤植の可能性が高いです。

実際の動作を確認しましょう。次のC言語のプログラムをx86のマシンで動かしてみます:

#include <math.h>
#include <stdio.h>

__attribute__((noinline))
long f(float x)
{
    return lrintf(x);
}

int main(void)
{
    printf("0x%lx\n", (unsigned long)f(INFINITY));
}

objdump -d a.out の結果は

0000000100000f60 <_f>:
100000f60: 55                          	pushq	%rbp
100000f61: 48 89 e5                    	movq	%rsp, %rbp
100000f64: f3 48 0f 2d c0              	cvtss2si	%xmm0, %rax
100000f69: 5d                          	popq	%rbp
100000f6a: c3                          	retq
100000f6b: 0f 1f 44 00 00              	nopl	(%rax,%rax)

となり、CVTSS2SI 命令を使っていることがわかります。そして、実行結果は

$ clang -O2 -Wall test.c
$ ./a.out               
0x8000000000000000

となります。0xFFFFFFFFFFFFFFFF ではありません!(Intel Macで動作確認)

この間違いには、「Binary Hacks Rebooted」の執筆をしているときに気がつきました。それからしばらく放置していましたが、去年の10月に思い立ってIntelに報告しました:

The indefinite integer value for CVTSS2SI in SDM Volume 2 – Intel Community

2ヶ月ほど放置を食らっていましたが、12月にIntelの中の人から反応があり、それからはメールでやり取りしていました。メールの文面はものすごく丁寧で、大企業を感じました。ちょっと話が通じにくいなと感じる時もありましたが……。

修正はDecember 2024版には間に合いませんでしたが、「March 2025版で直します」という旨の連絡が来ました。そういうわけで、3月は時々Intel SDMのページを見に行っていましたが、更新されないまま4月になりました。が、ようやく今回更新されたバージョンが出たというわけです。

March 2025版のCVTSS2SI命令の説明を見てみましょう:

記述が変わっているのがわかりますね。

ここの記述が間違っていると、例えばx86互換プロセッサーやエミュレーターを作っている人が勘違いする可能性がありました。まあ、そういう人はこの記述に違和感を持って実機の動作を確認するでしょうし、実害はなかったでしょう。AppleのRosetta 2も正しい動作でエミュレーションしています。

それでも、勘違いが発生する可能性を少しでも下げるためには、やはり修正してもらって正解だったと思います。

Spread the love