ladybird/Userland/Utilities/wasm.cpp

544 lines
20 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/FileStream.h>
#include <LibLine/Editor.h>
#include <LibWasm/AbstractMachine/AbstractMachine.h>
#include <LibWasm/AbstractMachine/Interpreter.h>
2021-04-27 17:43:01 +00:00
#include <LibWasm/Printer/Printer.h>
#include <LibWasm/Types.h>
#include <signal.h>
#include <unistd.h>
RefPtr<Line::Editor> g_line_editor;
static auto g_stdout = Core::OutputFileStream::standard_error();
static Wasm::Printer g_printer { g_stdout };
static bool g_continue { false };
static void (*old_signal)(int);
static Wasm::DebuggerBytecodeInterpreter g_interpreter;
static void print_buffer(ReadonlyBytes buffer, int split)
{
for (size_t i = 0; i < buffer.size(); ++i) {
if (split > 0) {
if (i % split == 0 && i) {
out(" ");
for (size_t j = i - split; j < i; ++j) {
auto ch = buffer[j];
out("{:c}", ch >= 32 && ch <= 127 ? ch : '.'); // silly hack
}
outln();
}
}
out("{:02x} ", buffer[i]);
}
puts("");
}
static void sigint_handler(int)
{
if (!g_continue) {
signal(SIGINT, old_signal);
kill(getpid(), SIGINT);
}
g_continue = false;
}
static bool post_interpret_hook(Wasm::Configuration&, Wasm::InstructionPointer& ip, const Wasm::Instruction& instr, const Wasm::Interpreter& interpreter)
{
if (interpreter.did_trap()) {
g_continue = false;
const_cast<Wasm::Interpreter&>(interpreter).clear_trap();
warnln("Trapped when executing ip={}", ip);
g_printer.print(instr);
warnln("");
}
return true;
}
static bool pre_interpret_hook(Wasm::Configuration& config, Wasm::InstructionPointer& ip, const Wasm::Instruction& 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(String::formatted("{:0>4} ", ip.value()).bytes());
g_printer.print(instr);
}
if (g_continue)
return true;
g_stdout.write(String::formatted("{:0>4} ", ip.value()).bytes());
g_printer.print(instr);
String 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("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_uint<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;
}
print_buffer(mem->data(), 32);
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_uint<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_uint<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<u64> values_to_push;
Vector<Wasm::Value> values;
for (size_t index = 2; index < args.size(); ++index)
values_to_push.append(args[index].to_uint().value_or(0));
for (auto& param : type.parameters())
values.append(Wasm::Value { param, values_to_push.take_last() });
Wasm::Result result { Wasm::Trap {} };
{
Wasm::BytecodeInterpreter::CallFrameHandle handle { g_interpreter, config };
result = config.call(g_interpreter, *address, move(values));
}
if (result.is_trap())
warnln("Execution trapped!");
if (!result.values().is_empty())
warnln("Returned:");
for (auto& value : result.values()) {
g_stdout.write(" -> "sv.bytes());
g_printer.print(value);
}
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 Optional<Wasm::Module> parse(const StringView& filename)
{
auto result = Core::File::open(filename, Core::OpenMode::ReadOnly);
if (result.is_error()) {
warnln("Failed to open {}: {}", filename, result.error());
return {};
}
auto stream = Core::InputFileStream(result.release_value());
auto parse_result = Wasm::Module::parse(stream);
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_string(parse_result.error()));
return {};
}
return parse_result.release_value();
}
static void print_link_error(const Wasm::LinkError& error)
{
for (const auto& missing : error.missing_imports)
warnln("Missing import '{}'", missing);
}
int main(int argc, char* argv[])
{
const char* filename = nullptr;
2021-04-27 17:43:01 +00:00
bool print = false;
bool attempt_instantiate = false;
bool debug = false;
String exported_function_to_execute;
Vector<u64> values_to_push;
Vector<String> modules_to_link_in;
HashMap<Wasm::Linker::Name, Wasm::HostFunction> exported_host_functions;
Core::ArgsParser parser;
parser.add_positional_argument(filename, "File name to parse", "file");
parser.add_option(debug, "Open a debugger", "debug", 'd');
2021-04-27 17:43:01 +00:00
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(Core::ArgsParser::Option {
.requires_argument = true,
.help_string = "Extra modules to link with, use to resolve imports",
.long_name = "link",
.short_name = 'l',
.value_name = "file",
.accept_value = [&](const char* str) {
if (auto v = StringView { str }; !v.is_empty()) {
modules_to_link_in.append(v);
return true;
}
return false;
},
});
parser.add_option(Core::ArgsParser::Option {
.requires_argument = true,
.help_string = "Supply arguments to the function (default=0) (expects u64, casts to required type)",
.long_name = "arg",
.short_name = 0,
.value_name = "u64",
.accept_value = [&](const char* str) -> bool {
if (auto v = StringView { str }.to_uint<u64>(); v.has_value()) {
values_to_push.append(v.value());
return true;
}
return false;
},
});
parser.add_option(Core::ArgsParser::Option {
.requires_argument = true,
.help_string = "Export a noop function that returns default-initialised values (module!name::t,t,t...:t,t,t...)",
.long_name = "export-noop",
.short_name = 0,
.value_name = "module!name:atypes:rtypes",
.accept_value = [&](const char* str) -> bool {
GenericLexer lexer { str };
auto name = lexer.consume_until("::");
auto parts = name.split_view('!');
if (parts.size() != 2) {
warnln("Expected a two-part name module!name, got '{}'", name);
return false;
}
auto module_name = parts[0];
auto export_name = parts[1];
auto arg_types = lexer.consume_until(":").split_view(',');
auto ret_types = lexer.consume_all().split_view(',');
Vector<Wasm::ValueType> argument_types, return_types;
for (auto& name : arg_types) {
if (name == "i32") {
argument_types.empend(Wasm::ValueType::Kind::I32);
} else if (name == "i64") {
argument_types.empend(Wasm::ValueType::Kind::I64);
} else if (name == "f32") {
argument_types.empend(Wasm::ValueType::Kind::F32);
} else if (name == "f64") {
argument_types.empend(Wasm::ValueType::Kind::F64);
} else {
warnln("Unknown type '{}'", name);
return false;
}
}
for (auto& name : ret_types) {
if (name == "i32") {
return_types.empend(Wasm::ValueType::Kind::I32);
} else if (name == "i64") {
return_types.empend(Wasm::ValueType::Kind::I64);
} else if (name == "f32") {
return_types.empend(Wasm::ValueType::Kind::F32);
} else if (name == "f64") {
return_types.empend(Wasm::ValueType::Kind::F64);
} else {
warnln("Unknown type '{}'", name);
return false;
}
}
Wasm::FunctionType function_type { argument_types, return_types };
exported_host_functions.set(
Wasm::Linker::Name { module_name, export_name, function_type },
Wasm::HostFunction {
[&](auto&, auto&) -> Wasm::Result {
Vector<Wasm::Value> values;
values.ensure_capacity(return_types.size());
for (auto& type : return_types)
values.empend(type, 0ull);
return Wasm::Result { move(values) };
},
function_type });
return true;
},
});
parser.parse(argc, argv);
if (debug && exported_function_to_execute.is_empty()) {
warnln("Debug what? (pass -e fn)");
return 1;
}
if (debug) {
old_signal = signal(SIGINT, sigint_handler);
}
if (!exported_function_to_execute.is_empty())
attempt_instantiate = true;
auto parse_result = parse(filename);
if (print && !attempt_instantiate) {
2021-04-27 17:43:01 +00:00
auto out_stream = Core::OutputFileStream::standard_output();
Wasm::Printer printer(out_stream);
printer.print(parse_result.value());
}
if (attempt_instantiate) {
Wasm::AbstractMachine machine;
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
NonnullOwnPtrVector<Wasm::ModuleInstance> linked_instances;
Vector<Wasm::Module> linked_modules;
for (auto& name : modules_to_link_in) {
auto parse_result = parse(name);
if (!parse_result.has_value()) {
warnln("Failed to parse linked module '{}'", name);
return 1;
}
linked_modules.append(parse_result.release_value());
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());
}
HashMap<Wasm::Linker::Name, Wasm::ExternValue> exports;
for (auto& entry : exported_host_functions) {
auto address = machine.store().allocate(move(entry.value));
if (!address.has_value()) {
warnln("Could not export {}/{}", entry.key.module, entry.key.name);
return 1;
}
exports.set(entry.key, *address);
}
Wasm::Linker linker { parse_result.value() };
for (auto& instance : linked_instances)
linker.link(instance);
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.value(), link_result.release_value());
if (result.is_error()) {
warnln("Module instantiation failed: {}", result.error().error);
return 1;
}
auto module_instance = result.release_value();
auto stream = Core::OutputFileStream::standard_output();
auto print_func = [&](const auto& address) {
Wasm::FunctionInstance* fn = machine.store().get(address);
stream.write(String::formatted("- Function with address {}, ptr = {}\n", address.value(), fn).bytes());
if (fn) {
stream.write(String::formatted(" wasm function? {}\n", fn->has<Wasm::WasmFunction>()).bytes());
fn->visit(
[&](const Wasm::WasmFunction& func) {
Wasm::Printer printer { stream, 3 };
stream.write(" type:\n"sv.bytes());
printer.print(func.type());
stream.write(" code:\n"sv.bytes());
printer.print(func.code());
},
[](const Wasm::HostFunction&) {});
}
};
if (print) {
// Now, let's dump the functions!
for (auto& address : module_instance->functions()) {
print_func(address);
}
}
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, 0ull });
else
values.append(Wasm::Value { param, values_to_push.take_last() });
}
if (print) {
outln("Executing ");
print_func(*run_address);
outln();
}
auto result = machine.invoke(g_interpreter, run_address.value(), move(values));
if (debug) {
Wasm::Configuration config { machine.store() };
config.set_frame(Wasm::Frame {
*module_instance,
Vector<Wasm::Value> {},
instance->get<Wasm::WasmFunction>().code().body(),
1,
});
const Wasm::Instruction instr { Wasm::Instructions::nop };
Wasm::InstructionPointer ip { 0 };
g_continue = false;
pre_interpret_hook(config, ip, instr);
}
if (result.is_trap())
warnln("Execution trapped!");
if (!result.values().is_empty())
warnln("Returned:");
for (auto& value : result.values()) {
Wasm::Printer printer { stream };
g_stdout.write(" -> "sv.bytes());
g_printer.print(value);
}
}
}
return 0;
}