diff --git a/doc/api/errors.md b/doc/api/errors.md index 3f7e8a4b165943..22295985020337 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -2890,6 +2890,17 @@ The WASI instance has already started. The WASI instance has not been started. + + +### `ERR_WEBASSEMBLY_RESPONSE` + + + +The `Response` that has been passed to `WebAssembly.compileStreaming` or to +`WebAssembly.instantiateStreaming` is not a valid WebAssembly response. + ### `ERR_WORKER_INIT_FAILED` diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index b64dfaf980fd92..e1b882b6dbe744 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -5,6 +5,7 @@ const { ObjectDefineProperties, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, + PromiseResolve, SafeMap, SafeWeakMap, StringPrototypeStartsWith, @@ -24,7 +25,11 @@ const { } = require('internal/util'); const { Buffer } = require('buffer'); -const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; +const { + ERR_INVALID_ARG_TYPE, + ERR_MANIFEST_ASSERT_INTEGRITY, + ERR_WEBASSEMBLY_RESPONSE, +} = require('internal/errors').codes; const assert = require('internal/assert'); function prepareMainThreadExecution(expandArgv1 = false, @@ -215,6 +220,44 @@ function setupFetch() { Request: lazyInterface('Request'), Response: lazyInterface('Response'), }); + + // The WebAssembly Web API: https://webassembly.github.io/spec/web-api + internalBinding('wasm_web_api').setImplementation((streamState, source) => { + (async () => { + const response = await PromiseResolve(source); + if (!(response instanceof lazyUndici().Response)) { + throw new ERR_INVALID_ARG_TYPE( + 'source', ['Response', 'Promise resolving to Response'], response); + } + + const contentType = response.headers.get('Content-Type'); + if (contentType !== 'application/wasm') { + throw new ERR_WEBASSEMBLY_RESPONSE( + `has unsupported MIME type '${contentType}'`); + } + + if (!response.ok) { + throw new ERR_WEBASSEMBLY_RESPONSE( + `has status code ${response.status}`); + } + + if (response.bodyUsed !== false) { + throw new ERR_WEBASSEMBLY_RESPONSE('body has already been used'); + } + + // Pass all data from the response body to the WebAssembly compiler. + for await (const chunk of response.body) { + streamState.push(chunk); + } + })().then(() => { + // No error occurred. Tell the implementation that the stream has ended. + streamState.finish(); + }, (err) => { + // An error occurred, either because the given object was not a valid + // and usable Response or because a network error occurred. + streamState.abort(err); + }); + }); } // TODO(aduh95): move this to internal/bootstrap/browser when the CLI flag is diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 1dfa36e5f36a96..d5bb5023a79a8c 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1659,6 +1659,7 @@ E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module', Error); E('ERR_VM_MODULE_STATUS', 'Module status %s', Error); E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error); +E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError); E('ERR_WORKER_INIT_FAILED', 'Worker initialization failure: %s', Error); E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') => `Initiated Worker with ${msg}: ${ArrayPrototypeJoin(errors, ', ')}`, diff --git a/node.gyp b/node.gyp index e748208adcde25..4e06d00c823290 100644 --- a/node.gyp +++ b/node.gyp @@ -543,6 +543,7 @@ 'src/node_util.cc', 'src/node_v8.cc', 'src/node_wasi.cc', + 'src/node_wasm_web_api.cc', 'src/node_watchdog.cc', 'src/node_worker.cc', 'src/node_zlib.cc', diff --git a/src/api/environment.cc b/src/api/environment.cc index 97261256858403..f3a8f49812d5ce 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -3,8 +3,10 @@ #include "node_errors.h" #include "node_internals.h" #include "node_native_module_env.h" +#include "node_options-inl.h" #include "node_platform.h" #include "node_v8_platform-inl.h" +#include "node_wasm_web_api.h" #include "uv.h" #if HAVE_INSPECTOR @@ -252,6 +254,13 @@ void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s) { s.allow_wasm_code_generation_callback : AllowWasmCodeGenerationCallback; isolate->SetAllowWasmCodeGenerationCallback(allow_wasm_codegen_cb); + Mutex::ScopedLock lock(node::per_process::cli_options_mutex); + if (per_process::cli_options->get_per_isolate_options() + ->get_per_env_options() + ->experimental_fetch) { + isolate->SetWasmStreamingCallback(wasm_web_api::StartStreamingCompilation); + } + if ((s.flags & SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK) == 0) { auto* promise_reject_cb = s.promise_reject_callback ? s.promise_reject_callback : PromiseRejectCallback; diff --git a/src/env.h b/src/env.h index bededfcb5debfe..7e35833e45bd25 100644 --- a/src/env.h +++ b/src/env.h @@ -550,7 +550,9 @@ constexpr size_t kFsStatsBufferLength = V(tls_wrap_constructor_function, v8::Function) \ V(trace_category_state_function, v8::Function) \ V(udp_constructor_function, v8::Function) \ - V(url_constructor_function, v8::Function) + V(url_constructor_function, v8::Function) \ + V(wasm_streaming_compilation_impl, v8::Function) \ + V(wasm_streaming_object_constructor, v8::Function) class Environment; struct AllocatedBuffer; diff --git a/src/node_binding.cc b/src/node_binding.cc index 29b9ccdaed8b10..2991ee34746e0f 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -87,6 +87,7 @@ V(uv) \ V(v8) \ V(wasi) \ + V(wasm_web_api) \ V(watchdog) \ V(worker) \ V(zlib) diff --git a/src/node_wasm_web_api.cc b/src/node_wasm_web_api.cc new file mode 100644 index 00000000000000..b23096120b1121 --- /dev/null +++ b/src/node_wasm_web_api.cc @@ -0,0 +1,196 @@ +#include "node_wasm_web_api.h" + +#include "memory_tracker-inl.h" +#include "node_errors.h" + +namespace node { +namespace wasm_web_api { + +using v8::ArrayBuffer; +using v8::ArrayBufferView; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::Value; +using v8::WasmStreaming; + +Local WasmStreamingObject::Initialize(Environment* env) { + Local templ = env->wasm_streaming_object_constructor(); + if (!templ.IsEmpty()) { + return templ; + } + + Local t = env->NewFunctionTemplate(New); + t->Inherit(BaseObject::GetConstructorTemplate(env)); + t->InstanceTemplate()->SetInternalFieldCount( + WasmStreamingObject::kInternalFieldCount); + + env->SetProtoMethod(t, "push", Push); + env->SetProtoMethod(t, "finish", Finish); + env->SetProtoMethod(t, "abort", Abort); + + auto function = t->GetFunction(env->context()).ToLocalChecked(); + env->set_wasm_streaming_object_constructor(function); + return function; +} + +void WasmStreamingObject::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(Push); + registry->Register(Finish); + registry->Register(Abort); +} + +void WasmStreamingObject::MemoryInfo(MemoryTracker* tracker) const { + // v8::WasmStreaming is opaque. We assume that the size of the WebAssembly + // module that is being compiled is roughly what V8 allocates (as in, off by + // only a small factor). + tracker->TrackFieldWithSize("streaming", wasm_size_); +} + +MaybeLocal WasmStreamingObject::Create( + Environment* env, std::shared_ptr streaming) { + Local ctor = Initialize(env); + Local obj; + if (!ctor->NewInstance(env->context(), 0, nullptr).ToLocal(&obj)) { + return MaybeLocal(); + } + + CHECK(streaming); + + WasmStreamingObject* ptr = Unwrap(obj); + CHECK_NOT_NULL(ptr); + ptr->streaming_ = streaming; + ptr->wasm_size_ = 0; + return obj; +} + +void WasmStreamingObject::New(const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + Environment* env = Environment::GetCurrent(args); + new WasmStreamingObject(env, args.This()); +} + +void WasmStreamingObject::Push(const FunctionCallbackInfo& args) { + WasmStreamingObject* obj; + ASSIGN_OR_RETURN_UNWRAP(&obj, args.Holder()); + CHECK(obj->streaming_); + + CHECK_EQ(args.Length(), 1); + Local chunk = args[0]; + + // The start of the memory section backing the ArrayBuffer(View), the offset + // of the ArrayBuffer(View) within the memory section, and its size in bytes. + const void* bytes; + size_t offset; + size_t size; + + if (LIKELY(chunk->IsArrayBufferView())) { + Local view = chunk.As(); + bytes = view->Buffer()->GetBackingStore()->Data(); + offset = view->ByteOffset(); + size = view->ByteLength(); + } else if (LIKELY(chunk->IsArrayBuffer())) { + Local buffer = chunk.As(); + bytes = buffer->GetBackingStore()->Data(); + offset = 0; + size = buffer->ByteLength(); + } else { + return node::THROW_ERR_INVALID_ARG_TYPE( + Environment::GetCurrent(args), + "chunk must be an ArrayBufferView or an ArrayBuffer"); + } + + // Forward the data to V8. Internally, V8 will make a copy. + obj->streaming_->OnBytesReceived(static_cast(bytes) + offset, + size); + obj->wasm_size_ += size; +} + +void WasmStreamingObject::Finish(const FunctionCallbackInfo& args) { + WasmStreamingObject* obj; + ASSIGN_OR_RETURN_UNWRAP(&obj, args.Holder()); + CHECK(obj->streaming_); + + CHECK_EQ(args.Length(), 0); + obj->streaming_->Finish(); +} + +void WasmStreamingObject::Abort(const FunctionCallbackInfo& args) { + WasmStreamingObject* obj; + ASSIGN_OR_RETURN_UNWRAP(&obj, args.Holder()); + CHECK(obj->streaming_); + + CHECK_EQ(args.Length(), 1); + obj->streaming_->Abort(args[0]); +} + +void StartStreamingCompilation(const FunctionCallbackInfo& info) { + // V8 passes an instance of v8::WasmStreaming to this callback, which we can + // use to pass the WebAssembly module bytes to V8 as we receive them. + // Unfortunately, our fetch() implementation is a JavaScript dependency, so it + // is difficult to implement the required logic here. Instead, we create a + // a WasmStreamingObject that encapsulates v8::WasmStreaming and that we can + // pass to the JavaScript implementation. The JavaScript implementation can + // then push() bytes from the Response and eventually either finish() or + // abort() the operation. + + // Create the wrapper object. + std::shared_ptr streaming = + WasmStreaming::Unpack(info.GetIsolate(), info.Data()); + Environment* env = Environment::GetCurrent(info); + Local obj; + if (!WasmStreamingObject::Create(env, streaming).ToLocal(&obj)) { + // A JavaScript exception is pending. Let V8 deal with it. + return; + } + + // V8 always passes one argument to this callback. + CHECK_EQ(info.Length(), 1); + + // Prepare the JavaScript implementation for invocation. We will pass the + // WasmStreamingObject as the first argument, followed by the argument that we + // received from V8, i.e., the first argument passed to compileStreaming (or + // instantiateStreaming). + Local impl = env->wasm_streaming_compilation_impl(); + CHECK(!impl.IsEmpty()); + Local args[] = {obj, info[0]}; + + // Hand control to the JavaScript implementation. It should never throw an + // error, but if it does, we leave it to the calling V8 code to handle that + // gracefully. Otherwise, we assert that the JavaScript function does not + // return anything. + MaybeLocal maybe_ret = + impl->Call(env->context(), info.This(), 2, args); + Local ret; + CHECK_IMPLIES(maybe_ret.ToLocal(&ret), ret->IsUndefined()); +} + +// Called once by JavaScript during initialization. +void SetImplementation(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + env->set_wasm_streaming_compilation_impl(info[0].As()); +} + +void Initialize(Local target, + Local, + Local context, + void*) { + Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "setImplementation", SetImplementation); +} + +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(SetImplementation); +} + +} // namespace wasm_web_api +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(wasm_web_api, node::wasm_web_api::Initialize) +NODE_MODULE_EXTERNAL_REFERENCE(wasm_web_api, + node::wasm_web_api::RegisterExternalReferences) diff --git a/src/node_wasm_web_api.h b/src/node_wasm_web_api.h new file mode 100644 index 00000000000000..9f5fe868167635 --- /dev/null +++ b/src/node_wasm_web_api.h @@ -0,0 +1,54 @@ +#ifndef SRC_NODE_WASM_WEB_API_H_ +#define SRC_NODE_WASM_WEB_API_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "base_object-inl.h" +#include "v8.h" + +namespace node { +namespace wasm_web_api { + +// Wrapper for interacting with a v8::WasmStreaming instance from JavaScript. +class WasmStreamingObject final : public BaseObject { + public: + static v8::Local Initialize(Environment* env); + + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(WasmStreamingObject) + SET_SELF_SIZE(WasmStreamingObject) + + static v8::MaybeLocal Create( + Environment* env, std::shared_ptr streaming); + + private: + WasmStreamingObject(Environment* env, v8::Local object) + : BaseObject(env, object) { + MakeWeak(); + } + + ~WasmStreamingObject() override {} + + private: + static void New(const v8::FunctionCallbackInfo& args); + static void Push(const v8::FunctionCallbackInfo& args); + static void Finish(const v8::FunctionCallbackInfo& args); + static void Abort(const v8::FunctionCallbackInfo& args); + + std::shared_ptr streaming_; + size_t wasm_size_; +}; + +// This is a v8::WasmStreamingCallback implementation that must be passed to +// v8::Isolate::SetWasmStreamingCallback when setting up the isolate in order to +// enable the WebAssembly.(compile|instantiate)Streaming APIs. +void StartStreamingCompilation(const v8::FunctionCallbackInfo& args); + +} // namespace wasm_web_api +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_WASM_WEB_API_H_ diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index f5d946eff1123b..927a8a6f80fb58 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -26,6 +26,8 @@ Last update: - streams: https://github.com/web-platform-tests/wpt/tree/8f60d94439/streams - url: https://github.com/web-platform-tests/wpt/tree/77d54aa9e0/url - user-timing: https://github.com/web-platform-tests/wpt/tree/df24fb604e/user-timing +- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/1dd414c796/wasm/jsapi +- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi - WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/cdd0f03df4/WebCryptoAPI - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 9f81c28c198c48..bde6cf862f6358 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -63,6 +63,14 @@ "commit": "df24fb604e2d40528ac1d1b5dd970e32fc5c2978", "path": "user-timing" }, + "wasm/jsapi": { + "commit": "1dd414c79616489ea021c800eb0375a709e8114e", + "path": "wasm/jsapi" + }, + "wasm/webapi": { + "commit": "fd1b23eeaaf9a01555d4fa29cf79ed11a4c44a50", + "path": "wasm/webapi" + }, "WebCryptoAPI": { "commit": "cdd0f03df41b222aed098fbbb11c6a3cc500a86b", "path": "WebCryptoAPI" diff --git a/test/fixtures/wpt/wasm/jsapi/META.yml b/test/fixtures/wpt/wasm/jsapi/META.yml new file mode 100644 index 00000000000000..cf5525ae1157f7 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/META.yml @@ -0,0 +1 @@ +spec: https://webassembly.github.io/spec/js-api/ diff --git a/test/fixtures/wpt/wasm/jsapi/assertions.js b/test/fixtures/wpt/wasm/jsapi/assertions.js new file mode 100644 index 00000000000000..162f5a9a6b8dcc --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/assertions.js @@ -0,0 +1,100 @@ +function assert_function_name(fn, name, description) { + const propdesc = Object.getOwnPropertyDescriptor(fn, "name"); + assert_equals(typeof propdesc, "object", `${description} should have name property`); + assert_false(propdesc.writable, "writable", `${description} name should not be writable`); + assert_false(propdesc.enumerable, "enumerable", `${description} name should not be enumerable`); + assert_true(propdesc.configurable, "configurable", `${description} name should be configurable`); + assert_equals(propdesc.value, name, `${description} name should be ${name}`); +} + +function assert_function_length(fn, length, description) { + const propdesc = Object.getOwnPropertyDescriptor(fn, "length"); + assert_equals(typeof propdesc, "object", `${description} should have length property`); + assert_false(propdesc.writable, "writable", `${description} length should not be writable`); + assert_false(propdesc.enumerable, "enumerable", `${description} length should not be enumerable`); + assert_true(propdesc.configurable, "configurable", `${description} length should be configurable`); + assert_equals(propdesc.value, length, `${description} length should be ${length}`); +} + +function assert_exported_function(fn, { name, length }, description) { + 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); +} + +function assert_Instance(instance, expected_exports) { + assert_equals(Object.getPrototypeOf(instance), WebAssembly.Instance.prototype, + "prototype"); + assert_true(Object.isExtensible(instance), "extensible"); + + assert_equals(instance.exports, instance.exports, "exports should be idempotent"); + const exports = instance.exports; + + assert_equals(Object.getPrototypeOf(exports), null, "exports prototype"); + assert_false(Object.isExtensible(exports), "extensible exports"); + assert_array_equals(Object.keys(exports), Object.keys(expected_exports), "matching export keys"); + for (const [key, expected] of Object.entries(expected_exports)) { + const property = Object.getOwnPropertyDescriptor(exports, key); + assert_equals(typeof property, "object", `${key} should be present`); + assert_false(property.writable, `${key}: writable`); + assert_true(property.enumerable, `${key}: enumerable`); + assert_false(property.configurable, `${key}: configurable`); + const actual = property.value; + assert_true(Object.isExtensible(actual), `${key}: extensible`); + + switch (expected.kind) { + case "function": + assert_exported_function(actual, expected, `value of ${key}`); + break; + case "global": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype, + `value of ${key}: prototype`); + assert_equals(actual.value, expected.value, `value of ${key}: value`); + assert_equals(actual.valueOf(), expected.value, `value of ${key}: valueOf()`); + break; + case "memory": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Memory.prototype, + `value of ${key}: prototype`); + assert_equals(Object.getPrototypeOf(actual.buffer), ArrayBuffer.prototype, + `value of ${key}: prototype of buffer`); + assert_equals(actual.buffer.byteLength, 0x10000 * expected.size, `value of ${key}: size of buffer`); + const array = new Uint8Array(actual.buffer); + assert_equals(array[0], 0, `value of ${key}: first element of buffer`); + assert_equals(array[array.byteLength - 1], 0, `value of ${key}: last element of buffer`); + break; + case "table": + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype, + `value of ${key}: prototype`); + assert_equals(actual.length, expected.length, `value of ${key}: length of table`); + break; + } + } +} + +function assert_WebAssemblyInstantiatedSource(actual, expected_exports={}) { + assert_equals(Object.getPrototypeOf(actual), Object.prototype, + "Prototype"); + assert_true(Object.isExtensible(actual), "Extensibility"); + + const module = Object.getOwnPropertyDescriptor(actual, "module"); + assert_equals(typeof module, "object", "module: type of descriptor"); + assert_true(module.writable, "module: writable"); + assert_true(module.enumerable, "module: enumerable"); + assert_true(module.configurable, "module: configurable"); + assert_equals(Object.getPrototypeOf(module.value), WebAssembly.Module.prototype, + "module: prototype"); + + const instance = Object.getOwnPropertyDescriptor(actual, "instance"); + assert_equals(typeof instance, "object", "instance: type of descriptor"); + assert_true(instance.writable, "instance: writable"); + assert_true(instance.enumerable, "instance: enumerable"); + assert_true(instance.configurable, "instance: configurable"); + assert_Instance(instance.value, expected_exports); +} diff --git a/test/fixtures/wpt/wasm/jsapi/bad-imports.js b/test/fixtures/wpt/wasm/jsapi/bad-imports.js new file mode 100644 index 00000000000000..786fc650e326b6 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/bad-imports.js @@ -0,0 +1,185 @@ +/** + * `t` should be a function that takes at least three arguments: + * + * - the name of the test; + * - the expected error (to be passed to `assert_throws_js`); + * - a function that takes a `WasmModuleBuilder` and initializes it; + * - (optionally) an options object. + * + * The function is expected to create a test that checks if instantiating a + * module with the result of the `WasmModuleBuilder` and the options object + * (if any) yields the correct error. + */ +function test_bad_imports(t) { + function value_type(type) { + switch (type) { + case "i32": return kWasmI32; + case "i64": return kWasmI64; + case "f32": return kWasmF32; + case "f64": return kWasmF64; + default: throw new TypeError(`Unexpected type ${type}`); + } + } + + for (const value of [null, true, "", Symbol(), 1, 0.1, NaN]) { + t(`Non-object imports argument: ${format_value(value)}`, + TypeError, + builder => {}, + value); + } + + for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN]) { + const imports = { + "module": value, + }; + t(`Non-object module: ${format_value(value)}`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + imports); + } + + t(`Missing imports argument`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }); + + for (const [value, name] of [[undefined, "undefined"], [{}, "empty object"], [{ "module\0": null }, "wrong property"]]) { + t(`Imports argument with missing property: ${name}`, + TypeError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + value); + } + + for (const value of [undefined, null, true, "", Symbol(), 1, 0.1, NaN, {}]) { + t(`Importing a function with an incorrectly-typed value: ${format_value(value)}`, + WebAssembly.LinkError, + builder => { + builder.addImport("module", "fn", kSig_v_v); + }, + { + "module": { + "fn": value, + }, + }); + } + + const nonGlobals = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [{}, "plain object"], + [WebAssembly.Global, "WebAssembly.Global"], + [WebAssembly.Global.prototype, "WebAssembly.Global.prototype"], + [Object.create(WebAssembly.Global.prototype), "Object.create(WebAssembly.Global.prototype)"], + ]; + + for (const type of ["i32", "i64", "f32", "f64"]) { + const extendedNonGlobals = nonGlobals.concat([ + type === "i64" ? [0, "Number"] : [0n, "BigInt"], + [new WebAssembly.Global({value: type === "f32" ? "f64" : "f32"}), "WebAssembly.Global object (wrong value type)"], + ]); + for (const [value, name = format_value(value)] of extendedNonGlobals) { + t(`Importing an ${type} global with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type)); + }, + { + "module": { + "global": value, + }, + }); + } + } + + for (const type of ["i32", "i64", "f32", "f64"]) { + const value = type === "i64" ? 0n : 0; + t(`Importing an ${type} mutable global with a primitive value`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type), true); + }, + { + "module": { + "global": value, + }, + }); + + const global = new WebAssembly.Global({ "value": type }, value); + t(`Importing an ${type} mutable global with an immutable Global object`, + WebAssembly.LinkError, + builder => { + builder.addImportedGlobal("module", "global", value_type(type), true); + }, + { + "module": { + "global": global, + }, + }); + } + + const nonMemories = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [1], + [0.1], + [NaN], + [{}, "plain object"], + [WebAssembly.Memory, "WebAssembly.Memory"], + [WebAssembly.Memory.prototype, "WebAssembly.Memory.prototype"], + [Object.create(WebAssembly.Memory.prototype), "Object.create(WebAssembly.Memory.prototype)"], + [new WebAssembly.Memory({"initial": 256}), "WebAssembly.Memory object (too large)"], + ]; + + for (const [value, name = format_value(value)] of nonMemories) { + t(`Importing memory with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedMemory("module", "memory", 0, 128); + }, + { + "module": { + "memory": value, + }, + }); + } + + const nonTables = [ + [undefined], + [null], + [true], + [""], + [Symbol()], + [1], + [0.1], + [NaN], + [{}, "plain object"], + [WebAssembly.Table, "WebAssembly.Table"], + [WebAssembly.Table.prototype, "WebAssembly.Table.prototype"], + [Object.create(WebAssembly.Table.prototype), "Object.create(WebAssembly.Table.prototype)"], + [new WebAssembly.Table({"element": "anyfunc", "initial": 256}), "WebAssembly.Table object (too large)"], + ]; + + for (const [value, name = format_value(value)] of nonTables) { + t(`Importing table with an incorrectly-typed value: ${name}`, + WebAssembly.LinkError, + builder => { + builder.addImportedTable("module", "table", 0, 128); + }, + { + "module": { + "table": value, + }, + }); + } +} diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js new file mode 100644 index 00000000000000..e94ce11717369f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/compile.any.js @@ -0,0 +1,85 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_Module(module) { + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype, + "Prototype"); + assert_true(Object.isExtensible(module), "Extensibility"); +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly.compile()); +}, "Missing argument"); + +promise_test(t => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + return Promise.all(invalidArguments.map(argument => { + return promise_rejects_js(t, TypeError, WebAssembly.compile(argument), + `compile(${format_value(argument)})`); + })); +}, "Invalid arguments"); + +promise_test(() => { + const fn = WebAssembly.compile; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + return Promise.all(thisValues.map(thisValue => { + return fn.call(thisValue, emptyModuleBinary).then(assert_Module); + })); +}, "Branding"); + +test(() => { + const promise = WebAssembly.compile(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(promise), Promise.prototype, "prototype"); + assert_true(Object.isExtensible(promise), "extensibility"); +}, "Promise type"); + +promise_test(t => { + const buffer = new Uint8Array(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.compile(buffer)); +}, "Empty buffer"); + +promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.compile(buffer)); +}, "Invalid code"); + +promise_test(() => { + return WebAssembly.compile(emptyModuleBinary).then(assert_Module); +}, "Result type"); + +promise_test(() => { + return WebAssembly.compile(emptyModuleBinary, {}).then(assert_Module); +}, "Stray argument"); + +promise_test(() => { + const buffer = new WasmModuleBuilder().toBuffer(); + assert_equals(buffer[0], 0); + const promise = WebAssembly.compile(buffer); + buffer[0] = 1; + return promise.then(assert_Module); +}, "Changing the buffer"); diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js new file mode 100644 index 00000000000000..30252bd6eeb3ab --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate-bad-imports.any.js @@ -0,0 +1,22 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...arguments) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + return promise_rejects_js(t, error, WebAssembly.instantiate(module, ...arguments)); + }, `WebAssembly.instantiate(module): ${name}`); +}); + +test_bad_imports((name, error, build, ...arguments) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + return promise_rejects_js(t, error, WebAssembly.instantiate(buffer, ...arguments)); + }, `WebAssembly.instantiate(buffer): ${name}`); +}); diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js new file mode 100644 index 00000000000000..8152f3a56f3f43 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/instantiate.any.js @@ -0,0 +1,152 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly.instantiate()); +}, "Missing arguments"); + +promise_test(() => { + const fn = WebAssembly.instantiate; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + return Promise.all(thisValues.map(thisValue => { + return fn.call(thisValue, emptyModuleBinary).then(assert_WebAssemblyInstantiatedSource); + })); +}, "Branding"); + +promise_test(t => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + return Promise.all(invalidArguments.map(argument => { + return promise_rejects_js(t, TypeError, WebAssembly.instantiate(argument), + `instantiate(${format_value(argument)})`); + })); +}, "Invalid arguments"); + +test(() => { + const promise = WebAssembly.instantiate(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(promise), Promise.prototype, "prototype"); + assert_true(Object.isExtensible(promise), "extensibility"); +}, "Promise type"); + +for (const [name, fn] of instanceTestFactory) { + promise_test(() => { + const { buffer, args, exports, verify } = fn(); + return WebAssembly.instantiate(buffer, ...args).then(result => { + assert_WebAssemblyInstantiatedSource(result, exports); + verify(result.instance); + }); + }, `${name}: BufferSource argument`); + + promise_test(() => { + const { buffer, args, exports, verify } = fn(); + const module = new WebAssembly.Module(buffer); + return WebAssembly.instantiate(module, ...args).then(instance => { + assert_Instance(instance, exports); + verify(instance); + }); + }, `${name}: Module argument`); +} + +promise_test(() => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiate(buffer, imports); + assert_array_equals(order, []); + return p.then(result => { + assert_WebAssemblyInstantiatedSource(result); + assert_array_equals(order, expected); + }); +}, "Synchronous options handling: Buffer argument"); + +promise_test(() => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiate(module, imports); + assert_array_equals(order, expected); + return p.then(instance => assert_Instance(instance, {})); +}, "Synchronous options handling: Module argument"); + +promise_test(t => { + const buffer = new Uint8Array(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.instantiate(buffer)); +}, "Empty buffer"); + +promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly.instantiate(buffer)); +}, "Invalid code"); + +promise_test(() => { + const buffer = new WasmModuleBuilder().toBuffer(); + assert_equals(buffer[0], 0); + const promise = WebAssembly.instantiate(buffer); + buffer[0] = 1; + return promise.then(assert_WebAssemblyInstantiatedSource); +}, "Changing the buffer"); diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js new file mode 100644 index 00000000000000..4b06d1da3c49b9 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/multi-value.any.js @@ -0,0 +1,149 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +const type_if_fi = makeSig([kWasmF64, kWasmI32], [kWasmI32, kWasmF64]); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("swap", type_if_fi) + .addBody([ + kExprLocalGet, 1, + kExprLocalGet, 0, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const result = await WebAssembly.instantiate(buffer); + const swapped = result.instance.exports.swap(4.2, 7); + assert_true(Array.isArray(swapped)); + assert_equals(Object.getPrototypeOf(swapped), Array.prototype); + assert_array_equals(swapped, [7, 4.2]); +}, "multiple return values from wasm to js"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + const swap = builder + .addFunction("swap", type_if_fi) + .addBody([ + kExprLocalGet, 1, + kExprLocalGet, 0, + kExprReturn, + ]); + builder + .addFunction("callswap", kSig_i_v) + .addBody([ + ...wasmF64Const(4.2), + ...wasmI32Const(7), + kExprCallFunction, swap.index, + kExprDrop, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const result = await WebAssembly.instantiate(buffer); + const swapped = result.instance.exports.callswap(); + assert_equals(swapped, 7); +}, "multiple return values inside wasm"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", type_if_fi); + builder + .addFunction("callfn", kSig_i_v) + .addBody([ + ...wasmF64Const(4.2), + ...wasmI32Const(7), + kExprCallFunction, fnIndex, + kExprDrop, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const actual = []; + const imports = { + "module": { + fn(f32, i32) { + assert_equals(f32, 4.2); + assert_equals(i32, 7); + const result = [2, 7.3]; + let i = 0; + return { + get [Symbol.iterator]() { + actual.push("@@iterator getter"); + return function iterator() { + actual.push("@@iterator call"); + return { + get next() { + actual.push("next getter"); + return function next(...args) { + assert_array_equals(args, []); + let j = ++i; + actual.push(`next call ${j}`); + if (j > result.length) { + return { + get done() { + actual.push(`done call ${j}`); + return true; + } + }; + } + return { + get done() { + actual.push(`done call ${j}`); + return false; + }, + get value() { + actual.push(`value call ${j}`); + return { + get valueOf() { + actual.push(`valueOf get ${j}`); + return function() { + actual.push(`valueOf call ${j}`); + return result[j - 1]; + }; + } + }; + } + }; + }; + } + }; + } + }, + }; + }, + } + }; + + const { instance } = await WebAssembly.instantiate(buffer, imports); + const result = instance.exports.callfn(); + assert_equals(result, 2); + assert_array_equals(actual, [ + "@@iterator getter", + "@@iterator call", + "next getter", + "next call 1", + "done call 1", + "value call 1", + "next call 2", + "done call 2", + "value call 2", + "next call 3", + "done call 3", + "valueOf get 1", + "valueOf call 1", + "valueOf get 2", + "valueOf call 2", + ]); +}, "multiple return values from js to wasm"); diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js new file mode 100644 index 00000000000000..c6d2cdaf662e8b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/toStringTag.any.js @@ -0,0 +1,42 @@ +// META: global=window,dedicatedworker,jsshell + +"use strict"; +// https://webidl.spec.whatwg.org/#es-namespaces +// https://webassembly.github.io/spec/js-api/#namespacedef-webassembly + +test(() => { + assert_own_property(WebAssembly, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly", "value"); + assert_equals(propDesc.writable, false, "writable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.configurable, true, "configurable"); +}, "@@toStringTag exists on the namespace object with the appropriate descriptor"); + +test(() => { + assert_equals(WebAssembly.toString(), "[object WebAssembly]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object WebAssembly]"); +}, "Object.prototype.toString applied to the namespace object"); + +test(t => { + assert_own_property(WebAssembly, Symbol.toStringTag, "Precondition: @@toStringTag on the namespace object"); + t.add_cleanup(() => { + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "WebAssembly" }); + }); + + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "Test" }); + assert_equals(WebAssembly.toString(), "[object Test]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object Test]"); +}, "Object.prototype.toString applied after modifying the namespace object's @@toStringTag"); + +test(t => { + assert_own_property(WebAssembly, Symbol.toStringTag, "Precondition: @@toStringTag on the namespace object"); + t.add_cleanup(() => { + Object.defineProperty(WebAssembly, Symbol.toStringTag, { value: "WebAssembly" }); + }); + + assert_true(delete WebAssembly[Symbol.toStringTag]); + assert_equals(WebAssembly.toString(), "[object Object]"); + assert_equals(Object.prototype.toString.call(WebAssembly), "[object Object]"); +}, "Object.prototype.toString applied after deleting @@toStringTag"); diff --git a/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js b/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js new file mode 100644 index 00000000000000..8b4f4582ab2987 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/constructor/validate.any.js @@ -0,0 +1,99 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.validate()); +}, "Missing argument"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.validate(argument), + `validate(${format_value(argument)})`); + } +}, "Invalid arguments"); + +test(() => { + const fn = WebAssembly.validate; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly, + ]; + for (const thisValue of thisValues) { + assert_true(fn.call(thisValue, emptyModuleBinary), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +const modules = [ + // Incomplete header. + [[], false], + [[0x00], false], + [[0x00, 0x61], false], + [[0x00, 0x61, 0x73], false], + [[0x00, 0x61, 0x73, 0x6d], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00], false], + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00], false], + + // Complete header. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], true], + + // Invalid version. + [[0x00, 0x61, 0x73, 0x6d, 0x00, 0x00, 0x00, 0x00], false], + [[0x00, 0x61, 0x73, 0x6d, 0x02, 0x00, 0x00, 0x00], false], + + // Nameless custom section. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00], false], + + // Custom section with empty name. + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00], true], + + // Custom section with name "a". + [[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x61], true], +]; +const bufferTypes = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, +]; +for (const [module, expected] of modules) { + const name = module.map(n => n.toString(16)).join(" "); + for (const bufferType of bufferTypes) { + if (module.length % bufferType.BYTES_PER_ELEMENT === 0) { + test(() => { + const bytes = new Uint8Array(module); + const moduleBuffer = new bufferType(bytes.buffer); + assert_equals(WebAssembly.validate(moduleBuffer), expected); + }, `Validating module [${name}] in ${bufferType.name}`); + } + } +} + +test(() => { + assert_true(WebAssembly.validate(emptyModuleBinary, {})); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js b/test/fixtures/wpt/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js new file mode 100644 index 00000000000000..572db0c01b620d --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/error-interfaces-no-symbol-tostringtag.js @@ -0,0 +1,13 @@ +// META: global=jsshell + +test(() => { + assert_not_own_property(WebAssembly.CompileError.prototype, Symbol.toStringTag); +}, "WebAssembly.CompileError"); + +test(() => { + assert_not_own_property(WebAssembly.LinkError.prototype, Symbol.toStringTag); +}, "WebAssembly.LinkError"); + +test(() => { + assert_not_own_property(WebAssembly.RuntimeError.prototype, Symbol.toStringTag); +}, "WebAssembly.RuntimeError"); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js new file mode 100644 index 00000000000000..9ddebae0e968a2 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/basic.tentative.any.js @@ -0,0 +1,121 @@ +// META: global=window,worker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_throws_wasm(fn, message) { + try { + fn(); + assert_not_reached(`expected to throw with ${message}`); + } catch (e) { + assert_true(e instanceof WebAssembly.Exception, `Error should be a WebAssembly.Exception with ${message}`); + } +} + +promise_test(async () => { + const kWasmAnyRef = 0x6f; + const kSig_v_r = makeSig([kWasmAnyRef], []); + const builder = new WasmModuleBuilder(); + const except = builder.addException(kSig_v_r); + builder.addFunction("throw_param", kSig_v_r) + .addBody([ + kExprLocalGet, 0, + kExprThrow, except, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + const values = [ + undefined, + null, + true, + false, + "test", + Symbol(), + 0, + 1, + 4.2, + NaN, + Infinity, + {}, + () => {}, + ]; + for (const v of values) { + assert_throws_wasm(() => instance.exports.throw_param(v), String(v)); + } +}, "Wasm function throws argument"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const except = builder.addException(kSig_v_a); + builder.addFunction("throw_null", kSig_v_v) + .addBody([ + kExprRefNull, kWasmAnyFunc, + kExprThrow, except, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_null()); +}, "Wasm function throws null"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const except = builder.addException(kSig_v_i); + builder.addFunction("throw_int", kSig_v_v) + .addBody([ + ...wasmI32Const(7), + kExprThrow, except, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_int()); +}, "Wasm function throws integer"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + const except = builder.addException(kSig_v_r); + builder.addFunction("catch_exception", kSig_r_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, fnIndex, + kExprCatch, except, + kExprReturn, + kExprEnd, + kExprRefNull, kWasmAnyRef, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_exception()); +}, "Imported JS function throws"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + builder.addFunction("catch_and_rethrow", kSig_r_v) + .addBody([ + kExprTry, kWasmStmt, + kExprCallFunction, fnIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd, + kExprRefNull, kWasmAnyRef, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_and_rethrow()); +}, "Imported JS function throws, Wasm catches and rethrows"); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js new file mode 100644 index 00000000000000..0fd47b455e023c --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/constructor.tentative.any.js @@ -0,0 +1,62 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +test(() => { + assert_function_name( + WebAssembly.Exception, + "Exception", + "WebAssembly.Exception" + ); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Exception, 1, "WebAssembly.Exception"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Exception()); +}, "No arguments"); + +test(() => { + const argument = new WebAssembly.Tag({ parameters: [] }); + assert_throws_js(TypeError, () => WebAssembly.Exception(argument)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js( + TypeError, + () => new WebAssembly.Exception(invalidArgument), + `new Exception(${format_value(invalidArgument)})` + ); + } +}, "Invalid descriptor argument"); + +test(() => { + const typesAndArgs = [ + ["i32", 123n], + ["i32", Symbol()], + ["f32", 123n], + ["f64", 123n], + ["i64", undefined], + ]; + for (const typeAndArg of typesAndArgs) { + const exn = new WebAssembly.Tag({ parameters: [typeAndArg[0]] }); + assert_throws_js( + TypeError, + () => new WebAssembly.Exception(exn, typeAndArg[1]) + ); + } +}, "Invalid exception argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js new file mode 100644 index 00000000000000..ecd2fbd42fd18a --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/getArg.tentative.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(TypeError, () => exn.getArg()); + assert_throws_js(TypeError, () => exn.getArg(tag)); +}, "Missing arguments"); + +test(() => { + const invalidValues = [undefined, null, true, "", Symbol(), 1, {}]; + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (argument of invalidValues) { + assert_throws_js(TypeError, () => exn.getArg(argument, 0)); + } +}, "Invalid exception argument"); + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(TypeError, () => exn.getArg(tag, 1)); +}, "Index out of bounds"); + +test(() => { + const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { + valueOf() { + return 0x100000000; + }, + }, + ]; + + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (const value of outOfRangeValues) { + assert_throws_js(TypeError, () => exn.getArg(tag, value)); + } +}, "Getting out-of-range argument"); + +test(() => { + const tag = new WebAssembly.Tag({ parameters: ["i32"] }); + const exn = new WebAssembly.Exception(tag, [42]); + assert_equals(exn.getArg(tag, 0), 42); +}, "getArg"); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js new file mode 100644 index 00000000000000..e28a88a3c5fdcf --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/is.tentative.any.js @@ -0,0 +1,25 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + assert_throws_js(TypeError, () => exn.is()); +}, "Missing arguments"); + +test(() => { + const invalidValues = [undefined, null, true, "", Symbol(), 1, {}]; + const tag = new WebAssembly.Tag({ parameters: [] }); + const exn = new WebAssembly.Exception(tag, []); + for (argument of invalidValues) { + assert_throws_js(TypeError, () => exn.is(argument)); + } +}, "Invalid exception argument"); + +test(() => { + const tag1 = new WebAssembly.Tag({ parameters: ["i32"] }); + const tag2 = new WebAssembly.Tag({ parameters: ["i32"] }); + const exn = new WebAssembly.Exception(tag1, [42]); + assert_true(exn.is(tag1)); + assert_false(exn.is(tag2)); +}, "is"); diff --git a/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js new file mode 100644 index 00000000000000..52635186c762fc --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/exception/toString.tentative.any.js @@ -0,0 +1,21 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { parameters: [] }; + const tag = new WebAssembly.Tag(argument); + const exception = new WebAssembly.Exception(tag, []); + assert_class_string(exception, "WebAssembly.Exception"); +}, "Object.prototype.toString on an Exception"); + +test(() => { + assert_own_property(WebAssembly.Exception.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor( + WebAssembly.Exception.prototype, + Symbol.toStringTag + ); + assert_equals(propDesc.value, "WebAssembly.Exception", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js new file mode 100644 index 00000000000000..626cd13c9f0095 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/function/call.tentative.any.js @@ -0,0 +1,16 @@ +// 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") + +test(() => { + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_throws_js(TypeError, () => new fun(1, 2)); +}, "test constructing function"); diff --git a/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js new file mode 100644 index 00000000000000..636aeca4dc1fa0 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/function/constructor.tentative.any.js @@ -0,0 +1,65 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function addxy(x, y) { + return x + y +} + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_function_name(WebAssembly.Function, "Function", "WebAssembly.Function"); +}, "name"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_function_length(WebAssembly.Function, 2, "WebAssembly.Function"); +}, "length"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function()); + const argument = {parameters: [], results: []}; + assert_throws_js(TypeError, () => new WebAssembly.Function(argument)); +}, "Too few arguments"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + const arguments = [{parameters: ["i32", "i32"], results: ["i32"]}, addxy]; + assert_throws_js(TypeError, () => WebAssembly.Function(...arguments)); +}, "Calling"); + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + var fun = new WebAssembly.Function({parameters: ["i32", "i32"], results: ["i32"]}, addxy); + assert_true(fun instanceof WebAssembly.Function) +}, "construct with JS function") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: []}, addxy)) +}, "fail with missing results") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({results: []}, addxy)) +}, "fail with missing parameters") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [1], results: [true]}, addxy)) +}, "fail with non-string parameters & results") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: ["invalid"], results: ["invalid"]}, addxy)) +}, "fail with non-existent parameter and result type") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, 72)) +}, "fail with non-function object") + +test(() => { + assert_implements(WebAssembly.Function, "WebAssembly.Function is not implemented"); + assert_throws_js(TypeError, () => new WebAssembly.Function({parameters: [], results: []}, {})) +}, "fail to construct with non-callable object") diff --git a/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/table.tentative.any.js new file mode 100644 index 00000000000000..d7d0d86e3b6a88 --- /dev/null +++ b/test/fixtures/wpt/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/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/function/type.tentative.any.js new file mode 100644 index 00000000000000..e01a23a9e4339e --- /dev/null +++ b/test/fixtures/wpt/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") diff --git a/test/fixtures/wpt/wasm/jsapi/functions/entry-different-function-realm.html b/test/fixtures/wpt/wasm/jsapi/functions/entry-different-function-realm.html new file mode 100644 index 00000000000000..3af3dd924fb435 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/entry-different-function-realm.html @@ -0,0 +1,45 @@ + + +Entry settings object for host functions when the function realm is different from the test realm + + + + + + + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/entry.html b/test/fixtures/wpt/wasm/jsapi/functions/entry.html new file mode 100644 index 00000000000000..15018074491054 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/entry.html @@ -0,0 +1,43 @@ + + +Entry settings object for host functions + + + + + + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/helper.js b/test/fixtures/wpt/wasm/jsapi/functions/helper.js new file mode 100644 index 00000000000000..487791c69ad430 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/helper.js @@ -0,0 +1,12 @@ +function call_later(f) { + const builder = new WasmModuleBuilder(); + const functionIndex = builder.addImport("module", "imported", kSig_v_v); + builder.addStart(functionIndex); + const buffer = builder.toBuffer(); + + WebAssembly.instantiate(buffer, { + "module": { + "imported": f, + } + }); +} diff --git a/test/fixtures/wpt/wasm/jsapi/functions/incumbent.html b/test/fixtures/wpt/wasm/jsapi/functions/incumbent.html new file mode 100644 index 00000000000000..cb2763297709a8 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/incumbent.html @@ -0,0 +1,54 @@ + + +Incumbent settings object for host functions + + + + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/README.md b/test/fixtures/wpt/wasm/jsapi/functions/resources/README.md new file mode 100644 index 00000000000000..a89258a4e01267 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/README.md @@ -0,0 +1,5 @@ +A couple notes about the files scattered in this `resources/` directory: + +* The nested directory structure is necessary here so that relative URL resolution can be tested; we need different sub-paths for each document. + +* The semi-duplicate `window-to-open.html`s scattered throughout are present because Firefox, at least, does not fire `Window` `load` events for 404s, so we want to ensure that no matter which global is used, `window`'s `load` event is hit and our tests can proceed. diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/current/current.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/current/current.html new file mode 100644 index 00000000000000..63d9c437fc5683 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/current/current.html @@ -0,0 +1,4 @@ + + +Current page used as a test helper + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/current/resources/window-to-open.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/current/resources/window-to-open.html new file mode 100644 index 00000000000000..1bc4cca9a3920f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/current/resources/window-to-open.html @@ -0,0 +1,3 @@ + + +If the current settings object is used this page will be opened diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/entry-incumbent.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/entry-incumbent.html new file mode 100644 index 00000000000000..6b210563e99bc2 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/entry-incumbent.html @@ -0,0 +1,15 @@ + + +Incumbent page used as a test helper + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/function/function.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/function/function.html new file mode 100644 index 00000000000000..979b902eaa0e17 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/function/function.html @@ -0,0 +1,3 @@ + + +Realm for a host function used as a test helper diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/function/resources/window-to-open.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/function/resources/window-to-open.html new file mode 100644 index 00000000000000..3928c1f8aa9e96 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/function/resources/window-to-open.html @@ -0,0 +1,3 @@ + + +If the function's settings object is used this page will be opened diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/incumbent-incumbent.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/incumbent-incumbent.html new file mode 100644 index 00000000000000..5e84f65a084e68 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/incumbent-incumbent.html @@ -0,0 +1,24 @@ + + +Incumbent page used as a test helper + + + + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/relevant.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/relevant.html new file mode 100644 index 00000000000000..06df91c23741f5 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/relevant.html @@ -0,0 +1,14 @@ + + +Relevant page used as a test helper + + diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html new file mode 100644 index 00000000000000..4138b5a084409d --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/relevant/resources/window-to-open.html @@ -0,0 +1,3 @@ + + +If the relevant settings object is used this page will be opened diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/resources/window-to-open.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/resources/window-to-open.html new file mode 100644 index 00000000000000..7743b9b578201e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/resources/window-to-open.html @@ -0,0 +1,3 @@ + + +If the incumbent settings object is used this page will be opened diff --git a/test/fixtures/wpt/wasm/jsapi/functions/resources/window-to-open.html b/test/fixtures/wpt/wasm/jsapi/functions/resources/window-to-open.html new file mode 100644 index 00000000000000..ce357937f5e4f9 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/functions/resources/window-to-open.html @@ -0,0 +1,3 @@ + + +If the entry settings object is used this page will be opened diff --git a/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js new file mode 100644 index 00000000000000..f536f5d7b5df6c --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/constructor.any.js @@ -0,0 +1,166 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_Global(actual, expected) { + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Global.prototype, + "prototype"); + assert_true(Object.isExtensible(actual), "extensible"); + + assert_equals(actual.value, expected, "value"); + assert_equals(actual.valueOf(), expected, "valueOf"); +} + +test(() => { + assert_function_name(WebAssembly.Global, "Global", "WebAssembly.Global"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Global, 1, "WebAssembly.Global"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Global()); +}, "No arguments"); + +test(() => { + const argument = { "value": "i32" }; + assert_throws_js(TypeError, () => WebAssembly.Global(argument)); +}, "Calling"); + +test(() => { + const order = []; + + new WebAssembly.Global({ + get value() { + order.push("descriptor value"); + return { + toString() { + order.push("descriptor value toString"); + return "f64"; + }, + }; + }, + + get mutable() { + order.push("descriptor mutable"); + return false; + }, + }, { + valueOf() { + order.push("value valueOf()"); + } + }); + + assert_array_equals(order, [ + "descriptor mutable", + "descriptor value", + "descriptor value toString", + "value valueOf()", + ]); +}, "Order of evaluation"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Global(invalidArgument), + `new Global(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { value }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument)); + } +}, "Invalid type argument"); + +test(() => { + const argument = { "value": "i64" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0n); +}, "i64 with default"); + +for (const type of ["i32", "f32", "f64"]) { + test(() => { + const argument = { "value": type }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0); + }, `Default value for type ${type}`); + + const valueArguments = [ + [undefined, 0], + [null, 0], + [true, 1], + [false, 0], + [2, 2], + ["3", 3], + [{ toString() { return "5" } }, 5, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8, "object with valueOf returning string"], + [{ toString() { return 6 } }, 6, "object with toString returning number"], + [{ valueOf() { return 9 } }, 9, "object with valueOf returning number"], + ]; + for (const [value, expected, name = format_value(value)] of valueArguments) { + test(() => { + const argument = { "value": type }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type ${type}`); + } + + test(() => { + const argument = { "value": type }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, 0n)); + }, `BigInt value for type ${type}`); +} + +const valueArguments = [ + [undefined, 0n], + [true, 1n], + [false, 0n], + ["3", 3n], + [123n, 123n], + [{ toString() { return "5" } }, 5n, "object with toString returning string"], + [{ valueOf() { return "8" } }, 8n, "object with valueOf returning string"], + [{ toString() { return 6n } }, 6n, "object with toString returning bigint"], + [{ valueOf() { return 9n } }, 9n, "object with valueOf returning bigint"], +]; +for (const [value, expected, name = format_value(value)] of valueArguments) { + test(() => { + const argument = { "value": "i64" }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type i64`); +} + +const invalidBigints = [ + null, + 666, + { toString() { return 5 } }, + { valueOf() { return 8 } }, + Symbol(), +]; +for (const invalidBigint of invalidBigints) { + test(() => { + var argument = { "value": "i64" }; + assert_throws_js(TypeError, () => new WebAssembly.Global(argument, invalidBigint)); + }, `Pass non-bigint as i64 Global value: ${format_value(invalidBigint)}`); +} + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument, 0, {}); + assert_Global(global, 0); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/global/toString.any.js b/test/fixtures/wpt/wasm/jsapi/global/toString.any.js new file mode 100644 index 00000000000000..359c4273b5bd78 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument); + assert_class_string(global, "WebAssembly.Global"); +}, "Object.prototype.toString on an Global"); + +test(() => { + assert_own_property(WebAssembly.Global.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Global", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js new file mode 100644 index 00000000000000..173af647f27dc7 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/type.tentative.any.js @@ -0,0 +1,65 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); + + assert_equals(globaltype.value, argument.value); + assert_equals(globaltype.mutable, argument.mutable); +} + +test(() => { + assert_type({ "value": "i32", "mutable": true}); +}, "i32, mutable"); + +test(() => { + assert_type({ "value": "i32", "mutable": false}); +}, "i32, immutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": true}); +}, "i64, mutable"); + +test(() => { + assert_type({ "value": "i64", "mutable": false}); +}, "i64, immutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": true}); +}, "f32, mutable"); + +test(() => { + assert_type({ "value": "f32", "mutable": false}); +}, "f32, immutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": true}); +}, "f64, mutable"); + +test(() => { + assert_type({ "value": "f64", "mutable": false}); +}, "f64, immutable"); + +test(() => { + assert_type({"value": "externref", "mutable": true}) +}, "externref, mutable") + +test(() => { + assert_type({"value": "externref", "mutable": false}) +}, "externref, immutable") + +test(() => { + assert_type({"value": "anyfunc", "mutable": true}) +}, "anyfunc, mutable") + +test(() => { + assert_type({"value": "anyfunc", "mutable": false}) +}, "anyfunc, immutable") + +test(() => { + const myglobal = new WebAssembly.Global({"value": "i32", "mutable": true}); + const propertyNames = Object.getOwnPropertyNames(myglobal.type()); + assert_equals(propertyNames[0], "mutable"); + assert_equals(propertyNames[1], "value"); +}, "key ordering"); diff --git a/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js b/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js new file mode 100644 index 00000000000000..f95b7ca9e3f0d5 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/value-get-set.any.js @@ -0,0 +1,152 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Global, + WebAssembly.Global.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `getter with this=${format_value(thisValue)}`); + assert_throws_js(TypeError, () => setter.call(thisValue, 1), `setter with this=${format_value(thisValue)}`); + } +}, "Branding"); + +for (const type of ["i32", "i64", "f32", "f64"]) { + const [initial, value, invalid] = type === "i64" ? [0n, 1n, 2] : [0, 1, 2n]; + const immutableOptions = [ + [{}, "missing"], + [{ "mutable": undefined }, "undefined"], + [{ "mutable": null }, "null"], + [{ "mutable": false }, "false"], + [{ "mutable": "" }, "empty string"], + [{ "mutable": 0 }, "zero"], + ]; + for (const [opts, name] of immutableOptions) { + test(() => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + assert_throws_js(TypeError, () => global.value = value); + + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} (${name})`); + + test(t => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + const value = { + valueOf: t.unreached_func("should not call valueOf"), + toString: t.unreached_func("should not call toString"), + }; + assert_throws_js(TypeError, () => global.value = value); + + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} with ToNumber side-effects (${name})`); + } + + const mutableOptions = [ + [{ "mutable": true }, "true"], + [{ "mutable": 1 }, "one"], + [{ "mutable": "x" }, "string"], + [Object.create({ "mutable": true }), "true on prototype"], + ]; + for (const [opts, name] of mutableOptions) { + test(() => { + opts.value = type; + const global = new WebAssembly.Global(opts); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + + global.value = value; + + assert_throws_js(TypeError, () => global.value = invalid); + + assert_equals(global.value, value, "post-set value"); + assert_equals(global.valueOf(), value, "post-set valueOf"); + }, `Mutable ${type} (${name})`); + } +} + +test(() => { + const argument = { "value": "i64", "mutable": true }; + const global = new WebAssembly.Global(argument); + + assert_equals(global.value, 0n, "initial value using ToJSValue"); + + const valid = [ + [123n, 123n], + [2n ** 63n, - (2n ** 63n)], + [true, 1n], + [false, 0n], + ["456", 456n], + ]; + for (const [input, output] of valid) { + global.value = input; + assert_equals(global.valueOf(), output, "post-set valueOf"); + } + + const invalid = [ + undefined, + null, + 0, + 1, + 4.2, + Symbol(), + ]; + for (const input of invalid) { + assert_throws_js(TypeError, () => global.value = input); + } +}, "i64 mutability"); + +test(() => { + const argument = { "value": "i32", "mutable": true }; + const global = new WebAssembly.Global(argument); + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + assert_throws_js(TypeError, () => setter.call(global)); +}, "Calling setter without argument"); + +test(() => { + const argument = { "value": "i32", "mutable": true }; + const global = new WebAssembly.Global(argument); + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Global.prototype, "value"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + const setter = desc.set; + assert_equals(typeof setter, "function"); + + assert_equals(getter.call(global, {}), 0); + assert_equals(setter.call(global, 1, {}), undefined); + assert_equals(global.value, 1); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js b/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js new file mode 100644 index 00000000000000..0695a5a61fbc6e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/global/valueOf.any.js @@ -0,0 +1,28 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { "value": "i32" }; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Global, + WebAssembly.Global.prototype, + ]; + + const fn = WebAssembly.Global.prototype.valueOf; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "value": "i32" }; + const global = new WebAssembly.Global(argument, 0); + assert_equals(global.valueOf({}), 0); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/idlharness.any.js b/test/fixtures/wpt/wasm/jsapi/idlharness.any.js new file mode 100644 index 00000000000000..98713d4bf6e43a --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/idlharness.any.js @@ -0,0 +1,22 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: script=../resources/load_wasm.js + +'use strict'; + +// https://webassembly.github.io/spec/js-api/ + +idl_test( + ['wasm-js-api'], + [], + async idl_array => { + self.mod = await createWasmModule(); + self.instance = new WebAssembly.Instance(self.mod); + + idl_array.add_objects({ + Memory: [new WebAssembly.Memory({initial: 1024})], + Module: [self.mod], + Instance: [self.instance], + }); + } +); diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js new file mode 100644 index 00000000000000..e4a5abb8eb2169 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor-bad-imports.any.js @@ -0,0 +1,13 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...arguments) => { + test(() => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + assert_throws_js(error, () => new WebAssembly.Instance(module, ...arguments)); + }, `new WebAssembly.Instance(module): ${name}`); +}); diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js new file mode 100644 index 00000000000000..1aa4739b6294d0 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor-caching.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +function getExports() { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + builder.addGlobal(kWasmI32, false).exportAs("global"); + builder.addMemory(4, 8, true); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module); + return instance.exports; +} + +test(() => { + const exports = getExports(); + + const builder = new WasmModuleBuilder(); + const functionIndex = builder.addImport("module", "imported", kSig_v_d); + builder.addExport("exportedFunction", functionIndex); + + const globalIndex = builder.addImportedGlobal("module", "global", kWasmI32); + builder.addExportOfKind("exportedGlobal", kExternalGlobal, globalIndex); + + builder.addImportedMemory("module", "memory", 4); + builder.exportMemoryAs("exportedMemory"); + + const tableIndex = builder.addImportedTable("module", "table", 1); + builder.addExportOfKind("exportedTable", kExternalTable, tableIndex); + + const buffer = builder.toBuffer(); + + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, { + "module": { + "imported": exports.fn, + "global": exports.global, + "memory": exports.memory, + "table": exports.table, + } + }); + + assert_equals(instance.exports.exportedFunction, exports.fn); + assert_equals(instance.exports.exportedGlobal, exports.global); + assert_equals(instance.exports.exportedMemory, exports.memory); + assert_equals(instance.exports.exportedTable, exports.table); +}); diff --git a/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js new file mode 100644 index 00000000000000..26390ebd2cdb2e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/constructor.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_function_name(WebAssembly.Instance, "Instance", "WebAssembly.Instance"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Instance, 1, "WebAssembly.Instance"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Instance()); +}, "No arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => new WebAssembly.Instance(argument), + `new Instance(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_throws_js(TypeError, () => WebAssembly.Instance(module)); +}, "Calling"); + +for (const [name, fn] of instanceTestFactory) { + test(() => { + const { buffer, args, exports, verify } = fn(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, ...args); + assert_Instance(instance, exports); + verify(instance); + }, name); +} diff --git a/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js b/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js new file mode 100644 index 00000000000000..6dcfbcee950d87 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/exports.any.js @@ -0,0 +1,66 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Instance, + WebAssembly.Instance.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, "exports"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, "exports"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(instance, {}), exports); +}, "Stray argument"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + instance.exports = {}; + assert_equals(instance.exports, exports, "Should not change the exports"); +}, "Setting (sloppy mode)"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + const exports = instance.exports; + assert_throws_js(TypeError, () => { + "use strict"; + instance.exports = {}; + }); + assert_equals(instance.exports, exports, "Should not change the exports"); +}, "Setting (strict mode)"); diff --git a/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js b/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js new file mode 100644 index 00000000000000..547a9ca8295f5b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instance/toString.any.js @@ -0,0 +1,19 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const emptyModuleBinary = new WasmModuleBuilder().toBuffer(); + const module = new WebAssembly.Module(emptyModuleBinary); + const instance = new WebAssembly.Instance(module); + assert_class_string(instance, "WebAssembly.Instance"); +}, "Object.prototype.toString on an Instance"); + +test(() => { + assert_own_property(WebAssembly.Instance.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Instance.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Instance", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js b/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js new file mode 100644 index 00000000000000..ac468947ec22e2 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/instanceTestFactory.js @@ -0,0 +1,761 @@ +const instanceTestFactory = [ + [ + "Empty module without imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "Empty module with undefined imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [undefined], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "Empty module with empty imports argument", + function() { + return { + buffer: emptyModuleBinary, + args: [{}], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "getter order for imports object", + function() { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global1", kWasmI32); + builder.addImportedGlobal("module2", "global3", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedGlobal("module", "global2", kWasmI32); + const buffer = builder.toBuffer(); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global1() { + order.push("global1 getter"); + return 0; + }, + get global2() { + order.push("global2 getter"); + return 0; + }, + get memory() { + order.push("memory getter"); + return new WebAssembly.Memory({ "initial": 64, maximum: 128 }); + }, + } + }, + get module2() { + order.push("module2 getter"); + return { + get global3() { + order.push("global3 getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global1 getter", + "module2 getter", + "global3 getter", + "module getter", + "memory getter", + "module getter", + "global2 getter", + ]; + return { + buffer, + args: [imports], + exports: {}, + verify: () => assert_array_equals(order, expected), + }; + } + ], + + [ + "imports", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("module", "fn", kSig_v_v); + builder.addImportedGlobal("module", "global", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedTable("module", "table", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "module": { + "fn": function() {}, + "global": 0, + "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }), + "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }), + }, + get "module2"() { + assert_unreached("Should not get modules that are not imported"); + }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "imports with empty module names", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "fn", kSig_v_v); + builder.addImportedGlobal("", "global", kWasmI32); + builder.addImportedMemory("", "memory", 0, 128); + builder.addImportedTable("", "table", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "": { + "fn": function() {}, + "global": 0, + "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }), + "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }), + }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "imports with empty names", + function() { + const builder = new WasmModuleBuilder(); + + builder.addImport("a", "", kSig_v_v); + builder.addImportedGlobal("b", "", kWasmI32); + builder.addImportedMemory("c", "", 0, 128); + builder.addImportedTable("d", "", 0, 128); + + const buffer = builder.toBuffer(); + const imports = { + "a": { "": function() {} }, + "b": { "": 0 }, + "c": { "": new WebAssembly.Memory({ "initial": 64, maximum: 128 }) }, + "d": { "": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }) }, + }; + + return { + buffer, + args: [imports], + exports: {}, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: function", + function() { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("", kSig_v_d) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "function", "name": "0", "length": 1 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: table", + function() { + const builder = new WasmModuleBuilder(); + + builder.setTableBounds(1); + builder.addExportOfKind("", kExternalTable, 0); + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "table", "length": 1 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports with empty name: global", + function() { + const builder = new WasmModuleBuilder(); + + builder.addGlobal(kWasmI32, true) + .exportAs("") + .init = 7; + + const buffer = builder.toBuffer(); + + const exports = { + "": { "kind": "global", "value": 7 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "No imports", + function() { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + + builder.addGlobal(kWasmI32, true) + .exportAs("global") + .init = 7; + builder.addGlobal(kWasmF64, true) + .exportAs("global2") + .init = 1.2; + + builder.addMemory(4, 8, true); + + const buffer = builder.toBuffer(); + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 1 }, + "fn2": { "kind": "function", "name": "1", "length": 0 }, + "table": { "kind": "table", "length": 1 }, + "global": { "kind": "global", "value": 7 }, + "global2": { "kind": "global", "value": 1.2 }, + "memory": { "kind": "memory", "size": 4 }, + }; + + return { + buffer, + args: [], + exports, + verify: () => {}, + }; + } + ], + + [ + "exports and imports", + function() { + const value = 102; + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI32); + builder + .addFunction("fn", kSig_i_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => assert_equals(instance.exports.fn(), value) + }; + } + ], + + [ + "i64 exports and imports", + function() { + const value = 102n; + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64); + builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const index2 = builder.addImportedGlobal("module", "global2", kWasmI64); + builder.addExportOfKind("global", kExternalGlobal, index2); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + "global2": 2n ** 63n, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": "0", "length": 0 }, + "global": { "kind": "global", "value": -(2n ** 63n) }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => assert_equals(instance.exports.fn(), value) + }; + } + ], + + [ + "import with i32-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6n; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i32", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_i_i); + const fn2 = builder + .addFunction("fn2", kSig_i_v) + .addBody([ + kExprI32Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26); + return { valueOf() { return 6; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_v); + const fn2 = builder + .addFunction("fn2", kSig_v_v) + .addBody([ + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function() { + called = true; + return 6; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_throws_js(TypeError, () => instance.exports.fn2()); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with function that takes and returns i64", + function() { + const builder = new WasmModuleBuilder(); + + const fnIndex = builder.addImport("module", "fn", kSig_l_l); + const fn2 = builder + .addFunction("fn2", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprCallFunction, + fnIndex, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + let called = false; + const imports = { + "module": { + "fn": function(n) { + called = true; + assert_equals(n, -26n); + return { valueOf() { return 6n; } }; + }, + }, + }; + + return { + buffer, + args: [imports], + exports: { + "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 }, + }, + verify: instance => { + assert_equals(instance.exports.fn2(), 6n); + assert_true(called, "Should have called into JS"); + } + }; + } + ], + + [ + "import with i32-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_i) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6n)) + }; + } + ], + + [ + "import with i64-taking function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_v_l) + .addBody([ + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 1 }, + }, + verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6)) + }; + } + ], + + [ + "export i64-returning function", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprI64Const, 0x66, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + return { + buffer, + args: [], + exports: { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }, + verify: instance => assert_equals(instance.exports.fn(), -26n) + }; + } + ], + + [ + "i32 mutable WebAssembly.Global import", + function() { + const initial = 102; + const value = new WebAssembly.Global({ "value": "i32", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI32, true); + const fn = builder + .addFunction("fn", kSig_i_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "i64 mutable WebAssembly.Global import", + function() { + const initial = 102n; + const value = new WebAssembly.Global({ "value": "i64", "mutable": true }, initial); + + const builder = new WasmModuleBuilder(); + + const index = builder.addImportedGlobal("module", "global", kWasmI64, true); + const fn = builder + .addFunction("fn", kSig_l_v) + .addBody([ + kExprGlobalGet, + index, + kExprReturn, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const imports = { + "module": { + "global": value, + }, + }; + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 0 }, + }; + + return { + buffer, + args: [imports], + exports, + verify: instance => { + assert_equals(instance.exports.fn(), initial); + const after = 201n; + value.value = after; + assert_equals(instance.exports.fn(), after); + } + }; + } + ], + + [ + "Multiple i64 arguments", + function() { + const builder = new WasmModuleBuilder(); + + const fn = builder + .addFunction("fn", kSig_l_ll) + .addBody([ + kExprLocalGet, 1, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const exports = { + "fn": { "kind": "function", "name": String(fn.index), "length": 2 }, + }; + + return { + buffer, + args: [], + exports, + verify: instance => { + const fn = instance.exports.fn; + assert_equals(fn(1n, 0n), 0n); + assert_equals(fn(1n, 123n), 123n); + assert_equals(fn(1n, -123n), -123n); + assert_equals(fn(1n, "5"), 5n); + assert_throws_js(TypeError, () => fn(1n, 5)); + } + }; + } + ], + + [ + "stray argument", + function() { + return { + buffer: emptyModuleBinary, + args: [{}, {}], + exports: {}, + verify: () => {} + }; + } + ], +]; diff --git a/test/fixtures/wpt/wasm/jsapi/interface.any.js b/test/fixtures/wpt/wasm/jsapi/interface.any.js new file mode 100644 index 00000000000000..19d29ead0a7264 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/interface.any.js @@ -0,0 +1,160 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function test_operations(object, object_name, operations) { + for (const [name, length] of operations) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_true(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, object[name]); + }, `${object_name}.${name}`); + + test(() => { + assert_function_name(object[name], name, `${object_name}.${name}`); + }, `${object_name}.${name}: name`); + + test(() => { + assert_function_length(object[name], length, `${object_name}.${name}`); + }, `${object_name}.${name}: length`); + } +} + +function test_attributes(object, object_name, attributes) { + for (const [name, mutable] of attributes) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + }, `${object_name}.${name}`); + + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + assert_equals(typeof propdesc.get, "function"); + assert_function_name(propdesc.get, "get " + name, `getter for "${name}"`); + assert_function_length(propdesc.get, 0, `getter for "${name}"`); + }, `${object_name}.${name}: getter`); + + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(object, name); + assert_equals(typeof propdesc, "object"); + if (mutable) { + assert_equals(typeof propdesc.set, "function"); + assert_function_name(propdesc.set, "set " + name, `setter for "${name}"`); + assert_function_length(propdesc.set, 1, `setter for "${name}"`); + } else { + assert_equals(typeof propdesc.set, "undefined"); + } + }, `${object_name}.${name}: setter`); + } +} + +test(() => { + const propdesc = Object.getOwnPropertyDescriptor(this, "WebAssembly"); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, this.WebAssembly); +}, "WebAssembly: property descriptor"); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly()); +}, "WebAssembly: calling"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly()); +}, "WebAssembly: constructing"); + +const interfaces = [ + "Module", + "Instance", + "Memory", + "Table", + "Global", + "CompileError", + "LinkError", + "RuntimeError", +]; + +for (const name of interfaces) { + test(() => { + const propdesc = Object.getOwnPropertyDescriptor(WebAssembly, name); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, WebAssembly[name]); + }, `WebAssembly.${name}: property descriptor`); + + test(() => { + const interface_object = WebAssembly[name]; + const propdesc = Object.getOwnPropertyDescriptor(interface_object, "prototype"); + assert_equals(typeof propdesc, "object"); + assert_false(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_false(propdesc.configurable, "configurable"); + }, `WebAssembly.${name}: prototype`); + + test(() => { + const interface_object = WebAssembly[name]; + const interface_prototype_object = interface_object.prototype; + const propdesc = Object.getOwnPropertyDescriptor(interface_prototype_object, "constructor"); + assert_equals(typeof propdesc, "object"); + assert_true(propdesc.writable, "writable"); + assert_false(propdesc.enumerable, "enumerable"); + assert_true(propdesc.configurable, "configurable"); + assert_equals(propdesc.value, interface_object); + }, `WebAssembly.${name}: prototype.constructor`); +} + +test_operations(WebAssembly, "WebAssembly", [ + ["validate", 1], + ["compile", 1], + ["instantiate", 1], +]); + + +test_operations(WebAssembly.Module, "WebAssembly.Module", [ + ["exports", 1], + ["imports", 1], + ["customSections", 2], +]); + + +test_attributes(WebAssembly.Instance.prototype, "WebAssembly.Instance", [ + ["exports", false], +]); + + +test_operations(WebAssembly.Memory.prototype, "WebAssembly.Memory", [ + ["grow", 1], +]); + +test_attributes(WebAssembly.Memory.prototype, "WebAssembly.Memory", [ + ["buffer", false], +]); + + +test_operations(WebAssembly.Table.prototype, "WebAssembly.Table", [ + ["grow", 1], + ["get", 1], + ["set", 1], +]); + +test_attributes(WebAssembly.Table.prototype, "WebAssembly.Table", [ + ["length", false], +]); + + +test_operations(WebAssembly.Global.prototype, "WebAssembly.Global", [ + ["valueOf", 0], +]); + +test_attributes(WebAssembly.Global.prototype, "WebAssembly.Global", [ + ["value", true], +]); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/assertions.js b/test/fixtures/wpt/wasm/jsapi/memory/assertions.js new file mode 100644 index 00000000000000..b539513adcab7d --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/assertions.js @@ -0,0 +1,38 @@ +function assert_ArrayBuffer(actual, { size=0, shared=false, detached=false }, message) { + // https://github.com/WebAssembly/spec/issues/840 + // See https://github.com/whatwg/html/issues/5380 for why not `self.SharedArrayBuffer` + const isShared = !("isView" in actual.constructor); + assert_equals(isShared, shared, `${message}: constructor`); + const sharedString = shared ? "Shared" : ""; + assert_equals(actual.toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: toString()`); + assert_equals(Object.getPrototypeOf(actual).toString(), `[object ${sharedString}ArrayBuffer]`, `${message}: prototype toString()`); + if (detached) { + // https://github.com/tc39/ecma262/issues/678 + let byteLength; + try { + byteLength = actual.byteLength; + } catch (e) { + byteLength = 0; + } + assert_equals(byteLength, 0, `${message}: detached size`); + } else { + assert_equals(actual.byteLength, 0x10000 * size, `${message}: size`); + if (size > 0) { + const array = new Uint8Array(actual); + assert_equals(array[0], 0, `${message}: first element`); + assert_equals(array[array.byteLength - 1], 0, `${message}: last element`); + } + } + assert_equals(Object.isFrozen(actual), shared, "buffer frozen"); + assert_equals(Object.isExtensible(actual), !shared, "buffer extensibility"); +} + +function assert_Memory(memory, { size=0, shared=false }) { + assert_equals(Object.getPrototypeOf(memory), WebAssembly.Memory.prototype, + "prototype"); + assert_true(Object.isExtensible(memory), "extensible"); + + // https://github.com/WebAssembly/spec/issues/840 + assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent"); + assert_ArrayBuffer(memory.buffer, { size, shared }); +} diff --git a/test/fixtures/wpt/wasm/jsapi/memory/buffer.any.js b/test/fixtures/wpt/wasm/jsapi/memory/buffer.any.js new file mode 100644 index 00000000000000..fb1d1200b892be --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/buffer.any.js @@ -0,0 +1,64 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "buffer"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, "buffer"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(memory, {}), buffer); +}, "Stray argument"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const memory2 = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + assert_not_equals(buffer, memory2.buffer, "Need two distinct buffers"); + memory.buffer = memory2.buffer; + assert_equals(memory.buffer, buffer, "Should not change the buffer"); +}, "Setting (sloppy mode)"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const memory2 = new WebAssembly.Memory(argument); + const buffer = memory.buffer; + assert_not_equals(buffer, memory2.buffer, "Need two distinct buffers"); + assert_throws_js(TypeError, () => { + "use strict"; + memory.buffer = memory2.buffer; + }); + assert_equals(memory.buffer, buffer, "Should not change the buffer"); +}, "Setting (strict mode)"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js new file mode 100644 index 00000000000000..216fc4ca55591f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor-shared.tentative.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 10, "shared": true })); +}, "Shared memory without maximum"); + +test(t => { + const order = []; + + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + + get shared() { + order.push("shared"); + return { + valueOf: t.unreached_func("should not call shared valueOf"), + }; + }, + }); + + assert_array_equals(order, [ + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + "shared", + ]); +}, "Order of evaluation for descriptor (with shared)"); + +test(() => { + const argument = { "initial": 4, "maximum": 10, shared: true }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4, "shared": true }); +}, "Shared memory"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js new file mode 100644 index 00000000000000..d5378dbe82b00b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor-types.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const argument = { initial: 5, minimum: 6 }; + assert_throws_js(TypeError, () => new WebAssembly.Memory(argument)); +}, "Initializing with both initial and minimum"); + +test(() => { + const argument = { minimum: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 0 }); + }, "Zero minimum"); + +test(() => { + const argument = { minimum: 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4 }); + }, "Non-zero minimum"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js new file mode 100644 index 00000000000000..0a0be11e370877 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/constructor.any.js @@ -0,0 +1,139 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + assert_function_name(WebAssembly.Memory, "Memory", "WebAssembly.Memory"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Memory, 1, "WebAssembly.Memory"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory()); +}, "No arguments"); + +test(() => { + const argument = { "initial": 0 }; + assert_throws_js(TypeError, () => WebAssembly.Memory(argument)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Memory(invalidArgument), + `new Memory(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": undefined })); +}, "Undefined initial value in descriptor"); + +const outOfRangeValues = [ + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, +]; + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": value })); + }, `Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Memory({ "initial": 0, "maximum": value })); + }, `Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js(RangeError, () => new WebAssembly.Memory({ "initial": 10, "maximum": 9 })); +}, "Initial value exceeds maximum"); + +test(() => { + const proxy = new Proxy({}, { + has(o, x) { + assert_unreached(`Should not call [[HasProperty]] with ${x}`); + }, + get(o, x) { + // Due to the requirement not to supply both minimum and initial, we need to ignore one of them. + switch (x) { + case "shared": + return false; + case "initial": + case "maximum": + return 0; + default: + return undefined; + } + }, + }); + new WebAssembly.Memory(proxy); +}, "Proxy descriptor"); + +test(() => { + const order = []; + + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + }); + + assert_array_equals(order, [ + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + ]); +}, "Order of evaluation for descriptor"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 0 }); +}, "Zero initial"); + +test(() => { + const argument = { "initial": 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { "size": 4 }); +}, "Non-zero initial"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument, {}); + assert_Memory(memory, { "size": 0 }); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js b/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js new file mode 100644 index 00000000000000..c511129491f4de --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/grow.any.js @@ -0,0 +1,189 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/memory/assertions.js + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js(TypeError, () => memory.grow()); +}, "Missing arguments"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Memory, + WebAssembly.Memory.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Memory.prototype.grow; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial"); + +test(() => { + const argument = { "initial": { valueOf() { return 0 } } }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow({ valueOf() { return 2 } }); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial with valueOf"); + +test(() => { + const argument = { "initial": 3 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 3 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 3); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 5 }, "New buffer after growing"); +}, "Non-zero initial"); + +test(() => { + const argument = { "initial": 0, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Zero initial with respected maximum"); + +test(() => { + const argument = { "initial": 0, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(1); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing once"); + assert_ArrayBuffer(newMemory, { "size": 1 }, "New buffer after growing once"); + + const result2 = memory.grow(1); + assert_equals(result2, 1); + + const newestMemory = memory.buffer; + assert_not_equals(newMemory, newestMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "New buffer after growing twice"); + assert_ArrayBuffer(newMemory, { "detached": true }, "New buffer after growing twice"); + assert_ArrayBuffer(newestMemory, { "size": 2 }, "Newest buffer after growing twice"); +}, "Zero initial with respected maximum grown twice"); + +test(() => { + const argument = { "initial": 1, "maximum": 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 1 }, "Buffer before growing"); + + assert_throws_js(RangeError, () => memory.grow(2)); + assert_equals(memory.buffer, oldMemory); + assert_ArrayBuffer(memory.buffer, { "size": 1 }, "Buffer before trying to grow"); +}, "Zero initial growing too much"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js(TypeError, () => memory.grow(value)); + }, `Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 0 }, "Buffer before growing"); + + const result = memory.grow(2, {}); + assert_equals(result, 0); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "detached": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2 }, "New buffer after growing"); +}, "Stray argument"); + +test(() => { + const argument = { "initial": 1, "maximum": 2, "shared": true }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Buffer before growing"); + + const result = memory.grow(1); + assert_equals(result, 1); + + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(oldMemory, { "size": 1, "shared": true }, "Old buffer after growing"); + assert_ArrayBuffer(newMemory, { "size": 2, "shared": true }, "New buffer after growing"); + + // The old and new buffers must have the same value for the + // [[ArrayBufferData]] internal slot. + const oldArray = new Uint8Array(oldMemory); + const newArray = new Uint8Array(newMemory); + assert_equals(oldArray[0], 0, "old first element"); + assert_equals(newArray[0], 0, "new first element"); + oldArray[0] = 1; + assert_equals(oldArray[0], 1, "old first element"); + assert_equals(newArray[0], 1, "new first element"); + +}, "Growing shared memory does not detach old buffer"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/toString.any.js b/test/fixtures/wpt/wasm/jsapi/memory/toString.any.js new file mode 100644 index 00000000000000..f4059f76577227 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { "initial": 0 }; + const memory = new WebAssembly.Memory(argument); + assert_class_string(memory, "WebAssembly.Memory"); +}, "Object.prototype.toString on an Memory"); + +test(() => { + assert_own_property(WebAssembly.Memory.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Memory.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Memory", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js new file mode 100644 index 00000000000000..a96a3227adca7f --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/memory/type.tentative.any.js @@ -0,0 +1,37 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const memory = new WebAssembly.Memory(argument); + const memorytype = memory.type() + + assert_equals(memorytype.minimum, argument.minimum); + assert_equals(memorytype.maximum, argument.maximum); + if (argument.shared !== undefined) { + assert_equals(memorytype.shared, argument.shared); + } +} + +test(() => { + assert_type({ "minimum": 0 }); +}, "Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5 }); +}, "Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0 }); +}, "Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5 }); +}, "None-zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 10, "shared": false}); +}, "non-shared memory"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 10, "shared": true}); +}, "shared memory"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js new file mode 100644 index 00000000000000..9978f7e6ac8f2b --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/constructor.any.js @@ -0,0 +1,69 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_function_name(WebAssembly.Module, "Module", "WebAssembly.Module"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Module, 1, "WebAssembly.Module"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Module()); +}, "No arguments"); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module(emptyModuleBinary)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "test", + Symbol(), + 7, + NaN, + {}, + ArrayBuffer, + ArrayBuffer.prototype, + Array.from(emptyModuleBinary), + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => new WebAssembly.Module(argument), + `new Module(${format_value(argument)})`); + } +}, "Invalid arguments"); + +test(() => { + const buffer = new Uint8Array(); + assert_throws_js(WebAssembly.CompileError, () => new WebAssembly.Module(buffer)); +}, "Empty buffer"); + +test(() => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + assert_throws_js(WebAssembly.CompileError, () => new WebAssembly.Module(buffer)); +}, "Invalid code"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype); +}, "Prototype"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_true(Object.isExtensible(module)); +}, "Extensibility"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary, {}); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js b/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js new file mode 100644 index 00000000000000..4029877e92c7b8 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/customSections.any.js @@ -0,0 +1,140 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_ArrayBuffer(buffer, expected) { + assert_equals(Object.getPrototypeOf(buffer), ArrayBuffer.prototype, "Prototype"); + assert_true(Object.isExtensible(buffer), "isExtensible"); + assert_array_equals(new Uint8Array(buffer), expected); +} + +function assert_sections(sections, expected) { + assert_true(Array.isArray(sections), "Should be array"); + assert_equals(Object.getPrototypeOf(sections), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(sections), "isExtensible"); + + assert_equals(sections.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ArrayBuffer(sections[i], expected[i]); + } +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.customSections()); + const module = new WebAssembly.Module(emptyModuleBinary); + assert_throws_js(TypeError, () => WebAssembly.Module.customSections(module)); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.customSections(argument, ""), + `customSections(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.customSections; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_sections(fn.call(thisValue, module, ""), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_sections(WebAssembly.Module.customSections(module, ""), []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.customSections(module, ""), + WebAssembly.Module.customSections(module, "")); +}, "Empty module: array caching"); + +test(() => { + const bytes1 = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + const bytes2 = [74, 83, 65, 80, 73]; + + const builder = new WasmModuleBuilder(); + builder.addCustomSection("name", bytes1); + builder.addCustomSection("name", bytes2); + builder.addCustomSection("foo", bytes1); + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, "name"), [ + bytes1, + bytes2, + ]) + + assert_sections(WebAssembly.Module.customSections(module, "foo"), [ + bytes1, + ]) + + assert_sections(WebAssembly.Module.customSections(module, ""), []) + assert_sections(WebAssembly.Module.customSections(module, "\0"), []) + assert_sections(WebAssembly.Module.customSections(module, "name\0"), []) + assert_sections(WebAssembly.Module.customSections(module, "foo\0"), []) +}, "Custom sections"); + +test(() => { + const bytes = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + const name = "yee\uD801\uDC37eey" + + const builder = new WasmModuleBuilder(); + builder.addCustomSection(name, bytes); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, name), [ + bytes, + ]); + assert_sections(WebAssembly.Module.customSections(module, "yee\uFFFDeey"), []); + assert_sections(WebAssembly.Module.customSections(module, "yee\uFFFD\uFFFDeey"), []); +}, "Custom sections with surrogate pairs"); + +test(() => { + const bytes = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; + + const builder = new WasmModuleBuilder(); + builder.addCustomSection("na\uFFFDme", bytes); + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + + assert_sections(WebAssembly.Module.customSections(module, "name"), []); + assert_sections(WebAssembly.Module.customSections(module, "na\uFFFDme"), [ + bytes, + ]); + assert_sections(WebAssembly.Module.customSections(module, "na\uDC01me"), []); +}, "Custom sections with U+FFFD"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_sections(WebAssembly.Module.customSections(module, "", {}), []); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/module/exports.any.js b/test/fixtures/wpt/wasm/jsapi/module/exports.any.js new file mode 100644 index 00000000000000..40a3935a4a23ba --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/exports.any.js @@ -0,0 +1,185 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +function assert_ModuleExportDescriptor(export_, expected) { + assert_equals(Object.getPrototypeOf(export_), Object.prototype, "Prototype"); + assert_true(Object.isExtensible(export_), "isExtensible"); + + const name = Object.getOwnPropertyDescriptor(export_, "name"); + assert_true(name.writable, "name: writable"); + assert_true(name.enumerable, "name: enumerable"); + assert_true(name.configurable, "name: configurable"); + assert_equals(name.value, expected.name); + + const kind = Object.getOwnPropertyDescriptor(export_, "kind"); + assert_true(kind.writable, "kind: writable"); + assert_true(kind.enumerable, "kind: enumerable"); + assert_true(kind.configurable, "kind: configurable"); + assert_equals(kind.value, expected.kind); +} + +function assert_exports(exports, expected) { + assert_true(Array.isArray(exports), "Should be array"); + assert_equals(Object.getPrototypeOf(exports), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(exports), "isExtensible"); + + assert_equals(exports.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ModuleExportDescriptor(exports[i], expected[i]); + } +} + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.exports()); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.exports(argument), + `exports(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.exports; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_array_equals(fn.call(thisValue, module), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module); + assert_true(Array.isArray(exports)); +}, "Return type"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module); + assert_exports(exports, []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.exports(module), WebAssembly.Module.exports(module)); +}, "Empty module: array caching"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + builder.setTableBounds(1); + builder.addExportOfKind("table", kExternalTable, 0); + + builder.addGlobal(kWasmI32, true) + .exportAs("global") + .init = 7; + builder.addGlobal(kWasmF64, true) + .exportAs("global2") + .init = 1.2; + + builder.addMemory(0, 256, true); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "function", "name": "fn" }, + { "kind": "function", "name": "fn2" }, + { "kind": "table", "name": "table" }, + { "kind": "global", "name": "global" }, + { "kind": "global", "name": "global2" }, + { "kind": "memory", "name": "memory" }, + ]; + assert_exports(exports, expected); +}, "exports"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("", kSig_v_v) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "function", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: function"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.setTableBounds(1); + builder.addExportOfKind("", kExternalTable, 0); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "table", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: table"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addGlobal(kWasmI32, true) + .exportAs("") + .init = 7; + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { "kind": "global", "name": "" }, + ]; + assert_exports(exports, expected); +}, "exports with empty name: global"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const exports = WebAssembly.Module.exports(module, {}); + assert_exports(exports, []); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/module/imports.any.js b/test/fixtures/wpt/wasm/jsapi/module/imports.any.js new file mode 100644 index 00000000000000..ec550ce6c41af1 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/imports.any.js @@ -0,0 +1,185 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_ModuleImportDescriptor(import_, expected) { + assert_equals(Object.getPrototypeOf(import_), Object.prototype, "Prototype"); + assert_true(Object.isExtensible(import_), "isExtensible"); + + const module = Object.getOwnPropertyDescriptor(import_, "module"); + assert_true(module.writable, "module: writable"); + assert_true(module.enumerable, "module: enumerable"); + assert_true(module.configurable, "module: configurable"); + assert_equals(module.value, expected.module); + + const name = Object.getOwnPropertyDescriptor(import_, "name"); + assert_true(name.writable, "name: writable"); + assert_true(name.enumerable, "name: enumerable"); + assert_true(name.configurable, "name: configurable"); + assert_equals(name.value, expected.name); + + const kind = Object.getOwnPropertyDescriptor(import_, "kind"); + assert_true(kind.writable, "kind: writable"); + assert_true(kind.enumerable, "kind: enumerable"); + assert_true(kind.configurable, "kind: configurable"); + assert_equals(kind.value, expected.kind); +} + +function assert_imports(imports, expected) { + assert_true(Array.isArray(imports), "Should be array"); + assert_equals(Object.getPrototypeOf(imports), Array.prototype, "Prototype"); + assert_true(Object.isExtensible(imports), "isExtensible"); + + assert_equals(imports.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_ModuleImportDescriptor(imports[i], expected[i]); + } +} + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + assert_throws_js(TypeError, () => WebAssembly.Module.imports()); +}, "Missing arguments"); + +test(() => { + const invalidArguments = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => WebAssembly.Module.imports(argument), + `imports(${format_value(argument)})`); + } +}, "Non-Module arguments"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const fn = WebAssembly.Module.imports; + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Module, + WebAssembly.Module.prototype, + ]; + for (const thisValue of thisValues) { + assert_array_equals(fn.call(thisValue, module), []); + } +}, "Branding"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module); + assert_true(Array.isArray(imports)); +}, "Return type"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module); + assert_imports(imports, []); +}, "Empty module"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + assert_not_equals(WebAssembly.Module.imports(module), WebAssembly.Module.imports(module)); +}, "Empty module: array caching"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("module", "fn", kSig_v_v); + builder.addImportedGlobal("module", "global", kWasmI32); + builder.addImportedMemory("module", "memory", 0, 128); + builder.addImportedTable("module", "table", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "module", "kind": "function", "name": "fn" }, + { "module": "module", "kind": "global", "name": "global" }, + { "module": "module", "kind": "memory", "name": "memory" }, + { "module": "module", "kind": "table", "name": "table" }, + ]; + assert_imports(imports, expected); +}, "imports"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "fn", kSig_v_v); + builder.addImportedGlobal("", "global", kWasmI32); + builder.addImportedMemory("", "memory", 0, 128); + builder.addImportedTable("", "table", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "", "kind": "function", "name": "fn" }, + { "module": "", "kind": "global", "name": "global" }, + { "module": "", "kind": "memory", "name": "memory" }, + { "module": "", "kind": "table", "name": "table" }, + ]; + assert_imports(imports, expected); +}, "imports with empty module name"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("a", "", kSig_v_v); + builder.addImportedGlobal("b", "", kWasmI32); + builder.addImportedMemory("c", "", 0, 128); + builder.addImportedTable("d", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "a", "kind": "function", "name": "" }, + { "module": "b", "kind": "global", "name": "" }, + { "module": "c", "kind": "memory", "name": "" }, + { "module": "d", "kind": "table", "name": "" }, + ]; + assert_imports(imports, expected); +}, "imports with empty names"); + +test(() => { + const builder = new WasmModuleBuilder(); + + builder.addImport("", "", kSig_v_v); + builder.addImportedGlobal("", "", kWasmI32); + builder.addImportedMemory("", "", 0, 128); + builder.addImportedTable("", "", 0, 128); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { "module": "", "kind": "function", "name": "" }, + { "module": "", "kind": "global", "name": "" }, + { "module": "", "kind": "memory", "name": "" }, + { "module": "", "kind": "table", "name": "" }, + ]; + assert_imports(imports, expected); +}, "imports with empty module names and names"); + +test(() => { + const module = new WebAssembly.Module(emptyModuleBinary); + const imports = WebAssembly.Module.imports(module, {}); + assert_imports(imports, []); +}, "Stray argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/module/toString.any.js b/test/fixtures/wpt/wasm/jsapi/module/toString.any.js new file mode 100644 index 00000000000000..1c20cd6108c84e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/module/toString.any.js @@ -0,0 +1,18 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const emptyModuleBinary = new WasmModuleBuilder().toBuffer(); + const module = new WebAssembly.Module(emptyModuleBinary); + assert_class_string(module, "WebAssembly.Module"); +}, "Object.prototype.toString on an Module"); + +test(() => { + assert_own_property(WebAssembly.Module.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Module.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Module", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/proto-from-ctor-realm.html b/test/fixtures/wpt/wasm/jsapi/proto-from-ctor-realm.html new file mode 100644 index 00000000000000..45405b52900bf9 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/proto-from-ctor-realm.html @@ -0,0 +1,95 @@ + + +WebAssembly JS API: Default [[Prototype]] value is from NewTarget's Realm + + + + + + + + + + + diff --git a/test/fixtures/wpt/wasm/jsapi/prototypes.any.js b/test/fixtures/wpt/wasm/jsapi/prototypes.any.js new file mode 100644 index 00000000000000..714f4f8430e5eb --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/prototypes.any.js @@ -0,0 +1,43 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +test(() => { + class _Module extends WebAssembly.Module {} + let module = new _Module(emptyModuleBinary); + assert_true(module instanceof _Module, "_Module instanceof _Module"); + assert_true(module instanceof WebAssembly.Module, "_Module instanceof WebAssembly.Module"); +}, "_Module"); + +test(() => { + class _Instance extends WebAssembly.Instance {} + let instance = new _Instance(new WebAssembly.Module(emptyModuleBinary)); + assert_true(instance instanceof _Instance, "_Instance instanceof _Instance"); + assert_true(instance instanceof WebAssembly.Instance, "_Instance instanceof WebAssembly.Instance"); +}, "_Instance"); + +test(() => { + class _Memory extends WebAssembly.Memory {} + let memory = new _Memory({initial: 0, maximum: 1}); + assert_true(memory instanceof _Memory, "_Memory instanceof _Memory"); + assert_true(memory instanceof WebAssembly.Memory, "_Memory instanceof WebAssembly.Memory"); +}, "_Memory"); + +test(() => { + class _Table extends WebAssembly.Table {} + let table = new _Table({initial: 0, element: "anyfunc"}); + assert_true(table instanceof _Table, "_Table instanceof _Table"); + assert_true(table instanceof WebAssembly.Table, "_Table instanceof WebAssembly.Table"); +}, "_Table"); + +test(() => { + class _Global extends WebAssembly.Global {} + let global = new _Global({value: "i32", mutable: false}, 0); + assert_true(global instanceof _Global, "_Global instanceof _Global"); + assert_true(global instanceof WebAssembly.Global, "_Global instanceof WebAssembly.Global"); +}, "_Global"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/assertions.js b/test/fixtures/wpt/wasm/jsapi/table/assertions.js new file mode 100644 index 00000000000000..19cc5c3b92d6fa --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/assertions.js @@ -0,0 +1,24 @@ +function assert_equal_to_array(table, expected, message) { + assert_equals(table.length, expected.length, `${message}: length`); + // The argument check in get() happens before the range check, and negative numbers + // are illegal, hence will throw TypeError per spec. + assert_throws_js(TypeError, () => table.get(-1), `${message}: table.get(-1)`); + for (let i = 0; i < expected.length; ++i) { + assert_equals(table.get(i), expected[i], `${message}: table.get(${i} of ${expected.length})`); + } + assert_throws_js(RangeError, () => table.get(expected.length), + `${message}: table.get(${expected.length} of ${expected.length})`); + assert_throws_js(RangeError, () => table.get(expected.length + 1), + `${message}: table.get(${expected.length + 1} of ${expected.length})`); +} + +function assert_Table(actual, expected) { + assert_equals(Object.getPrototypeOf(actual), WebAssembly.Table.prototype, + "prototype"); + assert_true(Object.isExtensible(actual), "extensible"); + + assert_equals(actual.length, expected.length, "length"); + for (let i = 0; i < expected.length; ++i) { + assert_equals(actual.get(i), null, `actual.get(${i})`); + } +} diff --git a/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js new file mode 100644 index 00000000000000..99ca41b55a9152 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/constructor-types.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/table/assertions.js + +test(() => { + const argument = { "element": "anyfunc", "initial": 0, "minimum": 0 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument)); +}, "Initializing with both initial and minimum"); + +test(() => { + const argument = { "element": "anyfunc", "minimum": 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 0 }); +}, "Zero minimum"); + +test(() => { + const argument = { "element": "anyfunc", "minimum": 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 5 }); +}, "Non-zero minimum"); \ No newline at end of file diff --git a/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js b/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js new file mode 100644 index 00000000000000..6d38d04e4f5050 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/constructor.any.js @@ -0,0 +1,208 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/table/assertions.js + +test(() => { + assert_function_name(WebAssembly.Table, "Table", "WebAssembly.Table"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Table, 1, "WebAssembly.Table"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table()); +}, "No arguments"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + assert_throws_js(TypeError, () => WebAssembly.Table(argument)); +}, "Calling"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({})); +}, "Empty descriptor"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js(TypeError, + () => new WebAssembly.Table(invalidArgument), + `new Table(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": undefined })); +}, "Undefined initial value in descriptor"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": undefined, "initial": 0 })); +}, "Undefined element value in descriptor"); + +const outOfRangeValues = [ + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, +]; + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": value })); + }, `Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": 0, "maximum": value })); + }, `Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js(RangeError, () => new WebAssembly.Table({ "element": "anyfunc", "initial": 10, "maximum": 9 })); +}, "Initial value exceeds maximum"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 0 }); +}, "Basic (zero)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { "length": 5 }); +}, "Basic (non-zero)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument, null, {}); + assert_Table(table, { "length": 0 }); +}, "Stray argument"); + +test(() => { + const proxy = new Proxy({}, { + has(o, x) { + assert_unreached(`Should not call [[HasProperty]] with ${x}`); + }, + get(o, x) { + switch (x) { + case "element": + return "anyfunc"; + case "initial": + case "maximum": + return 0; + default: + return undefined; + } + }, + }); + const table = new WebAssembly.Table(proxy); + assert_Table(table, { "length": 0 }); +}, "Proxy descriptor"); + +test(() => { + const table = new WebAssembly.Table({ + "element": { + toString() { return "anyfunc"; }, + }, + "initial": 1, + }); + assert_Table(table, { "length": 1 }); +}, "Type conversion for descriptor.element"); + +test(() => { + const order = []; + + new WebAssembly.Table({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + + get element() { + order.push("element"); + return { + toString() { + order.push("element toString"); + return "anyfunc"; + }, + }; + }, + }); + + assert_array_equals(order, [ + "element", + "element toString", + "initial", + "initial valueOf", + "maximum", + "maximum valueOf", + ]); +}, "Order of evaluation for descriptor"); + +test(() => { + const testObject = {}; + const argument = { "element": "externref", "initial": 3 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.length, 3); + assert_equals(table.get(0), testObject); + assert_equals(table.get(1), testObject); + assert_equals(table.get(2), testObject); +}, "initialize externref table with default value"); + +test(() => { + const argument = { "element": "i32", "initial": 3 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument)); +}, "initialize table with a wrong element value"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer(); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + const argument = { "element": "anyfunc", "initial": 3 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.length, 3); + assert_equals(table.get(0), fn); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "initialize anyfunc table with default value"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 3 }; + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, {})); + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, "cannot be used as a wasm function")); + assert_throws_js(TypeError, () => new WebAssembly.Table(argument, 37)); +}, "initialize anyfunc table with a bad default value"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js b/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js new file mode 100644 index 00000000000000..9301057a533ed4 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/get-set.any.js @@ -0,0 +1,263 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=assertions.js + +let functions = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + builder + .addFunction("fn", kSig_v_d) + .addBody([]) + .exportFunc(); + builder + .addFunction("fn2", kSig_v_v) + .addBody([]) + .exportFunc(); + + const buffer = builder.toBuffer() + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + functions = instance.exports; +}); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.get()); +}, "Missing arguments: get"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.get; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding: get"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.set()); +}, "Missing arguments: set"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.set; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument, null), `this=${format_value(thisValue)}`); + } +}, "Branding: set"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn, fn2} = functions; + + assert_equals(table.set(0, fn), undefined, "set() returns undefined."); + table.set(2, fn2); + table.set(4, fn); + + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + + table.set(0, null); + assert_equal_to_array(table, [null, null, fn2, null, fn]); +}, "Basic"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn, fn2} = functions; + + table.set(0, fn); + table.set(2, fn2); + table.set(4, fn); + + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + + table.grow(4); + + assert_equal_to_array(table, [fn, null, fn2, null, fn, null, null, null, null]); +}, "Growing"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + + const {fn} = functions; + + // -1 is the wrong type hence the type check on entry gets this + // before the range check does. + assert_throws_js(TypeError, () => table.set(-1, fn)); + assert_throws_js(RangeError, () => table.set(5, fn)); + assert_equal_to_array(table, [null, null, null, null, null]); +}, "Setting out-of-bounds"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const invalidArguments = [ + undefined, + true, + false, + "test", + Symbol(), + 7, + NaN, + {}, + ]; + for (const argument of invalidArguments) { + assert_throws_js(TypeError, () => table.set(0, argument), + `set(${format_value(argument)})`); + } + assert_equal_to_array(table, [null]); +}, "Setting non-function"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const fn = function() {}; + assert_throws_js(TypeError, () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm function"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + + const fn = () => {}; + assert_throws_js(TypeError, () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm arrow function"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.get(value)); + }, `Getting out-of-range argument: ${format_value(value)}`); + + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.set(value, null)); + }, `Setting out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + let called = 0; + const value = { + valueOf() { + called++; + return 0; + }, + }; + assert_throws_js(TypeError, () => table.set(value, {})); + assert_equals(called, 1); +}, "Order of argument conversion"); + +test(() => { + const {fn} = functions; + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + + assert_equals(table.get(0, {}), null); + assert_equals(table.set(0, fn, {}), undefined); +}, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer(); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument, fn); + + assert_equals(table.get(0), fn); + table.set(0); + assert_equals(table.get(0), null); + + table.set(0, fn); + assert_equals(table.get(0), fn); + + assert_throws_js(TypeError, () => table.set(0, {})); + assert_throws_js(TypeError, () => table.set(0, 37)); +}, "Arguments for anyfunc table set"); + +test(() => { + const testObject = {}; + const argument = { "element": "externref", "initial": 1 }; + const table = new WebAssembly.Table(argument, testObject); + + assert_equals(table.get(0), testObject); + table.set(0); + assert_equals(table.get(0), undefined); + + table.set(0, testObject); + assert_equals(table.get(0), testObject); + + table.set(0, 37); + assert_equals(table.get(0), 37); +}, "Arguments for externref table set"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/grow.any.js b/test/fixtures/wpt/wasm/jsapi/table/grow.any.js new file mode 100644 index 00000000000000..520d24bf4bafbb --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/grow.any.js @@ -0,0 +1,126 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=assertions.js + +function nulls(n) { + return Array(n).fill(null); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow()); +}, "Missing arguments"); + +test(t => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const argument = { + valueOf: t.unreached_func("Should not touch the argument (valueOf)"), + toString: t.unreached_func("Should not touch the argument (toString)"), + }; + + const fn = WebAssembly.Table.prototype.grow; + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => fn.call(thisValue, argument), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + + const result = table.grow(3); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Basic"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 3, "maximum": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(3), "before"); + + const result = table.grow(2); + assert_equals(result, 3); + assert_equal_to_array(table, nulls(5), "after"); +}, "Reached maximum"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2, "maximum": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(2), "before"); + + assert_throws_js(RangeError, () => table.grow(4)); + assert_equal_to_array(table, nulls(2), "after"); +}, "Exceeded maximum"); + +const outOfRangeValues = [ + undefined, + NaN, + Infinity, + -Infinity, + -1, + 0x100000000, + 0x1000000000, + "0x100000000", + { valueOf() { return 0x100000000; } }, +]; + +for (const value of outOfRangeValues) { + test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(value)); + }, `Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { "element": "anyfunc", "initial": 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + + const result = table.grow(3, null, {}); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Stray argument"); + +test(() => { + const builder = new WasmModuleBuilder(); + builder + .addFunction("fn", kSig_v_v) + .addBody([]) + .exportFunc(); + const bin = builder.toBuffer() + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + const fn = new WebAssembly.Instance(new WebAssembly.Module(bin)).exports.fn; + const result = table.grow(2, fn); + assert_equals(result, 1); + assert_equals(table.get(0), null); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "Grow with exported-function argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(2, {})); +}, "Grow with non-function argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js(TypeError, () => table.grow(2, () => true)); +}, "Grow with JS-function argument"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/length.any.js b/test/fixtures/wpt/wasm/jsapi/table/length.any.js new file mode 100644 index 00000000000000..a9ef095ded4458 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/length.any.js @@ -0,0 +1,60 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const thisValues = [ + undefined, + null, + true, + "", + Symbol(), + 1, + {}, + WebAssembly.Table, + WebAssembly.Table.prototype, + ]; + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, "length"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(typeof desc.set, "undefined"); + + for (const thisValue of thisValues) { + assert_throws_js(TypeError, () => getter.call(thisValue), `this=${format_value(thisValue)}`); + } +}, "Branding"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + + const desc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, "length"); + assert_equals(typeof desc, "object"); + + const getter = desc.get; + assert_equals(typeof getter, "function"); + + assert_equals(getter.call(table, {}), 2); +}, "Stray argument"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + table.length = 4; + assert_equals(table.length, 2, "Should not change the length"); +}, "Setting (sloppy mode)"); + +test(() => { + const argument = { "element": "anyfunc", "initial": 2 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.length, 2, "Initial length"); + assert_throws_js(TypeError, () => { + "use strict"; + table.length = 4; + }); + assert_equals(table.length, 2, "Should not change the length"); +}, "Setting (strict mode)"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/toString.any.js b/test/fixtures/wpt/wasm/jsapi/table/toString.any.js new file mode 100644 index 00000000000000..8a09f2832c1d64 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/toString.any.js @@ -0,0 +1,17 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { "element": "anyfunc", "initial": 0 }; + const table = new WebAssembly.Table(argument); + assert_class_string(table, "WebAssembly.Table"); +}, "Object.prototype.toString on an Table"); + +test(() => { + assert_own_property(WebAssembly.Table.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor(WebAssembly.Table.prototype, Symbol.toStringTag); + assert_equals(propDesc.value, "WebAssembly.Table", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js new file mode 100644 index 00000000000000..596e10b6bf548e --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/table/type.tentative.any.js @@ -0,0 +1,26 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const mytable = new WebAssembly.Table(argument); + const tabletype = mytable.type() + assert_equals(tabletype.minimum, argument.minimum); + assert_equals(tabletype.maximum, argument.maximum); + assert_equals(tabletype.element, argument.element); +} + +test(() => { + assert_type({ "minimum": 0, "element": "anyfunc"}); +}, "Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5, "element": "anyfunc" }); +}, "Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0, "element": "anyfunc" }); +}, "Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5, "element": "anyfunc" }); +}, "Non-zero maximum"); diff --git a/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js new file mode 100644 index 00000000000000..de63e7bf46d1e8 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/tag/constructor.tentative.any.js @@ -0,0 +1,49 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +test(() => { + assert_function_name(WebAssembly.Tag, "Tag", "WebAssembly.Tag"); +}, "name"); + +test(() => { + assert_function_length(WebAssembly.Tag, 1, "WebAssembly.Tag"); +}, "length"); + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Tag()); +}, "No arguments"); + +test(() => { + const argument = { parameters: [] }; + assert_throws_js(TypeError, () => WebAssembly.Tag(argument)); +}, "Calling"); + +test(() => { + const invalidArguments = [ + undefined, + null, + false, + true, + "", + "test", + Symbol(), + 1, + NaN, + {}, + ]; + for (const invalidArgument of invalidArguments) { + assert_throws_js( + TypeError, + () => new WebAssembly.Tag(invalidArgument), + `new Tag(${format_value(invalidArgument)})` + ); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { parameters: [value] }; + assert_throws_js(TypeError, () => new WebAssembly.Tag(argument)); + } +}, "Invalid type parameter"); diff --git a/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js new file mode 100644 index 00000000000000..ad9a4ba152f4f8 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/tag/toString.tentative.any.js @@ -0,0 +1,20 @@ +// META: global=window,dedicatedworker,jsshell + +test(() => { + const argument = { parameters: [] }; + const tag = new WebAssembly.Tag(argument); + assert_class_string(tag, "WebAssembly.Tag"); +}, "Object.prototype.toString on a Tag"); + +test(() => { + assert_own_property(WebAssembly.Tag.prototype, Symbol.toStringTag); + + const propDesc = Object.getOwnPropertyDescriptor( + WebAssembly.Tag.prototype, + Symbol.toStringTag + ); + assert_equals(propDesc.value, "WebAssembly.Tag", "value"); + assert_equals(propDesc.configurable, true, "configurable"); + assert_equals(propDesc.enumerable, false, "enumerable"); + assert_equals(propDesc.writable, false, "writable"); +}, "@@toStringTag exists on the prototype with the appropriate descriptor"); diff --git a/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js b/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js new file mode 100644 index 00000000000000..9d2f0de1a00f20 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/tag/type.tentative.any.js @@ -0,0 +1,21 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const tag = new WebAssembly.Tag(argument); + const tagtype = tag.type(); + + assert_array_equals(tagtype.parameters, argument.parameters); +} + +test(() => { + assert_type({ parameters: [] }); +}, "[]"); + +test(() => { + assert_type({ parameters: ["i32", "i64"] }); +}, "[i32 i64]"); + +test(() => { + assert_type({ parameters: ["i32", "i64", "f32", "f64"] }); +}, "[i32 i64 f32 f64]"); diff --git a/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js b/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js new file mode 100644 index 00000000000000..7be72f86dae752 --- /dev/null +++ b/test/fixtures/wpt/wasm/jsapi/wasm-module-builder.js @@ -0,0 +1,1323 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Used for encoding f32 and double constants to bits. +let byte_view = new Uint8Array(8); +let data_view = new DataView(byte_view.buffer); + +// The bytes function receives one of +// - several arguments, each of which is either a number or a string of length +// 1; if it's a string, the charcode of the contained character is used. +// - a single array argument containing the actual arguments +// - a single string; the returned buffer will contain the char codes of all +// contained characters. +function bytes(...input) { + if (input.length == 1 && typeof input[0] == 'array') input = input[0]; + if (input.length == 1 && typeof input[0] == 'string') { + let len = input[0].length; + let view = new Uint8Array(len); + for (let i = 0; i < len; i++) view[i] = input[0].charCodeAt(i); + return view.buffer; + } + let view = new Uint8Array(input.length); + for (let i = 0; i < input.length; i++) { + let val = input[i]; + if (typeof val == 'string') { + assertEquals(1, val.length, 'string inputs must have length 1'); + val = val.charCodeAt(0); + } + view[i] = val | 0; + } + return view.buffer; +} + +// Header declaration constants +var kWasmH0 = 0; +var kWasmH1 = 0x61; +var kWasmH2 = 0x73; +var kWasmH3 = 0x6d; + +var kWasmV0 = 0x1; +var kWasmV1 = 0; +var kWasmV2 = 0; +var kWasmV3 = 0; + +var kHeaderSize = 8; +var kPageSize = 65536; +var kSpecMaxPages = 65535; +var kMaxVarInt32Size = 5; +var kMaxVarInt64Size = 10; + +let kDeclNoLocals = 0; + +// Section declaration constants +let kUnknownSectionCode = 0; +let kTypeSectionCode = 1; // Function signature declarations +let kImportSectionCode = 2; // Import declarations +let kFunctionSectionCode = 3; // Function declarations +let kTableSectionCode = 4; // Indirect function table and other tables +let kMemorySectionCode = 5; // Memory attributes +let kGlobalSectionCode = 6; // Global declarations +let kExportSectionCode = 7; // Exports +let kStartSectionCode = 8; // Start function declaration +let kElementSectionCode = 9; // Elements section +let kCodeSectionCode = 10; // Function code +let kDataSectionCode = 11; // Data segments +let kDataCountSectionCode = 12; // Data segment count (between Element & Code) +let kExceptionSectionCode = 13; // Exception section (between Global & Export) + +// Name section types +let kModuleNameCode = 0; +let kFunctionNamesCode = 1; +let kLocalNamesCode = 2; + +let kWasmFunctionTypeForm = 0x60; +let kWasmAnyFunctionTypeForm = 0x70; + +let kHasMaximumFlag = 1; +let kSharedHasMaximumFlag = 3; + +// Segment flags +let kActiveNoIndex = 0; +let kPassive = 1; +let kActiveWithIndex = 2; +let kPassiveWithElements = 5; + +// Function declaration flags +let kDeclFunctionName = 0x01; +let kDeclFunctionImport = 0x02; +let kDeclFunctionLocals = 0x04; +let kDeclFunctionExport = 0x08; + +// Local types +let kWasmStmt = 0x40; +let kWasmI32 = 0x7f; +let kWasmI64 = 0x7e; +let kWasmF32 = 0x7d; +let kWasmF64 = 0x7c; +let kWasmS128 = 0x7b; +let kWasmAnyRef = 0x6f; +let kWasmAnyFunc = 0x70; + +let kExternalFunction = 0; +let kExternalTable = 1; +let kExternalMemory = 2; +let kExternalGlobal = 3; +let kExternalException = 4; + +let kTableZero = 0; +let kMemoryZero = 0; +let kSegmentZero = 0; + +let kExceptionAttribute = 0; + +// Useful signatures +let kSig_i_i = makeSig([kWasmI32], [kWasmI32]); +let kSig_l_l = makeSig([kWasmI64], [kWasmI64]); +let kSig_i_l = makeSig([kWasmI64], [kWasmI32]); +let kSig_i_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32]); +let kSig_i_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], [kWasmI32]); +let kSig_v_iiii = makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmI32], []); +let kSig_f_ff = makeSig([kWasmF32, kWasmF32], [kWasmF32]); +let kSig_d_dd = makeSig([kWasmF64, kWasmF64], [kWasmF64]); +let kSig_l_ll = makeSig([kWasmI64, kWasmI64], [kWasmI64]); +let kSig_i_dd = makeSig([kWasmF64, kWasmF64], [kWasmI32]); +let kSig_v_v = makeSig([], []); +let kSig_i_v = makeSig([], [kWasmI32]); +let kSig_l_v = makeSig([], [kWasmI64]); +let kSig_f_v = makeSig([], [kWasmF32]); +let kSig_d_v = makeSig([], [kWasmF64]); +let kSig_v_i = makeSig([kWasmI32], []); +let kSig_v_ii = makeSig([kWasmI32, kWasmI32], []); +let kSig_v_iii = makeSig([kWasmI32, kWasmI32, kWasmI32], []); +let kSig_v_l = makeSig([kWasmI64], []); +let kSig_v_d = makeSig([kWasmF64], []); +let kSig_v_dd = makeSig([kWasmF64, kWasmF64], []); +let kSig_v_ddi = makeSig([kWasmF64, kWasmF64, kWasmI32], []); +let kSig_ii_v = makeSig([], [kWasmI32, kWasmI32]); +let kSig_iii_v = makeSig([], [kWasmI32, kWasmI32, kWasmI32]); +let kSig_ii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32]); +let kSig_iii_i = makeSig([kWasmI32], [kWasmI32, kWasmI32, kWasmI32]); +let kSig_ii_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32, kWasmI32]); +let kSig_iii_ii = makeSig([kWasmI32, kWasmI32], [kWasmI32, kWasmI32, kWasmI32]); + +let kSig_v_f = makeSig([kWasmF32], []); +let kSig_f_f = makeSig([kWasmF32], [kWasmF32]); +let kSig_f_d = makeSig([kWasmF64], [kWasmF32]); +let kSig_d_d = makeSig([kWasmF64], [kWasmF64]); +let kSig_r_r = makeSig([kWasmAnyRef], [kWasmAnyRef]); +let kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]); +let kSig_i_r = makeSig([kWasmAnyRef], [kWasmI32]); +let kSig_v_r = makeSig([kWasmAnyRef], []); +let kSig_v_a = makeSig([kWasmAnyFunc], []); +let kSig_v_rr = makeSig([kWasmAnyRef, kWasmAnyRef], []); +let kSig_v_aa = makeSig([kWasmAnyFunc, kWasmAnyFunc], []); +let kSig_r_v = makeSig([], [kWasmAnyRef]); +let kSig_a_v = makeSig([], [kWasmAnyFunc]); +let kSig_a_i = makeSig([kWasmI32], [kWasmAnyFunc]); + +function makeSig(params, results) { + return {params: params, results: results}; +} + +function makeSig_v_x(x) { + return makeSig([x], []); +} + +function makeSig_v_xx(x) { + return makeSig([x, x], []); +} + +function makeSig_r_v(r) { + return makeSig([], [r]); +} + +function makeSig_r_x(r, x) { + return makeSig([x], [r]); +} + +function makeSig_r_xx(r, x) { + return makeSig([x, x], [r]); +} + +// Opcodes +let kExprUnreachable = 0x00; +let kExprNop = 0x01; +let kExprBlock = 0x02; +let kExprLoop = 0x03; +let kExprIf = 0x04; +let kExprElse = 0x05; +let kExprTry = 0x06; +let kExprCatch = 0x07; +let kExprCatchAll = 0x19; +let kExprThrow = 0x08; +let kExprRethrow = 0x09; +let kExprBrOnExn = 0x0a; +let kExprEnd = 0x0b; +let kExprBr = 0x0c; +let kExprBrIf = 0x0d; +let kExprBrTable = 0x0e; +let kExprReturn = 0x0f; +let kExprCallFunction = 0x10; +let kExprCallIndirect = 0x11; +let kExprReturnCall = 0x12; +let kExprReturnCallIndirect = 0x13; +let kExprDrop = 0x1a; +let kExprSelect = 0x1b; +let kExprLocalGet = 0x20; +let kExprLocalSet = 0x21; +let kExprLocalTee = 0x22; +let kExprGlobalGet = 0x23; +let kExprGlobalSet = 0x24; +let kExprTableGet = 0x25; +let kExprTableSet = 0x26; +let kExprI32LoadMem = 0x28; +let kExprI64LoadMem = 0x29; +let kExprF32LoadMem = 0x2a; +let kExprF64LoadMem = 0x2b; +let kExprI32LoadMem8S = 0x2c; +let kExprI32LoadMem8U = 0x2d; +let kExprI32LoadMem16S = 0x2e; +let kExprI32LoadMem16U = 0x2f; +let kExprI64LoadMem8S = 0x30; +let kExprI64LoadMem8U = 0x31; +let kExprI64LoadMem16S = 0x32; +let kExprI64LoadMem16U = 0x33; +let kExprI64LoadMem32S = 0x34; +let kExprI64LoadMem32U = 0x35; +let kExprI32StoreMem = 0x36; +let kExprI64StoreMem = 0x37; +let kExprF32StoreMem = 0x38; +let kExprF64StoreMem = 0x39; +let kExprI32StoreMem8 = 0x3a; +let kExprI32StoreMem16 = 0x3b; +let kExprI64StoreMem8 = 0x3c; +let kExprI64StoreMem16 = 0x3d; +let kExprI64StoreMem32 = 0x3e; +let kExprMemorySize = 0x3f; +let kExprMemoryGrow = 0x40; +let kExprI32Const = 0x41; +let kExprI64Const = 0x42; +let kExprF32Const = 0x43; +let kExprF64Const = 0x44; +let kExprI32Eqz = 0x45; +let kExprI32Eq = 0x46; +let kExprI32Ne = 0x47; +let kExprI32LtS = 0x48; +let kExprI32LtU = 0x49; +let kExprI32GtS = 0x4a; +let kExprI32GtU = 0x4b; +let kExprI32LeS = 0x4c; +let kExprI32LeU = 0x4d; +let kExprI32GeS = 0x4e; +let kExprI32GeU = 0x4f; +let kExprI64Eqz = 0x50; +let kExprI64Eq = 0x51; +let kExprI64Ne = 0x52; +let kExprI64LtS = 0x53; +let kExprI64LtU = 0x54; +let kExprI64GtS = 0x55; +let kExprI64GtU = 0x56; +let kExprI64LeS = 0x57; +let kExprI64LeU = 0x58; +let kExprI64GeS = 0x59; +let kExprI64GeU = 0x5a; +let kExprF32Eq = 0x5b; +let kExprF32Ne = 0x5c; +let kExprF32Lt = 0x5d; +let kExprF32Gt = 0x5e; +let kExprF32Le = 0x5f; +let kExprF32Ge = 0x60; +let kExprF64Eq = 0x61; +let kExprF64Ne = 0x62; +let kExprF64Lt = 0x63; +let kExprF64Gt = 0x64; +let kExprF64Le = 0x65; +let kExprF64Ge = 0x66; +let kExprI32Clz = 0x67; +let kExprI32Ctz = 0x68; +let kExprI32Popcnt = 0x69; +let kExprI32Add = 0x6a; +let kExprI32Sub = 0x6b; +let kExprI32Mul = 0x6c; +let kExprI32DivS = 0x6d; +let kExprI32DivU = 0x6e; +let kExprI32RemS = 0x6f; +let kExprI32RemU = 0x70; +let kExprI32And = 0x71; +let kExprI32Ior = 0x72; +let kExprI32Xor = 0x73; +let kExprI32Shl = 0x74; +let kExprI32ShrS = 0x75; +let kExprI32ShrU = 0x76; +let kExprI32Rol = 0x77; +let kExprI32Ror = 0x78; +let kExprI64Clz = 0x79; +let kExprI64Ctz = 0x7a; +let kExprI64Popcnt = 0x7b; +let kExprI64Add = 0x7c; +let kExprI64Sub = 0x7d; +let kExprI64Mul = 0x7e; +let kExprI64DivS = 0x7f; +let kExprI64DivU = 0x80; +let kExprI64RemS = 0x81; +let kExprI64RemU = 0x82; +let kExprI64And = 0x83; +let kExprI64Ior = 0x84; +let kExprI64Xor = 0x85; +let kExprI64Shl = 0x86; +let kExprI64ShrS = 0x87; +let kExprI64ShrU = 0x88; +let kExprI64Rol = 0x89; +let kExprI64Ror = 0x8a; +let kExprF32Abs = 0x8b; +let kExprF32Neg = 0x8c; +let kExprF32Ceil = 0x8d; +let kExprF32Floor = 0x8e; +let kExprF32Trunc = 0x8f; +let kExprF32NearestInt = 0x90; +let kExprF32Sqrt = 0x91; +let kExprF32Add = 0x92; +let kExprF32Sub = 0x93; +let kExprF32Mul = 0x94; +let kExprF32Div = 0x95; +let kExprF32Min = 0x96; +let kExprF32Max = 0x97; +let kExprF32CopySign = 0x98; +let kExprF64Abs = 0x99; +let kExprF64Neg = 0x9a; +let kExprF64Ceil = 0x9b; +let kExprF64Floor = 0x9c; +let kExprF64Trunc = 0x9d; +let kExprF64NearestInt = 0x9e; +let kExprF64Sqrt = 0x9f; +let kExprF64Add = 0xa0; +let kExprF64Sub = 0xa1; +let kExprF64Mul = 0xa2; +let kExprF64Div = 0xa3; +let kExprF64Min = 0xa4; +let kExprF64Max = 0xa5; +let kExprF64CopySign = 0xa6; +let kExprI32ConvertI64 = 0xa7; +let kExprI32SConvertF32 = 0xa8; +let kExprI32UConvertF32 = 0xa9; +let kExprI32SConvertF64 = 0xaa; +let kExprI32UConvertF64 = 0xab; +let kExprI64SConvertI32 = 0xac; +let kExprI64UConvertI32 = 0xad; +let kExprI64SConvertF32 = 0xae; +let kExprI64UConvertF32 = 0xaf; +let kExprI64SConvertF64 = 0xb0; +let kExprI64UConvertF64 = 0xb1; +let kExprF32SConvertI32 = 0xb2; +let kExprF32UConvertI32 = 0xb3; +let kExprF32SConvertI64 = 0xb4; +let kExprF32UConvertI64 = 0xb5; +let kExprF32ConvertF64 = 0xb6; +let kExprF64SConvertI32 = 0xb7; +let kExprF64UConvertI32 = 0xb8; +let kExprF64SConvertI64 = 0xb9; +let kExprF64UConvertI64 = 0xba; +let kExprF64ConvertF32 = 0xbb; +let kExprI32ReinterpretF32 = 0xbc; +let kExprI64ReinterpretF64 = 0xbd; +let kExprF32ReinterpretI32 = 0xbe; +let kExprF64ReinterpretI64 = 0xbf; +let kExprI32SExtendI8 = 0xc0; +let kExprI32SExtendI16 = 0xc1; +let kExprI64SExtendI8 = 0xc2; +let kExprI64SExtendI16 = 0xc3; +let kExprI64SExtendI32 = 0xc4; +let kExprRefNull = 0xd0; +let kExprRefIsNull = 0xd1; +let kExprRefFunc = 0xd2; + +// Prefix opcodes +let kNumericPrefix = 0xfc; +let kSimdPrefix = 0xfd; +let kAtomicPrefix = 0xfe; + +// Numeric opcodes. +let kExprMemoryInit = 0x08; +let kExprDataDrop = 0x09; +let kExprMemoryCopy = 0x0a; +let kExprMemoryFill = 0x0b; +let kExprTableInit = 0x0c; +let kExprElemDrop = 0x0d; +let kExprTableCopy = 0x0e; +let kExprTableGrow = 0x0f; +let kExprTableSize = 0x10; +let kExprTableFill = 0x11; + +// Atomic opcodes. +let kExprAtomicNotify = 0x00; +let kExprI32AtomicWait = 0x01; +let kExprI64AtomicWait = 0x02; +let kExprI32AtomicLoad = 0x10; +let kExprI32AtomicLoad8U = 0x12; +let kExprI32AtomicLoad16U = 0x13; +let kExprI32AtomicStore = 0x17; +let kExprI32AtomicStore8U = 0x19; +let kExprI32AtomicStore16U = 0x1a; +let kExprI32AtomicAdd = 0x1e; +let kExprI32AtomicAdd8U = 0x20; +let kExprI32AtomicAdd16U = 0x21; +let kExprI32AtomicSub = 0x25; +let kExprI32AtomicSub8U = 0x27; +let kExprI32AtomicSub16U = 0x28; +let kExprI32AtomicAnd = 0x2c; +let kExprI32AtomicAnd8U = 0x2e; +let kExprI32AtomicAnd16U = 0x2f; +let kExprI32AtomicOr = 0x33; +let kExprI32AtomicOr8U = 0x35; +let kExprI32AtomicOr16U = 0x36; +let kExprI32AtomicXor = 0x3a; +let kExprI32AtomicXor8U = 0x3c; +let kExprI32AtomicXor16U = 0x3d; +let kExprI32AtomicExchange = 0x41; +let kExprI32AtomicExchange8U = 0x43; +let kExprI32AtomicExchange16U = 0x44; +let kExprI32AtomicCompareExchange = 0x48; +let kExprI32AtomicCompareExchange8U = 0x4a; +let kExprI32AtomicCompareExchange16U = 0x4b; + +let kExprI64AtomicLoad = 0x11; +let kExprI64AtomicLoad8U = 0x14; +let kExprI64AtomicLoad16U = 0x15; +let kExprI64AtomicLoad32U = 0x16; +let kExprI64AtomicStore = 0x18; +let kExprI64AtomicStore8U = 0x1b; +let kExprI64AtomicStore16U = 0x1c; +let kExprI64AtomicStore32U = 0x1d; +let kExprI64AtomicAdd = 0x1f; +let kExprI64AtomicAdd8U = 0x22; +let kExprI64AtomicAdd16U = 0x23; +let kExprI64AtomicAdd32U = 0x24; +let kExprI64AtomicSub = 0x26; +let kExprI64AtomicSub8U = 0x29; +let kExprI64AtomicSub16U = 0x2a; +let kExprI64AtomicSub32U = 0x2b; +let kExprI64AtomicAnd = 0x2d; +let kExprI64AtomicAnd8U = 0x30; +let kExprI64AtomicAnd16U = 0x31; +let kExprI64AtomicAnd32U = 0x32; +let kExprI64AtomicOr = 0x34; +let kExprI64AtomicOr8U = 0x37; +let kExprI64AtomicOr16U = 0x38; +let kExprI64AtomicOr32U = 0x39; +let kExprI64AtomicXor = 0x3b; +let kExprI64AtomicXor8U = 0x3e; +let kExprI64AtomicXor16U = 0x3f; +let kExprI64AtomicXor32U = 0x40; +let kExprI64AtomicExchange = 0x42; +let kExprI64AtomicExchange8U = 0x45; +let kExprI64AtomicExchange16U = 0x46; +let kExprI64AtomicExchange32U = 0x47; +let kExprI64AtomicCompareExchange = 0x49 +let kExprI64AtomicCompareExchange8U = 0x4c; +let kExprI64AtomicCompareExchange16U = 0x4d; +let kExprI64AtomicCompareExchange32U = 0x4e; + +// Simd opcodes. +let kExprS128LoadMem = 0x00; +let kExprS128StoreMem = 0x01; +let kExprI32x4Splat = 0x0c; +let kExprI32x4Eq = 0x2c; +let kExprS1x4AllTrue = 0x75; +let kExprF32x4Min = 0x9e; + +class Binary { + constructor() { + this.length = 0; + this.buffer = new Uint8Array(8192); + } + + ensure_space(needed) { + if (this.buffer.length - this.length >= needed) return; + let new_capacity = this.buffer.length * 2; + while (new_capacity - this.length < needed) new_capacity *= 2; + let new_buffer = new Uint8Array(new_capacity); + new_buffer.set(this.buffer); + this.buffer = new_buffer; + } + + trunc_buffer() { + return new Uint8Array(this.buffer.buffer, 0, this.length); + } + + reset() { + this.length = 0; + } + + emit_u8(val) { + this.ensure_space(1); + this.buffer[this.length++] = val; + } + + emit_u16(val) { + this.ensure_space(2); + this.buffer[this.length++] = val; + this.buffer[this.length++] = val >> 8; + } + + emit_u32(val) { + this.ensure_space(4); + this.buffer[this.length++] = val; + this.buffer[this.length++] = val >> 8; + this.buffer[this.length++] = val >> 16; + this.buffer[this.length++] = val >> 24; + } + + emit_leb_u(val, max_len) { + this.ensure_space(max_len); + for (let i = 0; i < max_len; ++i) { + let v = val & 0xff; + val = val >>> 7; + if (val == 0) { + this.buffer[this.length++] = v; + return; + } + this.buffer[this.length++] = v | 0x80; + } + throw new Error("Leb value exceeds maximum length of " + max_len); + } + + emit_u32v(val) { + this.emit_leb_u(val, kMaxVarInt32Size); + } + + emit_u64v(val) { + this.emit_leb_u(val, kMaxVarInt64Size); + } + + emit_bytes(data) { + this.ensure_space(data.length); + this.buffer.set(data, this.length); + this.length += data.length; + } + + emit_string(string) { + // When testing illegal names, we pass a byte array directly. + if (string instanceof Array) { + this.emit_u32v(string.length); + this.emit_bytes(string); + return; + } + + // This is the hacky way to convert a JavaScript string to a UTF8 encoded + // string only containing single-byte characters. + let string_utf8 = unescape(encodeURIComponent(string)); + this.emit_u32v(string_utf8.length); + for (let i = 0; i < string_utf8.length; i++) { + this.emit_u8(string_utf8.charCodeAt(i)); + } + } + + emit_header() { + this.emit_bytes([ + kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 + ]); + } + + emit_section(section_code, content_generator) { + // Emit section name. + this.emit_u8(section_code); + // Emit the section to a temporary buffer: its full length isn't know yet. + const section = new Binary; + content_generator(section); + // Emit section length. + this.emit_u32v(section.length); + // Copy the temporary buffer. + // Avoid spread because {section} can be huge. + this.emit_bytes(section.trunc_buffer()); + } +} + +class WasmFunctionBuilder { + constructor(module, name, type_index) { + this.module = module; + this.name = name; + this.type_index = type_index; + this.body = []; + this.locals = []; + this.local_names = []; + } + + numLocalNames() { + let num_local_names = 0; + for (let loc_name of this.local_names) { + if (loc_name !== undefined) ++num_local_names; + } + return num_local_names; + } + + exportAs(name) { + this.module.addExport(name, this.index); + return this; + } + + exportFunc() { + this.exportAs(this.name); + return this; + } + + addBody(body) { + for (let b of body) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0 ) + throw new Error('invalid body (entries must be 8 bit numbers): ' + body); + } + this.body = body.slice(); + // Automatically add the end for the function block to the body. + this.body.push(kExprEnd); + return this; + } + + addBodyWithEnd(body) { + this.body = body; + return this; + } + + getNumLocals() { + let total_locals = 0; + for (let l of this.locals) { + for (let type of ["i32", "i64", "f32", "f64", "s128"]) { + total_locals += l[type + "_count"] || 0; + } + } + return total_locals; + } + + addLocals(locals, names) { + const old_num_locals = this.getNumLocals(); + this.locals.push(locals); + if (names) { + const missing_names = old_num_locals - this.local_names.length; + this.local_names.push(...new Array(missing_names), ...names); + } + return this; + } + + end() { + return this.module; + } +} + +class WasmGlobalBuilder { + constructor(module, type, mutable) { + this.module = module; + this.type = type; + this.mutable = mutable; + this.init = 0; + } + + exportAs(name) { + this.module.exports.push({name: name, kind: kExternalGlobal, + index: this.index}); + return this; + } +} + +class WasmTableBuilder { + constructor(module, type, initial_size, max_size) { + this.module = module; + this.type = type; + this.initial_size = initial_size; + this.has_max = max_size != undefined; + this.max_size = max_size; + } + + exportAs(name) { + this.module.exports.push({name: name, kind: kExternalTable, + index: this.index}); + return this; + } +} + +class WasmModuleBuilder { + constructor() { + this.types = []; + this.imports = []; + this.exports = []; + this.globals = []; + this.tables = []; + this.exceptions = []; + this.functions = []; + this.element_segments = []; + this.data_segments = []; + this.explicit = []; + this.num_imported_funcs = 0; + this.num_imported_globals = 0; + this.num_imported_tables = 0; + this.num_imported_exceptions = 0; + return this; + } + + addStart(start_index) { + this.start_index = start_index; + return this; + } + + addMemory(min, max, exp, shared) { + this.memory = {min: min, max: max, exp: exp, shared: shared}; + return this; + } + + addExplicitSection(bytes) { + this.explicit.push(bytes); + return this; + } + + stringToBytes(name) { + var result = new Binary(); + result.emit_string(name); + return result.trunc_buffer() + } + + createCustomSection(name, bytes) { + name = this.stringToBytes(name); + var section = new Binary(); + section.emit_u8(kUnknownSectionCode); + section.emit_u32v(name.length + bytes.length); + section.emit_bytes(name); + section.emit_bytes(bytes); + return section.trunc_buffer(); + } + + addCustomSection(name, bytes) { + this.explicit.push(this.createCustomSection(name, bytes)); + } + + addType(type) { + this.types.push(type); + var pl = type.params.length; // should have params + var rl = type.results.length; // should have results + return this.types.length - 1; + } + + addGlobal(local_type, mutable) { + let glob = new WasmGlobalBuilder(this, local_type, mutable); + glob.index = this.globals.length + this.num_imported_globals; + this.globals.push(glob); + return glob; + } + + addTable(type, initial_size, max_size = undefined) { + if (type != kWasmAnyRef && type != kWasmAnyFunc) { + throw new Error('Tables must be of type kWasmAnyRef or kWasmAnyFunc'); + } + let table = new WasmTableBuilder(this, type, initial_size, max_size); + table.index = this.tables.length + this.num_imported_tables; + this.tables.push(table); + return table; + } + + addException(type) { + let type_index = (typeof type) == "number" ? type : this.addType(type); + let except_index = this.exceptions.length + this.num_imported_exceptions; + this.exceptions.push(type_index); + return except_index; + } + + addFunction(name, type) { + let type_index = (typeof type) == "number" ? type : this.addType(type); + let func = new WasmFunctionBuilder(this, name, type_index); + func.index = this.functions.length + this.num_imported_funcs; + this.functions.push(func); + return func; + } + + addImport(module, name, type) { + if (this.functions.length != 0) { + throw new Error('Imported functions must be declared before local ones'); + } + let type_index = (typeof type) == "number" ? type : this.addType(type); + this.imports.push({module: module, name: name, kind: kExternalFunction, + type: type_index}); + return this.num_imported_funcs++; + } + + addImportedGlobal(module, name, type, mutable = false) { + if (this.globals.length != 0) { + throw new Error('Imported globals must be declared before local ones'); + } + let o = {module: module, name: name, kind: kExternalGlobal, type: type, + mutable: mutable}; + this.imports.push(o); + return this.num_imported_globals++; + } + + addImportedMemory(module, name, initial = 0, maximum, shared) { + let o = {module: module, name: name, kind: kExternalMemory, + initial: initial, maximum: maximum, shared: shared}; + this.imports.push(o); + return this; + } + + addImportedTable(module, name, initial, maximum, type) { + if (this.tables.length != 0) { + throw new Error('Imported tables must be declared before local ones'); + } + let o = {module: module, name: name, kind: kExternalTable, initial: initial, + maximum: maximum, type: type || kWasmAnyFunctionTypeForm}; + this.imports.push(o); + return this.num_imported_tables++; + } + + addImportedException(module, name, type) { + if (this.exceptions.length != 0) { + throw new Error('Imported exceptions must be declared before local ones'); + } + let type_index = (typeof type) == "number" ? type : this.addType(type); + let o = {module: module, name: name, kind: kExternalException, type: type_index}; + this.imports.push(o); + return this.num_imported_exceptions++; + } + + addExport(name, index) { + this.exports.push({name: name, kind: kExternalFunction, index: index}); + return this; + } + + addExportOfKind(name, kind, index) { + this.exports.push({name: name, kind: kind, index: index}); + return this; + } + + addDataSegment(addr, data, is_global = false) { + this.data_segments.push( + {addr: addr, data: data, is_global: is_global, is_active: true}); + return this.data_segments.length - 1; + } + + addPassiveDataSegment(data) { + this.data_segments.push({data: data, is_active: false}); + return this.data_segments.length - 1; + } + + exportMemoryAs(name) { + this.exports.push({name: name, kind: kExternalMemory, index: 0}); + } + + addElementSegment(table, base, is_global, array) { + this.element_segments.push({table: table, base: base, is_global: is_global, + array: array, is_active: true}); + return this; + } + + addPassiveElementSegment(array, is_import = false) { + this.element_segments.push({array: array, is_active: false}); + return this; + } + + appendToTable(array) { + for (let n of array) { + if (typeof n != 'number') + throw new Error('invalid table (entries have to be numbers): ' + array); + } + if (this.tables.length == 0) { + this.addTable(kWasmAnyFunc, 0); + } + // Adjust the table to the correct size. + let table = this.tables[0]; + const base = table.initial_size; + const table_size = base + array.length; + table.initial_size = table_size; + if (table.has_max && table_size > table.max_size) { + table.max_size = table_size; + } + return this.addElementSegment(0, base, false, array); + } + + setTableBounds(min, max = undefined) { + if (this.tables.length != 0) { + throw new Error("The table bounds of table '0' have already been set."); + } + this.addTable(kWasmAnyFunc, min, max); + return this; + } + + setName(name) { + this.name = name; + return this; + } + + toBuffer(debug = false) { + let binary = new Binary; + let wasm = this; + + // Add header + binary.emit_header(); + + // Add type section + if (wasm.types.length > 0) { + if (debug) print("emitting types @ " + binary.length); + binary.emit_section(kTypeSectionCode, section => { + section.emit_u32v(wasm.types.length); + for (let type of wasm.types) { + section.emit_u8(kWasmFunctionTypeForm); + section.emit_u32v(type.params.length); + for (let param of type.params) { + section.emit_u8(param); + } + section.emit_u32v(type.results.length); + for (let result of type.results) { + section.emit_u8(result); + } + } + }); + } + + // Add imports section + if (wasm.imports.length > 0) { + if (debug) print("emitting imports @ " + binary.length); + binary.emit_section(kImportSectionCode, section => { + section.emit_u32v(wasm.imports.length); + for (let imp of wasm.imports) { + section.emit_string(imp.module); + section.emit_string(imp.name || ''); + section.emit_u8(imp.kind); + if (imp.kind == kExternalFunction) { + section.emit_u32v(imp.type); + } else if (imp.kind == kExternalGlobal) { + section.emit_u32v(imp.type); + section.emit_u8(imp.mutable); + } else if (imp.kind == kExternalMemory) { + var has_max = (typeof imp.maximum) != "undefined"; + var is_shared = (typeof imp.shared) != "undefined"; + if (is_shared) { + section.emit_u8(has_max ? 3 : 2); // flags + } else { + section.emit_u8(has_max ? 1 : 0); // flags + } + section.emit_u32v(imp.initial); // initial + if (has_max) section.emit_u32v(imp.maximum); // maximum + } else if (imp.kind == kExternalTable) { + section.emit_u8(imp.type); + var has_max = (typeof imp.maximum) != "undefined"; + section.emit_u8(has_max ? 1 : 0); // flags + section.emit_u32v(imp.initial); // initial + if (has_max) section.emit_u32v(imp.maximum); // maximum + } else if (imp.kind == kExternalException) { + section.emit_u32v(kExceptionAttribute); + section.emit_u32v(imp.type); + } else { + throw new Error("unknown/unsupported import kind " + imp.kind); + } + } + }); + } + + // Add functions declarations + if (wasm.functions.length > 0) { + if (debug) print("emitting function decls @ " + binary.length); + binary.emit_section(kFunctionSectionCode, section => { + section.emit_u32v(wasm.functions.length); + for (let func of wasm.functions) { + section.emit_u32v(func.type_index); + } + }); + } + + // Add table section + if (wasm.tables.length > 0) { + if (debug) print ("emitting tables @ " + binary.length); + binary.emit_section(kTableSectionCode, section => { + section.emit_u32v(wasm.tables.length); + for (let table of wasm.tables) { + section.emit_u8(table.type); + section.emit_u8(table.has_max); + section.emit_u32v(table.initial_size); + if (table.has_max) section.emit_u32v(table.max_size); + } + }); + } + + // Add memory section + if (wasm.memory !== undefined) { + if (debug) print("emitting memory @ " + binary.length); + binary.emit_section(kMemorySectionCode, section => { + section.emit_u8(1); // one memory entry + const has_max = wasm.memory.max !== undefined; + const is_shared = wasm.memory.shared !== undefined; + // Emit flags (bit 0: reszeable max, bit 1: shared memory) + if (is_shared) { + section.emit_u8(has_max ? kSharedHasMaximumFlag : 2); + } else { + section.emit_u8(has_max ? kHasMaximumFlag : 0); + } + section.emit_u32v(wasm.memory.min); + if (has_max) section.emit_u32v(wasm.memory.max); + }); + } + + // Add global section. + if (wasm.globals.length > 0) { + if (debug) print ("emitting globals @ " + binary.length); + binary.emit_section(kGlobalSectionCode, section => { + section.emit_u32v(wasm.globals.length); + for (let global of wasm.globals) { + section.emit_u8(global.type); + section.emit_u8(global.mutable); + if ((typeof global.init_index) == "undefined") { + // Emit a constant initializer. + switch (global.type) { + case kWasmI32: + section.emit_u8(kExprI32Const); + section.emit_u32v(global.init); + break; + case kWasmI64: + section.emit_u8(kExprI64Const); + section.emit_u64v(global.init); + break; + case kWasmF32: + section.emit_bytes(wasmF32Const(global.init)); + break; + case kWasmF64: + section.emit_bytes(wasmF64Const(global.init)); + break; + case kWasmAnyFunc: + case kWasmAnyRef: + if (global.function_index !== undefined) { + section.emit_u8(kExprRefFunc); + section.emit_u32v(global.function_index); + } else { + section.emit_u8(kExprRefNull); + } + break; + } + } else { + // Emit a global-index initializer. + section.emit_u8(kExprGlobalGet); + section.emit_u32v(global.init_index); + } + section.emit_u8(kExprEnd); // end of init expression + } + }); + } + + // Add exceptions. + if (wasm.exceptions.length > 0) { + if (debug) print("emitting exceptions @ " + binary.length); + binary.emit_section(kExceptionSectionCode, section => { + section.emit_u32v(wasm.exceptions.length); + for (let type of wasm.exceptions) { + section.emit_u32v(kExceptionAttribute); + section.emit_u32v(type); + } + }); + } + + // Add export table. + var mem_export = (wasm.memory !== undefined && wasm.memory.exp); + var exports_count = wasm.exports.length + (mem_export ? 1 : 0); + if (exports_count > 0) { + if (debug) print("emitting exports @ " + binary.length); + binary.emit_section(kExportSectionCode, section => { + section.emit_u32v(exports_count); + for (let exp of wasm.exports) { + section.emit_string(exp.name); + section.emit_u8(exp.kind); + section.emit_u32v(exp.index); + } + if (mem_export) { + section.emit_string("memory"); + section.emit_u8(kExternalMemory); + section.emit_u8(0); + } + }); + } + + // Add start function section. + if (wasm.start_index !== undefined) { + if (debug) print("emitting start function @ " + binary.length); + binary.emit_section(kStartSectionCode, section => { + section.emit_u32v(wasm.start_index); + }); + } + + // Add element segments + if (wasm.element_segments.length > 0) { + if (debug) print("emitting element segments @ " + binary.length); + binary.emit_section(kElementSectionCode, section => { + var inits = wasm.element_segments; + section.emit_u32v(inits.length); + + for (let init of inits) { + if (init.is_active) { + // Active segment. + if (init.table == 0) { + section.emit_u32v(kActiveNoIndex); + } else { + section.emit_u32v(kActiveWithIndex); + section.emit_u32v(init.table); + } + if (init.is_global) { + section.emit_u8(kExprGlobalGet); + } else { + section.emit_u8(kExprI32Const); + } + section.emit_u32v(init.base); + section.emit_u8(kExprEnd); + if (init.table != 0) { + section.emit_u8(kExternalFunction); + } + section.emit_u32v(init.array.length); + for (let index of init.array) { + section.emit_u32v(index); + } + } else { + // Passive segment. + section.emit_u8(kPassiveWithElements); // flags + section.emit_u8(kWasmAnyFunc); + section.emit_u32v(init.array.length); + for (let index of init.array) { + if (index === null) { + section.emit_u8(kExprRefNull); + section.emit_u8(kExprEnd); + } else { + section.emit_u8(kExprRefFunc); + section.emit_u32v(index); + section.emit_u8(kExprEnd); + } + } + } + } + }); + } + + // If there are any passive data segments, add the DataCount section. + if (wasm.data_segments.some(seg => !seg.is_active)) { + binary.emit_section(kDataCountSectionCode, section => { + section.emit_u32v(wasm.data_segments.length); + }); + } + + // Add function bodies. + if (wasm.functions.length > 0) { + // emit function bodies + if (debug) print("emitting code @ " + binary.length); + binary.emit_section(kCodeSectionCode, section => { + section.emit_u32v(wasm.functions.length); + let header = new Binary; + for (let func of wasm.functions) { + header.reset(); + // Function body length will be patched later. + let local_decls = []; + for (let l of func.locals || []) { + if (l.i32_count > 0) { + local_decls.push({count: l.i32_count, type: kWasmI32}); + } + if (l.i64_count > 0) { + local_decls.push({count: l.i64_count, type: kWasmI64}); + } + if (l.f32_count > 0) { + local_decls.push({count: l.f32_count, type: kWasmF32}); + } + if (l.f64_count > 0) { + local_decls.push({count: l.f64_count, type: kWasmF64}); + } + if (l.s128_count > 0) { + local_decls.push({count: l.s128_count, type: kWasmS128}); + } + if (l.anyref_count > 0) { + local_decls.push({count: l.anyref_count, type: kWasmAnyRef}); + } + if (l.anyfunc_count > 0) { + local_decls.push({count: l.anyfunc_count, type: kWasmAnyFunc}); + } + } + + header.emit_u32v(local_decls.length); + for (let decl of local_decls) { + header.emit_u32v(decl.count); + header.emit_u8(decl.type); + } + + section.emit_u32v(header.length + func.body.length); + section.emit_bytes(header.trunc_buffer()); + section.emit_bytes(func.body); + } + }); + } + + // Add data segments. + if (wasm.data_segments.length > 0) { + if (debug) print("emitting data segments @ " + binary.length); + binary.emit_section(kDataSectionCode, section => { + section.emit_u32v(wasm.data_segments.length); + for (let seg of wasm.data_segments) { + if (seg.is_active) { + section.emit_u8(0); // linear memory index 0 / flags + if (seg.is_global) { + // initializer is a global variable + section.emit_u8(kExprGlobalGet); + section.emit_u32v(seg.addr); + } else { + // initializer is a constant + section.emit_u8(kExprI32Const); + section.emit_u32v(seg.addr); + } + section.emit_u8(kExprEnd); + } else { + section.emit_u8(kPassive); // flags + } + section.emit_u32v(seg.data.length); + section.emit_bytes(seg.data); + } + }); + } + + // Add any explicitly added sections + for (let exp of wasm.explicit) { + if (debug) print("emitting explicit @ " + binary.length); + binary.emit_bytes(exp); + } + + // Add names. + let num_function_names = 0; + let num_functions_with_local_names = 0; + for (let func of wasm.functions) { + if (func.name !== undefined) ++num_function_names; + if (func.numLocalNames() > 0) ++num_functions_with_local_names; + } + if (num_function_names > 0 || num_functions_with_local_names > 0 || + wasm.name !== undefined) { + if (debug) print('emitting names @ ' + binary.length); + binary.emit_section(kUnknownSectionCode, section => { + section.emit_string('name'); + // Emit module name. + if (wasm.name !== undefined) { + section.emit_section(kModuleNameCode, name_section => { + name_section.emit_string(wasm.name); + }); + } + // Emit function names. + if (num_function_names > 0) { + section.emit_section(kFunctionNamesCode, name_section => { + name_section.emit_u32v(num_function_names); + for (let func of wasm.functions) { + if (func.name === undefined) continue; + name_section.emit_u32v(func.index); + name_section.emit_string(func.name); + } + }); + } + // Emit local names. + if (num_functions_with_local_names > 0) { + section.emit_section(kLocalNamesCode, name_section => { + name_section.emit_u32v(num_functions_with_local_names); + for (let func of wasm.functions) { + if (func.numLocalNames() == 0) continue; + name_section.emit_u32v(func.index); + name_section.emit_u32v(func.numLocalNames()); + for (let i = 0; i < func.local_names.length; ++i) { + if (func.local_names[i] === undefined) continue; + name_section.emit_u32v(i); + name_section.emit_string(func.local_names[i]); + } + } + }); + } + }); + } + + return binary.trunc_buffer(); + } + + toArray(debug = false) { + return Array.from(this.toBuffer(debug)); + } + + instantiate(ffi) { + let module = this.toModule(); + let instance = new WebAssembly.Instance(module, ffi); + return instance; + } + + asyncInstantiate(ffi) { + return WebAssembly.instantiate(this.toBuffer(), ffi) + .then(({module, instance}) => instance); + } + + toModule(debug = false) { + return new WebAssembly.Module(this.toBuffer(debug)); + } +} + +function wasmSignedLeb(val, max_len = 5) { + let res = []; + for (let i = 0; i < max_len; ++i) { + let v = val & 0x7f; + // If {v} sign-extended from 7 to 32 bits is equal to val, we are done. + if (((v << 25) >> 25) == val) { + res.push(v); + return res; + } + res.push(v | 0x80); + val = val >> 7; + } + throw new Error( + 'Leb value <' + val + '> exceeds maximum length of ' + max_len); +} + +function wasmI32Const(val) { + return [kExprI32Const, ...wasmSignedLeb(val, 5)]; +} + +function wasmF32Const(f) { + // Write in little-endian order at offset 0. + data_view.setFloat32(0, f, true); + return [ + kExprF32Const, byte_view[0], byte_view[1], byte_view[2], byte_view[3] + ]; +} + +function wasmF64Const(f) { + // Write in little-endian order at offset 0. + data_view.setFloat64(0, f, true); + return [ + kExprF64Const, byte_view[0], byte_view[1], byte_view[2], + byte_view[3], byte_view[4], byte_view[5], byte_view[6], byte_view[7] + ]; +} diff --git a/test/fixtures/wpt/wasm/webapi/META.yml b/test/fixtures/wpt/wasm/webapi/META.yml new file mode 100644 index 00000000000000..69715cd7c8dcf5 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/META.yml @@ -0,0 +1 @@ +spec: https://webassembly.github.io/spec/web-api/ diff --git a/test/fixtures/wpt/wasm/webapi/abort.any.js b/test/fixtures/wpt/wasm/webapi/abort.any.js new file mode 100644 index 00000000000000..f5ddd353aa9e35 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/abort.any.js @@ -0,0 +1,37 @@ +const methods = [ + "compileStreaming", + "instantiateStreaming", +]; + +for (const method of methods) { + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + controller.abort(); + const request = fetch('../incrementer.wasm', { signal }); + return promise_rejects_dom(t, 'AbortError', WebAssembly[method](request), + `${method} should reject`); + }, `${method}() on an already-aborted request should reject with AbortError`); + + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + const request = fetch('../incrementer.wasm', { signal }); + const promise = WebAssembly[method](request); + controller.abort(); + return promise_rejects_dom(t, 'AbortError', promise, `${method} should reject`); + }, `${method}() synchronously followed by abort should reject with AbortError`); + + promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + return fetch('../incrementer.wasm', { signal }) + .then(response => { + Promise.resolve().then(() => controller.abort()); + return WebAssembly[method](response); + }) + .catch(err => { + assert_equals(err.name, "AbortError"); + }); + }, `${method}() asynchronously racing with abort should succeed or reject with AbortError`); +} diff --git a/test/fixtures/wpt/wasm/webapi/body.any.js b/test/fixtures/wpt/wasm/webapi/body.any.js new file mode 100644 index 00000000000000..4db7e8d123cfb6 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/body.any.js @@ -0,0 +1,19 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + argument.arrayBuffer(); + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method} after consumption`); + + promise_test(t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + const promise = WebAssembly[method](argument); + argument.arrayBuffer(); + return promise_rejects_js(t, TypeError, promise); + }, `${method} before consumption`); +} diff --git a/test/fixtures/wpt/wasm/webapi/contenttype.any.js b/test/fixtures/wpt/wasm/webapi/contenttype.any.js new file mode 100644 index 00000000000000..0a2f5f1122ce46 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/contenttype.any.js @@ -0,0 +1,64 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/assertions.js + +promise_test(t => { + const response = fetch("/wasm/incrementer.wasm").then(res => new Response(res.body)); + return promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(response)); +}, "Response with no Content-Type: compileStreaming"); + +promise_test(t => { + const response = fetch("/wasm/incrementer.wasm").then(res => new Response(res.body)); + return promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(response)); +}, "Response with no Content-Type: instantiateStreaming"); + +const invalidContentTypes = [ + "", + "application/javascript", + "application/octet-stream", + "text/wasm", + "application/wasm;", + "application/wasm;x", + "application/wasm;charset=UTF-8", +]; + +for (const contenttype of invalidContentTypes) { + promise_test(t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + return promise_rejects_js(t, TypeError, WebAssembly.compileStreaming(response)); + }, `Response with Content-Type ${format_value(contenttype)}: compileStreaming`); + + promise_test(t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + return promise_rejects_js(t, TypeError, WebAssembly.instantiateStreaming(response)); + }, `Response with Content-Type ${format_value(contenttype)}: instantiateStreaming`); +} + +const validContentTypes = [ + "application/wasm", + "APPLICATION/wasm", + "APPLICATION/WASM", +]; + +for (const contenttype of validContentTypes) { + promise_test(async t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + const module = await WebAssembly.compileStreaming(response); + assert_equals(Object.getPrototypeOf(module), WebAssembly.Module.prototype, + "prototype"); + }, `Response with Content-Type ${format_value(contenttype)}: compileStreaming`); + + promise_test(async t => { + const response = fetch(`/wasm/incrementer.wasm?pipe=header(Content-Type,${encodeURIComponent(contenttype)})`); + const result = await WebAssembly.instantiateStreaming(response); + assert_WebAssemblyInstantiatedSource( + result, + { + "increment": { + "kind": "function", + "name": "0", + "length": 1 + } + } + ); + }, `Response with Content-Type ${format_value(contenttype)}: instantiateStreaming`); +} diff --git a/test/fixtures/wpt/wasm/webapi/empty-body.any.js b/test/fixtures/wpt/wasm/webapi/empty-body.any.js new file mode 100644 index 00000000000000..0771647b708ec3 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/empty-body.any.js @@ -0,0 +1,20 @@ +// META: global=window,worker + +const invalidArguments = [ + [() => new Response(undefined, { headers: { "Content-Type": "application/wasm" } }), "no body"], + [() => new Response("", { headers: { "Content-Type": "application/wasm" } }), "empty body"], +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const [argumentFactory, name] of invalidArguments) { + promise_test(t => { + const argument = argumentFactory(); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](argument)); + }, `${method}: ${name}`); + + promise_test(t => { + const argument = Promise.resolve(argumentFactory()); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](argument)); + }, `${method}: ${name} in a promise`); + } +} diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/execute-start.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/execute-start.tentative.html new file mode 100644 index 00000000000000..a35adbe8ebfc1c --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/execute-start.tentative.html @@ -0,0 +1,23 @@ + +Check execution of WebAssembly start function + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/exported-names.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/exported-names.tentative.html new file mode 100644 index 00000000000000..16a9c59787bc94 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/exported-names.tentative.html @@ -0,0 +1,17 @@ + +Exported names from a WebAssembly module + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/invalid-bytecode.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/invalid-bytecode.tentative.html new file mode 100644 index 00000000000000..0e447dbee51042 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/invalid-bytecode.tentative.html @@ -0,0 +1,24 @@ + +Handling of importing invalid WebAssembly modules + + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html new file mode 100644 index 00000000000000..f45e06ece5f3bc --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle-errors.tentative.html @@ -0,0 +1,38 @@ + +Cyclic linking between JavaScript and WebAssembly (JS higher) + + + + + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html new file mode 100644 index 00000000000000..38b0d3203c27d8 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/js-wasm-cycle.tentative.html @@ -0,0 +1,11 @@ + +Check bindings in JavaScript and WebAssembly cycle (JS higher) + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/module-parse-error.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/module-parse-error.tentative.html new file mode 100644 index 00000000000000..0e447dbee51042 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/module-parse-error.tentative.html @@ -0,0 +1,24 @@ + +Handling of importing invalid WebAssembly modules + + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.js new file mode 100644 index 00000000000000..e0dcf493f8e780 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.js @@ -0,0 +1 @@ +export { f } from "./resources/resolve-export.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.tentative.html new file mode 100644 index 00000000000000..14688221021d53 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resolve-export.tentative.html @@ -0,0 +1,25 @@ + +Check ResolveExport on invalid re-export from WebAssembly + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/execute-start.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/execute-start.wasm new file mode 100644 index 00000000000000..ecfdda1f9af82c Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/execute-start.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/exported-names.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/exported-names.wasm new file mode 100644 index 00000000000000..ebffad193c342c Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/exported-names.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm new file mode 100644 index 00000000000000..1ae8b721f3be9c Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-bytecode.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-module.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-module.wasm new file mode 100644 index 00000000000000..dd711f0953bf8f Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/invalid-module.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js new file mode 100644 index 00000000000000..06cb8a0ad9f328 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.js @@ -0,0 +1,2 @@ +export const func = 42; +import { f } from "./js-wasm-cycle-function-error.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm new file mode 100644 index 00000000000000..b89d94dde74dc7 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-function-error.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js new file mode 100644 index 00000000000000..1f375b8ce1576a --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.js @@ -0,0 +1,2 @@ +export const glob = new WebAssembly.Global({ value: "i32" }, 42); +import { f } from "./js-wasm-cycle-global.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm new file mode 100644 index 00000000000000..2a9017f87b123e Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-global.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js new file mode 100644 index 00000000000000..92e37a86acd866 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.js @@ -0,0 +1,2 @@ +export const mem = new WebAssembly.Memory({ initial: 10 }); +import { f } from "./js-wasm-cycle-memory.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm new file mode 100644 index 00000000000000..e699a9b3c47c49 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-memory.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js new file mode 100644 index 00000000000000..5d6794489f0e16 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.js @@ -0,0 +1,2 @@ +export const tab = new WebAssembly.Table({ element: "anyfunc" }); +import { f } from "./js-wasm-cycle-table.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm new file mode 100644 index 00000000000000..ec4883e652e468 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-table.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js new file mode 100644 index 00000000000000..f7b0d62080b31d --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.js @@ -0,0 +1,2 @@ +export const val = 42; +import { f } from "./js-wasm-cycle-value.wasm"; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm new file mode 100644 index 00000000000000..083409e2606b67 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle-value.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.js new file mode 100644 index 00000000000000..8ee579e2ad3421 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.js @@ -0,0 +1,13 @@ +function f() { return 42; } +export { f }; + +import { mem, tab, glob, func } from "./js-wasm-cycle.wasm"; +assert_true(glob instanceof WebAssembly.Global); +assert_equals(glob.valueOf(), 1); +assert_true(mem instanceof WebAssembly.Memory); +assert_true(tab instanceof WebAssembly.Table); +assert_true(func instanceof Function); + +f = () => { return 24 }; + +assert_equals(func(), 42); diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm new file mode 100644 index 00000000000000..77a3b86ab67528 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/js-wasm-cycle.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/log.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/log.js new file mode 100644 index 00000000000000..0c4f5ed519b0fd --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/log.js @@ -0,0 +1 @@ +export function logExec() { log.push("executed"); } diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/resolve-export.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/resolve-export.wasm new file mode 100644 index 00000000000000..d8fc92d022fbf4 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/resolve-export.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm new file mode 100644 index 00000000000000..f9f0cf27992d4b Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-i64-global.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm new file mode 100644 index 00000000000000..0ee948f96fdfbb Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-export-to-wasm.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm new file mode 100644 index 00000000000000..c27bcb068de86e Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-error-from-wasm.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm new file mode 100644 index 00000000000000..652ff143100f83 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-from-wasm.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.js new file mode 100644 index 00000000000000..78982c32dc69d7 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.js @@ -0,0 +1 @@ +export let f = 5; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.wasm new file mode 100644 index 00000000000000..2f23c58520fe59 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-func.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.js new file mode 100644 index 00000000000000..4258cd2d7d1e95 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.js @@ -0,0 +1 @@ +export let g = 5; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.wasm new file mode 100644 index 00000000000000..2f8bd77940cc21 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-global.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.js new file mode 100644 index 00000000000000..4cee8898383784 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.js @@ -0,0 +1 @@ +export let m = 5; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm new file mode 100644 index 00000000000000..d9474047cd334b Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-memory.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.js new file mode 100644 index 00000000000000..ca823646cbb365 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.js @@ -0,0 +1 @@ +export let t = 5; diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.wasm new file mode 100644 index 00000000000000..8ccc8be7f21382 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-import-table.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.js new file mode 100644 index 00000000000000..161edab4f6f1f1 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.js @@ -0,0 +1,15 @@ +import * as mod from "./wasm-js-cycle.wasm"; + +let jsGlob = new WebAssembly.Global({ value: "i32", mutable: true }, 42); +let jsMem = new WebAssembly.Memory({ initial: 10 }); +let jsTab = new WebAssembly.Table({ initial: 10, element: "anyfunc" }); +let jsFunc = () => { return 42; }; + +export { jsGlob, jsMem, jsTab, jsFunc }; + +export function mutateBindings() { + jsGlob = 0; + jsMem = 0; + jsTab = 0; + jsFunc = 0; +} diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm new file mode 100644 index 00000000000000..b700377b279031 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/wasm-js-cycle.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker-helper.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker-helper.js new file mode 100644 index 00000000000000..277bb4c1ea5b79 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker-helper.js @@ -0,0 +1 @@ +export function pm(x) { postMessage(x); } diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.js b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.js new file mode 100644 index 00000000000000..c72464f71a4561 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.js @@ -0,0 +1 @@ +import * as mod from "./worker.wasm" diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.wasm b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.wasm new file mode 100644 index 00000000000000..e942dc54acf77e Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/esm-integration/resources/worker.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html new file mode 100644 index 00000000000000..3761a22f218db0 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import-wasm-export.tentative.html @@ -0,0 +1,14 @@ + +Check import and export between WebAssembly modules + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import.tentative.html new file mode 100644 index 00000000000000..243cfd46e4bdf0 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-import.tentative.html @@ -0,0 +1,34 @@ + +Errors for imports of WebAssembly modules + + + + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html new file mode 100644 index 00000000000000..298d4d40b0006e --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-js-cycle.tentative.html @@ -0,0 +1,32 @@ + +Check bindings in JavaScript and WebAssembly cycle (Wasm higher) + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html new file mode 100644 index 00000000000000..6c43e72b09bfe7 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/wasm-to-wasm-link-error.tentative.html @@ -0,0 +1,26 @@ + +Errors for linking WebAssembly module scripts + + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/worker-import.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/worker-import.tentative.html new file mode 100644 index 00000000000000..739f2d3f28cc68 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/worker-import.tentative.html @@ -0,0 +1,13 @@ + +Testing import of WebAssembly from JavaScript worker + + + + diff --git a/test/fixtures/wpt/wasm/webapi/esm-integration/worker.tentative.html b/test/fixtures/wpt/wasm/webapi/esm-integration/worker.tentative.html new file mode 100644 index 00000000000000..8002e07ce7f1cf --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/esm-integration/worker.tentative.html @@ -0,0 +1,13 @@ + +Testing WebAssembly worker + + + + diff --git a/test/fixtures/wpt/wasm/webapi/historical.any.js b/test/fixtures/wpt/wasm/webapi/historical.any.js new file mode 100644 index 00000000000000..257112c4160f54 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/historical.any.js @@ -0,0 +1,29 @@ +// META: global=window,worker + +promise_test(async t => { + const db_name = "WebAssembly"; + const obj_store = "store"; + const module_key = "module"; + + await new Promise((resolve, reject) => { + const delete_request = indexedDB.deleteDatabase(db_name); + delete_request.onsuccess = resolve; + delete_request.onerror = reject; + }); + + const db = await new Promise((resolve, reject) => { + const open_request = indexedDB.open(db_name); + open_request.onupgradeneeded = function() { + open_request.result.createObjectStore(obj_store); + }; + open_request.onsuccess = function() { + resolve(open_request.result); + }; + open_request.onerror = reject; + }); + + const mod = await WebAssembly.compileStreaming(fetch('../incrementer.wasm')); + const tx = db.transaction(obj_store, 'readwrite'); + const store = tx.objectStore(obj_store); + assert_throws_dom("DataCloneError", () => store.put(mod, module_key)); +}); diff --git a/test/fixtures/wpt/wasm/webapi/idlharness.any.js b/test/fixtures/wpt/wasm/webapi/idlharness.any.js new file mode 100644 index 00000000000000..0c4669e6caa7b2 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/idlharness.any.js @@ -0,0 +1,10 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +"use strict"; + +idl_test( + ["wasm-web-api"], + ["wasm-js-api"], + idl_array => {} +); diff --git a/test/fixtures/wpt/wasm/webapi/instantiateStreaming-bad-imports.any.js b/test/fixtures/wpt/wasm/webapi/instantiateStreaming-bad-imports.any.js new file mode 100644 index 00000000000000..38ecc40252e51d --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/instantiateStreaming-bad-imports.any.js @@ -0,0 +1,13 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/bad-imports.js + +test_bad_imports((name, error, build, ...args) => { + promise_test(t => { + const builder = new WasmModuleBuilder(); + build(builder); + const buffer = builder.toBuffer(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, error, WebAssembly.instantiateStreaming(response, ...args)); + }, name); +}); diff --git a/test/fixtures/wpt/wasm/webapi/instantiateStreaming.any.js b/test/fixtures/wpt/wasm/webapi/instantiateStreaming.any.js new file mode 100644 index 00000000000000..cf3a5e7331f30e --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/instantiateStreaming.any.js @@ -0,0 +1,49 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/instanceTestFactory.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +for (const [name, fn] of instanceTestFactory) { + promise_test(async () => { + const { buffer, args, exports, verify } = fn(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + const result = await WebAssembly.instantiateStreaming(response, ...args); + assert_WebAssemblyInstantiatedSource(result, exports); + verify(result.instance); + }, name); +} + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + builder.addImportedGlobal("module", "global", kWasmI32); + const buffer = builder.toBuffer(); + const response = new Response(buffer, { "headers": { "Content-Type": "application/wasm" } }); + const order = []; + + const imports = { + get module() { + order.push("module getter"); + return { + get global() { + order.push("global getter"); + return 0; + }, + } + }, + }; + + const expected = [ + "module getter", + "global getter", + ]; + const p = WebAssembly.instantiateStreaming(response, imports); + assert_array_equals(order, []); + const result = await p; + assert_WebAssemblyInstantiatedSource(result, {}); + assert_array_equals(order, expected); +}, "Synchronous options handling"); diff --git a/test/fixtures/wpt/wasm/webapi/invalid-args.any.js b/test/fixtures/wpt/wasm/webapi/invalid-args.any.js new file mode 100644 index 00000000000000..b27e018a984e39 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/invalid-args.any.js @@ -0,0 +1,28 @@ +// META: global=window,worker + +const invalidArguments = [ + [undefined], + [null], + [true], + ["test"], + [Symbol()], + [0], + [0.1], + [NaN], + [{}, "Empty object"], + [Response, "Response interface object"], + [Response.prototype, "Response interface prototype object"], +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const [argument, name = format_value(argument)] of invalidArguments) { + promise_test(t => { + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method}: ${name}`); + + promise_test(t => { + const promise = Promise.resolve(argument); + return promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + }, `${method}: ${name} in a promise`); + } +} diff --git a/test/fixtures/wpt/wasm/webapi/invalid-code.any.js b/test/fixtures/wpt/wasm/webapi/invalid-code.any.js new file mode 100644 index 00000000000000..37373d49971a3d --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/invalid-code.any.js @@ -0,0 +1,21 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +let emptyModuleBinary; +setup(() => { + emptyModuleBinary = new WasmModuleBuilder().toBuffer(); +}); + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0, 0])); + const response = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](response)); + }, `Invalid code (0x0000): ${method}`); + + promise_test(t => { + const buffer = new Uint8Array(Array.from(emptyModuleBinary).concat([0xCA, 0xFE])); + const response = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + return promise_rejects_js(t, WebAssembly.CompileError, WebAssembly[method](response)); + }, `Invalid code (0xCAFE): ${method}`); +} diff --git a/test/fixtures/wpt/wasm/webapi/modified-contenttype.any.js b/test/fixtures/wpt/wasm/webapi/modified-contenttype.any.js new file mode 100644 index 00000000000000..354930517c73ea --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/modified-contenttype.any.js @@ -0,0 +1,24 @@ +// META: global=window,worker +// META: script=/wasm/jsapi/wasm-module-builder.js + +["compileStreaming", "instantiateStreaming"].forEach(method => { + promise_test(async t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "test/test" } }); + argument.headers.set("Content-Type", "application/wasm"); + // This should resolve successfully + await WebAssembly[method](argument); + // Ensure body can only be read once + return promise_rejects_js(t, TypeError, argument.blob()); + }, `${method} with Content-Type set late`); + + promise_test(async t => { + const buffer = new WasmModuleBuilder().toBuffer(); + const argument = new Response(buffer, { headers: { "Content-Type": "application/wasm" } }); + argument.headers.delete("Content-Type"); + // Ensure Wasm cannot be created + await promise_rejects_js(t, TypeError, WebAssembly[method](argument)); + // This should resolve successfully + await argument.arrayBuffer(); + }, `${method} with Content-Type removed late`); +}); diff --git a/test/fixtures/wpt/wasm/webapi/origin.sub.any.js b/test/fixtures/wpt/wasm/webapi/origin.sub.any.js new file mode 100644 index 00000000000000..bf7901eeddee7c --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/origin.sub.any.js @@ -0,0 +1,15 @@ +// META: global=window,worker + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const url = "http://{{domains[www]}}:{{ports[http][0]}}/wasm/incrementer.wasm"; + const response = fetch(url, { "mode": "no-cors" }); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Opaque response: ${method}`); + + promise_test(t => { + const url = "/fetch/api/resources/redirect.py?redirect_status=301&location=/wasm/incrementer.wasm"; + const response = fetch(url, { "mode": "no-cors", "redirect": "manual" }); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Opaque redirect response: ${method}`); +} diff --git a/test/fixtures/wpt/wasm/webapi/rejected-arg.any.js b/test/fixtures/wpt/wasm/webapi/rejected-arg.any.js new file mode 100644 index 00000000000000..49018db5e89eb0 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/rejected-arg.any.js @@ -0,0 +1,9 @@ +// META: global=window,worker + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + promise_test(t => { + const error = { "name": "custom error" }; + const promise = Promise.reject(error); + return promise_rejects_exactly(t, error, WebAssembly[method](promise)); + }, `${method}`); +} diff --git a/test/fixtures/wpt/wasm/webapi/resources/incrementer.no_mime_type.wasm b/test/fixtures/wpt/wasm/webapi/resources/incrementer.no_mime_type.wasm new file mode 100644 index 00000000000000..47afcdef2a2812 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/resources/incrementer.no_mime_type.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm new file mode 100644 index 00000000000000..47afcdef2a2812 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm.headers b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm.headers new file mode 100644 index 00000000000000..76b9c163b6cd33 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wasm.headers @@ -0,0 +1,2 @@ +Content-Type: application/wasm +Cache-Control: max-age=3600 diff --git a/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm new file mode 100644 index 00000000000000..47afcdef2a2812 Binary files /dev/null and b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm differ diff --git a/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers new file mode 100644 index 00000000000000..833ee71634def2 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/resources/incrementer.wrong_mime_type.wasm.headers @@ -0,0 +1,2 @@ +Content-Type: text/css +Cache-Control: max-age=3600 diff --git a/test/fixtures/wpt/wasm/webapi/status.any.js b/test/fixtures/wpt/wasm/webapi/status.any.js new file mode 100644 index 00000000000000..f3859646cc54b9 --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/status.any.js @@ -0,0 +1,21 @@ +// META: global=window,worker + +const statuses = [ + 0, + 300, + 400, + 404, + 500, + 600, + 700, + 999, +]; + +for (const method of ["compileStreaming", "instantiateStreaming"]) { + for (const status of statuses) { + promise_test(t => { + const response = fetch(`status.py?status=${status}`); + return promise_rejects_js(t, TypeError, WebAssembly[method](response)); + }, `Response with status ${status}: ${method}`); + } +} diff --git a/test/fixtures/wpt/wasm/webapi/wasm_stream_compile_test.html b/test/fixtures/wpt/wasm/webapi/wasm_stream_compile_test.html new file mode 100644 index 00000000000000..790410e425befd --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/wasm_stream_compile_test.html @@ -0,0 +1,115 @@ + + +WebAssembly.compileStreaming + + + + diff --git a/test/fixtures/wpt/wasm/webapi/wasm_stream_instantiate_test.html b/test/fixtures/wpt/wasm/webapi/wasm_stream_instantiate_test.html new file mode 100644 index 00000000000000..f39f6504953f5e --- /dev/null +++ b/test/fixtures/wpt/wasm/webapi/wasm_stream_instantiate_test.html @@ -0,0 +1,115 @@ + + +WebAssembly.instantiateStreaming + + + + diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index d5f69e8165f61c..d5bf6e133ebc99 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -42,6 +42,7 @@ const expectedModules = new Set([ 'Internal Binding util', 'Internal Binding uv', 'Internal Binding v8', + 'Internal Binding wasm_web_api', 'Internal Binding worker', 'NativeModule buffer', 'NativeModule events', diff --git a/test/parallel/test-fetch-disabled.mjs b/test/parallel/test-fetch-disabled.mjs index f06d484701c3ec..ea6b6807d8dbb5 100644 --- a/test/parallel/test-fetch-disabled.mjs +++ b/test/parallel/test-fetch-disabled.mjs @@ -8,3 +8,6 @@ assert.strictEqual(typeof globalThis.FormData, 'undefined'); assert.strictEqual(typeof globalThis.Headers, 'undefined'); assert.strictEqual(typeof globalThis.Request, 'undefined'); assert.strictEqual(typeof globalThis.Response, 'undefined'); + +assert.strictEqual(typeof WebAssembly.compileStreaming, 'undefined'); +assert.strictEqual(typeof WebAssembly.instantiateStreaming, 'undefined'); diff --git a/test/parallel/test-wasm-web-api.js b/test/parallel/test-wasm-web-api.js new file mode 100644 index 00000000000000..9576e13d669582 --- /dev/null +++ b/test/parallel/test-wasm-web-api.js @@ -0,0 +1,226 @@ +'use strict'; + +const common = require('../common'); +const fixtures = require('../common/fixtures'); + +const assert = require('assert'); +const events = require('events'); +const fs = require('fs/promises'); +const { createServer } = require('http'); + +assert.strictEqual(typeof WebAssembly.compileStreaming, 'function'); +assert.strictEqual(typeof WebAssembly.instantiateStreaming, 'function'); + +const simpleWasmBytes = fixtures.readSync('simple.wasm'); + +// Sets up an HTTP server with the given response handler and calls fetch() to +// obtain a Response from the newly created server. +async function testRequest(handler) { + const server = createServer((_, res) => handler(res)).unref().listen(0); + await events.once(server, 'listening'); + const { port } = server.address(); + return fetch(`http://127.0.0.1:${port}/`); +} + +// Runs the given function both with the promise itself and as a continuation +// of the promise. We use this to test that the API accepts not just a Response +// but also a Promise that resolves to a Response. +function withPromiseAndResolved(makePromise, consume) { + return Promise.all([ + consume(makePromise()), + makePromise().then(consume), + ]); +} + +// The makeResponsePromise function must return a Promise that resolves to a +// Response. The checkResult function receives the Promise returned by +// WebAssembly.compileStreaming and must return a Promise itself. +function testCompileStreaming(makeResponsePromise, checkResult) { + return withPromiseAndResolved( + common.mustCall(makeResponsePromise, 2), + common.mustCall((response) => { + return checkResult(WebAssembly.compileStreaming(response)); + }, 2) + ); +} + +function testCompileStreamingSuccess(makeResponsePromise) { + return testCompileStreaming(makeResponsePromise, async (modPromise) => { + const mod = await modPromise; + assert.strictEqual(mod.constructor, WebAssembly.Module); + }); +} + +function testCompileStreamingRejection(makeResponsePromise, rejection) { + return testCompileStreaming(makeResponsePromise, (modPromise) => { + assert.strictEqual(modPromise.constructor, Promise); + return assert.rejects(modPromise, rejection); + }); +} + +function testCompileStreamingSuccessUsingFetch(responseCallback) { + return testCompileStreamingSuccess(() => testRequest(responseCallback)); +} + +function testCompileStreamingRejectionUsingFetch(responseCallback, rejection) { + return testCompileStreamingRejection(() => testRequest(responseCallback), + rejection); +} + +(async () => { + // A non-Response should cause a TypeError. + for (const invalid of [undefined, null, 0, true, 'foo', {}, [], Symbol()]) { + await withPromiseAndResolved(() => Promise.resolve(invalid), (arg) => { + return assert.rejects(() => WebAssembly.compileStreaming(arg), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: /^The "source" argument .*$/ + }); + }); + } + + // When given a Promise, any rejection should be propagated as-is. + { + const err = new RangeError('foo'); + await assert.rejects(() => { + return WebAssembly.compileStreaming(Promise.reject(err)); + }, (actualError) => actualError === err); + } + + // A valid WebAssembly file with the correct MIME type. + await testCompileStreamingSuccessUsingFetch((res) => { + res.setHeader('Content-Type', 'application/wasm'); + res.end(simpleWasmBytes); + }); + + // The same valid WebAssembly file with the same MIME type, but using a + // Response whose body is a Buffer instead of calling fetch(). + await testCompileStreamingSuccess(() => { + return Promise.resolve(new Response(simpleWasmBytes, { + status: 200, + headers: { 'Content-Type': 'application/wasm' } + })); + }); + + // The same valid WebAssembly file with the same MIME type, but using a + // Response whose body is a ReadableStream instead of calling fetch(). + await testCompileStreamingSuccess(async () => { + const handle = await fs.open(fixtures.path('simple.wasm')); + const stream = handle.readableWebStream(); + return Promise.resolve(new Response(stream, { + status: 200, + headers: { 'Content-Type': 'application/wasm' } + })); + }); + + // A larger valid WebAssembly file with the correct MIME type that causes the + // client to pass it to the compiler in many separate chunks. For this, we use + // the same WebAssembly file as in the previous test but insert useless custom + // sections into the WebAssembly module to increase the file size without + // changing the relevant contents. + await testCompileStreamingSuccessUsingFetch((res) => { + res.setHeader('Content-Type', 'application/wasm'); + + // Send the WebAssembly magic and version first. + res.write(simpleWasmBytes.slice(0, 8), common.mustCall()); + + // Construct a 4KiB custom section. + const customSection = Buffer.concat([ + Buffer.from([ + 0, // Custom section. + 134, 32, // (134 & 0x7f) + 0x80 * 32 = 6 + 4096 bytes in this section. + 5, // The length of the following section name. + ]), + Buffer.from('?'.repeat(5)), // The section name + Buffer.from('\0'.repeat(4096)), // The actual section data + ]); + + // Now repeatedly send useless custom sections. These have no use for the + // WebAssembly compiler but they are syntactically valid. The client has to + // keep reading the stream until the very end to obtain the relevant + // sections within the module. This adds up to a few hundred kibibytes. + (function next(i) { + if (i < 100) { + while (res.write(customSection)); + res.once('drain', () => next(i + 1)); + } else { + // End the response body with the actual module contents. + res.end(simpleWasmBytes.slice(8)); + } + })(0); + }); + + // A valid WebAssembly file with an empty parameter in the (otherwise valid) + // MIME type. + await testCompileStreamingRejectionUsingFetch((res) => { + res.setHeader('Content-Type', 'application/wasm;'); + res.end(simpleWasmBytes); + }, { + name: 'TypeError', + code: 'ERR_WEBASSEMBLY_RESPONSE', + message: 'WebAssembly response has unsupported MIME type ' + + "'application/wasm;'" + }); + + // A valid WebAssembly file with an invalid MIME type. + await testCompileStreamingRejectionUsingFetch((res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.end(simpleWasmBytes); + }, { + name: 'TypeError', + code: 'ERR_WEBASSEMBLY_RESPONSE', + message: 'WebAssembly response has unsupported MIME type ' + + "'application/octet-stream'" + }); + + // HTTP status code indiciating an error. + await testCompileStreamingRejectionUsingFetch((res) => { + res.statusCode = 418; + res.setHeader('Content-Type', 'application/wasm'); + res.end(simpleWasmBytes); + }, { + name: 'TypeError', + code: 'ERR_WEBASSEMBLY_RESPONSE', + message: /^WebAssembly response has status code 418$/ + }); + + // HTTP status code indiciating an error, but using a Response whose body is + // a Buffer instead of calling fetch(). + await testCompileStreamingSuccess(() => { + return Promise.resolve(new Response(simpleWasmBytes, { + status: 200, + headers: { 'Content-Type': 'application/wasm' } + })); + }); + + // Extra bytes after the WebAssembly file. + await testCompileStreamingRejectionUsingFetch((res) => { + res.setHeader('Content-Type', 'application/wasm'); + res.end(Buffer.concat([simpleWasmBytes, Buffer.from('foo')])); + }, { + name: 'CompileError', + message: /^WebAssembly\.compileStreaming\(\): .*$/ + }); + + // Missing bytes at the end of the WebAssembly file. + await testCompileStreamingRejectionUsingFetch((res) => { + res.setHeader('Content-Type', 'application/wasm'); + res.end(simpleWasmBytes.subarray(0, simpleWasmBytes.length - 3)); + }, { + name: 'CompileError', + message: /^WebAssembly\.compileStreaming\(\): .*$/ + }); + + // Incomplete HTTP response body. The TypeError might come as a surprise, but + // it originates from within fetch(). + await testCompileStreamingRejectionUsingFetch((res) => { + res.setHeader('Content-Length', simpleWasmBytes.length); + res.setHeader('Content-Type', 'application/wasm'); + res.write(simpleWasmBytes.slice(0, 5), common.mustSucceed(() => { + res.destroy(); + })); + }, { + name: 'TypeError', + message: /terminated/ + }); +})().then(common.mustCall()); diff --git a/test/wpt/status/wasm/webapi.json b/test/wpt/status/wasm/webapi.json new file mode 100644 index 00000000000000..e631f5c0a95310 --- /dev/null +++ b/test/wpt/status/wasm/webapi.json @@ -0,0 +1,24 @@ +{ + "historical.any.js": { + "skip": "indexedDB is not defined" + }, + "origin.sub.any.js": { + "skip": "CORS not implemented" + }, + + "abort.any.js": { + "skip": "WPTRunner does not support fetch()" + }, + "contenttype.any.js": { + "skip": "WPTRunner does not support fetch()" + }, + "empty-body.any.js": { + "skip": "Bug in undici, see https://github.com/nodejs/undici/issues/1345" + }, + "idlharness.any.js": { + "skip": "not configured" + }, + "status.any.js": { + "skip": "WPTRunner does not support fetch()" + } +} diff --git a/test/wpt/test-wasm-webapi.js b/test/wpt/test-wasm-webapi.js new file mode 100644 index 00000000000000..fecc6e89a3fe1a --- /dev/null +++ b/test/wpt/test-wasm-webapi.js @@ -0,0 +1,7 @@ +'use strict'; + +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('wasm/webapi'); +runner.runJsTests();