From 41f7cdd040bf479fffb2f37759b193611edfe4b2 Mon Sep 17 00:00:00 2001 From: Jessica Tallon Date: Thu, 2 Sep 2021 15:58:18 +0000 Subject: [PATCH] Bug 1715275 - Add support for a JS WebAssembly.Function object r=rhunt This introduces a new Constructor WebAssembly.Function which is part of the WASM JS-API type reflection work. This allows normal JS functions to be wrapped with a type and used with WebAssembly objects (e.g. Tables). All exported WebAssembly functions will now be of this type, which nows provides a .type() method which will expose the arguments and results to JS. Differential Revision: https://phabricator.services.mozilla.com/D118712 --- js/public/ProtoKey.h | 1 + js/public/friend/ErrorNumbers.msg | 2 + js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/GlobalObject.cpp | 1 + js/src/wasm/WasmJS.cpp | 316 ++++++++++++++++-- js/src/wasm/WasmJS.h | 2 + .../jsapi/function/call.tentative.any.js.ini | 12 + .../function/constructor.tentative.any.js.ini | 57 ++++ .../jsapi/function/table.tentative.any.js.ini | 12 + .../jsapi/function/type.tentative.any.js.ini | 21 ++ .../tests/wasm/jsapi/assertions.js | 9 +- .../wasm/jsapi/function/call.tentative.any.js | 11 + .../function/constructor.tentative.any.js | 35 ++ .../jsapi/function/table.tentative.any.js | 30 ++ .../wasm/jsapi/function/type.tentative.any.js | 28 ++ 15 files changed, 513 insertions(+), 25 deletions(-) create mode 100644 testing/web-platform/meta/wasm/jsapi/function/call.tentative.any.js.ini create mode 100644 testing/web-platform/meta/wasm/jsapi/function/constructor.tentative.any.js.ini create mode 100644 testing/web-platform/meta/wasm/jsapi/function/table.tentative.any.js.ini create mode 100644 testing/web-platform/meta/wasm/jsapi/function/type.tentative.any.js.ini create mode 100644 testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js create mode 100644 testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js create mode 100644 testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js create mode 100644 testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js diff --git a/js/public/ProtoKey.h b/js/public/ProtoKey.h index 8e601ed750fe..a5ccae69386c 100644 --- a/js/public/ProtoKey.h +++ b/js/public/ProtoKey.h @@ -122,6 +122,7 @@ REAL(WasmTable, OCLASP(WasmTable)) \ REAL(WasmGlobal, OCLASP(WasmGlobal)) \ REAL(WasmTag, OCLASP(WasmTag)) \ + REAL(WasmFunction, CLASP(WasmFunction)) \ REAL(WasmException, OCLASP(WasmException)) \ REAL(FinalizationRegistry, OCLASP(FinalizationRegistry)) \ REAL(WeakRef, OCLASP(WeakRef)) \ diff --git a/js/public/friend/ErrorNumbers.msg b/js/public/friend/ErrorNumbers.msg index 45dee1452918..8e27ea3bda15 100644 --- a/js/public/friend/ErrorNumbers.msg +++ b/js/public/friend/ErrorNumbers.msg @@ -470,6 +470,8 @@ MSG_DEF(JSMSG_WASM_BAD_EXN_ARG, 0, JSEXN_TYPEERR, "first argument mus MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD, 0, JSEXN_TYPEERR, "second argument must be an object") MSG_DEF(JSMSG_WASM_BAD_EXN_PAYLOAD_LEN, 2, JSEXN_TYPEERR, "expected {0} values but got {1}") MSG_DEF(JSMSG_WASM_BAD_EXN_TAG, 0, JSEXN_TYPEERR, "exception's tag did not match the provided exception tag") +MSG_DEF(JSMSG_WASM_BAD_FUNCTION_VALUE, 0, JSEXN_TYPEERR, "second argument must be a function") +MSG_DEF(JSMSG_WASM_BAD_ARG_TYPE, 0, JSEXN_TYPEERR, "parameters and results must be an iterator of value types") MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer") MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}") MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified") diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index cb2333f78395..a050b0df1502 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -405,6 +405,7 @@ "RequireObjectCoercible") \ MACRO_(resolve, resolve, "resolve") \ MACRO_(result, result, "result") \ + MACRO_(results, results, "results") \ MACRO_(resumeGenerator, resumeGenerator, "resumeGenerator") \ MACRO_(return, return_, "return") \ MACRO_(revoke, revoke, "revoke") \ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 6354218f71c2..d2d4c0868b23 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -162,6 +162,7 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) { case JSProto_WasmTable: case JSProto_WasmGlobal: case JSProto_WasmTag: + case JSProto_WasmFunction: case JSProto_WasmException: return false; diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index c0380e96cac5..bcfd66f3b55b 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -46,6 +46,7 @@ #include "vm/GlobalObject.h" // js::GlobalObject #include "vm/HelperThreadState.h" // js::PromiseHelperTask #include "vm/Interpreter.h" +#include "vm/JSFunction.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/PromiseObject.h" // js::PromiseObject #include "vm/StringType.h" @@ -1050,6 +1051,32 @@ static JSObject* GetWasmConstructorPrototype(JSContext* cx, return proto; } +[[nodiscard]] bool ParseValTypeArguments(JSContext* cx, HandleValue src, + ValTypeVector& dest) { + JS::ForOfIterator iterator(cx); + + if (!iterator.init(src, JS::ForOfIterator::ThrowOnNonIterable)) { + return false; + } + + RootedValue nextParam(cx); + while (true) { + bool done; + if (!iterator.next(&nextParam, &done)) { + return false; + } + if (done) { + break; + } + + ValType valType; + if (!ToValType(cx, nextParam, &valType) || !dest.append(valType)) { + return false; + } + } + return true; +} + // ============================================================================ // WebAssembly.Module class and methods @@ -2201,10 +2228,16 @@ bool WasmInstanceObject::getExportedFunction( if (!name) { return false; } - - fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, - gc::AllocKind::FUNCTION_EXTENDED, TenuredObject, - FunctionFlags::WASM)); + RootedObject proto(cx); +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmFunction); + if (!proto) { + return false; + } +#endif + fun.set(NewFunctionWithProto( + cx, WasmCall, numArgs, FunctionFlags::WASM, nullptr, name, proto, + gc::AllocKind::FUNCTION_EXTENDED, TenuredObject)); if (!fun) { return false; } @@ -3610,26 +3643,9 @@ bool WasmTagObject::construct(JSContext* cx, unsigned argc, Value* vp) { return false; } - JS::ForOfIterator iterator(cx); - if (!iterator.init(paramsVal, JS::ForOfIterator::ThrowOnNonIterable)) { - return false; - } - ValTypeVector params; - RootedValue nextParam(cx); - while (true) { - bool done; - if (!iterator.next(&nextParam, &done)) { - return false; - } - if (done) { - break; - } - - ValType argType; - if (!ToValType(cx, nextParam, &argType) || !params.append(argType)) { - return false; - } + if (!ParseValTypeArguments(cx, paramsVal, params)) { + return false; } RootedObject proto(cx); @@ -4045,6 +4061,257 @@ ArrayObject& WasmExceptionObject::refs() const { return getReservedSlot(REFS_SLOT).toObject().as(); } +// ============================================================================ +// WebAssembly.Function and methods +#ifdef ENABLE_WASM_TYPE_REFLECTIONS +static JSObject* CreateWasmFunctionPrototype(JSContext* cx, JSProtoKey key) { + // WasmFunction's prototype should inherit from JSFunction's prototype. + RootedObject jsProto( + cx, GlobalObject::getOrCreatePrototype(cx, JSProto_Function)); + if (!jsProto) { + return nullptr; + } + return GlobalObject::createBlankPrototypeInheriting(cx, &PlainObject::class_, + jsProto); +} + +[[nodiscard]] static bool IsWasmFunction(HandleValue v) { + if (!v.isObject()) { + return false; + } + if (!v.toObject().is()) { + return false; + } + return v.toObject().as().isWasm(); +} + +/* static */ +static bool ValTypesToArray(JSContext* cx, Handle key, + const ValTypeVector& value, + MutableHandle dest) { + RootedArrayObject result(cx, NewDenseEmptyArray(cx)); + for (ValType v : value) { + RootedString type(cx, UTF8CharsToString(cx, ToString(v).get())); + if (!type) { + return false; + } + if (!NewbornArrayPush(cx, result, StringValue(type))) { + return false; + } + } + return dest.append(IdValuePair(key, ObjectValue(*result))); +} + +bool WasmFunctionTypeImpl(JSContext* cx, const CallArgs& args) { + RootedFunction function(cx, &args.thisv().toObject().as()); + + // Lookup the type information. + RootedWasmInstanceObject instanceObj( + cx, ExportedFunctionToInstanceObject(function)); + uint32_t funcIndex = ExportedFunctionToFuncIndex(function); + Instance& instance = instanceObj->instance(); + const FuncType& ft = instance.metadata(instance.code().bestTier()) + .lookupFuncExport(funcIndex) + .funcType(); + + const ValTypeVector& parameters = ft.args(); + RootedId parametersId(cx, NameToId(cx->names().parameters)); + Rooted props(cx, IdValueVector(cx)); + if (!ValTypesToArray(cx, parametersId, parameters, &props)) { + return false; + } + + const ValTypeVector& results = ft.results(); + RootedId resultsId(cx, NameToId(cx->names().results)); + if (!ValTypesToArray(cx, resultsId, results, &props)) { + return false; + } + + RootedObject functionType( + cx, NewPlainObjectWithProperties(cx, props.begin(), props.length(), + GenericObject)); + if (!functionType) { + return false; + } + + args.rval().setObject(*functionType); + return true; +} + +bool WasmFunctionType(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +JSFunction* WasmFunctionCreate(JSContext* cx, HandleFunction fun, + wasm::ValTypeVector&& params, + wasm::ValTypeVector&& results, + HandleObject proto) { + MOZ_RELEASE_ASSERT(!IsWasmExportedFunction(fun)); + + // We want to import the function to a wasm module and then export it again so + // that it behaves exactly like a normal wasm function and can be used like + // one in wasm tables. Below we create the wasm module with fun as it's import + // then exporting it. + FeatureOptions options; + ScriptedCaller scriptedCaller; + SharedCompileArgs compileArgs = + CompileArgs::build(cx, std::move(scriptedCaller), options); + ModuleEnvironment moduleEnv(compileArgs->features); + CompilerEnvironment compilerEnv(CompileMode::Once, Tier::Optimized, + OptimizedBackend::Ion, DebugEnabled::False); + compilerEnv.computeParameters(); + + // Add the Import for the function + FuncType funcType = FuncType(std::move(params), std::move(results)); + TypeDef funcTypeDef = TypeDef(std::move(funcType)); + if (!moduleEnv.types.append(std::move(funcTypeDef))) { + return nullptr; + } + if (!moduleEnv.typeIds.resize(1)) { + return nullptr; + } + FuncDesc funcDesc = + FuncDesc(&moduleEnv.types[0].funcType(), &moduleEnv.typeIds[0], 0); + if (!moduleEnv.funcs.append(funcDesc) || + !moduleEnv.funcImportGlobalDataOffsets.resize(1)) { + return nullptr; + } + moduleEnv.declareFuncExported(0, false, false); + + // We will be looking up and using the function in the future by index so the + // name doesn't matter. + CacheableChars fieldName = DuplicateString(""); + if (!moduleEnv.exports.emplaceBack(std::move(fieldName), 0, + DefinitionKind::Function)) { + return nullptr; + } + + ModuleGenerator mg(*compileArgs, &moduleEnv, &compilerEnv, nullptr, nullptr); + if (!mg.init(nullptr)) { + return nullptr; + } + ShareableBytes* sharableBytes = cx->new_(); + // We're not compiling any function definitions. + if (!mg.finishFuncDefs()) { + return nullptr; + } + SharedModule wasmModule = mg.finishModule(*sharableBytes); + // Instantiate the module. + Rooted imports(cx); + imports.get().funcs.append(fun); + RootedWasmInstanceObject instanceObj(cx); + if (!wasmModule->instantiate(cx, imports.get(), nullptr, &instanceObj)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return nullptr; + } + + // Get the exported function which wraps the JS function to return. + RootedFunction exportedFun(cx); + instanceObj->getExportedFunction(cx, instanceObj, 0, &exportedFun); + return exportedFun; +} + +bool WasmFunctionConstruct(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "WebAssembly.Function")) { + return false; + } + + if (!args.requireAtLeast(cx, "WebAssembly.Function", 2)) { + return false; + } + + ValTypeVector params; + ValTypeVector results; + if (!args[0].isObject()) { + return false; + } + RootedObject obj(cx, &args[0].toObject()); + + RootedId parametersId(cx, NameToId(cx->names().parameters)); + RootedValue parametersVal(cx); + if (!GetProperty(cx, obj, obj, parametersId, ¶metersVal)) { + return false; + } + if (!ParseValTypeArguments(cx, parametersVal, params)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_ARG_TYPE); + return false; + } + + RootedId resultsId(cx, NameToId(cx->names().results)); + RootedValue resultsVal(cx); + if (!GetProperty(cx, obj, obj, resultsId, &resultsVal)) { + return false; + } + if (!ParseValTypeArguments(cx, resultsVal, results)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_ARG_TYPE); + return false; + } + + if (!args[1].isObject() || !args[1].toObject().is()) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_WASM_BAD_FUNCTION_VALUE); + return false; + } + + RootedFunction funcObj(cx, &args[1].toObject().as()); + + RootedObject proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmFunction, + &proto)) { + return false; + } + if (!proto) { + proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmFunction); + } + + RootedFunction function(cx, WasmFunctionCreate(cx, funcObj, std::move(params), + std::move(results), proto)); + if (!function) { + return false; + } + args.rval().setObject(*function); + + return true; +} + +static JSObject* CreateWasmFunctionConstructor(JSContext* cx, JSProtoKey key) { + RootedObject proto( + cx, GlobalObject::getOrCreateFunctionConstructor(cx, cx->global())); + if (!proto) { + return nullptr; + } + + HandlePropertyName name = cx->names().WasmFunction; + return NewFunctionWithProto(cx, WasmFunctionConstruct, 1, + FunctionFlags::NATIVE_CTOR, nullptr, name, proto, + gc::AllocKind::FUNCTION, TenuredObject); +} + +const JSFunctionSpec WasmFunctionMethods[] = { + JS_FN("type", WasmFunctionType, 0, 0), JS_FS_END}; + +const ClassSpec WasmFunctionClassSpec = {CreateWasmFunctionConstructor, + CreateWasmFunctionPrototype, + nullptr, + nullptr, + WasmFunctionMethods, + nullptr, + nullptr, + ClassSpec::DontDefineConstructor}; + +const JSClass js::WasmFunctionClass = { + "WebAssembly.Function", 0, JS_NULL_CLASS_OPS, &WasmFunctionClassSpec}; + +#else +const JSClass js::WasmFunctionClass = {"WebAssembly.Function", 0, + JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC}; +#endif + // ============================================================================ // WebAssembly class and static methods @@ -5067,6 +5334,9 @@ static bool WebAssemblyClassFinish(JSContext* cx, HandleObject object, {"CompileError", GetExceptionProtoKey(JSEXN_WASMCOMPILEERROR)}, {"LinkError", GetExceptionProtoKey(JSEXN_WASMLINKERROR)}, {"RuntimeError", GetExceptionProtoKey(JSEXN_WASMRUNTIMEERROR)}, +#ifdef ENABLE_WASM_TYPE_REFLECTIONS + {"Function", JSProto_WasmFunction}, +#endif }; RootedValue ctorValue(cx); RootedId id(cx); diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index 3c7bbcd53def..0100bfe9f479 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -573,6 +573,8 @@ class WasmNamespaceObject : public NativeObject { static const ClassSpec classSpec_; }; +extern const JSClass WasmFunctionClass; + } // namespace js #endif // wasm_js_h diff --git a/testing/web-platform/meta/wasm/jsapi/function/call.tentative.any.js.ini b/testing/web-platform/meta/wasm/jsapi/function/call.tentative.any.js.ini new file mode 100644 index 000000000000..6fea7c772436 --- /dev/null +++ b/testing/web-platform/meta/wasm/jsapi/function/call.tentative.any.js.ini @@ -0,0 +1,12 @@ +[call.tentative.any.worker.html] + [test calling function] + expected: + if release_or_beta: FAIL +[call.tentative.any.html] + [test calling function] + expected: + if release_or_beta: FAIL +[call.tentative.any.js] + [test calling function] + expected: + if release_or_beta: FAIL diff --git a/testing/web-platform/meta/wasm/jsapi/function/constructor.tentative.any.js.ini b/testing/web-platform/meta/wasm/jsapi/function/constructor.tentative.any.js.ini new file mode 100644 index 000000000000..789710616460 --- /dev/null +++ b/testing/web-platform/meta/wasm/jsapi/function/constructor.tentative.any.js.ini @@ -0,0 +1,57 @@ +[constructor.tentative.any.worker.html] + [construct with JS function] + expected: + if release_or_beta: FAIL + [fail with missing parameters] + expected: + if release_or_beta: FAIL + [fail with missing results] + expected: + if release_or_beta: FAIL + [fail with non-string parameters & results] + expected: + if release_or_beta: FAIL + [fail with non-existant parameter and result type] + expected: + if release_or_beta: FAIL + [fail with non-function object] + expected: + if release_or_beta: FAIL +[constructor.tentative.any.html] + [construct with JS function] + expected: + if release_or_beta: FAIL + [fail with missing parameters] + expected: + if release_or_beta: FAIL + [fail with missing results] + expected: + if release_or_beta: FAIL + [fail with non-string parameters & results] + expected: + if release_or_beta: FAIL + [fail with non-existant parameter and result type] + expected: + if release_or_beta: FAIL + [fail with non-function object] + expected: + if release_or_beta: FAIL +[constructor.tentative.any.js] + [construct with JS function] + expected: + if release_or_beta: FAIL + [fail with missing parameters] + expected: + if release_or_beta: FAIL + [fail with missing results] + expected: + if release_or_beta: FAIL + [fail with non-string parameters & results] + expected: + if release_or_beta: FAIL + [fail with non-existant parameter and result type] + expected: + if release_or_beta: FAIL + [fail with non-function object] + expected: + if release_or_beta: FAIL diff --git a/testing/web-platform/meta/wasm/jsapi/function/table.tentative.any.js.ini b/testing/web-platform/meta/wasm/jsapi/function/table.tentative.any.js.ini new file mode 100644 index 000000000000..7183df5bc5a7 --- /dev/null +++ b/testing/web-platform/meta/wasm/jsapi/function/table.tentative.any.js.ini @@ -0,0 +1,12 @@ +[table.tentative.any.worker.html] + [Test insertion into table] + expected: + if release_or_beta: FAIL +[table.tentative.any.html] + [Test insertion into table] + expected: + if release_or_beta: FAIL +[table.tentative.any.js] + [Test insertion into table] + expected: + if release_or_beta: FAIL diff --git a/testing/web-platform/meta/wasm/jsapi/function/type.tentative.any.js.ini b/testing/web-platform/meta/wasm/jsapi/function/type.tentative.any.js.ini new file mode 100644 index 000000000000..88d63c4ce485 --- /dev/null +++ b/testing/web-platform/meta/wasm/jsapi/function/type.tentative.any.js.ini @@ -0,0 +1,21 @@ +[type.tentative.any.worker.html] + [Check empty results and parameters] + expected: + if release_or_beta: FAIL + [Check all types] + expected: + if release_or_beta: FAIL +[type.tentative.any.html] + [Check empty results and parameters] + expected: + if release_or_beta: FAIL + [Check all types] + expected: + if release_or_beta: FAIL +[type.tentative.any.js] + [Check empty results and parameters] + expected: + if release_or_beta: FAIL + [Check all types] + expected: + if release_or_beta: FAIL diff --git a/testing/web-platform/tests/wasm/jsapi/assertions.js b/testing/web-platform/tests/wasm/jsapi/assertions.js index f00303f6aa50..162f5a9a6b8d 100644 --- a/testing/web-platform/tests/wasm/jsapi/assertions.js +++ b/testing/web-platform/tests/wasm/jsapi/assertions.js @@ -17,8 +17,13 @@ function assert_function_length(fn, length, description) { } function assert_exported_function(fn, { name, length }, description) { - assert_equals(Object.getPrototypeOf(fn), Function.prototype, - `${description}: prototype`); + if (WebAssembly.Function === undefined) { + assert_equals(Object.getPrototypeOf(fn), Function.prototype, + `${description}: prototype`); + } else { + assert_equals(Object.getPrototypeOf(fn), WebAssembly.Function.prototype, + `${description}: prototype`); + } assert_function_name(fn, name, description); assert_function_length(fn, length, description); diff --git a/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js new file mode 100644 index 000000000000..2d5b36b7bdb1 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/call.tentative.any.js @@ -0,0 +1,11 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function addxy(x, y) { + return x + y +} + +test(() => { + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_equals(fun(1, 2), 3) +}, "test calling function") diff --git a/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js new file mode 100644 index 000000000000..3b989967f91e --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/constructor.tentative.any.js @@ -0,0 +1,35 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function addxy(x, y) { + return x + y +} + +test(() => { + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_true(fun instanceof WebAssembly.Function) +}, "construct with JS function") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: []}, addxy)) +}, "fail with missing results") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({results: []}, addxy)) +}, "fail with missing parameters") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [1], results: [true]}, addxy)) +}, "fail with non-string parameters & results") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: ["invalid"], results: ["invalid"]}, addxy)) +}, "fail with non-existent parameter and result type") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, 72)) +}, "fail with non-function object") + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, {})) +}, "fail to construct with non-callable object") diff --git a/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js new file mode 100644 index 000000000000..d7d0d86e3b6a --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/table.tentative.any.js @@ -0,0 +1,30 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function testfunc(n) {} + +test(() => { + var table = new WebAssembly.Table({element: "anyfunc", initial: 3}) + var func1 = new WebAssembly.Function({parameters: ["i32"], results: []}, testfunc) + table.set(0, func1) + var func2 = new WebAssembly.Function({parameters: ["f32"], results: []}, testfunc) + table.set(1, func2) + var func3 = new WebAssembly.Function({parameters: ["i64"], results: []}, testfunc) + table.set(2, func3) + + var first = table.get(0) + assert_true(first instanceof WebAssembly.Function) + assert_equals(first, func1) + assert_equals(first.type().parameters[0], func1.type().parameters[0]) + + var second = table.get(1) + assert_true(second instanceof WebAssembly.Function) + assert_equals(second, func2) + assert_equals(second.type().parameters[0], func2.type().parameters[0]) + + var third = table.get(2) + assert_true(third instanceof WebAssembly.Function) + assert_equals(third, func3) + assert_equals(third.type().parameters[0], func3.type().parameters[0]) + +}, "Test insertion into table") diff --git a/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js b/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js new file mode 100644 index 000000000000..e01a23a9e433 --- /dev/null +++ b/testing/web-platform/tests/wasm/jsapi/function/type.tentative.any.js @@ -0,0 +1,28 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function addNumbers(x, y, z) { + return x+y+z; +} + +function doNothing() {} + +function assert_function(functype, func) { + var wasmFunc = new WebAssembly.Function(functype, func); + assert_equals(functype.parameters.length, wasmFunc.type().parameters.length); + for(let i = 0; i < functype.parameters.length; i++) { + assert_equals(functype.parameters[i], wasmFunc.type().parameters[i]); + } + assert_equals(functype.results.length, wasmFunc.type().results.length); + for(let i = 0; i < functype.results.length; i++) { + assert_equals(functype.results[i], wasmFunc.type().results[i]); + } +} + +test(() => { + assert_function({results: [], parameters: []}, doNothing); +}, "Check empty results and parameters") + +test(() => { + assert_function({results: ["f64"], parameters: ["i32", "i64", "f32"]}, addNumbers) +}, "Check all types")