Какова цель JS_CANONICALIZE_NAN в движке Spidermonkey? - PullRequest
2 голосов
/ 24 февраля 2012

Мне интересно, какова цель JS_CANONICALIZE_NAN и нужна ли она всегда на всех платформах?

1 Ответ

7 голосов
/ 26 февраля 2012

Это весело! Итак, SpiderMonkey внутренне использует представление теговых значений для представления «нетипизированных значений» JavScript - это позволяет виртуальной машине определять такие вещи, как «переменная, хранящаяся в a, представляет собой число, а значение, хранящееся в b, представляет собой число, так работает a + b делает числовое сложение ".

Существует множество различных схем для маркировки значений, и SpiderMonkey использует такую ​​схему, которая называется "NaN boxing". Это означает, что все нетипизированные значения в движке представлены 64-битными значениями, которые могут быть:

  • двойной или
  • помеченный non-double, который живет в "пространстве NaN" значений с плавающей запятой IEEE двойной точности.

Настоящая хитрость в том, что современные системы обычно используют однобитовый шаблон для представления NaN, который вы можете наблюдать в результате математических операций sqrt(-1) или log(0). но есть много битовых комбинаций, которые также считаются NaN согласно спецификации IEEE с плавающей запятой.

Двойник состоит из подполей:

{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, не производят что-то похожее на помеченные не двойные значения, потому что это может привести к эксплуатируемому поведению в ВМ. (Относиться к числам как к объектам - очень-очень плохая новость.) Поэтому, когда мы формируем двойные числа (как в DOUBLE_TO_JSVAL), мы обязательно канонизируем все двойные числа, где d != d, в каноническую форму NaN.

Больше информации в ошибка 584168 .

...