i31ref.js (4966B)
1 // -0 is a valid value with which to create an i31ref, but because it 2 // roundtrips to +0, it wrecks a lot of our WASM tests (which use Object.is for 3 // comparison). Therefore we don't include -0 in the main list, and deal with 4 // that complexity in this file specifically. 5 WasmI31refValues.push(-0); 6 7 let InvalidI31Values = [ 8 null, 9 Number.EPSILON, 10 Number.MAX_SAFE_INTEGER, 11 Number.MIN_SAFE_INTEGER, 12 Number.MIN_VALUE, 13 Number.MAX_VALUE, 14 Infinity, 15 -Infinity, 16 Number.NaN, 17 // Number objects are not coerced 18 ...WasmI31refValues.map(n => new Number(n)), 19 // Non-integers are not valid 20 ...WasmI31refValues.map(n => n + 0.1), 21 ...WasmI31refValues.map(n => n + 0.5), 22 ...WasmI31refValues.map(n => n + 0.9) 23 ]; 24 25 // Return an equivalent JS number for if a JS number is converted to i31ref 26 // and then zero extended back to 32-bits. 27 function valueAsI31GetU(value) { 28 // Zero extending will drop the sign bit, if any 29 return value & 0x7fffffff; 30 } 31 32 let identity = (n) => n; 33 34 let { 35 castFromAnyref, 36 castFromExternref, 37 refI31, 38 refI31Identity, 39 i31GetU, 40 i31GetS, 41 i31EqualsI31, 42 i31EqualsEq 43 } = wasmEvalText(`(module 44 (func $identity (import "" "identity") (param anyref) (result anyref)) 45 46 (func (export "castFromAnyref") (param anyref) (result i32) 47 local.get 0 48 ref.test (ref i31) 49 ) 50 (func (export "castFromExternref") (param externref) (result i32) 51 local.get 0 52 any.convert_extern 53 ref.test (ref i31) 54 ) 55 (func (export "refI31") (param i32) (result anyref) 56 local.get 0 57 ref.i31 58 ) 59 (func (export "refI31Identity") (param i32) (result anyref) 60 local.get 0 61 ref.i31 62 call $identity 63 ) 64 (func (export "i31GetU") (param i32) (result i32) 65 local.get 0 66 ref.i31 67 i31.get_u 68 ) 69 (func (export "i31GetS") (param i32) (result i32) 70 local.get 0 71 ref.i31 72 i31.get_s 73 ) 74 (func (export "i31EqualsI31") (param i32) (param i32) (result i32) 75 (ref.eq 76 (ref.i31 local.get 0) 77 (ref.i31 local.get 1) 78 ) 79 ) 80 (func (export "i31EqualsEq") (param i32) (param eqref) (result i32) 81 (ref.eq 82 (ref.i31 local.get 0) 83 local.get 1 84 ) 85 ) 86 )`, {"": {identity}}).exports; 87 88 // Test that wasm will represent JS number values that are 31-bit integers as 89 // an i31ref 90 for (let i of WasmI31refValues) { 91 assertEq(castFromAnyref(i), 1); 92 assertEq(castFromExternref(i), 1); 93 } 94 95 // Test that wasm will not represent a JS value that is not a 31-bit number as 96 // an i31ref 97 for (let i of InvalidI31Values) { 98 assertEq(castFromAnyref(i), 0); 99 assertEq(castFromExternref(i), 0); 100 } 101 102 // Test that we can roundtrip 31-bit integers through the i31ref type 103 // faithfully. 104 for (let i of WasmI31refValues) { 105 assertEq(refI31(i), Object.is(i, -0) ? 0 : i); 106 assertEq(refI31Identity(i), Object.is(i, -0) ? 0 : i); 107 assertEq(i31GetU(i), valueAsI31GetU(i)); 108 assertEq(i31GetS(i), Object.is(i, -0) ? 0 : i); 109 } 110 111 // Test that i31ref values are truncated when given a 32-bit value 112 for (let i of WasmI31refValues) { 113 let adjusted = i | 0x80000000; 114 assertEq(refI31(adjusted), Object.is(i, -0) ? 0 : i); 115 } 116 117 // Test that comparing identical i31 values works 118 for (let a of WasmI31refValues) { 119 for (let b of WasmI31refValues) { 120 assertEq(!!i31EqualsI31(a, b), a === b); 121 } 122 } 123 124 // Test that an i31ref is never mistaken for a different kind of reference 125 for (let a of WasmI31refValues) { 126 for (let b of WasmEqrefValues) { 127 assertEq(!!i31EqualsEq(a, b), a === b); 128 } 129 } 130 131 // Test that i32 values get wrapped correctly 132 const bigI32Tests = [ 133 { 134 input: MaxI31refValue + 1, 135 expected: MinI31refValue, 136 }, 137 { 138 input: MinI31refValue - 1, 139 expected: MaxI31refValue, 140 }, 141 ] 142 for (const {input, expected} of bigI32Tests) { 143 const { get, getElem } = wasmEvalText(`(module 144 (func (export "get") (param i32) (result i32) 145 (i31.get_s (ref.i31 (local.get 0))) 146 ) 147 148 (table i31ref (elem (item (ref.i31 (i32.const ${input}))))) 149 (func (export "getElem") (result i32) 150 (i31.get_s (table.get 0 (i32.const 0))) 151 ) 152 )`).exports; 153 assertEq(get(input), expected); 154 assertEq(getElem(), expected); 155 } 156 157 // Test that (ref.i31 (i32 const value)) optimization is correct 158 for (let value of WasmI31refValues) { 159 let {compare} = wasmEvalText(`(module 160 (func $innerCompare (param i32) (param i31ref) (result i32) 161 (ref.eq 162 (ref.i31 local.get 0) 163 local.get 1 164 ) 165 ) 166 (func (export "compare") (result i32) 167 i32.const ${value} 168 (ref.i31 i32.const ${value}) 169 call $innerCompare 170 ) 171 )`).exports; 172 assertEq(compare(value), 1); 173 } 174 175 const { i31GetU_null, i31GetS_null } = wasmEvalText(`(module 176 (func (export "i31GetU_null") (result i32) 177 ref.null i31 178 i31.get_u 179 ) 180 (func (export "i31GetS_null") (result i32) 181 ref.null i31 182 i31.get_s 183 ) 184 )`).exports; 185 186 assertErrorMessage(() => i31GetU_null(), WebAssembly.RuntimeError, /dereferencing null pointer/); 187 assertErrorMessage(() => i31GetS_null(), WebAssembly.RuntimeError, /dereferencing null pointer/);