Commit 3f82636a authored by Benjamin Bouvier's avatar Benjamin Bouvier
Browse files

Bug 1415224: Remove wasm testing mode for custom NaN payloads; r=luke

MozReview-Commit-ID: 1CB9zUkTIyk

--HG--
extra : rebase_source : 2b00b600b4607fff066ef2d1e8dae6f523a410fb
parent d9ca9fae
Loading
Loading
Loading
Loading
+43 −26
Original line number Diff line number Diff line
@@ -50,6 +50,49 @@ function jsify(wasmVal) {
    return wasmVal;
}

function makeF32Assert(i, func, expected, params=[]) {
    return `
    (func (export "assert_${i}") (result i32)
     ${ params.join('\n') }
     call ${func}
     i32.reinterpret/f32
     i32.const ${expected}
     i32.eq
    )`;
}

function makeF64Assert(i, func, expected, params=[]) {
    return `
    (func (export "assert_${i}") (result i32)
     ${ params.join('\n') }
     call ${func}
     i64.reinterpret/f64
     i64.const ${expected}
     i64.eq
    )`;
}

function wasmAssert(src, assertions) {
    let newSrc = src.substr(0, src.lastIndexOf(')'));
    let i = 0;
    for (let a of assertions) {
        if (a.type === 'f32')
            newSrc += makeF32Assert(i++, a.func, a.expected, a.params);
        if (a.type === 'f64')
            newSrc += makeF64Assert(i++, a.func, a.expected, a.params);
    }
    newSrc += ')';

    let { exports } = wasmEvalText(newSrc);

    for (let j = 0; j < i; j++) {
        let { func, expected, params } = assertions[j];
        let paramText = params ? params.join(', ') : '';
        assertEq(exports['assert_' + j](), 1,
                 `Unexpected value when running ${func}(${paramText}), expecting ${expected}.`);
    }
}

// Assert that the expected value is equal to the int64 value, as passed by
// Baldr: {low: int32, high: int32}.
// - if the expected value is in the int32 range, it can be just a number.
@@ -72,32 +115,6 @@ function assertEqI64(observed, expect) {
    }
}

// Asserts in Baldr test mode that NaN payloads match.
function assertEqNaN(x, y) {
    if (typeof x === 'number') {
        assertEq(Number.isNaN(x), Number.isNaN(y));
        return;
    }

    assertEq(typeof x === 'object' &&
             typeof x.nan_low === 'number',
             true,
             "assertEqNaN args must have shape {nan_high, nan_low}");

    assertEq(typeof y === 'object' &&
             typeof y.nan_low === 'number',
             true,
             "assertEqNaN args must have shape {nan_high, nan_low}");

    assertEq(typeof x.nan_high,
             typeof y.nan_high,
             "both args must have nan_high, or none");

    assertEq(x.nan_high, y.nan_high, "assertEqNaN nan_high don't match");
    if (typeof x.nan_low !== 'undefined')
        assertEq(x.nan_low, y.nan_low, "assertEqNaN nan_low don't match");
}

function createI64(val) {
    let ret;
    if (typeof val === 'number') {
+79 −79
Original line number Diff line number Diff line
@@ -36,9 +36,6 @@ assertSameBitPattern(0, 8, 8);

wasmEvalText('(module (import "" "float64" (param f64)) (func (call 0 (f64.const nan:0x123456))) (export "" 0))', checkBitPatterns).exports[""]();

// Enable test mode.
setJitCompilerOption('wasm.test-mode', 1);

// SANITY CHECKS

// There are two kinds of NaNs: signaling and quiet. Usually, the first bit of
@@ -48,83 +45,86 @@ setJitCompilerOption('wasm.test-mode', 1);

// A float32 has 32 bits, 23 bits of them being reserved for the mantissa
// (= NaN payload).
var f32_nan_base = 0x7f800000;

var f32_qnan_code = '(f32.const nan:0x600000)';
var f32_snan_code = '(f32.const nan:0x200000)';
var f32_snan = wasmEvalText(`(module (func (result f32) ${f32_snan_code}) (export "" 0))`).exports[""]();
assertEqNaN(f32_snan, { nan_low: f32_nan_base | 0x200000 });

var f32_qnan_code = '(f32.const nan:0x600000)';
var f32_qnan = wasmEvalText(`(module (func (result f32) ${f32_qnan_code}) (export "" 0))`).exports[""]();
assertEqNaN(f32_qnan, { nan_low: f32_nan_base | 0x600000 });
var f32_snan = '0x7fa00000';
var f32_qnan = '0x7fe00000';

// A float64 has 64 bits, 1 for the sign, 11 for the exponent, the rest for the
// mantissa (payload).
var f64_nan_base_high = 0x7ff00000;

var f64_snan_code = '(f64.const nan:0x4000000000000)';
var f64_snan = wasmEvalText(`(module (func (result f64) ${f64_snan_code}) (export "" 0))`).exports[""]();
assertEqNaN(f64_snan, { nan_low: 0x0, nan_high: f64_nan_base_high | 0x40000 });

var f64_qnan_code = '(f64.const nan:0xc000000000000)';
var f64_qnan = wasmEvalText(`(module (func (result f64) ${f64_qnan_code}) (export "" 0))`).exports[""]();
assertEqNaN(f64_qnan, { nan_low: 0x0, nan_high: f64_nan_base_high | 0xc0000 });

// Actual tests.

// An example where a signaling nan gets transformed into a quiet nan:
// snan + 0.0 = qnan
var nan = wasmEvalText(`(module (func (result f32) (f32.add ${f32_snan_code} (f32.const 0))) (export "" 0))`).exports[""]();
assertEqNaN(nan, f32_qnan);

// Globals.
var m = wasmEvalText(`(module
    (import "globals" "x" (global f32))
    (func (result f32) (get_global 0))
    (export "global" global 0)
    (export "test" 0))
`, { globals: { x: f32_snan } }).exports;

assertEqNaN(m.test(), f32_snan);
assertEqNaN(m.global, f32_snan);
var f64_snan = '0x7ff4000000000000';
var f64_qnan = '0x7ffc000000000000';

wasmAssert(`(module
    (func $f32_snan (result f32) ${f32_snan_code})
    (func $f32_qnan (result f32) ${f32_qnan_code})
    (func $f64_snan (result f64) ${f64_snan_code})
    (func $f64_qnan (result f64) ${f64_qnan_code})
)`, [
    { type: 'f32', func: '$f32_snan', expected: f32_snan },
    { type: 'f32', func: '$f32_qnan', expected: f32_qnan },
    { type: 'f64', func: '$f64_snan', expected: f64_snan },
    { type: 'f64', func: '$f64_qnan', expected: f64_qnan },
]);

var m = wasmEvalText(`(module
    (import "globals" "x" (global f64))
    (func (result f64) (get_global 0))
    (export "global" global 0)
    (export "test" 0))
`, { globals: { x: f64_snan } }).exports;
// Actual tests.

assertEqNaN(m.test(), f64_snan);
assertEqNaN(m.global, f64_snan);
wasmAssert(`(module
    (global (mut f32) (f32.const 0))
    (global (mut f64) (f64.const 0))

    ;; An example where a signaling nan gets transformed into a quiet nan:
    ;; snan + 0.0 = qnan
    (func $add (result f32) (f32.add ${f32_snan_code} (f32.const 0)))

    ;; Shouldn't affect NaNess.
    (func $set_get_global_f32 (result f32)
        ${f32_snan_code}
        set_global 0
        get_global 0
    )

    ;; Shouldn't affect NaNess.
    (func $set_get_global_f64 (result f64)
        ${f64_snan_code}
        set_global 1
        get_global 1
    )
)`, [
    { type: 'f32', func: '$add', expected: f32_qnan },
    { type: 'f32', func: '$set_get_global_f32', expected: f32_snan },
    { type: 'f64', func: '$set_get_global_f64', expected: f64_snan },
]);

// NaN propagation behavior.
var constantCache = new Map;
function getConstant(code) {
    if (typeof code === 'number')
        return code;
    if (constantCache.has(code)) {
        return constantCache.get(code);
    }
    let type = code.indexOf('f32') >= 0 ? 'f32' : 'f64';
    let val = wasmEvalText(`(module (func (result ${type}) ${code}) (export "" 0))`).exports[""]();
    constantCache.set(code, val);
    return val;
}
function test(type, opcode, lhs_code, rhs_code) {
    let qnan_code = type === 'f32' ? f32_qnan : f64_qnan;

function test(type, opcode, snan_code, rhs_code, qnan_val) {
    var snan_val = getConstant(snan_code);
    var rhs = getConstant(rhs_code);
    let t = type;
    let op = opcode;

    // Test all forms:
    // - (constant, constant),
    // - (constant, variable),
    // - (variable, constant),
    // - (variable, variable)
    assertEqNaN(wasmEvalText(`(module (func (result ${type}) (${type}.${opcode} ${snan_code} ${rhs_code})) (export "" 0))`).exports[""](), qnan_val);
    assertEqNaN(wasmEvalText(`(module (func (param ${type}) (result ${type}) (${type}.${opcode} (get_local 0) ${rhs_code})) (export "" 0))`).exports[""](snan_val), qnan_val);
    assertEqNaN(wasmEvalText(`(module (func (param ${type}) (result ${type}) (${type}.${opcode} ${snan_code} (get_local 0))) (export "" 0))`).exports[""](rhs), qnan_val);
    assertEqNaN(wasmEvalText(`(module (func (param ${type}) (param ${type}) (result ${type}) (${type}.${opcode} (get_local 0) (get_local 1))) (export "" 0))`).exports[""](snan_val, rhs), qnan_val);
    wasmAssert(`(module
        (func $1 (result ${t}) (${t}.${op} ${lhs_code} ${rhs_code}))
        (func $2 (param ${t}) (result ${t}) (${t}.${op} (get_local 0) ${rhs_code}))
        (func $3 (param ${t}) (result ${t}) (${t}.${op} ${lhs_code} (get_local 0)))
        (func $4 (param ${t}) (param ${t}) (result ${t}) (${t}.${op} (get_local 0) (get_local 1)))
    )`, [
        { type, func: '$1', expected: qnan_code },
        { type, func: '$2', params: [lhs_code], expected: qnan_code },
        { type, func: '$3', params: [rhs_code], expected: qnan_code },
        { type, func: '$4', params: [lhs_code, rhs_code], expected: qnan_code },
    ]);
}

var f32_zero = '(f32.const 0)';
@@ -137,39 +137,39 @@ var f32_negone = '(f32.const -1)';
var f64_negone = '(f64.const -1)';

// x - 0.0 doesn't get folded into x:
test('f32', 'sub', f32_snan_code, f32_zero, f32_qnan);
test('f64', 'sub', f64_snan_code, f64_zero, f64_qnan);
test('f32', 'sub', f32_snan_code, f32_zero);
test('f64', 'sub', f64_snan_code, f64_zero);

// x * 1.0 doesn't get folded into x:
test('f32', 'mul', f32_snan_code, f32_one, f32_qnan);
test('f32', 'mul', f32_one, f32_snan_code, f32_qnan);
test('f32', 'mul', f32_snan_code, f32_one);
test('f32', 'mul', f32_one, f32_snan_code);

test('f64', 'mul', f64_snan_code, f64_one, f64_qnan);
test('f64', 'mul', f64_one, f64_snan_code, f64_qnan);
test('f64', 'mul', f64_snan_code, f64_one);
test('f64', 'mul', f64_one, f64_snan_code);

// x * -1.0 doesn't get folded into -x:
test('f32', 'mul', f32_snan_code, f32_negone, f32_qnan);
test('f32', 'mul', f32_negone, f32_snan_code, f32_qnan);
test('f32', 'mul', f32_snan_code, f32_negone);
test('f32', 'mul', f32_negone, f32_snan_code);

test('f64', 'mul', f64_snan_code, f64_negone, f64_qnan);
test('f64', 'mul', f64_negone, f64_snan_code, f64_qnan);
test('f64', 'mul', f64_snan_code, f64_negone);
test('f64', 'mul', f64_negone, f64_snan_code);

// x / -1.0 doesn't get folded into -1 * x:
test('f32', 'div', f32_snan_code, f32_negone, f32_qnan);
test('f64', 'div', f64_snan_code, f64_negone, f64_qnan);
test('f32', 'div', f32_snan_code, f32_negone);
test('f64', 'div', f64_snan_code, f64_negone);

// min doesn't get folded when one of the operands is a NaN
test('f32', 'min', f32_snan_code, f32_zero, f32_qnan);
test('f32', 'min', f32_zero, f32_snan_code, f32_qnan);
test('f32', 'min', f32_snan_code, f32_zero);
test('f32', 'min', f32_zero, f32_snan_code);

test('f64', 'min', f64_snan_code, f64_zero, f64_qnan);
test('f64', 'min', f64_zero, f64_snan_code, f64_qnan);
test('f64', 'min', f64_snan_code, f64_zero);
test('f64', 'min', f64_zero, f64_snan_code);

// ditto for max
test('f32', 'max', f32_snan_code, f32_zero, f32_qnan);
test('f32', 'max', f32_zero, f32_snan_code, f32_qnan);
test('f32', 'max', f32_snan_code, f32_zero);
test('f32', 'max', f32_zero, f32_snan_code);

test('f64', 'max', f64_snan_code, f64_zero, f64_qnan);
test('f64', 'max', f64_zero, f64_snan_code, f64_qnan);
test('f64', 'max', f64_snan_code, f64_zero);
test('f64', 'max', f64_zero, f64_snan_code);

setJitCompilerOption('wasm.test-mode', 0);
+2 −2
Original line number Diff line number Diff line
@@ -2874,14 +2874,14 @@ Simulator::softwareInterrupt(SimInstruction* instr)
void
Simulator::canonicalizeNaN(double* value)
{
    if (!JitOptions.wasmTestMode && FPSCR_default_NaN_mode_)
    if (!wasm::CodeExists && !wasm::LookupCodeSegment(get_pc_as<void*>()) && FPSCR_default_NaN_mode_)
        *value = JS::CanonicalizeNaN(*value);
}

void
Simulator::canonicalizeNaN(float* value)
{
    if (!JitOptions.wasmTestMode && FPSCR_default_NaN_mode_)
    if (!wasm::CodeExists && !wasm::LookupCodeSegment(get_pc_as<void*>()) && FPSCR_default_NaN_mode_)
        *value = JS::CanonicalizeNaN(*value);
}

+0 −22
Original line number Diff line number Diff line
@@ -615,20 +615,10 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args)
                return false;
            break;
          case ValType::F32:
            if (JitOptions.wasmTestMode && v.isObject()) {
                if (!ReadCustomFloat32NaNObject(cx, v, (uint32_t*)&exportArgs[i]))
                    return false;
                break;
            }
            if (!RoundFloat32(cx, v, (float*)&exportArgs[i]))
                return false;
            break;
          case ValType::F64:
            if (JitOptions.wasmTestMode && v.isObject()) {
                if (!ReadCustomDoubleNaNObject(cx, v, (uint64_t*)&exportArgs[i]))
                    return false;
                break;
            }
            if (!ToNumber(cx, v, (double*)&exportArgs[i]))
                return false;
            break;
@@ -724,21 +714,9 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args)
            return false;
        break;
      case ExprType::F32:
        if (JitOptions.wasmTestMode && IsNaN(*(float*)retAddr)) {
            retObj = CreateCustomNaNObject(cx, (float*)retAddr);
            if (!retObj)
                return false;
            break;
        }
        args.rval().set(NumberValue(*(float*)retAddr));
        break;
      case ExprType::F64:
        if (JitOptions.wasmTestMode && IsNaN(*(double*)retAddr)) {
            retObj = CreateCustomNaNObject(cx, (double*)retAddr);
            if (!retObj)
                return false;
            break;
        }
        args.rval().set(NumberValue(*(double*)retAddr));
        break;
      case ExprType::I8x16:
+0 −84
Original line number Diff line number Diff line
@@ -81,72 +81,6 @@ wasm::HasSupport(JSContext* cx)
// ============================================================================
// Imports

template<typename T>
JSObject*
wasm::CreateCustomNaNObject(JSContext* cx, T* addr)
{
    MOZ_ASSERT(IsNaN(*addr));

    RootedObject obj(cx, JS_NewPlainObject(cx));
    if (!obj)
        return nullptr;

    int32_t* i32 = (int32_t*)addr;
    RootedValue intVal(cx, Int32Value(i32[0]));
    if (!JS_DefineProperty(cx, obj, "nan_low", intVal, JSPROP_ENUMERATE))
        return nullptr;

    if (IsSame<double, T>::value) {
        intVal = Int32Value(i32[1]);
        if (!JS_DefineProperty(cx, obj, "nan_high", intVal, JSPROP_ENUMERATE))
            return nullptr;
    }

    return obj;
}

template JSObject* wasm::CreateCustomNaNObject(JSContext* cx, float* addr);
template JSObject* wasm::CreateCustomNaNObject(JSContext* cx, double* addr);

bool
wasm::ReadCustomFloat32NaNObject(JSContext* cx, HandleValue v, uint32_t* ret)
{
    RootedObject obj(cx, &v.toObject());
    RootedValue val(cx);

    int32_t i32;
    if (!JS_GetProperty(cx, obj, "nan_low", &val))
        return false;
    if (!ToInt32(cx, val, &i32))
        return false;

    *ret = i32;
    return true;
}

bool
wasm::ReadCustomDoubleNaNObject(JSContext* cx, HandleValue v, uint64_t* ret)
{
    RootedObject obj(cx, &v.toObject());
    RootedValue val(cx);

    int32_t i32;
    if (!JS_GetProperty(cx, obj, "nan_high", &val))
        return false;
    if (!ToInt32(cx, val, &i32))
        return false;
    *ret = uint32_t(i32);
    *ret <<= 32;

    if (!JS_GetProperty(cx, obj, "nan_low", &val))
        return false;
    if (!ToInt32(cx, val, &i32))
        return false;
    *ret |= uint32_t(i32);

    return true;
}

JSObject*
wasm::CreateI64Object(JSContext* cx, int64_t i64)
{
@@ -299,15 +233,6 @@ GetImports(JSContext* cx,
                break;
              }
              case ValType::F32: {
                if (JitOptions.wasmTestMode && v.isObject()) {
                    uint32_t bits;
                    if (!ReadCustomFloat32NaNObject(cx, v, &bits))
                        return false;
                    float f;
                    BitwiseCast(bits, &f);
                    val = Val(f);
                    break;
                }
                if (!v.isNumber())
                    return ThrowBadImportType(cx, import.field.get(), "Number");
                double d;
@@ -317,15 +242,6 @@ GetImports(JSContext* cx,
                break;
              }
              case ValType::F64: {
                if (JitOptions.wasmTestMode && v.isObject()) {
                    uint64_t bits;
                    if (!ReadCustomDoubleNaNObject(cx, v, &bits))
                        return false;
                    double d;
                    BitwiseCast(bits, &d);
                    val = Val(d);
                    break;
                }
                if (!v.isNumber())
                    return ThrowBadImportType(cx, import.field.get(), "Number");
                double d;
Loading