3

我想知道 JS_CANONICALIZE_NAN 的目的是什么,是否所有平台都需要它?

4

1 回答 1

8

这是一个有趣的!因此,SpiderMonkey 在内部使用标记值表示来表示 JavScript 的“无类型值”——这允许 VM 确定诸如“存储的变量a是数字,存储的值b是数字,所以运行a + b会进行数字加法”之类的事情.

有许多不同的值标记方案,SpiderMonkey 使用一种称为“NaN 装箱”的方案。这意味着引擎中的所有无类型值都由 64 位值表示,这些值可以是:

  • 双倍,或
  • 一个标记的非双精度,位于 IEEE 双精度浮点值的“NaN 空间”中。

这里真正的诀窍是现代系统通常使用单个位模式来表示 NaN,您可以将其作为 math.h'ssqrt(-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)

请注意,安静 NaN 的唯一区别在于符号位,后跟 12 位 1,满足上述 NaN 要求。最后一个,信号 NaN,清除第 12 个(is_quiet)NaN 位并启用第 13 个以保持上述 NaN 不变。

除此之外,NaN 空间可以自由使用——11 位来填充指数,确保有效位非零,并且你还有很多空间。在 x64 上,我们使用 47 位虚拟地址假设,这为我们留下了64 - 47 - 11 = 6用于注释值类型的位。在 x86 上,所有对象指针都适合低 32 位。

但是,我们仍然需要确保非规范的 NaN,如果它们通过 js-ctypes 之类的东西潜入,不会产生看起来像标记的非双精度值的东西,因为这可能导致 VM 中的可利用行为。(将数字视为对象是非常糟糕的消息。)因此,当我们形成双精度数时(如在 DOUBLE_TO_JSVAL 中),我们确保将所有双精度数d != d规范化为规范的 NaN 形式。

更多信息在错误 584168中。

于 2012-02-26T10:55:59.570 回答