br-on-cast.js (10283B)
1 function typingModule(types, from, to, brParams, branchResults, fallthroughResults) { 2 return `(module 3 ${types} 4 (func 5 (param ${brParams.join(' ')}) 6 (result ${branchResults.join(' ')}) 7 8 (block (result ${fallthroughResults.join(' ')}) 9 (; push params onto the stack in the same order as they appear, leaving 10 the last param at the top of the stack. ;) 11 ${brParams.map((_, i) => `local.get ${i}`).join('\n')} 12 br_on_cast 1 ${from} ${to} 13 ) 14 unreachable 15 ) 16 )`; 17 } 18 19 function validTyping(types, from, to, brParams, branchResults, fallthroughResults) { 20 wasmValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults)); 21 } 22 23 function invalidTyping(types, from, to, brParams, branchResults, fallthroughResults, error) { 24 wasmFailValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults), error); 25 } 26 27 // valid: eqref -> struct 28 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['eqref']); 29 // valid: eqref -> struct (and looser types on results) 30 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref null $a)'], ['anyref']); 31 // valid: eqref -> nullable struct (note that fallthrough becomes non-nullable) 32 validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref null $a)'], ['(ref eq)']); 33 // valid: struct -> struct (from anyref) 34 validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['(ref $a)'], ['anyref']); 35 // valid: struct -> struct (canonicalized) 36 validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $b)']); 37 // valid: nullable struct -> non-nullable struct (canonicalized) 38 validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref $b)', ['(ref null $a)'], ['(ref $b)'], ['(ref null $a)']); 39 // valid: nullable struct -> nullable struct (canonicalized) 40 validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref null $b)', ['(ref null $a)'], ['(ref null $a)'], ['(ref $a)']); 41 // valid: eqref -> struct with extra arg 42 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', '(ref $a)'], ['i32', 'eqref']); 43 // valid: eqref -> struct with two extra args 44 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref']); 45 46 // invalid: block result type must have slot for casted-to type 47 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['eqref'], /type mismatch/); 48 // invalid: block result type must be supertype of casted-to type 49 invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', 'eqref', '(ref $a)', ['eqref'], ['(ref $b)'], ['(ref $a)'], /type mismatch/); 50 // invalid: input is missing extra i32 from the branch target type 51 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref'], /popping value/); 52 // invalid: input has extra [i32, f32] swapped from the branch target type 53 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], ['i32', 'f32', 'eqref'], /type mismatch/); 54 // invalid: input has extra [i32, f32] swapped from the branch fallthrough type 55 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['f32', 'i32', 'eqref'], /type mismatch/); 56 // invalid: casting to non-nullable but fallthrough not nullable 57 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['(ref eq)'], /type mismatch/); 58 // invalid: struct -> struct (same recursion group) 59 invalidTyping('(rec (type $a (struct)) (type $b (struct)))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)'], /type mismatch/); 60 61 // Simple runtime test of casting 62 { 63 let { makeA, makeB, isA, isB } = wasmEvalText(`(module 64 (type $a (sub (struct))) 65 (type $b (sub $a (struct (field i32)))) 66 67 (func (export "makeA") (result eqref) 68 struct.new_default $a 69 ) 70 71 (func (export "makeB") (result eqref) 72 struct.new_default $b 73 ) 74 75 (func (export "isA") (param eqref) (result i32) 76 (block (result (ref $a)) 77 local.get 0 78 br_on_cast 0 anyref (ref $a) 79 80 i32.const 0 81 br 1 82 ) 83 drop 84 i32.const 1 85 ) 86 87 (func (export "isB") (param eqref) (result i32) 88 (block (result (ref $a)) 89 local.get 0 90 br_on_cast 0 anyref (ref $b) 91 92 i32.const 0 93 br 1 94 ) 95 drop 96 i32.const 1 97 ) 98 )`).exports; 99 100 let a = makeA(); 101 let b = makeB(); 102 103 assertEq(isA(a), 1); 104 assertEq(isA(b), 1); 105 assertEq(isB(a), 0); 106 assertEq(isB(b), 1); 107 } 108 109 // Runtime test of casting with extra values 110 { 111 function assertEqResults(a, b) { 112 if (!(a instanceof Array)) { 113 a = [a]; 114 } 115 if (!(b instanceof Array)) { 116 b = [b]; 117 } 118 if (a.length !== b.length) { 119 assertEq(a.length, b.length); 120 } 121 for (let i = 0; i < a.length; i++) { 122 let x = a[i]; 123 let y = b[i]; 124 // intentionally use loose equality to allow bigint to compare equally 125 // to number, as can happen with how we use the JS-API here. 126 assertEq(x == y, true, `expected ${x} == ${y}, at ${i}`); 127 } 128 } 129 130 function testExtra(values) { 131 let { makeT, makeF, select } = wasmEvalText(`(module 132 (type $t (struct)) 133 (type $f (struct (field i32))) 134 135 (func (export "makeT") (result eqref) 136 struct.new_default $t 137 ) 138 (func (export "makeF") (result eqref) 139 struct.new_default $f 140 ) 141 142 (func (export "select") (param eqref) (result ${values.map((type) => type).join(" ")}) 143 (block (result (ref $t)) 144 local.get 0 145 br_on_cast 0 anyref (ref $t) 146 147 ${values.map((type, i) => `${type}.const ${values.length + i}`).join("\n")} 148 br 1 149 ) 150 drop 151 ${values.map((type, i) => `${type}.const ${i}`).join("\n")} 152 ) 153 )`).exports; 154 155 let t = makeT(); 156 let f = makeF(); 157 158 let trueValues = values.map((type, i) => i); 159 let falseValues = values.map((type, i) => values.length + i); 160 161 assertEqResults(select(t), trueValues); 162 assertEqResults(select(f), falseValues); 163 } 164 165 // multiples of primitive valtypes 166 for (let valtype of ['i32', 'i64', 'f32', 'f64']) { 167 testExtra([valtype]); 168 testExtra([valtype, valtype]); 169 testExtra([valtype, valtype, valtype]); 170 testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]); 171 } 172 173 // random sundry of valtypes 174 testExtra(['i32', 'f32', 'i64', 'f64']); 175 testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']); 176 } 177 178 // Runtime test of casting with extra values passed through the branch 179 { 180 function assertEqResults(a, b) { 181 if (!(a instanceof Array)) { 182 a = [a]; 183 } 184 if (!(b instanceof Array)) { 185 b = [b]; 186 } 187 if (a.length !== b.length) { 188 assertEq(a.length, b.length); 189 } 190 for (let i = 0; i < a.length; i++) { 191 let x = a[i]; 192 let y = b[i]; 193 // intentionally use loose equality to allow bigint to compare equally 194 // to number, as can happen with how we use the JS-API here. 195 assertEq(x == y, true, `expected ${x} == ${y}, at ${i}`); 196 } 197 } 198 199 function testExtra(values) { 200 let types = values.map((type) => type).join(" "); 201 202 // Construct a `select` function which takes `values` twice and a value to 203 // cast. It will return the first values if the 'true' value is passed, 204 // otherwise it will return the 'false' values. 205 let { makeT, makeF, select } = wasmEvalText(`(module 206 (type $t (struct)) 207 (type $f (struct (field i32))) 208 209 (func (export "makeT") (result eqref) 210 struct.new_default $t 211 ) 212 (func (export "makeF") (result eqref) 213 struct.new_default $f 214 ) 215 216 (func (export "select") (param ${types} ${types} eqref) (result ${types}) 217 (block (result ${types} anyref) 218 (block (result ${types} (ref $t)) 219 ;; branch to 0, passing along 'true' values and the 'casted' value 220 ${values.map((_, i) => `local.get ${i}`).join(" ")} 221 local.get ${values.length * 2} 222 br_on_cast 0 anyref (ref $t) 223 224 ;; branch to 1, passing along 'false' values and the 'uncasted' value 225 ${values.map((_, i) => `local.get ${values.length + i}`).join(" ")} 226 local.get ${values.length * 2} 227 br_on_cast_fail 1 anyref (ref $t) 228 229 ;; cast and cast_fail of the same type cannot both fallthrough 230 unreachable 231 ) 232 233 ;; fallthrough, passing our values along 234 ) 235 236 ;; drop the casted/uncasted value at the top, we don't need it 237 drop 238 ) 239 )`).exports; 240 241 let t = makeT(); 242 let f = makeF(); 243 244 let trueValues = values.map((type, i) => type == 'i64' ? BigInt(i) : i); 245 let falseValues = values.map((type, i) => { 246 let value = values.length + i; 247 return type == 'i64' ? BigInt(value) : value; 248 }); 249 250 assertEqResults(select(...trueValues, ...falseValues, t), trueValues); 251 assertEqResults(select(...trueValues, ...falseValues, f), falseValues); 252 } 253 254 // multiples of primitive valtypes 255 for (let valtype of ['i32', 'i64', 'f32', 'f64']) { 256 testExtra([valtype]); 257 testExtra([valtype, valtype]); 258 testExtra([valtype, valtype, valtype]); 259 testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]); 260 } 261 262 // random sundry of valtypes 263 testExtra(['i32', 'f32', 'i64', 'f64']); 264 testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']); 265 } 266 267 // This test causes the `values` vector returned by 268 // `OpIter<Policy>::readBrOnCast` to contain three entries, the last of which 269 // is the argument, hence is reftyped. This is used to verify an assertion to 270 // that effect in FunctionCompiler::brOnCastCommon. 271 { 272 let tOnCast = 273 `(module 274 (type $a (struct)) 275 (func (export "onCast") (param f32 i32 eqref) (result f32 i32 (ref $a)) 276 local.get 0 277 local.get 1 278 local.get 2 279 br_on_cast 0 anyref (ref $a) 280 unreachable 281 ) 282 )`; 283 let { onCast } = wasmEvalText(tOnCast).exports; 284 }