mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
a60ecea16a
Prior to funcref, a partial chunk of an invalid module was never needed, but funcref allows a partially instantiated module to modify imported tables with references to its own functions, which means we need to keep the second module alive while that function reference is present within the imported table. This was tested by the spectests, but very rarely caught as our GC does not behave particularly predictably, making it so the offending module remains in memory just long enough to let the tests pass. This commit makes it so all function references keep their respective modules alive.
860 lines
33 KiB
C++
860 lines
33 KiB
C++
/*
|
|
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
|
* Copyright (c) 2022, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/GenericLexer.h>
|
|
#include <AK/Hex.h>
|
|
#include <AK/MemoryStream.h>
|
|
#include <AK/StackInfo.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/MappedFile.h>
|
|
#include <LibFileSystem/FileSystem.h>
|
|
#include <LibLine/Editor.h>
|
|
#include <LibMain/Main.h>
|
|
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
|
#include <LibWasm/AbstractMachine/BytecodeInterpreter.h>
|
|
#include <LibWasm/Printer/Printer.h>
|
|
#include <LibWasm/Types.h>
|
|
#include <LibWasm/Wasi.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
RefPtr<Line::Editor> g_line_editor;
|
|
static OwnPtr<Stream> g_stdout {};
|
|
static OwnPtr<Wasm::Printer> g_printer {};
|
|
static bool g_continue { false };
|
|
static void (*old_signal)(int);
|
|
static StackInfo g_stack_info;
|
|
static Wasm::DebuggerBytecodeInterpreter g_interpreter(g_stack_info);
|
|
|
|
struct ParsedValue {
|
|
Wasm::Value value;
|
|
Wasm::ValueType type;
|
|
};
|
|
|
|
static void sigint_handler(int)
|
|
{
|
|
if (!g_continue) {
|
|
signal(SIGINT, old_signal);
|
|
kill(getpid(), SIGINT);
|
|
}
|
|
g_continue = false;
|
|
}
|
|
|
|
static Optional<u128> convert_to_uint(StringView string)
|
|
{
|
|
if (string.is_empty())
|
|
return {};
|
|
|
|
u128 value = 0;
|
|
auto const characters = string.characters_without_null_termination();
|
|
|
|
for (size_t i = 0; i < string.length(); i++) {
|
|
if (characters[i] < '0' || characters[i] > '9')
|
|
return {};
|
|
|
|
value *= 10;
|
|
value += u128 { static_cast<u64>(characters[i] - '0'), 0 };
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static Optional<u128> convert_to_uint_from_hex(StringView string)
|
|
{
|
|
if (string.is_empty())
|
|
return {};
|
|
|
|
u128 value = 0;
|
|
auto const count = string.length();
|
|
auto const upper_bound = NumericLimits<u128>::max();
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
char digit = string[i];
|
|
if (value > (upper_bound >> 4))
|
|
return {};
|
|
|
|
auto digit_val = decode_hex_digit(digit);
|
|
if (digit_val == 255)
|
|
return {};
|
|
|
|
value = (value << 4) + digit_val;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static ErrorOr<ParsedValue> parse_value(StringView spec)
|
|
{
|
|
constexpr auto is_sep = [](char c) { return is_ascii_space(c) || c == ':'; };
|
|
// Scalar: 'T.const[:\s]v' (i32.const 42)
|
|
auto parse_scalar = []<typename T>(StringView text) -> ErrorOr<Wasm::Value> {
|
|
if constexpr (IsFloatingPoint<T>) {
|
|
if (text.trim_whitespace().equals_ignoring_ascii_case("nan"sv)) {
|
|
if constexpr (IsSame<T, float>)
|
|
return Wasm::Value { nanf("") };
|
|
else
|
|
return Wasm::Value { nan("") };
|
|
}
|
|
if (text.trim_whitespace().equals_ignoring_ascii_case("inf"sv)) {
|
|
if constexpr (IsSame<T, float>)
|
|
return Wasm::Value { HUGE_VALF };
|
|
else
|
|
return Wasm::Value { HUGE_VAL };
|
|
}
|
|
}
|
|
if (auto v = text.to_number<T>(); v.has_value())
|
|
return Wasm::Value { *v };
|
|
return Error::from_string_literal("Invalid scalar value");
|
|
};
|
|
// Vector: 'v128.const[:\s]v' (v128.const 0x01000000020000000300000004000000) or 'v(T.const[:\s]v, ...)' (v(i32.const 1, i32.const 2, i32.const 3, i32.const 4))
|
|
auto parse_u128 = [](StringView text) -> ErrorOr<Wasm::Value> {
|
|
u128 value;
|
|
if (text.starts_with("0x"sv)) {
|
|
if (auto v = convert_to_uint_from_hex(text); v.has_value())
|
|
value = *v;
|
|
else
|
|
return Error::from_string_literal("Invalid hex v128 value");
|
|
} else {
|
|
if (auto v = convert_to_uint(text); v.has_value())
|
|
value = *v;
|
|
else
|
|
return Error::from_string_literal("Invalid v128 value");
|
|
}
|
|
|
|
return Wasm::Value { value };
|
|
};
|
|
|
|
GenericLexer lexer(spec);
|
|
if (lexer.consume_specific("v128.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
// The rest of the string is the value
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_u128(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::V128)
|
|
};
|
|
}
|
|
|
|
if (lexer.consume_specific("i8.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<i8>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::I32)
|
|
};
|
|
}
|
|
if (lexer.consume_specific("i16.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<i16>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::I32)
|
|
};
|
|
}
|
|
if (lexer.consume_specific("i32.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<i32>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::I32)
|
|
};
|
|
}
|
|
if (lexer.consume_specific("i64.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<i64>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::I64)
|
|
};
|
|
}
|
|
if (lexer.consume_specific("f32.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<float>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::F32)
|
|
};
|
|
}
|
|
if (lexer.consume_specific("f64.const"sv)) {
|
|
lexer.ignore_while(is_sep);
|
|
auto text = lexer.consume_all();
|
|
return ParsedValue {
|
|
.value = TRY(parse_scalar.operator()<double>(text)),
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::F64)
|
|
};
|
|
}
|
|
|
|
if (lexer.consume_specific("v("sv)) {
|
|
Vector<ParsedValue> values;
|
|
for (;;) {
|
|
lexer.ignore_while(is_sep);
|
|
if (lexer.consume_specific(")"sv))
|
|
break;
|
|
if (lexer.is_eof()) {
|
|
warnln("Expected ')' to close vector");
|
|
break;
|
|
}
|
|
auto value = parse_value(lexer.consume_until(is_any_of(",)"sv)));
|
|
if (value.is_error())
|
|
return value.release_error();
|
|
lexer.consume_specific(',');
|
|
values.append(value.release_value());
|
|
}
|
|
|
|
if (values.is_empty())
|
|
return Error::from_string_literal("Empty vector");
|
|
|
|
auto element_type = values.first().type;
|
|
for (auto& value : values) {
|
|
if (value.type != element_type)
|
|
return Error::from_string_literal("Mixed types in vector");
|
|
}
|
|
|
|
unsigned total_size = 0;
|
|
unsigned width = 0;
|
|
u128 result = 0;
|
|
u128 last_value = 0;
|
|
for (auto& parsed : values) {
|
|
if (total_size >= 128)
|
|
return Error::from_string_literal("Vector too large");
|
|
|
|
switch (parsed.type.kind()) {
|
|
case Wasm::ValueType::F32:
|
|
case Wasm::ValueType::I32:
|
|
width = sizeof(u32);
|
|
break;
|
|
case Wasm::ValueType::F64:
|
|
case Wasm::ValueType::I64:
|
|
width = sizeof(u64);
|
|
break;
|
|
case Wasm::ValueType::V128:
|
|
case Wasm::ValueType::FunctionReference:
|
|
case Wasm::ValueType::ExternReference:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
last_value = parsed.value.value();
|
|
|
|
result |= last_value << total_size;
|
|
total_size += width * 8;
|
|
}
|
|
|
|
if (total_size < 128)
|
|
warnln("Vector value '{}' is only {} bytes wide, repeating last element", spec, total_size);
|
|
while (total_size < 128) {
|
|
// Repeat the last value until we fill the 128 bits
|
|
result |= last_value << total_size;
|
|
total_size += width * 8;
|
|
}
|
|
|
|
return ParsedValue {
|
|
.value = Wasm::Value { result },
|
|
.type = Wasm::ValueType(Wasm::ValueType::Kind::V128)
|
|
};
|
|
}
|
|
|
|
return Error::from_string_literal("Invalid value");
|
|
}
|
|
|
|
static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr, Wasm::Interpreter const& interpreter)
|
|
{
|
|
if (interpreter.did_trap()) {
|
|
g_continue = false;
|
|
warnln("Trapped when executing ip={}", ip);
|
|
g_printer->print(instr);
|
|
warnln("Trap reason: {}", interpreter.trap_reason());
|
|
const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, Wasm::Instruction const& instr)
|
|
{
|
|
static bool always_print_stack = false;
|
|
static bool always_print_instruction = false;
|
|
if (always_print_stack)
|
|
config.dump_stack();
|
|
if (always_print_instruction) {
|
|
g_stdout->write_until_depleted(ByteString::formatted("{:0>4} ", ip.value())).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(instr);
|
|
}
|
|
if (g_continue)
|
|
return true;
|
|
g_stdout->write_until_depleted(ByteString::formatted("{:0>4} ", ip.value())).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(instr);
|
|
ByteString last_command = "";
|
|
for (;;) {
|
|
auto result = g_line_editor->get_line("> ");
|
|
if (result.is_error()) {
|
|
return false;
|
|
}
|
|
auto str = result.release_value();
|
|
g_line_editor->add_to_history(str);
|
|
if (str.is_empty())
|
|
str = last_command;
|
|
else
|
|
last_command = str;
|
|
auto args = str.split_view(' ');
|
|
if (args.is_empty())
|
|
continue;
|
|
auto& cmd = args[0];
|
|
if (cmd.is_one_of("h", "help")) {
|
|
warnln("Wasm shell commands");
|
|
warnln("Toplevel:");
|
|
warnln("- [s]tep Run one instruction");
|
|
warnln("- next Alias for step");
|
|
warnln("- [c]ontinue Execute until a trap or the program exit point");
|
|
warnln("- [p]rint <args...> Print various things (see section on print)");
|
|
warnln("- call <fn> <args...> Call the function <fn> with the given arguments");
|
|
warnln("- set <args...> Set shell option (see section on settings)");
|
|
warnln("- unset <args...> Unset shell option (see section on settings)");
|
|
warnln("- [h]elp Print this help");
|
|
warnln();
|
|
warnln("Print:");
|
|
warnln("- print [s]tack Print the contents of the stack, including frames and labels");
|
|
warnln("- print [[m]em]ory <index> Print the contents of the memory identified by <index>");
|
|
warnln("- print [[i]nstr]uction Print the current instruction");
|
|
warnln("- print [[f]unc]tion <index> Print the function identified by <index>");
|
|
warnln();
|
|
warnln("Settings:");
|
|
warnln("- set print stack Make the shell print the stack on every instruction executed");
|
|
warnln("- set print [instr]uction Make the shell print the instruction that will be executed next");
|
|
warnln();
|
|
continue;
|
|
}
|
|
if (cmd.is_one_of("s", "step", "next")) {
|
|
return true;
|
|
}
|
|
if (cmd.is_one_of("p", "print")) {
|
|
if (args.size() < 2) {
|
|
warnln("Print what?");
|
|
continue;
|
|
}
|
|
auto& what = args[1];
|
|
if (what.is_one_of("s", "stack")) {
|
|
config.dump_stack();
|
|
continue;
|
|
}
|
|
if (what.is_one_of("m", "mem", "memory")) {
|
|
if (args.size() < 3) {
|
|
warnln("print what memory?");
|
|
continue;
|
|
}
|
|
auto value = args[2].to_number<u64>();
|
|
if (!value.has_value()) {
|
|
warnln("invalid memory index {}", args[2]);
|
|
continue;
|
|
}
|
|
auto mem = config.store().get(Wasm::MemoryAddress(value.value()));
|
|
if (!mem) {
|
|
warnln("invalid memory index {} (not found)", args[2]);
|
|
continue;
|
|
}
|
|
warnln("{:>32hex-dump}", mem->data().bytes());
|
|
continue;
|
|
}
|
|
if (what.is_one_of("i", "instr", "instruction")) {
|
|
g_printer->print(instr);
|
|
continue;
|
|
}
|
|
if (what.is_one_of("f", "func", "function")) {
|
|
if (args.size() < 3) {
|
|
warnln("print what function?");
|
|
continue;
|
|
}
|
|
auto value = args[2].to_number<u64>();
|
|
if (!value.has_value()) {
|
|
warnln("invalid function index {}", args[2]);
|
|
continue;
|
|
}
|
|
auto fn = config.store().get(Wasm::FunctionAddress(value.value()));
|
|
if (!fn) {
|
|
warnln("invalid function index {} (not found)", args[2]);
|
|
continue;
|
|
}
|
|
if (auto* fn_value = fn->get_pointer<Wasm::HostFunction>()) {
|
|
warnln("Host function at {:p}", &fn_value->function());
|
|
continue;
|
|
}
|
|
if (auto* fn_value = fn->get_pointer<Wasm::WasmFunction>()) {
|
|
g_printer->print(fn_value->code());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (cmd == "call"sv) {
|
|
if (args.size() < 2) {
|
|
warnln("call what?");
|
|
continue;
|
|
}
|
|
Optional<Wasm::FunctionAddress> address;
|
|
auto index = args[1].to_number<u64>();
|
|
if (index.has_value()) {
|
|
address = config.frame().module().functions()[index.value()];
|
|
} else {
|
|
auto& name = args[1];
|
|
for (auto& export_ : config.frame().module().exports()) {
|
|
if (export_.name() == name) {
|
|
if (auto addr = export_.value().get_pointer<Wasm::FunctionAddress>()) {
|
|
address = *addr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!address.has_value()) {
|
|
failed_to_find:;
|
|
warnln("Could not find a function {}", args[1]);
|
|
continue;
|
|
}
|
|
|
|
auto fn = config.store().get(*address);
|
|
if (!fn)
|
|
goto failed_to_find;
|
|
|
|
auto type = fn->visit([&](auto& value) { return value.type(); });
|
|
if (type.parameters().size() + 2 != args.size()) {
|
|
warnln("Expected {} arguments for call, but found only {}", type.parameters().size(), args.size() - 2);
|
|
continue;
|
|
}
|
|
Vector<ParsedValue> values_to_push;
|
|
Vector<Wasm::Value> values;
|
|
auto ok = true;
|
|
for (size_t index = 2; index < args.size(); ++index) {
|
|
auto r = parse_value(args[index]);
|
|
if (r.is_error()) {
|
|
warnln("Failed to parse argument {}: {}", args[index], r.error());
|
|
ok = false;
|
|
break;
|
|
}
|
|
values_to_push.append(r.release_value());
|
|
}
|
|
if (!ok)
|
|
continue;
|
|
for (auto& param : type.parameters()) {
|
|
auto v = values_to_push.take_last();
|
|
if (v.type != param) {
|
|
warnln("Type mismatch in argument: expected {}, but got {}", Wasm::ValueType::kind_name(param.kind()), Wasm::ValueType::kind_name(v.type.kind()));
|
|
ok = false;
|
|
break;
|
|
}
|
|
values.append(v.value);
|
|
}
|
|
if (!ok)
|
|
continue;
|
|
|
|
Wasm::Result result { Wasm::Trap {} };
|
|
{
|
|
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
|
|
result = config.call(g_interpreter, *address, move(values)).assert_wasm_result();
|
|
}
|
|
if (result.is_trap()) {
|
|
warnln("Execution trapped: {}", result.trap().reason);
|
|
} else {
|
|
if (!result.values().is_empty())
|
|
warnln("Returned:");
|
|
size_t index = 0;
|
|
for (auto& value : result.values()) {
|
|
g_stdout->write_until_depleted(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(value, type.results()[index]);
|
|
++index;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (cmd.is_one_of("set", "unset")) {
|
|
auto value = !cmd.starts_with('u');
|
|
if (args.size() < 3) {
|
|
warnln("(un)set what (to what)?");
|
|
continue;
|
|
}
|
|
if (args[1] == "print"sv) {
|
|
if (args[2] == "stack"sv)
|
|
always_print_stack = value;
|
|
else if (args[2].is_one_of("instr", "instruction"))
|
|
always_print_instruction = value;
|
|
else
|
|
warnln("Unknown print category '{}'", args[2]);
|
|
continue;
|
|
}
|
|
warnln("Unknown set category '{}'", args[1]);
|
|
continue;
|
|
}
|
|
if (cmd.is_one_of("c", "continue")) {
|
|
g_continue = true;
|
|
return true;
|
|
}
|
|
warnln("Command not understood: {}", cmd);
|
|
}
|
|
}
|
|
|
|
static RefPtr<Wasm::Module> parse(StringView filename)
|
|
{
|
|
auto result = Core::MappedFile::map(filename);
|
|
if (result.is_error()) {
|
|
warnln("Failed to open {}: {}", filename, result.error());
|
|
return {};
|
|
}
|
|
|
|
auto parse_result = Wasm::Module::parse(*result.value());
|
|
if (parse_result.is_error()) {
|
|
warnln("Something went wrong, either the file is invalid, or there's a bug with LibWasm!");
|
|
warnln("The parse error was {}", Wasm::parse_error_to_byte_string(parse_result.error()));
|
|
return {};
|
|
}
|
|
return parse_result.release_value();
|
|
}
|
|
|
|
static void print_link_error(Wasm::LinkError const& error)
|
|
{
|
|
for (auto const& missing : error.missing_imports)
|
|
warnln("Missing import '{}'", missing);
|
|
}
|
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
{
|
|
StringView filename;
|
|
bool print = false;
|
|
bool attempt_instantiate = false;
|
|
bool debug = false;
|
|
bool export_all_imports = false;
|
|
bool shell_mode = false;
|
|
bool wasi = false;
|
|
ByteString exported_function_to_execute;
|
|
Vector<ParsedValue> values_to_push;
|
|
Vector<ByteString> modules_to_link_in;
|
|
Vector<StringView> args_if_wasi;
|
|
Vector<StringView> wasi_preopened_mappings;
|
|
|
|
Core::ArgsParser parser;
|
|
parser.add_positional_argument(filename, "File name to parse", "file");
|
|
parser.add_option(debug, "Open a debugger", "debug", 'd');
|
|
parser.add_option(print, "Print the parsed module", "print", 'p');
|
|
parser.add_option(attempt_instantiate, "Attempt to instantiate the module", "instantiate", 'i');
|
|
parser.add_option(exported_function_to_execute, "Attempt to execute the named exported function from the module (implies -i)", "execute", 'e', "name");
|
|
parser.add_option(export_all_imports, "Export noop functions corresponding to imports", "export-noop");
|
|
parser.add_option(shell_mode, "Launch a REPL in the module's context (implies -i)", "shell", 's');
|
|
parser.add_option(wasi, "Enable WASI", "wasi", 'w');
|
|
parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Directory mappings to expose via WASI",
|
|
.long_name = "wasi-map-dir",
|
|
.short_name = 0,
|
|
.value_name = "path[:path]",
|
|
.accept_value = [&](StringView str) {
|
|
if (!str.is_empty()) {
|
|
wasi_preopened_mappings.append(str);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Extra modules to link with, use to resolve imports",
|
|
.long_name = "link",
|
|
.short_name = 'l',
|
|
.value_name = "file",
|
|
.accept_value = [&](StringView str) {
|
|
if (!str.is_empty()) {
|
|
modules_to_link_in.append(str);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Supply arguments to the function (default=0) (T.const:v or v(T.const:v, ...))",
|
|
.long_name = "arg",
|
|
.short_name = 0,
|
|
.value_name = "value",
|
|
.accept_value = [&](StringView str) -> bool {
|
|
auto result = parse_value(str);
|
|
if (result.is_error()) {
|
|
warnln("Failed to parse value: {}", result.error());
|
|
return false;
|
|
}
|
|
values_to_push.append(result.release_value());
|
|
return true;
|
|
},
|
|
});
|
|
parser.add_positional_argument(args_if_wasi, "Arguments to pass to the WASI module", "args", Core::ArgsParser::Required::No);
|
|
parser.parse(arguments);
|
|
|
|
if (shell_mode) {
|
|
debug = true;
|
|
attempt_instantiate = true;
|
|
}
|
|
|
|
if (!shell_mode && debug && exported_function_to_execute.is_empty()) {
|
|
warnln("Debug what? (pass -e fn)");
|
|
return 1;
|
|
}
|
|
|
|
if (debug || shell_mode) {
|
|
old_signal = signal(SIGINT, sigint_handler);
|
|
}
|
|
|
|
if (!exported_function_to_execute.is_empty())
|
|
attempt_instantiate = true;
|
|
|
|
auto parse_result = parse(filename);
|
|
if (parse_result.is_null())
|
|
return 1;
|
|
|
|
g_stdout = TRY(Core::File::standard_output());
|
|
g_printer = TRY(try_make<Wasm::Printer>(*g_stdout));
|
|
|
|
if (print && !attempt_instantiate) {
|
|
Wasm::Printer printer(*g_stdout);
|
|
printer.print(*parse_result);
|
|
}
|
|
|
|
if (attempt_instantiate) {
|
|
Wasm::AbstractMachine machine;
|
|
Optional<Wasm::Wasi::Implementation> wasi_impl;
|
|
|
|
if (wasi) {
|
|
wasi_impl.emplace(Wasm::Wasi::Implementation::Details {
|
|
.provide_arguments = [&] {
|
|
Vector<String> strings;
|
|
for (auto& string : args_if_wasi)
|
|
strings.append(String::from_utf8(string).release_value_but_fixme_should_propagate_errors());
|
|
return strings; },
|
|
.provide_environment = {},
|
|
.provide_preopened_directories = [&] {
|
|
Vector<Wasm::Wasi::Implementation::MappedPath> paths;
|
|
for (auto& string : wasi_preopened_mappings) {
|
|
auto split_index = string.find(':');
|
|
if (split_index.has_value()) {
|
|
LexicalPath host_path { FileSystem::real_path(string.substring_view(0, *split_index)).release_value_but_fixme_should_propagate_errors() };
|
|
LexicalPath mapped_path { string.substring_view(*split_index + 1) };
|
|
paths.append({move(host_path), move(mapped_path)});
|
|
} else {
|
|
LexicalPath host_path { FileSystem::real_path(string).release_value_but_fixme_should_propagate_errors() };
|
|
LexicalPath mapped_path { string };
|
|
paths.append({move(host_path), move(mapped_path)});
|
|
}
|
|
}
|
|
return paths; },
|
|
});
|
|
}
|
|
|
|
Core::EventLoop main_loop;
|
|
if (debug) {
|
|
g_line_editor = Line::Editor::construct();
|
|
g_interpreter.pre_interpret_hook = pre_interpret_hook;
|
|
g_interpreter.post_interpret_hook = post_interpret_hook;
|
|
}
|
|
|
|
// First, resolve the linked modules
|
|
Vector<NonnullOwnPtr<Wasm::ModuleInstance>> linked_instances;
|
|
Vector<NonnullRefPtr<Wasm::Module>> linked_modules;
|
|
for (auto& name : modules_to_link_in) {
|
|
auto parse_result = parse(name);
|
|
if (parse_result.is_null()) {
|
|
warnln("Failed to parse linked module '{}'", name);
|
|
return 1;
|
|
}
|
|
linked_modules.append(parse_result.release_nonnull());
|
|
Wasm::Linker linker { linked_modules.last() };
|
|
for (auto& instance : linked_instances)
|
|
linker.link(*instance);
|
|
auto link_result = linker.finish();
|
|
if (link_result.is_error()) {
|
|
warnln("Linking imported module '{}' failed", name);
|
|
print_link_error(link_result.error());
|
|
return 1;
|
|
}
|
|
auto instantiation_result = machine.instantiate(linked_modules.last(), link_result.release_value());
|
|
if (instantiation_result.is_error()) {
|
|
warnln("Instantiation of imported module '{}' failed: {}", name, instantiation_result.error().error);
|
|
return 1;
|
|
}
|
|
linked_instances.append(instantiation_result.release_value());
|
|
}
|
|
|
|
Wasm::Linker linker { *parse_result };
|
|
for (auto& instance : linked_instances)
|
|
linker.link(*instance);
|
|
|
|
if (wasi) {
|
|
HashMap<Wasm::Linker::Name, Wasm::ExternValue> wasi_exports;
|
|
for (auto& entry : linker.unresolved_imports()) {
|
|
if (entry.module != "wasi_snapshot_preview1"sv)
|
|
continue;
|
|
auto function = wasi_impl->function_by_name(entry.name);
|
|
if (function.is_error()) {
|
|
dbgln("wasi function {} not implemented :(", entry.name);
|
|
continue;
|
|
}
|
|
auto address = machine.store().allocate(function.release_value());
|
|
wasi_exports.set(entry, *address);
|
|
}
|
|
|
|
linker.link(wasi_exports);
|
|
}
|
|
|
|
if (export_all_imports) {
|
|
HashMap<Wasm::Linker::Name, Wasm::ExternValue> exports;
|
|
for (auto& entry : linker.unresolved_imports()) {
|
|
if (!entry.type.has<Wasm::TypeIndex>())
|
|
continue;
|
|
auto type = parse_result->type_section().types()[entry.type.get<Wasm::TypeIndex>().value()];
|
|
auto address = machine.store().allocate(Wasm::HostFunction(
|
|
[name = entry.name, type = type](auto&, auto& arguments) -> Wasm::Result {
|
|
StringBuilder argument_builder;
|
|
bool first = true;
|
|
size_t index = 0;
|
|
for (auto& argument : arguments) {
|
|
AllocatingMemoryStream stream;
|
|
auto value_type = type.parameters()[index];
|
|
Wasm::Printer { stream }.print(argument, value_type);
|
|
if (first)
|
|
first = false;
|
|
else
|
|
argument_builder.append(", "sv);
|
|
auto buffer = ByteBuffer::create_uninitialized(stream.used_buffer_size()).release_value_but_fixme_should_propagate_errors();
|
|
stream.read_until_filled(buffer).release_value_but_fixme_should_propagate_errors();
|
|
argument_builder.append(StringView(buffer).trim_whitespace());
|
|
++index;
|
|
}
|
|
dbgln("[wasm runtime] Stub function {} was called with the following arguments: {}", name, argument_builder.to_byte_string());
|
|
Vector<Wasm::Value> result;
|
|
result.ensure_capacity(type.results().size());
|
|
for (auto expect_result : type.results())
|
|
result.append(Wasm::Value(expect_result));
|
|
return Wasm::Result { move(result) };
|
|
},
|
|
type,
|
|
entry.name));
|
|
exports.set(entry, *address);
|
|
}
|
|
|
|
linker.link(exports);
|
|
}
|
|
|
|
auto link_result = linker.finish();
|
|
if (link_result.is_error()) {
|
|
warnln("Linking main module failed");
|
|
print_link_error(link_result.error());
|
|
return 1;
|
|
}
|
|
auto result = machine.instantiate(*parse_result, link_result.release_value());
|
|
if (result.is_error()) {
|
|
warnln("Module instantiation failed: {}", result.error().error);
|
|
return 1;
|
|
}
|
|
auto module_instance = result.release_value();
|
|
|
|
auto launch_repl = [&] {
|
|
Wasm::Configuration config { machine.store() };
|
|
Wasm::Expression expression { {} };
|
|
config.set_frame(Wasm::Frame {
|
|
*module_instance,
|
|
Vector<Wasm::Value> {},
|
|
expression,
|
|
0,
|
|
});
|
|
Wasm::Instruction instr { Wasm::Instructions::nop };
|
|
Wasm::InstructionPointer ip { 0 };
|
|
g_continue = false;
|
|
pre_interpret_hook(config, ip, instr);
|
|
};
|
|
|
|
auto print_func = [&](auto const& address) {
|
|
Wasm::FunctionInstance* fn = machine.store().get(address);
|
|
g_stdout->write_until_depleted(ByteString::formatted("- Function with address {}, ptr = {}\n", address.value(), fn)).release_value_but_fixme_should_propagate_errors();
|
|
if (fn) {
|
|
g_stdout->write_until_depleted(ByteString::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>())).release_value_but_fixme_should_propagate_errors();
|
|
fn->visit(
|
|
[&](Wasm::WasmFunction const& func) {
|
|
Wasm::Printer printer { *g_stdout, 3 };
|
|
g_stdout->write_until_depleted(" type:\n"sv).release_value_but_fixme_should_propagate_errors();
|
|
printer.print(func.type());
|
|
g_stdout->write_until_depleted(" code:\n"sv).release_value_but_fixme_should_propagate_errors();
|
|
printer.print(func.code());
|
|
},
|
|
[](Wasm::HostFunction const&) {});
|
|
}
|
|
};
|
|
if (print) {
|
|
// Now, let's dump the functions!
|
|
for (auto& address : module_instance->functions()) {
|
|
print_func(address);
|
|
}
|
|
}
|
|
|
|
if (shell_mode) {
|
|
launch_repl();
|
|
return 0;
|
|
}
|
|
|
|
if (!exported_function_to_execute.is_empty()) {
|
|
Optional<Wasm::FunctionAddress> run_address;
|
|
Vector<Wasm::Value> values;
|
|
for (auto& entry : module_instance->exports()) {
|
|
if (entry.name() == exported_function_to_execute) {
|
|
if (auto addr = entry.value().get_pointer<Wasm::FunctionAddress>())
|
|
run_address = *addr;
|
|
}
|
|
}
|
|
if (!run_address.has_value()) {
|
|
warnln("No such exported function, sorry :(");
|
|
return 1;
|
|
}
|
|
|
|
auto instance = machine.store().get(*run_address);
|
|
VERIFY(instance);
|
|
|
|
if (instance->has<Wasm::HostFunction>()) {
|
|
warnln("Exported function is a host function, cannot run that yet");
|
|
return 1;
|
|
}
|
|
|
|
for (auto& param : instance->get<Wasm::WasmFunction>().type().parameters()) {
|
|
if (values_to_push.is_empty()) {
|
|
values.append(Wasm::Value(param));
|
|
} else if (param == values_to_push.last().type) {
|
|
values.append(values_to_push.take_last().value);
|
|
} else {
|
|
warnln("Type mismatch in argument: expected {}, but got {}", Wasm::ValueType::kind_name(param.kind()), Wasm::ValueType::kind_name(values_to_push.last().type.kind()));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (print) {
|
|
outln("Executing ");
|
|
print_func(*run_address);
|
|
outln();
|
|
}
|
|
|
|
auto result = machine.invoke(g_interpreter, run_address.value(), move(values)).assert_wasm_result();
|
|
|
|
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);
|
|
} else {
|
|
if (!result.values().is_empty())
|
|
warnln("Returned:");
|
|
auto result_type = instance->get<Wasm::WasmFunction>().type().results();
|
|
size_t index = 0;
|
|
for (auto& value : result.values()) {
|
|
g_stdout->write_until_depleted(" -> "sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
|
g_printer->print(value, result_type[index]);
|
|
++index;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|