[WASM-References] Add support for Anyref tables, Table.get and Table.set (for Anyref...
authorjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Jun 2019 03:14:47 +0000 (03:14 +0000)
committerjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Jun 2019 03:14:47 +0000 (03:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198398

Reviewed by Saam Barati.

JSTests:

* wasm/references/anyref_table.js: Added.
(string_appeared_here.doGCSet):
(doGCTest):
(doGCSet.doGCTest.let.count.0.doBarrierSet):
* wasm/references/anyref_table_import.js: Added.
(makeImport):
(string_appeared_here.fullGC.assert.eq.1.exports.get_tbl.makeImport):
(string_appeared_here.fullGC.assert.eq.1.exports.get_tbl):
* wasm/references/is_null_error.js: Removed.
* wasm/references/validation.js: Added.
(assert.throws.new.WebAssembly.Module.bin):
(assert.throws):
* wasm/wasm.json:

Source/JavaScriptCore:

Create a new table subtype called FuncRefTable (note: Anyfunc was renamed to Funcref in the references spec).
Table now write-barriers and visits its children's wrapper objects. FuncRefTable caches some extra data to
support calling from wasm. A JSWebAssemblyTable is required to set an anyref element, but this is only because
we need to write barrier it (so it should not restrict how we implement threads). This patch does, however,
restrict the implementation of function references to require every Ref.func to have an associated wrapper. This
can be done statically, so this too should not restrict our threads implementation.

* wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::addTableGet):
(JSC::Wasm::AirIRGenerator::addTableSet):
(JSC::Wasm::AirIRGenerator::addCallIndirect):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::addLocal):
(JSC::Wasm::B3IRGenerator::addTableGet):
(JSC::Wasm::B3IRGenerator::addTableSet):
(JSC::Wasm::B3IRGenerator::addCallIndirect):
* wasm/WasmFormat.h:
(JSC::Wasm::TableInformation::TableInformation):
(JSC::Wasm::TableInformation::type const):
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* wasm/WasmSectionParser.cpp:
(JSC::Wasm::SectionParser::parseTableHelper):
* wasm/WasmTable.cpp:
(JSC::Wasm::Table::Table):
(JSC::Wasm::Table::tryCreate):
(JSC::Wasm::Table::grow):
(JSC::Wasm::Table::clear):
(JSC::Wasm::Table::set):
(JSC::Wasm::Table::get):
(JSC::Wasm::Table::visitChildren):
(JSC::Wasm::FuncRefTable::FuncRefTable):
(JSC::Wasm::FuncRefTable::setFunction):
(JSC::Wasm::Table::~Table): Deleted.
(JSC::Wasm::Table::clearFunction): Deleted.
(JSC::Wasm::Table::setFunction): Deleted.
* wasm/WasmTable.h:
(JSC::Wasm::Table::length const):
(JSC::Wasm::Table::type const):
(JSC::Wasm::Table::setOwner):
(JSC::Wasm::FuncRefTable::offsetOfFunctions):
(JSC::Wasm::FuncRefTable::offsetOfInstances):
(JSC::Wasm::Table::offsetOfFunctions): Deleted.
(JSC::Wasm::Table::offsetOfInstances): Deleted.
* wasm/WasmValidate.cpp:
(JSC::Wasm::Validate::addTableGet):
(JSC::Wasm::Validate::addTableSet):
(JSC::Wasm::Validate::addCallIndirect):
* wasm/js/JSWebAssemblyTable.cpp:
(JSC::JSWebAssemblyTable::JSWebAssemblyTable):
(JSC::JSWebAssemblyTable::finishCreation):
(JSC::JSWebAssemblyTable::visitChildren):
(JSC::JSWebAssemblyTable::grow):
(JSC::JSWebAssemblyTable::get):
(JSC::JSWebAssemblyTable::set):
(JSC::JSWebAssemblyTable::clear):
(JSC::JSWebAssemblyTable::getFunction): Deleted.
(JSC::JSWebAssemblyTable::clearFunction): Deleted.
(JSC::JSWebAssemblyTable::setFunction): Deleted.
* wasm/js/JSWebAssemblyTable.h:
* wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::link):
(JSC::WebAssemblyModuleRecord::evaluate):
* wasm/js/WebAssemblyTableConstructor.cpp:
(JSC::constructJSWebAssemblyTable):
* wasm/js/WebAssemblyTablePrototype.cpp:
(JSC::webAssemblyTableProtoFuncGet):
(JSC::webAssemblyTableProtoFuncSet):
* wasm/wasm.json:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@246139 268f45cc-cd09-0410-ab3c-d52691b4dbfc

22 files changed:
JSTests/ChangeLog
JSTests/wasm/js-api/table.js
JSTests/wasm/references/anyref_table.js [new file with mode: 0644]
JSTests/wasm/references/anyref_table_import.js [new file with mode: 0644]
JSTests/wasm/references/is_null_error.js [deleted file]
JSTests/wasm/references/validation.js [new file with mode: 0644]
JSTests/wasm/wasm.json
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmFormat.h
Source/JavaScriptCore/wasm/WasmFunctionParser.h
Source/JavaScriptCore/wasm/WasmSectionParser.cpp
Source/JavaScriptCore/wasm/WasmTable.cpp
Source/JavaScriptCore/wasm/WasmTable.h
Source/JavaScriptCore/wasm/WasmValidate.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyTable.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyTable.h
Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyTableConstructor.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyTablePrototype.cpp
Source/JavaScriptCore/wasm/wasm.json

index 189ecbd..59fc2ac 100644 (file)
@@ -1,5 +1,26 @@
 2019-06-05  Justin Michaud  <justin_michaud@apple.com>
 
+        [WASM-References] Add support for Anyref tables, Table.get and Table.set (for Anyref only).
+        https://bugs.webkit.org/show_bug.cgi?id=198398
+
+        Reviewed by Saam Barati.
+
+        * wasm/references/anyref_table.js: Added.
+        (string_appeared_here.doGCSet):
+        (doGCTest):
+        (doGCSet.doGCTest.let.count.0.doBarrierSet):
+        * wasm/references/anyref_table_import.js: Added.
+        (makeImport):
+        (string_appeared_here.fullGC.assert.eq.1.exports.get_tbl.makeImport):
+        (string_appeared_here.fullGC.assert.eq.1.exports.get_tbl):
+        * wasm/references/is_null_error.js: Removed.
+        * wasm/references/validation.js: Added.
+        (assert.throws.new.WebAssembly.Module.bin):
+        (assert.throws):
+        * wasm/wasm.json:
+
+2019-06-05  Justin Michaud  <justin_michaud@apple.com>
+
         WebAssembly: pow functions returns 0 when exponent 1.0 or -1.0
         https://bugs.webkit.org/show_bug.cgi?id=198106
 
index cf68b44..bbca89e 100644 (file)
@@ -132,29 +132,29 @@ function assertBadTableImport(tableDescription, message) {
 {
     let badDescriptions = [
         [{initial: 10, element: "i32"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, element: "f32"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, element: "f64"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, element: "i64"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, maximum: 20, element: "i32"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -1 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, maximum: 20, element: "f32"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -3 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, maximum: 20, element: "f64"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -4 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
         [{initial: 10, maximum: 20, element: "i64"},
-         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
-         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
+         "WebAssembly.Module doesn't parse at byte 18: Table type should be anyfunc or anyref, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
+         "WebAssembly.Module doesn't parse at byte 26: Table type should be anyfunc or anyref, got -2 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')"],
 
         [{initial: 10, maximum: 9, element: "anyfunc"},
          "WebAssembly.Module doesn't parse at byte 21: resizable limits has a initial page count of 10 which is greater than its maximum 9 (evaluating 'new WebAssembly.Module(builder.WebAssembly().get())')",
diff --git a/JSTests/wasm/references/anyref_table.js b/JSTests/wasm/references/anyref_table.js
new file mode 100644 (file)
index 0000000..8a0c293
--- /dev/null
@@ -0,0 +1,100 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 20, maximum: 30, element: "anyref"})
+      .End()
+      .Export()
+          .Function("set_tbl")
+          .Function("get_tbl")
+          .Function("tbl_is_null")
+          .Function("set_tbl_null")
+          .Table("tbl", 0)
+      .End()
+      .Code()
+        .Function("set_tbl", { params: ["anyref"], ret: "void" })
+          .I32Const(0)
+          .GetLocal(0)
+          .TableSet()
+        .End()
+
+        .Function("get_tbl", { params: [], ret: "anyref" })
+          .I32Const(0)
+          .TableGet()
+        .End()
+
+        .Function("tbl_is_null", { params: [], ret: "i32" })
+            .Call(1)
+            .RefIsNull()
+        .End()
+
+        .Function("set_tbl_null", { params: [], ret: "void" })
+            .RefNull()
+            .Call(0)
+        .End()
+      .End().WebAssembly().get()));
+
+fullGC()
+
+assert.eq($1.exports.get_tbl(), null)
+assert.eq($1.exports.tbl_is_null(), 1)
+
+$1.exports.set_tbl("hi")
+fullGC()
+assert.eq($1.exports.get_tbl(), "hi")
+assert.eq($1.exports.tbl_is_null(), 0)
+
+assert.eq($1.exports.tbl.get(0), "hi")
+assert.eq($1.exports.tbl.get(1), null)
+
+$1.exports.tbl.set(0, { test: "test" });
+fullGC()
+assert.eq($1.exports.get_tbl().test, "test")
+
+assert.eq($1.exports.tbl.grow(10), 20)
+assert.eq($1.exports.tbl.grow(0), 30)
+assert.eq($1.exports.get_tbl().test, "test")
+fullGC()
+assert.eq($1.exports.get_tbl().test, "test")
+
+function doGCSet() {
+    fullGC()
+    $1.exports.set_tbl({ test: -1 })
+    fullGC()
+}
+
+function doGCTest() {
+    for (let i=0; i<1000; ++i) {
+        assert.eq($1.exports.get_tbl().test, -1)
+        fullGC()
+    }
+}
+
+doGCSet()
+doGCTest()
+
+let count = 0
+
+function doBarrierSet() {
+    ++count
+    $1.exports.set_tbl({ test: -count })
+}
+
+function doBarrierTest() {
+    let garbage = { val: "hi", val2: 5, arr: [] }
+    for (let i=0; i<100; ++i) garbage.arr += ({ field: i })
+
+    for (let j=0; j<1000; ++j) {
+        assert.eq($1.exports.get_tbl().test, -count)
+        edenGC()
+    }
+}
+
+for (let i=0; i<5; ++i) {
+    doBarrierSet()
+    doBarrierTest()
+    doBarrierTest()
+}
diff --git a/JSTests/wasm/references/anyref_table_import.js b/JSTests/wasm/references/anyref_table_import.js
new file mode 100644 (file)
index 0000000..88cab7c
--- /dev/null
@@ -0,0 +1,227 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+{
+    function makeImport() {
+        const tbl = new WebAssembly.Table({initial:2, element:"anyref"});
+
+        const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Import()
+                .Table("imp", "tbl", {initial: 2, element: "anyref"})
+          .End()
+          .Function().End()
+          .Export()
+              .Function("set_tbl")
+              .Function("get_tbl")
+              .Function("tbl_is_null")
+              .Function("set_tbl_null")
+              .Table("tbl", 0)
+          .End()
+          .Code()
+            .Function("set_tbl", { params: ["anyref"], ret: "void" })
+              .I32Const(0)
+              .GetLocal(0)
+              .TableSet()
+            .End()
+
+            .Function("get_tbl", { params: [], ret: "anyref" })
+              .I32Const(0)
+              .TableGet()
+            .End()
+
+            .Function("tbl_is_null", { params: [], ret: "i32" })
+                .Call(1)
+                .RefIsNull()
+            .End()
+
+            .Function("set_tbl_null", { params: [], ret: "void" })
+                .RefNull()
+                .Call(0)
+            .End()
+          .End().WebAssembly().get()), { imp: { tbl }});
+        fullGC()
+        return $1
+    }
+
+    const $1 = makeImport()
+    fullGC()
+
+    assert.eq($1.exports.get_tbl(), null)
+    assert.eq($1.exports.tbl_is_null(), 1)
+
+    $1.exports.set_tbl("hi")
+    fullGC()
+    assert.eq($1.exports.get_tbl(), "hi")
+    assert.eq($1.exports.tbl_is_null(), 0)
+
+    assert.eq($1.exports.tbl.get(0), "hi")
+    assert.eq($1.exports.tbl.get(1), null)
+
+    $1.exports.tbl.set(0, { test: "test" });
+    fullGC()
+    assert.eq($1.exports.get_tbl().test, "test")
+
+    assert.eq($1.exports.tbl.grow(10), 2)
+    assert.eq($1.exports.tbl.grow(0), 12)
+    assert.eq($1.exports.get_tbl().test, "test")
+    fullGC()
+    assert.eq($1.exports.get_tbl().test, "test")
+}
+
+{
+    function makeImport() {
+        const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Import()
+                .Table("imp", "tbl", {initial: 2, element: "anyref"})
+          .End()
+          .Function().End()
+          .Export()
+              .Function("set_tbl")
+              .Function("get_tbl")
+              .Function("tbl_is_null")
+              .Function("set_tbl_null")
+              .Table("tbl", 0)
+          .End()
+          .Code()
+            .Function("set_tbl", { params: ["anyref"], ret: "void" })
+              .I32Const(0)
+              .GetLocal(0)
+              .TableSet()
+            .End()
+
+            .Function("get_tbl", { params: [], ret: "anyref" })
+              .I32Const(0)
+              .TableGet()
+            .End()
+
+            .Function("tbl_is_null", { params: [], ret: "i32" })
+                .Call(1)
+                .RefIsNull()
+            .End()
+
+            .Function("set_tbl_null", { params: [], ret: "void" })
+                .RefNull()
+                .Call(0)
+            .End()
+          .End().WebAssembly().get()), { imp: { tbl: new WebAssembly.Table({initial:2, element:"anyref"}) }});
+        fullGC()
+
+        $1.exports.tbl.set(0, { test: "test" });
+
+        const $2 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Import()
+                .Table("imp", "tbl", {initial: 2, element: "anyref"})
+          .End()
+          .Function().End()
+          .Export()
+              .Function("set_tbl")
+              .Function("get_tbl")
+              .Function("tbl_is_null")
+              .Function("set_tbl_null")
+              .Table("tbl", 0)
+          .End()
+          .Code()
+            .Function("set_tbl", { params: ["anyref"], ret: "void" })
+              .I32Const(0)
+              .GetLocal(0)
+              .TableSet()
+            .End()
+
+            .Function("get_tbl", { params: [], ret: "anyref" })
+              .I32Const(0)
+              .TableGet()
+            .End()
+
+            .Function("tbl_is_null", { params: [], ret: "i32" })
+                .Call(1)
+                .RefIsNull()
+            .End()
+
+            .Function("set_tbl_null", { params: [], ret: "void" })
+                .RefNull()
+                .Call(0)
+            .End()
+          .End().WebAssembly().get()), { imp: { tbl: $1.exports.tbl }});
+        fullGC()
+
+        return $2
+    }
+
+    const $1 = makeImport()
+    fullGC()
+
+    assert.eq($1.exports.get_tbl().test, "test")
+    assert.eq($1.exports.tbl_is_null(), 0)
+
+    $1.exports.set_tbl("hi")
+    fullGC()
+    assert.eq($1.exports.get_tbl(), "hi")
+    assert.eq($1.exports.tbl_is_null(), 0)
+
+    assert.eq($1.exports.tbl.get(0), "hi")
+    assert.eq($1.exports.tbl.get(1), null)
+
+    $1.exports.tbl.set(0, { test: "test" });
+    fullGC()
+    assert.eq($1.exports.get_tbl().test, "test")
+
+    assert.eq($1.exports.tbl.grow(10), 2)
+    assert.eq($1.exports.tbl.grow(0), 12)
+    assert.eq($1.exports.get_tbl().test, "test")
+    fullGC()
+    assert.eq($1.exports.get_tbl().test, "test")
+}
+
+{
+    let tbl = new WebAssembly.Table({initial:1, element:"anyref"});
+
+    function doSet(i, v) {
+        tbl.set(i, { test: v });
+    }
+
+    function makeGarbage(depth) {
+        let garbage = { val: "hi", val2: 5, arr: [] }
+        for (let i=0; i<100; ++i) garbage.arr += ({ field: i })
+
+        if (depth < 5)
+            return 1 + makeGarbage(depth + 1);
+        return 0;
+    }
+
+    for (let iter=0; iter<5; ++iter) {
+        for (let k=0; k<100; ++k)
+            tbl = new WebAssembly.Table({initial:100, element:"anyref"})
+
+        for (let i=1; i<20; ++i) {
+            const len = tbl.length;
+            for (let j=0; j<len; ++j)
+                doSet(j, j);
+            makeGarbage(0);
+            tbl.grow(Math.pow(2,i));
+            for (let j=0; j<len; ++j)
+                assert.eq(tbl.get(j).test, j);
+            for (let j=0; j<tbl.length; ++j)
+                doSet(j, -j);
+        }
+    }
+}
+
+{
+    const tbl = new WebAssembly.Table({initial:2, element:"anyfunc"});
+
+    const mod = new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Import()
+            .Table("imp", "tbl", {initial: 2, element: "anyref"})
+      .End()
+      .Function().End()
+      .Export()
+      .End()
+      .Code()
+      .End().WebAssembly().get())
+
+    assert.throws(() => new WebAssembly.Instance(mod, { imp: { tbl }}), Error, "Table import imp:tbl provided a 'type' that is wrong (evaluating 'new WebAssembly.Instance(mod, { imp: { tbl }})')");
+}
diff --git a/JSTests/wasm/references/is_null_error.js b/JSTests/wasm/references/is_null_error.js
deleted file mode 100644 (file)
index 915b36a..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as assert from '../assert.js';
-import Builder from '../Builder.js';
-
-{
-    const builder = (new Builder())
-      .Type().End()
-      .Function().End()
-      .Export()
-          .Function("j")
-      .End()
-      .Code()
-        .Function("j", { params: [], ret: "i32" })
-            .I32Const(0)
-            .RefIsNull()
-        .End()
-      .End();
-
-    const bin = builder.WebAssembly();
-    bin.trim();
-
-    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: ref.is_null to type I32 expected Anyref, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
-}
diff --git a/JSTests/wasm/references/validation.js b/JSTests/wasm/references/validation.js
new file mode 100644 (file)
index 0000000..d38c265
--- /dev/null
@@ -0,0 +1,94 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+{
+    const builder = (new Builder())
+      .Type().End()
+      .Function().End()
+      .Export()
+          .Function("j")
+      .End()
+      .Code()
+        .Function("j", { params: [], ret: "i32" })
+            .I32Const(0)
+            .RefIsNull()
+        .End()
+      .End();
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: ref.is_null to type I32 expected Anyref, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
+}
+
+{
+    const builder = (new Builder())
+      .Type().End()
+      .Function().End()
+      .Export()
+          .Function("j")
+      .End()
+      .Code()
+        .Function("j", { params: [], ret: "void" })
+            .I32Const(0)
+            .I32Const(0)
+            .TableSet()
+        .End()
+      .End();
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: table.set value to type I32 expected Anyref, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
+}
+
+{
+    const builder = (new Builder())
+      .Type().End()
+      .Import()
+            .Table("imp", "tbl", {initial: 2, element: "anyfunc"})
+      .End()
+      .Function().End()
+      .Export()
+          .Function("j")
+      .End()
+      .Code()
+        .Function("j", { params: ["anyref"], ret: "void" })
+            .I32Const(0)
+            .GetLocal(0)
+            .TableSet()
+        .End()
+      .End();
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: table.set expects the table to have type Anyref, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
+}
+
+{
+    const builder = (new Builder())
+      .Type().End()
+      .Import()
+            .Table("imp", "tbl", {initial: 2, element: "anyfunc"})
+      .End()
+      .Function().End()
+      .Export()
+          .Function("j")
+      .End()
+      .Code()
+        .Function("j", { params: [], ret: "anyref" })
+            .I32Const(0)
+            .TableGet()
+        .End()
+      .End();
+
+    const bin = builder.WebAssembly();
+    bin.trim();
+
+    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: table.get expects the table to have type Anyref, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
+}
+
+{
+    assert.throws(() => new WebAssembly.Table({initial:2, element:"i32"}), TypeError, "WebAssembly.Table expects its 'element' field to be the string 'anyfunc' or 'anyref'");
+}
index e61aa74..30bbc74 100644 (file)
@@ -18,7 +18,7 @@
     },
     "value_type": ["i32", "i64", "f32", "f64", "anyref"],
     "block_type": ["i32", "i64", "f32", "f64", "void", "anyref"],
-    "elem_type": ["anyfunc"],
+    "elem_type": ["anyfunc","anyref"],
     "external_kind": {
         "Function": { "type": "uint8", "value": 0 },
         "Table":    { "type": "uint8", "value": 1 },
@@ -66,6 +66,8 @@
         "tee_local":           { "category": "special",    "value":  34, "return": ["any"],      "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "write a local variable or parameter and return the same value" },
         "get_global":          { "category": "special",    "value":  35, "return": ["any"],      "parameter": [],                       "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "read a global variable" },
         "set_global":          { "category": "special",    "value":  36, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "write a global variable" },
+        "table.get":           { "category": "special",    "value":  37, "return": ["anyref"],   "parameter": ["i32"],                  "immediate": [],                                                                                           "description": "get a table value" },
+        "table.set":           { "category": "special",    "value":  38, "return": [],           "parameter": ["i32", "anyref"],        "immediate": [],                                                                                           "description": "set a table value" },
         "call":                { "category": "call",       "value":  16, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "function_index", "type": "varuint32"}],                                            "description": "call a function by its index" },
         "call_indirect":       { "category": "call",       "value":  17, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "type_index",     "type": "varuint32"}, {"name": "reserved", "type": "varuint1"}],  "description": "call a function indirect with an expected signature" },
         "i32.load8_s":         { "category": "memory",     "value":  44, "return": ["i32"],      "parameter": ["addr"],                 "immediate": [{"name": "flags",          "type": "varuint32"}, {"name": "offset",   "type": "varuint32"}], "description": "load from memory" },
index 843865c..9ad3564 100644 (file)
@@ -1,5 +1,83 @@
 2019-06-05  Justin Michaud  <justin_michaud@apple.com>
 
+        [WASM-References] Add support for Anyref tables, Table.get and Table.set (for Anyref only).
+        https://bugs.webkit.org/show_bug.cgi?id=198398
+
+        Reviewed by Saam Barati.
+
+        Create a new table subtype called FuncRefTable (note: Anyfunc was renamed to Funcref in the references spec).
+        Table now write-barriers and visits its children's wrapper objects. FuncRefTable caches some extra data to
+        support calling from wasm. A JSWebAssemblyTable is required to set an anyref element, but this is only because
+        we need to write barrier it (so it should not restrict how we implement threads). This patch does, however,
+        restrict the implementation of function references to require every Ref.func to have an associated wrapper. This
+        can be done statically, so this too should not restrict our threads implementation. 
+
+        * wasm/WasmAirIRGenerator.cpp:
+        (JSC::Wasm::AirIRGenerator::addTableGet):
+        (JSC::Wasm::AirIRGenerator::addTableSet):
+        (JSC::Wasm::AirIRGenerator::addCallIndirect):
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::addLocal):
+        (JSC::Wasm::B3IRGenerator::addTableGet):
+        (JSC::Wasm::B3IRGenerator::addTableSet):
+        (JSC::Wasm::B3IRGenerator::addCallIndirect):
+        * wasm/WasmFormat.h:
+        (JSC::Wasm::TableInformation::TableInformation):
+        (JSC::Wasm::TableInformation::type const):
+        * wasm/WasmFunctionParser.h:
+        (JSC::Wasm::FunctionParser<Context>::parseExpression):
+        (JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
+        * wasm/WasmSectionParser.cpp:
+        (JSC::Wasm::SectionParser::parseTableHelper):
+        * wasm/WasmTable.cpp:
+        (JSC::Wasm::Table::Table):
+        (JSC::Wasm::Table::tryCreate):
+        (JSC::Wasm::Table::grow):
+        (JSC::Wasm::Table::clear):
+        (JSC::Wasm::Table::set):
+        (JSC::Wasm::Table::get):
+        (JSC::Wasm::Table::visitChildren):
+        (JSC::Wasm::FuncRefTable::FuncRefTable):
+        (JSC::Wasm::FuncRefTable::setFunction):
+        (JSC::Wasm::Table::~Table): Deleted.
+        (JSC::Wasm::Table::clearFunction): Deleted.
+        (JSC::Wasm::Table::setFunction): Deleted.
+        * wasm/WasmTable.h:
+        (JSC::Wasm::Table::length const):
+        (JSC::Wasm::Table::type const):
+        (JSC::Wasm::Table::setOwner):
+        (JSC::Wasm::FuncRefTable::offsetOfFunctions):
+        (JSC::Wasm::FuncRefTable::offsetOfInstances):
+        (JSC::Wasm::Table::offsetOfFunctions): Deleted.
+        (JSC::Wasm::Table::offsetOfInstances): Deleted.
+        * wasm/WasmValidate.cpp:
+        (JSC::Wasm::Validate::addTableGet):
+        (JSC::Wasm::Validate::addTableSet):
+        (JSC::Wasm::Validate::addCallIndirect):
+        * wasm/js/JSWebAssemblyTable.cpp:
+        (JSC::JSWebAssemblyTable::JSWebAssemblyTable):
+        (JSC::JSWebAssemblyTable::finishCreation):
+        (JSC::JSWebAssemblyTable::visitChildren):
+        (JSC::JSWebAssemblyTable::grow):
+        (JSC::JSWebAssemblyTable::get):
+        (JSC::JSWebAssemblyTable::set):
+        (JSC::JSWebAssemblyTable::clear):
+        (JSC::JSWebAssemblyTable::getFunction): Deleted.
+        (JSC::JSWebAssemblyTable::clearFunction): Deleted.
+        (JSC::JSWebAssemblyTable::setFunction): Deleted.
+        * wasm/js/JSWebAssemblyTable.h:
+        * wasm/js/WebAssemblyModuleRecord.cpp:
+        (JSC::WebAssemblyModuleRecord::link):
+        (JSC::WebAssemblyModuleRecord::evaluate):
+        * wasm/js/WebAssemblyTableConstructor.cpp:
+        (JSC::constructJSWebAssemblyTable):
+        * wasm/js/WebAssemblyTablePrototype.cpp:
+        (JSC::webAssemblyTableProtoFuncGet):
+        (JSC::webAssemblyTableProtoFuncSet):
+        * wasm/wasm.json:
+
+2019-06-05  Justin Michaud  <justin_michaud@apple.com>
+
         WebAssembly: pow functions returns 0 when exponent 1.0 or -1.0
         https://bugs.webkit.org/show_bug.cgi?id=198106
 
index 3f51d0b..57b3df8 100644 (file)
@@ -232,8 +232,13 @@ public:
     ExpressionType addConstant(Type, uint64_t);
     ExpressionType addConstant(BasicBlock*, Type, uint64_t);
 
+    // References
     PartialResult WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
 
+    // Tables
+    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     PartialResult WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
@@ -949,6 +954,40 @@ auto AirIRGenerator::addRefIsNull(ExpressionType& value, ExpressionType& result)
     return { };
 }
 
+auto AirIRGenerator::addTableGet(ExpressionType& idx, ExpressionType& result) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    ASSERT(idx.tmp());
+    ASSERT(idx.type() == Type::I32);
+    result = tmpForType(Type::Anyref);
+
+    uint64_t (*doGet)(Instance*, int32_t) = [] (Instance* instance, int32_t idx) -> uint64_t {
+        return JSValue::encode(instance->table()->get(idx));
+    };
+
+    emitCCall(doGet, result, instanceValue(), idx);
+
+    return { };
+}
+
+auto AirIRGenerator::addTableSet(ExpressionType& idx, ExpressionType& value) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    ASSERT(idx.tmp());
+    ASSERT(idx.type() == Type::I32);
+    ASSERT(value.tmp());
+
+    void (*doSet)(Instance*, int32_t, uint64_t value) = [] (Instance* instance, int32_t idx, uint64_t value) -> void {
+        // FIXME: We need to box wasm Funcrefs once they are supported here.
+        // <https://bugs.webkit.org/show_bug.cgi?id=198157>
+        instance->table()->set(idx, JSValue::decode(value));
+    };
+
+    emitCCall(doSet, TypedTmp(), instanceValue(), idx, value);
+
+    return { };
+}
+
 auto AirIRGenerator::getLocal(uint32_t index, ExpressionType& result) -> PartialResult
 {
     ASSERT(m_locals[index].tmp());
@@ -1841,6 +1880,7 @@ auto AirIRGenerator::addCallIndirect(const Signature& signature, Vector<Expressi
 {
     ExpressionType calleeIndex = args.takeLast();
     ASSERT(signature.argumentCount() == args.size());
+    ASSERT(m_info.tableInformation.type() == TableElementType::Funcref);
 
     m_makesCalls = true;
     // Note: call indirect can call either WebAssemblyFunction or WebAssemblyWrapperFunction. Because
@@ -1856,13 +1896,13 @@ auto AirIRGenerator::addCallIndirect(const Signature& signature, Vector<Expressi
     ExpressionType callableFunctionBufferLength = g64();
     {
         RELEASE_ASSERT(Arg::isValidAddrForm(Instance::offsetOfTable(), B3::Width64));
-        RELEASE_ASSERT(Arg::isValidAddrForm(Table::offsetOfFunctions(), B3::Width64));
-        RELEASE_ASSERT(Arg::isValidAddrForm(Table::offsetOfInstances(), B3::Width64));
-        RELEASE_ASSERT(Arg::isValidAddrForm(Table::offsetOfLength(), B3::Width64));
+        RELEASE_ASSERT(Arg::isValidAddrForm(FuncRefTable::offsetOfFunctions(), B3::Width64));
+        RELEASE_ASSERT(Arg::isValidAddrForm(FuncRefTable::offsetOfInstances(), B3::Width64));
+        RELEASE_ASSERT(Arg::isValidAddrForm(FuncRefTable::offsetOfLength(), B3::Width64));
 
         append(Move, Arg::addr(instanceValue(), Instance::offsetOfTable()), callableFunctionBufferLength);
-        append(Move, Arg::addr(callableFunctionBufferLength, Table::offsetOfFunctions()), callableFunctionBuffer);
-        append(Move, Arg::addr(callableFunctionBufferLength, Table::offsetOfInstances()), instancesBuffer);
+        append(Move, Arg::addr(callableFunctionBufferLength, FuncRefTable::offsetOfFunctions()), callableFunctionBuffer);
+        append(Move, Arg::addr(callableFunctionBufferLength, FuncRefTable::offsetOfInstances()), instancesBuffer);
         append(Move32, Arg::addr(callableFunctionBufferLength, Table::offsetOfLength()), callableFunctionBufferLength);
     }
 
index 5e87f4b..2226ffc 100644 (file)
@@ -185,8 +185,13 @@ public:
     PartialResult WARN_UNUSED_RETURN addLocal(Type, uint32_t);
     ExpressionType addConstant(Type, uint64_t);
 
+    // References
     PartialResult WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
 
+    // Tables
+    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     PartialResult WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
@@ -561,6 +566,36 @@ auto B3IRGenerator::addRefIsNull(ExpressionType& value, ExpressionType& result)
     return { };
 }
 
+auto B3IRGenerator::addTableGet(ExpressionType& idx, ExpressionType& result) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    uint64_t (*doGet)(Instance*, int32_t) = [] (Instance* instance, int32_t idx) -> uint64_t {
+        return JSValue::encode(instance->table()->get(idx));
+    };
+
+    result = m_currentBlock->appendNew<CCallValue>(m_proc, toB3Type(Anyref), origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(doGet, B3CCallPtrTag)),
+        instanceValue(), idx);
+
+    return { };
+}
+
+auto B3IRGenerator::addTableSet(ExpressionType& idx, ExpressionType& value) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    void (*doSet)(Instance*, int32_t, uint64_t value) = [] (Instance* instance, int32_t idx, uint64_t value) -> void {
+        // FIXME: We need to box wasm Funcrefs once they are supported here.
+        // <https://bugs.webkit.org/show_bug.cgi?id=198157>
+        instance->table()->set(idx, JSValue::decode(value));
+    };
+
+    m_currentBlock->appendNew<CCallValue>(m_proc, B3::Void, origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(doSet, B3CCallPtrTag)),
+        instanceValue(), idx, value);
+
+    return { };
+}
+
 auto B3IRGenerator::getLocal(uint32_t index, ExpressionType& result) -> PartialResult
 {
     ASSERT(m_locals[index]);
@@ -1263,9 +1298,9 @@ auto B3IRGenerator::addCallIndirect(const Signature& signature, Vector<Expressio
         ExpressionType table = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), origin(),
             instanceValue(), safeCast<int32_t>(Instance::offsetOfTable()));
         callableFunctionBuffer = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), origin(),
-            table, safeCast<int32_t>(Table::offsetOfFunctions()));
+            table, safeCast<int32_t>(FuncRefTable::offsetOfFunctions()));
         instancesBuffer = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), origin(),
-            table, safeCast<int32_t>(Table::offsetOfInstances()));
+            table, safeCast<int32_t>(FuncRefTable::offsetOfInstances()));
         callableFunctionBufferLength = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, Int32, origin(),
             table, safeCast<int32_t>(Table::offsetOfLength()));
         mask = m_currentBlock->appendNew<Value>(m_proc, ZExt32, origin(),
index b762264..6292250 100644 (file)
@@ -54,6 +54,11 @@ namespace Wasm {
 struct CompilationContext;
 struct ModuleInformation;
 
+enum class TableElementType : uint8_t {
+    Anyref,
+    Funcref
+};
+
 inline bool isValueType(Type type)
 {
     switch (type) {
@@ -214,11 +219,12 @@ public:
         ASSERT(!*this);
     }
 
-    TableInformation(uint32_t initial, Optional<uint32_t> maximum, bool isImport)
+    TableInformation(uint32_t initial, Optional<uint32_t> maximum, bool isImport, TableElementType type)
         : m_initial(initial)
         , m_maximum(maximum)
         , m_isImport(isImport)
         , m_isValid(true)
+        , m_type(type)
     {
         ASSERT(*this);
     }
@@ -227,12 +233,14 @@ public:
     bool isImport() const { return m_isImport; }
     uint32_t initial() const { return m_initial; }
     Optional<uint32_t> maximum() const { return m_maximum; }
+    TableElementType type() const { return m_type; }
 
 private:
     uint32_t m_initial;
     Optional<uint32_t> m_maximum;
     bool m_isImport { false };
     bool m_isValid { false };
+    TableElementType m_type;
 };
     
 struct CustomSection {
index 175056b..dfae8f2 100644 (file)
@@ -281,6 +281,24 @@ auto FunctionParser<Context>::parseExpression() -> PartialResult
         return { };
     }
 
+    case TableGet: {
+        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
+        ExpressionType result, idx;
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(idx, "table.get");
+        WASM_TRY_ADD_TO_CONTEXT(addTableGet(idx, result));
+        m_expressionStack.append(result);
+        return { };
+    }
+
+    case TableSet: {
+        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
+        ExpressionType val, idx;
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(val, "table.set");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(idx, "table.set");
+        WASM_TRY_ADD_TO_CONTEXT(addTableSet(idx, val));
+        return { };
+    }
+
     case RefNull: {
         WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
         m_expressionStack.append(m_context.addConstant(Anyref, JSValue::encode(jsNull())));
@@ -373,6 +391,7 @@ auto FunctionParser<Context>::parseExpression() -> PartialResult
         WASM_PARSER_FAIL_IF(!parseVarUInt1(reserved), "can't get call_indirect's reserved byte");
         WASM_PARSER_FAIL_IF(reserved, "call_indirect's 'reserved' varuint1 must be 0x0");
         WASM_PARSER_FAIL_IF(m_info.usedSignatures.size() <= signatureIndex, "call_indirect's signature index ", signatureIndex, " exceeds known signatures ", m_info.usedSignatures.size());
+        WASM_PARSER_FAIL_IF(m_info.tableInformation.type() != TableElementType::Funcref, "call_indirect is only valid when a table has type anyfunc");
 
         const Signature& calleeSignature = m_info.usedSignatures[signatureIndex].get();
         size_t argumentCount = calleeSignature.argumentCount() + 1; // Add the callee's index.
@@ -654,16 +673,14 @@ auto FunctionParser<Context>::parseUnreachableExpression() -> PartialResult
         return { };
     }
 
+    case TableGet:
+    case TableSet:
+    case RefIsNull:
     case RefNull: {
         WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
         return { };
     }
 
-    case RefIsNull: {
-        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
-        return { };
-    }
-
     case GrowMemory:
     case CurrentMemory: {
         uint8_t reserved;
index 6b940f7..7e45dee 100644 (file)
@@ -196,7 +196,7 @@ auto SectionParser::parseTableHelper(bool isImport) -> PartialResult
 
     int8_t type;
     WASM_PARSER_FAIL_IF(!parseInt7(type), "can't parse Table type");
-    WASM_PARSER_FAIL_IF(type != Wasm::Anyfunc, "Table type should be anyfunc, got ", type);
+    WASM_PARSER_FAIL_IF(type != Wasm::Anyfunc && type != Wasm::Anyref, "Table type should be anyfunc or anyref, got ", type);
 
     uint32_t initial;
     Optional<uint32_t> maximum;
@@ -207,7 +207,8 @@ auto SectionParser::parseTableHelper(bool isImport) -> PartialResult
 
     ASSERT(!maximum || *maximum >= initial);
 
-    m_info->tableInformation = TableInformation(initial, maximum, isImport);
+    TableElementType tableType = type == Wasm::Anyfunc ? TableElementType::Funcref : TableElementType::Anyref;
+    m_info->tableInformation = TableInformation(initial, maximum, isImport, tableType);
 
     return { };
 }
index e5c608c..e77514c 100644 (file)
@@ -47,40 +47,44 @@ void Table::setLength(uint32_t length)
     ASSERT(m_mask == WTF::maskForSize(allocatedLength(length)));
 }
 
-RefPtr<Table> Table::tryCreate(uint32_t initial, Optional<uint32_t> maximum)
-{
-    if (!isValidLength(initial))
-        return nullptr;
-    return adoptRef(new (NotNull, fastMalloc(sizeof(Table))) Table(initial, maximum));
-}
-
-Table::~Table()
-{
-}
-
-Table::Table(uint32_t initial, Optional<uint32_t> maximum)
+Table::Table(uint32_t initial, Optional<uint32_t> maximum, TableElementType type)
+    : m_type(type)
+    , m_maximum(maximum)
+    , m_owner(nullptr)
 {
     setLength(initial);
-    m_maximum = maximum;
     ASSERT(!m_maximum || *m_maximum >= m_length);
 
     // FIXME: It might be worth trying to pre-allocate maximum here. The spec recommends doing so.
     // But for now, we're not doing that.
-    m_importableFunctions = MallocPtr<WasmToWasmImportableFunction>::malloc((sizeof(WasmToWasmImportableFunction) * Checked<size_t>(allocatedLength(m_length))).unsafeGet());
     // FIXME this over-allocates and could be smarter about not committing all of that memory https://bugs.webkit.org/show_bug.cgi?id=181425
-    m_instances = MallocPtr<Instance*>::malloc((sizeof(Instance*) * Checked<size_t>(allocatedLength(m_length))).unsafeGet());
+    m_jsValues = MallocPtr<WriteBarrier<Unknown>>::malloc((sizeof(WriteBarrier<Unknown>) * Checked<size_t>(allocatedLength(m_length))).unsafeGet());
     for (uint32_t i = 0; i < allocatedLength(m_length); ++i) {
-        new (&m_importableFunctions.get()[i]) WasmToWasmImportableFunction();
-        ASSERT(m_importableFunctions.get()[i].signatureIndex == Wasm::Signature::invalidIndex); // We rely on this in compiled code.
-        m_instances.get()[i] = nullptr;
+        new (&m_jsValues.get()[i]) WriteBarrier<Unknown>();
+        m_jsValues.get()[i].setStartingValue(jsNull());
+    }
+}
+
+RefPtr<Table> Table::tryCreate(uint32_t initial, Optional<uint32_t> maximum, TableElementType type)
+{
+    if (!isValidLength(initial))
+        return nullptr;
+    switch (type) {
+    case TableElementType::Funcref:
+        return adoptRef(new FuncRefTable(initial, maximum));
+    case TableElementType::Anyref:
+        return adoptRef(new Table(initial, maximum));
     }
 }
 
 Optional<uint32_t> Table::grow(uint32_t delta)
 {
+    RELEASE_ASSERT(m_owner);
     if (delta == 0)
         return length();
 
+    auto locker = holdLock(m_owner->cellLock());
+
     using Checked = Checked<uint32_t, RecordOverflow>;
     Checked newLengthChecked = length();
     newLengthChecked += delta;
@@ -108,27 +112,83 @@ Optional<uint32_t> Table::grow(uint32_t delta)
         return true;
     };
 
-    if (!checkedGrow(m_importableFunctions))
-        return WTF::nullopt;
-    if (!checkedGrow(m_instances))
+    if (auto* funcRefTable = asFuncrefTable()) {
+        if (!checkedGrow(funcRefTable->m_importableFunctions))
+            return WTF::nullopt;
+        if (!checkedGrow(funcRefTable->m_instances))
+            return WTF::nullopt;
+    }
+
+    if (!checkedGrow(m_jsValues))
         return WTF::nullopt;
 
     setLength(newLength);
-
     return newLength;
 }
 
-void Table::clearFunction(uint32_t index)
+void Table::clear(uint32_t index)
+{
+    RELEASE_ASSERT(index < length());
+    RELEASE_ASSERT(m_owner);
+    if (auto* funcRefTable = asFuncrefTable()) {
+        funcRefTable->m_importableFunctions.get()[index & m_mask] = WasmToWasmImportableFunction();
+        ASSERT(funcRefTable->m_importableFunctions.get()[index & m_mask].signatureIndex == Wasm::Signature::invalidIndex); // We rely on this in compiled code.
+        funcRefTable->m_instances.get()[index & m_mask] = nullptr;
+    }
+    m_jsValues.get()[index & m_mask].setStartingValue(jsNull());
+}
+
+void Table::set(uint32_t index, JSValue value)
+{
+    RELEASE_ASSERT(index < length());
+    RELEASE_ASSERT(isAnyrefTable());
+    RELEASE_ASSERT(m_owner);
+    clear(index);
+    m_jsValues.get()[index & m_mask].set(*m_owner->vm(), m_owner, value);
+}
+
+JSValue Table::get(uint32_t index) const
 {
     RELEASE_ASSERT(index < length());
-    m_importableFunctions.get()[index & m_mask] = WasmToWasmImportableFunction();
-    ASSERT(m_importableFunctions.get()[index & m_mask].signatureIndex == Wasm::Signature::invalidIndex); // We rely on this in compiled code.
-    m_instances.get()[index & m_mask] = nullptr;
+    RELEASE_ASSERT(m_owner);
+    return m_jsValues.get()[index & m_mask].get();
+}
+
+void Table::visitChildren(SlotVisitor& visitor)
+{
+    RELEASE_ASSERT(m_owner);
+    auto locker = holdLock(m_owner->cellLock());
+    for (unsigned i = 0; i < m_length; ++i)
+        visitor.append(m_jsValues.get()[i]);
+}
+
+FuncRefTable* Table::asFuncrefTable()
+{
+    return m_type == TableElementType::Funcref ? static_cast<FuncRefTable*>(this) : nullptr;
+}
+
+FuncRefTable::FuncRefTable(uint32_t initial, Optional<uint32_t> maximum)
+    : Table(initial, maximum, TableElementType::Funcref)
+{
+    // FIXME: It might be worth trying to pre-allocate maximum here. The spec recommends doing so.
+    // But for now, we're not doing that.
+    m_importableFunctions = MallocPtr<WasmToWasmImportableFunction>::malloc((sizeof(WasmToWasmImportableFunction) * Checked<size_t>(allocatedLength(m_length))).unsafeGet());
+    // FIXME this over-allocates and could be smarter about not committing all of that memory https://bugs.webkit.org/show_bug.cgi?id=181425
+    m_instances = MallocPtr<Instance*>::malloc((sizeof(Instance*) * Checked<size_t>(allocatedLength(m_length))).unsafeGet());
+    for (uint32_t i = 0; i < allocatedLength(m_length); ++i) {
+        new (&m_importableFunctions.get()[i]) WasmToWasmImportableFunction();
+        ASSERT(m_importableFunctions.get()[i].signatureIndex == Wasm::Signature::invalidIndex); // We rely on this in compiled code.
+        m_instances.get()[i] = nullptr;
+    }
 }
 
-void Table::setFunction(uint32_t index, WasmToWasmImportableFunction function, Instance* instance)
+void FuncRefTable::setFunction(uint32_t index, JSObject* optionalWrapper, WasmToWasmImportableFunction function, Instance* instance)
 {
     RELEASE_ASSERT(index < length());
+    RELEASE_ASSERT(m_owner);
+    clear(index);
+    if (optionalWrapper)
+        m_jsValues.get()[index & m_mask].set(*m_owner->vm(), m_owner, optionalWrapper);
     m_importableFunctions.get()[index & m_mask] = function;
     m_instances.get()[index & m_mask] = instance;
 }
index 7e30041..4d57d41 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "WasmFormat.h"
 #include "WasmLimits.h"
+#include "WriteBarrier.h"
 #include <wtf/MallocPtr.h>
 #include <wtf/Optional.h>
 #include <wtf/Ref.h>
 namespace JSC { namespace Wasm {
 
 class Instance;
+class FuncRefTable;
 
 class Table : public ThreadSafeRefCounted<Table> {
+    WTF_MAKE_NONCOPYABLE(Table);
+    WTF_MAKE_FAST_ALLOCATED(Table);
 public:
-    static RefPtr<Table> tryCreate(uint32_t initial, Optional<uint32_t> maximum);
+    static RefPtr<Table> tryCreate(uint32_t initial, Optional<uint32_t> maximum, TableElementType);
 
-    JS_EXPORT_PRIVATE ~Table();
+    JS_EXPORT_PRIVATE ~Table() = default;
 
     Optional<uint32_t> maximum() const { return m_maximum; }
     uint32_t length() const { return m_length; }
-    Optional<uint32_t> grow(uint32_t delta) WARN_UNUSED_RETURN;
-    void clearFunction(uint32_t);
-    void setFunction(uint32_t, WasmToWasmImportableFunction, Instance*);
 
-    static ptrdiff_t offsetOfFunctions() { return OBJECT_OFFSETOF(Table, m_importableFunctions); }
-    static ptrdiff_t offsetOfInstances() { return OBJECT_OFFSETOF(Table, m_instances); }
     static ptrdiff_t offsetOfLength() { return OBJECT_OFFSETOF(Table, m_length); }
     static ptrdiff_t offsetOfMask() { return OBJECT_OFFSETOF(Table, m_mask); }
 
     static uint32_t allocatedLength(uint32_t length);
     uint32_t mask() const { return m_mask; }
+
+    void setOwner(JSObject* owner)
+    {
+        ASSERT(!m_owner);
+        ASSERT(owner);
+        m_owner = owner;
+    }
+
+    TableElementType type() const { return m_type; }
+    bool isAnyrefTable() const { return m_type == TableElementType::Anyref; }
+    FuncRefTable* asFuncrefTable();
+
     static bool isValidLength(uint32_t length) { return length < maxTableEntries; }
 
-private:
-    Table(uint32_t initial, Optional<uint32_t> maximum);
+    void clear(uint32_t);
+    void set(uint32_t, JSValue);
+    JSValue get(uint32_t) const;
+
+    Optional<uint32_t> grow(uint32_t delta);
+
+    void visitChildren(SlotVisitor&);
+
+protected:
+    Table(uint32_t initial, Optional<uint32_t> maximum, TableElementType = TableElementType::Anyref);
 
     void setLength(uint32_t);
 
+    uint32_t m_length;
+    uint32_t m_mask;
+    const TableElementType m_type;
+    const Optional<uint32_t> m_maximum;
+
+    MallocPtr<WriteBarrier<Unknown>> m_jsValues;
+    JSObject* m_owner;
+};
+
+class FuncRefTable : public Table {
+public:
+    JS_EXPORT_PRIVATE ~FuncRefTable() = default;
+
+    void setFunction(uint32_t, JSObject*, WasmToWasmImportableFunction, Instance*);
+
+    static ptrdiff_t offsetOfFunctions() { return OBJECT_OFFSETOF(FuncRefTable, m_importableFunctions); }
+    static ptrdiff_t offsetOfInstances() { return OBJECT_OFFSETOF(FuncRefTable, m_instances); }
+
+private:
+    FuncRefTable(uint32_t initial, Optional<uint32_t> maximum);
+
     MallocPtr<WasmToWasmImportableFunction> m_importableFunctions;
     // call_indirect needs to do an Instance check to potentially context switch when calling a function to another instance. We can hold raw pointers to Instance here because the embedder ensures that Table keeps all the instances alive. We couldn't hold a Ref here because it would cause cycles.
     MallocPtr<Instance*> m_instances;
-    uint32_t m_length;
-    uint32_t m_mask;
-    Optional<uint32_t> m_maximum;
+
+    friend class Table;
 };
 
 } } // namespace JSC::Wasm
index fd54162..6619dfb 100644 (file)
@@ -100,8 +100,13 @@ public:
     Result WARN_UNUSED_RETURN addLocal(Type, uint32_t);
     ExpressionType addConstant(Type type, uint64_t) { return type; }
 
+    // References
     Result WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
 
+    // Tables
+    Result WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
+    Result WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+
     // Locals
     Result WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     Result WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
@@ -171,6 +176,24 @@ auto Validate::addArguments(const Signature& signature) -> Result
     return { };
 }
 
+auto Validate::addTableGet(ExpressionType& idx, ExpressionType& result) -> Result
+{
+    result = Type::Anyref;
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != idx, "table.get index to type ", idx, " expected ", Type::I32);
+    WASM_VALIDATOR_FAIL_IF(TableElementType::Anyref != m_module.tableInformation.type(), "table.get expects the table to have type ", Type::Anyref);
+
+    return { };
+}
+
+auto Validate::addTableSet(ExpressionType& idx, ExpressionType& value) -> Result
+{
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != idx, "table.set index to type ", idx, " expected ", Type::I32);
+    WASM_VALIDATOR_FAIL_IF(Type::Anyref != value, "table.set value to type ", value, " expected ", Type::Anyref);
+    WASM_VALIDATOR_FAIL_IF(TableElementType::Anyref != m_module.tableInformation.type(), "table.set expects the table to have type ", Type::Anyref);
+
+    return { };
+}
+
 auto Validate::addRefIsNull(ExpressionType& value, ExpressionType& result) -> Result
 {
     result = Type::I32;
@@ -346,6 +369,7 @@ auto Validate::addCall(unsigned, const Signature& signature, const Vector<Expres
 
 auto Validate::addCallIndirect(const Signature& signature, const Vector<ExpressionType>& args, ExpressionType& result) -> Result
 {
+    WASM_VALIDATOR_FAIL_IF(m_module.tableInformation.type() != TableElementType::Funcref, "Table must have type Anyfunc to call");
     const auto argumentCount = signature.argumentCount();
     WASM_VALIDATOR_FAIL_IF(argumentCount != args.size() - 1, "arity mismatch in call_indirect, got ", args.size() - 1, " arguments, expected ", argumentCount);
 
index 5be49dd..e441d0f 100644 (file)
@@ -47,6 +47,7 @@ JSWebAssemblyTable* JSWebAssemblyTable::create(ExecState* exec, VM& vm, Structur
     }
 
     auto* instance = new (NotNull, allocateCell<JSWebAssemblyTable>(vm.heap)) JSWebAssemblyTable(vm, structure, WTFMove(table));
+    instance->table()->setOwner(instance);
     instance->finishCreation(vm);
     return instance;
 }
@@ -60,12 +61,6 @@ JSWebAssemblyTable::JSWebAssemblyTable(VM& vm, Structure* structure, Ref<Wasm::T
     : Base(vm, structure)
     , m_table(WTFMove(table))
 {
-    // FIXME: It might be worth trying to pre-allocate maximum here. The spec recommends doing so.
-    // But for now, we're not doing that.
-    // FIXME this over-allocates and could be smarter about not committing all of that memory https://bugs.webkit.org/show_bug.cgi?id=181425
-    m_jsFunctions = MallocPtr<WriteBarrier<JSObject>>::malloc((sizeof(WriteBarrier<JSObject>) * Checked<size_t>(allocatedLength())).unsafeGet());
-    for (uint32_t i = 0; i < allocatedLength(); ++i)
-        new(&m_jsFunctions.get()[i]) WriteBarrier<JSObject>();
 }
 
 void JSWebAssemblyTable::finishCreation(VM& vm)
@@ -85,55 +80,49 @@ void JSWebAssemblyTable::visitChildren(JSCell* cell, SlotVisitor& visitor)
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
 
     Base::visitChildren(thisObject, visitor);
-
-    for (unsigned i = 0; i < thisObject->length(); ++i)
-        visitor.append(thisObject->m_jsFunctions.get()[i]);
+    thisObject->table()->visitChildren(visitor);
 }
 
 bool JSWebAssemblyTable::grow(uint32_t delta)
 {
     if (delta == 0)
         return true;
+    return !!m_table->grow(delta);
+}
 
-    size_t oldLength = length();
-
-    auto grew = m_table->grow(delta);
-    if (!grew)
-        return false;
-
-    size_t newLength = grew.value();
-    if (newLength > m_table->allocatedLength(oldLength))
-        // FIXME this over-allocates and could be smarter about not committing all of that memory https://bugs.webkit.org/show_bug.cgi?id=181425
-        m_jsFunctions.realloc((sizeof(WriteBarrier<JSObject>) * Checked<size_t>(m_table->allocatedLength(newLength))).unsafeGet());
-
-    for (size_t i = oldLength; i < m_table->allocatedLength(newLength); ++i)
-        new (&m_jsFunctions.get()[i]) WriteBarrier<JSObject>();
-
-    return true;
+JSValue JSWebAssemblyTable::get(uint32_t index)
+{
+    RELEASE_ASSERT(index < length());
+    return m_table->get(index);
 }
 
-JSObject* JSWebAssemblyTable::getFunction(uint32_t index)
+void JSWebAssemblyTable::set(uint32_t index, JSValue value)
 {
     RELEASE_ASSERT(index < length());
-    return m_jsFunctions.get()[index & m_table->mask()].get();
+    RELEASE_ASSERT(m_table->isAnyrefTable());
+    m_table->set(index, value);
 }
 
-void JSWebAssemblyTable::clearFunction(uint32_t index)
+void JSWebAssemblyTable::set(uint32_t index, WebAssemblyFunction* function)
 {
-    m_table->clearFunction(index);
-    m_jsFunctions.get()[index & m_table->mask()] = WriteBarrier<JSObject>();
+    RELEASE_ASSERT(index < length());
+    RELEASE_ASSERT(m_table->asFuncrefTable());
+    auto& subThis = *static_cast<Wasm::FuncRefTable*>(&m_table.get());
+    subThis.setFunction(index, function, function->importableFunction(), &function->instance()->instance());
 }
 
-void JSWebAssemblyTable::setFunction(VM& vm, uint32_t index, WebAssemblyFunction* function)
+void JSWebAssemblyTable::set(uint32_t index, WebAssemblyWrapperFunction* function)
 {
-    m_table->setFunction(index, function->importableFunction(), &function->instance()->instance());
-    m_jsFunctions.get()[index & m_table->mask()].set(vm, this, function);
+    RELEASE_ASSERT(index < length());
+    RELEASE_ASSERT(m_table->asFuncrefTable());
+    auto& subThis = *static_cast<Wasm::FuncRefTable*>(&m_table.get());
+    subThis.setFunction(index, function, function->importableFunction(), &function->instance()->instance());
 }
 
-void JSWebAssemblyTable::setFunction(VM& vm, uint32_t index, WebAssemblyWrapperFunction* function)
+void JSWebAssemblyTable::clear(uint32_t index)
 {
-    m_table->setFunction(index, function->importableFunction(), &function->instance()->instance());
-    m_jsFunctions.get()[index & m_table->mask()].set(vm, this, function);
+    RELEASE_ASSERT(index < length());
+    m_table->clear(index);
 }
 
 } // namespace JSC
index d2f3eac..ae66f97 100644 (file)
@@ -52,10 +52,11 @@ public:
     uint32_t length() const { return m_table->length(); }
     uint32_t allocatedLength() const { return m_table->allocatedLength(length()); }
     bool grow(uint32_t delta) WARN_UNUSED_RETURN;
-    JSObject* getFunction(uint32_t);
-    void clearFunction(uint32_t);
-    void setFunction(VM&, uint32_t, WebAssemblyFunction*);
-    void setFunction(VM&, uint32_t, WebAssemblyWrapperFunction*);
+    JSValue get(uint32_t);
+    void set(uint32_t, WebAssemblyFunction*);
+    void set(uint32_t, WebAssemblyWrapperFunction*);
+    void set(uint32_t, JSValue);
+    void clear(uint32_t);
 
     Wasm::Table* table() { return m_table.ptr(); }
 
@@ -66,7 +67,6 @@ private:
     static void visitChildren(JSCell*, SlotVisitor&);
 
     Ref<Wasm::Table> m_table;
-    MallocPtr<WriteBarrier<JSObject>> m_jsFunctions;
 };
 
 } // namespace JSC
index 5c04d4e..4f801e5 100644 (file)
@@ -280,6 +280,11 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
                     return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Imported Table", "'maximum' is larger than the module's expected 'maximum'")));
             }
 
+            auto expectedType = moduleInformation.tableInformation.type();
+            auto actualType = table->table()->type();
+            if (expectedType != actualType)
+                return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "Table import", "provided a 'type' that is wrong")));
+
             // ii. Append v to tables.
             // iii. Append v.[[Table]] to imports.
             m_instance->setTable(vm, table);
@@ -301,7 +306,7 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
         if (!!moduleInformation.tableInformation && !hasTableImport) {
             RELEASE_ASSERT(!moduleInformation.tableInformation.isImport());
             // We create a Table when it's a Table definition.
-            RefPtr<Wasm::Table> wasmTable = Wasm::Table::tryCreate(moduleInformation.tableInformation.initial(), moduleInformation.tableInformation.maximum());
+            RefPtr<Wasm::Table> wasmTable = Wasm::Table::tryCreate(moduleInformation.tableInformation.initial(), moduleInformation.tableInformation.maximum(), moduleInformation.tableInformation.type());
             if (!wasmTable)
                 return exception(createJSWebAssemblyLinkError(exec, vm, "couldn't create Table"));
             JSWebAssemblyTable* table = JSWebAssemblyTable::create(exec, vm, globalObject->webAssemblyTableStructure(), wasmTable.releaseNonNull());
@@ -536,12 +541,12 @@ JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
                     // Because a WebAssemblyWrapperFunction can never wrap another WebAssemblyWrapperFunction,
                     // the only type this could be is WebAssemblyFunction.
                     RELEASE_ASSERT(wasmFunction);
-                    table->setFunction(vm, tableIndex, wasmFunction);
+                    table->set(tableIndex, wasmFunction);
                     ++tableIndex;
                     continue;
                 }
 
-                table->setFunction(vm, tableIndex,
+                table->set(tableIndex,
                     WebAssemblyWrapperFunction::create(vm, globalObject, globalObject->webAssemblyWrapperFunctionStructure(), functionImport, functionIndex, m_instance.get(), signatureIndex));
                 ++tableIndex;
                 continue;
@@ -557,7 +562,7 @@ JSValue WebAssemblyModuleRecord::evaluate(ExecState* exec)
             WebAssemblyFunction* function = WebAssemblyFunction::create(
                 vm, globalObject, globalObject->webAssemblyFunctionStructure(), signature.argumentCount(), String(), m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
 
-            table->setFunction(vm, tableIndex, function);
+            table->set(tableIndex, function);
             ++tableIndex;
         }
     });
index 1a2eaa7..ca8260a 100644 (file)
@@ -58,14 +58,19 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyTable(ExecState* exec)
         memoryDescriptor = jsCast<JSObject*>(argument);
     }
 
+    Wasm::TableElementType type;
     {
         Identifier elementIdent = Identifier::fromString(&vm, "element");
         JSValue elementValue = memoryDescriptor->get(exec, elementIdent);
         RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
         String elementString = elementValue.toWTFString(exec);
         RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
-        if (elementString != "anyfunc")
-            return JSValue::encode(throwException(exec, throwScope, createTypeError(exec, "WebAssembly.Table expects its 'element' field to be the string 'anyfunc'"_s)));
+        if (elementString == "anyfunc")
+            type = Wasm::TableElementType::Funcref;
+        else if (elementString == "anyref")
+            type = Wasm::TableElementType::Anyref;
+        else
+            return JSValue::encode(throwException(exec, throwScope, createTypeError(exec, "WebAssembly.Table expects its 'element' field to be the string 'anyfunc' or 'anyref'"_s)));
     }
 
     Identifier initialIdent = Identifier::fromString(&vm, "initial");
@@ -90,7 +95,7 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyTable(ExecState* exec)
         }
     }
 
-    RefPtr<Wasm::Table> wasmTable = Wasm::Table::tryCreate(initial, maximum);
+    RefPtr<Wasm::Table> wasmTable = Wasm::Table::tryCreate(initial, maximum, type);
     if (!wasmTable) {
         return JSValue::encode(throwException(exec, throwScope,
             createRangeError(exec, "couldn't create Table"_s)));
index f481783..7f1057f 100644 (file)
@@ -109,9 +109,7 @@ static EncodedJSValue JSC_HOST_CALL webAssemblyTableProtoFuncGet(ExecState* exec
     if (index >= table->length())
         return JSValue::encode(throwException(exec, throwScope, createRangeError(exec, "WebAssembly.Table.prototype.get expects an integer less than the length of the table"_s)));
 
-    if (JSObject* result = table->getFunction(index))
-        return JSValue::encode(result);
-    return JSValue::encode(jsNull());
+    return JSValue::encode(table->get(index));
 }
 
 static EncodedJSValue JSC_HOST_CALL webAssemblyTableProtoFuncSet(ExecState* exec)
@@ -123,10 +121,6 @@ static EncodedJSValue JSC_HOST_CALL webAssemblyTableProtoFuncSet(ExecState* exec
     RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
 
     JSValue value = exec->argument(1);
-    WebAssemblyFunction* wasmFunction;
-    WebAssemblyWrapperFunction* wasmWrapperFunction;
-    if (!value.isNull() && !isWebAssemblyHostFunction(vm, value, wasmFunction, wasmWrapperFunction))
-        return JSValue::encode(throwException(exec, throwScope, createTypeError(exec, "WebAssembly.Table.prototype.set expects the second argument to be null or an instance of WebAssembly.Function"_s)));
 
     uint32_t index = toNonWrappingUint32(exec, exec->argument(0));
     RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
@@ -134,16 +128,24 @@ static EncodedJSValue JSC_HOST_CALL webAssemblyTableProtoFuncSet(ExecState* exec
     if (index >= table->length())
         return JSValue::encode(throwException(exec, throwScope, createRangeError(exec, "WebAssembly.Table.prototype.set expects an integer less than the length of the table"_s)));
 
-    if (value.isNull())
-        table->clearFunction(index);
-    else {
-        ASSERT(value.isObject() && isWebAssemblyHostFunction(vm, jsCast<JSObject*>(value), wasmFunction, wasmWrapperFunction));
-        ASSERT(!!wasmFunction || !!wasmWrapperFunction);
-        if (wasmFunction)
-            table->setFunction(vm, index, wasmFunction);
-        else
-            table->setFunction(vm, index, wasmWrapperFunction);
-    }
+    if (table->table()->asFuncrefTable()) {
+        WebAssemblyFunction* wasmFunction;
+        WebAssemblyWrapperFunction* wasmWrapperFunction;
+        if (!value.isNull() && !isWebAssemblyHostFunction(vm, value, wasmFunction, wasmWrapperFunction))
+            return JSValue::encode(throwException(exec, throwScope, createTypeError(exec, "WebAssembly.Table.prototype.set expects the second argument to be null or an instance of WebAssembly.Function"_s)));
+
+        if (value.isNull())
+            table->clear(index);
+        else {
+            ASSERT(value.isObject() && isWebAssemblyHostFunction(vm, jsCast<JSObject*>(value), wasmFunction, wasmWrapperFunction));
+            ASSERT(!!wasmFunction || !!wasmWrapperFunction);
+            if (wasmFunction)
+                table->set(index, wasmFunction);
+            else
+                table->set(index, wasmWrapperFunction);
+        }
+    } else
+        table->set(index, value);
     
     return JSValue::encode(jsUndefined());
 }
index e61aa74..30bbc74 100644 (file)
@@ -18,7 +18,7 @@
     },
     "value_type": ["i32", "i64", "f32", "f64", "anyref"],
     "block_type": ["i32", "i64", "f32", "f64", "void", "anyref"],
-    "elem_type": ["anyfunc"],
+    "elem_type": ["anyfunc","anyref"],
     "external_kind": {
         "Function": { "type": "uint8", "value": 0 },
         "Table":    { "type": "uint8", "value": 1 },
@@ -66,6 +66,8 @@
         "tee_local":           { "category": "special",    "value":  34, "return": ["any"],      "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "write a local variable or parameter and return the same value" },
         "get_global":          { "category": "special",    "value":  35, "return": ["any"],      "parameter": [],                       "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "read a global variable" },
         "set_global":          { "category": "special",    "value":  36, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "write a global variable" },
+        "table.get":           { "category": "special",    "value":  37, "return": ["anyref"],   "parameter": ["i32"],                  "immediate": [],                                                                                           "description": "get a table value" },
+        "table.set":           { "category": "special",    "value":  38, "return": [],           "parameter": ["i32", "anyref"],        "immediate": [],                                                                                           "description": "set a table value" },
         "call":                { "category": "call",       "value":  16, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "function_index", "type": "varuint32"}],                                            "description": "call a function by its index" },
         "call_indirect":       { "category": "call",       "value":  17, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "type_index",     "type": "varuint32"}, {"name": "reserved", "type": "varuint1"}],  "description": "call a function indirect with an expected signature" },
         "i32.load8_s":         { "category": "memory",     "value":  44, "return": ["i32"],      "parameter": ["addr"],                 "immediate": [{"name": "flags",          "type": "varuint32"}, {"name": "offset",   "type": "varuint32"}], "description": "load from memory" },