mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-24 13:35:12 +00:00
LibWasm: Make traps hold on to externally-managed data
...instead of specially handling JS::Completion. This makes it possible for LibWeb/LibJS to have full control over how these things are made, stored, and visited (whenever). Fixes an issue where we couldn't roundtrip a JS exception through Wasm.
This commit is contained in:
parent
7b2a427430
commit
51bab5b186
Notes:
github-actions[bot]
2025-04-22 14:45:25 +00:00
Author: https://github.com/alimpfard Commit: https://github.com/LadybirdBrowser/ladybird/commit/51bab5b186b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4423 Reviewed-by: https://github.com/ADKaster ✅
11 changed files with 176 additions and 111 deletions
|
@ -239,6 +239,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
}
|
||||
|
||||
BytecodeInterpreter interpreter(m_stack_info);
|
||||
auto handle = register_scoped(interpreter);
|
||||
|
||||
for (auto& entry : module.global_section().entries()) {
|
||||
Configuration config { m_store };
|
||||
|
@ -250,9 +251,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
entry.expression(),
|
||||
1,
|
||||
});
|
||||
auto result = config.execute(interpreter).assert_wasm_result();
|
||||
auto result = config.execute(interpreter);
|
||||
if (result.is_trap())
|
||||
return InstantiationError { ByteString::formatted("Global value construction trapped: {}", result.trap().reason) };
|
||||
return InstantiationError { "Global instantiation trapped", move(result.trap()) };
|
||||
global_values.append(result.values().first());
|
||||
}
|
||||
|
||||
|
@ -271,9 +272,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
entry,
|
||||
entry.instructions().size(),
|
||||
});
|
||||
auto result = config.execute(interpreter).assert_wasm_result();
|
||||
auto result = config.execute(interpreter);
|
||||
if (result.is_trap())
|
||||
return InstantiationError { ByteString::formatted("Element construction trapped: {}", result.trap().reason) };
|
||||
return InstantiationError { "Element section initialisation trapped", move(result.trap()) };
|
||||
|
||||
for (auto& value : result.values()) {
|
||||
auto reference = value.to<Reference>();
|
||||
|
@ -306,9 +307,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
active_ptr->expression,
|
||||
1,
|
||||
});
|
||||
auto result = config.execute(interpreter).assert_wasm_result();
|
||||
auto result = config.execute(interpreter);
|
||||
if (result.is_trap())
|
||||
return InstantiationError { ByteString::formatted("Element section initialisation trapped: {}", result.trap().reason) };
|
||||
return InstantiationError { "Element section initialisation trapped", move(result.trap()) };
|
||||
auto d = result.values().first().to<i32>();
|
||||
auto table_instance = m_store.get(main_module_instance.tables()[active_ptr->index.value()]);
|
||||
if (current_index >= main_module_instance.elements().size())
|
||||
|
@ -341,9 +342,9 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
data.offset,
|
||||
1,
|
||||
});
|
||||
auto result = config.execute(interpreter).assert_wasm_result();
|
||||
auto result = config.execute(interpreter);
|
||||
if (result.is_trap())
|
||||
return InstantiationError { ByteString::formatted("Data section initialisation trapped: {}", result.trap().reason) };
|
||||
return InstantiationError { "Data section initialisation trapped", move(result.trap()) };
|
||||
size_t offset = result.values().first().to<u64>();
|
||||
if (main_module_instance.memories().size() <= data.index.value()) {
|
||||
return InstantiationError {
|
||||
|
@ -391,7 +392,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
|||
}
|
||||
auto result = invoke(functions[index.value()], {});
|
||||
if (result.is_trap())
|
||||
return InstantiationError { ByteString::formatted("Start function trapped: {}", result.trap().reason) };
|
||||
return InstantiationError { "Start function trapped", move(result.trap()) };
|
||||
}
|
||||
|
||||
return InstantiationResult { move(main_module_instance_pointer) };
|
||||
|
@ -491,6 +492,7 @@ Optional<InstantiationError> AbstractMachine::allocate_all_final_phase(Module co
|
|||
Result AbstractMachine::invoke(FunctionAddress address, Vector<Value> arguments)
|
||||
{
|
||||
BytecodeInterpreter interpreter(m_stack_info);
|
||||
auto handle = register_scoped(interpreter);
|
||||
return invoke(interpreter, address, move(arguments));
|
||||
}
|
||||
|
||||
|
@ -575,4 +577,10 @@ void Linker::populate()
|
|||
m_unresolved_imports.set(m_ordered_imports.last());
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractMachine::visit_external_resources(HostVisitOps const& host)
|
||||
{
|
||||
for (auto interpreter_ptr : m_active_interpreters)
|
||||
interpreter_ptr->visit_external_resources(host);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
|
@ -14,18 +15,13 @@
|
|||
#include <AK/UFixedBigInt.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
// NOTE: Special case for Wasm::Result.
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
class Configuration;
|
||||
class Result;
|
||||
struct Interpreter;
|
||||
struct Trap;
|
||||
|
||||
struct InstantiationError {
|
||||
ByteString error { "Unknown error" };
|
||||
};
|
||||
struct LinkError {
|
||||
enum OtherErrors {
|
||||
InvalidImportedModule,
|
||||
|
@ -198,37 +194,43 @@ private:
|
|||
u128 m_value;
|
||||
};
|
||||
|
||||
struct Trap {
|
||||
ByteString reason;
|
||||
struct ExternallyManagedTrap {
|
||||
Array<u8, 64> data;
|
||||
|
||||
template<typename T>
|
||||
T const& unsafe_external_object_as() const
|
||||
{
|
||||
static_assert(sizeof(T) <= sizeof(data), "Object size is too large for ExternallyManagedTrap");
|
||||
return *reinterpret_cast<T const*>(data.data());
|
||||
}
|
||||
};
|
||||
|
||||
// A variant of Result that does not include external reasons for error (JS::Completion, for now).
|
||||
class PureResult {
|
||||
public:
|
||||
explicit PureResult(Vector<Value> values)
|
||||
: m_result(move(values))
|
||||
struct Trap {
|
||||
Variant<ByteString, ExternallyManagedTrap> data;
|
||||
|
||||
ByteString format() const
|
||||
{
|
||||
if (auto const* ptr = data.get_pointer<ByteString>())
|
||||
return *ptr;
|
||||
return "<Externally managed Trap Data>";
|
||||
}
|
||||
|
||||
PureResult(Trap trap)
|
||||
: m_result(move(trap))
|
||||
template<typename T>
|
||||
static Trap from_external_object(T&& object)
|
||||
{
|
||||
static_assert(sizeof(T) <= sizeof(ExternallyManagedTrap::data), "Object size is too large for ExternallyManagedTrap");
|
||||
static_assert(IsTriviallyCopyable<T>, "Object must be trivially copyable");
|
||||
static_assert(IsTriviallyDestructible<T>, "Object must be trivially destructible");
|
||||
|
||||
ExternallyManagedTrap externally_managed_trap;
|
||||
new (externally_managed_trap.data.data()) T(forward<T>(object));
|
||||
return Trap { externally_managed_trap };
|
||||
}
|
||||
|
||||
auto is_trap() const { return m_result.has<Trap>(); }
|
||||
auto& values() const { return m_result.get<Vector<Value>>(); }
|
||||
auto& values() { return m_result.get<Vector<Value>>(); }
|
||||
auto& trap() const { return m_result.get<Trap>(); }
|
||||
auto& trap() { return m_result.get<Trap>(); }
|
||||
|
||||
private:
|
||||
friend class Result;
|
||||
explicit PureResult(Variant<Vector<Value>, Trap>&& result)
|
||||
: m_result(move(result))
|
||||
static Trap from_string(ByteString string)
|
||||
{
|
||||
return Trap { move(string) };
|
||||
}
|
||||
|
||||
Variant<Vector<Value>, Trap> m_result;
|
||||
};
|
||||
|
||||
class Result {
|
||||
|
@ -243,34 +245,24 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
Result(JS::Completion completion)
|
||||
: m_result(move(completion))
|
||||
{
|
||||
VERIFY(m_result.get<JS::Completion>().is_abrupt());
|
||||
}
|
||||
|
||||
Result(PureResult&& result)
|
||||
: m_result(result.m_result.downcast<decltype(m_result)>())
|
||||
{
|
||||
}
|
||||
|
||||
auto is_trap() const { return m_result.has<Trap>(); }
|
||||
auto is_completion() const { return m_result.has<JS::Completion>(); }
|
||||
auto& values() const { return m_result.get<Vector<Value>>(); }
|
||||
auto& values() { return m_result.get<Vector<Value>>(); }
|
||||
auto& trap() const { return m_result.get<Trap>(); }
|
||||
auto& trap() { return m_result.get<Trap>(); }
|
||||
auto& completion() { return m_result.get<JS::Completion>(); }
|
||||
auto& completion() const { return m_result.get<JS::Completion>(); }
|
||||
|
||||
PureResult assert_wasm_result() &&
|
||||
{
|
||||
VERIFY(!is_completion());
|
||||
return PureResult(move(m_result).downcast<Vector<Value>, Trap>());
|
||||
}
|
||||
|
||||
private:
|
||||
Variant<Vector<Value>, Trap, JS::Completion> m_result;
|
||||
explicit Result(Variant<Vector<Value>, Trap>&& result)
|
||||
: m_result(move(result))
|
||||
{
|
||||
}
|
||||
|
||||
Variant<Vector<Value>, Trap> m_result;
|
||||
};
|
||||
|
||||
struct InstantiationError {
|
||||
ByteString error { "Unknown error" };
|
||||
Optional<Trap> relevant_trap {};
|
||||
};
|
||||
|
||||
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>;
|
||||
|
@ -628,6 +620,10 @@ private:
|
|||
|
||||
using InstantiationResult = AK::ErrorOr<NonnullOwnPtr<ModuleInstance>, InstantiationError>;
|
||||
|
||||
struct HostVisitOps {
|
||||
Function<void(ExternallyManagedTrap&)> visit_trap;
|
||||
};
|
||||
|
||||
class AbstractMachine {
|
||||
public:
|
||||
explicit AbstractMachine() = default;
|
||||
|
@ -644,11 +640,38 @@ public:
|
|||
|
||||
void enable_instruction_count_limit() { m_should_limit_instruction_count = true; }
|
||||
|
||||
void visit_external_resources(HostVisitOps const&);
|
||||
|
||||
private:
|
||||
class InterpreterHandle {
|
||||
public:
|
||||
explicit InterpreterHandle(AbstractMachine& machine, Interpreter& interpreter)
|
||||
: m_machine(machine)
|
||||
, m_interpreter(interpreter)
|
||||
{
|
||||
m_machine.m_active_interpreters.set(&m_interpreter);
|
||||
}
|
||||
|
||||
~InterpreterHandle()
|
||||
{
|
||||
m_machine.m_active_interpreters.remove(&m_interpreter);
|
||||
}
|
||||
|
||||
private:
|
||||
AbstractMachine& m_machine;
|
||||
Interpreter& m_interpreter;
|
||||
};
|
||||
|
||||
[[nodiscard]] InterpreterHandle register_scoped(Interpreter& interpreter)
|
||||
{
|
||||
return InterpreterHandle(*this, interpreter);
|
||||
}
|
||||
|
||||
Optional<InstantiationError> allocate_all_initial_phase(Module const&, ModuleInstance&, Vector<ExternValue>&, Vector<Value>& global_values, Vector<FunctionAddress>& own_functions);
|
||||
Optional<InstantiationError> allocate_all_final_phase(Module const&, ModuleInstance&, Vector<Vector<Reference>>& elements);
|
||||
Store m_store;
|
||||
StackInfo m_stack_info;
|
||||
HashTable<Interpreter*> m_active_interpreters;
|
||||
bool m_should_limit_instruction_count { false };
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ void BytecodeInterpreter::interpret(Configuration& configuration)
|
|||
while (current_ip_value < max_ip_value) {
|
||||
if (should_limit_instruction_count) {
|
||||
if (executed_instructions++ >= Constants::max_allowed_executed_instructions_per_call) [[unlikely]] {
|
||||
m_trap = Trap { "Exceeded maximum allowed number of instructions" };
|
||||
m_trap = Trap::from_string("Exceeded maximum allowed number of instructions");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ void BytecodeInterpreter::load_and_push(Configuration& configuration, Instructio
|
|||
auto base = entry.to<i32>();
|
||||
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
|
||||
if (instance_address + sizeof(ReadType) > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
dbgln("LibWasm: Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + sizeof(ReadType), memory->size());
|
||||
return;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ void BytecodeInterpreter::load_and_push_mxn(Configuration& configuration, Instru
|
|||
auto base = entry.to<i32>();
|
||||
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
|
||||
if (instance_address + M * N / 8 > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
dbgln("LibWasm: Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + M * N / 8, memory->size());
|
||||
return;
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ void BytecodeInterpreter::load_and_push_lane_n(Configuration& configuration, Ins
|
|||
auto base = configuration.value_stack().take_last().to<u32>();
|
||||
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + memarg_and_lane.memory.offset;
|
||||
if (instance_address + N / 8 > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
return;
|
||||
}
|
||||
auto slice = memory->data().bytes().slice(instance_address, N / 8);
|
||||
|
@ -149,7 +149,7 @@ void BytecodeInterpreter::load_and_push_zero_n(Configuration& configuration, Ins
|
|||
auto base = configuration.value_stack().take_last().to<u32>();
|
||||
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + memarg_and_lane.offset;
|
||||
if (instance_address + N / 8 > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
return;
|
||||
}
|
||||
auto slice = memory->data().bytes().slice(instance_address, N / 8);
|
||||
|
@ -168,7 +168,7 @@ void BytecodeInterpreter::load_and_push_m_splat(Configuration& configuration, In
|
|||
auto base = entry.to<i32>();
|
||||
u64 instance_address = static_cast<u64>(bit_cast<u32>(base)) + arg.offset;
|
||||
if (instance_address + M / 8 > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
dbgln("LibWasm: Memory access out of bounds (expected {} to be less than or equal to {})", instance_address + M / 8, memory->size());
|
||||
return;
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ void BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd
|
|||
|
||||
configuration.value_stack().remove(configuration.value_stack().size() - span.size(), span.size());
|
||||
|
||||
Result result { Trap { ""sv } };
|
||||
Result result { Trap::from_string("") };
|
||||
if (instance->has<WasmFunction>()) {
|
||||
CallFrameHandle handle { *this, configuration };
|
||||
result = configuration.call(*this, address, move(args));
|
||||
|
@ -251,11 +251,6 @@ void BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd
|
|||
return;
|
||||
}
|
||||
|
||||
if (result.is_completion()) {
|
||||
m_trap = move(result.completion());
|
||||
return;
|
||||
}
|
||||
|
||||
configuration.value_stack().ensure_capacity(configuration.value_stack().size() + result.values().size());
|
||||
for (auto& entry : result.values().in_reverse())
|
||||
configuration.value_stack().unchecked_append(entry);
|
||||
|
@ -361,7 +356,7 @@ void BytecodeInterpreter::store_to_memory(Configuration& configuration, Instruct
|
|||
Checked addition { instance_address };
|
||||
addition += data.size();
|
||||
if (addition.has_overflow() || addition.value() > memory->size()) {
|
||||
m_trap = Trap { "Memory access out of bounds" };
|
||||
m_trap = Trap::from_string("Memory access out of bounds");
|
||||
dbgln("LibWasm: Memory access out of bounds (expected 0 <= {} and {} <= {})", instance_address, instance_address + data.size(), memory->size());
|
||||
return;
|
||||
}
|
||||
|
@ -376,7 +371,7 @@ T BytecodeInterpreter::read_value(ReadonlyBytes data)
|
|||
auto value_or_error = stream.read_value<LittleEndian<T>>();
|
||||
if (value_or_error.is_error()) {
|
||||
dbgln("Read from {} failed", data.data());
|
||||
m_trap = Trap { "Read from memory failed" };
|
||||
m_trap = Trap::from_string("Read from memory failed");
|
||||
}
|
||||
return value_or_error.release_value();
|
||||
}
|
||||
|
@ -387,7 +382,7 @@ float BytecodeInterpreter::read_value<float>(ReadonlyBytes data)
|
|||
FixedMemoryStream stream { data };
|
||||
auto raw_value_or_error = stream.read_value<LittleEndian<u32>>();
|
||||
if (raw_value_or_error.is_error())
|
||||
m_trap = Trap { "Read from memory failed" };
|
||||
m_trap = Trap::from_string("Read from memory failed");
|
||||
auto raw_value = raw_value_or_error.release_value();
|
||||
return bit_cast<float>(static_cast<u32>(raw_value));
|
||||
}
|
||||
|
@ -398,7 +393,7 @@ double BytecodeInterpreter::read_value<double>(ReadonlyBytes data)
|
|||
FixedMemoryStream stream { data };
|
||||
auto raw_value_or_error = stream.read_value<LittleEndian<u64>>();
|
||||
if (raw_value_or_error.is_error())
|
||||
m_trap = Trap { "Read from memory failed" };
|
||||
m_trap = Trap::from_string("Read from memory failed");
|
||||
auto raw_value = raw_value_or_error.release_value();
|
||||
return bit_cast<double>(static_cast<u64>(raw_value));
|
||||
}
|
||||
|
@ -409,7 +404,7 @@ ALWAYS_INLINE void BytecodeInterpreter::interpret_instruction(Configuration& con
|
|||
|
||||
switch (instruction.opcode().value()) {
|
||||
case Instructions::unreachable.value():
|
||||
m_trap = Trap { "Unreachable" };
|
||||
m_trap = Trap::from_string("Unreachable");
|
||||
return;
|
||||
case Instructions::nop.value():
|
||||
return;
|
||||
|
@ -1659,7 +1654,7 @@ void DebuggerBytecodeInterpreter::interpret_instruction(Configuration& configura
|
|||
if (pre_interpret_hook) {
|
||||
auto result = pre_interpret_hook(configuration, ip, instruction);
|
||||
if (!result) {
|
||||
m_trap = Trap { "Trapped by user request" };
|
||||
m_trap = Trap::from_string("Trapped by user request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1669,7 +1664,7 @@ void DebuggerBytecodeInterpreter::interpret_instruction(Configuration& configura
|
|||
if (post_interpret_hook) {
|
||||
auto result = post_interpret_hook(configuration, ip, instruction, *this);
|
||||
if (!result) {
|
||||
m_trap = Trap { "Trapped by user request" };
|
||||
m_trap = Trap::from_string("Trapped by user request");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,17 @@ struct BytecodeInterpreter : public Interpreter {
|
|||
|
||||
virtual ~BytecodeInterpreter() override = default;
|
||||
virtual bool did_trap() const final { return !m_trap.has<Empty>(); }
|
||||
virtual ByteString trap_reason() const final
|
||||
virtual Trap trap() const final
|
||||
{
|
||||
return m_trap.visit(
|
||||
[](Empty) -> ByteString { VERIFY_NOT_REACHED(); },
|
||||
[](Trap const& trap) { return trap.reason; },
|
||||
[](JS::Completion const& completion) { return completion.value().to_string_without_side_effects().to_byte_string(); });
|
||||
return m_trap.get<Trap>();
|
||||
}
|
||||
virtual void clear_trap() final { m_trap = Empty {}; }
|
||||
virtual void visit_external_resources(HostVisitOps const& host) override
|
||||
{
|
||||
if (auto ptr = m_trap.get_pointer<Trap>())
|
||||
if (auto data = ptr->data.get_pointer<ExternallyManagedTrap>())
|
||||
host.visit_trap(*data);
|
||||
}
|
||||
|
||||
struct CallFrameHandle {
|
||||
explicit CallFrameHandle(BytecodeInterpreter& interpreter, Configuration& configuration)
|
||||
|
@ -82,11 +85,11 @@ protected:
|
|||
ALWAYS_INLINE bool trap_if_not(bool value, StringView reason)
|
||||
{
|
||||
if (!value)
|
||||
m_trap = Trap { reason };
|
||||
m_trap = Trap { ByteString(reason) };
|
||||
return !m_trap.has<Empty>();
|
||||
}
|
||||
|
||||
Variant<Trap, JS::Completion, Empty> m_trap;
|
||||
Variant<Trap, Empty> m_trap;
|
||||
StackInfo const& m_stack_info;
|
||||
};
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ Result Configuration::call(Interpreter& interpreter, FunctionAddress address, Ve
|
|||
{
|
||||
auto* function = m_store.get(address);
|
||||
if (!function)
|
||||
return Trap {};
|
||||
return Trap::from_string("Attempt to call nonexistent function by address");
|
||||
if (auto* wasm_function = function->get_pointer<WasmFunction>()) {
|
||||
Vector<Value> locals = move(arguments);
|
||||
locals.ensure_capacity(locals.size() + wasm_function->code().func().locals().size());
|
||||
|
@ -50,7 +50,7 @@ Result Configuration::execute(Interpreter& interpreter)
|
|||
{
|
||||
interpreter.interpret(*this);
|
||||
if (interpreter.did_trap())
|
||||
return Trap { interpreter.trap_reason() };
|
||||
return interpreter.trap();
|
||||
|
||||
Vector<Value> results;
|
||||
results.ensure_capacity(frame().arity());
|
||||
|
|
|
@ -13,9 +13,10 @@ namespace Wasm {
|
|||
struct Interpreter {
|
||||
virtual ~Interpreter() = default;
|
||||
virtual void interpret(Configuration&) = 0;
|
||||
virtual Trap trap() const = 0;
|
||||
virtual bool did_trap() const = 0;
|
||||
virtual ByteString trap_reason() const = 0;
|
||||
virtual void clear_trap() = 0;
|
||||
virtual void visit_external_resources(HostVisitOps const&) { }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,6 @@ if (NOT WIN32)
|
|||
endif()
|
||||
|
||||
serenity_lib(LibWasm wasm)
|
||||
target_link_libraries(LibWasm PRIVATE LibCore LibGC LibJS)
|
||||
target_link_libraries(LibWasm PRIVATE LibCore)
|
||||
|
||||
include(wasm_spec_tests)
|
||||
|
|
|
@ -59,6 +59,10 @@ void visit_edges(JS::Object& object, JS::Cell::Visitor& visitor)
|
|||
visitor.visit(cache.imported_objects());
|
||||
visitor.visit(cache.extern_values());
|
||||
visitor.visit(cache.global_instances());
|
||||
cache.abstract_machine().visit_external_resources({ .visit_trap = [&visitor](Wasm::ExternallyManagedTrap const& trap) {
|
||||
auto& completion = trap.unsafe_external_object_as<JS::Completion>();
|
||||
visitor.visit(completion.value());
|
||||
} });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +160,32 @@ WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> instantiate_streaming(JS::VM& vm,
|
|||
|
||||
namespace Detail {
|
||||
|
||||
#define TRY_OR_RETURN_TRAP(...) \
|
||||
({ \
|
||||
/* Ignore -Wshadow to allow nesting the macro. */ \
|
||||
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
|
||||
auto&& _temporary_result = (__VA_ARGS__)); \
|
||||
static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
|
||||
"Do not return a reference from a fallible expression"); \
|
||||
if (_temporary_result.is_error()) [[unlikely]] \
|
||||
return Wasm::Trap::from_external_object(_temporary_result.release_error()); \
|
||||
_temporary_result.release_value(); \
|
||||
})
|
||||
|
||||
#define TRY_OR_RETURN_OOM_TRAP(vm, ...) \
|
||||
({ \
|
||||
/* Ignore -Wshadow to allow nesting the macro. */ \
|
||||
AK_IGNORE_DIAGNOSTIC("-Wshadow", \
|
||||
auto&& _temporary_result = (__VA_ARGS__)); \
|
||||
if (_temporary_result.is_error()) { \
|
||||
VERIFY(_temporary_result.error().code() == ENOMEM); \
|
||||
return Wasm::Trap::from_external_object((vm).throw_completion<JS::InternalError>((vm).error_message(::JS::VM::ErrorMessage::OutOfMemory))); \
|
||||
} \
|
||||
static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
|
||||
"Do not return a reference from a fallible expression"); \
|
||||
_temporary_result.release_value(); \
|
||||
})
|
||||
|
||||
JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS::VM& vm, Wasm::Module const& module, GC::Ptr<JS::Object> import_object)
|
||||
{
|
||||
Wasm::Linker linker { module };
|
||||
|
@ -216,28 +246,28 @@ JS::ThrowCompletionOr<NonnullOwnPtr<Wasm::ModuleInstance>> instantiate_module(JS
|
|||
++index;
|
||||
}
|
||||
|
||||
auto result = TRY(JS::call(vm, function, JS::js_undefined(), argument_values.span()));
|
||||
auto result = TRY_OR_RETURN_TRAP(JS::call(vm, function, JS::js_undefined(), argument_values.span()));
|
||||
if (type.results().is_empty())
|
||||
return Wasm::Result { Vector<Wasm::Value> {} };
|
||||
|
||||
if (type.results().size() == 1)
|
||||
return Wasm::Result { Vector<Wasm::Value> { TRY(to_webassembly_value(vm, result, type.results().first())) } };
|
||||
return Wasm::Result { Vector<Wasm::Value> { TRY_OR_RETURN_TRAP(to_webassembly_value(vm, result, type.results().first())) } };
|
||||
|
||||
auto method = TRY(result.get_method(vm, vm.names.iterator));
|
||||
auto method = TRY_OR_RETURN_TRAP(result.get_method(vm, vm.names.iterator));
|
||||
if (method == JS::js_undefined())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, result.to_string_without_side_effects());
|
||||
return Wasm::Trap::from_external_object(vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, result.to_string_without_side_effects()));
|
||||
|
||||
auto values = TRY(JS::iterator_to_list(vm, TRY(JS::get_iterator_from_method(vm, result, *method))));
|
||||
auto values = TRY_OR_RETURN_TRAP(JS::iterator_to_list(vm, TRY_OR_RETURN_TRAP(JS::get_iterator_from_method(vm, result, *method))));
|
||||
|
||||
if (values.size() != type.results().size())
|
||||
return vm.throw_completion<JS::TypeError>(ByteString::formatted("Invalid number of return values for multi-value wasm return of {} objects", type.results().size()));
|
||||
return Wasm::Trap::from_external_object(vm.throw_completion<JS::TypeError>(ByteString::formatted("Invalid number of return values for multi-value wasm return of {} objects", type.results().size())));
|
||||
|
||||
Vector<Wasm::Value> wasm_values;
|
||||
TRY_OR_THROW_OOM(vm, wasm_values.try_ensure_capacity(values.size()));
|
||||
TRY_OR_RETURN_OOM_TRAP(vm, wasm_values.try_ensure_capacity(values.size()));
|
||||
|
||||
size_t i = 0;
|
||||
for (auto& value : values)
|
||||
wasm_values.append(TRY(to_webassembly_value(vm, value, type.results()[i++])));
|
||||
wasm_values.append(TRY_OR_RETURN_TRAP(to_webassembly_value(vm, value, type.results()[i++])));
|
||||
|
||||
return Wasm::Result { move(wasm_values) };
|
||||
},
|
||||
|
@ -415,8 +445,11 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
|
|||
auto& cache = get_cache(realm);
|
||||
auto result = cache.abstract_machine().invoke(address, move(values));
|
||||
// FIXME: Use the convoluted mapping of errors defined in the spec.
|
||||
if (result.is_trap())
|
||||
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Wasm execution trapped (WIP): {}", result.trap().reason)));
|
||||
if (result.is_trap()) {
|
||||
if (auto ptr = result.trap().data.get_pointer<Wasm::ExternallyManagedTrap>())
|
||||
return ptr->unsafe_external_object_as<JS::Completion>();
|
||||
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Wasm execution trapped (WIP): {}", result.trap().format())));
|
||||
}
|
||||
|
||||
if (result.values().is_empty())
|
||||
return JS::js_undefined();
|
||||
|
|
|
@ -471,7 +471,7 @@ if (ASSERT_FAIL_HAS_INT OR EMSCRIPTEN)
|
|||
target_compile_definitions(test262-runner PRIVATE ASSERT_FAIL_HAS_INT)
|
||||
endif()
|
||||
|
||||
lagom_utility(wasm SOURCES ../../Utilities/wasm.cpp LIBS LibFileSystem LibWasm LibLine LibMain LibJS)
|
||||
lagom_utility(wasm SOURCES ../../Utilities/wasm.cpp LIBS LibFileSystem LibWasm LibLine LibMain)
|
||||
lagom_utility(xml SOURCES ../../Utilities/xml.cpp LIBS LibFileSystem LibMain LibXML LibURL)
|
||||
|
||||
include(CTest)
|
||||
|
|
|
@ -401,11 +401,11 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
|||
|
||||
auto functype = WebAssemblyModule::machine().store().get(function_address)->visit([&](auto& func) { return func.type(); });
|
||||
auto result = WebAssemblyModule::machine().invoke(function_address, arguments);
|
||||
if (result.is_trap())
|
||||
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Execution trapped: {}", result.trap().reason)));
|
||||
|
||||
if (result.is_completion())
|
||||
return result.completion();
|
||||
if (result.is_trap()) {
|
||||
if (auto ptr = result.trap().data.get_pointer<Wasm::ExternallyManagedTrap>())
|
||||
return ptr->unsafe_external_object_as<JS::Completion>();
|
||||
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Execution trapped: {}", result.trap().format())));
|
||||
}
|
||||
|
||||
if (result.values().is_empty())
|
||||
return JS::js_null();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <LibWasm/Printer/Printer.h>
|
||||
#include <LibWasm/Types.h>
|
||||
#include <LibWasm/Wasi.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -264,7 +265,7 @@ static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer&
|
|||
g_continue = false;
|
||||
warnln("Trapped when executing ip={}", ip);
|
||||
g_printer->print(instr);
|
||||
warnln("Trap reason: {}", interpreter.trap_reason());
|
||||
warnln("Trap reason: {}", interpreter.trap().format());
|
||||
const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
|
||||
}
|
||||
return true;
|
||||
|
@ -446,13 +447,13 @@ static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPoi
|
|||
if (!ok)
|
||||
continue;
|
||||
|
||||
Wasm::Result result { Wasm::Trap {} };
|
||||
Wasm::Result result { Wasm::Trap::from_string("") };
|
||||
{
|
||||
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
|
||||
result = config.call(g_interpreter, *address, move(values)).assert_wasm_result();
|
||||
result = config.call(g_interpreter, *address, move(values));
|
||||
}
|
||||
if (result.is_trap()) {
|
||||
warnln("Execution trapped: {}", result.trap().reason);
|
||||
warnln("Execution trapped: {}", result.trap().format());
|
||||
} else {
|
||||
if (!result.values().is_empty())
|
||||
warnln("Returned:");
|
||||
|
@ -833,15 +834,16 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
outln();
|
||||
}
|
||||
|
||||
auto result = machine.invoke(g_interpreter, run_address.value(), move(values)).assert_wasm_result();
|
||||
auto result = machine.invoke(g_interpreter, run_address.value(), move(values));
|
||||
|
||||
if (debug)
|
||||
launch_repl();
|
||||
|
||||
if (result.is_trap()) {
|
||||
if (result.trap().reason.starts_with("exit:"sv))
|
||||
return -result.trap().reason.substring_view(5).to_number<i32>().value_or(-1);
|
||||
warnln("Execution trapped: {}", result.trap().reason);
|
||||
auto trap_reason = result.trap().format();
|
||||
if (trap_reason.starts_with("exit:"sv))
|
||||
return -trap_reason.substring_view(5).to_number<i32>().value_or(-1);
|
||||
warnln("Execution trapped: {}", trap_reason);
|
||||
} else {
|
||||
if (!result.values().is_empty())
|
||||
warnln("Returned:");
|
||||
|
|
Loading…
Add table
Reference in a new issue