LibWeb: Add WebAssembly.Global and exports support for global instances

This commit is contained in:
Andrew Kaster 2024-12-23 14:47:25 -07:00 committed by Ali Mohammad Pur
commit 44e3817219
Notes: github-actions[bot] 2024-12-24 14:21:40 +00:00
10 changed files with 268 additions and 4 deletions

View file

@ -770,6 +770,7 @@ set(SOURCES
UIEvents/WheelEvent.cpp
UserTiming/PerformanceMark.cpp
UserTiming/PerformanceMeasure.cpp
WebAssembly/Global.cpp
WebAssembly/Instance.cpp
WebAssembly/Memory.cpp
WebAssembly/Module.cpp

View file

@ -802,6 +802,7 @@ class PerformanceMeasure;
}
namespace Web::WebAssembly {
class Global;
class Instance;
class Memory;
class Module;

View file

@ -0,0 +1,179 @@
/*
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibWasm/Types.h>
#include <LibWeb/Bindings/GlobalPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/Global.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::WebAssembly {
GC_DEFINE_ALLOCATOR(Global);
// https://webassembly.github.io/spec/js-api/#tovaluetype
static Wasm::ValueType to_value_type(Bindings::ValueType type)
{
switch (type) {
case Bindings::ValueType::I32:
return Wasm::ValueType { Wasm::ValueType::I32 };
case Bindings::ValueType::I64:
return Wasm::ValueType { Wasm::ValueType::I64 };
case Bindings::ValueType::F32:
return Wasm::ValueType { Wasm::ValueType::F32 };
case Bindings::ValueType::F64:
return Wasm::ValueType { Wasm::ValueType::F64 };
case Bindings::ValueType::V128:
return Wasm::ValueType { Wasm::ValueType::V128 };
case Bindings::ValueType::Anyfunc:
return Wasm::ValueType { Wasm::ValueType::FunctionReference };
case Bindings::ValueType::Externref:
return Wasm::ValueType { Wasm::ValueType::ExternReference };
}
VERIFY_NOT_REACHED();
}
// https://webassembly.github.io/spec/js-api/#dom-global-global
WebIDL::ExceptionOr<GC::Ref<Global>> Global::construct_impl(JS::Realm& realm, GlobalDescriptor& descriptor, JS::Value v)
{
auto& vm = realm.vm();
// 1. Let mutable be descriptor["mutable"].
auto mutable_ = descriptor.mutable_;
// 2. Let valuetype be ToValueType(descriptor["value"]).
auto value_type = to_value_type(descriptor.value);
// 3. If valuetype is v128,
// 3.1 Throw a TypeError exception.
if (value_type.kind() == Wasm::ValueType::V128)
return vm.throw_completion<JS::TypeError>("V128 is not supported as a global value type"sv);
// 4. If v is missing,
// 4.1 Let value be DefaultValue(valuetype).
// 5. Otherwise,
// 5.1 Let value be ToWebAssemblyValue(v, valuetype).
// FIXME: https://github.com/WebAssembly/spec/issues/1861
// Is there a difference between *missing* and undefined for optional any values?
auto value = v.is_undefined()
? Detail::default_webassembly_value(vm, value_type)
: TRY(Detail::to_webassembly_value(vm, v, value_type));
// 6. If mutable is true, let globaltype be var valuetype; otherwise, let globaltype be const valuetype.
auto global_type = Wasm::GlobalType { value_type, mutable_ };
// 7. Let store be the current agents associated store.
// 8. Let (store, globaladdr) be global_alloc(store, globaltype, value).
// 9. Set the current agents associated store to store.
// 10. Initialize this from globaladdr.
auto& cache = Detail::get_cache(realm);
auto address = cache.abstract_machine().store().allocate(global_type, value);
if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Global allocation failed"sv);
return realm.create<Global>(realm, *address);
}
Global::Global(JS::Realm& realm, Wasm::GlobalAddress address)
: Bindings::PlatformObject(realm)
, m_address(address)
{
}
// https://webassembly.github.io/spec/js-api/#initialize-a-global-object
void Global::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Global, WebAssembly.Global);
// 1. Let map be the surrounding agent's associated Global object cache.
// 2. Assert: map[globaladdr] doesnt exist.
auto& cache = Detail::get_cache(realm);
auto exists = cache.global_instances().contains(m_address);
VERIFY(!exists);
// 3. Set global.[[Global]] to globaladdr.
// 4. Set map[globaladdr] to global.
cache.add_global_instance(m_address, *this);
}
// https://webassembly.github.io/spec/js-api/#getglobalvalue
static WebIDL::ExceptionOr<JS::Value> get_global_value(Global const& global)
{
// 1. Let store be the current agents associated store.
// 2. Let globaladdr be global.[[Global]].
// 3. Let globaltype be global_type(store, globaladdr).
// 4. If globaltype is of the form mut v128, throw a TypeError.
auto& cache = Detail::get_cache(global.realm());
auto* global_instance = cache.abstract_machine().store().get(global.address());
if (!global_instance)
return global.vm().throw_completion<JS::RangeError>("Could not find the global instance"sv);
auto value_type = global_instance->type().type();
if (value_type.kind() == Wasm::ValueType::V128)
return global.vm().throw_completion<JS::TypeError>("V128 is not supported as a global value type"sv);
// 5. Let value be global_read(store, globaladdr).
auto value = global_instance->value();
// 6. Return ToJSValue(value).
return Detail::to_js_value(global.vm(), value, value_type);
}
// https://webassembly.github.io/spec/js-api/#dom-global-value
WebIDL::ExceptionOr<JS::Value> Global::value() const
{
return get_global_value(*this);
}
// https://webassembly.github.io/spec/js-api/#dom-global-valueof
WebIDL::ExceptionOr<JS::Value> Global::value_of() const
{
return get_global_value(*this);
}
// https://webassembly.github.io/spec/js-api/#dom-global-value
WebIDL::ExceptionOr<void> Global::set_value(JS::Value the_given_value)
{
auto& realm = this->realm();
auto& vm = this->vm();
// 1. Let store be the current agents associated store.
// 2. Let globaladdr be this.[[Global]].
// 3. Let mut valuetype be global_type(store, globaladdr).
// 4. If valuetype is v128, throw a TypeError.
// 5. If mut is const, throw a TypeError.
auto& cache = Detail::get_cache(realm);
auto* global_instance = cache.abstract_machine().store().get(address());
if (!global_instance)
return vm.throw_completion<JS::RangeError>("Could not find the global instance"sv);
auto mut_value_type = global_instance->type();
if (mut_value_type.type().kind() == Wasm::ValueType::V128)
return vm.throw_completion<JS::TypeError>("Cannot set the value of a V128 global"sv);
if (!mut_value_type.is_mutable())
return vm.throw_completion<JS::TypeError>("Cannot set the value of a const global"sv);
// 6. Let value be ToWebAssemblyValue(the given value, valuetype).
auto value = TRY(Detail::to_webassembly_value(vm, the_given_value, mut_value_type.type()));
// 7. Let store be global_write(store, globaladdr, value).
// 8. If store is error, throw a RangeError exception.
// 9. Set the current agents associated store to store.
// Note: The store cannot fail, because we checked for mut/val above.
global_instance->set_value(value);
return {};
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/GlobalPrototype.h>
#include <LibWeb/Bindings/PlatformObject.h>
namespace Web::WebAssembly {
struct GlobalDescriptor {
Bindings::ValueType value;
bool mutable_ { false };
};
class Global : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Global, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Global);
public:
static WebIDL::ExceptionOr<GC::Ref<Global>> construct_impl(JS::Realm&, GlobalDescriptor& descriptor, JS::Value v);
WebIDL::ExceptionOr<JS::Value> value_of() const;
WebIDL::ExceptionOr<void> set_value(JS::Value);
WebIDL::ExceptionOr<JS::Value> value() const;
Wasm::GlobalAddress address() const { return m_address; }
private:
Global(JS::Realm&, Wasm::GlobalAddress);
virtual void initialize(JS::Realm&) override;
Wasm::GlobalAddress m_address;
};
}

View file

@ -0,0 +1,24 @@
// https://webassembly.github.io/spec/js-api/#enumdef-valuetype
enum ValueType {
"i32",
"i64",
"f32",
"f64",
"v128",
"externref",
"anyfunc",
};
// https://webassembly.github.io/spec/js-api/#dictdef-globaldescriptor
dictionary GlobalDescriptor {
required ValueType value;
boolean mutable = false;
};
// https://webassembly.github.io/spec/js-api/#global
[LegacyNamespace=WebAssembly, Exposed=*]
interface Global {
constructor(GlobalDescriptor descriptor, optional any v);
any valueOf();
attribute any value;
};

View file

@ -11,6 +11,7 @@
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/InstancePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/Global.h>
#include <LibWeb/WebAssembly/Instance.h>
#include <LibWeb/WebAssembly/Memory.h>
#include <LibWeb/WebAssembly/Module.h>
@ -45,6 +46,9 @@ void Instance::initialize(JS::Realm& realm)
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE_WITH_CUSTOM_NAME(Instance, WebAssembly.Instance);
auto& cache = Detail::get_cache(realm);
// https://webassembly.github.io/spec/js-api/#create-an-exports-object
for (auto& export_ : m_module_instance->exports()) {
export_.value().visit(
[&](Wasm::FunctionAddress const& address) {
@ -56,6 +60,14 @@ void Instance::initialize(JS::Realm& realm)
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
},
[&](Wasm::GlobalAddress const& address) {
Optional<GC::Ptr<Global>> object = cache.get_global_instance(address);
if (!object.has_value()) {
object = realm.create<Global>(realm, address);
}
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
},
[&](Wasm::MemoryAddress const& address) {
Optional<GC::Ptr<Memory>> object = m_memory_instances.get(address);
if (!object.has_value()) {
@ -73,9 +85,6 @@ void Instance::initialize(JS::Realm& realm)
}
m_exports->define_direct_property(export_.name(), *object, JS::default_attributes);
},
[&](auto const&) {
// FIXME: Implement other exports!
});
}

View file

@ -21,6 +21,7 @@
#include <LibWeb/Fetch/Response.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/WebAssembly/Global.h>
#include <LibWeb/WebAssembly/Instance.h>
#include <LibWeb/WebAssembly/Memory.h>
#include <LibWeb/WebAssembly/Module.h>
@ -56,6 +57,7 @@ void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
visitor.visit(cache.function_instances());
visitor.visit(cache.imported_objects());
visitor.visit(cache.extern_values());
visitor.visit(cache.global_instances());
}
}

View file

@ -45,18 +45,22 @@ public:
void add_function_instance(Wasm::FunctionAddress address, GC::Ptr<JS::NativeFunction> function) { m_function_instances.set(address, function); }
void add_imported_object(GC::Ptr<JS::Object> object) { m_imported_objects.set(object); }
void add_extern_value(Wasm::ExternAddress address, JS::Value value) { m_extern_values.set(address, value); }
void add_global_instance(Wasm::GlobalAddress address, GC::Ptr<WebAssembly::Global> global) { m_global_instances.set(address, global); }
Optional<GC::Ptr<JS::NativeFunction>> get_function_instance(Wasm::FunctionAddress address) { return m_function_instances.get(address); }
Optional<JS::Value> get_extern_value(Wasm::ExternAddress address) { return m_extern_values.get(address); }
Optional<GC::Ptr<WebAssembly::Global>> get_global_instance(Wasm::GlobalAddress address) { return m_global_instances.get(address); }
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::NativeFunction>> function_instances() const { return m_function_instances; }
HashMap<Wasm::ExternAddress, JS::Value> extern_values() const { return m_extern_values; }
HashMap<Wasm::GlobalAddress, GC::Ptr<WebAssembly::Global>> global_instances() const { return m_global_instances; }
HashTable<GC::Ptr<JS::Object>> imported_objects() const { return m_imported_objects; }
Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; }
private:
HashMap<Wasm::FunctionAddress, GC::Ptr<JS::NativeFunction>> m_function_instances;
HashMap<Wasm::ExternAddress, JS::Value> m_extern_values;
HashMap<Wasm::GlobalAddress, GC::Ptr<WebAssembly::Global>> m_global_instances;
Vector<NonnullRefPtr<CompiledWebAssemblyModule>> m_compiled_modules;
HashTable<GC::Ptr<JS::Object>> m_imported_objects;
Wasm::AbstractMachine m_abstract_machine;

View file

@ -349,6 +349,7 @@ libweb_js_bindings(UIEvents/UIEvent)
libweb_js_bindings(UIEvents/WheelEvent)
libweb_js_bindings(UserTiming/PerformanceMark)
libweb_js_bindings(UserTiming/PerformanceMeasure)
libweb_js_bindings(WebAssembly/Global)
libweb_js_bindings(WebAssembly/Instance)
libweb_js_bindings(WebAssembly/Memory)
libweb_js_bindings(WebAssembly/Module)