[WASM-References] Add support for Funcref in parameters and return types
authorjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 17 Jun 2019 18:44:18 +0000 (18:44 +0000)
committerjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 17 Jun 2019 18:44:18 +0000 (18:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198157

Reviewed by Yusuke Suzuki.

JSTests:

* wasm/Builder.js:
(export.default.Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.case.string_appeared_here.this.section):
* wasm/references/anyref_globals.js:
* wasm/references/func_ref.js: Added.
(fullGC.gc.makeExportedFunction):
(makeExportedIdent):
(makeAnyfuncIdent):
(fun):
(assert.eq.instance.exports.fix.fun):
(assert.eq.instance.exports.fix):
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly.imp.ref):
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly):
(GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly.fun):
(GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly.assert.throws):
(GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly):
(assert.throws):
(assert.throws.doTest):
(let.importedFun.of):
(makeAnyfuncIdent.fun):
* wasm/references/validation.js:
(assert.throws):
* wasm/wasm.json:

Source/JavaScriptCore:

Add support for funcref in parameters, globals, and in table.get/set. When converting a JSValue to
a funcref (nee anyfunc), we first make sure it is an exported wasm function or null.

We also add support for Ref.func. Anywhere a Ref.func is used, (statically) we construct a JS wrapper
for it so that we never need to construct JSValues when handling references. This should make threads
easier to implement.

Finally, we add some missing bounds checks for table.get/set.

* wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::tmpForType):
(JSC::Wasm::AirIRGenerator::moveOpForValueType):
(JSC::Wasm::AirIRGenerator::AirIRGenerator):
(JSC::Wasm::AirIRGenerator::addLocal):
(JSC::Wasm::AirIRGenerator::addConstant):
(JSC::Wasm::AirIRGenerator::addRefFunc):
(JSC::Wasm::AirIRGenerator::addTableSet):
(JSC::Wasm::AirIRGenerator::setGlobal):
(JSC::Wasm::AirIRGenerator::addReturn):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::addLocal):
(JSC::Wasm::B3IRGenerator::addTableSet):
(JSC::Wasm::B3IRGenerator::addRefFunc):
(JSC::Wasm::B3IRGenerator::setGlobal):
* wasm/WasmBBQPlan.cpp:
(JSC::Wasm::BBQPlan::compileFunctions):
* wasm/WasmCallingConvention.h:
(JSC::Wasm::CallingConventionAir::marshallArgument const):
(JSC::Wasm::CallingConventionAir::setupCall const):
* wasm/WasmExceptionType.h:
* wasm/WasmFormat.h:
(JSC::Wasm::isValueType):
(JSC::Wasm::isSubtype):
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* wasm/WasmInstance.cpp:
(JSC::Wasm::Instance::Instance):
(JSC::Wasm::Instance::getFunctionWrapper const):
(JSC::Wasm::Instance::setFunctionWrapper):
* wasm/WasmInstance.h:
* wasm/WasmModuleInformation.h:
(JSC::Wasm::ModuleInformation::referencedFunctions const):
(JSC::Wasm::ModuleInformation::addReferencedFunction const):
* wasm/WasmSectionParser.cpp:
(JSC::Wasm::SectionParser::parseGlobal):
(JSC::Wasm::SectionParser::parseInitExpr):
* wasm/WasmValidate.cpp:
(JSC::Wasm::Validate::addTableGet):
(JSC::Wasm::Validate::addTableSet):
(JSC::Wasm::Validate::addRefIsNull):
(JSC::Wasm::Validate::addRefFunc):
(JSC::Wasm::Validate::setLocal):
(JSC::Wasm::Validate::addCall):
(JSC::Wasm::Validate::addCallIndirect):
* wasm/js/JSToWasm.cpp:
(JSC::Wasm::createJSToWasmWrapper):
* wasm/js/JSWebAssemblyHelpers.h:
(JSC::isWebAssemblyHostFunction):
* wasm/js/JSWebAssemblyInstance.cpp:
(JSC::JSWebAssemblyInstance::visitChildren):
* wasm/js/JSWebAssemblyRuntimeError.cpp:
(JSC::createJSWebAssemblyRuntimeError):
* wasm/js/JSWebAssemblyRuntimeError.h:
* wasm/js/WasmToJS.cpp:
(JSC::Wasm::handleBadI64Use):
(JSC::Wasm::wasmToJS):
(JSC::Wasm::emitWasmToJSException):
* wasm/js/WasmToJS.h:
* wasm/js/WebAssemblyFunction.cpp:
(JSC::callWebAssemblyFunction):
(JSC::WebAssemblyFunction::jsCallEntrypointSlow):
* wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::link):
* wasm/wasm.json:

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

29 files changed:
JSTests/ChangeLog
JSTests/wasm/Builder.js
JSTests/wasm/references/anyref_globals.js
JSTests/wasm/references/func_ref.js [new file with mode: 0644]
JSTests/wasm/references/validation.js
JSTests/wasm/wasm.json
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmBBQPlan.cpp
Source/JavaScriptCore/wasm/WasmCallingConvention.h
Source/JavaScriptCore/wasm/WasmExceptionType.h
Source/JavaScriptCore/wasm/WasmFormat.h
Source/JavaScriptCore/wasm/WasmFunctionParser.h
Source/JavaScriptCore/wasm/WasmInstance.cpp
Source/JavaScriptCore/wasm/WasmInstance.h
Source/JavaScriptCore/wasm/WasmModuleInformation.h
Source/JavaScriptCore/wasm/WasmSectionParser.cpp
Source/JavaScriptCore/wasm/WasmValidate.cpp
Source/JavaScriptCore/wasm/js/JSToWasm.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyHelpers.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyRuntimeError.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyRuntimeError.h
Source/JavaScriptCore/wasm/js/WasmToJS.cpp
Source/JavaScriptCore/wasm/js/WasmToJS.h
Source/JavaScriptCore/wasm/js/WebAssemblyFunction.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp
Source/JavaScriptCore/wasm/wasm.json

index 8ade09f..0a79ce8 100644 (file)
@@ -1,3 +1,33 @@
+2019-06-17  Justin Michaud  <justin_michaud@apple.com>
+
+        [WASM-References] Add support for Funcref in parameters and return types
+        https://bugs.webkit.org/show_bug.cgi?id=198157
+
+        Reviewed by Yusuke Suzuki.
+
+        * wasm/Builder.js:
+        (export.default.Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.case.string_appeared_here.this.section):
+        * wasm/references/anyref_globals.js:
+        * wasm/references/func_ref.js: Added.
+        (fullGC.gc.makeExportedFunction):
+        (makeExportedIdent):
+        (makeAnyfuncIdent):
+        (fun):
+        (assert.eq.instance.exports.fix.fun):
+        (assert.eq.instance.exports.fix):
+        (string_appeared_here.End.End.Function.End.Code.End.WebAssembly.imp.ref):
+        (string_appeared_here.End.End.Function.End.Code.End.WebAssembly):
+        (GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly.fun):
+        (GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly.assert.throws):
+        (GetLocal.0.I32Const.0.TableSet.End.End.WebAssembly):
+        (assert.throws):
+        (assert.throws.doTest):
+        (let.importedFun.of):
+        (makeAnyfuncIdent.fun):
+        * wasm/references/validation.js:
+        (assert.throws):
+        * wasm/wasm.json:
+
 2019-06-17  Ross Kirsling  <ross.kirsling@sony.com>
 
         Update test262 tests (2019.06.13)
index 7e7c1b7..65f1f47 100644 (file)
@@ -536,6 +536,10 @@ export default class Builder {
                             s.data.push({ type, op: "get_global", mutability: _normalizeMutability(mutability), initValue });
                             return _errorHandlingProxyFor(globalBuilder);
                         },
+                        RefFunc: (type, initValue, mutability) => {
+                            s.data.push({ type, op: "ref.func", mutability: _normalizeMutability(mutability), initValue });
+                            return _errorHandlingProxyFor(globalBuilder);
+                        },
                         RefNull: (type, mutability) => {
                             s.data.push({ type, op: "ref.null", mutability: _normalizeMutability(mutability) });
                             return _errorHandlingProxyFor(globalBuilder);
index 184457a..2d3ca7b 100644 (file)
@@ -56,7 +56,9 @@ assert.eq($1.exports.get_glob(), null)
 
 const obj = { test: "hi" }
 
-$1.exports.set_glob(obj); assert.eq($1.exports.get_glob(), obj);
+assert.throws(() => $1.exports.expglob2 = null, TypeError, "Attempted to assign to readonly property.")
+
+$1.exports.set_glob(obj); assert.eq($1.exports.get_glob(), obj); assert.eq($1.exports.expglob2, "hi")
 $1.exports.set_glob(5); assert.eq($1.exports.get_glob(), 5)
 $1.exports.set_glob(null); assert.eq($1.exports.get_glob(), null)
 $1.exports.set_glob("hi"); assert.eq($1.exports.get_glob(), "hi")
diff --git a/JSTests/wasm/references/func_ref.js b/JSTests/wasm/references/func_ref.js
new file mode 100644 (file)
index 0000000..35e06e4
--- /dev/null
@@ -0,0 +1,447 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+fullGC()
+gc()
+
+function makeExportedFunction(i) {
+    const builder = (new Builder())
+          .Type().End()
+          .Function().End()
+          .Export()
+              .Function("h")
+          .End()
+          .Code()
+            .Function("h", { params: [], ret: "i32" }, [])
+              .I32Const(i)
+            .End()
+          .End();
+
+    const bin = builder.WebAssembly().get();
+    const module = new WebAssembly.Module(bin);
+    const instance = new WebAssembly.Instance(module);
+
+    return instance.exports.h
+}
+
+function makeExportedIdent() {
+    const builder = (new Builder())
+          .Type().End()
+          .Function().End()
+          .Export()
+              .Function("h")
+          .End()
+          .Code()
+            .Function("h", { params: ["i32"], ret: "i32" }, [])
+              .GetLocal(0)
+            .End()
+          .End();
+
+    const bin = builder.WebAssembly().get();
+    const module = new WebAssembly.Module(bin);
+    const instance = new WebAssembly.Instance(module);
+
+    return instance.exports.h
+}
+
+function makeAnyfuncIdent() {
+    const builder = (new Builder())
+          .Type().End()
+          .Function().End()
+          .Export()
+              .Function("h")
+          .End()
+          .Code()
+            .Function("h", { params: ["anyfunc"], ret: "anyfunc" }, [])
+              .GetLocal(0)
+            .End()
+          .End();
+
+    const bin = builder.WebAssembly().get();
+    const module = new WebAssembly.Module(bin);
+    const instance = new WebAssembly.Instance(module);
+
+    return instance.exports.h
+}
+
+{
+    const myfun = makeExportedFunction(1337);
+    function fun() {
+        return 41;
+    }
+
+    const builder = (new Builder())
+          .Type().End()
+          .Function().End()
+          .Export()
+              .Function("h")
+              .Function("i")
+              .Function("get_h")
+              .Function("fix")
+              .Function("get_not_exported")
+              .Function("local_read")
+          .End()
+          .Code()
+            .Function("h", { params: ["anyfunc"], ret: "anyref" }, ["anyref"])
+              .GetLocal(0)
+              .SetLocal(1)
+              .GetLocal(1)
+            .End()
+
+            .Function("i", { params: ["anyfunc"], ret: "anyfunc" }, ["anyfunc"])
+              .GetLocal(0)
+              .SetLocal(1)
+              .GetLocal(1)
+            .End()
+
+            .Function("get_h", { params: [], ret: "anyfunc" }, ["anyfunc"])
+              .I32Const(0)
+              .RefFunc(0)
+              .SetLocal(0)
+              .If("anyfunc")
+              .Block("anyfunc", (b) =>
+                b.GetLocal(0)
+              )
+              .Else()
+              .Block("anyfunc", (b) =>
+                b.GetLocal(0)
+              )
+              .End()
+            .End()
+
+            .Function("fix", { params: [], ret: "anyfunc" }, [])
+              .RefFunc(3)
+            .End()
+
+            .Function("get_not_exported", { params: [], ret: "anyfunc" }, [])
+              .RefFunc(5)
+            .End()
+
+            .Function("ret_42", { params: [], ret: "i32" }, [])
+              .I32Const(42)
+            .End()
+
+            .Function("local_read", { params: [], ret: "i32" }, ["anyfunc"])
+              .GetLocal(0)
+              .RefIsNull()
+            .End()
+          .End();
+
+    const bin = builder.WebAssembly().get();
+    const module = new WebAssembly.Module(bin);
+    const instance = new WebAssembly.Instance(module);
+    fullGC();
+
+    assert.eq(instance.exports.local_read(), 1)
+    assert.eq(instance.exports.h(null), null)
+
+    assert.throws(() => instance.exports.h(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+    assert.eq(instance.exports.h(myfun), myfun)
+    assert.throws(() => instance.exports.h(5), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+    assert.throws(() => instance.exports.h(undefined), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+    assert.eq(instance.exports.i(null), null)
+    assert.eq(instance.exports.i(myfun), myfun)
+    assert.throws(() => instance.exports.i(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+    assert.throws(() => instance.exports.i(5), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+    assert.throws(() => instance.exports.get_h()(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+    assert.eq(instance.exports.get_h()(null), null)
+    assert.eq(instance.exports.get_h()(myfun), myfun)
+    assert.throws(() => instance.exports.get_h()(5), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+    assert.eq(instance.exports.get_not_exported()(), 42)
+
+    assert.eq(instance.exports.fix()(), instance.exports.fix());
+    assert.eq(instance.exports.fix(), instance.exports.fix);
+}
+
+// Globals
+
+{
+    const myfun = makeExportedFunction(42);
+    function fun() {
+        return 41;
+    }
+
+    const $1 = (() => new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Import()
+           .Global().Anyfunc("imp", "ref", "immutable").End()
+      .End()
+      .Function().End()
+      .Global()
+          .RefNull("anyfunc", "mutable")
+          .RefNull("anyfunc", "immutable")
+          .GetGlobal("anyfunc", 0, "mutable")
+          .RefFunc("anyfunc", 2, "immutable")
+      .End()
+      .Export()
+          .Function("set_glob")
+          .Function("get_glob")
+          .Function("glob_is_null")
+          .Function("set_glob_null")
+          .Function("get_import")
+          .Global("expglob", 2)
+          .Global("expglob2", 0)
+          .Global("exp_glob_is_null", 4)
+      .End()
+      .Code()
+        .Function("set_glob", { params: ["anyfunc"], ret: "void" })
+          .GetLocal(0)
+          .SetGlobal(1)
+        .End()
+
+        .Function("get_glob", { params: [], ret: "anyfunc" })
+            .GetGlobal(1)
+        .End()
+
+        .Function("glob_is_null", { params: [], ret: "i32" })
+            .Call(1)
+            .RefIsNull()
+        .End()
+
+        .Function("set_glob_null", { params: [], ret: "void" })
+            .RefNull()
+            .Call(0)
+        .End()
+
+        .Function("get_import", { params: [], ret: "anyfunc" })
+            .GetGlobal(0)
+        .End()
+      .End().WebAssembly().get()), { imp: { ref: makeExportedFunction(1337) } }))();
+
+    fullGC();
+
+    assert.eq($1.exports.get_import()(), 1337)
+    assert.eq($1.exports.expglob, null)
+    assert.eq($1.exports.expglob2(), 1337)
+    assert.eq($1.exports.exp_glob_is_null, $1.exports.glob_is_null);
+    assert.eq($1.exports.get_glob(), null)
+
+    $1.exports.set_glob(myfun); assert.eq($1.exports.get_glob(), myfun); assert.eq($1.exports.get_glob()(), 42); assert.eq($1.exports.expglob2(), 1337)
+    $1.exports.set_glob(null); assert.eq($1.exports.get_glob(), null)
+    $1.exports.set_glob(myfun); assert.eq($1.exports.get_glob()(), 42);
+
+    assert.throws(() => $1.exports.set_glob(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+    assert.eq($1.exports.glob_is_null(), 0)
+    $1.exports.set_glob_null(); assert.eq($1.exports.get_glob(), null)
+    assert.eq($1.exports.glob_is_null(), 1)
+}
+
+assert.throws(() => new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+  .Type().End()
+  .Import()
+       .Global().Anyfunc("imp", "ref", "immutable").End()
+  .End()
+  .Function().End()
+  .Code().End().WebAssembly().get()), { imp: { ref: function() { return "hi" } } }), Error, "imported global imp:ref must be a wasm exported function or null (evaluating 'new WebAssembly.Instance')");
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+  .Type().End()
+  .Function().End()
+  .Code()
+    .Function("h", { params: ["anyfunc"], ret: "anyref" })
+      .GetLocal(0)
+    .End()
+  .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: control flow returns with unexpected type, in function at index 0 (evaluating 'new WebAssembly.Module')");
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+  .Type().End()
+  .Function().End()
+  .Table()
+    .Table({initial: 1, element: "anyfunc"})
+  .End()
+  .Code()
+    .Function("h", { params: ["i32"], ret: "void" })
+      .GetLocal(0)
+      .I32Const(0)
+      .TableSet()
+    .End()
+  .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: table.set value to type I32 expected Anyfunc, in function at index 0 (evaluating 'new WebAssembly.Module')");
+
+// Tables
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 1, element: "anyfunc"})
+      .End()
+      .Global()
+          .RefNull("anyfunc", "mutable")
+      .End()
+      .Export()
+          .Function("set_glob")
+          .Function("get_glob")
+          .Function("call_glob")
+          .Function("ret_20")
+      .End()
+      .Code()
+        .Function("set_glob", { params: ["anyfunc"], ret: "void" })
+          .GetLocal(0)
+          .SetGlobal(0)
+        .End()
+
+        .Function("get_glob", { params: [], ret: "anyfunc" })
+            .GetGlobal(0)
+        .End()
+
+        .Function("call_glob", { params: ["i32"], ret: "i32" })
+            .I32Const(0)
+            .GetGlobal(0)
+            .TableSet()
+
+            .GetLocal(0)
+            .I32Const(0)
+            .CallIndirect(2,0)
+        .End()
+
+        .Function("ret_20", { params: ["i32"], ret: "i32" })
+            .I32Const(20)
+        .End()
+      .End().WebAssembly().get()));
+
+    const myfun = makeExportedFunction(1337);
+    function fun(i) {
+        return 41;
+    }
+    const ident = makeExportedIdent();
+
+    $1.exports.set_glob($1.exports.ret_20); assert.eq($1.exports.get_glob(), $1.exports.ret_20); assert.eq($1.exports.call_glob(42), 20)
+    $1.exports.set_glob(null); assert.eq($1.exports.get_glob(), null); assert.throws(() => $1.exports.call_glob(42), Error, "call_indirect to a null table entry (evaluating 'func(...args)')")
+    $1.exports.set_glob(ident); assert.eq($1.exports.get_glob(), ident); assert.eq($1.exports.call_glob(42), 42)
+
+    assert.throws(() => $1.exports.set_glob(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+    $1.exports.set_glob(myfun); assert.eq($1.exports.get_glob(), myfun); assert.throws(() => $1.exports.call_glob(42), Error, "call_indirect to a signature that does not match (evaluating 'func(...args)')")
+
+    for (let i=0; i<1000; ++i) {
+        assert.throws(() => $1.exports.set_glob(function() {}), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')");
+    }
+}
+
+// Table set/get
+
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 1, element: "anyfunc"})
+      .End()
+      .Export()
+          .Function("set")
+          .Function("get")
+      .End()
+      .Code()
+        .Function("set", { params: ["anyfunc"], ret: "void" })
+          .I32Const(0)
+          .GetLocal(0)
+          .TableSet()
+        .End()
+
+        .Function("get", { params: [], ret: "anyfunc" })
+            .I32Const(0)
+            .TableGet()
+        .End()
+      .End().WebAssembly().get()));
+
+    function doSet() {
+        const myfun = makeExportedFunction(444);
+        for (let i=0; i<1000; ++i) {
+            $1.exports.set(myfun);
+        }
+        $1.exports.set(myfun); assert.eq($1.exports.get(), myfun); assert.eq($1.exports.get()(), 444);
+    }
+
+    function doTest(j,k, l) {
+        fullGC();
+        let garbage = { val: "hi", val2: 5, arr: [] }
+        for (let i=0; i<100; ++i) garbage.arr += ({ field: i + j + k + l })
+        fullGC();
+
+        for (let i=0; i<100; ++i) {
+            assert.eq($1.exports.get()(), 444);
+            fullGC();
+        }
+    }
+
+
+    doSet()
+    doTest(0,0,0)
+}
+
+// Wasm->JS Calls
+
+for (let importedFun of [function(i) { return i; }, makeAnyfuncIdent()]) {
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Import()
+           .Function("imp", "h", { params: ["anyfunc"], ret: "anyfunc" })
+      .End()
+      .Function().End()
+      .Table()
+            .Table({initial: 1, element: "anyfunc"})
+      .End()
+      .Export()
+          .Function("test1")
+          .Function("test2")
+          .Function("test3")
+          .Function("test4")
+      .End()
+      .Code()
+        .Function("test1", { params: ["anyfunc"], ret: "anyfunc" })
+          .GetLocal(0)
+          .Call(0)
+        .End()
+
+        .Function("test2", { params: [], ret: "anyfunc" })
+          .RefFunc(1)
+          .Call(0)
+        .End()
+
+        .Function("test3", { params: ["anyfunc"], ret: "anyfunc" })
+          .GetLocal(0)
+          .I32Const(0)
+          .RefFunc(0)
+          .TableSet()
+          .I32Const(0)
+          .CallIndirect(0, 0)
+        .End()
+
+        .Function("test4", { params: [], ret: "anyfunc" })
+          .RefFunc(1)
+          .I32Const(0)
+          .RefFunc(0)
+          .TableSet()
+          .I32Const(0)
+          .CallIndirect(0, 0)
+        .End()
+      .End().WebAssembly().get()), { imp: { h: importedFun } });
+
+    const myfun = makeExportedFunction(1337);
+    function fun(i) {
+        return 41;
+    }
+
+    for (let test of [$1.exports.test1, $1.exports.test3]) {
+        assert.eq(test(myfun), myfun)
+        assert.eq(test(myfun)(), 1337)
+        assert.throws(() => test(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+        for (let i=0; i<1000; ++i) {
+            assert.throws(() => test(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+        }
+    }
+
+    for (let test of [$1.exports.test2, $1.exports.test4]) {
+        assert.eq(test(), $1.exports.test1)
+        assert.eq(test()(myfun), myfun)
+        assert.throws(() => test()(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+
+        for (let i=0; i<1000; ++i) {
+            assert.throws(() => test()(fun), Error, "Anyfunc must be an exported wasm function (evaluating 'func(...args)')")
+        }
+    }
+}
index d38c265..8f81636 100644 (file)
@@ -63,7 +63,7 @@ import Builder from '../Builder.js';
     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())')");
+    assert.throws(() => new WebAssembly.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: table.set value to type Anyref expected Anyfunc, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
 }
 
 {
@@ -86,7 +86,7 @@ import Builder from '../Builder.js';
     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.Module(bin.get()), WebAssembly.CompileError, "WebAssembly.Module doesn't validate: control flow returns with unexpected type, in function at index 0 (evaluating 'new WebAssembly.Module(bin.get())')");
 }
 
 {
index 30bbc74..64ca0b5 100644 (file)
         "i64":     { "type": "varint7", "value":  -2, "b3type": "B3::Int64" },
         "f32":     { "type": "varint7", "value":  -3, "b3type": "B3::Float" },
         "f64":     { "type": "varint7", "value":  -4, "b3type": "B3::Double" },
-        "anyfunc": { "type": "varint7", "value": -16, "b3type": "B3::Void" },
+        "anyfunc": { "type": "varint7", "value": -16, "b3type": "B3::Int64" },
         "anyref":  { "type": "varint7", "value": -17, "b3type": "B3::Int64" },
         "func":    { "type": "varint7", "value": -32, "b3type": "B3::Void" },
         "void":    { "type": "varint7", "value": -64, "b3type": "B3::Void" }
     },
-    "value_type": ["i32", "i64", "f32", "f64", "anyref"],
-    "block_type": ["i32", "i64", "f32", "f64", "void", "anyref"],
+    "value_type": ["i32", "i64", "f32", "f64", "anyref", "anyfunc"],
+    "block_type": ["i32", "i64", "f32", "f64", "void", "anyref", "anyfunc"],
     "elem_type": ["anyfunc","anyref"],
     "external_kind": {
         "Function": { "type": "uint8", "value": 0 },
@@ -59,8 +59,9 @@
         "i64.const":           { "category": "special",    "value":  66, "return": ["i64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "varint64"}],                                             "description": "a constant value interpreted as i64" },
         "f64.const":           { "category": "special",    "value":  68, "return": ["f64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "double"}],                                               "description": "a constant value interpreted as f64" },
         "f32.const":           { "category": "special",    "value":  67, "return": ["f32"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "float"}],                                                "description": "a constant value interpreted as f32" },
-        "ref.null":            { "category": "special",    "value": 208, "return": ["anyref"],   "parameter": [],                       "immediate": [],                                                                                           "description": "a constant null reference" },
+        "ref.null":            { "category": "special",    "value": 208, "return": ["anyfunc"],  "parameter": [],                       "immediate": [],                                                                                           "description": "a constant null reference" },
         "ref.is_null":         { "category": "special",    "value": 209, "return": ["i32"],      "parameter": ["anyref"],               "immediate": [],                                                                                           "description": "determine if a reference is null" },
+        "ref.func":            { "category": "special",    "value": 210, "return": ["anyfunc"],  "parameter": [],                       "immediate": [{"name": "function_index",  "type": "varuint32"}],                                           "description": "return a reference to the function at the given index" },
         "get_local":           { "category": "special",    "value":  32, "return": ["any"],      "parameter": [],                       "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "read a local variable or parameter" },
         "set_local":           { "category": "special",    "value":  33, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "write a local variable or parameter" },
         "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" },
index b740324..b56953b 100644 (file)
@@ -1,3 +1,86 @@
+2019-06-17  Justin Michaud  <justin_michaud@apple.com>
+
+        [WASM-References] Add support for Funcref in parameters and return types
+        https://bugs.webkit.org/show_bug.cgi?id=198157
+
+        Reviewed by Yusuke Suzuki.
+
+        Add support for funcref in parameters, globals, and in table.get/set. When converting a JSValue to 
+        a funcref (nee anyfunc), we first make sure it is an exported wasm function or null. 
+
+        We also add support for Ref.func. Anywhere a Ref.func is used, (statically) we construct a JS wrapper
+        for it so that we never need to construct JSValues when handling references. This should make threads
+        easier to implement.
+
+        Finally, we add some missing bounds checks for table.get/set.
+
+        * wasm/WasmAirIRGenerator.cpp:
+        (JSC::Wasm::AirIRGenerator::tmpForType):
+        (JSC::Wasm::AirIRGenerator::moveOpForValueType):
+        (JSC::Wasm::AirIRGenerator::AirIRGenerator):
+        (JSC::Wasm::AirIRGenerator::addLocal):
+        (JSC::Wasm::AirIRGenerator::addConstant):
+        (JSC::Wasm::AirIRGenerator::addRefFunc):
+        (JSC::Wasm::AirIRGenerator::addTableSet):
+        (JSC::Wasm::AirIRGenerator::setGlobal):
+        (JSC::Wasm::AirIRGenerator::addReturn):
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::addLocal):
+        (JSC::Wasm::B3IRGenerator::addTableSet):
+        (JSC::Wasm::B3IRGenerator::addRefFunc):
+        (JSC::Wasm::B3IRGenerator::setGlobal):
+        * wasm/WasmBBQPlan.cpp:
+        (JSC::Wasm::BBQPlan::compileFunctions):
+        * wasm/WasmCallingConvention.h:
+        (JSC::Wasm::CallingConventionAir::marshallArgument const):
+        (JSC::Wasm::CallingConventionAir::setupCall const):
+        * wasm/WasmExceptionType.h:
+        * wasm/WasmFormat.h:
+        (JSC::Wasm::isValueType):
+        (JSC::Wasm::isSubtype):
+        * wasm/WasmFunctionParser.h:
+        (JSC::Wasm::FunctionParser<Context>::parseExpression):
+        (JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
+        * wasm/WasmInstance.cpp:
+        (JSC::Wasm::Instance::Instance):
+        (JSC::Wasm::Instance::getFunctionWrapper const):
+        (JSC::Wasm::Instance::setFunctionWrapper):
+        * wasm/WasmInstance.h:
+        * wasm/WasmModuleInformation.h:
+        (JSC::Wasm::ModuleInformation::referencedFunctions const):
+        (JSC::Wasm::ModuleInformation::addReferencedFunction const):
+        * wasm/WasmSectionParser.cpp:
+        (JSC::Wasm::SectionParser::parseGlobal):
+        (JSC::Wasm::SectionParser::parseInitExpr):
+        * wasm/WasmValidate.cpp:
+        (JSC::Wasm::Validate::addTableGet):
+        (JSC::Wasm::Validate::addTableSet):
+        (JSC::Wasm::Validate::addRefIsNull):
+        (JSC::Wasm::Validate::addRefFunc):
+        (JSC::Wasm::Validate::setLocal):
+        (JSC::Wasm::Validate::addCall):
+        (JSC::Wasm::Validate::addCallIndirect):
+        * wasm/js/JSToWasm.cpp:
+        (JSC::Wasm::createJSToWasmWrapper):
+        * wasm/js/JSWebAssemblyHelpers.h:
+        (JSC::isWebAssemblyHostFunction):
+        * wasm/js/JSWebAssemblyInstance.cpp:
+        (JSC::JSWebAssemblyInstance::visitChildren):
+        * wasm/js/JSWebAssemblyRuntimeError.cpp:
+        (JSC::createJSWebAssemblyRuntimeError):
+        * wasm/js/JSWebAssemblyRuntimeError.h:
+        * wasm/js/WasmToJS.cpp:
+        (JSC::Wasm::handleBadI64Use):
+        (JSC::Wasm::wasmToJS):
+        (JSC::Wasm::emitWasmToJSException):
+        * wasm/js/WasmToJS.h:
+        * wasm/js/WebAssemblyFunction.cpp:
+        (JSC::callWebAssemblyFunction):
+        (JSC::WebAssemblyFunction::jsCallEntrypointSlow):
+        * wasm/js/WebAssemblyModuleRecord.cpp:
+        (JSC::WebAssemblyModuleRecord::link):
+        * wasm/wasm.json:
+
 2019-06-16  Darin Adler  <darin@apple.com>
 
         Rename AtomicString to AtomString
index adb33f5..ab304a5 100644 (file)
@@ -234,10 +234,11 @@ public:
 
     // References
     PartialResult WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addRefFunc(uint32_t index, ExpressionType& result);
 
     // Tables
-    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
-    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& index, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& index, ExpressionType& value);
 
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
@@ -370,6 +371,7 @@ private:
             return g32();
         case Type::I64:
         case Type::Anyref:
+        case Type::Anyfunc:
             return g64();
         case Type::F32:
             return f32();
@@ -554,6 +556,7 @@ private:
             return Move32;
         case Type::I64:
         case Type::Anyref:
+        case Type::Anyfunc:
             return Move;
         case Type::F32:
             return MoveFloat;
@@ -799,6 +802,7 @@ AirIRGenerator::AirIRGenerator(const ModuleInformation& info, B3::Procedure& pro
             break;
         case Type::I64:
         case Type::Anyref:
+        case Type::Anyfunc:
             append(Move, arg, m_locals[i]);
             break;
         case Type::F32:
@@ -884,6 +888,7 @@ auto AirIRGenerator::addLocal(Type type, uint32_t count) -> PartialResult
         m_locals.uncheckedAppend(local);
         switch (type) {
         case Type::Anyref:
+        case Type::Anyfunc:
             append(Move, Arg::imm(JSValue::encode(jsNull())), local);
             break;
         case Type::I32:
@@ -918,6 +923,7 @@ auto AirIRGenerator::addConstant(BasicBlock* block, Type type, uint64_t value) -
     case Type::I32:
     case Type::I64:
     case Type::Anyref:
+    case Type::Anyfunc:
         append(block, Move, Arg::bigImm(value), result);
         break;
     case Type::F32:
@@ -953,36 +959,47 @@ auto AirIRGenerator::addRefIsNull(ExpressionType& value, ExpressionType& result)
     return { };
 }
 
-auto AirIRGenerator::addTableGet(ExpressionType& idx, ExpressionType& result) -> PartialResult
+auto AirIRGenerator::addRefFunc(uint32_t index, 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);
+    result = tmpForType(Type::Anyfunc);
+    emitCCall(&doWasmRefFunc, result, instanceValue(), addConstant(Type::I32, index));
 
-    uint64_t (*doGet)(Instance*, int32_t) = [] (Instance* instance, int32_t idx) -> uint64_t {
-        return JSValue::encode(instance->table()->get(idx));
-    };
+    return { };
+}
 
-    emitCCall(doGet, result, instanceValue(), idx);
+auto AirIRGenerator::addTableGet(ExpressionType& index, ExpressionType& result) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    ASSERT(index.tmp());
+    ASSERT(index.type() == Type::I32);
+    result = tmpForType(Type::Anyref);
+
+    emitCCall(&getWasmTableElement, result, instanceValue(), index);
+    emitCheck([&] {
+        return Inst(BranchTest32, nullptr, Arg::resCond(MacroAssembler::Zero), result, result);
+    }, [=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+        this->emitThrowException(jit, ExceptionType::OutOfBoundsTableAccess);
+    });
 
     return { };
 }
 
-auto AirIRGenerator::addTableSet(ExpressionType& idx, ExpressionType& value) -> PartialResult
+auto AirIRGenerator::addTableSet(ExpressionType& index, 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(index.tmp());
+    ASSERT(index.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));
-    };
+    auto shouldThrow = g32();
+    emitCCall(&setWasmTableElement, shouldThrow, instanceValue(), index, value);
 
-    emitCCall(doSet, TypedTmp(), instanceValue(), idx, value);
+    emitCheck([&] {
+        return Inst(BranchTest32, nullptr, Arg::resCond(MacroAssembler::Zero), shouldThrow, shouldThrow);
+    }, [=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+        this->emitThrowException(jit, ExceptionType::OutOfBoundsTableAccess);
+    });
 
     return { };
 }
@@ -1103,7 +1120,7 @@ auto AirIRGenerator::setGlobal(uint32_t index, ExpressionType value) -> PartialR
         append(moveOpForValueType(type), value, Arg::addr(temp));
     }
 
-    if (type == Anyref)
+    if (isSubtype(type, Anyref))
         emitWriteBarrierForJSWrapper();
 
     return { };
@@ -1623,6 +1640,7 @@ auto AirIRGenerator::addReturn(const ControlData& data, const ExpressionList& re
             break;
         case Type::I64:
         case Type::Anyref:
+        case Type::Anyfunc:
             append(Move, returnValues[0], returnValueGPR);
             append(Ret64, returnValueGPR);
             break;
index 55bd2d8..7760974 100644 (file)
@@ -187,10 +187,11 @@ public:
 
     // References
     PartialResult WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addRefFunc(uint32_t index, ExpressionType& result);
 
     // Tables
-    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
-    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+    PartialResult WARN_UNUSED_RETURN addTableGet(ExpressionType& index, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableSet(ExpressionType& index, ExpressionType& value);
 
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
@@ -538,7 +539,7 @@ auto B3IRGenerator::addLocal(Type type, uint32_t count) -> PartialResult
     for (uint32_t i = 0; i < count; ++i) {
         Variable* local = m_proc.addVariable(toB3Type(type));
         m_locals.uncheckedAppend(local);
-        auto val = type == Anyref ? JSValue::encode(jsNull()) : 0;
+        auto val = isSubtype(type, Anyref) ? JSValue::encode(jsNull()) : 0;
         m_currentBlock->appendNew<VariableValue>(m_proc, Set, Origin(), local, constant(toB3Type(type), val, Origin()));
     }
     return { };
@@ -565,32 +566,51 @@ auto B3IRGenerator::addRefIsNull(ExpressionType& value, ExpressionType& result)
     return { };
 }
 
-auto B3IRGenerator::addTableGet(ExpressionType& idx, ExpressionType& result) -> PartialResult
+auto B3IRGenerator::addTableGet(ExpressionType& index, 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);
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(&getWasmTableElement, B3CCallPtrTag)),
+        instanceValue(), index);
+
+    {
+        CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, origin(),
+            m_currentBlock->appendNew<Value>(m_proc, Equal, origin(), result, m_currentBlock->appendNew<Const64Value>(m_proc, origin(), 0)));
+
+        check->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+            this->emitExceptionCheck(jit, ExceptionType::OutOfBoundsTableAccess);
+        });
+    }
 
     return { };
 }
 
-auto B3IRGenerator::addTableSet(ExpressionType& idx, ExpressionType& value) -> PartialResult
+auto B3IRGenerator::addTableSet(ExpressionType& index, ExpressionType& value) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    auto shouldThrow = m_currentBlock->appendNew<CCallValue>(m_proc, B3::Int32, origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(&setWasmTableElement, B3CCallPtrTag)),
+        instanceValue(), index, value);
+
+    {
+        CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, origin(),
+            m_currentBlock->appendNew<Value>(m_proc, Equal, origin(), shouldThrow, m_currentBlock->appendNew<Const32Value>(m_proc, origin(), 0)));
+
+        check->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+            this->emitExceptionCheck(jit, ExceptionType::OutOfBoundsTableAccess);
+        });
+    }
+
+    return { };
+}
+
+auto B3IRGenerator::addRefFunc(uint32_t index, ExpressionType& result) -> 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);
+    result = m_currentBlock->appendNew<CCallValue>(m_proc, B3::Int64, origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(&doWasmRefFunc, B3CCallPtrTag)),
+        instanceValue(), addConstant(Type::I32, index));
 
     return { };
 }
@@ -679,7 +699,7 @@ auto B3IRGenerator::setGlobal(uint32_t index, ExpressionType value) -> PartialRe
     Value* globalsArray = m_currentBlock->appendNew<MemoryValue>(m_proc, Load, pointerType(), origin(), instanceValue(), safeCast<int32_t>(Instance::offsetOfGlobals()));
     m_currentBlock->appendNew<MemoryValue>(m_proc, Store, origin(), value, globalsArray, safeCast<int32_t>(index * sizeof(Register)));
 
-    if (m_info.globals[index].type == Anyref)
+    if (isSubtype(m_info.globals[index].type, Anyref))
         emitWriteBarrierForJSWrapper();
 
     return { };
index 0b38f9d..65cde66 100644 (file)
@@ -286,7 +286,7 @@ void BBQPlan::compileFunctions(CompilationEffort effort)
 
         m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
 
-        if (m_exportedFunctionIndices.contains(functionIndex)) {
+        if (m_exportedFunctionIndices.contains(functionIndex) || m_moduleInformation->referencedFunctions().contains(functionIndex)) {
             auto locker = holdLock(m_lock);
             auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(m_compilationContexts[functionIndex], signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex));
             ASSERT_UNUSED(result, result.isNewEntry);
index 10eb8cd..f7be38c 100644 (file)
@@ -236,6 +236,7 @@ private:
         case Type::I32:
         case Type::I64:
         case Type::Anyref:
+        case Wasm::Anyfunc:
             marshallArgumentImpl(m_gprArgs, gpArgumentCount, stackOffset, regFunc, stackFunc);
             break;
         case Type::F32:
@@ -301,6 +302,7 @@ public:
         case Type::I32:
         case Type::I64:
         case Type::Anyref:
+        case Wasm::Anyfunc:
             patchpoint->resultConstraint = B3::ValueRep::reg(GPRInfo::returnValueGPR);
             break;
         default:
index d446c0e..932861c 100644 (file)
@@ -33,6 +33,7 @@ namespace Wasm {
 
 #define FOR_EACH_EXCEPTION(macro) \
     macro(OutOfBoundsMemoryAccess,  "Out of bounds memory access") \
+    macro(OutOfBoundsTableAccess,  "Out of bounds table access") \
     macro(OutOfBoundsCallIndirect, "Out of bounds call_indirect") \
     macro(NullTableEntry,  "call_indirect to a null table entry") \
     macro(BadSignature, "call_indirect to a signature that does not match") \
@@ -42,7 +43,8 @@ namespace Wasm {
     macro(IntegerOverflow, "Integer overflow") \
     macro(StackOverflow, "Stack overflow") \
     macro(I64ArgumentType, "WebAssembly function with an i64 argument can't be called from JavaScript") \
-    macro(I64ReturnType, "WebAssembly function that returns i64 can't be called from JavaScript")
+    macro(I64ReturnType, "WebAssembly function that returns i64 can't be called from JavaScript") \
+    macro(FuncrefNotWasm, "Anyfunc must be an exported wasm function")
 
 enum class ExceptionType : uint32_t {
 #define MAKE_ENUM(enumName, error) enumName,
index 6292250..7c03441 100644 (file)
@@ -68,12 +68,20 @@ inline bool isValueType(Type type)
     case F64:
         return true;
     case Anyref:
+    case Anyfunc:
         return Options::useWebAssemblyReferences();
     default:
         break;
     }
     return false;
 }
+
+inline bool isSubtype(Type sub, Type parent)
+{
+    if (sub == parent)
+        return true;
+    return sub == Anyfunc && parent == Anyref;
+}
     
 enum class ExternalKind : uint8_t {
     // FIXME auto-generate this. https://bugs.webkit.org/show_bug.cgi?id=165231
@@ -138,6 +146,7 @@ struct Global {
     enum InitializationType {
         IsImport,
         FromGlobalImport,
+        FromRefFunc,
         FromExpression
     };
 
@@ -234,6 +243,7 @@ public:
     uint32_t initial() const { return m_initial; }
     Optional<uint32_t> maximum() const { return m_maximum; }
     TableElementType type() const { return m_type; }
+    Wasm::Type wasmType() const { return m_type == TableElementType::Funcref ? Type::Anyfunc : Type::Anyref; }
 
 private:
     uint32_t m_initial;
index dfae8f2..59eee26 100644 (file)
@@ -283,25 +283,25 @@ auto FunctionParser<Context>::parseExpression() -> PartialResult
 
     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));
+        ExpressionType result, index;
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(index, "table.get");
+        WASM_TRY_ADD_TO_CONTEXT(addTableGet(index, result));
         m_expressionStack.append(result);
         return { };
     }
 
     case TableSet: {
         WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
-        ExpressionType val, idx;
+        ExpressionType val, index;
         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));
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(index, "table.set");
+        WASM_TRY_ADD_TO_CONTEXT(addTableSet(index, val));
         return { };
     }
 
     case RefNull: {
         WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
-        m_expressionStack.append(m_context.addConstant(Anyref, JSValue::encode(jsNull())));
+        m_expressionStack.append(m_context.addConstant(Anyfunc, JSValue::encode(jsNull())));
         return { };
     }
 
@@ -314,6 +314,17 @@ auto FunctionParser<Context>::parseExpression() -> PartialResult
         return { };
     }
 
+    case RefFunc: {
+        uint32_t index;
+        ExpressionType result;
+        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get index for ref.func");
+
+        WASM_TRY_ADD_TO_CONTEXT(addRefFunc(index, result));
+        m_expressionStack.append(result);
+        return { };
+    }
+
     case GetLocal: {
         uint32_t index;
         ExpressionType result;
@@ -681,6 +692,13 @@ auto FunctionParser<Context>::parseUnreachableExpression() -> PartialResult
         return { };
     }
 
+    case RefFunc: {
+        uint32_t unused;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get immediate for ", m_currentOpcode, " in unreachable context");
+        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
+        return { };
+    }
+
     case GrowMemory:
     case CurrentMemory: {
         uint8_t reserved;
index 3705996..52bf0f2 100644 (file)
@@ -29,6 +29,7 @@
 #if ENABLE(WEBASSEMBLY)
 
 #include "JSCInlines.h"
+#include "JSWebAssemblyHelpers.h"
 #include "JSWebAssemblyInstance.h"
 #include "Register.h"
 #include "WasmModuleInformation.h"
@@ -57,7 +58,7 @@ Instance::Instance(Context* context, Ref<Module>&& module, EntryFrame** pointerT
         new (importFunctionInfo(i)) ImportFunctionInfo();
     memset(static_cast<void*>(m_globals.get()), 0, globalMemoryByteSize(m_module.get()));
     for (unsigned i = 0; i < m_module->moduleInformation().globals.size(); ++i) {
-        if (m_module.get().moduleInformation().globals[i].type == Anyref)
+        if (isSubtype(m_module.get().moduleInformation().globals[i].type, Anyref))
             m_globalsToMark.set(i);
     }
 }
@@ -80,6 +81,75 @@ void Instance::setGlobal(unsigned i, JSValue value)
     m_globals.get()[i].anyref.set(*owner<JSWebAssemblyInstance>()->vm(), owner<JSWebAssemblyInstance>(), value);
 }
 
+JSValue Instance::getFunctionWrapper(unsigned i) const
+{
+    JSValue value = m_functionWrappers.get(i).get();
+    if (value.isEmpty())
+        return jsNull();
+    return value;
+}
+
+void Instance::setFunctionWrapper(unsigned i, JSValue value)
+{
+    ASSERT(m_owner);
+    ASSERT(value.isFunction(*owner<JSWebAssemblyInstance>()->vm()));
+    ASSERT(!m_functionWrappers.contains(i));
+    auto locker = holdLock(owner<JSWebAssemblyInstance>()->cellLock());
+    m_functionWrappers.set(i, WriteBarrier<Unknown>(*owner<JSWebAssemblyInstance>()->vm(), owner<JSWebAssemblyInstance>(), value));
+    ASSERT(getFunctionWrapper(i) == value);
+}
+
+EncodedJSValue getWasmTableElement(Instance* instance, int32_t signedIndex)
+{
+    if (signedIndex < 0)
+        return 0;
+
+    uint32_t index = signedIndex;
+    if (index >= instance->table()->length())
+        return 0;
+
+    return JSValue::encode(instance->table()->get(index));
+}
+
+bool setWasmTableElement(Instance* instance, int32_t signedIndex, EncodedJSValue encValue)
+{
+    if (signedIndex < 0)
+        return false;
+
+    uint32_t index = signedIndex;
+    if (index >= instance->table()->length())
+        return false;
+
+    JSValue value = JSValue::decode(encValue);
+    if (instance->table()->type() == Wasm::TableElementType::Anyref)
+        instance->table()->set(index, value);
+    else if (instance->table()->type() == Wasm::TableElementType::Funcref) {
+        WebAssemblyFunction* wasmFunction;
+        WebAssemblyWrapperFunction* wasmWrapperFunction;
+
+        if (isWebAssemblyHostFunction(*instance->owner<JSObject>()->vm(), value, wasmFunction, wasmWrapperFunction)) {
+            ASSERT(!!wasmFunction || !!wasmWrapperFunction);
+            if (wasmFunction)
+                instance->table()->asFuncrefTable()->setFunction(index, jsCast<JSObject*>(value), wasmFunction->importableFunction(), &wasmFunction->instance()->instance());
+            else
+                instance->table()->asFuncrefTable()->setFunction(index, jsCast<JSObject*>(value), wasmWrapperFunction->importableFunction(), &wasmWrapperFunction->instance()->instance());
+        } else if (value.isNull())
+            instance->table()->clear(index);
+        else
+            ASSERT_NOT_REACHED();
+    } else
+        ASSERT_NOT_REACHED();
+
+    return true;
+}
+
+EncodedJSValue doWasmRefFunc(Instance* instance, uint32_t index)
+{
+    JSValue value = instance->getFunctionWrapper(index);
+    ASSERT(value.isFunction(*instance->owner<JSObject>()->vm()));
+    return JSValue::encode(value);
+}
+
 } } // namespace JSC::Wasm
 
 #endif // ENABLE(WEBASSEMBLY)
index 5d1fcf3..7a23e56 100644 (file)
 namespace JSC { namespace Wasm {
 
 struct Context;
+class Instance;
+
+EncodedJSValue getWasmTableElement(Instance*, int32_t);
+bool setWasmTableElement(Instance*, int32_t, EncodedJSValue encValue);
+EncodedJSValue doWasmRefFunc(Instance*, uint32_t);
 
 class Instance : public ThreadSafeRefCounted<Instance>, public CanMakeWeakPtr<Instance> {
 public:
     using StoreTopCallFrameCallback = WTF::Function<void(void*)>;
+    using FunctionWrapperMap = HashMap<uint32_t, WriteBarrier<Unknown>, IntHash<uint32_t>, WTF::UnsignedWithZeroKeyHashTraits<uint32_t>>;
 
     static Ref<Instance> create(Context*, Ref<Module>&&, EntryFrame** pointerToTopEntryFrame, void** pointerToActualStackLimit, StoreTopCallFrameCallback&&);
 
@@ -91,6 +97,9 @@ public:
     void setGlobal(unsigned i, int64_t bits) { m_globals.get()[i].primitive = bits; }
     void setGlobal(unsigned, JSValue);
     const BitVector& globalsToMark() { return m_globalsToMark; }
+    JSValue getFunctionWrapper(unsigned) const;
+    typename FunctionWrapperMap::ValuesConstIteratorRange functionWrappers() const { return m_functionWrappers.values(); }
+    void setFunctionWrapper(unsigned, JSValue);
 
     static ptrdiff_t offsetOfMemory() { return OBJECT_OFFSETOF(Instance, m_memory); }
     static ptrdiff_t offsetOfGlobals() { return OBJECT_OFFSETOF(Instance, m_globals); }
@@ -159,6 +168,7 @@ private:
         uint64_t primitive;
     };
     MallocPtr<GlobalValue> m_globals;
+    FunctionWrapperMap m_functionWrappers;
     BitVector m_globalsToMark;
     EntryFrame** m_pointerToTopEntryFrame { nullptr };
     void** m_pointerToActualStackLimit { nullptr };
index ad5eb95..88f66cb 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "WasmFormat.h"
 
+#include <wtf/BitVector.h>
 #include <wtf/Optional.h>
 
 namespace JSC { namespace Wasm {
@@ -66,6 +67,9 @@ struct ModuleInformation : public ThreadSafeRefCounted<ModuleInformation> {
     uint32_t memoryCount() const { return memory ? 1 : 0; }
     uint32_t tableCount() const { return tableInformation ? 1 : 0; }
 
+    const BitVector& referencedFunctions() const { return m_referencedFunctions; }
+    void addReferencedFunction(unsigned index) const { m_referencedFunctions.set(index); }
+
     Vector<Import> imports;
     Vector<SignatureIndex> importFunctionSignatureIndices;
     Vector<SignatureIndex> internalFunctionSignatureIndices;
@@ -84,6 +88,8 @@ struct ModuleInformation : public ThreadSafeRefCounted<ModuleInformation> {
     unsigned firstInternalGlobal { 0 };
     Vector<CustomSection> customSections;
     Ref<NameSection> nameSection;
+    
+    mutable BitVector m_referencedFunctions;
 };
 
     
index 30b6f3c..254e826 100644 (file)
@@ -291,9 +291,11 @@ auto SectionParser::parseGlobal() -> PartialResult
         WASM_FAIL_IF_HELPER_FAILS(parseInitExpr(initOpcode, global.initialBitsOrImportNumber, typeForInitOpcode));
         if (initOpcode == GetGlobal)
             global.initializationType = Global::FromGlobalImport;
+        else if (initOpcode == RefFunc)
+            global.initializationType = Global::FromRefFunc;
         else
             global.initializationType = Global::FromExpression;
-        WASM_PARSER_FAIL_IF(typeForInitOpcode != global.type, "Global init_expr opcode of type ", typeForInitOpcode, " doesn't match global's type ", global.type);
+        WASM_PARSER_FAIL_IF(!isSubtype(typeForInitOpcode, global.type), "Global init_expr opcode of type ", typeForInitOpcode, " doesn't match global's type ", global.type);
 
         m_info->globals.uncheckedAppend(WTFMove(global));
     }
@@ -479,11 +481,21 @@ auto SectionParser::parseInitExpr(uint8_t& opcode, uint64_t& bitsOrImportNumber,
     }
 
     case RefNull: {
-        resultType = Anyref;
+        resultType = Anyfunc;
         bitsOrImportNumber = JSValue::encode(jsNull());
         break;
     }
 
+    case RefFunc: {
+        uint32_t index;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get ref.func index");
+        WASM_PARSER_FAIL_IF(index >= m_info->functions.size(), "ref.func index", index, " exceeds the number of functions ", m_info->functions.size());
+
+        resultType = Anyfunc;
+        bitsOrImportNumber = index;
+        break;
+    }
+
     default:
         WASM_PARSER_FAIL_IF(true, "unknown init_expr opcode ", opcode);
     }
index a4b78e4..7f1ac61 100644 (file)
@@ -103,10 +103,11 @@ public:
 
     // References
     Result WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
+    Result WARN_UNUSED_RETURN addRefFunc(uint32_t index, ExpressionType& result);
 
     // Tables
-    Result WARN_UNUSED_RETURN addTableGet(ExpressionType& idx, ExpressionType& result);
-    Result WARN_UNUSED_RETURN addTableSet(ExpressionType& idx, ExpressionType& value);
+    Result WARN_UNUSED_RETURN addTableGet(ExpressionType& index, ExpressionType& result);
+    Result WARN_UNUSED_RETURN addTableSet(ExpressionType& index, ExpressionType& value);
 
     // Locals
     Result WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
@@ -177,20 +178,21 @@ auto Validate::addArguments(const Signature& signature) -> Result
     return { };
 }
 
-auto Validate::addTableGet(ExpressionType& idx, ExpressionType& result) -> Result
+auto Validate::addTableGet(ExpressionType& index, 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);
+    result = m_module.tableInformation.wasmType();
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != index, "table.get index to type ", index, " expected ", Type::I32);
 
     return { };
 }
 
-auto Validate::addTableSet(ExpressionType& idx, ExpressionType& value) -> Result
+auto Validate::addTableSet(ExpressionType& index, 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);
+    auto type = m_module.tableInformation.wasmType();
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != index, "table.set index to type ", index, " expected ", Type::I32);
+    WASM_VALIDATOR_FAIL_IF(!isSubtype(value, type), "table.set value to type ", value, " expected ", type);
+    WASM_VALIDATOR_FAIL_IF(m_module.tableInformation.type() != TableElementType::Anyref
+        && m_module.tableInformation.type() != TableElementType::Funcref, "table.set expects the table to have type anyref or anyfunc");
 
     return { };
 }
@@ -198,7 +200,16 @@ auto Validate::addTableSet(ExpressionType& idx, ExpressionType& value) -> Result
 auto Validate::addRefIsNull(ExpressionType& value, ExpressionType& result) -> Result
 {
     result = Type::I32;
-    WASM_VALIDATOR_FAIL_IF(Type::Anyref != value, "ref.is_null to type ", value, " expected ", Type::Anyref);
+    WASM_VALIDATOR_FAIL_IF(!isSubtype(value, Type::Anyref), "ref.is_null to type ", value, " expected ", Type::Anyref);
+
+    return { };
+}
+
+auto Validate::addRefFunc(uint32_t index, ExpressionType& result) -> Result
+{
+    result = Type::Anyfunc;
+    WASM_VALIDATOR_FAIL_IF(index >= m_module.functionIndexSpaceSize(), "ref.func index ", index, " is too large, max is ", m_module.functionIndexSpaceSize());
+    m_module.addReferencedFunction(index);
 
     return { };
 }
@@ -224,7 +235,7 @@ auto Validate::setLocal(uint32_t index, ExpressionType value) -> Result
 {
     ExpressionType localType;
     WASM_FAIL_IF_HELPER_FAILS(getLocal(index, localType));
-    WASM_VALIDATOR_FAIL_IF(localType != value, "set_local to type ", value, " expected ", localType);
+    WASM_VALIDATOR_FAIL_IF(!isSubtype(value, localType), "set_local to type ", value, " expected ", localType);
     return { };
 }
 
@@ -362,7 +373,7 @@ auto Validate::addCall(unsigned, const Signature& signature, const Vector<Expres
     WASM_VALIDATOR_FAIL_IF(signature.argumentCount() != args.size(), "arity mismatch in call, got ", args.size(), " arguments, expected ", signature.argumentCount());
 
     for (unsigned i = 0; i < args.size(); ++i)
-        WASM_VALIDATOR_FAIL_IF(args[i] != signature.argument(i), "argument type mismatch in call, got ", args[i], ", expected ", signature.argument(i));
+        WASM_VALIDATOR_FAIL_IF(!isSubtype(args[i], signature.argument(i)), "argument type mismatch in call, got ", args[i], ", expected ", signature.argument(i));
 
     result = signature.returnType();
     return { };
@@ -375,7 +386,7 @@ auto Validate::addCallIndirect(const Signature& signature, const Vector<Expressi
     WASM_VALIDATOR_FAIL_IF(argumentCount != args.size() - 1, "arity mismatch in call_indirect, got ", args.size() - 1, " arguments, expected ", argumentCount);
 
     for (unsigned i = 0; i < argumentCount; ++i)
-        WASM_VALIDATOR_FAIL_IF(args[i] != signature.argument(i), "argument type mismatch in call_indirect, got ", args[i], ", expected ", signature.argument(i));
+        WASM_VALIDATOR_FAIL_IF(!isSubtype(args[i], signature.argument(i)), "argument type mismatch in call_indirect, got ", args[i], ", expected ", signature.argument(i));
 
     WASM_VALIDATOR_FAIL_IF(args.last() != I32, "non-i32 call_indirect index ", args.last());
 
index 26313e8..6852957 100644 (file)
@@ -31,6 +31,7 @@
 #include "CCallHelpers.h"
 #include "DisallowMacroScratchRegisterUsage.h"
 #include "JSCInlines.h"
+#include "JSWebAssemblyHelpers.h"
 #include "JSWebAssemblyInstance.h"
 #include "JSWebAssemblyRuntimeError.h"
 #include "MaxFrameExtentForSlowPathCall.h"
@@ -83,6 +84,7 @@ std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& comp
             FALLTHROUGH;
         case Wasm::I32:
         case Wasm::Anyref:
+        case Wasm::Anyfunc:
             if (numGPRs >= wasmCallingConvention().m_gprArgs.size())
                 totalFrameSize += sizeof(void*);
             ++numGPRs;
@@ -122,20 +124,7 @@ std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& comp
             jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR2, JSWebAssemblyInstance::offsetOfInstance()), GPRInfo::argumentGPR2);
         }
 
-        jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR2, Instance::offsetOfPointerToTopEntryFrame()), GPRInfo::argumentGPR0);
-        jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR0), GPRInfo::argumentGPR0);
-        jit.copyCalleeSavesToEntryFrameCalleeSavesBuffer(GPRInfo::argumentGPR0);
-        jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
-        jit.move(CCallHelpers::TrustedImm32(static_cast<int32_t>(argumentsIncludeI64 ? ExceptionType::I64ArgumentType : ExceptionType::I64ReturnType)), GPRInfo::argumentGPR1);
-
-        CCallHelpers::Call call = jit.call(OperationPtrTag);
-
-        jit.jump(GPRInfo::returnValueGPR, ExceptionHandlerPtrTag);
-        jit.breakpoint(); // We should not reach this.
-
-        jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
-            linkBuffer.link(call, FunctionPtr<OperationPtrTag>(wasmToJSException));
-        });
+        emitThrowWasmToJSException(jit, GPRInfo::argumentGPR2, argumentsIncludeI64 ? ExceptionType::I64ArgumentType : ExceptionType::I64ReturnType);
         return result;
     }
 
@@ -165,6 +154,7 @@ std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& comp
             switch (signature.argument(i)) {
             case Wasm::I32:
             case Wasm::I64:
+            case Wasm::Anyfunc:
             case Wasm::Anyref:
                 if (numGPRs >= wasmCallingConvention().m_gprArgs.size()) {
                     if (signature.argument(i) == Wasm::I32) {
@@ -250,7 +240,7 @@ std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& comp
         jit.moveTrustedValue(jsUndefined(), JSValueRegs { GPRInfo::returnValueGPR });
         break;
     case Wasm::Anyref:
-        // FIXME: We need to box wasm Funcrefs once they are supported here.
+    case Wasm::Anyfunc:
         break;
     case Wasm::I32:
         jit.zeroExtend32ToPtr(GPRInfo::returnValueGPR, GPRInfo::returnValueGPR);
@@ -268,7 +258,6 @@ std::unique_ptr<InternalFunction> createJSToWasmWrapper(CompilationContext& comp
     }
     case Wasm::I64:
     case Wasm::Func:
-    case Wasm::Anyfunc:
         jit.breakpoint();
         break;
     default:
index ea292a2..13d0a09 100644 (file)
@@ -122,7 +122,7 @@ ALWAYS_INLINE bool isWebAssemblyHostFunction(VM& vm, JSValue value, WebAssemblyF
 }
 
 
-ALWAYS_INLINE bool isWebAssemblyHostFunction(VM& vm, JSObject* object)
+ALWAYS_INLINE bool isWebAssemblyHostFunction(VM& vm, JSValue object)
 {
     WebAssemblyFunction* unused;
     WebAssemblyWrapperFunction* unused2;
index 666bf47..323427f 100644 (file)
@@ -91,11 +91,12 @@ void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor)
     for (unsigned i = 0; i < thisObject->instance().numImportFunctions(); ++i)
         visitor.append(*thisObject->instance().importFunction<WriteBarrier<JSObject>>(i)); // This also keeps the functions' JSWebAssemblyInstance alive.
 
-    for (size_t i : thisObject->instance().globalsToMark()) {
-        // FIXME: We need to box wasm Funcrefs once they are supported here.
-        // <https://bugs.webkit.org/show_bug.cgi?id=198157>
+    for (size_t i : thisObject->instance().globalsToMark())
         visitor.appendUnbarriered(JSValue::decode(thisObject->instance().loadI64Global(i)));
-    }
+
+    auto locker = holdLock(cell->cellLock());
+    for (auto& wrapper : thisObject->instance().functionWrappers())
+        visitor.appendUnbarriered(wrapper.get());
 }
 
 void JSWebAssemblyInstance::finalizeCreation(VM& vm, ExecState* exec, Ref<Wasm::CodeBlock>&& wasmCodeBlock, JSObject* importObject, Wasm::CreationMode creationMode)
index 963c282..8ab2f90 100644 (file)
@@ -48,6 +48,12 @@ JSWebAssemblyRuntimeError::JSWebAssemblyRuntimeError(VM& vm, Structure* structur
 
 const ClassInfo JSWebAssemblyRuntimeError::s_info = { "WebAssembly.RuntimeError", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebAssemblyRuntimeError) };
 
+JSObject* createJSWebAssemblyRuntimeError(ExecState* exec, VM& vm, const String& message)
+{
+    ASSERT(!message.isEmpty());
+    JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+    return JSWebAssemblyRuntimeError::create(exec, vm, globalObject->webAssemblyRuntimeErrorStructure(), message);
+}
     
 } // namespace JSC
 
index 68d3ad1..861f472 100644 (file)
@@ -43,6 +43,8 @@ protected:
     JSWebAssemblyRuntimeError(VM&, Structure*);
 };
 
+JSObject* createJSWebAssemblyRuntimeError(ExecState*, VM&, const String&);
+
 } // namespace JSC
 
 #endif // ENABLE(WEBASSEMBLY)
index 5bff9cc..8e718ea 100644 (file)
@@ -32,6 +32,7 @@
 #include "FrameTracers.h"
 #include "JITExceptions.h"
 #include "JSCInlines.h"
+#include "JSWebAssemblyHelpers.h"
 #include "JSWebAssemblyInstance.h"
 #include "JSWebAssemblyRuntimeError.h"
 #include "LinkBuffer.h"
@@ -65,7 +66,6 @@ static Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> handleBa
         switch (argType) {
         case Void:
         case Func:
-        case Anyfunc:
             RELEASE_ASSERT_NOT_REACHED();
 
         case I64: {
@@ -162,10 +162,10 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
             switch (argType) {
             case Void:
             case Func:
-            case Anyfunc:
             case I64:
                 RELEASE_ASSERT_NOT_REACHED();
             case Anyref:
+            case Anyfunc:
             case I32: {
                 GPRReg gprReg;
                 if (marshalledGPRs < wasmCC.m_gprArgs.size())
@@ -237,14 +237,17 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
                     switch (argType) {
                     case Void:
                     case Func:
-                    case Anyfunc:
                     case I64:
                         RELEASE_ASSERT_NOT_REACHED();
                     case I32:
                         arg = jsNumber(static_cast<int32_t>(buffer[argNum]));
                         break;
+                    case Anyfunc: {
+                        arg = JSValue::decode(buffer[argNum]);
+                        ASSERT(isWebAssemblyHostFunction(*vm, arg) || arg.isNull());
+                        break;
+                    }
                     case Anyref:
-                        // FIXME: We need to box wasm Funcrefs once they are supported here.
                         arg = JSValue::decode(buffer[argNum]);
                         break;
                     case F32:
@@ -268,7 +271,6 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
                 uint64_t realResult;
                 switch (signature.returnType()) {
                 case Func:
-                case Anyfunc:
                 case I64:
                     RELEASE_ASSERT_NOT_REACHED();
                     break;
@@ -278,6 +280,11 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
                     realResult = static_cast<uint64_t>(static_cast<uint32_t>(result.toInt32(exec)));
                     break;
                 }
+                case Anyfunc: {
+                    realResult = JSValue::encode(result);
+                    ASSERT(result.isFunction(*vm) || result.isNull());
+                    break;
+                }
                 case Anyref: {
                     realResult = JSValue::encode(result);
                     break;
@@ -371,10 +378,10 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
             switch (argType) {
             case Void:
             case Func:
-            case Anyfunc:
             case I64:
                 RELEASE_ASSERT_NOT_REACHED(); // Handled above.
             case Anyref:
+            case Anyfunc:
             case I32: {
                 GPRReg gprReg;
                 if (marshalledGPRs < wasmCC.m_gprArgs.size())
@@ -390,7 +397,6 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
                     jit.zeroExtend32ToPtr(gprReg, gprReg); // Clear non-int32 and non-tag bits.
                     jit.boxInt32(gprReg, JSValueRegs(gprReg), DoNotHaveTagRegisters);
                 }
-                // FIXME: We need to box wasm Funcrefs once they are supported here.
                 jit.store64(gprReg, calleeFrame.withOffset(calleeFrameOffset));
                 calleeFrameOffset += sizeof(Register);
                 break;
@@ -441,10 +447,10 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
             switch (argType) {
             case Void:
             case Func:
-            case Anyfunc:
             case I64:
                 RELEASE_ASSERT_NOT_REACHED(); // Handled above.
             case Anyref:
+            case Anyfunc:
             case I32:
                 // Skipped: handled above.
                 if (marshalledGPRs >= wasmCC.m_gprArgs.size())
@@ -519,7 +525,6 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
         // Discard.
         break;
     case Func:
-    case Anyfunc:
         // For the JavaScript embedding, imports with these types in their signature return are a WebAssembly.Module validation error.
         RELEASE_ASSERT_NOT_REACHED();
         break;
@@ -553,6 +558,7 @@ Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM* vm
         done.link(&jit);
         break;
     }
+    case Anyfunc:
     case Anyref:
         break;
     case F32: {
@@ -695,6 +701,25 @@ void* wasmToJSException(ExecState* exec, Wasm::ExceptionType type, Instance* was
     return vm.targetMachinePCForThrow;
 }
 
+void emitThrowWasmToJSException(CCallHelpers& jit, GPRReg wasmInstance, Wasm::ExceptionType type)
+{
+    ASSERT(wasmInstance != GPRInfo::argumentGPR0);
+    jit.loadPtr(CCallHelpers::Address(wasmInstance, Wasm::Instance::offsetOfPointerToTopEntryFrame()), GPRInfo::argumentGPR0);
+    jit.loadPtr(CCallHelpers::Address(GPRInfo::argumentGPR0), GPRInfo::argumentGPR0);
+    jit.copyCalleeSavesToEntryFrameCalleeSavesBuffer(GPRInfo::argumentGPR0);
+    jit.move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+    jit.move(CCallHelpers::TrustedImm32(static_cast<int32_t>(type)), GPRInfo::argumentGPR1);
+
+    CCallHelpers::Call call = jit.call(OperationPtrTag);
+
+    jit.jump(GPRInfo::returnValueGPR, ExceptionHandlerPtrTag);
+    jit.breakpoint(); // We should not reach this.
+
+    jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
+        linkBuffer.link(call, FunctionPtr<OperationPtrTag>(Wasm::wasmToJSException));
+    });
+}
+
 } } // namespace JSC::Wasm
 
 #endif // ENABLE(WEBASSEMBLY)
index e0c74c3..5445800 100644 (file)
@@ -45,6 +45,7 @@ class Instance;
 Expected<MacroAssemblerCodeRef<WasmEntryPtrTag>, BindingFailure> wasmToJS(VM*, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex, unsigned importIndex);
 
 void* wasmToJSException(ExecState*, Wasm::ExceptionType, Instance*);
+void emitThrowWasmToJSException(CCallHelpers&, GPRReg wasmInstance, Wasm::ExceptionType);
 
 } } // namespace JSC::Wasm
 
index 91b5b83..d897150 100644 (file)
@@ -84,6 +84,11 @@ static EncodedJSValue JSC_HOST_CALL callWebAssemblyFunction(ExecState* exec)
         case Wasm::I32:
             arg = JSValue::decode(arg.toInt32(exec));
             break;
+        case Wasm::Anyfunc: {
+            if (!isWebAssemblyHostFunction(vm, arg) && !arg.isNull())
+                return JSValue::encode(throwException(exec, scope, createJSWebAssemblyRuntimeError(exec, vm, "Anyfunc must be an exported wasm function")));
+            break;
+        }
         case Wasm::Anyref:
             break;
         case Wasm::I64:
@@ -97,7 +102,6 @@ static EncodedJSValue JSC_HOST_CALL callWebAssemblyFunction(ExecState* exec)
             break;
         case Wasm::Void:
         case Wasm::Func:
-        case Wasm::Anyfunc:
             RELEASE_ASSERT_NOT_REACHED();
         }
         RETURN_IF_EXCEPTION(scope, encodedJSValue());
@@ -228,6 +232,7 @@ MacroAssemblerCodePtr<JSEntryPtrTag> WebAssemblyFunction::jsCallEntrypointSlow()
             argumentsIncludeI64 = true;
             break;
         case Wasm::Anyref:
+        case Wasm::Anyfunc:
         case Wasm::I32:
             if (numGPRs >= Wasm::wasmCallingConvention().m_gprArgs.size())
                 totalFrameSize += sizeof(CPURegister);
@@ -303,6 +308,27 @@ MacroAssemblerCodePtr<JSEntryPtrTag> WebAssemblyFunction::jsCallEntrypointSlow()
                     ++numGPRs;
                 }
                 break;
+            case Wasm::Anyfunc: {
+                // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+                bool (*shouldThrow)(Wasm::Instance*, JSValue) = [] (Wasm::Instance* wasmInstance, JSValue arg) -> bool {
+                    JSWebAssemblyInstance* instance = wasmInstance->owner<JSWebAssemblyInstance>();
+                    JSGlobalObject* globalObject = instance->globalObject();
+                    VM& vm = globalObject->vm();
+                    return !isWebAssemblyHostFunction(vm, arg) && !arg.isNull();
+                };
+                jit.move(CCallHelpers::TrustedImmPtr(&instance()->instance()), GPRInfo::argumentGPR0);
+                jit.load64(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), GPRInfo::argumentGPR1);
+                jit.setupArguments<decltype(shouldThrow)>(GPRInfo::argumentGPR0, GPRInfo::argumentGPR1);
+                auto call = jit.call(OperationPtrTag);
+
+                jit.addLinkTask([=] (LinkBuffer& linkBuffer) {
+                    linkBuffer.link(call, FunctionPtr<OperationPtrTag>(shouldThrow));
+                });
+
+                slowPath.append(jit.branchTest32(CCallHelpers::NonZero, GPRInfo::returnValueGPR));
+
+                FALLTHROUGH;
+            }
             case Wasm::Anyref: {
                 jit.load64(CCallHelpers::Address(GPRInfo::callFrameRegister, jsOffset), scratchGPR);
 
@@ -466,13 +492,11 @@ MacroAssemblerCodePtr<JSEntryPtrTag> WebAssemblyFunction::jsCallEntrypointSlow()
         isNaN.link(&jit);
         break;
     }
-    case Wasm::Anyref: {
-        // FIXME: We need to box wasm Funcrefs once they are supported here.
+    case Wasm::Anyfunc:
+    case Wasm::Anyref:
         break;
-    }
     case Wasm::I64:
     case Wasm::Func:
-    case Wasm::Anyfunc:
         return nullptr;
     default:
         break;
index 4f801e5..12b1cef 100644 (file)
@@ -235,10 +235,15 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
             // ii. If the global_type of i is i64 or Type(v) is not Number, throw a WebAssembly.LinkError.
             if (moduleInformation.globals[import.kindIndex].type == Wasm::I64)
                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "cannot be an i64")));
-            if (moduleInformation.globals[import.kindIndex].type != Wasm::Anyref && !value.isNumber())
+            if (!isSubtype(moduleInformation.globals[import.kindIndex].type, Wasm::Anyref) && !value.isNumber())
                 return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "must be a number")));
             // iii. Append ToWebAssemblyValue(v) to imports.
             switch (moduleInformation.globals[import.kindIndex].type) {
+            case Wasm::Anyfunc:
+                if (!isWebAssemblyHostFunction(vm, value) && !value.isNull())
+                    return exception(createJSWebAssemblyLinkError(exec, vm, importFailMessage(import, "imported global", "must be a wasm exported function or null")));
+                m_instance->instance().setGlobal(import.kindIndex, value);
+                break;
             case Wasm::Anyref:
                 m_instance->instance().setGlobal(import.kindIndex, value);
                 break;
@@ -319,6 +324,47 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
         }
     }
 
+    unsigned functionImportCount = codeBlock->functionImportCount();
+    auto makeFunctionWrapper = [&] (const String& field, uint32_t index) -> JSValue {
+        // If we already made a wrapper, do not make a new one.
+        JSValue wrapper = m_instance->instance().getFunctionWrapper(index);
+
+        if (!wrapper.isNull())
+            return wrapper;
+
+        // 1. If e is a closure c:
+        //   i. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
+        //   ii. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
+        if (index < functionImportCount) {
+            JSObject* functionImport = m_instance->instance().importFunction<WriteBarrier<JSObject>>(index)->get();
+            if (isWebAssemblyHostFunction(vm, functionImport))
+                wrapper = functionImport;
+            else {
+                Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(index);
+                wrapper = WebAssemblyWrapperFunction::create(vm, globalObject, globalObject->webAssemblyWrapperFunctionStructure(), functionImport, index, m_instance.get(), signatureIndex);
+            }
+        } else {
+            //   iii. Otherwise:
+            //     a. Let func be an Exported Function Exotic Object created from c.
+            //     b. Append func to funcs.
+            //     c. Return func.
+            Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(index);
+            Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(index);
+            Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(index);
+            const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
+            WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, globalObject->webAssemblyFunctionStructure(), signature.argumentCount(), field, m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
+            wrapper = function;
+        }
+
+        ASSERT(wrapper.isFunction(vm));
+        m_instance->instance().setFunctionWrapper(index, wrapper);
+
+        return wrapper;
+    };
+
+    for (auto index : moduleInformation.referencedFunctions())
+        makeFunctionWrapper("Referenced function", index);
+
     // Globals
     {
         for (size_t globalIndex = moduleInformation.firstInternalGlobal; globalIndex < moduleInformation.globals.size(); ++globalIndex) {
@@ -327,13 +373,16 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
             if (global.initializationType == Wasm::Global::FromGlobalImport) {
                 ASSERT(global.initialBitsOrImportNumber < moduleInformation.firstInternalGlobal);
                 m_instance->instance().setGlobal(globalIndex, m_instance->instance().loadI64Global(global.initialBitsOrImportNumber));
+            } else if (global.initializationType == Wasm::Global::FromRefFunc) {
+                ASSERT(global.initialBitsOrImportNumber < moduleInformation.functionIndexSpaceSize());
+                ASSERT(makeFunctionWrapper("Global init expr", global.initialBitsOrImportNumber).isFunction(vm));
+                m_instance->instance().setGlobal(globalIndex, JSValue::encode(makeFunctionWrapper("Global init expr", global.initialBitsOrImportNumber)));
             } else
                 m_instance->instance().setGlobal(globalIndex, global.initialBitsOrImportNumber);
         }
     }
 
     SymbolTable* exportSymbolTable = module->exportSymbolTable();
-    unsigned functionImportCount = codeBlock->functionImportCount();
 
     // Let exports be a list of (string, JS value) pairs that is mapped from each external value e in instance.exports as follows:
     JSModuleEnvironment* moduleEnvironment = JSModuleEnvironment::create(vm, globalObject, nullptr, exportSymbolTable, JSValue(), this);
@@ -341,30 +390,9 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
         JSValue exportedValue;
         switch (exp.kind) {
         case Wasm::ExternalKind::Function: {
-            // 1. If e is a closure c:
-            //   i. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
-            //   ii. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
-            if (exp.kindIndex < functionImportCount) {
-                unsigned functionIndex = exp.kindIndex;
-                JSObject* functionImport = m_instance->instance().importFunction<WriteBarrier<JSObject>>(functionIndex)->get();
-                if (isWebAssemblyHostFunction(vm, functionImport))
-                    exportedValue = functionImport;
-                else {
-                    Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(functionIndex);
-                    exportedValue = WebAssemblyWrapperFunction::create(vm, globalObject, globalObject->webAssemblyWrapperFunctionStructure(), functionImport, functionIndex, m_instance.get(), signatureIndex);
-                }
-            } else {
-                //   iii. Otherwise:
-                //     a. Let func be an Exported Function Exotic Object created from c.
-                //     b. Append func to funcs.
-                //     c. Return func.
-                Wasm::Callee& embedderEntrypointCallee = codeBlock->embedderEntrypointCalleeFromFunctionIndexSpace(exp.kindIndex);
-                Wasm::WasmToWasmImportableFunction::LoadLocation entrypointLoadLocation = codeBlock->entrypointLoadLocationFromFunctionIndexSpace(exp.kindIndex);
-                Wasm::SignatureIndex signatureIndex = module->signatureIndexFromFunctionIndexSpace(exp.kindIndex);
-                const Wasm::Signature& signature = Wasm::SignatureInformation::get(signatureIndex);
-                WebAssemblyFunction* function = WebAssemblyFunction::create(vm, globalObject, globalObject->webAssemblyFunctionStructure(), signature.argumentCount(), String::fromUTF8(exp.field), m_instance.get(), embedderEntrypointCallee, entrypointLoadLocation, signatureIndex);
-                exportedValue = function;
-            }
+            exportedValue = makeFunctionWrapper(String::fromUTF8(exp.field), exp.kindIndex);
+            ASSERT(exportedValue.isFunction(vm));
+            ASSERT(makeFunctionWrapper(String::fromUTF8(exp.field), exp.kindIndex) == exportedValue);
             break;
         }
         case Wasm::ExternalKind::Table: {
@@ -388,8 +416,7 @@ void WebAssemblyModuleRecord::link(ExecState* exec, JSValue, JSObject* importObj
             // Return ToJSValue(v).
             switch (global.type) {
             case Wasm::Anyref:
-                // FIXME: We need to box wasm Funcrefs once they are supported here.
-                // <https://bugs.webkit.org/show_bug.cgi?id=198157>
+            case Wasm::Anyfunc:
                 exportedValue = JSValue::decode(m_instance->instance().loadI64Global(exp.kindIndex));
                 break;
 
index 30bbc74..75285d6 100644 (file)
         "i64":     { "type": "varint7", "value":  -2, "b3type": "B3::Int64" },
         "f32":     { "type": "varint7", "value":  -3, "b3type": "B3::Float" },
         "f64":     { "type": "varint7", "value":  -4, "b3type": "B3::Double" },
-        "anyfunc": { "type": "varint7", "value": -16, "b3type": "B3::Void" },
+        "anyfunc": { "type": "varint7", "value": -16, "b3type": "B3::Int64" },
         "anyref":  { "type": "varint7", "value": -17, "b3type": "B3::Int64" },
         "func":    { "type": "varint7", "value": -32, "b3type": "B3::Void" },
         "void":    { "type": "varint7", "value": -64, "b3type": "B3::Void" }
     },
-    "value_type": ["i32", "i64", "f32", "f64", "anyref"],
-    "block_type": ["i32", "i64", "f32", "f64", "void", "anyref"],
+    "value_type": ["i32", "i64", "f32", "f64", "anyref", "anyfunc"],
+    "block_type": ["i32", "i64", "f32", "f64", "void", "anyref", "anyfunc"],
     "elem_type": ["anyfunc","anyref"],
     "external_kind": {
         "Function": { "type": "uint8", "value": 0 },
@@ -59,8 +59,9 @@
         "i64.const":           { "category": "special",    "value":  66, "return": ["i64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "varint64"}],                                             "description": "a constant value interpreted as i64" },
         "f64.const":           { "category": "special",    "value":  68, "return": ["f64"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "double"}],                                               "description": "a constant value interpreted as f64" },
         "f32.const":           { "category": "special",    "value":  67, "return": ["f32"],      "parameter": [],                       "immediate": [{"name": "value",          "type": "float"}],                                                "description": "a constant value interpreted as f32" },
-        "ref.null":            { "category": "special",    "value": 208, "return": ["anyref"],   "parameter": [],                       "immediate": [],                                                                                           "description": "a constant null reference" },
+        "ref.null":            { "category": "special",    "value": 208, "return": ["anyfunc"],  "parameter": [],                       "immediate": [],                                                                                           "description": "a constant null reference" },
         "ref.is_null":         { "category": "special",    "value": 209, "return": ["i32"],      "parameter": ["anyref"],               "immediate": [],                                                                                           "description": "determine if a reference is null" },
+        "ref.func":            { "category": "special",    "value": 210, "return": ["anyfunc"],  "parameter": [],                       "immediate": [{"name": "function_index", "type": "varuint32"}],                                            "description": "return a reference to the function at the given index" },
         "get_local":           { "category": "special",    "value":  32, "return": ["any"],      "parameter": [],                       "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "read a local variable or parameter" },
         "set_local":           { "category": "special",    "value":  33, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "local_index",    "type": "varuint32"}],                                            "description": "write a local variable or parameter" },
         "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" },