summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/tables.js
blob: 3f0003b4a1242a5d4241192ee669c39e44cf2963 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
const Module = WebAssembly.Module;
const Instance = WebAssembly.Instance;
const Table = WebAssembly.Table;
const Memory = WebAssembly.Memory;
const LinkError = WebAssembly.LinkError;
const RuntimeError = WebAssembly.RuntimeError;

const badFuncRefError = /can only pass WebAssembly exported functions to funcref/;

function assertSegmentFitError(f) {
    assertErrorMessage(f, RuntimeError, /out of bounds/);
}

var callee = i => `(func $f${i} (result i32) (i32.const ${i}))`;

wasmFailValidateText(`(module (elem (i32.const 0) $f0) ${callee(0)})`, /elem segment requires a table/);
wasmFailValidateText(`(module (table 10 funcref) (elem (i32.const 0) 0))`, /element index out of range/);
wasmFailValidateText(`(module (table 10 funcref) (func) (elem (i32.const 0) 0 1))`, /element index out of range/);
wasmFailValidateText(`(module (table 10 funcref) (func) (elem (f32.const 0) 0) ${callee(0)})`, /type mismatch/);

assertSegmentFitError(() => wasmEvalText(`(module (table 10 funcref) (elem (i32.const 10) $f0) ${callee(0)})`));
assertSegmentFitError(() => wasmEvalText(`(module (table 10 funcref) (elem (i32.const 8) $f0 $f0 $f0) ${callee(0)})`));
assertSegmentFitError(() => wasmEvalText(`(module (table 0 funcref) (func) (elem (i32.const 0x10001)))`));

assertSegmentFitError(() => wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (global.get 0) $f0) ${callee(0)})`, {globals:{a:10}}));
assertSegmentFitError(() => wasmEvalText(`(module (import "globals" "a" (global i32)) (table 10 funcref) (elem (global.get 0) $f0 $f0 $f0) ${callee(0)})`, {globals:{a:8}}));

assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 0) $f0) ${callee(0)})`)) instanceof Module, true);
assertEq(new Module(wasmTextToBinary(`(module (table 10 funcref) (elem (i32.const 1) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`)) instanceof Module, true);
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}});
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}});

// Explicit table index in a couple of ways, note this requires us to talk about the table type also.
assertEq(new Module(wasmTextToBinary(`(module
                                        (table 10 funcref)
                                        (table 10 funcref)
                                        (elem 1 (i32.const 1) func $f0 $f0)
                                        ${callee(0)})`)) instanceof Module, true);
assertEq(new Module(wasmTextToBinary(`(module
                                        (table 10 funcref)
                                        (table 10 funcref)
                                        (elem (table 1) (i32.const 1) func $f0 $f0)
                                        ${callee(0)})`)) instanceof Module, true);

// With "funcref" rather than "func".
assertEq(new Module(wasmTextToBinary(`(module
                                        (table 10 funcref)
                                        (table 10 funcref)
                                        (elem (table 1) (i32.const 1) funcref (ref.func $f0) (ref.func $f0))
                                        ${callee(0)})`)) instanceof Module, true);

// Syntax for the offset, ditto.
assertEq(new Module(wasmTextToBinary(`(module
                                        (table 10 funcref)
                                        (table 10 funcref)
                                        (elem 1 (offset (i32.const 1)) func $f0 $f0)
                                        ${callee(0)})`)) instanceof Module, true);

var m = new Module(wasmTextToBinary(`
    (module
        (import "globals" "table" (table 10 funcref))
        (import "globals" "a" (global i32))
        (elem (global.get 0) $f0 $f0)
        ${callee(0)})
`));
var tbl = new Table({initial:50, element:"anyfunc"});
assertEq(new Instance(m, {globals:{a:20, table:tbl}}) instanceof Instance, true);
assertSegmentFitError(() => new Instance(m, {globals:{a:50, table:tbl}}));

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))`
var callee = i => `(func $f${i} (type $v2i) (i32.const ${i}))`;

var call = wasmEvalText(`(module (table 10 funcref) ${callee(0)} ${caller})`).exports.call;
assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/);
assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/);

var call = wasmEvalText(`(module (table 10 funcref) (elem (i32.const 0)) ${callee(0)} ${caller})`).exports.call;
assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/);
assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/);

var call = wasmEvalText(`(module (table 10 funcref) (elem (i32.const 0) $f0) ${callee(0)} ${caller})`).exports.call;
assertEq(call(0), 0);
assertErrorMessage(() => call(1), RuntimeError, /indirect call to null/);
assertErrorMessage(() => call(2), RuntimeError, /indirect call to null/);
assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/);

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;
assertErrorMessage(() => call(0), RuntimeError, /indirect call to null/);
assertEq(call(1), 0);
assertEq(call(2), 1);
assertErrorMessage(() => call(3), RuntimeError, /indirect call to null/);
assertEq(call(4), 0);
assertEq(call(5), 2);
assertErrorMessage(() => call(6), RuntimeError, /indirect call to null/);
assertErrorMessage(() => call(10), RuntimeError, /index out of bounds/);

var imports = {a:{b:()=>42}};
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;
assertEq(call(0), 0);
assertErrorMessage(() => call(1), RuntimeError, /indirect call signature mismatch/);
assertEq(call(2), 42);

var tbl = new Table({initial:3, element:"anyfunc"});
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;
assertEq(call(0), 0);
assertEq(call(1), 1);
assertEq(tbl.get(0)(), 0);
assertEq(tbl.get(1)(), 1);
assertErrorMessage(() => call(2), RuntimeError, /indirect call to null/);
assertEq(tbl.get(2), null);

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;
assertEq(exp.tbl, tbl);
assertEq(exp.call(0), 0);
assertEq(exp.call(1), 1);
assertEq(exp.call(2), 2);
assertEq(call(0), 0);
assertEq(call(1), 1);
assertEq(call(2), 2);
assertEq(tbl.get(0)(), 0);
assertEq(tbl.get(1)(), 1);
assertEq(tbl.get(2)(), 2);

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
assertEq(exp1.tbl.get(0), exp1.f0);
assertEq(exp1.tbl.get(1), exp1.f0);
assertEq(exp1.tbl.get(2), null);
assertEq(exp1.call(0), 0);
assertEq(exp1.call(1), 0);
assertErrorMessage(() => exp1.call(2), RuntimeError, /indirect call to null/);
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
assertEq(exp1.tbl, exp2.tbl);
assertEq(exp2.tbl.get(0), exp1.f0);
assertEq(exp2.tbl.get(1), exp2.f1);
assertEq(exp2.tbl.get(2), exp2.f1);
assertEq(exp1.call(0), 0);
assertEq(exp1.call(1), 1);
assertEq(exp1.call(2), 1);
assertEq(exp2.call(0), 0);
assertEq(exp2.call(1), 1);
assertEq(exp2.call(2), 1);

var tbl = new Table({initial:3, element:"anyfunc"});
var e1 = wasmEvalText(`(module (func $f (result i32) (i32.const 42)) (export "f" (func $f)))`).exports;
var e2 = wasmEvalText(`(module (func $g (result f32) (f32.const 10)) (export "g" (func $g)))`).exports;
var e3 = wasmEvalText(`(module (func $h (result i32) (i32.const 13)) (export "h" (func $h)))`).exports;
tbl.set(0, e1.f);
tbl.set(1, e2.g);
tbl.set(2, e3.h);
var e4 = wasmEvalText(`(module (import "a" "b" (table 3 funcref)) ${caller})`, {a:{b:tbl}}).exports;
assertEq(e4.call(0), 42);
assertErrorMessage(() => e4.call(1), RuntimeError, /indirect call signature mismatch/);
assertEq(e4.call(2), 13);

var asmjsFun = (function() { "use asm"; function f() {} return f })();
assertEq(isAsmJSFunction(asmjsFun), isAsmJSCompilationAvailable());
assertErrorMessage(() => tbl.set(0, asmjsFun), TypeError, badFuncRefError);
assertErrorMessage(() => tbl.grow(1, asmjsFun), TypeError, badFuncRefError);

var m = new Module(wasmTextToBinary(`(module
    (type $i2i (func (param i32) (result i32)))
    (import "a" "mem" (memory 1))
    (import "a" "tbl" (table 10 funcref))
    (import "a" "imp" (func $imp (result i32)))
    (func $call (param $i i32) (result i32)
        (i32.add
            (call $imp)
            (i32.add
                (i32.load (i32.const 0))
                (if (result i32) (i32.eqz (local.get $i))
                    (then (i32.const 0))
                    (else
                        (local.set $i (i32.sub (local.get $i) (i32.const 1)))
                        (call_indirect (type $i2i) (local.get $i) (local.get $i)))))))
    (export "call" (func $call))
)`));
var failTime = false;
var tbl = new Table({initial:10, element:"anyfunc"});
var mem1 = new Memory({initial:1});
var e1 = new Instance(m, {a:{mem:mem1, tbl, imp() {if (failTime) throw new Error("ohai"); return 1}}}).exports;
tbl.set(0, e1.call);
var mem2 = new Memory({initial:1});
var e2 = new Instance(m, {a:{mem:mem2, tbl, imp() {return 10} }}).exports;
tbl.set(1, e2.call);
var mem3 = new Memory({initial:1});
var e3 = new Instance(m, {a:{mem:mem3, tbl, imp() {return 100} }}).exports;
new Int32Array(mem1.buffer)[0] = 1000;
new Int32Array(mem2.buffer)[0] = 10000;
new Int32Array(mem3.buffer)[0] = 100000;
assertEq(e3.call(2), 111111);
failTime = true;
assertErrorMessage(() => e3.call(2), Error, "ohai");

// Call signatures are matched structurally:

var call = wasmEvalText(`(module
    (type $v2i1 (func (result i32)))
    (type $v2i2 (func (result i32)))
    (type $i2v (func (param i32)))
    (table funcref (elem $a $b $c))
    (func $a (type $v2i1) (i32.const 0))
    (func $b (type $v2i2) (i32.const 1))
    (func $c (type $i2v))
    (func $call (param i32) (result i32) (call_indirect (type $v2i1) (local.get 0)))
    (export "call" (func $call))
)`).exports.call;
assertEq(call(0), 0);
assertEq(call(1), 1);
assertErrorMessage(() => call(2), RuntimeError, /indirect call signature mismatch/);

var call = wasmEvalText(`(module
    (type $A (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
    (type $B (func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)))
    (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)))
    (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)))
    (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)))
    (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)))
    (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)))
    (table funcref (elem $a $b $c $d $e $f $g))
    (func $a (type $A) (local.get 7))
    (func $b (type $B) (local.get 8))
    (func $c (type $C) (local.get 9))
    (func $d (type $D) (local.get 10))
    (func $e (type $E) (local.get 11))
    (func $f (type $F) (local.get 12))
    (func $g (type $G) (local.get 13))
    (func $call (param i32) (result i32)
        (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)))
    (export "call" (func $call))
)`).exports.call;
assertEq(call(0), 42);
for (var i = 1; i < 7; i++)
    assertErrorMessage(() => call(i), RuntimeError, /indirect call signature mismatch/);
assertErrorMessage(() => call(7), RuntimeError, /index out of bounds/);

// Function identity isn't lost:
var tbl = wasmEvalText(`(module (table (export "tbl") funcref (elem $f)) (func $f))`).exports.tbl;
tbl.get(0).foo = 42;
gc();
assertEq(tbl.get(0).foo, 42);

(function testCrossRealmCall() {
    var g = newGlobal({sameCompartmentAs: this});

    // The memory.size builtin asserts cx->realm matches instance->realm so
    // we call it here.
    var src = `
        (module
            (import "a" "t" (table 3 funcref))
            (import "a" "m" (memory 1))
            (func $f (result i32) (i32.add (i32.const 3) (memory.size)))
            (elem (i32.const 0) $f))
    `;
    g.mem = new Memory({initial:4});
    g.tbl = new Table({initial:3, element:"anyfunc"});
    var i1 = g.evaluate("new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`" + src + "`)), {a:{t:tbl,m:mem}})");

    var call = new Instance(new Module(wasmTextToBinary(`
        (module
            (import "a" "t" (table 3 funcref))
            (import "a" "m" (memory 1))
            (type $v2i (func (result i32)))
            (func $call (param $i i32) (result i32) (i32.add (call_indirect (type $v2i) (local.get $i)) (memory.size)))
            (export "call" (func $call)))
    `)), {a:{t:g.tbl,m:g.mem}}).exports.call;

    for (var i = 0; i < 10; i++)
        assertEq(call(0), 11);
})();


// Test active segments with a table index.

{
    function makeIt(flag, tblindex) {
        return new Uint8Array([0x00, 0x61, 0x73, 0x6d,
                               0x01, 0x00, 0x00, 0x00,
                               0x04,                   // Table section
                               0x04,                   // Section size
                               0x01,                   // One table
                               0x70,                   // Type: FuncRef
                               0x00,                   // Limits: Min only
                               0x01,                   // Limits: Min
                               0x09,                   // Elements section
                               0x08,                   // Section size
                               0x01,                   // One element segment
                               flag,                   // Flag should be 2, or > 2 if invalid
                               tblindex,               // Table index must be 0, or > 0 if invalid
                               0x41,                   // Init expr: i32.const
                               0x00,                   // Init expr: zero (payload)
                               0x0b,                   // Init expr: end
                               0x00,                   // Extern kind: Function
                               0x00]);                 // Zero functions
    }

    // Should succeed because this is what an active segment with index looks like
    new WebAssembly.Module(makeIt(0x02, 0x00));

    // Should fail because the kind is unknown
    assertErrorMessage(() => new WebAssembly.Module(makeIt(0x08, 0x00)),
                       WebAssembly.CompileError,
                       /invalid elem segment flags field/);

    // Should fail because the table index is invalid
    assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)),
                       WebAssembly.CompileError,
                       /table index out of range/);
}

// table of externref
{
    const myArray = ["yay", "arrays"];
    const { set, get } = wasmEvalText(`(module
        (table $t 2 externref)

        (func (export "set") (param i32 externref)
            (table.set $t (local.get 0) (local.get 1))
        )
        (func (export "get") (param i32) (result externref)
            (table.get $t (local.get 0))
        )
    )`).exports;
    assertEq(get(0), null);
    assertEq(get(1), null);
    set(0, "hello");
    set(1, myArray);
    assertEq(get(0), "hello");
    assertEq(get(1), myArray);
}

// table of externref with active element segment
{
    const myArray = ["yay", "arrays"];
    const { get } = wasmEvalText(`(module
        (global (import "test" "g1") externref)
        (global (import "test" "g2") externref)
        (table $t externref (elem (global.get 0) (global.get 1)))

        (func (export "get") (param i32) (result externref)
            (table.get $t (local.get 0))
        )
    )`, { test: { g1: "hello", g2: myArray } }).exports;
    assertEq(get(0), "hello");
    assertEq(get(1), myArray);
}

// passive element segment of externref
{
    const myArray = ["yay", "arrays"];
    const { get } = wasmEvalText(`(module
        (global (import "test" "g1") externref)
        (global (import "test" "g2") externref)
        (table $t 2 externref)
        (elem $e externref (global.get 0) (global.get 1))

        (func $start
            (table.init $t $e (i32.const 0) (i32.const 0) (table.size $t))
        )
        (func (export "get") (param i32) (result externref)
            (table.get $t (local.get 0))
        )

        (start $start)
    )`, { test: { g1: "hello", g2: myArray } }).exports;
    assertEq(get(0), "hello");
    assertEq(get(1), myArray);
}

// declared element segment of externref (literally useless but legal!)
{
    const myArray = ["yay", "arrays"];
    wasmEvalText(`(module
        (global (import "test" "g1") externref)
        (global (import "test" "g2") externref)
        (elem $e declare externref (global.get 0) (global.get 1))
    )`, { test: { g1: "hello", g2: myArray } });
}

// result types are validated in element expressions
{
    assertErrorMessage(() => wasmEvalText(`(module
        (elem $e externref (ref.func 0))
        (func)
    )`), WebAssembly.CompileError, /expression has type (funcref|\(ref 0\)) but expected externref/);
}

// non-GC: mutable globals are rejected (global.get is constant only if the global is constant)
if (!wasmGcEnabled()) {
    assertErrorMessage(() => {
        wasmEvalText(`(module
            (global (import "test" "g1") (mut externref))
            (global (import "test" "g2") (mut externref))
            (table $t externref (elem (global.get 0) (global.get 1)))

            (func (export "get") (param i32) (result externref)
                (table.get $t (local.get 0))
            )
        )`, { test: { g1: "hello", g2: ["yay", "arrays"] } });
    }, WebAssembly.CompileError, /global.get in initializer expression must reference a global immutable import/);
}