br-on-cast-fail.js (7344B)
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_fail 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'], ['eqref'], ['(ref $a)']); 29 // valid: eqref -> struct (and looser types on results) 30 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['anyref'], ['(ref null $a)']); 31 // valid: eqref -> nullable struct (note that branch type becomes non-nullable) 32 validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref eq)'], ['(ref null $a)']); 33 // valid: struct -> struct (from anyref) 34 validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['anyref'], ['(ref $a)']); 35 // valid: struct -> struct (canonicalized) 36 validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)']); 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 null $a)'], ['(ref $b)']); 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 $a)'], ['(ref null $a)']); 41 // valid: eqref -> struct with extra arg 42 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', 'eqref'], ['i32', '(ref $a)']); 43 // valid: eqref -> struct with two extra args 44 validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)']); 45 46 // invalid: block result type must have slot for casted-to type 47 invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['(ref $a)'], /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', 'eqref'], ['i32', 'f32', '(ref $a)'], /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', 'eqref'], ['i32', 'f32', '(ref $a)'], /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', 'eqref'], ['f32', 'i32', '(ref $a)'], /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 $a)'], ['(ref $b)'], /type mismatch/); 60 61 // Simple runtime test of cast-fail-ing 62 { 63 let { makeA, makeB, isA, isB } = wasmEvalText(`(module 64 (type $a (struct)) 65 (type $b (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 eqref) 77 local.get 0 78 br_on_cast_fail 0 eqref (ref $a) 79 80 i32.const 1 81 br 1 82 ) 83 drop 84 i32.const 0 85 ) 86 87 (func (export "isB") (param eqref) (result i32) 88 (block (result eqref) 89 local.get 0 90 br_on_cast_fail 0 eqref (ref $b) 91 92 i32.const 1 93 br 1 94 ) 95 drop 96 i32.const 0 97 ) 98 )`).exports; 99 100 let a = makeA(); 101 let b = makeB(); 102 103 assertEq(isA(a), 1); 104 assertEq(isA(b), 0); 105 assertEq(isB(a), 0); 106 assertEq(isB(b), 1); 107 } 108 109 // Runtime test of cast-fail-ing 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); 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") 143 (param eqref) (result ${values.map((type) => type).join(" ")}) 144 (block (result eqref) 145 local.get 0 146 br_on_cast_fail 0 eqref (ref $t) 147 148 ${values.map((type, i) => `${type}.const ${values.length + i}`) 149 .join("\n")} 150 br 1 151 ) 152 drop 153 ${values.map((type, i) => `${type}.const ${i}`).join("\n")} 154 ) 155 )`).exports; 156 157 let t = makeT(); 158 let f = makeF(); 159 160 let trueValues = values.map((type, i) => i); 161 let falseValues = values.map((type, i) => values.length + i); 162 163 assertEqResults(select(t), falseValues); 164 assertEqResults(select(f), trueValues); 165 } 166 167 // multiples of primitive valtypes 168 for (let valtype of ['i32', 'i64', 'f32', 'f64']) { 169 testExtra([valtype]); 170 testExtra([valtype, valtype]); 171 testExtra([valtype, valtype, valtype]); 172 testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]); 173 } 174 175 // random sundry of valtypes 176 testExtra(['i32', 'f32', 'i64', 'f64']); 177 testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']); 178 } 179 180 // This test causes the `values` vector returned by 181 // `OpIter<Policy>::readBrOnCastFail` to contain three entries, the last of 182 // which is the argument, hence is reftyped. This is used to verify an 183 // assertion to that effect in FunctionCompiler::brOnCastCommon. 184 { 185 let tOnCastFail = 186 `(module 187 (type $a (struct)) 188 (func (export "onCastFail") (param f32 i32 eqref) (result f32 i32 eqref) 189 local.get 0 190 local.get 1 191 local.get 2 192 br_on_cast_fail 0 eqref (ref $a) 193 unreachable 194 ) 195 )`; 196 let { onCastFail } = wasmEvalText(tOnCastFail).exports; 197 }