This commit is contained in:
Pavel Shliak 2025-01-02 11:40:21 +00:00 committed by GitHub
commit 5053756719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 170 additions and 38 deletions

View file

@ -782,6 +782,7 @@ set(SOURCES
WebAssembly/Module.cpp
WebAssembly/Table.cpp
WebAssembly/WebAssembly.cpp
WebAssembly/Error.cpp
WebAudio/AudioBuffer.cpp
WebAudio/AudioBufferSourceNode.cpp
WebAudio/AudioContext.cpp

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Error.h"
#include <LibJS/Runtime/GlobalObject.h>
namespace Web::WebAssembly {
GC_DEFINE_ALLOCATOR(CompileError);
GC::Ref<CompileError> CompileError::create(JS::Realm& realm)
{
return realm.heap().allocate<CompileError>(realm.intrinsics().error_prototype());
}
GC::Ref<CompileError> CompileError::create(JS::Realm& realm, StringView message)
{
auto& vm = realm.vm();
auto error = CompileError::create(realm);
u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr);
return error;
}
CompileError::CompileError(JS::Object& prototype)
: JS::Error(prototype)
{
}
GC_DEFINE_ALLOCATOR(LinkError);
GC::Ref<LinkError> LinkError::create(JS::Realm& realm)
{
return realm.heap().allocate<LinkError>(realm.intrinsics().error_prototype());
}
GC::Ref<LinkError> LinkError::create(JS::Realm& realm, StringView message)
{
auto& vm = realm.vm();
auto error = LinkError::create(realm);
u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr);
return error;
}
LinkError::LinkError(JS::Object& prototype)
: JS::Error(prototype)
{
}
GC_DEFINE_ALLOCATOR(RuntimeError);
GC::Ref<RuntimeError> RuntimeError::create(JS::Realm& realm)
{
return realm.heap().allocate<RuntimeError>(realm.intrinsics().error_prototype());
}
GC::Ref<RuntimeError> RuntimeError::create(JS::Realm& realm, StringView message)
{
auto& vm = realm.vm();
auto error = RuntimeError::create(realm);
u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
error->define_direct_property(vm.names.message, JS::PrimitiveString::create(vm, message), attr);
return error;
}
RuntimeError::RuntimeError(JS::Object& prototype)
: JS::Error(prototype)
{
}
} // namespace Web::WebAssembly

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2024, Pavel Shliak <shlyakpavel@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Error.h>
namespace Web::WebAssembly {
class CompileError final : public JS::Error {
JS_OBJECT(CompileError, JS::Error);
GC_DECLARE_ALLOCATOR(CompileError);
public:
static GC::Ref<CompileError> create(JS::Realm&);
static GC::Ref<CompileError> create(JS::Realm&, StringView message);
virtual ~CompileError() override = default;
private:
explicit CompileError(JS::Object& prototype);
};
class LinkError final : public JS::Error {
JS_OBJECT(LinkError, JS::Error);
GC_DECLARE_ALLOCATOR(LinkError);
public:
static GC::Ref<LinkError> create(JS::Realm&);
static GC::Ref<LinkError> create(JS::Realm&, StringView message);
virtual ~LinkError() override = default;
private:
explicit LinkError(JS::Object& prototype);
};
class RuntimeError final : public JS::Error {
JS_OBJECT(RuntimeError, JS::Error);
GC_DECLARE_ALLOCATOR(RuntimeError);
public:
static GC::Ref<RuntimeError> create(JS::Realm&);
static GC::Ref<RuntimeError> create(JS::Realm&, StringView message);
virtual ~RuntimeError() override = default;
private:
explicit RuntimeError(JS::Object& prototype);
};
} // namespace Web::WebAssembly

View file

@ -9,6 +9,7 @@
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ModulePrototype.h>
#include <LibWeb/WebAssembly/Error.h>
#include <LibWeb/WebAssembly/Module.h>
#include <LibWeb/WebAssembly/WebAssembly.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
@ -25,7 +26,7 @@ WebIDL::ExceptionOr<GC::Ref<Module>> Module::construct_impl(JS::Realm& realm, GC
auto stable_bytes_or_error = WebIDL::get_buffer_source_copy(bytes->raw_object());
if (stable_bytes_or_error.is_error()) {
VERIFY(stable_bytes_or_error.error().code() == ENOMEM);
return vm.throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory));
return vm.throw_completion<CompileError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory));
}
auto stable_bytes = stable_bytes_or_error.release_value();

View file

@ -29,11 +29,15 @@ static Wasm::ValueType table_kind_to_value_type(Bindings::TableKind kind)
VERIFY_NOT_REACHED();
}
// https://webassembly.github.io/spec/js-api/#tables
WebIDL::ExceptionOr<GC::Ref<Table>> Table::construct_impl(JS::Realm& realm, TableDescriptor& descriptor, JS::Value value)
{
auto& vm = realm.vm();
auto reference_type = table_kind_to_value_type(descriptor.element);
// If elementType is not a reftype, throw a TypeError exception.
if (!reference_type.is_reference())
return vm.throw_completion<JS::TypeError>("elementType is not a reftype"_string);
auto reference_value = vm.argument_count() == 1
? Detail::default_webassembly_value(vm, reference_type)
: TRY(Detail::to_webassembly_value(vm, value, reference_type));

View file

@ -8,6 +8,7 @@
#include <AK/ByteBuffer.h>
#include <AK/MemoryStream.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
@ -17,10 +18,12 @@
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
#include <LibWasm/AbstractMachine/Validator.h>
#include <LibWasm/Types.h>
#include <LibWeb/Bindings/ResponsePrototype.h>
#include <LibWeb/Fetch/Response.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/WebAssembly/Error.h>
#include <LibWeb/WebAssembly/Global.h>
#include <LibWeb/WebAssembly/Instance.h>
#include <LibWeb/WebAssembly/Memory.h>
@ -157,6 +160,11 @@ namespace Detail {
JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module, GC::Ptr<JS::Object> import_object)
{
// 1. If module.imports is not empty, and importObject is undefined, throw a TypeError exception.
if (!module.import_section().imports().is_empty() && !import_object) {
return vm.throw_completion<JS::TypeError>("Import object must be defined when module has imports"sv);
}
Wasm::Linker linker { module };
auto& cache = get_cache(*vm.current_realm());
// https://webassembly.github.io/spec/js-api/index.html#read-the-imports
@ -173,27 +181,29 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve {}::{}", import_name.module, import_name.name);
// 3.1. Let o be ? Get(importObject, moduleName).
auto value_or_error = import_object->get(import_name.module);
if (value_or_error.is_error())
break;
if (!value_or_error.is_error() && !value_or_error.release_value().is_object()) {
return vm.throw_completion<JS::TypeError>("Module name must resolve to an object"sv);
}
auto value = value_or_error.release_value();
// 3.2. If o is not an Object, throw a TypeError exception.
auto object_or_error = value.to_object(vm);
if (object_or_error.is_error())
break;
return vm.throw_completion<JS::TypeError>("Module name must resolve to an object"sv);
auto object = object_or_error.release_value();
// 3.3. Let v be ? Get(o, componentName).
auto import_or_error = object->get(import_name.name);
if (import_or_error.is_error())
break;
return vm.throw_completion<JS::TypeError>("Cannot get component name from module"sv);
auto import_ = import_or_error.release_value();
TRY(import_name.type.visit(
// 3.4. If externtype is of the form func functype,
[&](Wasm::TypeIndex index) -> JS::ThrowCompletionOr<void> {
dbgln_if(LIBWEB_WASM_DEBUG, "Trying to resolve a function {}::{}, type index {}", import_name.module, import_name.name, index.value());
auto& type = module.type_section().types()[index.value()];
// FIXME: 3.4.1. If IsCallable(v) is false, throw a LinkError exception.
if (!import_.is_function())
return {};
// FIXME: IsCallable()
if (!import_.is_function()) {
return vm.throw_completion<LinkError>("Imported value is not a callable function"sv);
}
auto& function = import_.as_function();
// 3.4.2. If v has a [[FunctionAddress]] internal slot, and therefore is an Exported Function,
Optional<Wasm::FunctionAddress> address;
@ -263,32 +273,26 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
if (import_.is_number() || import_.is_bigint()) {
// 3.5.1.1. If valtype is i64 and v is a Number,
if (import_.is_number() && type.type().kind() == Wasm::ValueType::I64) {
// FIXME: 3.5.1.1.1. Throw a LinkError exception.
return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a Number to a BigInteger"sv);
return vm.throw_completion<LinkError>("Import resolution attempted to cast a Number to a BigInteger"sv);
}
// 3.5.1.2. If valtype is not i64 and v is a BigInt,
if (import_.is_bigint() && type.type().kind() != Wasm::ValueType::I64) {
// FIXME: 3.5.1.2.1. Throw a LinkError exception.
return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a BigInteger to a Number"sv);
return vm.throw_completion<LinkError>("Import resolution attempted to cast a BigInteger to a Number"sv);
}
// 3.5.1.3. If valtype is v128,
if (type.type().kind() == Wasm::ValueType::V128) {
// FIXME: 3.5.1.3.1. Throw a LinkError exception.
return vm.throw_completion<JS::TypeError>("LinkError: Import resolution attempted to cast a Number or BigInt to a V128"sv);
return vm.throw_completion<LinkError>("Cannot import v128 type for global"sv);
}
// 3.5.1.4. Let value be ToWebAssemblyValue(v, valtype).
auto cast_value = TRY(to_webassembly_value(vm, import_, type.type()));
// 3.5.1.5. Let store be the surrounding agent's associated store.
// 3.5.1.6. Let (store, globaladdr) be global_alloc(store, const valtype, value).
// 3.5.1.7. Set the surrounding agent's associated store to store.
address = cache.abstract_machine().store().allocate({ type.type(), false }, cast_value);
}
// FIXME: 3.5.2. Otherwise, if v implements Global,
// FIXME: 3.5.2.1. Let globaladdr be v.[[Global]].
// 3.5.3. Otherwise,
else {
// FIXME: 3.5.3.1. Throw a LinkError exception.
return vm.throw_completion<JS::TypeError>("LinkError: Invalid value for global type"sv);
} else {
// FIXME: https://webassembly.github.io/spec/js-api/#read-the-imports step 5.2
// if v implements Global
// let globaladdr be v.[[Global]]
return vm.throw_completion<LinkError>("Invalid value for global type"sv);
}
// 3.5.4. Let externglobal be global globaladdr.
@ -300,8 +304,7 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
[&](Wasm::MemoryType const&) -> JS::ThrowCompletionOr<void> {
// 3.6.1. If v does not implement Memory, throw a LinkError exception.
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);
return vm.throw_completion<LinkError>("Expected an instance of WebAssembly.Memory for a memory import"sv);
}
// 3.6.2. Let externmem be the external value mem v.[[Memory]].
auto address = static_cast<WebAssembly::Memory const&>(import_.as_object()).address();
@ -313,8 +316,7 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
[&](Wasm::TableType const&) -> JS::ThrowCompletionOr<void> {
// 3.7.1. If v does not implement Table, throw a LinkError exception.
if (!import_.is_object() || !is<WebAssembly::Table>(import_.as_object())) {
// FIXME: Throw a LinkError instead
return vm.throw_completion<JS::TypeError>("LinkError: Expected an instance of WebAssembly.Table for a table import"sv);
return vm.throw_completion<LinkError>("Expected an instance of WebAssembly.Table for a table import"sv);
}
// 3.7.2. Let tableaddr be v.[[Table]].
// 3.7.3. Let externtable be the external value table tableaddr.
@ -324,8 +326,9 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
return {};
},
[&](auto const&) -> JS::ThrowCompletionOr<void> {
// (noop)
return {};
// FIXME: Implement these.
dbgln("Unimplemented import of non-function attempted");
return vm.throw_completion<JS::TypeError>("Not Implemented"sv);
}));
}
}
@ -334,17 +337,15 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
linker.link(resolved_imports);
auto link_result = linker.finish();
if (link_result.is_error()) {
// FIXME: Throw a LinkError.
StringBuilder builder;
builder.append("LinkError: Missing "sv);
builder.append("Missing "sv);
builder.join(' ', link_result.error().missing_imports);
return vm.throw_completion<JS::TypeError>(MUST(builder.to_string()));
return vm.throw_completion<LinkError>(MUST(builder.to_string()));
}
auto instance_result = cache.abstract_machine().instantiate(module, link_result.release_value());
if (instance_result.is_error()) {
// FIXME: Throw a LinkError instead.
return vm.throw_completion<JS::TypeError>(instance_result.error().error);
return vm.throw_completion<LinkError>(String::from_byte_string(instance_result.error().error).release_value_but_fixme_should_propagate_errors());
}
return instance_result.release_value();
@ -356,14 +357,12 @@ JS::ThrowCompletionOr<NonnullRefPtr<CompiledWebAssemblyModule>> compile_a_webass
FixedMemoryStream stream { data.bytes() };
auto module_result = Wasm::Module::parse(stream);
if (module_result.is_error()) {
// FIXME: Throw CompileError instead.
return vm.throw_completion<JS::TypeError>(Wasm::parse_error_to_byte_string(module_result.error()));
return vm.throw_completion<CompileError>(String::from_byte_string(Wasm::parse_error_to_byte_string(module_result.error())).release_value_but_fixme_should_propagate_errors());
}
auto& cache = get_cache(*vm.current_realm());
if (auto validation_result = cache.abstract_machine().validate(module_result.value()); validation_result.is_error()) {
// FIXME: Throw CompileError instead.
return vm.throw_completion<JS::TypeError>(validation_result.error().error_string);
return vm.throw_completion<CompileError>(String::from_byte_string(validation_result.error().error_string).release_value_but_fixme_should_propagate_errors());
}
auto compiled_module = make_ref_counted<CompiledWebAssemblyModule>(module_result.release_value());
cache.add_compiled_module(compiled_module);