nan-semantics.js (6083B)
1 var f64 = new Float64Array(2); 2 var f32 = new Float32Array(f64.buffer); 3 var u8 = new Uint8Array(f64.buffer); 4 5 function assertSameBitPattern(from, to, offset) { 6 for (let i = from; i < to; i++) 7 assertEq(u8[i], u8[i + offset], 'non equality in assertSameBitPattern'); 8 } 9 10 // Check that custom NaN can't escape to normal JS, in non-testing mode. 11 f32[0] = NaN; 12 f32[0] = f32[0]; // Force canonicalization. 13 14 f32[1] = wasmEvalText(` 15 (module 16 (func (result f32) 17 (f32.const nan:0x123456)) 18 (export "" (func 0))) 19 `).exports[""](); 20 assertSameBitPattern(0, 4, 4); 21 22 var checkBitPatterns = { 23 "": { 24 float32(x) { 25 f32[1] = x; 26 assertSameBitPattern(0, 4, 4); 27 }, 28 float64(x) { 29 f64[1] = x; 30 assertSameBitPattern(0, 8, 8); 31 } 32 } 33 } 34 35 wasmEvalText(` 36 (module 37 (import "" "float32" (func (param f32))) 38 (func 39 (call 0 (f32.const nan:0x123456))) 40 (export "" (func 0))) 41 `, checkBitPatterns).exports[""](); 42 43 f64[0] = NaN; 44 f64[0] = f64[0]; // Force canonicalization. 45 f64[1] = wasmEvalText(` 46 (module 47 (func (result f64) 48 (f64.const nan:0x123456)) 49 (export "" (func 0))) 50 `).exports[""](); 51 assertSameBitPattern(0, 8, 8); 52 53 wasmEvalText(` 54 (module 55 (import "" "float64" (func (param f64))) 56 (func 57 (call 0 (f64.const nan:0x123456))) 58 (export "" (func 0))) 59 `, checkBitPatterns).exports[""](); 60 61 // SANITY CHECKS 62 63 // There are two kinds of NaNs: signaling and quiet. Usually, the first bit of 64 // the payload is used to indicate whether it is quiet (1 for quiet, 0 for 65 // signaling). Most operations have to transform a signaling NaN into a quiet 66 // NaN, which prevents some optimizations in WebAssembly. 67 68 // A float32 has 32 bits, 23 bits of them being reserved for the mantissa 69 // (= NaN payload). 70 var f32_qnan_code = '(f32.const nan:0x600000)'; 71 var f32_snan_code = '(f32.const nan:0x200000)'; 72 73 var f32_snan = '0x7fa00000'; 74 var f32_qnan = '0x7fe00000'; 75 76 // A float64 has 64 bits, 1 for the sign, 11 for the exponent, the rest for the 77 // mantissa (payload). 78 var f64_nan_base_high = 0x7ff00000; 79 80 var f64_snan_code = '(f64.const nan:0x4000000000000)'; 81 var f64_qnan_code = '(f64.const nan:0xc000000000000)'; 82 83 var f64_snan = '0x7ff4000000000000'; 84 var f64_qnan = '0x7ffc000000000000'; 85 86 wasmAssert(`(module 87 (func $f32_snan (result f32) ${f32_snan_code}) 88 (func $f32_qnan (result f32) ${f32_qnan_code}) 89 (func $f64_snan (result f64) ${f64_snan_code}) 90 (func $f64_qnan (result f64) ${f64_qnan_code}) 91 )`, [ 92 { type: 'f32', func: '$f32_snan', expected: f32_snan }, 93 { type: 'f32', func: '$f32_qnan', expected: f32_qnan }, 94 { type: 'f64', func: '$f64_snan', expected: f64_snan }, 95 { type: 'f64', func: '$f64_qnan', expected: f64_qnan }, 96 ]); 97 98 // Actual tests. 99 100 // Wasm spec v1.1 section 4.3.3, sections "fadd" et seq and "NaN propagation": 101 // If the input NaN is not canonical then the output may be any arithmetic NaN, 102 // ie a quiet NaN with arbitrary payload. 103 104 wasmAssert(`(module 105 (global (mut f32) (f32.const 0)) 106 (global (mut f64) (f64.const 0)) 107 108 ;; An example where a signaling nan gets transformed into a quiet nan: 109 ;; snan + 0.0 = qnan 110 (func $add (result f32) (f32.add ${f32_snan_code} (f32.const 0))) 111 112 ;; Shouldn't affect NaNess. 113 (func $global.set.get_f32 (result f32) 114 ${f32_snan_code} 115 global.set 0 116 global.get 0 117 ) 118 119 ;; Shouldn't affect NaNess. 120 (func $global.set.get_f64 (result f64) 121 ${f64_snan_code} 122 global.set 1 123 global.get 1 124 ) 125 )`, [ 126 { type: 'f32', func: '$add', expected: 'nan:arithmetic' }, 127 { type: 'f32', func: '$global.set.get_f32', expected: f32_snan }, 128 { type: 'f64', func: '$global.set.get_f64', expected: f64_snan }, 129 ]); 130 131 // NaN propagation behavior. 132 function test(type, opcode, lhs_code, rhs_code) { 133 let qnan_code = type === 'f32' ? f32_qnan : f64_qnan; 134 135 let t = type; 136 let op = opcode; 137 138 // Test all forms: 139 // - (constant, constant), 140 // - (constant, variable), 141 // - (variable, constant), 142 // - (variable, variable) 143 wasmAssert(`(module 144 (func $1 (result ${t}) (${t}.${op} ${lhs_code} ${rhs_code})) 145 (func $2 (param ${t}) (result ${t}) (${t}.${op} (local.get 0) ${rhs_code})) 146 (func $3 (param ${t}) (result ${t}) (${t}.${op} ${lhs_code} (local.get 0))) 147 (func $4 (param ${t}) (param ${t}) (result ${t}) (${t}.${op} (local.get 0) (local.get 1))) 148 )`, [ 149 { type, func: '$1', expected: 'nan:arithmetic' }, 150 { type, func: '$2', args: [lhs_code], expected: 'nan:arithmetic' }, 151 { type, func: '$3', args: [rhs_code], expected: 'nan:arithmetic' }, 152 { type, func: '$4', args: [lhs_code, rhs_code], expected: 'nan:arithmetic' }, 153 ]); 154 } 155 156 var f32_zero = '(f32.const 0)'; 157 var f64_zero = '(f64.const 0)'; 158 159 var f32_one = '(f32.const 1)'; 160 var f64_one = '(f64.const 1)'; 161 162 var f32_negone = '(f32.const -1)'; 163 var f64_negone = '(f64.const -1)'; 164 165 // x - 0.0 doesn't get folded into x: 166 test('f32', 'sub', f32_snan_code, f32_zero); 167 test('f64', 'sub', f64_snan_code, f64_zero); 168 169 // x * 1.0 doesn't get folded into x: 170 test('f32', 'mul', f32_snan_code, f32_one); 171 test('f32', 'mul', f32_one, f32_snan_code); 172 173 test('f64', 'mul', f64_snan_code, f64_one); 174 test('f64', 'mul', f64_one, f64_snan_code); 175 176 // x * -1.0 doesn't get folded into -x: 177 test('f32', 'mul', f32_snan_code, f32_negone); 178 test('f32', 'mul', f32_negone, f32_snan_code); 179 180 test('f64', 'mul', f64_snan_code, f64_negone); 181 test('f64', 'mul', f64_negone, f64_snan_code); 182 183 // x / -1.0 doesn't get folded into -1 * x: 184 test('f32', 'div', f32_snan_code, f32_negone); 185 test('f64', 'div', f64_snan_code, f64_negone); 186 187 // min doesn't get folded when one of the operands is a NaN 188 test('f32', 'min', f32_snan_code, f32_zero); 189 test('f32', 'min', f32_zero, f32_snan_code); 190 191 test('f64', 'min', f64_snan_code, f64_zero); 192 test('f64', 'min', f64_zero, f64_snan_code); 193 194 // ditto for max 195 test('f32', 'max', f32_snan_code, f32_zero); 196 test('f32', 'max', f32_zero, f32_snan_code); 197 198 test('f64', 'max', f64_snan_code, f64_zero); 199 test('f64', 'max', f64_zero, f64_snan_code);