summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/nan-semantics.js
blob: d61d00fe83a5dcb2957eb34cd099626c82ffd571 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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);