JS_CANONICALIZE_NANの目的は何であり、すべてのプラットフォームで常に必要かどうか疑問に思います。
1 に答える
これは楽しいものです!そのため、SpiderMonkey はタグ付きの値表現を内部的に使用して、JavScript の「型なし値」を表します。これにより、VM は「格納されている変数a
は数値であり、格納されている値は数値b
であるため、実行するa + b
と数値加算が行われる」などを判断できます。 .
値のタグ付けにはさまざまなスキームがあり、SpiderMonkey は「NaN ボクシング」と呼ばれるものを使用します。これは、エンジン内のすべての型指定されていない値が、次のいずれかの 64 ビット値で表されることを意味します。
- ダブル、または
- IEEE 倍精度浮動小数点値の「NaN 空間」に存在するタグ付きの非 double。
ここでの本当の秘訣は、最近のシステムでは通常、単一のビット パターンを使用して NaN を表すことです。これは、math.h の結果として観察できsqrt(-1)
ますlog(0)
。しかし、IEEE 浮動小数点仕様に従って NaN と見なされるビット パターンも多数あります。
double はサブフィールドで構成されます。
{sign: 1, exponent: 11, significand: 52}
NaN は、指数フィールドを 1 で埋め、仮数にゼロ以外の値を配置することによって表されます。
次のような小さなプログラムを実行して、プラットフォームの NaN 値を確認する場合:
#include <stdio.h>
#include <math.h>
#include <limits>
static unsigned long long
DoubleAsULL(double d) {
return *((unsigned long long *) &d);
}
int main() {
double sqrtNaN = sqrt(-1);
printf("%5f 0x%llx\n", sqrtNaN, DoubleAsULL(sqrtNaN));
double logNaN = log(-1);
printf("%5f 0x%llx\n", logNaN, DoubleAsULL(logNaN));
double compilerNaN = NAN;
printf("%5f 0x%llx\n", compilerNaN, DoubleAsULL(compilerNaN));
double compilerSNAN = std::numeric_limits<double>::signaling_NaN();
printf("%5f 0x%llx\n", compilerSNAN, DoubleAsULL(compilerSNAN));
return 0;
}
次のような出力が表示されます。
-nan 0xfff8000000000000 // Canonical qNaNs...
nan 0x7ff8000000000000
nan 0x7ff8000000000000
nan 0x7ff4000000000000 // sNaN (signaling)
quiet NaN の唯一の違いは符号ビットであり、その後に常に 12 ビットの 1 が続き、上記の NaN 要件を満たしていることに注意してください。最後の信号 NaN は、12 番目 (is_quiet) の NaN ビットをクリアし、13 番目が上記の NaN 不変式を維持できるようにします。
それ以外では、NaN スペースは自由に使用できます。指数を埋めるのに 11 ビット、signficand がゼロでないことを確認し、十分なスペースが残っていることを確認してください。x64 では、47 ビットの仮想アドレスの仮定を使用します。これにより、64 - 47 - 11 = 6
値の型に注釈を付けるためのビットが残ります。x86 では、すべてのオブジェクト ポインターが下位 32 ビットに収まります。
ただし、非正規の NaN が js-ctypes などを介して忍び込む場合、タグ付けされた非 double 値のようなものを生成しないようにする必要があります。これは、VM で悪用可能な動作につながる可能性があるためです。(数値をオブジェクトとして扱うことは非常に悪いニュースです。) したがって、(DOUBLE_TO_JSVAL のように) double を形成するときは、すべての doubled != d
を正規の NaN 形式に正規化するようにします。
詳細はバグ 584168にあります。