LibWeb: Port WebAssembly.Memory to IDL

This commit is contained in:
Timothy Flynn 2023-03-15 19:29:57 -04:00 committed by Andreas Kling
parent de32c44762
commit ca96f8e364
Notes: sideshowbarker 2024-07-17 08:35:21 +09:00
15 changed files with 148 additions and 216 deletions

View file

@ -38,6 +38,7 @@ static bool is_platform_object(Type const& type)
"FormData"sv,
"ImageData"sv,
"Instance"sv,
"Memory"sv,
"Module"sv,
"MutationRecord"sv,
"NamedNodeMap"sv,

View file

@ -103,7 +103,6 @@ class @legacy_constructor_class@;)~~~");
// FIXME: Special case WebAssembly. We should convert WASM to use IDL.
{
auto gen = generator.fork();
add_interface(gen, "WebAssemblyMemoryPrototype"sv, "WebAssemblyMemoryConstructor"sv, {});
add_interface(gen, "WebAssemblyTablePrototype"sv, "WebAssemblyTableConstructor"sv, {});
}
@ -153,8 +152,6 @@ static ErrorOr<void> generate_intrinsic_definitions(StringView output_path, Vect
// FIXME: Special case WebAssembly. We should convert WASM to use IDL.
generator.append(R"~~~(
#include <LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h>
#include <LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h>
#include <LibWeb/WebAssembly/WebAssemblyTableConstructor.h>
#include <LibWeb/WebAssembly/WebAssemblyTablePrototype.h>)~~~");
@ -225,7 +222,6 @@ void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Rea
// FIXME: Special case WebAssembly. We should convert WASM to use IDL.
{
auto gen = generator.fork();
add_interface(gen, "WebAssembly.Memory"sv, "WebAssemblyMemoryPrototype"sv, "WebAssemblyMemoryConstructor"sv, {});
add_interface(gen, "WebAssembly.Table"sv, "WebAssemblyTablePrototype"sv, "WebAssemblyTableConstructor"sv, {});
}

View file

@ -443,9 +443,8 @@ set(SOURCES
URL/URLSearchParams.cpp
URL/URLSearchParamsIterator.cpp
WebAssembly/Instance.cpp
WebAssembly/Memory.cpp
WebAssembly/Module.cpp
WebAssembly/WebAssemblyMemoryConstructor.cpp
WebAssembly/WebAssemblyMemoryPrototype.cpp
WebAssembly/WebAssemblyObject.cpp
WebAssembly/WebAssemblyTableConstructor.cpp
WebAssembly/WebAssemblyTableObject.cpp

View file

@ -483,6 +483,7 @@ class ResourceLoader;
namespace Web::WebAssembly {
class Instance;
class Memory;
class Module;
}

View file

@ -13,6 +13,7 @@
#include <LibWeb/Bindings/InstancePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/Instance.h>
#include <LibWeb/WebAssembly/Memory.h>
#include <LibWeb/WebAssembly/Module.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
#include <LibWeb/WebAssembly/WebAssemblyTableObject.h>
@ -60,9 +61,9 @@ JS::ThrowCompletionOr<void> Instance::initialize(JS::Realm& realm)
return {};
},
[&](Wasm::MemoryAddress const& address) -> JS::ThrowCompletionOr<void> {
Optional<JS::GCPtr<Bindings::WebAssemblyMemoryObject>> object = cache.memory_instances.get(address);
Optional<JS::GCPtr<Memory>> object = cache.memory_instances.get(address);
if (!object.has_value()) {
object = MUST_OR_THROW_OOM(heap().allocate<Web::Bindings::WebAssemblyMemoryObject>(realm, realm, address));
object = MUST_OR_THROW_OOM(heap().allocate<Memory>(realm, realm, address));
cache.memory_instances.set(address, *object);
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibWasm/Types.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/Memory.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
namespace Web::WebAssembly {
WebIDL::ExceptionOr<JS::NonnullGCPtr<Memory>> Memory::construct_impl(JS::Realm& realm, MemoryDescriptor& descriptor)
{
auto& vm = realm.vm();
Wasm::Limits limits { descriptor.initial, move(descriptor.maximum) };
Wasm::MemoryType memory_type { move(limits) };
auto address = Bindings::WebAssemblyObject::s_abstract_machine.store().allocate(memory_type);
if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv);
return MUST_OR_THROW_OOM(vm.heap().allocate<Memory>(realm, realm, *address));
}
Memory::Memory(JS::Realm& realm, Wasm::MemoryAddress address)
: Bindings::PlatformObject(realm)
, m_address(address)
{
}
JS::ThrowCompletionOr<void> Memory::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Base::initialize(realm));
set_prototype(&Bindings::ensure_web_prototype<Bindings::MemoryPrototype>(realm, "WebAssembly.Memory"sv));
return {};
}
// https://webassembly.github.io/spec/js-api/#dom-memory-grow
WebIDL::ExceptionOr<u32> Memory::grow(u32 delta)
{
auto& vm = this->vm();
auto* memory = Bindings::WebAssemblyObject::s_abstract_machine.store().get(address());
if (!memory)
return vm.throw_completion<JS::RangeError>("Could not find the memory instance to grow"sv);
auto previous_size = memory->size() / Wasm::Constants::page_size;
if (!memory->grow(delta * Wasm::Constants::page_size))
return vm.throw_completion<JS::RangeError>("Memory.grow() grows past the stated limit of the memory instance"sv);
return previous_size;
}
// https://webassembly.github.io/spec/js-api/#dom-memory-buffer
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> Memory::buffer() const
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
auto* memory = Bindings::WebAssemblyObject::s_abstract_machine.store().get(address());
if (!memory)
return vm.throw_completion<JS::RangeError>("Could not find the memory instance"sv);
auto array_buffer = JS::ArrayBuffer::create(realm, &memory->data());
array_buffer->set_detach_key(MUST_OR_THROW_OOM(JS::PrimitiveString::create(vm, "WebAssembly.Memory"sv)));
return array_buffer;
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/GCPtr.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/PlatformObject.h>
namespace Web::WebAssembly {
struct MemoryDescriptor {
u32 initial { 0 };
Optional<u32> maximum;
};
class Memory : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Memory, Bindings::PlatformObject);
public:
static WebIDL::ExceptionOr<JS::NonnullGCPtr<Memory>> construct_impl(JS::Realm&, MemoryDescriptor& descriptor);
WebIDL::ExceptionOr<u32> grow(u32 delta);
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> buffer() const;
Wasm::MemoryAddress address() const { return m_address; }
private:
Memory(JS::Realm&, Wasm::MemoryAddress);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
Wasm::MemoryAddress m_address;
};
}

View file

@ -0,0 +1,14 @@
dictionary MemoryDescriptor {
required [EnforceRange] unsigned long initial;
[EnforceRange] unsigned long maximum;
};
// https://webassembly.github.io/spec/js-api/#memories
[LegacyNamespace=WebAssembly, Exposed=*]
interface Memory {
constructor(MemoryDescriptor descriptor);
unsigned long grow([EnforceRange] unsigned long delta);
readonly attribute ArrayBuffer buffer;
};

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebAssembly/WebAssemblyMemoryConstructor.h>
#include <LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
namespace Web::Bindings {
WebAssemblyMemoryConstructor::WebAssemblyMemoryConstructor(JS::Realm& realm)
: NativeFunction(*realm.intrinsics().function_prototype())
{
}
WebAssemblyMemoryConstructor::~WebAssemblyMemoryConstructor() = default;
JS::ThrowCompletionOr<JS::Value> WebAssemblyMemoryConstructor::call()
{
return vm().throw_completion<JS::TypeError>(JS::ErrorType::ConstructorWithoutNew, "WebAssembly.Memory");
}
JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Object>> WebAssemblyMemoryConstructor::construct(FunctionObject&)
{
auto& vm = this->vm();
auto& realm = *vm.current_realm();
auto descriptor = TRY(vm.argument(0).to_object(vm));
auto initial_value = TRY(descriptor->get("initial"));
auto maximum_value = TRY(descriptor->get("maximum"));
if (!initial_value.is_number())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Number");
u32 initial = TRY(initial_value.to_u32(vm));
Optional<u32> maximum;
if (!maximum_value.is_undefined())
maximum = TRY(maximum_value.to_u32(vm));
auto address = WebAssemblyObject::s_abstract_machine.store().allocate(Wasm::MemoryType { Wasm::Limits { initial, maximum } });
if (!address.has_value())
return vm.throw_completion<JS::TypeError>("Wasm Memory allocation failed"sv);
return MUST_OR_THROW_OOM(vm.heap().allocate<WebAssemblyMemoryObject>(realm, realm, *address));
}
JS::ThrowCompletionOr<void> WebAssemblyMemoryConstructor::initialize(JS::Realm& realm)
{
auto& vm = this->vm();
MUST_OR_THROW_OOM(NativeFunction::initialize(realm));
define_direct_property(vm.names.prototype, &Bindings::ensure_web_prototype<WebAssemblyMemoryPrototype>(realm, "WebAssembly.Memory"), 0);
define_direct_property(vm.names.length, JS::Value(1), JS::Attribute::Configurable);
return {};
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/NativeFunction.h>
namespace Web::Bindings {
class WebAssemblyMemoryConstructor : public JS::NativeFunction {
JS_OBJECT(WebAssemblyMemoryConstructor, JS::NativeFunction);
public:
explicit WebAssemblyMemoryConstructor(JS::Realm&);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual ~WebAssemblyMemoryConstructor() override;
virtual JS::ThrowCompletionOr<JS::Value> call() override;
virtual JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Object>> construct(JS::FunctionObject& new_target) override;
private:
virtual bool has_constructor() const override { return true; }
};
}

View file

@ -1,60 +0,0 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/WebAssembly/WebAssemblyMemoryPrototype.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
namespace Web::Bindings {
JS::ThrowCompletionOr<void> WebAssemblyMemoryPrototype::initialize(JS::Realm& realm)
{
MUST_OR_THROW_OOM(Object::initialize(realm));
define_native_accessor(realm, "buffer", buffer_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
define_native_function(realm, "grow", grow, 1, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable);
return {};
}
JS_DEFINE_NATIVE_FUNCTION(WebAssemblyMemoryPrototype::grow)
{
auto page_count = TRY(vm.argument(0).to_u32(vm));
auto* this_object = TRY(vm.this_value().to_object(vm));
if (!is<WebAssemblyMemoryObject>(this_object))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "WebAssembly.Memory");
auto* memory_object = static_cast<WebAssemblyMemoryObject*>(this_object);
auto address = memory_object->address();
auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address);
if (!memory)
return JS::js_undefined();
auto previous_size = memory->size() / Wasm::Constants::page_size;
if (!memory->grow(page_count * Wasm::Constants::page_size))
return vm.throw_completion<JS::TypeError>("Memory.grow() grows past the stated limit of the memory instance"sv);
return JS::Value(static_cast<u32>(previous_size));
}
JS_DEFINE_NATIVE_FUNCTION(WebAssemblyMemoryPrototype::buffer_getter)
{
auto& realm = *vm.current_realm();
auto* this_object = TRY(vm.this_value().to_object(vm));
if (!is<WebAssemblyMemoryObject>(this_object))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "WebAssembly.Memory");
auto* memory_object = static_cast<WebAssemblyMemoryObject*>(this_object);
auto address = memory_object->address();
auto* memory = WebAssemblyObject::s_abstract_machine.store().get(address);
if (!memory)
return JS::js_undefined();
auto array_buffer = JS::ArrayBuffer::create(realm, &memory->data());
array_buffer->set_detach_key(MUST_OR_THROW_OOM(JS::PrimitiveString::create(vm, "WebAssembly.Memory"sv)));
return array_buffer;
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "WebAssemblyMemoryConstructor.h"
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Forward.h>
namespace Web::Bindings {
class WebAssemblyMemoryPrototype final : public JS::Object {
JS_OBJECT(WebAssemblyMemoryPrototype, JS::Object);
public:
explicit WebAssemblyMemoryPrototype(JS::Realm& realm)
: JS::Object(ConstructWithPrototypeTag::Tag, *realm.intrinsics().object_prototype())
{
}
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
private:
JS_DECLARE_NATIVE_FUNCTION(grow);
JS_DECLARE_NATIVE_FUNCTION(buffer_getter);
};
}

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "WebAssemblyMemoryPrototype.h"
#include "WebAssemblyTableObject.h"
#include "WebAssemblyTablePrototype.h"
#include <AK/MemoryStream.h>
@ -20,8 +19,10 @@
#include <LibWasm/AbstractMachine/Validator.h>
#include <LibWeb/Bindings/InstancePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MemoryPrototype.h>
#include <LibWeb/Bindings/ModulePrototype.h>
#include <LibWeb/WebAssembly/Instance.h>
#include <LibWeb/WebAssembly/Memory.h>
#include <LibWeb/WebAssembly/Module.h>
#include <LibWeb/WebAssembly/WebAssemblyObject.h>
@ -42,7 +43,7 @@ JS::ThrowCompletionOr<void> WebAssemblyObject::initialize(JS::Realm& realm)
define_native_function(realm, "compile", compile, 1, attr);
define_native_function(realm, "instantiate", instantiate, 1, attr);
auto& memory_constructor = Bindings::ensure_web_constructor<WebAssemblyMemoryPrototype>(realm, "WebAssembly.Memory"sv);
auto& memory_constructor = Bindings::ensure_web_constructor<MemoryPrototype>(realm, "WebAssembly.Memory"sv);
define_direct_property("Memory", &memory_constructor, JS::Attribute::Writable | JS::Attribute::Configurable);
auto& instance_constructor = Bindings::ensure_web_constructor<InstancePrototype>(realm, "WebAssembly.Instance"sv);
@ -262,11 +263,11 @@ JS::ThrowCompletionOr<size_t> WebAssemblyObject::instantiate_module(JS::VM& vm,
return {};
},
[&](Wasm::MemoryType const&) -> JS::ThrowCompletionOr<void> {
if (!import_.is_object() || !is<WebAssemblyMemoryObject>(import_.as_object())) {
if (!import_.is_object() || !is<WebAssembly::Memory>(import_.as_object())) {
// FIXME: Throw a LinkError instead
return vm.throw_completion<JS::TypeError>("LinkError: Expected an instance of WebAssembly.Memory for a memory import"sv);
}
auto address = static_cast<WebAssemblyMemoryObject const&>(import_.as_object()).address();
auto address = static_cast<WebAssembly::Memory const&>(import_.as_object()).address();
resolved_imports.set(import_name, Wasm::ExternValue { address });
return {};
},
@ -472,10 +473,4 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
return function;
}
WebAssemblyMemoryObject::WebAssemblyMemoryObject(JS::Realm& realm, Wasm::MemoryAddress address)
: Object(ConstructWithPrototypeTag::Tag, Bindings::ensure_web_prototype<WebAssemblyMemoryPrototype>(realm, "WebAssembly.Memory"))
, m_address(address)
{
}
}

View file

@ -12,7 +12,6 @@
namespace Web::Bindings {
class WebAssemblyMemoryObject;
class WebAssemblyTableObject;
JS::ThrowCompletionOr<size_t> parse_module(JS::VM&, JS::Object* buffer);
JS::NativeFunction* create_native_function(JS::VM&, Wasm::FunctionAddress address, DeprecatedString const& name);
@ -46,7 +45,7 @@ public:
// WebAssemblyModuleObject's and WebAssemblyInstantiatedModuleObject's.
struct ModuleCache {
HashMap<Wasm::FunctionAddress, JS::GCPtr<JS::FunctionObject>> function_instances;
HashMap<Wasm::MemoryAddress, JS::GCPtr<WebAssemblyMemoryObject>> memory_instances;
HashMap<Wasm::MemoryAddress, JS::GCPtr<WebAssembly::Memory>> memory_instances;
HashMap<Wasm::TableAddress, JS::GCPtr<WebAssemblyTableObject>> table_instances;
};
struct GlobalModuleCache {
@ -66,17 +65,4 @@ private:
JS_DECLARE_NATIVE_FUNCTION(instantiate);
};
class WebAssemblyMemoryObject final : public JS::Object {
JS_OBJECT(WebAssemblyMemoryObject, JS::Object);
public:
WebAssemblyMemoryObject(JS::Realm&, Wasm::MemoryAddress);
virtual ~WebAssemblyMemoryObject() override = default;
auto address() const { return m_address; }
private:
Wasm::MemoryAddress m_address;
};
}

View file

@ -201,6 +201,7 @@ libweb_js_bindings(UIEvents/WheelEvent)
libweb_js_bindings(URL/URL)
libweb_js_bindings(URL/URLSearchParams ITERABLE)
libweb_js_bindings(WebAssembly/Instance)
libweb_js_bindings(WebAssembly/Memory)
libweb_js_bindings(WebAssembly/Module)
libweb_js_bindings(WebGL/WebGLContextEvent)
libweb_js_bindings(WebGL/WebGLRenderingContext)