diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 9c15896514f..90c604b700d 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -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 diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 3d8e3321b5a..2563067e15f 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -802,6 +802,7 @@ class PerformanceMeasure; } namespace Web::WebAssembly { +class Global; class Instance; class Memory; class Module; diff --git a/Libraries/LibWeb/WebAssembly/Global.cpp b/Libraries/LibWeb/WebAssembly/Global.cpp new file mode 100644 index 00000000000..7fd79d19ba3 --- /dev/null +++ b/Libraries/LibWeb/WebAssembly/Global.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +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> 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("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("Wasm Global allocation failed"sv); + + return realm.create(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 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("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("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 Global::value() const +{ + return get_global_value(*this); +} + +// https://webassembly.github.io/spec/js-api/#dom-global-valueof +WebIDL::ExceptionOr Global::value_of() const +{ + return get_global_value(*this); +} + +// https://webassembly.github.io/spec/js-api/#dom-global-value +WebIDL::ExceptionOr 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("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("Cannot set the value of a V128 global"sv); + + if (!mut_value_type.is_mutable()) + return vm.throw_completion("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 {}; +} + +} diff --git a/Libraries/LibWeb/WebAssembly/Global.h b/Libraries/LibWeb/WebAssembly/Global.h new file mode 100644 index 00000000000..719b6edbe33 --- /dev/null +++ b/Libraries/LibWeb/WebAssembly/Global.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +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> construct_impl(JS::Realm&, GlobalDescriptor& descriptor, JS::Value v); + + WebIDL::ExceptionOr value_of() const; + + WebIDL::ExceptionOr set_value(JS::Value); + WebIDL::ExceptionOr 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; +}; + +} diff --git a/Libraries/LibWeb/WebAssembly/Global.idl b/Libraries/LibWeb/WebAssembly/Global.idl new file mode 100644 index 00000000000..e3be65ef2df --- /dev/null +++ b/Libraries/LibWeb/WebAssembly/Global.idl @@ -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; +}; diff --git a/Libraries/LibWeb/WebAssembly/Instance.cpp b/Libraries/LibWeb/WebAssembly/Instance.cpp index d4c3b99713f..c16d10fd70f 100644 --- a/Libraries/LibWeb/WebAssembly/Instance.cpp +++ b/Libraries/LibWeb/WebAssembly/Instance.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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> object = cache.get_global_instance(address); + if (!object.has_value()) { + object = realm.create(realm, address); + } + + m_exports->define_direct_property(export_.name(), *object, JS::default_attributes); + }, [&](Wasm::MemoryAddress const& address) { Optional> 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! }); } diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 30a18bde166..abbf47be433 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -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()); } } diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Libraries/LibWeb/WebAssembly/WebAssembly.h index f114bb0f1b8..8e26f89aae1 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.h +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -45,18 +45,22 @@ public: void add_function_instance(Wasm::FunctionAddress address, GC::Ptr function) { m_function_instances.set(address, function); } void add_imported_object(GC::Ptr 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 global) { m_global_instances.set(address, global); } Optional> get_function_instance(Wasm::FunctionAddress address) { return m_function_instances.get(address); } Optional get_extern_value(Wasm::ExternAddress address) { return m_extern_values.get(address); } + Optional> get_global_instance(Wasm::GlobalAddress address) { return m_global_instances.get(address); } HashMap> function_instances() const { return m_function_instances; } HashMap extern_values() const { return m_extern_values; } + HashMap> global_instances() const { return m_global_instances; } HashTable> imported_objects() const { return m_imported_objects; } Wasm::AbstractMachine& abstract_machine() { return m_abstract_machine; } private: HashMap> m_function_instances; HashMap m_extern_values; + HashMap> m_global_instances; Vector> m_compiled_modules; HashTable> m_imported_objects; Wasm::AbstractMachine m_abstract_machine; diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 856c7f71fa4..b29a5e71203 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -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) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 5bf2323dcb2..46e2da926cc 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -294,7 +294,7 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface) static ByteString make_input_acceptable_cpp(ByteString const& input) { - if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register", "switch")) { + if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register", "switch", "mutable")) { StringBuilder builder; builder.append(input); builder.append('_');