какова цель JS_CANONICALIZE_NAN в движке Spidermonkey?

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


person Franck Freiburger    schedule 24.02.2012    source источник


Ответы (1)


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

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

  • двойник, или
  • помеченный недвойник, который находится в «пространстве NaN» значений с плавающей запятой двойной точности IEEE.

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

Дубль состоит из подполей:

{sign: 1, exponent: 11, significand: 52}

NaN представлены путем заполнения поля экспоненты единицами и помещения ненулевого значения в мантиссу.

Если вы запустите небольшую программу, подобную этой, чтобы увидеть значения 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.

person cdleary    schedule 26.02.2012