summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/nan-semantics.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/nan-semantics.js')
-rw-r--r--js/src/jit-test/tests/wasm/nan-semantics.js177
1 files changed, 177 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/nan-semantics.js b/js/src/jit-test/tests/wasm/nan-semantics.js
new file mode 100644
index 000000000..d61d00fe8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/nan-semantics.js
@@ -0,0 +1,177 @@
+load(libdir + "wasm.js");
+
+var f64 = new Float64Array(2);
+var f32 = new Float32Array(f64.buffer);
+var u8 = new Uint8Array(f64.buffer);
+
+function assertSameBitPattern(from, to, offset) {
+ for (let i = from; i < to; i++)
+ assertEq(u8[i], u8[i + offset], 'non equality in assertSameBitPattern');
+}
+
+// Check that custom NaN can't escape to normal JS, in non-testing mode.
+f32[0] = NaN;
+f32[0] = f32[0]; // Force canonicalization.
+
+f32[1] = wasmEvalText('(module (func (result f32) (f32.const nan:0x123456)) (export "" 0))').exports[""]();
+assertSameBitPattern(0, 4, 4);
+
+var checkBitPatterns = {
+ "": {
+ float32(x) {
+ f32[1] = x;
+ assertSameBitPattern(0, 4, 4);
+ },
+ float64(x) {
+ f64[1] = x;
+ assertSameBitPattern(0, 8, 8);
+ }
+ }
+}
+
+wasmEvalText('(module (import "" "float32" (param f32)) (func (call 0 (f32.const nan:0x123456))) (export "" 0))', checkBitPatterns).exports[""]();
+
+f64[0] = NaN;
+f64[0] = f64[0]; // Force canonicalization.
+f64[1] = wasmEvalText('(module (func (result f64) (f64.const nan:0x123456)) (export "" 0))').exports[""]();
+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
+// the payload is used to indicate whether it is quiet (1 for quiet, 0 for
+// signaling). Most operations have to transform a signaling NaN into a quiet
+// NaN, which prevents some optimizations in WebAssembly.
+
+// A float32 has 32 bits, 23 bits of them being reserved for the mantissa
+// (= NaN payload).
+var f32_nan_base = 0x7f800000;
+
+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 });
+
+// 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 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;
+
+assertEqNaN(m.test(), f64_snan);
+assertEqNaN(m.global, 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, snan_code, rhs_code, qnan_val) {
+ var snan_val = getConstant(snan_code);
+ var rhs = getConstant(rhs_code);
+
+ // 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);
+}
+
+var f32_zero = '(f32.const 0)';
+var f64_zero = '(f64.const 0)';
+
+var f32_one = '(f32.const 1)';
+var f64_one = '(f64.const 1)';
+
+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);
+
+// 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('f64', 'mul', f64_snan_code, f64_one, f64_qnan);
+test('f64', 'mul', f64_one, f64_snan_code, f64_qnan);
+
+// 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('f64', 'mul', f64_snan_code, f64_negone, f64_qnan);
+test('f64', 'mul', f64_negone, f64_snan_code, f64_qnan);
+
+// 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);
+
+// 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('f64', 'min', f64_snan_code, f64_zero, f64_qnan);
+test('f64', 'min', f64_zero, f64_snan_code, f64_qnan);
+
+// ditto for max
+test('f32', 'max', f32_snan_code, f32_zero, f32_qnan);
+test('f32', 'max', f32_zero, f32_snan_code, f32_qnan);
+
+test('f64', 'max', f64_snan_code, f64_zero, f64_qnan);
+test('f64', 'max', f64_zero, f64_snan_code, f64_qnan);
+
+setJitCompilerOption('wasm.test-mode', 0);