mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:08:10 +00:00
LibWasm+LibWeb: Sneak a JS::Completion into Wasm::Result
Imported functions in Wasm may throw JS exceptions, and we need to preserve these exceptions so we can pass them to the calling JS code. This also adds a `assert_wasm_result()` API to Result for cases where only Wasm traps or values are expected (e.g. internal uses) to avoid making LibWasm (pointlessly) handle JS exceptions that will never show up in reality.
This commit is contained in:
parent
1c3050245e
commit
6b50f23242
11 changed files with 95 additions and 35 deletions
|
@ -552,7 +552,7 @@ if (BUILD_LAGOM)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(wasm ../../Userland/Utilities/wasm.cpp)
|
add_executable(wasm ../../Userland/Utilities/wasm.cpp)
|
||||||
target_link_libraries(wasm LibCore LibWasm LibLine LibMain)
|
target_link_libraries(wasm LibCore LibWasm LibLine LibMain LibJS)
|
||||||
|
|
||||||
add_executable(xml ../../Userland/Utilities/xml.cpp)
|
add_executable(xml ../../Userland/Utilities/xml.cpp)
|
||||||
target_link_libraries(xml LibCore LibXML LibMain)
|
target_link_libraries(xml LibCore LibXML LibMain)
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
serenity_testjs_test(test-wasm.cpp test-wasm LIBS LibWasm)
|
serenity_testjs_test(test-wasm.cpp test-wasm LIBS LibWasm LibJS)
|
||||||
install(TARGETS test-wasm RUNTIME DESTINATION bin OPTIONAL)
|
install(TARGETS test-wasm RUNTIME DESTINATION bin OPTIONAL)
|
||||||
|
|
|
@ -246,6 +246,9 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
|
||||||
if (result.is_trap())
|
if (result.is_trap())
|
||||||
return vm.throw_completion<JS::TypeError>(TRY_OR_THROW_OOM(vm, String::formatted("Execution trapped: {}", result.trap().reason)));
|
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.values().is_empty())
|
if (result.values().is_empty())
|
||||||
return JS::js_null();
|
return JS::js_null();
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
||||||
entry.expression(),
|
entry.expression(),
|
||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
auto result = config.execute(interpreter);
|
auto result = config.execute(interpreter).assert_wasm_result();
|
||||||
if (result.is_trap())
|
if (result.is_trap())
|
||||||
instantiation_result = InstantiationError { DeprecatedString::formatted("Global value construction trapped: {}", result.trap().reason) };
|
instantiation_result = InstantiationError { DeprecatedString::formatted("Global value construction trapped: {}", result.trap().reason) };
|
||||||
else
|
else
|
||||||
|
@ -202,7 +202,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
||||||
entry,
|
entry,
|
||||||
entry.instructions().size(),
|
entry.instructions().size(),
|
||||||
});
|
});
|
||||||
auto result = config.execute(interpreter);
|
auto result = config.execute(interpreter).assert_wasm_result();
|
||||||
if (result.is_trap()) {
|
if (result.is_trap()) {
|
||||||
instantiation_result = InstantiationError { DeprecatedString::formatted("Element construction trapped: {}", result.trap().reason) };
|
instantiation_result = InstantiationError { DeprecatedString::formatted("Element construction trapped: {}", result.trap().reason) };
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
|
@ -255,7 +255,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
||||||
active_ptr->expression,
|
active_ptr->expression,
|
||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
auto result = config.execute(interpreter);
|
auto result = config.execute(interpreter).assert_wasm_result();
|
||||||
if (result.is_trap()) {
|
if (result.is_trap()) {
|
||||||
instantiation_result = InstantiationError { DeprecatedString::formatted("Element section initialisation trapped: {}", result.trap().reason) };
|
instantiation_result = InstantiationError { DeprecatedString::formatted("Element section initialisation trapped: {}", result.trap().reason) };
|
||||||
return IterationDecision::Break;
|
return IterationDecision::Break;
|
||||||
|
@ -315,7 +315,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
|
||||||
data.offset,
|
data.offset,
|
||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
auto result = config.execute(interpreter);
|
auto result = config.execute(interpreter).assert_wasm_result();
|
||||||
if (result.is_trap()) {
|
if (result.is_trap()) {
|
||||||
instantiation_result = InstantiationError { DeprecatedString::formatted("Data section initialisation trapped: {}", result.trap().reason) };
|
instantiation_result = InstantiationError { DeprecatedString::formatted("Data section initialisation trapped: {}", result.trap().reason) };
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <LibWasm/Types.h>
|
#include <LibWasm/Types.h>
|
||||||
|
|
||||||
|
// NOTE: Special case for Wasm::Result.
|
||||||
|
#include <LibJS/Runtime/Completion.h>
|
||||||
|
|
||||||
namespace Wasm {
|
namespace Wasm {
|
||||||
|
|
||||||
class Configuration;
|
class Configuration;
|
||||||
|
@ -171,6 +174,35 @@ struct Trap {
|
||||||
DeprecatedString reason;
|
DeprecatedString reason;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PureResult(Trap trap)
|
||||||
|
: m_result(move(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))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant<Vector<Value>, Trap> m_result;
|
||||||
|
};
|
||||||
|
|
||||||
class Result {
|
class Result {
|
||||||
public:
|
public:
|
||||||
explicit Result(Vector<Value> values)
|
explicit Result(Vector<Value> values)
|
||||||
|
@ -183,14 +215,34 @@ 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_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() const { return m_result.get<Vector<Value>>(); }
|
||||||
auto& values() { return m_result.get<Vector<Value>>(); }
|
auto& values() { return m_result.get<Vector<Value>>(); }
|
||||||
auto& trap() const { return m_result.get<Trap>(); }
|
auto& trap() const { return m_result.get<Trap>(); }
|
||||||
auto& trap() { 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:
|
private:
|
||||||
Variant<Vector<Value>, Trap> m_result;
|
Variant<Vector<Value>, Trap, JS::Completion> m_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>;
|
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>;
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace Wasm {
|
||||||
|
|
||||||
void BytecodeInterpreter::interpret(Configuration& configuration)
|
void BytecodeInterpreter::interpret(Configuration& configuration)
|
||||||
{
|
{
|
||||||
m_trap.clear();
|
m_trap = Empty {};
|
||||||
auto& instructions = configuration.frame().expression().instructions();
|
auto& instructions = configuration.frame().expression().instructions();
|
||||||
auto max_ip_value = InstructionPointer { instructions.size() };
|
auto max_ip_value = InstructionPointer { instructions.size() };
|
||||||
auto& current_ip_value = configuration.ip();
|
auto& current_ip_value = configuration.ip();
|
||||||
|
@ -51,7 +51,7 @@ void BytecodeInterpreter::interpret(Configuration& configuration)
|
||||||
auto& instruction = instructions[current_ip_value.value()];
|
auto& instruction = instructions[current_ip_value.value()];
|
||||||
auto old_ip = current_ip_value;
|
auto old_ip = current_ip_value;
|
||||||
interpret(configuration, current_ip_value, instruction);
|
interpret(configuration, current_ip_value, instruction);
|
||||||
if (m_trap.has_value())
|
if (did_trap())
|
||||||
return;
|
return;
|
||||||
if (current_ip_value == old_ip) // If no jump occurred
|
if (current_ip_value == old_ip) // If no jump occurred
|
||||||
++current_ip_value;
|
++current_ip_value;
|
||||||
|
@ -140,6 +140,11 @@ void BytecodeInterpreter::call_address(Configuration& configuration, FunctionAdd
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.is_completion()) {
|
||||||
|
m_trap = move(result.completion());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
configuration.stack().entries().ensure_capacity(configuration.stack().size() + result.values().size());
|
configuration.stack().entries().ensure_capacity(configuration.stack().size() + result.values().size());
|
||||||
for (auto& entry : result.values().in_reverse())
|
for (auto& entry : result.values().in_reverse())
|
||||||
configuration.stack().entries().unchecked_append(move(entry));
|
configuration.stack().entries().unchecked_append(move(entry));
|
||||||
|
|
|
@ -15,9 +15,15 @@ namespace Wasm {
|
||||||
struct BytecodeInterpreter : public Interpreter {
|
struct BytecodeInterpreter : public Interpreter {
|
||||||
virtual void interpret(Configuration&) override;
|
virtual void interpret(Configuration&) override;
|
||||||
virtual ~BytecodeInterpreter() override = default;
|
virtual ~BytecodeInterpreter() override = default;
|
||||||
virtual bool did_trap() const override { return m_trap.has_value(); }
|
virtual bool did_trap() const override { return !m_trap.has<Empty>(); }
|
||||||
virtual DeprecatedString trap_reason() const override { return m_trap.value().reason; }
|
virtual DeprecatedString trap_reason() const override
|
||||||
virtual void clear_trap() override { m_trap.clear(); }
|
{
|
||||||
|
return m_trap.visit(
|
||||||
|
[](Empty) -> DeprecatedString { VERIFY_NOT_REACHED(); },
|
||||||
|
[](Trap const& trap) { return trap.reason; },
|
||||||
|
[](JS::Completion const& completion) { return completion.value()->to_string_without_side_effects().release_value().to_deprecated_string(); });
|
||||||
|
}
|
||||||
|
virtual void clear_trap() override { m_trap = Empty {}; }
|
||||||
|
|
||||||
struct CallFrameHandle {
|
struct CallFrameHandle {
|
||||||
explicit CallFrameHandle(BytecodeInterpreter& interpreter, Configuration& configuration)
|
explicit CallFrameHandle(BytecodeInterpreter& interpreter, Configuration& configuration)
|
||||||
|
@ -62,10 +68,10 @@ protected:
|
||||||
{
|
{
|
||||||
if (!value)
|
if (!value)
|
||||||
m_trap = Trap { reason };
|
m_trap = Trap { reason };
|
||||||
return m_trap.has_value();
|
return !m_trap.has<Empty>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Trap> m_trap;
|
Variant<Trap, JS::Completion, Empty> m_trap;
|
||||||
StackInfo m_stack_info;
|
StackInfo m_stack_info;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ set(SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_lib(LibWasm wasm)
|
serenity_lib(LibWasm wasm)
|
||||||
target_link_libraries(LibWasm PRIVATE LibCore)
|
target_link_libraries(LibWasm PRIVATE LibCore LibJS)
|
||||||
|
|
||||||
# FIXME: Install these into usr/Tests/LibWasm
|
# FIXME: Install these into usr/Tests/LibWasm
|
||||||
include(wasm_spec_tests)
|
include(wasm_spec_tests)
|
||||||
|
|
|
@ -200,20 +200,12 @@ JS::ThrowCompletionOr<size_t> WebAssemblyObject::instantiate_module(JS::VM& vm,
|
||||||
for (auto& entry : arguments)
|
for (auto& entry : arguments)
|
||||||
argument_values.append(to_js_value(vm, entry));
|
argument_values.append(to_js_value(vm, entry));
|
||||||
|
|
||||||
auto result_or_error = JS::call(vm, function, JS::js_undefined(), move(argument_values));
|
auto result = TRY(JS::call(vm, function, JS::js_undefined(), move(argument_values)));
|
||||||
if (result_or_error.is_error()) {
|
|
||||||
return Wasm::Trap();
|
|
||||||
}
|
|
||||||
if (type.results().is_empty())
|
if (type.results().is_empty())
|
||||||
return Wasm::Result { Vector<Wasm::Value> {} };
|
return Wasm::Result { Vector<Wasm::Value> {} };
|
||||||
|
|
||||||
if (type.results().size() == 1) {
|
if (type.results().size() == 1)
|
||||||
auto value_or_error = to_webassembly_value(vm, result_or_error.release_value(), type.results().first());
|
return Wasm::Result { Vector<Wasm::Value> { TRY(to_webassembly_value(vm, result, type.results().first())) } };
|
||||||
if (value_or_error.is_error())
|
|
||||||
return Wasm::Trap {};
|
|
||||||
|
|
||||||
return Wasm::Result { Vector<Wasm::Value> { value_or_error.release_value() } };
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Multiple returns
|
// FIXME: Multiple returns
|
||||||
TODO();
|
TODO();
|
||||||
|
|
|
@ -133,7 +133,7 @@ target_link_libraries(unzip PRIVATE LibArchive LibCompress LibCrypto)
|
||||||
target_link_libraries(update-cpp-test-results PRIVATE LibCpp)
|
target_link_libraries(update-cpp-test-results PRIVATE LibCpp)
|
||||||
target_link_libraries(useradd PRIVATE LibCrypt)
|
target_link_libraries(useradd PRIVATE LibCrypt)
|
||||||
target_link_libraries(wallpaper PRIVATE LibGfx LibGUI)
|
target_link_libraries(wallpaper PRIVATE LibGfx LibGUI)
|
||||||
target_link_libraries(wasm PRIVATE LibWasm LibLine)
|
target_link_libraries(wasm PRIVATE LibWasm LibLine LibJS)
|
||||||
target_link_libraries(wsctl PRIVATE LibGUI LibIPC)
|
target_link_libraries(wsctl PRIVATE LibGUI LibIPC)
|
||||||
target_link_libraries(xml PRIVATE LibXML)
|
target_link_libraries(xml PRIVATE LibXML)
|
||||||
target_link_libraries(zip PRIVATE LibArchive LibCompress LibCrypto)
|
target_link_libraries(zip PRIVATE LibArchive LibCompress LibCrypto)
|
||||||
|
|
|
@ -206,15 +206,17 @@ static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPoi
|
||||||
Wasm::Result result { Wasm::Trap {} };
|
Wasm::Result result { Wasm::Trap {} };
|
||||||
{
|
{
|
||||||
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
|
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
|
||||||
result = config.call(g_interpreter, *address, move(values));
|
result = config.call(g_interpreter, *address, move(values)).assert_wasm_result();
|
||||||
}
|
}
|
||||||
if (result.is_trap())
|
if (result.is_trap()) {
|
||||||
warnln("Execution trapped: {}", result.trap().reason);
|
warnln("Execution trapped: {}", result.trap().reason);
|
||||||
if (!result.values().is_empty())
|
} else {
|
||||||
warnln("Returned:");
|
if (!result.values().is_empty())
|
||||||
for (auto& value : result.values()) {
|
warnln("Returned:");
|
||||||
g_stdout->write(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
for (auto& value : result.values()) {
|
||||||
g_printer->print(value);
|
g_stdout->write(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
||||||
|
g_printer->print(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -513,7 +515,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
outln();
|
outln();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = machine.invoke(g_interpreter, run_address.value(), move(values));
|
auto result = machine.invoke(g_interpreter, run_address.value(), move(values)).assert_wasm_result();
|
||||||
|
|
||||||
if (debug)
|
if (debug)
|
||||||
launch_repl();
|
launch_repl();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue