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.
This commit is contained in:
parent
d4048aaa96
commit
a7ce007ff4
5 changed files with 96 additions and 22 deletions
|
|
@ -54,7 +54,7 @@ void Instance::initialize(JS::Realm& realm)
|
|||
[&](Wasm::FunctionAddress const& address) {
|
||||
Optional<GC::Ptr<JS::FunctionObject>> 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ JS::ThrowCompletionOr<NonnullRefPtr<Wasm::ModuleInstance>> instantiate_module(JS
|
|||
|
||||
// 2. Let imports be « ».
|
||||
HashMap<Wasm::Linker::Name, Wasm::ExternValue> 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<NonnullRefPtr<Wasm::ModuleInstance>> 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<NonnullRefPtr<Wasm::ModuleInstance>> 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<JS::HandledByHost> host_grow_shared_array_buffer(JS::VM& v
|
|||
|
||||
GC_DEFINE_ALLOCATOR(ExportedWasmFunction);
|
||||
|
||||
GC::Ref<ExportedWasmFunction> ExportedWasmFunction::create(JS::Realm& realm, Utf16FlyString name, Function<JS::ThrowCompletionOr<JS::Value>(JS::VM&)> behavior, Wasm::FunctionAddress exported_address)
|
||||
GC::Ref<ExportedWasmFunction> ExportedWasmFunction::create(JS::Realm& realm, Utf16FlyString name, size_t length, Function<JS::ThrowCompletionOr<JS::Value>(JS::VM&)> behavior, Wasm::FunctionAddress exported_address)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
|
||||
auto prototype = realm.intrinsics().function_prototype();
|
||||
return realm.create<ExportedWasmFunction>(
|
||||
auto function = realm.create<ExportedWasmFunction>(
|
||||
move(name),
|
||||
move(behavior),
|
||||
exported_address,
|
||||
prototype);
|
||||
function->define_direct_property(vm.names.length, JS::Value { static_cast<double>(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::ThrowCompletionOr<JS::Value>(JS::VM&)> behavior, Wasm::FunctionAddress exported_address, JS::Object& prototype)
|
||||
|
|
@ -676,19 +684,57 @@ JS::ThrowCompletionOr<JS::Value> 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<size_t>(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<Wasm::FunctionType> 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<JS::Value> {
|
||||
name_of_webassembly_function(store, address),
|
||||
length,
|
||||
[address, type = move(type), instance](JS::VM& vm) -> JS::ThrowCompletionOr<JS::Value> {
|
||||
(void)instance;
|
||||
auto& realm = *vm.current_realm();
|
||||
Vector<Wasm::Value> values;
|
||||
|
|
@ -861,17 +907,7 @@ JS::Value to_js_value(JS::VM& vm, Wasm::Value& wasm_value, Wasm::ValueType type)
|
|||
if (ref_.ref().has<Wasm::Reference::Null>())
|
||||
return JS::js_null();
|
||||
auto address = ref_.ref().get<Wasm::Reference::Func>().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<Wasm::Reference>();
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class ExportedWasmFunction final : public JS::NativeFunction {
|
|||
GC_DECLARE_ALLOCATOR(ExportedWasmFunction);
|
||||
|
||||
public:
|
||||
static GC::Ref<ExportedWasmFunction> create(JS::Realm&, Utf16FlyString name, ESCAPING Function<JS::ThrowCompletionOr<JS::Value>(JS::VM&)>, Wasm::FunctionAddress);
|
||||
static GC::Ref<ExportedWasmFunction> create(JS::Realm&, Utf16FlyString name, size_t length, ESCAPING Function<JS::ThrowCompletionOr<JS::Value>(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<NonnullRefPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM&, Wasm::Module const&, GC::Ptr<JS::Object> import_object);
|
||||
JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> 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<Wasm::Value> 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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
asyncTest(async (done) => {
|
||||
const wasmBytes = new Uint8Array([
|
||||
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
|
||||
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
|
||||
0x03, 0x02, 0x01, 0x00,
|
||||
0x07, 0x1d, 0x01, 0x19,
|
||||
0x73, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x33, 0x5f,
|
||||
0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74,
|
||||
0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
|
||||
0x00, 0x00,
|
||||
0x0a, 0x06, 0x01, 0x04, 0x00, 0x20, 0x00, 0x0b,
|
||||
]);
|
||||
|
||||
const { instance } = await WebAssembly.instantiate(wasmBytes);
|
||||
const fn = instance.exports.sqlite3_aggregate_context;
|
||||
const exportName = Object.getOwnPropertyNames(instance.exports)[0];
|
||||
const lengthDescriptor = Object.getOwnPropertyDescriptor(fn, "length");
|
||||
const nameDescriptor = Object.getOwnPropertyDescriptor(fn, "name");
|
||||
|
||||
println(`length: ${fn.length}`);
|
||||
println(`name: ${fn.name}`);
|
||||
println(`export name: ${exportName}`);
|
||||
println(`length descriptor: writable=${lengthDescriptor.writable}, enumerable=${lengthDescriptor.enumerable}, configurable=${lengthDescriptor.configurable}`);
|
||||
println(`name descriptor: writable=${nameDescriptor.writable}, enumerable=${nameDescriptor.enumerable}, configurable=${nameDescriptor.configurable}`);
|
||||
println(`call result: ${fn(42, 7)}`);
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue