TLDR
-
IEEE 754のNaNには最大51ビットの予備ペイロードがあり、動的型付けランタイムはこれを利用して型タグや非float値を単一の64ビットワードに詰め込んでいる。
重要なポイント
-
倍精度NaNには51ビットの使用可能なペイロードがある。IEEE 754規格は「診断情報」の伝搬目的でこれを保持するよう明示的に推奨している。
-
Quiet NaNは例外を起こさず伝搬する。Signaling NaNは例外フラグを発火させ、未初期化変数のセンチネルとして自然に使える。
-
NaN-boxingはすべてのJS値を64ビットにエンコードする。ポインタはビット0〜47、整数は
FFFF:0000:IIII:IIII、doubleは0x0000/0xFFFFの範囲を避けるため2^48シフトする。
-
JavaScriptCore(Safari/WebKit)、SpiderMonkey(nun-boxing/pun-boxing)、LuaJIT(NaN-tagging)はいずれも本番環境でNaN-boxingを採用しており、JSCのソースコメントが最も明快な公開ドキュメントになっている。
-
この手法はx86-64が48ビット仮想アドレスしか使わないことに依存している。将来64ビットフルアドレスのCPUが登場すれば、すべてのNaN-boxing VMはアーキテクチャ上の対策なしに破綻する。
Hacker Newsのコメント考察
-
NaN-boxingの主なランタイム上のメリットはfloatのヒープアロケーション回避によるGC負荷軽減だという点でコメント欄の意見は一致しており、これは単なる技術的な面白さではなく具体的なエンジニアリング上の成果といえる。
-
48ビットポインタの前提が最大の脆弱性として浮上している。ハードウェアが64ビットフルアドレスに移行すれば、アーキテクチャ対策なしにNaN-boxing VMは静かに壊れる。
-
Signaling NaNを使って言語レベルで未初期化変数を検出する使い方についても軽く議論されており、D言語がfloatのデフォルト値をNaNにするセマンティクスの実装例として挙げられている。
注目コメント
-
@WalterBright: DはfloatをデフォルトでNaNに初期化する(0.0ではない)。0.0が静かに隠してしまう未初期化変数バグを検出できる。
-
@GMoromisato: GridWhaleでNaN-boxingを使用。「無限ホテル」に例えており、型タグはいくらでも追加でき、GC時のfloatアロケーション削減が実践的な見返りだと説明している。
原文 | HNで議論する
英語版: The Secret Life of NaN (2018) · Original source