cast-abstract.js (9792B)
1 load(libdir + "wasm-binary.js"); 2 3 // Replace this with a string like 'non-null (ref $s1) -> (ref any) -> (ref any)' to test exactly one specific case. Makes debugging easier. 4 const specificTest = ''; 5 6 const preamble = ` 7 (type $s1 (sub (struct))) 8 (type $s2 (sub $s1 (struct (field i32)))) 9 (type $a1 (sub (array (ref null $s1)))) 10 (type $a2 (sub $a1 (array (ref null $s2)))) 11 (type $ft1 (sub (func (param (ref $s1)) (result (ref null $a1))))) 12 (type $ft2 (sub $ft1 (func (param (ref null $s1)) (result (ref null $a2))))) 13 (type $ft3 (sub (func (param (ref $a2)) (result i32)))) 14 (type $ft4 (sub $ft3 (func (param (ref $a1)) (result i32)))) 15 16 (func $f1 (type $ft1) ref.null $a1) 17 (func $f2 (type $ft2) ref.null $a2) 18 (func $f3 (type $ft3) i32.const 0) 19 (func $f4 (type $ft4) i32.const 0) 20 (elem declare func $f1 $f2 $f3 $f4) ;; allow funcs to be ref.func'd 21 `; 22 23 // All the concrete subtype relationships present in the preamble. 24 // [x, y] means x <: y. 25 const subtypes = [ 26 ['$s2', '$s1'], 27 ['$a2', '$a1'], 28 ['$ft2', '$ft1'], 29 ['$ft4', '$ft3'], 30 ]; 31 32 const any = { name: 'any', top: 'anyref' }; 33 const eq = { name: 'eq', top: 'anyref' }; 34 const struct = { name: 'struct', top: 'anyref' }; 35 const array = { name: 'array', top: 'anyref' }; 36 const s1 = { 37 index: 0, 38 name: '$s1', 39 make: 'struct.new_default $s1', 40 top: 'anyref', 41 }; 42 const s2 = { 43 index: 1, 44 name: '$s2', 45 make: 'struct.new_default $s2', 46 top: 'anyref', 47 }; 48 const a1 = { 49 index: 2, 50 name: '$a1', 51 make: '(array.new_default $a1 (i32.const 10))', 52 top: 'anyref', 53 }; 54 const a2 = { 55 index: 3, 56 name: '$a2', 57 make: '(array.new_default $a2 (i32.const 10))', 58 top: 'anyref', 59 }; 60 const i31 = { name: 'i31', make: '(ref.i31 (i32.const 123))', top: 'anyref' }; 61 const none = { name: 'none', none: true, top: 'anyref' }; 62 63 const func = { name: 'func', top: 'funcref' }; 64 const ft1 = { index: 4, name: '$ft1', make: 'ref.func $f1', top: 'funcref' }; 65 const ft2 = { index: 5, name: '$ft2', make: 'ref.func $f2', top: 'funcref' }; 66 const ft3 = { index: 6, name: '$ft3', make: 'ref.func $f3', top: 'funcref' }; 67 const ft4 = { index: 7, name: '$ft4', make: 'ref.func $f4', top: 'funcref' }; 68 const nofunc = { name: 'nofunc', none: true, top: 'funcref' }; 69 70 const extern = { name: 'extern', make: '(extern.convert_any (struct.new_default $s1))', top: 'externref' } 71 const noextern = { name: 'noextern', none: true, top: 'externref' } 72 73 const typeSets = [ 74 [any, eq, struct, s1, s2, none], 75 [any, eq, array, a1, a2, none], 76 [any, eq, s1, s2, a1, a2, none], 77 [any, eq, i31, none], 78 [any, eq, i31, s1, none], 79 [any, eq, i31, a1, none], 80 81 [func, ft1, ft2, nofunc], 82 [func, ft3, ft4, nofunc], 83 [func, ft1, ft2, ft3, ft4, nofunc], 84 85 [extern, noextern], 86 ]; 87 88 const nullables = [ // for example: 89 [true, true, true], // null $s1 -> null any -> null eq 90 [true, true, false], // null $s1 -> null any -> eq 91 [true, false, true], // null $s1 -> any -> null eq 92 [true, false, false], // null $s1 -> any -> eq 93 [false, true, true], // $s1 -> null any -> null eq 94 [false, true, false], // $s1 -> null any -> eq 95 [false, false, true], // $s1 -> any -> null eq 96 [false, false, false], // $s1 -> any -> eq 97 ] 98 99 function isSubtype(src, dest) { 100 if (src.name === dest.name) { 101 return true; 102 } 103 for (const [src2, dest2] of subtypes) { 104 if (src.name === src2 && dest.name === dest2) { 105 return true; 106 } 107 } 108 return false; 109 } 110 111 let numCases = 0; 112 113 // This will generate an enormous pile of test cases. All of these should be valid, 114 // as in passing WebAssembly.validate, but some may not be good casts at runtime. 115 for (const typeSet of typeSets) { 116 for (const start of typeSet) { 117 for (const middle of typeSet) { 118 for (const end of typeSet) { 119 for (const [nullable0, nullable1, nullable2] of nullables) { 120 for (const makeNull of [true, false]) { 121 const concrete0 = !!start.make; 122 const concrete1 = !!middle.make; 123 const concrete2 = !!end.make; 124 125 if (!concrete0 && !makeNull) { 126 // We can only start with null values for abstract types 127 continue; 128 } 129 130 if (!nullable0 && makeNull) { 131 // Can't use null as a valid value for a non-nullable type 132 continue; 133 } 134 135 numCases += 1; 136 137 let good1 = true; 138 let good2 = true; 139 140 // Null values will fail casts to non-nullable types 141 if (makeNull) { 142 if (!nullable1) { 143 good1 = false; 144 } 145 if (!nullable2) { 146 good2 = false; 147 } 148 } 149 150 // Bottom types can't represent non-null, so this will always fail 151 if (!makeNull) { 152 if (middle.none) { 153 good1 = false; 154 } 155 if (end.none) { 156 good2 = false; 157 } 158 } 159 160 // Concrete values are subject to subtyping relationships 161 if (!makeNull) { 162 if (concrete1 && !isSubtype(start, middle)) { 163 good1 = false; 164 } 165 if (concrete2 && !isSubtype(start, end)) { 166 good2 = false; 167 } 168 } 169 170 let emoji1 = good1 ? '✅' : '❌'; 171 let emoji2 = good2 ? '✅' : '❌'; 172 173 if (!good1) { 174 good2 = false; 175 emoji2 = '❓'; 176 } 177 178 const name = `${makeNull ? 'null' : 'non-null'} (ref ${nullable0 ? 'null ' : ''}${start.name}) -> (ref ${nullable1 ? 'null ' : ''}${middle.name}) -> (ref ${nullable2 ? 'null ' : ''}${end.name})`; 179 if (specificTest && name != specificTest) { 180 continue; 181 } 182 183 print(`${emoji1}${emoji2} ${name}`); 184 const make = makeNull ? `ref.null ${start.name}` : start.make; 185 const type0 = `(ref ${nullable0 ? 'null ' : ''}${start.name})`; 186 const type1 = `(ref ${nullable1 ? 'null ' : ''}${middle.name})`; 187 const type2 = `(ref ${nullable2 ? 'null ' : ''}${end.name})`; 188 const moduleText = `(module 189 ${preamble} 190 (func (export "make") (result ${type0}) 191 ${make} 192 ) 193 (func (export "cast1") (param ${type0}) (result ${type1}) 194 local.get 0 195 ref.cast ${type1} 196 ) 197 (func (export "cast2") (param ${type1}) (result ${type2}) 198 local.get 0 199 ref.cast ${type2} 200 ) 201 202 (func (export "test1") (param ${type0}) (result i32) 203 local.get 0 204 ref.test ${type1} 205 ) 206 (func (export "test2") (param ${type1}) (result i32) 207 local.get 0 208 ref.test ${type2} 209 ) 210 211 ;; these are basically ref.test but with branches 212 (func (export "branch1") (param ${type0}) (result i32) 213 (block (result ${type1}) 214 local.get 0 215 br_on_cast 0 ${start.top} ${type1} 216 drop 217 (return (i32.const 0)) 218 ) 219 drop 220 (return (i32.const 1)) 221 ) 222 (func (export "branch2") (param ${type1}) (result i32) 223 (block (result ${type2}) 224 local.get 0 225 br_on_cast 0 ${middle.top} ${type2} 226 drop 227 (return (i32.const 0)) 228 ) 229 drop 230 (return (i32.const 1)) 231 ) 232 (func (export "branchfail1") (param ${type0}) (result i32) 233 (block (result ${middle.top}) 234 local.get 0 235 br_on_cast_fail 0 ${start.top} ${type1} 236 drop 237 (return (i32.const 1)) 238 ) 239 drop 240 (return (i32.const 0)) 241 ) 242 (func (export "branchfail2") (param ${type1}) (result i32) 243 (block (result ${end.top}) 244 local.get 0 245 br_on_cast_fail 0 ${middle.top} ${type2} 246 drop 247 (return (i32.const 1)) 248 ) 249 drop 250 (return (i32.const 0)) 251 ) 252 )`; 253 254 function assertCast(func, good) { 255 if (good) { 256 return [func(), true]; 257 } else { 258 assertErrorMessage(func, WebAssembly.RuntimeError, /bad cast/); 259 return [null, false]; 260 } 261 } 262 263 try { 264 // The casts are split up so the stack trace will show you which cast is failing. 265 const { 266 make, 267 cast1, cast2, 268 test1, test2, 269 branch1, branch2, 270 branchfail1, branchfail2, 271 } = wasmEvalText(moduleText).exports; 272 273 const val = make(); 274 const [res1, ok1] = assertCast(() => cast1(val), good1); 275 assertEq(test1(val), good1 ? 1 : 0); 276 assertEq(branch1(val), good1 ? 1 : 0); 277 assertEq(branchfail1(val), good1 ? 1 : 0); 278 if (ok1) { 279 assertCast(() => cast2(res1), good2); 280 assertEq(test2(res1), good2 ? 1 : 0); 281 assertEq(branch2(res1), good2 ? 1 : 0); 282 assertEq(branchfail2(res1), good2 ? 1 : 0); 283 } 284 } catch (e) { 285 print("Failed! Module text was:"); 286 print(moduleText); 287 throw e; 288 } 289 } 290 } 291 } 292 } 293 } 294 } 295 296 print(`we hope you have enjoyed these ${numCases} test cases 😁`);