From a7ce007ff4b07c4c07059ef4c6da78449d73911f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 22 Jun 2026 15:13:16 -0400 Subject: [PATCH] LibWeb: Define name and length properties on exported wasm functions We were previously not setting any name or length properties on exported functions. Some sites like https://squeel.frankmayer.dev/ rely on these properties. The name property is a bit strange. Exported wasm functions have a name property which is its index in its module instance's function-address list. This patch is not enough for the squeel site to be fully functional; we will need to implement navigator.locks. --- Libraries/LibWeb/WebAssembly/Instance.cpp | 2 +- Libraries/LibWeb/WebAssembly/WebAssembly.cpp | 74 ++++++++++++++----- Libraries/LibWeb/WebAssembly/WebAssembly.h | 4 +- ...WebAssembly-exported-function-metadata.txt | 6 ++ ...ebAssembly-exported-function-metadata.html | 32 ++++++++ 5 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/Wasm/WebAssembly-exported-function-metadata.txt create mode 100644 Tests/LibWeb/Text/input/Wasm/WebAssembly-exported-function-metadata.html diff --git a/Libraries/LibWeb/WebAssembly/Instance.cpp b/Libraries/LibWeb/WebAssembly/Instance.cpp index f1ff2f830a..bb1f51a70d 100644 --- a/Libraries/LibWeb/WebAssembly/Instance.cpp +++ b/Libraries/LibWeb/WebAssembly/Instance.cpp @@ -54,7 +54,7 @@ void Instance::initialize(JS::Realm& realm) [&](Wasm::FunctionAddress const& address) { Optional> object = m_function_instances.get(address); if (!object.has_value()) { - object = Detail::create_native_function(vm, address, name, this); + object = Detail::create_native_function(vm, address, this); m_function_instances.set(address, *object); } diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 1fca03a7c0..d241f95cc1 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -289,6 +289,7 @@ JS::ThrowCompletionOr> instantiate_module(JS // 2. Let imports be « ». HashMap resolved_imports; + size_t imported_function_count = 0; if (import_object) { dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve stuff because import object was specified"); @@ -330,7 +331,7 @@ JS::ThrowCompletionOr> instantiate_module(JS else { // 3.4.3.1. Create a host function from v and functype, and let funcaddr be the result. cache.add_imported_object(function); - auto host_function = create_host_function(vm, function, function_type, ByteString::formatted("func{}", resolved_imports.size())); + auto host_function = create_host_function(vm, function, function_type, ByteString::number(imported_function_count)); address = cache.abstract_machine().store().allocate(move(host_function)); // FIXME: 3.4.3.2. Let index be the number of external functions in imports. This value index is known as the index of the host function funcaddr. // 'index' doesn't seem to be used anywhere? @@ -342,6 +343,7 @@ JS::ThrowCompletionOr> instantiate_module(JS // 3.4.4. Let externfunc be the external value func funcaddr. // 3.4.5. Append externfunc to imports. resolved_imports.set(import_name, Wasm::ExternValue { Wasm::FunctionAddress { *address } }); + ++imported_function_count; return {}; }, // 3.5. If externtype is of the form global mut valtype, @@ -647,14 +649,20 @@ JS::ThrowCompletionOr host_grow_shared_array_buffer(JS::VM& v GC_DEFINE_ALLOCATOR(ExportedWasmFunction); -GC::Ref ExportedWasmFunction::create(JS::Realm& realm, Utf16FlyString name, Function(JS::VM&)> behavior, Wasm::FunctionAddress exported_address) +GC::Ref ExportedWasmFunction::create(JS::Realm& realm, Utf16FlyString name, size_t length, Function(JS::VM&)> behavior, Wasm::FunctionAddress exported_address) { + auto& vm = realm.vm(); + auto prototype = realm.intrinsics().function_prototype(); - return realm.create( + auto function = realm.create( move(name), move(behavior), exported_address, prototype); + function->define_direct_property(vm.names.length, JS::Value { static_cast(length) }, JS::Attribute::Configurable); + function->define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, function->name()), JS::Attribute::Configurable); + + return function; } ExportedWasmFunction::ExportedWasmFunction(Utf16FlyString name, AK::Function(JS::VM&)> behavior, Wasm::FunctionAddress exported_address, JS::Object& prototype) @@ -676,19 +684,57 @@ JS::ThrowCompletionOr ExportedWasmFunction::call() return m_behavior(vm()); } -JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress address, Utf16FlyString name, Instance* instance) +// https://www.w3.org/TR/wasm-js-api-2/#name-of-the-webassembly-function +static Utf16FlyString name_of_webassembly_function(Wasm::Store& store, Wasm::FunctionAddress function_address) +{ + // 1. Let store be the surrounding agent’s associated store. + + // 2. Let funcinst be store.funcs[funcaddr]. + auto* function_instance = store.get(function_address); + VERIFY(function_instance); + + auto index = function_instance->visit( + // 3. If funcinst is of the form {type functype, hostcode hostfunc}, + [&](Wasm::HostFunction const& host_function) { + // 1. Assert: hostfunc is a JavaScript object and IsCallable(hostfunc) is true. + // 2. Let index be the index of the host function funcaddr. + auto index = host_function.name().to_number(TrimWhitespace::No); + VERIFY(index.has_value()); + return *index; + }, + // 4. Otherwise, + [&](Wasm::WasmFunction const& wasm_function) { + // 1. Let moduleinst be funcinst.module. + auto const& module_instance = wasm_function.module(); + + // 2. Assert: funcaddr is contained in moduleinst.funcaddrs. + // 3. Let index be the index of moduleinst.funcaddrs where funcaddr is found. + auto index = module_instance.functions().find_first_index(function_address); + VERIFY(index.has_value()); + return *index; + }); + + // 5. Return ! ToString(index). + return Utf16String::number(index); +} + +JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress address, Instance* instance) { auto& realm = *vm.current_realm(); - Optional type; auto& cache = get_cache(realm); - cache.abstract_machine().store().get(address)->visit([&](auto const& value) { type = value.type(); }); + if (auto entry = cache.get_function_instance(address); entry.has_value()) return *entry; + auto& store = cache.abstract_machine().store(); + auto type = store.get(address)->visit([&](auto const& value) { return value.type(); }); + auto length = type.parameters().size(); + auto function = ExportedWasmFunction::create( realm, - move(name), - [address, type = type.release_value(), instance](JS::VM& vm) -> JS::ThrowCompletionOr { + name_of_webassembly_function(store, address), + length, + [address, type = move(type), instance](JS::VM& vm) -> JS::ThrowCompletionOr { (void)instance; auto& realm = *vm.current_realm(); Vector values; @@ -861,17 +907,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type) if (ref_.ref().has()) return JS::js_null(); auto address = ref_.ref().get().address; - auto& cache = get_cache(realm); - auto* function = cache.abstract_machine().store().get(address); - auto name = function->visit( - [&](Wasm::WasmFunction& wasm_function) { - auto index = *wasm_function.module().functions().find_first_index(address); - return ByteString::formatted("func{}", index); - }, - [](Wasm::HostFunction& host_function) { - return host_function.name(); - }); - return create_native_function(vm, address, Utf16FlyString::from_utf8(name)); + return create_native_function(vm, address); } case Wasm::ValueType::ExternReference: { auto ref_ = wasm_value.to(); diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Libraries/LibWeb/WebAssembly/WebAssembly.h index 0cdb0e0581..cf066f4cb9 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.h +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -101,7 +101,7 @@ class ExportedWasmFunction final : public JS::NativeFunction { GC_DECLARE_ALLOCATOR(ExportedWasmFunction); public: - static GC::Ref create(JS::Realm&, Utf16FlyString name, ESCAPING Function(JS::VM&)>, Wasm::FunctionAddress); + static GC::Ref create(JS::Realm&, Utf16FlyString name, size_t length, ESCAPING Function(JS::VM&)>, Wasm::FunctionAddress); virtual ~ExportedWasmFunction() override = default; Wasm::FunctionAddress exported_address() const { return m_exported_address; } @@ -122,7 +122,7 @@ WebAssemblyCache& get_cache(JS::Realm&); JS::ThrowCompletionOr> instantiate_module(JS::VM&, Wasm::Module const&, GC::Ptr import_object); JS::ThrowCompletionOr> compile_a_webassembly_module(JS::VM&, ByteBuffer); -JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, Utf16FlyString name, Instance* instance = nullptr); +JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, Instance* instance = nullptr); JS::ThrowCompletionOr to_webassembly_value(JS::VM&, JS::Value value, Wasm::ValueType const& type); Wasm::Value default_webassembly_value(JS::VM&, Wasm::ValueType type); JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value, Wasm::ValueType type); diff --git a/Tests/LibWeb/Text/expected/Wasm/WebAssembly-exported-function-metadata.txt b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-exported-function-metadata.txt new file mode 100644 index 0000000000..3bf7b43b18 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Wasm/WebAssembly-exported-function-metadata.txt @@ -0,0 +1,6 @@ +length: 2 +name: 0 +export name: sqlite3_aggregate_context +length descriptor: writable=false, enumerable=false, configurable=true +name descriptor: writable=false, enumerable=false, configurable=true +call result: 42 diff --git a/Tests/LibWeb/Text/input/Wasm/WebAssembly-exported-function-metadata.html b/Tests/LibWeb/Text/input/Wasm/WebAssembly-exported-function-metadata.html new file mode 100644 index 0000000000..48cdb67169 --- /dev/null +++ b/Tests/LibWeb/Text/input/Wasm/WebAssembly-exported-function-metadata.html @@ -0,0 +1,32 @@ + + +