summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/nan-semantics.js
blob: b2a8587a4f9d21e19e1484e7197b876239d2b264 (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
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);