summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/nan-semantics.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/jit-test/tests/wasm/nan-semantics.js
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/wasm/nan-semantics.js')
-rw-r--r--js/src/jit-test/tests/wasm/nan-semantics.js199
1 files changed, 199 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 0000000000..b2a8587a4f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/nan-semantics.js
@@ -0,0 +1,199 @@
+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 "" (func 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" (func (param f32)))
+ (func
+ (call 0 (f32.const nan:0x123456)))
+ (export "" (func 0)))
+`, checkBitPatterns).exports[""]();
+
+f64[0] = NaN;
+f64[0] = f64[0]; // Force canonicalization.
+f64[1] = wasmEvalText(`
+(module
+ (func (result f64)
+ (f64.const nan:0x123456))
+ (export "" (func 0)))
+`).exports[""]();
+assertSameBitPattern(0, 8, 8);
+
+wasmEvalText(`
+(module
+ (import "" "float64" (func (param f64)))
+ (func
+ (call 0 (f64.const nan:0x123456)))
+ (export "" (func 0)))
+`, checkBitPatterns).exports[""]();
+
+// 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_qnan_code = '(f32.const nan:0x600000)';
+var f32_snan_code = '(f32.const nan:0x200000)';
+
+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_qnan_code = '(f64.const nan:0xc000000000000)';
+
+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 },
+]);
+
+// Actual tests.
+
+// Wasm spec v1.1 section 4.3.3, sections "fadd" et seq and "NaN propagation":
+// If the input NaN is not canonical then the output may be any arithmetic NaN,
+// ie a quiet NaN with arbitrary payload.
+
+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 $global.set.get_f32 (result f32)
+ ${f32_snan_code}
+ global.set 0
+ global.get 0
+ )
+
+ ;; Shouldn't affect NaNess.
+ (func $global.set.get_f64 (result f64)
+ ${f64_snan_code}
+ global.set 1
+ global.get 1
+ )
+)`, [
+ { type: 'f32', func: '$add', expected: 'nan:arithmetic' },
+ { type: 'f32', func: '$global.set.get_f32', expected: f32_snan },
+ { type: 'f64', func: '$global.set.get_f64', expected: f64_snan },
+]);
+
+// NaN propagation behavior.
+function test(type, opcode, lhs_code, rhs_code) {
+ let qnan_code = type === 'f32' ? f32_qnan : f64_qnan;
+
+ let t = type;
+ let op = opcode;
+
+ // Test all forms:
+ // - (constant, constant),
+ // - (constant, variable),
+ // - (variable, constant),
+ // - (variable, variable)
+ wasmAssert(`(module
+ (func $1 (result ${t}) (${t}.${op} ${lhs_code} ${rhs_code}))
+ (func $2 (param ${t}) (result ${t}) (${t}.${op} (local.get 0) ${rhs_code}))
+ (func $3 (param ${t}) (result ${t}) (${t}.${op} ${lhs_code} (local.get 0)))
+ (func $4 (param ${t}) (param ${t}) (result ${t}) (${t}.${op} (local.get 0) (local.get 1)))
+ )`, [
+ { type, func: '$1', expected: 'nan:arithmetic' },
+ { type, func: '$2', args: [lhs_code], expected: 'nan:arithmetic' },
+ { type, func: '$3', args: [rhs_code], expected: 'nan:arithmetic' },
+ { type, func: '$4', args: [lhs_code, rhs_code], expected: 'nan:arithmetic' },
+ ]);
+}
+
+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);
+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);
+test('f32', 'mul', f32_one, f32_snan_code);
+
+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);
+test('f32', 'mul', f32_negone, f32_snan_code);
+
+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);
+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);
+test('f32', 'min', f32_zero, f32_snan_code);
+
+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);
+test('f32', 'max', f32_zero, f32_snan_code);
+
+test('f64', 'max', f64_snan_code, f64_zero);
+test('f64', 'max', f64_zero, f64_snan_code);