tables.js (18624B)
1 const Module = WebAssembly.Module; 2 const Instance = WebAssembly.Instance; 3 const Table = WebAssembly.Table; 4 const Memory = WebAssembly.Memory; 5 const LinkError = WebAssembly.LinkError; 6 const RuntimeError = WebAssembly.RuntimeError; 7 8 const badFuncRefError = /can only pass WebAssembly exported functions to funcref/; 9 10 function assertSegmentFitError(f) { 11 assertErrorMessage(f, RuntimeError, /out of bounds/); 12 } 13 14 var callee = i => `(func $f${i} (result i32) (i32.const ${i}))`; 15 16 wasmFailValidateText(`(module (elem (i32.const 0) $f0) ${callee(0)})`, /elem segment requires a table/); 17 wasmFailValidateText(`(module (table 10 funcref) (elem (i32.const 0) 0))`, /element index out of range/); 18 wasmFailValidateText(`(module (table 10 funcref) (func) (elem (i32.const 0) 0 1))`, /element index out of range/); 19 wasmFailValidateText(`(module (table 10 funcref) (func) (elem (f32.const 0) 0) ${callee(0)})`, /type mismatch/); 20 21 assertSegmentFitError(() => wasmEvalText(`(module (table 10 funcref) (elem (i32.const 10) $f0) ${callee(0)})`)); 22 assertSegmentFitError(() => wasmEvalText(`(module (table 10 funcref) (elem (i32.const 8) $f0 $f0 $f0) ${callee(0)})`)); 23 assertSegmentFitError(() => wasmEvalText(`(module (table 0 funcref) (func) (elem (i32.const 0x10001)))`)); 24 25 assertSegmentFitError(() => wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (global.get 0) $f0) ${callee(0)})`, {globals:{a:10}})); 26 assertSegmentFitError(() => wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (global.get 0) $f0 $f0 $f0) ${callee(0)})`, {globals:{a:8}})); 27 28 assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 0) $f0) ${callee(0)})`)) instanceof Module, true); 29 assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`)) instanceof Module, true); 30 wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (global.get 0) $f0) ${callee(0)})`, {globals:{a:0}}); 31 wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (global.get 0) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`, {globals:{a:1}}); 32 33 // Explicit table index in a couple of ways, note this requires us to talk about the table type also. 34 assertEq(new Module(wasmTextToBinary(`(module 35 (table 10 funcref) 36 (table 10 funcref) 37 (elem 1 (i32.const 1) func $f0 $f0) 38 ${callee(0)})`)) instanceof Module, true); 39 assertEq(new Module(wasmTextToBinary(`(module 40 (table 10 funcref) 41 (table 10 funcref) 42 (elem (table 1) (i32.const 1) func $f0 $f0) 43 ${callee(0)})`)) instanceof Module, true); 44 45 // With "funcref" rather than "func". 46 assertEq(new Module(wasmTextToBinary(`(module 47 (table 10 funcref) 48 (table 10 funcref) 49 (elem (table 1) (i32.const 1) funcref (ref.func $f0) (ref.func $f0)) 50 ${callee(0)})`)) instanceof Module, true); 51 52 // Syntax for the offset, ditto. 53 assertEq(new Module(wasmTextToBinary(`(module 54 (table 10 funcref) 55 (table 10 funcref) 56 (elem 1 (offset (i32.const 1)) func $f0 $f0) 57 ${callee(0)})`)) instanceof Module, true); 58 59 var m = new Module(wasmTextToBinary(` 60 (module 61 (import "globals" "table" (table 10 funcref)) 62 (import "globals" "a" (global i32)) 63 (elem (global.get 0) $f0 $f0) 64 ${callee(0)}) 65 `)); 66 var tbl = new Table({initial:50, element:"anyfunc"}); 67 assertEq(new Instance(m, {globals:{a:20, table:tbl}}) instanceof Instance, true); 68 assertSegmentFitError(() => new Instance(m, {globals:{a:50, table:tbl}})); 69 70 var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect (type $v2i) (local.get $i))) (export "call" (func $call))` 71 var callee = i => `(func $f${i} (type $v2i) (i32.const ${i}))`; 72 73 var call = wasmEvalText(`(module (table 10 funcref) ${callee(0)} ${caller})`).exports.call; 74 assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/); 75 assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/); 76 77 var call = wasmEvalText(`(module (table 10 funcref) (elem (i32.const 0)) ${callee(0)} ${caller})`).exports.call; 78 assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/); 79 assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/); 80 81 var call = wasmEvalText(`(module (table 10 funcref) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`).exports.call; 82 assertEq(call(0), 0); 83 assertErrorMessage(() => call(1), RuntimeError, /indirect call to null/); 84 assertErrorMessage(() => call(2), RuntimeError, /indirect call to null/); 85 assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/); 86 87 var call = wasmEvalText(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f1) (elem (i32.const 4) $f0 $f2) ${callee(0)} ${callee(1)} ${callee(2)} ${caller})`).exports.call; 88 assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/); 89 assertEq(call(1), 0); 90 assertEq(call(2), 1); 91 assertErrorMessage(() => call(3), RuntimeError, /indirect call to null/); 92 assertEq(call(4), 0); 93 assertEq(call(5), 2); 94 assertErrorMessage(() => call(6), RuntimeError, /indirect call to null/); 95 assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/); 96 97 var imports = {a:{b:()=>42}}; 98 var call = wasmEvalText(`(module (import "a" "b" (func $f1)) (import "a" "b" (func $f2 (result i32))) (table 10 funcref) (elem (i32.const 0) $f0 $f1 $f2) ${callee(0)} ${caller})`, imports).exports.call; 99 assertEq(call(0), 0); 100 assertErrorMessage(() => call(1), RuntimeError, /indirect call signature mismatch/); 101 assertEq(call(2), 42); 102 103 var tbl = new Table({initial:3, element:"anyfunc"}); 104 var call = wasmEvalText(`(module (import "a" "b" (table 3 funcref)) (export "tbl" (table 0)) (elem (i32.const 0) $f0 $f1) ${callee(0)} ${callee(1)} ${caller})`, {a:{b:tbl}}).exports.call; 105 assertEq(call(0), 0); 106 assertEq(call(1), 1); 107 assertEq(tbl.get(0)(), 0); 108 assertEq(tbl.get(1)(), 1); 109 assertErrorMessage(() => call(2), RuntimeError, /indirect call to null/); 110 assertEq(tbl.get(2), null); 111 112 var exp = wasmEvalText(`(module (import "a" "b" (table 3 funcref)) (export "tbl" (table 0)) (elem (i32.const 2) $f2) ${callee(2)} ${caller})`, {a:{b:tbl}}).exports; 113 assertEq(exp.tbl, tbl); 114 assertEq(exp.call(0), 0); 115 assertEq(exp.call(1), 1); 116 assertEq(exp.call(2), 2); 117 assertEq(call(0), 0); 118 assertEq(call(1), 1); 119 assertEq(call(2), 2); 120 assertEq(tbl.get(0)(), 0); 121 assertEq(tbl.get(1)(), 1); 122 assertEq(tbl.get(2)(), 2); 123 124 var exp1 = wasmEvalText(`(module (table 10 funcref) (export "tbl" (table 0)) (elem (i32.const 0) $f0 $f0) ${callee(0)} (export "f0" (func $f0)) ${caller})`).exports 125 assertEq(exp1.tbl.get(0), exp1.f0); 126 assertEq(exp1.tbl.get(1), exp1.f0); 127 assertEq(exp1.tbl.get(2), null); 128 assertEq(exp1.call(0), 0); 129 assertEq(exp1.call(1), 0); 130 assertErrorMessage(() => exp1.call(2), RuntimeError, /indirect call to null/); 131 var exp2 = wasmEvalText(`(module (import "a" "b" (table 10 funcref)) (export "tbl" (table 0)) (elem (i32.const 1) $f1 $f1) ${callee(1)} (export "f1" (func $f1)) ${caller})`, {a:{b:exp1.tbl}}).exports 132 assertEq(exp1.tbl, exp2.tbl); 133 assertEq(exp2.tbl.get(0), exp1.f0); 134 assertEq(exp2.tbl.get(1), exp2.f1); 135 assertEq(exp2.tbl.get(2), exp2.f1); 136 assertEq(exp1.call(0), 0); 137 assertEq(exp1.call(1), 1); 138 assertEq(exp1.call(2), 1); 139 assertEq(exp2.call(0), 0); 140 assertEq(exp2.call(1), 1); 141 assertEq(exp2.call(2), 1); 142 143 var tbl = new Table({initial:3, element:"anyfunc"}); 144 var e1 = wasmEvalText(`(module (func $f (result i32) (i32.const 42)) (export "f" (func $f)))`).exports; 145 var e2 = wasmEvalText(`(module (func $g (result f32) (f32.const 10)) (export "g" (func $g)))`).exports; 146 var e3 = wasmEvalText(`(module (func $h (result i32) (i32.const 13)) (export "h" (func $h)))`).exports; 147 tbl.set(0, e1.f); 148 tbl.set(1, e2.g); 149 tbl.set(2, e3.h); 150 var e4 = wasmEvalText(`(module (import "a" "b" (table 3 funcref)) ${caller})`, {a:{b:tbl}}).exports; 151 assertEq(e4.call(0), 42); 152 assertErrorMessage(() => e4.call(1), RuntimeError, /indirect call signature mismatch/); 153 assertEq(e4.call(2), 13); 154 155 var asmjsFun = (function() { "use asm"; function f() {} return f })(); 156 assertEq(isAsmJSFunction(asmjsFun), isAsmJSCompilationAvailable()); 157 assertErrorMessage(() => tbl.set(0, asmjsFun), TypeError, badFuncRefError); 158 assertErrorMessage(() => tbl.grow(1, asmjsFun), TypeError, badFuncRefError); 159 160 var m = new Module(wasmTextToBinary(`(module 161 (type $i2i (func (param i32) (result i32))) 162 (import "a" "mem" (memory 1)) 163 (import "a" "tbl" (table 10 funcref)) 164 (import "a" "imp" (func $imp (result i32))) 165 (func $call (param $i i32) (result i32) 166 (i32.add 167 (call $imp) 168 (i32.add 169 (i32.load (i32.const 0)) 170 (if (result i32) (i32.eqz (local.get $i)) 171 (then (i32.const 0)) 172 (else 173 (local.set $i (i32.sub (local.get $i) (i32.const 1))) 174 (call_indirect (type $i2i) (local.get $i) (local.get $i))))))) 175 (export "call" (func $call)) 176 )`)); 177 var failTime = false; 178 var tbl = new Table({initial:10, element:"anyfunc"}); 179 var mem1 = new Memory({initial:1}); 180 var e1 = new Instance(m, {a:{mem:mem1, tbl, imp() {if (failTime) throw new Error("ohai"); return 1}}}).exports; 181 tbl.set(0, e1.call); 182 var mem2 = new Memory({initial:1}); 183 var e2 = new Instance(m, {a:{mem:mem2, tbl, imp() {return 10} }}).exports; 184 tbl.set(1, e2.call); 185 var mem3 = new Memory({initial:1}); 186 var e3 = new Instance(m, {a:{mem:mem3, tbl, imp() {return 100} }}).exports; 187 new Int32Array(mem1.buffer)[0] = 1000; 188 new Int32Array(mem2.buffer)[0] = 10000; 189 new Int32Array(mem3.buffer)[0] = 100000; 190 assertEq(e3.call(2), 111111); 191 failTime = true; 192 assertErrorMessage(() => e3.call(2), Error, "ohai"); 193 194 // Call signatures are matched structurally: 195 196 var call = wasmEvalText(`(module 197 (type $v2i1 (func (result i32))) 198 (type $v2i2 (func (result i32))) 199 (type $i2v (func (param i32))) 200 (table funcref (elem $a $b $c)) 201 (func $a (type $v2i1) (i32.const 0)) 202 (func $b (type $v2i2) (i32.const 1)) 203 (func $c (type $i2v)) 204 (func $call (param i32) (result i32) (call_indirect (type $v2i1) (local.get 0))) 205 (export "call" (func $call)) 206 )`).exports.call; 207 assertEq(call(0), 0); 208 assertEq(call(1), 1); 209 assertErrorMessage(() => call(2), RuntimeError, /indirect call signature mismatch/); 210 211 var call = wasmEvalText(`(module 212 (type $A (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 213 (type $B (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 214 (type $C (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 215 (type $D (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 216 (type $E (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 217 (type $F (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 218 (type $G (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32))) 219 (table funcref (elem $a $b $c $d $e $f $g)) 220 (func $a (type $A) (local.get 7)) 221 (func $b (type $B) (local.get 8)) 222 (func $c (type $C) (local.get 9)) 223 (func $d (type $D) (local.get 10)) 224 (func $e (type $E) (local.get 11)) 225 (func $f (type $F) (local.get 12)) 226 (func $g (type $G) (local.get 13)) 227 (func $call (param i32) (result i32) 228 (call_indirect (type $A) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 42) (local.get 0))) 229 (export "call" (func $call)) 230 )`).exports.call; 231 assertEq(call(0), 42); 232 for (var i = 1; i < 7; i++) 233 assertErrorMessage(() => call(i), RuntimeError, /indirect call signature mismatch/); 234 assertErrorMessage(() => call(7), RuntimeError, /index out of bounds/); 235 236 // Function identity isn't lost: 237 var tbl = wasmEvalText(`(module (table (export "tbl") funcref (elem $f)) (func $f))`).exports.tbl; 238 tbl.get(0).foo = 42; 239 gc(); 240 assertEq(tbl.get(0).foo, 42); 241 242 (function testCrossRealmCall() { 243 var g = newGlobal({sameCompartmentAs: this}); 244 245 // The memory.size builtin asserts cx->realm matches instance->realm so 246 // we call it here. 247 var src = ` 248 (module 249 (import "a" "t" (table 3 funcref)) 250 (import "a" "m" (memory 1)) 251 (func $f (result i32) (i32.add (i32.const 3) (memory.size))) 252 (elem (i32.const 0) $f)) 253 `; 254 g.mem = new Memory({initial:4}); 255 g.tbl = new Table({initial:3, element:"anyfunc"}); 256 var i1 = g.evaluate("new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`" + src + "`)), {a:{t:tbl,m:mem}})"); 257 258 var call = new Instance(new Module(wasmTextToBinary(` 259 (module 260 (import "a" "t" (table 3 funcref)) 261 (import "a" "m" (memory 1)) 262 (type $v2i (func (result i32))) 263 (func $call (param $i i32) (result i32) (i32.add (call_indirect (type $v2i) (local.get $i)) (memory.size))) 264 (export "call" (func $call))) 265 `)), {a:{t:g.tbl,m:g.mem}}).exports.call; 266 267 for (var i = 0; i < 10; i++) 268 assertEq(call(0), 11); 269 })(); 270 271 272 // Test active segments with a table index. 273 274 { 275 function makeIt(flag, tblindex) { 276 return new Uint8Array([0x00, 0x61, 0x73, 0x6d, 277 0x01, 0x00, 0x00, 0x00, 278 0x04, // Table section 279 0x04, // Section size 280 0x01, // One table 281 0x70, // Type: FuncRef 282 0x00, // Limits: Min only 283 0x01, // Limits: Min 284 0x09, // Elements section 285 0x08, // Section size 286 0x01, // One element segment 287 flag, // Flag should be 2, or > 2 if invalid 288 tblindex, // Table index must be 0, or > 0 if invalid 289 0x41, // Init expr: i32.const 290 0x00, // Init expr: zero (payload) 291 0x0b, // Init expr: end 292 0x00, // Extern kind: Function 293 0x00]); // Zero functions 294 } 295 296 // Should succeed because this is what an active segment with index looks like 297 new WebAssembly.Module(makeIt(0x02, 0x00)); 298 299 // Should fail because the kind is unknown 300 assertErrorMessage(() => new WebAssembly.Module(makeIt(0x08, 0x00)), 301 WebAssembly.CompileError, 302 /invalid elem segment flags field/); 303 304 // Should fail because the table index is invalid 305 assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)), 306 WebAssembly.CompileError, 307 /table index out of range/); 308 } 309 310 // table of externref 311 { 312 const myArray = ["yay", "arrays"]; 313 const { set, get } = wasmEvalText(`(module 314 (table $t 2 externref) 315 316 (func (export "set") (param i32 externref) 317 (table.set $t (local.get 0) (local.get 1)) 318 ) 319 (func (export "get") (param i32) (result externref) 320 (table.get $t (local.get 0)) 321 ) 322 )`).exports; 323 assertEq(get(0), null); 324 assertEq(get(1), null); 325 set(0, "hello"); 326 set(1, myArray); 327 assertEq(get(0), "hello"); 328 assertEq(get(1), myArray); 329 } 330 331 // table of externref with active element segment 332 { 333 const myArray = ["yay", "arrays"]; 334 const { get } = wasmEvalText(`(module 335 (global (import "test" "g1") externref) 336 (global (import "test" "g2") externref) 337 (table $t externref (elem (global.get 0) (global.get 1))) 338 339 (func (export "get") (param i32) (result externref) 340 (table.get $t (local.get 0)) 341 ) 342 )`, { test: { g1: "hello", g2: myArray } }).exports; 343 assertEq(get(0), "hello"); 344 assertEq(get(1), myArray); 345 } 346 347 // passive element segment of externref 348 { 349 const myArray = ["yay", "arrays"]; 350 const { get } = wasmEvalText(`(module 351 (global (import "test" "g1") externref) 352 (global (import "test" "g2") externref) 353 (table $t 2 externref) 354 (elem $e externref (global.get 0) (global.get 1)) 355 356 (func $start 357 (table.init $t $e (i32.const 0) (i32.const 0) (table.size $t)) 358 ) 359 (func (export "get") (param i32) (result externref) 360 (table.get $t (local.get 0)) 361 ) 362 363 (start $start) 364 )`, { test: { g1: "hello", g2: myArray } }).exports; 365 assertEq(get(0), "hello"); 366 assertEq(get(1), myArray); 367 } 368 369 // declared element segment of externref (literally useless but legal!) 370 { 371 const myArray = ["yay", "arrays"]; 372 wasmEvalText(`(module 373 (global (import "test" "g1") externref) 374 (global (import "test" "g2") externref) 375 (elem $e declare externref (global.get 0) (global.get 1)) 376 )`, { test: { g1: "hello", g2: myArray } }); 377 } 378 379 // result types are validated in element expressions 380 { 381 assertErrorMessage(() => wasmEvalText(`(module 382 (elem $e externref (ref.func 0)) 383 (func) 384 )`), WebAssembly.CompileError, /expression has type (funcref|\(ref 0\)) but expected externref/); 385 }