mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-01 21:59:07 +00:00
LibWeb: Add WebAssembly.Global and exports support for global instances
This commit is contained in:
parent
910ff8b694
commit
44e3817219
Notes:
github-actions[bot]
2024-12-24 14:21:40 +00:00
Author: https://github.com/ADKaster
Commit: 44e3817219
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3028
10 changed files with 268 additions and 4 deletions
|
@ -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
|
||||
|
|
|
@ -802,6 +802,7 @@ class PerformanceMeasure;
|
|||
}
|
||||
|
||||
namespace Web::WebAssembly {
|
||||
class Global;
|
||||
class Instance;
|
||||
class Memory;
|
||||
class Module;
|
||||
|
|
179
Libraries/LibWeb/WebAssembly/Global.cpp
Normal file
179
Libraries/LibWeb/WebAssembly/Global.cpp
Normal 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 agent’s associated store.
|
||||
// 8. Let (store, globaladdr) be global_alloc(store, globaltype, value).
|
||||
// 9. Set the current agent’s 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] doesn’t 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 agent’s 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 agent’s 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 agent’s associated store to store.
|
||||
// Note: The store cannot fail, because we checked for mut/val above.
|
||||
|
||||
global_instance->set_value(value);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
43
Libraries/LibWeb/WebAssembly/Global.h
Normal file
43
Libraries/LibWeb/WebAssembly/Global.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
24
Libraries/LibWeb/WebAssembly/Global.idl
Normal file
24
Libraries/LibWeb/WebAssembly/Global.idl
Normal 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;
|
||||
};
|
|
@ -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!
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue