summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/ref-types
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit-test/tests/wasm/ref-types
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/wasm/ref-types')
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-boxing.js140
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-fastpaths.js100
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-global-object.js91
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-global-postbarrier.js89
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-global-prebarrier.js61
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref-val-tracing.js10
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/externref.js482
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/funcref-fastpaths.js76
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/funcref.js135
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/fuzz-gc-while-allocating-global.js3
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/ref-func.js290
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/stackmaps1.js85
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/stackmaps2.js132
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/stackmaps3.js201
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/stackmaps4-params-n-locals.js143
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-api.js93
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-fill.js212
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-generalized.js432
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-multiple.js465
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-postbarrier-grow.js21
-rw-r--r--js/src/jit-test/tests/wasm/ref-types/tables-stress.js46
22 files changed, 3308 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/ref-types/directives.txt b/js/src/jit-test/tests/wasm/ref-types/directives.txt
new file mode 100644
index 0000000000..ed90b6bd5f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/directives.txt
@@ -0,0 +1 @@
+|jit-test| test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; test-also=--disable-wasm-huge-memory; skip-variant-if: --disable-wasm-huge-memory, !wasmHugeMemorySupported(); test-also=--wasm-test-serialization; include:wasm.js
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-boxing.js b/js/src/jit-test/tests/wasm/ref-types/externref-boxing.js
new file mode 100644
index 0000000000..854f3e7815
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-boxing.js
@@ -0,0 +1,140 @@
+// Moving a JS value through a wasm externref is a pair of boxing/unboxing
+// conversions that leaves the value unchanged. There are many cases,
+// along these axes:
+//
+// - global variables
+// - tables
+// - function parameters and returns
+// - struct fields [for the gc feature], see externref-boxing-struct.js
+
+// Global variables can receive values via several mechanisms:
+//
+// - on initialization when created from JS
+// - on initialization when created in Wasm, from an imported global
+// - through the "value" property if the value is mutable
+// - through the global.set wasm instruction, ditto
+//
+// Their values can be obtained in several ways:
+//
+// - through the "value" property
+// - through the global.get wasm instruction
+// - read when other globals are initialized from them
+
+// Set via initialization and read via 'value'
+
+for (let v of WasmExternrefValues)
+{
+ let g = new WebAssembly.Global({value: "externref"}, v);
+ assertEq(g.value, v);
+}
+
+// Set via 'value' and read via 'value'
+
+for (let v of WasmExternrefValues)
+{
+ let g = new WebAssembly.Global({value: "externref", mutable: true});
+ g.value = v;
+ assertEq(g.value, v);
+}
+
+// Set via initialization, then read via global.get and returned
+
+for (let v of WasmExternrefValues)
+{
+ let g = new WebAssembly.Global({value: "externref"}, v);
+ let ins = wasmEvalText(
+ `(module
+ (import "m" "g" (global $glob externref))
+ (func (export "f") (result externref)
+ (global.get $glob)))`,
+ {m:{g}});
+ assertEq(ins.exports.f(), v);
+}
+
+// Set via global.set, then read via 'value'
+
+for (let v of WasmExternrefValues)
+{
+ let g = new WebAssembly.Global({value: "externref", mutable: true});
+ let ins = wasmEvalText(
+ `(module
+ (import "m" "g" (global $glob (mut externref)))
+ (func (export "f") (param $v externref)
+ (global.set $glob (local.get $v))))`,
+ {m:{g}});
+ ins.exports.f(v);
+ assertEq(g.value, v);
+}
+
+// Tables of externref can receive values via several mechanisms:
+//
+// - through WebAssembly.Table.prototype.set()
+// - through the table.set, table.copy, and table.grow instructions
+// - through table.fill
+// - through WebAssembly.Table.prototype.grow()
+//
+// Their values can be read in several ways:
+//
+// - through WebAssembly.Table.prototype.get()
+// - through the table.get and table.copy instructions
+//
+// (Note, tables are always initialized to null when created from JS)
+
+// .set() and .get()
+
+for (let v of WasmExternrefValues)
+{
+ let t = new WebAssembly.Table({element: "externref", initial: 10});
+ t.set(3, v);
+ assertEq(t.get(3), v);
+}
+
+// write with table.set, read with .get()
+
+for (let v of WasmExternrefValues)
+{
+ let t = new WebAssembly.Table({element: "externref", initial: 10});
+ let ins = wasmEvalText(
+ `(module
+ (import "m" "t" (table $t 10 externref))
+ (func (export "f") (param $v externref)
+ (table.set $t (i32.const 3) (local.get $v))))`,
+ {m:{t}});
+ ins.exports.f(v);
+ assertEq(t.get(3), v);
+}
+
+// write with .set(), read with table.get
+
+for (let v of WasmExternrefValues)
+{
+ let t = new WebAssembly.Table({element: "externref", initial: 10});
+ let ins = wasmEvalText(
+ `(module
+ (import "m" "t" (table $t 10 externref))
+ (func (export "f") (result externref)
+ (table.get $t (i32.const 3))))`,
+ {m:{t}});
+ t.set(3, v);
+ assertEq(ins.exports.f(), v);
+}
+
+// Imported JS functions can receive externref values as parameters and return
+// them.
+
+for (let v of WasmExternrefValues)
+{
+ let returner = function () { return v; };
+ let receiver = function (w) { assertEq(w, v); };
+ let ins = wasmEvalText(
+ `(module
+ (import "m" "returner" (func $returner (result externref)))
+ (import "m" "receiver" (func $receiver (param externref)))
+ (func (export "test_returner") (result externref)
+ (call $returner))
+ (func (export "test_receiver") (param $v externref)
+ (call $receiver (local.get $v))))`,
+ {m:{returner, receiver}});
+ assertEq(ins.exports.test_returner(), v);
+ ins.exports.test_receiver(v);
+}
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-fastpaths.js b/js/src/jit-test/tests/wasm/ref-types/externref-fastpaths.js
new file mode 100644
index 0000000000..749a7c788b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-fastpaths.js
@@ -0,0 +1,100 @@
+setJitCompilerOption("baseline.warmup.trigger", 5);
+setJitCompilerOption("ion.warmup.trigger", 10);
+
+var iter = 1000;
+let objs = WasmExternrefValues;
+var index = 0;
+
+// Tests that:
+//
+// * wasm->js calls along the fast path unbox externref correctly on entry to JS.
+// This is tested by JS observing that the value that it receives is the same
+// that was passed into wasm in the first place.
+//
+// * js->wasm calls along the fast path box JS values into externref correctly on
+// entry into wasm. This is tested by the precise same test as the case
+// above.
+//
+// * js->wasm calls along the fast path unbox externref correctly on return from
+// wasm. This is tested by returning the passed-in value and observing in
+// JS that the value is the same as the value that was passed in.
+
+// `p` will be a register argument on most platforms.
+
+function js_externref_regarg(p) {
+ if (p !== objs[index])
+ throw new Error("Bad argument at " + index);
+}
+
+// `p` will be a stack argument on all platforms. In the internal ABI, the
+// externref parameter ends up in a stack slot in the call to js_externref_stackarg,
+// thus we're testing a different path in the stub code. (This holds for
+// architectures with up to 10 register arguments, which covers all tier-1
+// platforms as of 2019.)
+
+function js_externref_stackarg(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, p) {
+ if (p !== objs[index])
+ throw new Error("Bad argument at " + index);
+}
+
+var ins1 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "" "f" (func $f (param externref)))
+ (import "" "g" (func $g (param i32) (param i32) (param i32) (param i32) (param i32)
+ (param i32) (param i32) (param i32) (param i32) (param i32)
+ (param externref)))
+ (func (export "run1") (param externref) (result externref)
+ (call $f (local.get 0))
+ (local.get 0))
+ (func (export "run2") (param externref) (result externref)
+ (call $g (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3) (i32.const 4)
+ (i32.const 5) (i32.const 6) (i32.const 7) (i32.const 8) (i32.const 9)
+ (local.get 0))
+ (local.get 0)))`)),
+ {"":{f: js_externref_regarg, g: js_externref_stackarg}});
+
+index = 0;
+for ( let i=0; i < iter; i++ ) {
+ let res = ins1.exports.run1(objs[index]);
+ if (res !== objs[index])
+ throw new Error("Bad return at " + index);
+ index = (index + 1) % objs.length;
+}
+
+index = 0;
+for ( let i=0; i < iter; i++ ) {
+ let res = ins1.exports.run2(objs[index]);
+ if (res !== objs[index])
+ throw new Error("Bad return at " + index);
+ index = (index + 1) % objs.length;
+}
+
+// Test that:
+//
+// * wasm->js calls along the fast path box JS values into externref correctly on
+// return from JS. This is tested by returing a value from JS through wasm
+// back into JS and then checking that the object is the same as the one that
+// was returned.
+
+function js_returns_externref(p) {
+ return objs[index];
+}
+
+var ins2 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "" "f" (func $f (result externref)))
+ (import "" "g" (func $g (param externref)))
+ (func (export "run1") (result externref)
+ (local $tmp externref)
+ (local.set $tmp (call $f))
+ (call $g (local.get $tmp))
+ (local.get $tmp)))`)),
+ {"":{f: js_returns_externref, g: js_externref_regarg}});
+
+index = 0;
+for ( let i=0; i < iter; i++ ) {
+ let res = ins2.exports.run1();
+ if (res !== objs[index])
+ throw new Error("Bad return at " + index);
+ index = (index + 1) % objs.length;
+}
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js b/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js
new file mode 100644
index 0000000000..806896e9b8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js
@@ -0,0 +1,91 @@
+// |jit-test| skip-if: typeof WebAssembly.Global !== 'function'
+
+// Dummy object.
+function Baguette(calories) {
+ this.calories = calories;
+}
+
+assertEq(new WebAssembly.Global({value: "externref"}) instanceof WebAssembly.Global, true);
+
+(function() {
+ // Test initialization without a value.
+ let g = new WebAssembly.Global({value: "externref"});
+ assertEq(g.value, void 0);
+ assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+})();
+
+(function() {
+ // Test initialization with a value.
+ let g = new WebAssembly.Global({value: "externref"}, null);
+ assertEq(g.value, null);
+ assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+
+ let obj = {};
+ g = new WebAssembly.Global({value: "externref"}, obj);
+ assertEq(g.value, obj);
+ assertErrorMessage(() => g.value = 42, TypeError, /immutable global/);
+
+ g = new WebAssembly.Global({value: "externref"}, 1337);
+ assertEq(typeof g.value, "number");
+ assertEq(+g.value, 1337);
+
+ g = new WebAssembly.Global({value: "externref"}, 13.37);
+ assertEq(typeof g.value, "number");
+ assertEq(+g.value, 13.37);
+
+ g = new WebAssembly.Global({value: "externref"}, "string");
+ assertEq(typeof g.value, "string");
+ assertEq(g.value.toString(), "string");
+
+ g = new WebAssembly.Global({value: "externref"}, true);
+ assertEq(typeof g.value, "boolean");
+ assertEq(!!g.value, true);
+
+ g = new WebAssembly.Global({value: "externref"}, Symbol("status"));
+ assertEq(typeof g.value, "symbol");
+ assertEq(g.value.toString(), "Symbol(status)");
+
+ g = new WebAssembly.Global({value: "externref"}, undefined);
+ assertEq(g.value, undefined);
+})();
+
+(function() {
+ // Test mutable property and assignment.
+ let g = new WebAssembly.Global({value: "externref", mutable: true}, null);
+ assertEq(g.value, null);
+
+ let obj = { x: 42 };
+ g.value = obj;
+ assertEq(g.value, obj);
+ assertEq(g.value.x, 42);
+
+ obj = null;
+ assertEq(g.value.x, 42);
+
+ let otherObj = { y : 35 };
+ g.value = otherObj;
+ assertEq(g.value, otherObj);
+})();
+
+(function() {
+ // Test tracing.
+ let nom = new Baguette(1);
+ let g = new WebAssembly.Global({value: "externref"}, nom);
+ nom = null;
+ gc();
+ assertEq(g.value.calories, 1);
+})();
+
+var global = new WebAssembly.Global({ value: "externref", mutable: true }, null);
+
+// GCZeal mode 2 implies that every allocation (second parameter = every single
+// allocation) will trigger a full GC.
+gczeal(2, 1);
+
+{
+ let nomnom = new Baguette(42);
+ global.value = nomnom;
+ nomnom = null;
+}
+new Baguette();
+assertEq(global.value.calories, 42);
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-global-postbarrier.js b/js/src/jit-test/tests/wasm/ref-types/externref-global-postbarrier.js
new file mode 100644
index 0000000000..06d9521504
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-global-postbarrier.js
@@ -0,0 +1,89 @@
+const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
+
+// Dummy constructor.
+function Baguette(calories) {
+ this.calories = calories;
+}
+
+// Ensure the baseline compiler sync's before the postbarrier.
+(function() {
+ wasmEvalText(`(module
+ (global (mut externref) (ref.null extern))
+ (func (export "f")
+ global.get 0
+ ref.null extern
+ global.set 0
+ global.set 0
+ )
+ )`).exports.f();
+})();
+
+let exportsPlain = wasmEvalText(`(module
+ (global i32 (i32.const 42))
+ (global $g (mut externref) (ref.null extern))
+ (func (export "set") (param externref) local.get 0 global.set $g)
+ (func (export "get") (result externref) global.get $g)
+)`).exports;
+
+let exportsObj = wasmEvalText(`(module
+ (global $g (export "g") (mut externref) (ref.null extern))
+ (func (export "set") (param externref) local.get 0 global.set $g)
+ (func (export "get") (result externref) global.get $g)
+)`).exports;
+
+// 7 => Generational GC zeal.
+gczeal(7, 1);
+
+for (var i = 0; i < 100; i++) {
+ new Baguette(i);
+}
+
+function test(exports) {
+ // Test post-write barrier in wasm code.
+ {
+ let nomnom = new Baguette(15);
+ exports.set(nomnom);
+ nomnom = null;
+ }
+ new Baguette();
+ assertEq(exports.get().calories, 15);
+}
+
+test(exportsPlain);
+test(exportsObj);
+
+// Test stacks reported in profiling mode in a separate way, to not perturb
+// the behavior of the tested functions.
+if (!isSingleStepProfilingEnabled)
+ quit(0);
+
+enableGeckoProfiling();
+
+const EXPECTED_STACKS = [
+ // Expected output for (simulator+baseline).
+ ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>',
+ '<,0,!>', '0,!>', '!>', ''],
+
+ // Expected output for (simulator+via-Ion).
+ ['', '!>', '0,!>', '<,0,!>', 'filtering GC postbarrier,0,!>',
+ '<,0,!>', '0,!>', '!>', ''],
+
+ // Expected output for other configurations.
+ ['', '!>', '0,!>', '!>', ''],
+];
+
+function testStacks(exports) {
+ // Test post-write barrier in wasm code.
+ {
+ let nomnom = new Baguette(15);
+ startProfiling();
+ exports.set(nomnom);
+ assertEqPreciseStacks(endProfiling(), EXPECTED_STACKS);
+ nomnom = null;
+ }
+ new Baguette();
+ assertEq(exports.get().calories, 15);
+}
+
+testStacks(exportsPlain);
+testStacks(exportsObj);
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-global-prebarrier.js b/js/src/jit-test/tests/wasm/ref-types/externref-global-prebarrier.js
new file mode 100644
index 0000000000..139f71e758
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-global-prebarrier.js
@@ -0,0 +1,61 @@
+// Do not run the test if we're jit-compiling JS, since it's the wasm frames
+// we're interested in and eager JS compilation can upset the test.
+
+opts = getJitCompilerOptions();
+if (opts['ion.enable'] || opts['baseline.enable'])
+ quit();
+
+const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
+
+let e = wasmEvalText(`(module
+ (global $g (mut externref) (ref.null extern))
+ (func (export "set") (param externref) local.get 0 global.set $g)
+)`).exports;
+
+let obj = { field: null };
+
+// GCZeal mode 4 implies that prebarriers are being verified at many
+// locations in the interpreter, during interrupt checks, etc. It can be ultra
+// slow, so disable it with gczeal(0) when it's not strictly needed.
+gczeal(4, 1);
+e.set(obj);
+e.set(null);
+gczeal(0);
+
+if (!isSingleStepProfilingEnabled) {
+ quit(0);
+}
+
+enableGeckoProfiling();
+startProfiling();
+gczeal(4, 1);
+e.set(obj);
+gczeal(0);
+assertEqPreciseStacks(
+ endProfiling(),
+ [
+ // Expected output for (simulator+via-Ion/baseline).
+ ['', '!>', '0,!>', '<,0,!>', 'GC postbarrier,0,!>',
+ '<,0,!>', '0,!>', '!>', ''],
+
+ // Expected output for other configurations.
+ ['', '!>', '0,!>', '!>', ''],
+ ]);
+
+startProfiling();
+gczeal(4, 1);
+e.set(null);
+gczeal(0);
+
+// We're losing stack info in the prebarrier code.
+assertEqPreciseStacks(
+ endProfiling(),
+ [
+ // Expected output for (simulator+via-Ion/baseline).
+ ['', '!>', '0,!>', '', '0,!>', '<,0,!>', 'GC postbarrier,0,!>',
+ '<,0,!>', '0,!>', '!>', ''],
+
+ // Expected output for other configurations.
+ ['', '!>', '0,!>', '', '0,!>', '!>', ''],
+ ]);
+
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-val-tracing.js b/js/src/jit-test/tests/wasm/ref-types/externref-val-tracing.js
new file mode 100644
index 0000000000..3c7e596d12
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref-val-tracing.js
@@ -0,0 +1,10 @@
+gczeal(14, 1);
+let { exports } = wasmEvalText(`(module
+ (global $externref (import "glob" "externref") externref)
+ (func (export "get") (result externref) global.get $externref)
+)`, {
+ glob: {
+ externref: { sentinel: "lol" },
+ }
+});
+assertEq(exports.get().sentinel, "lol");
diff --git a/js/src/jit-test/tests/wasm/ref-types/externref.js b/js/src/jit-test/tests/wasm/ref-types/externref.js
new file mode 100644
index 0000000000..695279da73
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/externref.js
@@ -0,0 +1,482 @@
+// Dummy constructor.
+function Baguette(calories) {
+ this.calories = calories;
+}
+
+// Type checking.
+
+const { validate, CompileError } = WebAssembly;
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (func (result externref)
+ i32.const 42
+ )
+)`), CompileError, mismatchError('i32', 'externref'));
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (func (result externref)
+ i32.const 0
+ ref.null extern
+ i32.const 42
+ select (result externref)
+ )
+)`), CompileError, /type mismatch/);
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (func (result i32)
+ ref.null extern
+ if
+ i32.const 42
+ end
+ )
+)`), CompileError, mismatchError('externref', 'i32'));
+
+
+// Basic compilation tests.
+
+let simpleTests = [
+ "(module (func (drop (ref.null extern))))",
+ "(module (func $test (local externref)))",
+ "(module (func $test (param externref)))",
+ "(module (func $test (result externref) (ref.null extern)))",
+ "(module (func $test (block (result externref) (unreachable)) unreachable))",
+ "(module (func $test (result i32) (local externref) (ref.is_null (local.get 0))))",
+ `(module (import "a" "b" (func (param externref))))`,
+ `(module (import "a" "b" (func (result externref))))`,
+ `(module (global externref (ref.null extern)))`,
+ `(module (global (mut externref) (ref.null extern)))`,
+];
+
+for (let src of simpleTests) {
+ wasmEvalText(src, {a:{b(){}}});
+ assertEq(validate(wasmTextToBinary(src)), true);
+}
+
+// Basic behavioral tests.
+
+let { exports } = wasmEvalText(`(module
+ (func (export "is_null") (result i32)
+ ref.null extern
+ ref.is_null
+ )
+
+ (func $sum (param i32) (result i32)
+ local.get 0
+ i32.const 42
+ i32.add
+ )
+
+ (func (export "is_null_spill") (result i32)
+ ref.null extern
+ i32.const 58
+ call $sum
+ drop
+ ref.is_null
+ )
+
+ (func (export "is_null_local") (result i32) (local externref)
+ ref.null extern
+ local.set 0
+ i32.const 58
+ call $sum
+ drop
+ local.get 0
+ ref.is_null
+ )
+ )`);
+
+assertEq(exports.is_null(), 1);
+assertEq(exports.is_null_spill(), 1);
+assertEq(exports.is_null_local(), 1);
+
+// ExternRef param and result in wasm functions.
+
+exports = wasmEvalText(`(module
+ (func (export "is_null") (param $ref externref) (result i32)
+ local.get $ref
+ ref.is_null
+ )
+
+ (func (export "ref_or_null") (param $ref externref) (param $selector i32) (result externref)
+ local.get $ref
+ ref.null extern
+ local.get $selector
+ select (result externref)
+ )
+
+ (func $recursive (export "nested") (param $ref externref) (param $i i32) (result externref)
+ ;; i == 10 => ret $ref
+ local.get $i
+ i32.const 10
+ i32.eq
+ if
+ local.get $ref
+ return
+ end
+
+ local.get $ref
+
+ local.get $i
+ i32.const 1
+ i32.add
+
+ call $recursive
+ )
+)`).exports;
+
+assertEq(exports.is_null(undefined), 0);
+assertEq(exports.is_null(null), 1);
+assertEq(exports.is_null({}), 0);
+assertEq(exports.is_null("hi"), 0);
+assertEq(exports.is_null(3), 0);
+assertEq(exports.is_null(3.5), 0);
+assertEq(exports.is_null(true), 0);
+assertEq(exports.is_null(Symbol("croissant")), 0);
+assertEq(exports.is_null(new Baguette(100)), 0);
+
+let baguette = new Baguette(42);
+assertEq(exports.ref_or_null(null, 0), null);
+assertEq(exports.ref_or_null(baguette, 0), null);
+
+let ref = exports.ref_or_null(baguette, 1);
+assertEq(ref, baguette);
+assertEq(ref.calories, baguette.calories);
+
+ref = exports.nested(baguette, 0);
+assertEq(ref, baguette);
+assertEq(ref.calories, baguette.calories);
+
+// Make sure grow-memory isn't blocked by the lack of gc.
+(function() {
+ assertEq(wasmEvalText(`(module
+ (memory 0 64)
+ (func (export "f") (param externref) (result i32)
+ i32.const 10
+ memory.grow
+ drop
+ memory.size
+ )
+)`).exports.f({}), 10);
+})();
+
+// More interesting use cases about control flow joins.
+
+function assertJoin(body) {
+ let val = { i: -1 };
+ assertEq(wasmEvalText(`(module
+ (func (export "test") (param $ref externref) (param $i i32) (result externref)
+ ${body}
+ )
+ )`).exports.test(val), val);
+ assertEq(val.i, -1);
+}
+
+assertJoin("(block (result externref) local.get $ref)");
+assertJoin("(block $out (result externref) local.get $ref br $out)");
+assertJoin("(loop (result externref) local.get $ref)");
+
+assertJoin(`(block $out (result externref) (loop $top (result externref)
+ local.get $i
+ i32.const 1
+ i32.add
+ tee_local $i
+ i32.const 10
+ i32.eq
+ if
+ local.get $ref
+ return
+ end
+ br $top))
+`);
+
+assertJoin(`(block $out (loop $top
+ local.get $i
+ i32.const 1
+ i32.add
+ tee_local $i
+ i32.const 10
+ i32.le_s
+ if
+ br $top
+ else
+ local.get $ref
+ return
+ end
+ )) unreachable
+`);
+
+assertJoin(`(block $out (result externref) (loop $top
+ local.get $ref
+ local.get $i
+ i32.const 1
+ i32.add
+ tee_local $i
+ i32.const 10
+ i32.eq
+ br_if $out
+ br $top
+ ) unreachable)
+`);
+
+assertJoin(`(block $out (result externref) (block $unreachable (result externref) (loop $top
+ local.get $ref
+ local.get $i
+ i32.const 1
+ i32.add
+ tee_local $i
+ br_table $unreachable $out
+ ) unreachable))
+`);
+
+let x = { i: 42 }, y = { f: 53 };
+exports = wasmEvalText(`(module
+ (func (export "test") (param $lhs externref) (param $rhs externref) (param $i i32) (result externref)
+ local.get $lhs
+ local.get $rhs
+ local.get $i
+ select (result externref)
+ )
+)`).exports;
+
+let result = exports.test(x, y, 0);
+assertEq(result, y);
+assertEq(result.i, undefined);
+assertEq(result.f, 53);
+assertEq(x.i, 42);
+
+result = exports.test(x, y, 1);
+assertEq(result, x);
+assertEq(result.i, 42);
+assertEq(result.f, undefined);
+assertEq(y.f, 53);
+
+// ExternRef in params/result of imported functions.
+
+let firstBaguette = new Baguette(13),
+ secondBaguette = new Baguette(37);
+
+let imports = {
+ i: 0,
+ myBaguette: null,
+ funcs: {
+ param(x) {
+ if (this.i === 0) {
+ assertEq(x, firstBaguette);
+ assertEq(x.calories, 13);
+ assertEq(secondBaguette !== null, true);
+ } else if (this.i === 1 || this.i === 2) {
+ assertEq(x, secondBaguette);
+ assertEq(x.calories, 37);
+ assertEq(firstBaguette !== null, true);
+ } else if (this.i === 3) {
+ assertEq(x, null);
+ } else {
+ firstBaguette = null;
+ secondBaguette = null;
+ gc(); // evil mode
+ }
+ this.i++;
+ },
+ ret() {
+ return imports.myBaguette;
+ }
+ }
+};
+
+exports = wasmEvalText(`(module
+ (import "funcs" "ret" (func $ret (result externref)))
+ (import "funcs" "param" (func $param (param externref)))
+
+ (func (export "param") (param $x externref) (param $y externref)
+ local.get $y
+ local.get $x
+ call $param
+ call $param
+ )
+
+ (func (export "ret") (result externref)
+ call $ret
+ )
+)`, imports).exports;
+
+exports.param(firstBaguette, secondBaguette);
+exports.param(secondBaguette, null);
+exports.param(firstBaguette, secondBaguette);
+
+imports.myBaguette = null;
+assertEq(exports.ret(), null);
+
+imports.myBaguette = new Baguette(1337);
+assertEq(exports.ret(), imports.myBaguette);
+
+// Check lazy stubs generation.
+
+exports = wasmEvalText(`(module
+ (import "funcs" "mirror" (func $mirror (param externref) (result externref)))
+ (import "funcs" "augment" (func $augment (param externref) (result externref)))
+
+ (global $count_f (mut i32) (i32.const 0))
+ (global $count_g (mut i32) (i32.const 0))
+
+ (func $f (param $param externref) (result externref)
+ i32.const 1
+ global.get $count_f
+ i32.add
+ global.set $count_f
+
+ local.get $param
+ call $augment
+ )
+
+ (func $g (param $param externref) (result externref)
+ i32.const 1
+ global.get $count_g
+ i32.add
+ global.set $count_g
+
+ local.get $param
+ call $mirror
+ )
+
+ (table (export "table") 10 funcref)
+ (elem (i32.const 0) $f $g $mirror $augment)
+ (type $table_type (func (param externref) (result externref)))
+
+ (func (export "call_indirect") (param $i i32) (param $ref externref) (result externref)
+ local.get $ref
+ local.get $i
+ call_indirect (type $table_type)
+ )
+
+ (func (export "count_f") (result i32) global.get $count_f)
+ (func (export "count_g") (result i32) global.get $count_g)
+)`, {
+ funcs: {
+ mirror(x) {
+ return x;
+ },
+ augment(x) {
+ x.i++;
+ x.newProp = "hello";
+ return x;
+ }
+ }
+}).exports;
+
+x = { i: 19 };
+assertEq(exports.table.get(0)(x), x);
+assertEq(x.i, 20);
+assertEq(x.newProp, "hello");
+assertEq(exports.count_f(), 1);
+assertEq(exports.count_g(), 0);
+
+x = { i: 21 };
+assertEq(exports.table.get(1)(x), x);
+assertEq(x.i, 21);
+assertEq(typeof x.newProp, "undefined");
+assertEq(exports.count_f(), 1);
+assertEq(exports.count_g(), 1);
+
+x = { i: 22 };
+assertEq(exports.table.get(2)(x), x);
+assertEq(x.i, 22);
+assertEq(typeof x.newProp, "undefined");
+assertEq(exports.count_f(), 1);
+assertEq(exports.count_g(), 1);
+
+x = { i: 23 };
+assertEq(exports.table.get(3)(x), x);
+assertEq(x.i, 24);
+assertEq(x.newProp, "hello");
+assertEq(exports.count_f(), 1);
+assertEq(exports.count_g(), 1);
+
+// Globals.
+
+// ExternRef globals in wasm modules.
+
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "externref") externref))`, { glob: { externref: new WebAssembly.Global({ value: 'i32' }, 42) } }),
+ WebAssembly.LinkError,
+ /imported global type mismatch/);
+
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "i32") i32))`, { glob: { i32: {} } }),
+ WebAssembly.LinkError,
+ /import object field 'i32' is not a Number/);
+
+imports = {
+ constants: {
+ imm_null: null,
+ imm_bread: new Baguette(321),
+ mut_null: new WebAssembly.Global({ value: "externref", mutable: true }, null),
+ mut_bread: new WebAssembly.Global({ value: "externref", mutable: true }, new Baguette(123))
+ }
+};
+
+exports = wasmEvalText(`(module
+ (global $g_imp_imm_null (import "constants" "imm_null") externref)
+ (global $g_imp_imm_bread (import "constants" "imm_bread") externref)
+
+ (global $g_imp_mut_null (import "constants" "mut_null") (mut externref))
+ (global $g_imp_mut_bread (import "constants" "mut_bread") (mut externref))
+
+ (global $g_imm_null externref (ref.null extern))
+ (global $g_imm_getglob externref (global.get $g_imp_imm_bread))
+ (global $g_mut (mut externref) (ref.null extern))
+
+ (func (export "imm_null") (result externref) global.get $g_imm_null)
+ (func (export "imm_getglob") (result externref) global.get $g_imm_getglob)
+
+ (func (export "imp_imm_null") (result externref) global.get $g_imp_imm_null)
+ (func (export "imp_imm_bread") (result externref) global.get $g_imp_imm_bread)
+ (func (export "imp_mut_null") (result externref) global.get $g_imp_mut_null)
+ (func (export "imp_mut_bread") (result externref) global.get $g_imp_mut_bread)
+
+ (func (export "set_imp_null") (param externref) local.get 0 global.set $g_imp_mut_null)
+ (func (export "set_imp_bread") (param externref) local.get 0 global.set $g_imp_mut_bread)
+
+ (func (export "set_mut") (param externref) local.get 0 global.set $g_mut)
+ (func (export "get_mut") (result externref) global.get $g_mut)
+)`, imports).exports;
+
+assertEq(exports.imp_imm_null(), imports.constants.imm_null);
+assertEq(exports.imp_imm_bread(), imports.constants.imm_bread);
+
+assertEq(exports.imm_null(), null);
+assertEq(exports.imm_getglob(), imports.constants.imm_bread);
+
+assertEq(exports.imp_mut_null(), imports.constants.mut_null.value);
+assertEq(exports.imp_mut_bread(), imports.constants.mut_bread.value);
+
+let brandNewBaguette = new Baguette(1000);
+exports.set_imp_null(brandNewBaguette);
+assertEq(exports.imp_mut_null(), brandNewBaguette);
+assertEq(exports.imp_mut_bread(), imports.constants.mut_bread.value);
+
+exports.set_imp_bread(null);
+assertEq(exports.imp_mut_null(), brandNewBaguette);
+assertEq(exports.imp_mut_bread(), null);
+
+assertEq(exports.get_mut(), null);
+let glutenFreeBaguette = new Baguette("calories-free bread");
+exports.set_mut(glutenFreeBaguette);
+assertEq(exports.get_mut(), glutenFreeBaguette);
+assertEq(exports.get_mut().calories, "calories-free bread");
+
+// Make sure that dead code doesn't prevent compilation.
+wasmEvalText(
+ `(module
+ (func
+ (return)
+ (ref.null extern)
+ (drop)
+ )
+ )`);
+
+wasmEvalText(
+ `(module
+ (func (param externref)
+ (return)
+ (ref.is_null (get_local 0))
+ (drop)
+ )
+ )`);
diff --git a/js/src/jit-test/tests/wasm/ref-types/funcref-fastpaths.js b/js/src/jit-test/tests/wasm/ref-types/funcref-fastpaths.js
new file mode 100644
index 0000000000..1650deda3f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/funcref-fastpaths.js
@@ -0,0 +1,76 @@
+// Test the fast paths along which funcref values flow between wasm and JS.
+// Currently, these paths always go from wasm into JS; never from JS into wasm.
+
+setJitCompilerOption("baseline.warmup.trigger", 5);
+setJitCompilerOption("ion.warmup.trigger", 10);
+
+var iter = 10000;
+var objs = [];
+var index = 0;
+
+var ins0 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func $f (export "f") (result i32) (i32.const 37))
+ (func $g (export "g") (result i32) (i32.const 42)))`)));
+objs.push(ins0.exports.f);
+objs.push(ins0.exports.g);
+
+// Tests that:
+//
+// * wasm->js calls along the fast path pass funcref correctly on entry to JS.
+// This is tested by JS observing that the value that it receives is the same
+// that was passed into wasm in the first place.
+//
+// * js->wasm calls along the fast path pass funcref correctly on return from
+// wasm. This is tested by returning the passed-in value and observing in
+// JS that the value is the same as the value that was passed in.
+
+// `p` will be a register argument on most platforms.
+
+function js_funcref_regarg(p) {
+ if (p !== objs[index])
+ throw new Error("Bad argument at " + index);
+}
+
+// `p` will be a stack argument on all platforms. In the internal ABI, the
+// funcref parameter ends up in a stack slot in the call to js_funcref_stackarg,
+// thus we're testing a different path in the stub code. (This holds for
+// architectures with up to 10 register arguments, which covers all tier-1
+// platforms as of 2019.)
+
+function js_funcref_stackarg(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, p) {
+ if (p !== objs[index])
+ throw new Error("Bad argument at " + index);
+}
+
+var ins1 = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "" "f" (func $f (param funcref)))
+ (import "" "g" (func $g (param i32) (param i32) (param i32) (param i32) (param i32)
+ (param i32) (param i32) (param i32) (param i32) (param i32)
+ (param funcref)))
+ (func (export "run1") (param funcref) (result funcref)
+ (call $f (local.get 0))
+ (local.get 0))
+ (func (export "run2") (param funcref) (result funcref)
+ (call $g (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3) (i32.const 4)
+ (i32.const 5) (i32.const 6) (i32.const 7) (i32.const 8) (i32.const 9)
+ (local.get 0))
+ (local.get 0)))`)),
+ {"":{f: js_funcref_regarg, g: js_funcref_stackarg}});
+
+index = 0;
+for ( let i=0; i < iter; i++ ) {
+ let res = ins1.exports.run1(objs[index]);
+ if (res !== objs[index])
+ throw new Error("Bad return at " + index);
+ index = (index + 1) % objs.length;
+}
+
+index = 0;
+for ( let i=0; i < iter; i++ ) {
+ let res = ins1.exports.run2(objs[index]);
+ if (res !== objs[index])
+ throw new Error("Bad return at " + index);
+ index = (index + 1) % objs.length;
+}
diff --git a/js/src/jit-test/tests/wasm/ref-types/funcref.js b/js/src/jit-test/tests/wasm/ref-types/funcref.js
new file mode 100644
index 0000000000..21ce95c0cb
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/funcref.js
@@ -0,0 +1,135 @@
+const {Module,Instance,Global,RuntimeError} = WebAssembly;
+
+const badWasmFunc = /can only pass WebAssembly exported functions to funcref/;
+const typeErr = /type mismatch/;
+
+
+// Validation:
+
+wasmFailValidateText(`(module (func (local externref funcref) (local.set 0 (local.get 1))))`, typeErr);
+wasmEvalText(`(module (func (local funcref funcref) (local.set 0 (local.get 1))))`);
+wasmEvalText(`(module (func (local funcref) (local.set 0 (ref.null func))))`);
+wasmFailValidateText(`(module (func (local funcref externref) (local.set 0 (local.get 1))))`, typeErr);
+wasmEvalText(`(module (global (mut funcref) (ref.null func)) (func (param funcref) (global.set 0 (local.get 0))))`);
+wasmFailValidateText(`(module (global (mut externref) (ref.null extern)) (func (param funcref) (global.set 0 (local.get 0))))`, typeErr);
+wasmFailValidateText(`(module (global (mut funcref) (ref.null func)) (func (param externref) (global.set 0 (local.get 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref)) (func (param funcref) (call 0 (local.get 0))))`);
+wasmFailValidateText(`(module (func (param externref)) (func (param funcref) (call 0 (local.get 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param funcref)) (func (param externref) (call 0 (local.get 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref) (result funcref) (block (result funcref) (local.get 0) (br 0))))`);
+wasmFailValidateText(`(module (func (param funcref) (result externref) (block (result externref) (local.get 0) (br 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param externref) (result externref) (block (result funcref) (local.get 0) (br 0))))`, typeErr);
+wasmEvalText(`(module (func (param funcref funcref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`);
+wasmFailValidateText(`(module (func (param externref funcref) (result externref) (select (result externref) (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param funcref externref) (result externref) (select (result externref)(local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param externref funcref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+wasmFailValidateText(`(module (func (param funcref externref) (result funcref) (select (result funcref) (local.get 0) (local.get 1) (i32.const 0))))`, typeErr);
+
+
+// Runtime:
+
+var m = new Module(wasmTextToBinary(`(module (func (export "wasmFun")))`));
+const wasmFun1 = new Instance(m).exports.wasmFun;
+const wasmFun2 = new Instance(m).exports.wasmFun;
+const wasmFun3 = new Instance(m).exports.wasmFun;
+
+var run = wasmEvalText(`(module
+ (global (mut funcref) (ref.null func))
+ (func (param $x funcref) (param $test i32) (result funcref)
+ local.get $x
+ global.get 0
+ local.get $test
+ select (result funcref)
+ )
+ (func (export "run") (param $a funcref) (param $b funcref) (param $c funcref) (param $test1 i32) (param $test2 i32) (result funcref)
+ local.get $a
+ global.set 0
+ block (result funcref)
+ local.get $b
+ local.get $test1
+ br_if 0
+ drop
+ local.get $c
+ end
+ local.get $test2
+ call 0
+ )
+)`).exports.run;
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, false, false), wasmFun1);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, true, false), wasmFun1);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, true, true), wasmFun2);
+assertEq(run(wasmFun1, wasmFun2, wasmFun3, false, true), wasmFun3);
+
+var run = wasmEvalText(`(module
+ (type $t0 (func (param externref) (result externref)))
+ (type $t1 (func (param funcref) (result externref)))
+ (type $t2 (func (param funcref funcref) (result externref)))
+ (func $f0 (type $t0) ref.null extern)
+ (func $f1 (type $t1) ref.null extern)
+ (func $f2 (type $t2) ref.null extern)
+ (table funcref (elem $f0 $f1 $f2))
+ (func (export "run") (param i32 i32) (result externref)
+ block $b2 block $b1 block $b0
+ local.get 0
+ br_table $b0 $b1 $b2
+ end $b0
+ ref.null extern
+ local.get 1
+ call_indirect (type $t0)
+ return
+ end $b1
+ ref.null func
+ local.get 1
+ call_indirect (type $t1)
+ return
+ end $b2
+ ref.null func
+ ref.null func
+ local.get 1
+ call_indirect (type $t2)
+ return
+ )
+)`).exports.run;
+
+for (var i = 0; i < 3; i++) {
+ for (var j = 0; j < 3; j++) {
+ if (i == j)
+ assertEq(run(i, j), null);
+ else
+ assertErrorMessage(() => run(i, j), RuntimeError, /indirect call signature mismatch/);
+ }
+}
+
+
+// JS API:
+
+const wasmFun = wasmEvalText(`(module (func (export "x")))`).exports.x;
+
+var run = wasmEvalText(`(module (func (export "run") (param funcref) (result funcref) (local.get 0)))`).exports.run;
+assertEq(run(wasmFun), wasmFun);
+assertEq(run(null), null);
+assertErrorMessage(() => run(() => {}), TypeError, badWasmFunc);
+
+var importReturnValue;
+var importFun = () => importReturnValue;
+var run = wasmEvalText(`(module (func (import "" "i") (result funcref)) (func (export "run") (result funcref) (call 0)))`, {'':{i:importFun}}).exports.run;
+importReturnValue = wasmFun;
+assertEq(run(), wasmFun);
+importReturnValue = null;
+assertEq(run(), null);
+importReturnValue = undefined;
+assertErrorMessage(() => run(), TypeError, badWasmFunc);
+importReturnValue = () => {};
+assertErrorMessage(() => run(), TypeError, badWasmFunc);
+
+var g = new Global({value:'anyfunc', mutable:true}, wasmFun);
+assertEq(g.value, wasmFun);
+g.value = null;
+assertEq(g.value, null);
+Math.sin();
+assertErrorMessage(() => g.value = () => {}, TypeError, badWasmFunc);
+var g = new Global({value:'anyfunc', mutable:true}, null);
+assertEq(g.value, null);
+g.value = wasmFun;
+assertEq(g.value, wasmFun);
+assertErrorMessage(() => new Global({value:'anyfunc'}, () => {}), TypeError, badWasmFunc);
diff --git a/js/src/jit-test/tests/wasm/ref-types/fuzz-gc-while-allocating-global.js b/js/src/jit-test/tests/wasm/ref-types/fuzz-gc-while-allocating-global.js
new file mode 100644
index 0000000000..0b51622397
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/fuzz-gc-while-allocating-global.js
@@ -0,0 +1,3 @@
+enableShellAllocationMetadataBuilder();
+gczeal(9, 1);
+new WebAssembly.Global({ value: 'i32' }, 42);
diff --git a/js/src/jit-test/tests/wasm/ref-types/ref-func.js b/js/src/jit-test/tests/wasm/ref-types/ref-func.js
new file mode 100644
index 0000000000..de874669b3
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/ref-func.js
@@ -0,0 +1,290 @@
+load(libdir + "wasm-binary.js");
+
+const v2vSig = {args:[], ret:VoidCode};
+const v2vSigSection = sigSection([v2vSig]);
+
+// 'ref.func' parses, validates and returns a non-null value
+wasmFullPass(`
+ (module
+ (elem declare $run)
+ (func $run (result i32)
+ ref.func $run
+ ref.is_null
+ )
+ (export "run" (func $run))
+ )
+`, 0);
+
+// function returning reference to itself
+{
+ let {f1} = wasmEvalText(`
+ (module
+ (elem declare $f1)
+ (func $f1 (result funcref) ref.func $f1)
+ (export "f1" (func $f1))
+ )
+ `).exports;
+ assertEq(f1(), f1);
+}
+
+// function returning reference to a different function
+{
+ let {f1, f2} = wasmEvalText(`
+ (module
+ (elem declare $f1)
+ (func $f1)
+ (func $f2 (result funcref) ref.func $f1)
+ (export "f1" (func $f1))
+ (export "f2" (func $f2))
+ )
+ `).exports;
+ assertEq(f2(), f1);
+}
+
+// function returning reference to function in a different module
+{
+ let i1 = wasmEvalText(`
+ (module
+ (elem declare $f1)
+ (func $f1)
+ (export "f1" (func $f1))
+ )
+ `);
+ let i2 = wasmEvalText(`
+ (module
+ (import "" "f1" (func $f1))
+ (elem declare $f1)
+ (func $f2 (result funcref) ref.func $f1)
+ (export "f1" (func $f1))
+ (export "f2" (func $f2))
+ )
+ `, {"": i1.exports});
+
+ let f1 = i1.exports.f1;
+ let f2 = i2.exports.f2;
+ assertEq(f2(), f1);
+}
+
+// function index must be valid
+assertErrorMessage(() => {
+ wasmEvalText(`
+ (module
+ (func (result funcref) ref.func 10)
+ )
+ `);
+}, WebAssembly.CompileError, /(function index out of range)|(function index out of bounds)/);
+
+function validFuncRefText(forwardDeclare, tbl_type) {
+ return wasmEvalText(`
+ (module
+ (table 1 ${tbl_type})
+ (func $test (result funcref) ref.func $referenced)
+ (func $referenced)
+ ${forwardDeclare}
+ )
+ `);
+}
+
+// referenced function must be forward declared somehow
+assertErrorMessage(() => validFuncRefText('', 'funcref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/);
+
+// referenced function can be forward declared via segments
+assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem declare $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
+
+// also when the segment is passive or active 'funcref'
+assertEq(validFuncRefText('(elem 0 (i32.const 0) funcref (ref.func $referenced))', 'funcref') instanceof WebAssembly.Instance, true);
+assertEq(validFuncRefText('(elem funcref (ref.func $referenced))', 'funcref') instanceof WebAssembly.Instance, true);
+
+// reference function can be forward declared via globals
+assertEq(validFuncRefText('(global funcref (ref.func $referenced))', 'externref') instanceof WebAssembly.Instance, true);
+
+// reference function can be forward declared via export
+assertEq(validFuncRefText('(export "referenced" (func $referenced))', 'externref') instanceof WebAssembly.Instance, true);
+
+// reference function cannot be forward declared via start
+assertErrorMessage(() => validFuncRefText('(start $referenced)', 'externref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/);
+
+// Tests not expressible in the text format.
+
+// element segment with elemexpr can carry non-reference type, but this must be
+// rejected.
+
+assertErrorMessage(() => new WebAssembly.Module(
+ moduleWithSections([generalElemSection([{ flag: PassiveElemExpr,
+ typeCode: I32Code,
+ elems: [] }])])),
+ WebAssembly.CompileError,
+ /bad type/);
+
+// Test case for bug 1596026: when taking the ref.func of an imported function,
+// the value obtained should not be the JS function. This would assert (even in
+// a release build), so the test is merely that the code runs.
+
+var ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`
+ (module
+ (import "m" "f" (func $f (param i32) (result i32)))
+ (elem declare $f)
+ (table 1 funcref)
+ (func (export "f")
+ (table.set 0 (i32.const 0) (ref.func $f))))`)),
+ {m:{f:(x) => 37+x}});
+ins.exports.f();
+
+// Verification that we can handle encoding errors for passive element segments
+// properly.
+
+function checkPassiveElemSegment(mangle, err) {
+ let bin = moduleWithSections(
+ [v2vSigSection, declSection([0]), // One function
+ tableSection(1), // One table
+ { name: elemId, // One passive segment
+ body: (function () {
+ let body = [];
+ body.push(1); // 1 element segment
+ body.push(0x1 | 0x4); // Flags: Passive and uses element expression
+ body.push(AnyFuncCode + (mangle == "type" ? 1 : 0)); // always anyfunc
+ body.push(1); // Element count
+ body.push(RefFuncCode + (mangle == "ref.func" ? 1 : 0)); // always ref.func
+ body.push(0); // func index
+ body.push(EndCode + (mangle == "end" ? 1 : 0));
+ return body;
+ })() },
+ bodySection( // Empty function
+ [funcBody(
+ {locals:[],
+ body:[]})])
+ ]);
+ if (err) {
+ assertErrorMessage(() => new WebAssembly.Module(bin),
+ WebAssembly.CompileError,
+ err);
+ } else {
+ new WebAssembly.Module(bin);
+ }
+}
+
+checkPassiveElemSegment("");
+checkPassiveElemSegment("type", /bad type/);
+checkPassiveElemSegment("ref.func", /failed to read initializer operation/);
+checkPassiveElemSegment("end", /failed to read end of initializer expression/);
+
+// Passive element segments can contain literal null values.
+
+{
+ let txt =
+ `(module
+ (table (export "t") 10 funcref)
+ (elem (i32.const 1) $m)
+ (elem (i32.const 3) $m)
+ (elem (i32.const 6) $m)
+ (elem (i32.const 8) $m)
+ (elem funcref (ref.func $f) (ref.null func) (ref.func $g) (ref.null func) (ref.func $h))
+ (func $m)
+ (func $f)
+ (func $g)
+ (func $h)
+ (func (export "doit") (param $idx i32)
+ (table.init 4 (local.get $idx) (i32.const 0) (i32.const 5))))`;
+ let ins = wasmEvalText(txt);
+ ins.exports.doit(0);
+ ins.exports.doit(5);
+ assertEq(typeof ins.exports.t.get(0), "function");
+ assertEq(ins.exports.t.get(1), null);
+ assertEq(typeof ins.exports.t.get(2), "function");
+ assertEq(ins.exports.t.get(0) == ins.exports.t.get(2), false);
+ assertEq(ins.exports.t.get(3), null);
+ assertEq(typeof ins.exports.t.get(4), "function");
+ assertEq(typeof ins.exports.t.get(5), "function");
+ assertEq(ins.exports.t.get(6), null);
+ assertEq(typeof ins.exports.t.get(7), "function");
+ assertEq(ins.exports.t.get(8), null);
+ assertEq(typeof ins.exports.t.get(9), "function");
+}
+
+// Test ref.func in global initializer expressions
+
+for (let mutable of [true, false]) {
+ for (let imported of [true, false]) {
+ for (let exported of [true, false]) {
+ let globalType = mutable ? `(mut funcref)` : `funcref`;
+
+ let imports = {};
+
+ if (imported) {
+ imports = wasmEvalText(`
+ (module
+ (global $g (export "g") ${globalType} (ref.func $f))
+ (func $f (export "f") (result i32) i32.const 42)
+ )
+ `).exports;
+ }
+
+ let exports = wasmEvalText(`
+ (module
+ (global $g ${exported ? `(export "g")` : ``} ${imported ? `(import "" "g")` : ``} ${globalType} ${imported ? `` : `(ref.func $f)`})
+ ${exported ? `` : `(func (export "get_g") (result funcref) global.get $g)`}
+ (func $f (export "f") (result i32) i32.const 42)
+ )
+ `, { "": imports }).exports;
+
+ let targetFunc = imported ? imports.f : exports.f;
+ let globalVal = exported ? exports.g.value : exports.get_g();
+ assertEq(targetFunc(), 42);
+ assertEq(globalVal(), 42);
+ assertEq(targetFunc, globalVal);
+ if (imported && exported) {
+ assertEq(imports.g, exports.g);
+ }
+ }
+ }
+}
+
+// Test invalid ref.func indices
+
+function testCodeRefFuncIndex(index) {
+ assertErrorMessage(() => {
+ new WebAssembly.Module(moduleWithSections(
+ [v2vSigSection,
+ declSection([0]), // One function
+ bodySection(
+ [funcBody(
+ {locals:[],
+ body:[
+ RefFuncCode,
+ ...varU32(index),
+ DropCode
+ ]})])
+ ]))
+ },
+ WebAssembly.CompileError,
+ /(function index out of range)|(function index out of bounds)/);
+}
+
+testCodeRefFuncIndex(1);
+testCodeRefFuncIndex(2);
+testCodeRefFuncIndex(10);
+testCodeRefFuncIndex(1000);
+
+function testGlobalRefFuncIndex(index) {
+ assertErrorMessage(() => {
+ new WebAssembly.Module(moduleWithSections(
+ [v2vSigSection,
+ globalSection([
+ {
+ valType: AnyFuncCode,
+ flags: 0,
+ initExpr: [RefFuncCode, ...varU32(index), EndCode],
+ }
+ ])
+ ]))
+ },
+ WebAssembly.CompileError,
+ /(function index out of range)|(function index out of bounds)/);
+}
+
+testGlobalRefFuncIndex(1);
+testGlobalRefFuncIndex(2);
+testGlobalRefFuncIndex(10);
+testGlobalRefFuncIndex(1000);
diff --git a/js/src/jit-test/tests/wasm/ref-types/stackmaps1.js b/js/src/jit-test/tests/wasm/ref-types/stackmaps1.js
new file mode 100644
index 0000000000..0776b490b2
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/stackmaps1.js
@@ -0,0 +1,85 @@
+// Tests wasm frame tracing. Only tests for direct and indirect call chains
+// in wasm that lead to JS allocation. Does not test any timeout or interrupt
+// related aspects. The structure is
+//
+// test top level: call fn2
+// fn2: call fn1
+// fn1: do 100k times { call-direct fn0; call-indirect fn0; }
+// fn0: call out to JS that does allocation
+//
+// Eventually fn0 will trigger GC and we expect the chain of resulting frames
+// to be traced correctly. fn2, fn1 and fn0 have some ref-typed args, so
+// there will be traceable stack words to follow, in the sequence of frames.
+
+const {Module,Instance} = WebAssembly;
+
+let t =
+ `(module
+ (import "" "check3" (func $check3 (param externref) (param externref) (param externref)))
+ (import "" "alloc" (func $alloc (result externref)))
+ (type $typeOfFn0
+ (func (param i32) (param externref) (param i32)
+ (param externref) (param externref) (param i32) (result i32)))
+ (table 1 1 funcref)
+ (elem (i32.const 0) $fn0)
+
+ ;; -- fn 0
+ (func $fn0 (export "fn0")
+ (param $arg1 i32) (param $arg2 externref) (param $arg3 i32)
+ (param $arg4 externref) (param $arg5 externref) (param $arg6 i32) (result i32)
+ (call $alloc)
+ drop
+ (i32.add (i32.add (local.get $arg1) (local.get $arg3)) (local.get $arg6))
+
+ ;; Poke the ref-typed arguments, to be sure that they got kept alive
+ ;; properly across any GC that the |alloc| call might have done.
+ (call $check3 (local.get $arg2) (local.get $arg4) (local.get $arg5))
+ )
+
+ ;; -- fn 1
+ (func $fn1 (export "fn1") (param $arg1 externref) (result i32)
+ (local $i i32)
+
+ (loop (result i32)
+ ;; call direct 0
+ (call $fn0 (i32.const 10) (local.get $arg1) (i32.const 12)
+ (local.get $arg1) (local.get $arg1) (i32.const 15))
+
+ ;; call indirect 0
+ (call_indirect (type $typeOfFn0)
+ (i32.const 10) (local.get $arg1) (i32.const 12)
+ (local.get $arg1) (local.get $arg1) (i32.const 15)
+ (i32.const 0)) ;; table index
+
+ i32.add
+
+ ;; Do 60k iterations of this loop, to get a good amount of allocation
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if 0 (i32.lt_s (local.get $i) (i32.const 60000)))
+ )
+ )
+
+ ;; -- fn 2
+ (func $fn2 (export "fn2") (param $arg1 externref) (result i32)
+ (call $fn1 (local.get $arg1))
+ )
+ )`;
+
+function Croissant(chocolate, number) {
+ this.chocolate = chocolate;
+ this.number = number;
+}
+
+function allocates() {
+ return new Croissant(true, 271828);
+}
+
+function check3(a1, a2, a3) {
+ assertEq(a1.number, 31415927);
+ assertEq(a2.number, 31415927);
+ assertEq(a3.number, 31415927);
+}
+
+let i = wasmEvalText(t, {"":{alloc: allocates, check3: check3}});
+
+print(i.exports.fn2( new Croissant(false, 31415927) ));
diff --git a/js/src/jit-test/tests/wasm/ref-types/stackmaps2.js b/js/src/jit-test/tests/wasm/ref-types/stackmaps2.js
new file mode 100644
index 0000000000..986d378a75
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/stackmaps2.js
@@ -0,0 +1,132 @@
+// Tests wasm frame tracing in the presence of interrupt handlers that perform
+// allocation. The structure is
+//
+// test top level: call fn2
+// fn2: call fn1
+// fn1: repeat { call-direct fn0; call-indirect fn0; }
+// fn0: a 100-iteration loop that does nothing except waste time
+//
+// At the same time we are asynchronously runnning handler(), which does a lot
+// of allocation. At some point that will trigger a GC. Assuming that
+// handler() runs whilst fn0 is running (the most likely scenario, since fn0
+// consumes the majority of the wasm running time), then the runtime will walk
+// the stack from the wasm exit frame, through fn0, fn1 and finally fn2. As
+// with stackmaps1.js, there are some ref-typed args in use so as provide
+// traceable stack slots to follow.
+//
+// The test runs until the loop in fn1 determines that handler() has allocated
+// sufficient memory as to have caused at least three collections. This helps
+// keep the test effective in the face of wide variations in the rate of
+// progress of the handler()'s loop (eg x86+native is fast, arm64+simulator is
+// slow).
+
+const {Module,Instance} = WebAssembly;
+
+const DEBUG = false;
+
+let t =
+ `(module
+ (type $typeOfFn0
+ (func (param i32) (param externref) (param i32)
+ (param externref) (param externref) (param i32) (result i32)))
+
+ (import "" "alloc" (func $alloc (result externref)))
+ (import "" "quitp" (func $quitp (result i32)))
+ (import "" "check3" (func $check3 (param externref) (param externref) (param externref)))
+
+ (table 1 1 funcref)
+ (elem (i32.const 0) $fn0)
+
+ ;; -- fn 0
+ (func $fn0 (export "fn0")
+ (param $arg1 i32) (param $arg2 externref) (param $arg3 i32)
+ (param $arg4 externref) (param $arg5 externref) (param $arg6 i32) (result i32)
+ (local $i i32)
+
+ ;; spinloop to waste time
+ (loop
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if 0 (i32.lt_s (local.get $i) (i32.const 100)))
+ )
+
+ (i32.add (i32.add (local.get $arg1) (local.get $arg3)) (local.get $arg6))
+
+ ;; Poke the ref-typed arguments, to be sure that they got kept alive
+ ;; properly across any GC that might have happened.
+ (call $check3 (local.get $arg2) (local.get $arg4) (local.get $arg5))
+ )
+
+ ;; -- fn 1
+ (func $fn1 (export "fn1") (param $arg1 externref) (result i32)
+ (loop (result i32)
+ ;; call direct to $fn0
+ (call $fn0 (i32.const 10) (local.get $arg1) (i32.const 12)
+ (local.get $arg1) (local.get $arg1) (i32.const 15))
+
+ ;; call indirect to table index 0, which is $fn0
+ (call_indirect (type $typeOfFn0)
+ (i32.const 10) (local.get $arg1) (i32.const 12)
+ (local.get $arg1) (local.get $arg1) (i32.const 15)
+ (i32.const 0)) ;; table index
+
+ i32.add
+
+ ;; Continue iterating until handler() has allocated enough
+ (br_if 0 (i32.eq (call $quitp) (i32.const 0)))
+ )
+ )
+
+ ;; -- fn 2
+ (func $fn2 (export "fn2") (param $arg1 externref) (result i32)
+ (call $fn1 (local.get $arg1))
+ )
+ )`;
+
+function Croissant(chocolate, number) {
+ this.chocolate = chocolate;
+ this.number = number;
+}
+
+function allocates() {
+ return new Croissant(true, 271828);
+}
+
+let totAllocs = 0;
+
+function handler() {
+ if (DEBUG) {
+ print('XXXXXXXX icallback: START');
+ }
+ let q = allocates();
+ let sum = 0;
+ let iters = 15000;
+ for (let i = 0; i < iters; i++) {
+ let x = allocates();
+ // Without this hoop jumping to create an apparent use of |x|, Ion
+ // will remove the allocation call and make the test pointless.
+ if (x == q) { sum++; }
+ }
+ totAllocs += iters;
+ // Artificial use of |sum|. See comment above.
+ if (sum == 133713371337) { print("unlikely!"); }
+ timeout(0.5, handler);
+ if (DEBUG) {
+ print('XXXXXXXX icallback: END');
+ }
+ return true;
+}
+
+function quitp() {
+ return totAllocs > 200000 ? 1 : 0;
+}
+
+function check3(a1, a2, a3) {
+ assertEq(a1.number, 31415927);
+ assertEq(a2.number, 31415927);
+ assertEq(a3.number, 31415927);
+}
+
+let i = wasmEvalText(t, {"":{alloc: allocates, quitp: quitp, check3: check3}});
+
+timeout(0.5, handler);
+print(i.exports.fn2( new Croissant(false, 31415927) ));
diff --git a/js/src/jit-test/tests/wasm/ref-types/stackmaps3.js b/js/src/jit-test/tests/wasm/ref-types/stackmaps3.js
new file mode 100644
index 0000000000..1b4c15abd2
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/stackmaps3.js
@@ -0,0 +1,201 @@
+// Generates a bunch of numbers-on-the-heap, and tries to ensure that they are
+// held live -- at least for a short while -- only by references from the wasm
+// evaluation stack. Then assembles them in a list and checks that the list
+// is as expected (and we don't segfault). While all this is running we also
+// have an regular interrupt whose handler does a bunch of allocation, so as
+// to cause as much disruption as possible.
+
+// Note this makes an assumption about how the wasm compiler works. There's
+// no particular reason that the wasm compiler needs to keep the results of
+// the $mkBoxedInt calls on the machine stack. It could equally cache them in
+// registers or even reorder the call sequences so as to interleave
+// construction of the list elements with construction of the list itself. It
+// just happens that our baseline compiler will behave as described. That
+// said, however, it's hard to imagine how an implementation could complete
+// the list construction without having at least one root in a register or on
+// the stack, so the test still has value regardless of how the underlying
+// implementation works.
+
+const {Module,Instance} = WebAssembly;
+
+const DEBUG = false;
+
+let t =
+ `(module
+ (import "" "mkCons" (func $mkCons (param externref) (param externref) (result externref)))
+ (import "" "mkBoxedInt" (func $mkBoxedInt (result externref)))
+
+ (func $mkNil (result externref)
+ ref.null extern
+ )
+
+ (func $mkConsIgnoringScalar
+ (param $hd externref) (param i32) (param $tl externref)
+ (result externref)
+ (local.get $hd)
+ (local.get $tl)
+ call $mkCons
+ )
+
+ (func $mkList (export "mkList") (result externref)
+ call $mkList20
+ )
+
+ (func $mkList20 (result externref)
+ ;; create 20 pointers to boxed ints on the stack, plus a few
+ ;; scalars for added confusion
+ (local $scalar99 i32)
+ (local $scalar97 i32)
+ (local.set $scalar99 (i32.const 99))
+ (local.set $scalar97 (i32.const 97))
+
+ call $mkBoxedInt
+ local.get $scalar99
+ call $mkBoxedInt
+ call $mkBoxedInt
+ local.get $scalar97
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkBoxedInt
+ call $mkNil
+ ;; Now we have (pointers to) 20 boxed ints and a NIL on the stack, and
+ ;; nothing else holding them live. Build a list from the elements.
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkCons
+ call $mkConsIgnoringScalar
+ call $mkCons
+ call $mkConsIgnoringScalar
+ )
+ )`;
+
+let boxedIntCounter = 0;
+
+function BoxedInt() {
+ this.theInt = boxedIntCounter;
+ boxedIntCounter++;
+}
+
+function mkBoxedInt() {
+ return new BoxedInt();
+}
+
+function printBoxedInt(bi) {
+ print(bi.theInt);
+}
+
+function Cons(hd, tl) {
+ this.hd = hd;
+ this.tl = tl;
+}
+
+function mkCons(hd, tl) {
+ return new Cons(hd, tl);
+}
+
+function showList(list) {
+ print("[");
+ while (list) {
+ printBoxedInt(list.hd);
+ print(",");
+ list = list.tl;
+ }
+ print("]");
+}
+
+function checkList(list, expectedHdValue, expectedLength) {
+ while (list) {
+ if (expectedLength <= 0)
+ return false;
+ if (list.hd.theInt !== expectedHdValue) {
+ return false;
+ }
+ list = list.tl;
+ expectedHdValue++;
+ expectedLength--;
+ }
+ if (expectedLength == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+let i = wasmEvalText(t, {"":{mkCons: mkCons, mkBoxedInt: mkBoxedInt}});
+
+
+function Croissant(chocolate) {
+ this.chocolate = chocolate;
+}
+
+function allocates() {
+ return new Croissant(true);
+}
+
+function handler() {
+ if (DEBUG) {
+ print('XXXXXXXX icallback: START');
+ }
+ let q = allocates();
+ let sum = 0;
+ for (let i = 0; i < 15000; i++) {
+ let x = allocates();
+ // Without this hoop jumping to create an apparent use of |x|, Ion
+ // will remove the allocation call and make the test pointless.
+ if (x == q) { sum++; }
+ }
+ // Artificial use of |sum|. See comment above.
+ if (sum == 133713371337) { print("unlikely!"); }
+ timeout(1, handler);
+ if (DEBUG) {
+ print('XXXXXXXX icallback: END');
+ }
+ return true;
+}
+
+timeout(1, handler);
+
+for (let n = 0; n < 10000; n++) {
+ let listLowest = boxedIntCounter;
+
+ // Create the list in wasm land, possibly inducing GC on the way
+ let aList = i.exports.mkList();
+
+ // Check it is as we expect
+ let ok = checkList(aList, listLowest, 20/*expected length*/);
+ if (!ok) {
+ print("Failed on list: ");
+ showList(aList);
+ }
+ assertEq(ok, true);
+}
+
+// If we get here, the test finished successfully.
diff --git a/js/src/jit-test/tests/wasm/ref-types/stackmaps4-params-n-locals.js b/js/src/jit-test/tests/wasm/ref-types/stackmaps4-params-n-locals.js
new file mode 100644
index 0000000000..2b95ea14a6
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/stackmaps4-params-n-locals.js
@@ -0,0 +1,143 @@
+// A stress test for stackmap creation as it relates to params and locals.
+
+function Stuff(n) {
+ this.n = n;
+}
+function allocate(n) {
+ const res = new Stuff(n);
+ // The webassembly loop below will provide us with 254 as an arg after not
+ // very long.
+ if (n == 254) {
+ gc();
+ }
+ return res;
+}
+
+function visit(aStuff) {
+ return aStuff.n;
+}
+
+let bin = wasmTextToBinary(
+`
+(module
+ (import "" "allocate" (func $allocate (param i32) (result externref)))
+ (import "" "visit" (func $visit (param externref) (result i32)))
+
+ ;; A function with many params and locals, most of which are ref-typed.
+ ;; The purpose of having so many is to defeat any reasonable attempt at
+ ;; allocating them all in registers. The asymmetrically-placed i32s are
+ ;; an attempt to expose any misalignment or inversion of the stack layout
+ ;; vs what the stackmap claims the layout to be.
+
+ (func $manyParamsAndLocals (export "manyParamsAndLocals")
+ (param $p1 externref) (param $p2 i32) (param $p3 externref)
+ (param $p4 externref) (param $p5 externref) (param $p6 externref)
+ (param $p7 externref) (param $p8 externref) (param $p9 i32)
+ (result i32)
+ (local $l1 externref) (local $l2 externref) (local $l3 externref)
+ (local $l4 i32) (local $l5 externref) (local $l6 i32)
+ (local $l7 externref) (local $l8 externref) (local $l9 externref)
+
+ (local $i i32)
+ (local $runningTotal i32)
+
+ ;; Bind some objects to l1 .. l9. The JS harness will already
+ ;; have done the same for p1 .. p9.
+ (local.set $l1 (call $allocate (i32.const 1)))
+ (local.set $l2 (call $allocate (i32.const 3)))
+ (local.set $l3 (call $allocate (i32.const 5)))
+ (local.set $l4 (i32.const 7))
+ (local.set $l5 (call $allocate (i32.const 9)))
+ (local.set $l6 (i32.const 11))
+ (local.set $l7 (call $allocate (i32.const 13)))
+ (local.set $l8 (call $allocate (i32.const 15)))
+ (local.set $l9 (call $allocate (i32.const 17)))
+
+ ;; Now loop, allocating as we go, and forcing GC every 256 iterations.
+ ;; Also in each iteration, visit all the locals and params, in the hope
+ ;; of exposing any cases where they are not held live across GC.
+ (loop $CONT
+ ;; Allocate, and hold on to the resulting value, so that Ion can't
+ ;; delete the allocation.
+ (local.set $l9 (call $allocate (i32.and (local.get $i) (i32.const 255))))
+
+ ;; Visit all params and locals
+
+ local.get $runningTotal
+
+ (call $visit (local.get $p1))
+ i32.add
+ local.get $p2
+ i32.add
+ (call $visit (local.get $p3))
+ i32.add
+ (call $visit (local.get $p4))
+ i32.add
+ (call $visit (local.get $p5))
+ i32.add
+ (call $visit (local.get $p6))
+ i32.add
+ (call $visit (local.get $p7))
+ i32.add
+ (call $visit (local.get $p8))
+ i32.add
+ local.get $p9
+ i32.add
+
+ (call $visit (local.get $l1))
+ i32.add
+ (call $visit (local.get $l2))
+ i32.add
+ (call $visit (local.get $l3))
+ i32.add
+ local.get $l4
+ i32.add
+ (call $visit (local.get $l5))
+ i32.add
+ local.get $l6
+ i32.add
+ (call $visit (local.get $l7))
+ i32.add
+ (call $visit (local.get $l8))
+ i32.add
+ (call $visit (local.get $l9))
+ i32.add
+
+ local.set $runningTotal
+
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $CONT (i32.lt_s (local.get $i) (i32.const 10000)))
+ ) ;; loop
+
+ local.get $runningTotal
+ ) ;; func
+)
+`);
+
+let mod = new WebAssembly.Module(bin);
+let ins = new WebAssembly.Instance(mod, {"":{allocate, visit}});
+
+let p1 = allocate(97);
+let p2 = 93;
+let p3 = allocate(91);
+let p4 = allocate(83);
+let p5 = allocate(79);
+let p6 = allocate(73);
+let p7 = allocate(71);
+let p8 = allocate(67);
+let p9 = 61;
+
+let res = ins.exports.manyParamsAndLocals(p1, p2, p3, p4, p5, p6, p7, p8, p9);
+
+// Check that GC didn't mess up p1 .. p9
+res += visit(p1);
+res += p2;
+res += visit(p3);
+res += visit(p4);
+res += visit(p5);
+res += visit(p6);
+res += visit(p7);
+res += visit(p8);
+res += p9;
+
+assertEq(res, 9063795);
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-api.js b/js/src/jit-test/tests/wasm/ref-types/tables-api.js
new file mode 100644
index 0000000000..a2ae3c45f0
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-api.js
@@ -0,0 +1,93 @@
+// Test changes to the WebAssembly.Table API by reference-types
+
+function assertTableFilled(table, length, element) {
+ assertEq(table.length, length);
+ for (let i = 0; i < length; i++) {
+ assertEq(table.get(i), element);
+ }
+}
+
+const tableLength = 10;
+
+// Test table constructor can accept a default value to fill elements to
+for (let value of WasmExternrefValues) {
+ let table = new WebAssembly.Table({'element': 'externref', initial: tableLength}, value);
+ assertTableFilled(table, tableLength, value);
+}
+for (let value of WasmFuncrefValues) {
+ let table = new WebAssembly.Table({'element': 'anyfunc', initial: tableLength}, value);
+ assertTableFilled(table, tableLength, value);
+}
+
+// Test not specifying default value in constructor yields 'undefined' for externref
+{
+ let table = new WebAssembly.Table({'element': 'externref', initial: tableLength});
+ assertTableFilled(table, tableLength, undefined);
+}
+
+// Test not specifying default value in constructor yields 'null' for funcref
+{
+ let table = new WebAssembly.Table({'element': 'anyfunc', initial: tableLength});
+ assertTableFilled(table, tableLength, null);
+}
+
+// Test omitting the value in table set defaults to undefined for externref and
+// null for funcref.
+{
+ let t = new WebAssembly.Table({element:"externref", initial: 1});
+ // Clear out initial value
+ t.set(0, '');
+ // Set with an omitted value
+ t.set(0);
+ // Assert the omitted value is undefined
+ assertEq(t.get(0), undefined);
+}
+{
+ let t = new WebAssembly.Table({element:"anyfunc", initial: 1});
+ // Clear out initial value
+ t.set(0, WasmFuncrefValues[0]);
+ // Set with an omitted value
+ t.set(0);
+ // Assert the omitted value is null
+ assertEq(t.get(0), null);
+}
+
+// Test table grow. There is an optional fill argument that defaults to
+// undefined with externref, and null with funcref.
+{
+ let t = new WebAssembly.Table({element:"externref", initial:0});
+ t.grow(1);
+ assertEq(t.get(t.length-1), undefined);
+ let prev = undefined;
+ for (let v of WasmExternrefValues) {
+ t.grow(2, v);
+ assertEq(t.get(t.length-3), prev);
+ assertEq(t.get(t.length-2), v);
+
+ assertEq(t.get(t.length-1), v);
+ prev = v;
+ }
+}
+
+{
+ let t = new WebAssembly.Table({element:"anyfunc", initial:0});
+ t.grow(1);
+ assertEq(t.get(t.length-1), null);
+ let prev = null;
+ for (let v of WasmFuncrefValues) {
+ t.grow(2, v);
+ assertEq(t.get(t.length-3), prev);
+ assertEq(t.get(t.length-2), v);
+
+ assertEq(t.get(t.length-1), v);
+ prev = v;
+ }
+}
+
+// If growing by zero elements there are no spurious writes
+{
+ let t = new WebAssembly.Table({element:"externref", initial:1});
+ t.set(0, 1337);
+ t.grow(0, 1789);
+ assertEq(t.get(0), 1337);
+}
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-fill.js b/js/src/jit-test/tests/wasm/ref-types/tables-fill.js
new file mode 100644
index 0000000000..b6b821f1d0
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-fill.js
@@ -0,0 +1,212 @@
+const N = 8;
+
+function testTableFill(tbl_type, val_type, obj) {
+ assertEq(obj.length, N);
+
+ let ins
+ = wasmEvalText(
+ `(module
+ (table 8 ${tbl_type}) ;; table 0
+ (table $t 10 ${tbl_type}) ;; table 1
+
+ ;; fill/get for table 0, referenced implicitly
+ (func (export "fill0") (param $i i32) (param $r ${val_type}) (param $n i32)
+ (table.fill (local.get $i) (local.get $r) (local.get $n))
+ )
+ (func (export "get0") (param $i i32) (result ${tbl_type})
+ (table.get (local.get $i))
+ )
+
+ ;; fill/get for table 1, referenced explicitly
+ (func (export "fill1") (param $i i32) (param $r ${val_type}) (param $n i32)
+ (table.fill $t (local.get $i) (local.get $r) (local.get $n))
+ )
+ (func (export "get1") (param $i i32) (result ${tbl_type})
+ (table.get $t (local.get $i))
+ )
+ )`);
+
+ // An initial test to ascertain that tables 0 and 1 are independent
+
+ // Fill in table 0, then check it.
+ assertEq(ins.exports.fill0(2, obj[6], 5), undefined)
+ assertEq(ins.exports.fill0(1, obj[7], 3), undefined);
+
+ function check_table0() {
+ assertEq(ins.exports.get0(0), null);
+ assertEq(ins.exports.get0(1), obj[7]);
+ assertEq(ins.exports.get0(2), obj[7]);
+ assertEq(ins.exports.get0(3), obj[7]);
+ assertEq(ins.exports.get0(4), obj[6]);
+ assertEq(ins.exports.get0(5), obj[6]);
+ assertEq(ins.exports.get0(6), obj[6]);
+ assertEq(ins.exports.get0(7), null);
+ }
+
+ // Check that table 0 has the expected content.
+ check_table0();
+
+ // Check that messing with table 0 above hasn't changed table 1.
+ for (let i = 0; i < 10; i++) {
+ assertEq(ins.exports.get1(i), null);
+ }
+
+ // Now a bunch of tests involving only table 1.
+
+ // Partly outside the table
+ assertErrorMessage(() => ins.exports.fill1(8, obj[5], 3),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+ assertEq(ins.exports.get1(7), null);
+ assertEq(ins.exports.get1(8), null);
+ assertEq(ins.exports.get1(9), null);
+
+ // Within the table
+ assertEq(ins.exports.fill1(2, obj[0], 3), undefined);
+ assertEq(ins.exports.get1(1), null);
+ assertEq(ins.exports.get1(2), obj[0]);
+ assertEq(ins.exports.get1(3), obj[0]);
+ assertEq(ins.exports.get1(4), obj[0]);
+ assertEq(ins.exports.get1(5), null);
+
+ // Within the table
+ assertEq(ins.exports.fill1(4, obj[1], 2), undefined);
+ assertEq(ins.exports.get1(3), obj[0]);
+ assertEq(ins.exports.get1(4), obj[1]);
+ assertEq(ins.exports.get1(5), obj[1]);
+ assertEq(ins.exports.get1(6), null);
+
+ // Within the table
+ assertEq(ins.exports.fill1(4, obj[2], 0), undefined);
+ assertEq(ins.exports.get1(3), obj[0]);
+ assertEq(ins.exports.get1(4), obj[1]);
+ assertEq(ins.exports.get1(5), obj[1]);
+
+ // Within the table
+ assertEq(ins.exports.fill1(8, obj[3], 2), undefined);
+ assertEq(ins.exports.get1(7), null);
+ assertEq(ins.exports.get1(8), obj[3]);
+ assertEq(ins.exports.get1(9), obj[3]);
+
+ // Within the table
+ assertEq(ins.exports.fill1(9, null, 1), undefined);
+ assertEq(ins.exports.get1(8), obj[3]);
+ assertEq(ins.exports.get1(9), null);
+
+ // Within the table
+ assertEq(ins.exports.fill1(10, obj[4], 0), undefined);
+ assertEq(ins.exports.get1(9), null);
+
+ // Boundary tests on table 1: at the edge of the table.
+
+ // Length-zero fill1 at the edge of the table must succeed
+ assertEq(ins.exports.fill1(10, null, 0), undefined);
+
+ // Length-one fill1 at the edge of the table fails
+ assertErrorMessage(() => ins.exports.fill1(10, null, 1),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+ // Length-more-than-one fill1 at the edge of the table fails
+ assertErrorMessage(() => ins.exports.fill1(10, null, 2),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+
+ // Boundary tests on table 1: beyond the edge of the table:
+
+ // Length-zero fill1 beyond the edge of the table fails
+ assertErrorMessage(() => ins.exports.fill1(11, null, 0),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+ // Length-one fill1 beyond the edge of the table fails
+ assertErrorMessage(() => ins.exports.fill1(11, null, 1),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+ // Length-more-than-one fill1 beyond the edge of the table fails
+ assertErrorMessage(() => ins.exports.fill1(11, null, 2),
+ WebAssembly.RuntimeError, /index out of bounds/);
+
+ // Following all the above tests on table 1, check table 0 hasn't changed.
+ check_table0();
+}
+
+var objs = [];
+for (var i = 0; i < N; i++)
+ objs[i] = {n:i};
+testTableFill('externref', 'externref', objs);
+
+var funcs = [];
+for (var i = 0; i < N; i++)
+ funcs[i] = wasmEvalText(`(module (func (export "x") (result i32) (i32.const ${i})))`).exports.x;
+testTableFill('funcref', 'funcref', funcs);
+
+
+// Type errors. Required sig is: (i32, externref, i32) -> void
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $expected-3-args-got-0
+ (table.fill $t)
+ ))`),
+ WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $expected-3-args-got-1
+ (table.fill $t (i32.const 0))
+ ))`),
+ WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $expected-3-args-got-2
+ (table.fill $t (ref.null extern) (i32.const 0))
+ ))`),
+ WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $argty-1-wrong
+ (table.fill $t (i32.const 0) (ref.null extern) (f64.const 0))
+ ))`),
+ WebAssembly.CompileError,
+ /(type mismatch: expression has type f64 but expected i32)|(type mismatch: expected i32, found f64)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $argty-2-wrong
+ (table.fill $t (i32.const 0) (f32.const 0) (i32.const 0))
+ ))`),
+ WebAssembly.CompileError,
+ /(type mismatch: expression has type f32 but expected externref)|(type mismatch: expected externref, found f32)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $argty-3-wrong
+ (table.fill $t (i64.const 0) (ref.null extern) (i32.const 0))
+ ))`),
+ WebAssembly.CompileError,
+ /(type mismatch: expression has type i64 but expected i32)|(type mismatch: expected i32, found i64)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t 10 externref)
+ (func $retty-wrong (result i32)
+ (table.fill $t (i32.const 0) (ref.null extern) (i32.const 0))
+ ))`),
+ WebAssembly.CompileError,
+ /(popping value from empty stack)|(nothing on stack)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table 8 funcref)
+ (func (param $v externref)
+ (table.fill (i32.const 0) (local.get $v) (i32.const 0)))
+ )`),
+ WebAssembly.CompileError,
+ /(expression has type externref but expected funcref)|(type mismatch: expected funcref, found externref)/);
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js b/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js
new file mode 100644
index 0000000000..19e530baae
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js
@@ -0,0 +1,432 @@
+///////////////////////////////////////////////////////////////////////////
+//
+// General table management in wasm
+
+// Wasm: Create table-of-externref
+
+new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref))`));
+
+// Wasm: Import table-of-externref
+// JS: create table-of-externref
+
+new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (import "m" "t") 10 externref))`)),
+ {m:{t: new WebAssembly.Table({element:"externref", initial:10})}});
+
+new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "m" "t" (table 10 externref)))`)),
+ {m:{t: new WebAssembly.Table({element:"externref", initial:10})}});
+
+// Wasm: Export table-of-externref, initial values shall be null
+
+{
+ let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 externref))`)));
+ let t = ins.exports.t;
+ assertEq(t.length, 10);
+ for (let i=0; i < t.length; i++)
+ assertEq(t.get(0), null);
+}
+
+// JS: Exported table can be grown, and values are preserved
+
+{
+ let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 externref))`)));
+ let t = ins.exports.t;
+ let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
+ for (let i in objs)
+ t.set(i, objs[i]);
+ ins.exports.t.grow(10);
+ assertEq(ins.exports.t.length, 20);
+ for (let i in objs)
+ assertEq(t.get(i), objs[i]);
+}
+
+// Wasm: table.copy between tables of externref (currently source and destination
+// are both table zero)
+
+{
+ let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 externref)
+ (func (export "f")
+ (table.copy (i32.const 5) (i32.const 0) (i32.const 3))))`)));
+ let t = ins.exports.t;
+ let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
+ for (let i in objs)
+ t.set(i, objs[i]);
+ ins.exports.f();
+ assertEq(t.get(0), objs[0]);
+ assertEq(t.get(1), objs[1]);
+ assertEq(t.get(2), objs[2]);
+ assertEq(t.get(3), objs[3]);
+ assertEq(t.get(4), objs[4]);
+ assertEq(t.get(5), objs[0]);
+ assertEq(t.get(6), objs[1]);
+ assertEq(t.get(7), objs[2]);
+ assertEq(t.get(8), objs[8]);
+ assertEq(t.get(9), objs[9]);
+}
+
+// Wasm: table.copy from table(funcref) to table(externref) should not work
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 externref)
+ (func $f1)
+ (func $f2)
+ (func $f3)
+ (func $f4)
+ (func $f5)
+ (table 5 funcref)
+ (elem (table 1) (i32.const 0) func $f1 $f2 $f3 $f4 $f5)
+ (func (export "f")
+ (table.copy 0 1 (i32.const 5) (i32.const 0) (i32.const 5))))`)),
+ WebAssembly.CompileError,
+ /(expression has type funcref but expected externref)|(type mismatch)/);
+
+// Wasm: table.copy from table(externref) to table(funcref) should not work
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 funcref)
+ (table 10 externref)
+ (func (export "f")
+ (table.copy 0 1 (i32.const 0) (i32.const 0) (i32.const 5))))`)),
+ WebAssembly.CompileError,
+ /(expression has type externref but expected funcref)|(type mismatch)/);
+
+// Wasm: Element segments of funcref can't target tables of externref
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func $f1 (result i32) (i32.const 0))
+ (table (export "t") 10 externref)
+ (elem 0 (i32.const 0) funcref (ref.func $f1)))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+
+// Wasm: Element segments of externref can't target tables of funcref
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 funcref)
+ (elem 0 (i32.const 0) externref (ref.null extern)))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+// Wasm: table.init on table-of-externref is not allowed when the segment has
+// funcref.
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func $f1 (result i32) (i32.const 0))
+ (table 10 externref)
+ (elem funcref (ref.func $f1))
+ (func
+ (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
+ WebAssembly.CompileError,
+ /(expression has type funcref but expected externref)|(type mismatch)/);
+
+
+// Wasm: table.init on table-of-funcref is not allowed when the segment has
+// externref.
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 funcref)
+ (elem externref (ref.null extern))
+ (func
+ (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
+ WebAssembly.CompileError,
+ /(expression has type externref but expected funcref)|(type mismatch)/);
+
+// Wasm: table types must match at link time
+
+assertErrorMessage(
+ () => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "m" "t" (table 10 externref)))`)),
+ {m:{t: new WebAssembly.Table({element:"anyfunc", initial:10})}}),
+ WebAssembly.LinkError,
+ /imported table type mismatch/);
+
+// call_indirect cannot reference table-of-externref
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref)
+ (type $t (func (param i32) (result i32)))
+ (func (result i32)
+ (call_indirect (type $t) (i32.const 37))))`)),
+ WebAssembly.CompileError,
+ /(indirect calls must go through a table of 'funcref')|(indirect calls must go through a table of funcref)/);
+
+///////////////////////////////////////////////////////////////////////////
+//
+// additional js api tests
+
+{
+ let tbl = new WebAssembly.Table({element:"externref", initial:10});
+
+ // Initial value is undefined. This is different from when tables are
+ // created inside wasm.
+ assertEq(tbl.get(0), undefined);
+
+ // Identity preserving.
+ let x = {hi: 48};
+ tbl.set(0, x);
+ assertEq(tbl.get(0), x);
+ tbl.set(2, dummy);
+ assertEq(tbl.get(2), dummy);
+ tbl.set(2, null);
+ assertEq(tbl.get(2), null);
+
+ // Temporary semantics is to convert to object and leave as object; once we
+ // have a better wrapped externref this will change, we won't be able to
+ // observe the boxing.
+ tbl.set(1, 42);
+ let y = tbl.get(1);
+ assertEq(typeof y, "number");
+ assertEq(y, 42);
+}
+
+function dummy() { return 37 }
+
+///////////////////////////////////////////////////////////////////////////
+//
+// table.get and table.set
+
+const wasmFun = wasmEvalText(`(module (func (export "x")))`).exports.x;
+
+// table.get in bounds - returns right value type & value
+// table.get out of bounds - fails
+
+function testTableGet(type, x) {
+ let ins = wasmEvalText(
+ `(module
+ (table (export "t") 10 ${type})
+ (func (export "f") (param i32) (result ${type})
+ (table.get (local.get 0))))`);
+ ins.exports.t.set(0, x);
+ assertEq(ins.exports.f(0), x);
+ assertEq(ins.exports.f(1), null);
+ assertErrorMessage(() => ins.exports.f(10), WebAssembly.RuntimeError, /index out of bounds/);
+ assertErrorMessage(() => ins.exports.f(-5), WebAssembly.RuntimeError, /index out of bounds/);
+}
+testTableGet('externref', {});
+testTableGet('funcref', wasmFun);
+
+// table.get with non-i32 index - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref)
+ (func (export "f") (param f64) (result externref)
+ (table.get (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+// table.get when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (export "f") (param i32)
+ (drop (table.get (local.get 0)))))`)),
+ WebAssembly.CompileError,
+ /(table index out of range for table.get)|(table index out of bounds)/);
+
+// table.set in bounds with i32 x externref - works, no value generated
+// table.set with null - works
+// table.set out of bounds - fails
+
+function testTableSet(lhs_type, rhs_type, rhs_reftype, x) {
+ let ins = wasmEvalText(
+ `(module
+ (table (export "t") 10 ${lhs_type})
+ (func (export "set_ref") (param i32) (param ${rhs_type})
+ (table.set (local.get 0) (local.get 1)))
+ (func (export "set_null") (param i32)
+ (table.set (local.get 0) (ref.null ${rhs_reftype}))))`);
+ ins.exports.set_ref(3, x);
+ assertEq(ins.exports.t.get(3), x);
+ ins.exports.set_null(3);
+ assertEq(ins.exports.t.get(3), null);
+
+ assertErrorMessage(() => ins.exports.set_ref(10, x), WebAssembly.RuntimeError, /index out of bounds/);
+ assertErrorMessage(() => ins.exports.set_ref(-1, x), WebAssembly.RuntimeError, /index out of bounds/);
+}
+testTableSet('externref', 'externref', 'extern', {});
+testTableSet('funcref', 'funcref', 'func', wasmFun);
+
+// Wasm: table.set on table(funcref) with externref value should fail
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 funcref)
+ (func (export "set_ref") (param i32) (param externref)
+ (table.set (local.get 0) (local.get 1))))`)),
+ WebAssembly.CompileError,
+ /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected funcref, found externref)/);
+
+// table.set with non-i32 index - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref)
+ (func (export "f") (param f64)
+ (table.set (local.get 0) (ref.null extern))))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+// table.set with non-ref value - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref)
+ (func (export "f") (param f64)
+ (table.set (i32.const 0) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 funcref)
+ (func (export "f") (param f64)
+ (table.set (i32.const 0) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+// table.set when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (export "f") (param externref)
+ (table.set (i32.const 0) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /(table index out of range for table.set)|(table index out of bounds)/);
+
+function testTableGrow(lhs_type, lhs_reftype, rhs_type, x) {
+ let ins = wasmEvalText(
+ `(module
+ (table (export "t") 10 20 ${lhs_type})
+ (func (export "grow") (param i32) (result i32)
+ (table.grow (ref.null ${lhs_reftype}) (local.get 0)))
+ (func (export "grow2") (param i32) (param ${rhs_type}) (result i32)
+ (table.grow (local.get 1) (local.get 0))))`);
+
+ // we can grow table of references
+ // table.grow with zero delta - always works even at maximum
+ // table.grow with delta - works and returns correct old value
+ // table.grow with delta at upper limit - fails
+ // table.grow with negative delta - fails
+ assertEq(ins.exports.grow(0), 10);
+ assertEq(ins.exports.t.length, 10);
+ assertEq(ins.exports.grow(1), 10);
+ assertEq(ins.exports.t.length, 11);
+ assertEq(ins.exports.t.get(10), null);
+ assertEq(ins.exports.grow2(9, x), 11);
+ assertEq(ins.exports.t.length, 20);
+ for (var i = 11; i < 20; i++)
+ assertEq(ins.exports.t.get(i), x);
+ assertEq(ins.exports.grow(0), 20);
+
+ // The JS API throws if it can't grow
+ assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/);
+ assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/);
+
+ // The wasm API does not throw if it can't grow, but returns -1
+ assertEq(ins.exports.grow(1), -1);
+ assertEq(ins.exports.t.length, 20);
+ assertEq(ins.exports.grow(-1), -1);
+ assertEq(ins.exports.t.length, 20)
+}
+testTableGrow('externref', 'extern', 'externref', 42);
+testTableGrow('funcref', 'func', 'funcref', wasmFun);
+
+// Wasm: table.grow on table(funcref) with externref initializer should fail
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table (export "t") 10 20 funcref)
+ (func (export "grow2") (param i32) (param externref) (result i32)
+ (table.grow (local.get 1) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected funcref, found externref)/);
+
+// Special case for private tables without a maximum
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (table 10 externref)
+ (func (export "grow") (param i32) (result i32)
+ (table.grow (ref.null extern) (local.get 0))))`);
+ assertEq(ins.exports.grow(0), 10);
+ assertEq(ins.exports.grow(1), 10);
+ assertEq(ins.exports.grow(9), 11);
+ assertEq(ins.exports.grow(0), 20);
+}
+
+// table.grow with non-i32 argument - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (table 10 externref)
+ (func (export "f") (param f64)
+ (table.grow (ref.null extern) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /type mismatch/);
+
+// table.grow when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (func (export "f") (param i32)
+ (table.grow (ref.null extern) (local.get 0))))`)),
+ WebAssembly.CompileError,
+ /(table index out of range for table.grow)|(table index out of bounds)/);
+
+// table.size on table of externref
+
+for (let visibility of ['', '(export "t")', '(import "m" "t")']) {
+ let exp = {m:{t: new WebAssembly.Table({element:"externref",
+ initial: 10,
+ maximum: 20})}};
+ let ins = wasmEvalText(
+ `(module
+ (table ${visibility} 10 20 externref)
+ (func (export "grow") (param i32) (result i32)
+ (table.grow (ref.null extern) (local.get 0)))
+ (func (export "size") (result i32)
+ (table.size)))`,
+ exp);
+ assertEq(ins.exports.grow(0), 10);
+ assertEq(ins.exports.size(), 10);
+ assertEq(ins.exports.grow(1), 10);
+ assertEq(ins.exports.size(), 11);
+ assertEq(ins.exports.grow(9), 11);
+ assertEq(ins.exports.size(), 20);
+ assertEq(ins.exports.grow(0), 20);
+ assertEq(ins.exports.size(), 20);
+}
+
+// table.size on table of funcref
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (table (export "t") 2 funcref)
+ (func (export "f") (result i32)
+ (table.size)))`);
+ assertEq(ins.exports.f(), 2);
+ ins.exports.t.grow(1);
+ assertEq(ins.exports.f(), 3);
+}
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js b/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js
new file mode 100644
index 0000000000..ca93824170
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js
@@ -0,0 +1,465 @@
+// Note that negative tests not having to do with table indices have been taken
+// care of by tables-generalized.js.
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Positive tests
+
+// - multiple local tables of misc type: syntax, validation, instantiation
+// - element segments can point to a table
+// - call-indirect can specify a table and will use it
+
+var ins = wasmEvalText(
+ `(module
+ (table $t1 2 funcref)
+ (table $t2 2 funcref)
+ (type $ftype (func (param i32) (result i32)))
+ (elem (table $t1) (i32.const 0) func $f1 $f2)
+ (elem (table $t2) (i32.const 0) func $f3 $f4)
+ (func $f1 (param $n i32) (result i32)
+ (i32.add (local.get $n) (i32.const 1)))
+ (func $f2 (param $n i32) (result i32)
+ (i32.add (local.get $n) (i32.const 2)))
+ (func $f3 (param $n i32) (result i32)
+ (i32.add (local.get $n) (i32.const 3)))
+ (func $f4 (param $n i32) (result i32)
+ (i32.add (local.get $n) (i32.const 4)))
+ (func (export "f") (param $fn i32) (param $n i32) (result i32)
+ (call_indirect $t1 (type $ftype) (local.get $n) (local.get $fn)))
+ (func (export "g") (param $fn i32) (param $n i32) (result i32)
+ (call_indirect $t2 (type $ftype) (local.get $n) (local.get $fn))))`).exports;
+
+assertEq(ins.f(0, 10), 11);
+assertEq(ins.f(1, 10), 12);
+assertEq(ins.g(0, 10), 13);
+assertEq(ins.g(1, 10), 14);
+
+// - export multiple tables.
+// note the first and third tables make the export list not start at zero,
+// and make it sparse
+
+var ins = wasmEvalText(
+ `(module
+ (table $t0 (import "m" "t") 2 funcref)
+ (table $t1 (export "t1") 2 funcref)
+ (table 1 externref)
+ (table $t2 (export "t2") 3 funcref))`,
+ {m:{t: new WebAssembly.Table({element:"anyfunc", initial:2})}}).exports;
+
+assertEq(ins.t1 instanceof WebAssembly.Table, true);
+assertEq(ins.t1.length, 2);
+assertEq(ins.t2 instanceof WebAssembly.Table, true);
+assertEq(ins.t2.length, 3);
+
+// - multiple imported tables of misc type
+// - table.get and table.set can point to a table
+
+var exp = {m:{t0: new WebAssembly.Table({element:"anyfunc", initial:2}),
+ t1: new WebAssembly.Table({element:"externref", initial:3}),
+ t2: new WebAssembly.Table({element:"anyfunc", initial:4}),
+ t3: new WebAssembly.Table({element:"externref", initial:5})}};
+var ins = wasmEvalText(
+ `(module
+ (table $t0 (import "m" "t0") 2 funcref)
+ (table $t1 (import "m" "t1") 3 externref)
+ (table $t2 (import "m" "t2") 4 funcref)
+ (table $t3 (import "m" "t3") 5 externref)
+
+ (type $id_i32_t (func (param i32) (result i32)))
+ (func $id_i32 (param i32) (result i32) (local.get 0))
+ (elem (table $t0) (i32.const 1) func $id_i32)
+
+ (type $id_f64_t (func (param f64) (result f64)))
+ (func $id_f64 (param f64) (result f64) (local.get 0))
+ (elem (table $t2) (i32.const 3) func $id_f64)
+
+ (func (export "f0") (param i32) (result i32)
+ (call_indirect $t0 (type $id_i32_t) (local.get 0) (i32.const 1)))
+
+ (func (export "f1") (param externref)
+ (table.set $t1 (i32.const 2) (local.get 0)))
+
+ (func (export "f2") (param f64) (result f64)
+ (call_indirect $t2 (type $id_f64_t) (local.get 0) (i32.const 3)))
+
+ (func (export "f3")
+ (table.set $t3 (i32.const 4) (table.get $t1 (i32.const 2)))))`,
+
+ exp).exports;
+
+assertEq(ins.f0(37), 37);
+
+var x = {}
+ins.f1(x);
+assertEq(exp.m.t1.get(2), x);
+
+assertEq(ins.f2(3.25), 3.25);
+
+ins.f3();
+assertEq(exp.m.t3.get(4), x);
+
+// - table.grow can point to a table
+// - growing a table grows the right table but not the others
+// - table.size on tables other than table 0
+
+var exp = {m:{t0: new WebAssembly.Table({element:"externref", initial:2}),
+ t1: new WebAssembly.Table({element:"externref", initial:3})}};
+var ins = wasmEvalText(
+ `(module
+ (table $t0 (import "m" "t0") 2 externref)
+ (table $t1 (import "m" "t1") 3 externref)
+ (func (export "f") (result i32)
+ (table.grow $t1 (ref.null extern) (i32.const 5)))
+ (func (export "size0") (result i32)
+ (table.size $t0))
+ (func (export "size1") (result i32)
+ (table.size $t1)))`,
+ exp);
+
+assertEq(ins.exports.f(), 3);
+assertEq(exp.m.t1.length, 8);
+assertEq(ins.exports.size0(), 2);
+assertEq(ins.exports.size1(), 8);
+
+// - table.copy can point to tables
+
+var exp = {m:{t0: new WebAssembly.Table({element:"externref", initial:2}),
+ t1: new WebAssembly.Table({element:"externref", initial:3})}};
+var ins = wasmEvalText(
+ `(module
+ (table $t0 (import "m" "t0") 2 externref)
+ (table $t1 (import "m" "t1") 3 externref)
+ (func (export "f") (param $dest i32) (param $src i32) (param $len i32)
+ (table.copy $t1 $t0 (local.get $dest) (local.get $src) (local.get $len))))`,
+ exp);
+
+exp.m.t0.set(0, {x:0});
+exp.m.t0.set(1, {x:1});
+ins.exports.f(1, 0, 2);
+assertEq(exp.m.t1.get(1), exp.m.t0.get(0));
+assertEq(exp.m.t1.get(2), exp.m.t0.get(1));
+
+// - the table.copy syntax makes sense even in the non-parenthesized case
+
+var ins = wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func (export "copy") (param $dest i32) (param $src i32) (param $len i32)
+ local.get $dest
+ local.get $src
+ local.get $len
+ table.copy $t1 $t0)
+ (func (export "set") (param $n i32) (param $v externref)
+ (table.set $t0 (local.get $n) (local.get $v)))
+ (func (export "get") (param $n i32) (result externref)
+ (table.get $t1 (local.get $n))))`,
+ exp);
+
+var values = [{x:0}, {x:1}];
+ins.exports.set(0, values[0]);
+ins.exports.set(1, values[1]);
+ins.exports.copy(0, 0, 2);
+assertEq(ins.exports.get(0), values[0]);
+assertEq(ins.exports.get(1), values[1]);
+
+// Copy beween an external table and a local table. These cases are interesting
+// mostly because the representations of the tables must be compatible; the copy
+// in effect forces a representation for the function in the local table that
+// captures the function's instance, at the latest at the time the copy is
+// executed.
+//
+// In the case where the function is imported, it comes from a different module.
+//
+// Also tests:
+// - local tables can be exported and re-imported in another module
+
+var arg = 4;
+for (let [a,b,x,y,result,init] of [['$t0', '$t1', '(export "t")', '', arg*13, true],
+ ['$t0', '$t1', '', '(export "t")', arg*13, true],
+ ['$t0', '$t1', '(import "m" "t")', '', arg*13, true],
+ ['$t1', '$t0', '(import "m" "t")', '', arg-11, false]])
+{
+ var otherins = wasmEvalText(
+ `(module
+ (table $t (export "t") 2 funcref)
+ (type $fn1 (func (param i32) (result i32)))
+ (func $f1 (param $n i32) (result i32)
+ (i32.sub (local.get $n) (i32.const 11)))
+ (elem (table $t) (i32.const 1) func $f1))`);
+
+ let text =
+ `(module
+ (table ${a} ${x} 2 funcref)
+
+ (table ${b} ${y} 2 funcref)
+ (type $fn1 (func (param i32) (result i32)))
+ (func $f1 (param $n i32) (result i32)
+ (i32.mul (local.get $n) (i32.const 13)))
+ ${init ? "(elem (table $t1) (i32.const 1) func $f1)" : ""}
+
+ (func (export "f") (param $n i32) (result i32)
+ (table.copy $t0 $t1 (i32.const 0) (i32.const 0) (i32.const 2))
+ (call_indirect $t0 (type $fn1) (local.get $n) (i32.const 1))))`;
+ var ins = wasmEvalText(text, {m: otherins.exports});
+
+ assertEq(ins.exports.f(arg), result);
+}
+
+// - a table can be imported multiple times, and when it is, and one of them is grown,
+// they are all grown.
+// - test the (import "m" "t" (table ...)) syntax
+// - if table is grown from JS, wasm can observe the growth
+
+var tbl = new WebAssembly.Table({element:"externref", initial:1});
+var exp = {m: {t0: tbl, t1:tbl}};
+
+var ins = wasmEvalText(
+ `(module
+ (import "m" "t0" (table $t0 1 externref))
+ (import "m" "t1" (table $t1 1 externref))
+ (table $t2 (export "t2") 1 funcref)
+ (func (export "f") (result i32)
+ (table.grow $t0 (ref.null extern) (i32.const 1)))
+ (func (export "g") (result i32)
+ (table.grow $t1 (ref.null extern) (i32.const 1)))
+ (func (export "size") (result i32)
+ (table.size $t2)))`,
+ exp);
+
+assertEq(ins.exports.f(), 1);
+assertEq(ins.exports.g(), 2);
+assertEq(ins.exports.f(), 3);
+assertEq(ins.exports.g(), 4);
+assertEq(tbl.length, 5);
+ins.exports.t2.grow(3);
+assertEq(ins.exports.size(), 4);
+
+// - table.init on tables other than table 0
+
+var ins = wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (table $t1 2 funcref)
+ (elem func $f0 $f1) ;; 0
+ (type $ftype (func (param i32) (result i32)))
+ (func $f0 (param i32) (result i32)
+ (i32.mul (local.get 0) (i32.const 13)))
+ (func $f1 (param i32) (result i32)
+ (i32.sub (local.get 0) (i32.const 11)))
+ (func (export "call") (param i32) (param i32) (result i32)
+ (call_indirect $t1 (type $ftype) (local.get 1) (local.get 0)))
+ (func (export "init")
+ (table.init $t1 0 (i32.const 0) (i32.const 0) (i32.const 2))))`);
+
+ins.exports.init();
+assertEq(ins.exports.call(0, 10), 130);
+assertEq(ins.exports.call(1, 10), -1);
+
+// - [white-box] if a multi-imported table of funcref is grown and the grown
+// part is properly initialized with functions then calls through both tables
+// in the grown area should succeed, ie, bounds checks should pass. this is
+// an interesting case because we cache the table bounds for the benefit of
+// call_indirect, so here we're testing that the caches are updated properly
+// even when a table is observed multiple times (also by multiple modules).
+// there's some extra hair here because a table of funcref can be grown only
+// from JS at the moment.
+// - also test that bounds checking continues to catch OOB calls
+
+var tbl = new WebAssembly.Table({element:"anyfunc", initial:2});
+var exp = {m:{t0: tbl, t1: tbl}};
+var ins = wasmEvalText(
+ `(module
+ (import "m" "t0" (table $t0 2 funcref))
+ (import "m" "t1" (table $t1 2 funcref))
+ (type $ftype (func (param f64) (result f64)))
+ (func (export "f") (param $n f64) (result f64)
+ (f64.mul (local.get $n) (f64.const 3.25)))
+ (func (export "do0") (param $i i32) (param $n f64) (result f64)
+ (call_indirect $t0 (type $ftype) (local.get $n) (local.get $i)))
+ (func (export "do1") (param $i i32) (param $n f64) (result f64)
+ (call_indirect $t1 (type $ftype) (local.get $n) (local.get $i))))`,
+ exp);
+var ins2 = wasmEvalText(
+ `(module
+ (import "m" "t0" (table $t0 2 funcref))
+ (import "m" "t1" (table $t1 2 funcref))
+ (type $ftype (func (param f64) (result f64)))
+ (func (export "do0") (param $i i32) (param $n f64) (result f64)
+ (call_indirect $t0 (type $ftype) (local.get $n) (local.get $i)))
+ (func (export "do1") (param $i i32) (param $n f64) (result f64)
+ (call_indirect $t1 (type $ftype) (local.get $n) (local.get $i))))`,
+ exp);
+
+assertEq(tbl.grow(10), 2);
+tbl.set(11, ins.exports.f);
+assertEq(ins.exports.do0(11, 2.0), 6.5);
+assertEq(ins.exports.do1(11, 4.0), 13.0);
+assertEq(ins2.exports.do0(11, 2.0), 6.5);
+assertEq(ins2.exports.do1(11, 4.0), 13.0);
+assertErrorMessage(() => ins.exports.do0(12, 2.0),
+ WebAssembly.RuntimeError,
+ /index out of bounds/);
+assertErrorMessage(() => ins2.exports.do0(12, 2.0),
+ WebAssembly.RuntimeError,
+ /index out of bounds/);
+assertErrorMessage(() => ins.exports.do1(12, 2.0),
+ WebAssembly.RuntimeError,
+ /index out of bounds/);
+assertErrorMessage(() => ins2.exports.do1(12, 2.0),
+ WebAssembly.RuntimeError,
+ /index out of bounds/);
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Negative tests
+
+// Table index (statically) out of bounds
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (result externref)
+ (table.get 2 (i32.const 0))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.get)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (param externref)
+ (table.set 2 (i32.const 0) (local.get 0))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.set)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (param externref)
+ (table.copy 0 2 (i32.const 0) (i32.const 0) (i32.const 2))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.copy)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (param externref)
+ (table.copy 2 0 (i32.const 0) (i32.const 0) (i32.const 2))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.copy)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (result i32)
+ (table.size 2)))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.size)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 externref)
+ (table $t1 2 externref)
+ (func $f (result i32)
+ (table.grow 2 (ref.null extern) (i32.const 1))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.grow)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (elem func) ;; 0
+ (func $f (result i32)
+ (table.init 2 0 (i32.const 0) (i32.const 0) (i32.const 0))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for table.init)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (elem 2 (i32.const 0) func))`),
+ WebAssembly.CompileError,
+ /(table index out of range for element segment)|(table index out of bounds)/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (type $ft (func (param f64) (result i32)))
+ (func $f (result i32)
+ (call_indirect 2 (type $ft) (f64.const 3.14) (i32.const 0))))`),
+ WebAssembly.CompileError,
+ /(table index out of range for call_indirect)|(table index out of bounds)/);
+
+// Syntax errors when parsing text
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (elem func) ;; 0
+ (func $f (result i32)
+ (table.init $t0 (i32.const 0) (i32.const 0) (i32.const 0))))`), // no segment
+ SyntaxError,
+ /failed to find name/);
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (table $t0 2 funcref)
+ (table $t1 2 funcref)
+ (func $f
+ (table.copy 0 (i32.const 0) (i32.const 0) (i32.const 2))))`), // target without source
+ SyntaxError,
+ /unexpected token, expected an identifier or u32/);
+
+// Make sure that dead code doesn't prevent compilation.
+wasmEvalText(
+ `(module
+ (table (export "t") 10 externref)
+ (func (param i32)
+ (return)
+ (table.get (get_local 0))
+ (drop)
+ )
+ )`);
+
+wasmEvalText(
+ `(module
+ (table (export "t") 10 externref)
+ (func (param i32) (param i32)
+ (return)
+ (table.grow (get_local 1))
+ (drop)
+ )
+ )`);
+
+wasmEvalText(
+ `(module
+ (table (export "t") 10 externref)
+ (func (param i32) (param externref)
+ (return)
+ (table.set (get_local 0) (get_local 1))
+ )
+ )`);
+
+wasmEvalText(
+ `(module
+ (table (export "t") 10 externref)
+ (func
+ (return)
+ (table.size)
+ (drop)
+ )
+ )`);
+
+wasmEvalText(
+ `(module
+ (table (export "t") 10 externref)
+ (func
+ (return)
+ (table.copy (i32.const 0) (i32.const 1))
+ )
+ )`);
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-postbarrier-grow.js b/js/src/jit-test/tests/wasm/ref-types/tables-postbarrier-grow.js
new file mode 100644
index 0000000000..20086e6579
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-postbarrier-grow.js
@@ -0,0 +1,21 @@
+// Add test for issue with a post-write barrier that doesn't remove
+// store buffer entries when used on a table that may grow.
+
+let {set, table} = wasmEvalText(`(module
+ (table (export "table") 1 externref)
+ (func (export "set") (param externref)
+ i32.const 0
+ local.get 0
+ table.set
+ )
+)`).exports;
+
+let tenured = {};
+gc(tenured);
+assertEq(isNurseryAllocated(tenured), false);
+let nursery = {};
+assertEq(isNurseryAllocated(nursery), true);
+set(nursery);
+set(null);
+assertEq(table.grow(1000), 1, 'table grows');
+gc();
diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-stress.js b/js/src/jit-test/tests/wasm/ref-types/tables-stress.js
new file mode 100644
index 0000000000..2e13e76ba0
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ref-types/tables-stress.js
@@ -0,0 +1,46 @@
+for ( let prefix of ['', '(table $prefix 0 32 funcref)']) {
+ let mod = new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "m" "item" (func $item (result externref)))
+ ${prefix}
+ (table $tbl 0 externref)
+ (func (export "run") (param $numiters i32)
+ (local $i i32)
+ (local $j i32)
+ (local $last i32)
+ (local $iters i32)
+ (local $tmp externref)
+ (local.set $last (table.grow $tbl (ref.null extern) (i32.const 1)))
+ (table.set $tbl (local.get $last) (call $item))
+ (loop $iter_continue
+ (local.set $i (i32.const 0))
+ (local.set $j (local.get $last))
+ (block $l_break
+ (loop $l_continue
+ (br_if $l_break (i32.ge_s (local.get $j) (local.get $i)))
+ (local.set $tmp (table.get $tbl (local.get $i)))
+ (if (i32.eqz (i32.rem_s (local.get $i) (i32.const 3)))
+ (local.set $tmp (call $item)))
+ (table.set $tbl (local.get $i) (table.get $tbl (local.get $j)))
+ (table.set $tbl (local.get $j) (local.get $tmp))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (local.set $j (i32.sub (local.get $j) (i32.const 1)))
+ (br $l_continue))
+ (local.set $iters (i32.add (local.get $iters) (i32.const 1)))
+ (br_if $iter_continue (i32.lt_s (local.get $iters) (local.get $numiters)))))))`));
+
+ for (let [mode,freq] of [[14,100], // Compact every 100 allocations
+ [2,12], // Collect every 12 allocations
+ [7,100], // Minor gc every 100 allocations
+ [15,100]]) // Verify heap integrity
+ {
+ if (this.gczeal)
+ this.gczeal(mode,freq);
+ let k = 0;
+ let ins = new WebAssembly.Instance(mod, {m:{item:() => { return { x: k++ } }}}).exports;
+ for ( let i=0; i < 1000; i++ )
+ ins.run(1000);
+ }
+}
+
+