/* * Copyright (c) 2021, Ali Mohammad Pur * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(AK_OS_WINDOWS) # include #endif #include #include #include #include #include #if !defined(AK_OS_WINDOWS) # include #endif #include #include #if !defined(AK_OS_WINDOWS) # include #endif static OwnPtr g_stdout {}; static OwnPtr g_printer {}; static StackInfo g_stack_info; static Wasm::BytecodeInterpreter g_interpreter(g_stack_info); struct ParsedValue { Wasm::Value value; Wasm::ValueType type; }; static Optional 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(characters[i] - '0'), 0 }; } return value; } static Optional 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::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 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 = [](StringView text) -> ErrorOr { if constexpr (IsFloatingPoint) { if (text.trim_whitespace().equals_ignoring_ascii_case("nan"sv)) { if constexpr (IsSame) return Wasm::Value { nanf("") }; else return Wasm::Value { nan("") }; } if (text.trim_whitespace().equals_ignoring_ascii_case("inf"sv)) { if constexpr (IsSame) return Wasm::Value { HUGE_VALF }; else return Wasm::Value { HUGE_VAL }; } } if (auto v = text.to_number(); 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 { 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()(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()(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()(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()(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()(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()(text)), .type = Wasm::ValueType(Wasm::ValueType::Kind::F64) }; } if (lexer.consume_specific("v("sv)) { Vector 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::I8: case Wasm::ValueType::I16: case Wasm::ValueType::FunctionReference: case Wasm::ValueType::NoFunctionReference: case Wasm::ValueType::ExternReference: case Wasm::ValueType::NoExternReference: case Wasm::ValueType::ExceptionReference: case Wasm::ValueType::NoExceptionReference: case Wasm::ValueType::AnyReference: case Wasm::ValueType::EqReference: case Wasm::ValueType::I31Reference: case Wasm::ValueType::StructReference: case Wasm::ValueType::ArrayReference: case Wasm::ValueType::NoneReference: case Wasm::ValueType::TypeUseReference: 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 RefPtr 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); } template static ErrorOr trap_for_js_exception(JS::VM& vm, JS::ThrowCompletionOr const& result) { if (!result.is_error()) return result.value(); auto const& completion = result.error(); auto& exception = completion.value(); warnln("JS exception: {}", MUST(exception.to_utf16_string(vm))); return Wasm::Trap { ByteString("JS exception") }; } ErrorOr ladybird_main(Main::Arguments arguments) { StringView filename; bool print = false; bool print_compiled = false; bool dump_native = false; bool attempt_instantiate = false; bool export_all_imports = false; [[maybe_unused]] bool wasi = false; Optional specific_function_address; ByteString exported_function_to_execute; Vector values_to_push; Vector modules_to_link_in; Vector args_if_wasi; Vector wasi_preopened_mappings; HashMap js_exports; Wasm::AbstractMachine machine; auto vm = JS::VM::create(); auto root_execution_context = JS::create_simple_execution_context(*vm); auto& realm = *root_execution_context->realm; Core::ArgsParser parser; parser.add_positional_argument(filename, "File name to parse", "file"); parser.add_option(print, "Print the parsed module", "print", 'p'); parser.add_option(print_compiled, "Print the compiled module", "print-compiled"); parser.add_option(dump_native, "Disassemble Cranelift-compiled native code for each function", "dump-native"); parser.add_option(specific_function_address, "Optional compiled function address to print", "print-function", 'f', "address"); 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"); #if !defined(AK_OS_WINDOWS) parser.add_option(wasi, "Enable WASI", "wasi", 'w'); #endif parser.add_option(Core::ArgsParser::Option { .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, .help_string = "Export js `function(arg...) { source }` returning T as [module].[function]", .long_name = "export-js", .short_name = 0, .value_name = "module.function(arg:T...):T=source", .accept_value = [&](StringView str) { GenericLexer lexer(str); // [module] <.> [function] <(> {[name] <:> [type]} <)> (<:> [type])? <=> [text] auto module = lexer.consume_until('.'); if (!lexer.consume_specific('.')) { warnln("Invalid JS export module in '{}'", str); return false; } auto fn_name = lexer.consume_until(is_any_of("(=:"sv)); struct Arg { Wasm::ValueType type; StringView name; }; Vector formal_params; if (lexer.consume_specific('(')) { while (!lexer.consume_specific(')')) { auto name = lexer.consume_until(is_any_of(",:)"sv)); if (name.is_empty()) { warnln("Invalid JS export argument name in '{}'", str); return false; } auto type_kind = Wasm::ValueType::I32; if (lexer.consume_specific(':')) { if (lexer.consume_specific("i32"sv)) { type_kind = Wasm::ValueType::I32; } else if (lexer.consume_specific("i64"sv)) { type_kind = Wasm::ValueType::I64; } else if (lexer.consume_specific("f32"sv)) { type_kind = Wasm::ValueType::F32; } else if (lexer.consume_specific("f64"sv)) { type_kind = Wasm::ValueType::F64; } else if (lexer.consume_specific("v128"sv)) { type_kind = Wasm::ValueType::V128; } else { warnln("Invalid JS export argument type in '{}'", str); return false; } } formal_params.append(Arg { Wasm::ValueType(type_kind), name }); lexer.consume_specific(','); } } Vector returns; if (lexer.consume_specific(':')) { if (lexer.consume_specific("i32"sv)) { returns.append(Wasm::ValueType::I32); } else if (lexer.consume_specific("i64"sv)) { returns.append(Wasm::ValueType::I64); } else if (lexer.consume_specific("f32"sv)) { returns.append(Wasm::ValueType::F32); } else if (lexer.consume_specific("f64"sv)) { returns.append(Wasm::ValueType::F64); } else if (lexer.consume_specific("v128"sv)) { returns.append(Wasm::ValueType::V128); } else { warnln("Invalid JS export return type in '{}'", str); return false; } } if (!lexer.consume_specific('=') || lexer.is_eof()) { warnln("Invalid JS export source in '{}'", str); return false; } auto source_text = lexer.consume_all().trim_whitespace(); Utf16StringBuilder builder; builder.append_ascii("("sv); auto first = true; for (auto& arg : formal_params) { if (!first) builder.append_ascii(", "sv); first = false; auto argument_name = Utf16String::from_utf8(arg.name); builder.append(argument_name.utf16_view()); } builder.append_ascii(") => "sv); auto source_text_utf16 = Utf16String::from_utf8(source_text); builder.append(source_text_utf16.utf16_view()); auto js_function = builder.to_string(); auto name = ByteString::formatted("{}.{}", module, fn_name); auto script = JS::Script::parse(js_function, realm, name); if (script.is_error()) { warnln("Failed to parse JS export source '{}':", js_function); return false; } auto js_script = script.release_value(); auto maybe_function = vm->run(*js_script); if (maybe_function.is_error()) { warnln("Failed to run JS export source '{}'", js_function); return false; } auto function_val = maybe_function.release_value(); if (!function_val.is_function()) { warnln("JS export source '{}' did not parse as a function", js_function); return false; } auto& function = function_val.as_function(); Vector results; Vector params; for (auto& type : returns) results.append(Wasm::ValueType(type)); for (auto& arg : formal_params) params.append(Wasm::ValueType(arg.type)); Wasm::FunctionType function_type = { move(params), move(results) }; auto host_function = Wasm::HostFunction { [&vm, &function, formal_params, returns, name](Wasm::Configuration&, Span args) mutable -> Wasm::Result { Vector js_args; js_args.ensure_capacity(args.size()); for (size_t i = 0; i < formal_params.size(); ++i) { auto type = formal_params[i].type; if (i >= args.size()) { warnln("Not enough arguments provided to JS export function '{}'", name); return Wasm::Trap { ByteString("Not enough arguments") }; } auto& arg = args[i]; switch (type.kind()) { case Wasm::ValueType::I32: js_args.append(JS::Value(arg.to())); break; case Wasm::ValueType::I64: js_args.append(JS::Value(arg.to())); break; case Wasm::ValueType::F32: js_args.append(JS::Value(arg.to())); break; case Wasm::ValueType::F64: js_args.append(JS::Value(arg.to())); break; case Wasm::ValueType::V128: { auto value = arg.to(); ReadonlyBytes data { bit_cast(&value), sizeof(u128) }; js_args.append(vm->heap().allocate(Crypto::SignedBigInteger { Crypto::UnsignedBigInteger { data } })); break; } default: warnln("Unsupported argument type '{}' for JS export function '{}'", type.kind_name(), name); return Wasm::Trap { ByteString("Unsupported argument type") }; } } auto result = TRY(trap_for_js_exception(vm, JS::call(vm, function, JS::js_null(), js_args.span()))); if (returns.is_empty()) return Wasm::Result { Vector {} }; if (returns.size() != 1) return Wasm::Trap { ByteString("NYI") }; switch (returns[0]) { case Wasm::ValueType::I32: return Wasm::Result { Vector { Wasm::Value { TRY(trap_for_js_exception(*vm, result.to_u32(vm))) } } }; case Wasm::ValueType::I64: return Wasm::Result { Vector { Wasm::Value { TRY(trap_for_js_exception(*vm, result.to_bigint_uint64(vm))) } } }; case Wasm::ValueType::F32: return Wasm::Result { Vector { Wasm::Value { static_cast(TRY(trap_for_js_exception(*vm, result.to_double(vm)))) } } }; case Wasm::ValueType::F64: return Wasm::Result { Vector { Wasm::Value { TRY(trap_for_js_exception(*vm, result.to_double(vm))) } } }; case Wasm::ValueType::V128: { auto value = TRY(trap_for_js_exception(*vm, result.to_bigint(vm))); u128 out {}; Bytes data { bit_cast(&out), sizeof(u128) }; if (value->big_integer().unsigned_value().export_data(data).size() != data.size()) { dbgln("JS export function '{}' returned a v128 value that is not 128 bits wide", name); return Wasm::Trap { ByteString("Invalid v128 value") }; } return Wasm::Result { Vector { Wasm::Value { out } } }; } default: warnln("Unsupported return type for JS export function '{}'", name); return Wasm::Trap { ByteString("Unsupported return type") }; } }, function_type, name, }; auto host_function_instance = machine.store().allocate(move(host_function)); if (!host_function_instance.has_value()) { warnln("Failed to allocate host function instance for '{}'", name); return false; } js_exports.set({ .module = module, .name = fn_name, .type = function_type }, *host_function_instance); return true; }, }); 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 (!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(*g_stdout)); if (print && !attempt_instantiate) { Wasm::Printer printer(*g_stdout); printer.print(*parse_result); } if (attempt_instantiate || print_compiled || dump_native) { #if !defined(AK_OS_WINDOWS) Optional wasi_impl; if (wasi) { wasi_impl.emplace(Wasm::Wasi::Implementation::Details { .provide_arguments = [&] { Vector 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 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; }, }); } #endif Core::EventLoop::initialize_for_current_thread(); // First, resolve the linked modules Vector> linked_instances; Vector> 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 !defined(AK_OS_WINDOWS) if (wasi) { HashMap 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); } #endif linker.link(js_exports); if (export_all_imports) { HashMap exports; auto allocate_function_stub = [&](Wasm::FunctionType const& func, ByteString const& name) { return *machine.store().allocate(Wasm::HostFunction( [name, func](auto&, auto arguments) -> Wasm::Result { StringBuilder argument_builder; bool first = true; size_t index = 0; for (auto& argument : arguments) { AllocatingMemoryStream stream; auto value_type = func.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 result; result.ensure_capacity(func.results().size()); for (auto expect_result : func.results()) result.append(Wasm::Value(expect_result)); return Wasm::Result { move(result) }; }, func, name)); }; for (auto& entry : linker.unresolved_imports()) { Optional address; entry.type.visit( [&](Wasm::TypeIndex const& type_index) { auto& type = parse_result->type_section().types()[type_index.value()]; if (!type.is_function()) { dbgln("[wasm runtime] Cannot stub import {}::{} of non-function {}", entry.module, entry.name, type.name()); return; } address = allocate_function_stub(type.function(), entry.name); }, [&](Wasm::FunctionType const& func) { address = allocate_function_stub(func, entry.name); }, [&](Wasm::TableType const& table_type) { address = *machine.store().allocate(table_type); }, [&](Wasm::MemoryType const& memory_type) { address = *machine.store().allocate(memory_type); }, [&](Wasm::GlobalType const& global_type) { address = *machine.store().allocate(global_type, Wasm::Value(global_type.type())); }, [&](Wasm::TagType const& tag_type) { auto& type = parse_result->type_section().types()[tag_type.type().value()]; if (!type.is_function()) { dbgln("[wasm runtime] Cannot stub tag import {}::{}: type is not a function", entry.module, entry.name); return; } // The module is not yet validated here, so its canonical types may not be known. address = *machine.store().allocate(type.function(), nullptr, tag_type.flags()); }); if (address.has_value()) exports.set(entry, address.release_value()); } 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(); if (print_compiled) { Span functions = module_instance->functions(); Wasm::FunctionAddress spec = specific_function_address.value_or(0); if (specific_function_address.has_value()) functions = { &spec, 1 }; for (auto address : functions) { auto function = machine.store().get(address)->get_pointer(); if (!function) continue; auto& expression = function->code().func().body(); if (expression.compiled_instructions.dispatches.is_empty()) continue; ByteString export_name; for (auto& entry : function->module().exports()) { if (entry.value() == address) { export_name = ByteString::formatted(" '{}'", entry.name()); break; } } TRY(g_stdout->write_until_depleted(ByteString::formatted("Function #{}{} (stack usage = {}):\n", address.value(), export_name, expression.stack_usage_hint()))); Wasm::Printer printer { *g_stdout, 1 }; for (size_t ip = 0; ip < expression.compiled_instructions.dispatches.size(); ++ip) { auto& dispatch = expression.compiled_instructions.dispatches[ip]; auto& addresses = expression.compiled_instructions.src_dst_mappings[ip]; ByteString regs; auto first = true; ssize_t in_count = 0; ssize_t out_count = 0; #define M(name, _, ins, outs) \ case Wasm::Instructions::name.value(): \ in_count = ins; \ out_count = outs; \ break; switch (dispatch.instruction->opcode().value()) { ENUMERATE_WASM_OPCODES(M) } #undef M constexpr auto reg_name = [](Wasm::Dispatch::RegisterOrStack reg) -> ByteString { if (reg == Wasm::Dispatch::RegisterOrStack::Stack) return "stack"sv; if (reg >= Wasm::Dispatch::RegisterOrStack::CallRecord) return ByteString::formatted("cr{}", to_underlying(reg) - to_underlying(Wasm::Dispatch::RegisterOrStack::CallRecord)); return ByteString::formatted("reg{}", to_underlying(reg)); }; if (in_count > -1) { for (ssize_t index = 0; index < in_count; ++index) { if (first) regs = ByteString::formatted("{} ({}", regs, reg_name(addresses.sources[index])); else regs = ByteString::formatted("{}, {}", regs, reg_name(addresses.sources[index])); first = false; } if (out_count > 0) { if (first) regs = ByteString::formatted(" () -> {}", reg_name(addresses.destination)); else regs = ByteString::formatted("{}) -> {}", regs, reg_name(addresses.destination)); } else if (out_count == 0) { if (first) regs = ByteString::formatted(" () -x"); else regs = ByteString::formatted("{}) -x", regs); } else { if (first) regs = ByteString::formatted(" () -?"); else regs = ByteString::formatted("{}) -?", regs); } } else if (dispatch.instruction->opcode() == Wasm::Instructions::call || dispatch.instruction->opcode() == Wasm::Instructions::call_indirect) { if (addresses.destination != Wasm::Dispatch::RegisterOrStack::Stack) regs = ByteString::formatted("(?) -> {}", reg_name(addresses.destination)); } if (regs.is_empty()) regs = ByteString::formatted(" {{{:-<34}}}", regs); else regs = ByteString::formatted(" {{{: <33} }}", regs); TRY(g_stdout->write_until_depleted(ByteString::formatted(" [{:>03}]", ip))); TRY(g_stdout->write_until_depleted(regs.bytes())); printer.print(*dispatch.instruction); } TRY(g_stdout->write_until_depleted("\n"sv.bytes())); } } if (dump_native) { Span functions = module_instance->functions(); Wasm::FunctionAddress spec = specific_function_address.value_or(0); if (specific_function_address.has_value()) functions = { &spec, 1 }; for (auto address : functions) { auto* function = machine.store().get(address)->get_pointer(); if (!function) continue; auto& ci = function->code().func().body().compiled_instructions; if (!ci.cranelift_compiled || ci.cranelift_code_size == 0) continue; ByteString export_name; for (auto& entry : function->module().exports()) { if (entry.value() == address) { export_name = ByteString::formatted(" '{}'", entry.name()); break; } } auto const* code_ptr = bit_cast(ci.dispatches[0].handler_ptr); auto code_size = ci.cranelift_code_size; #if defined(AK_OS_WINDOWS) char tmp_path[MAX_PATH]; { char tmp_dir[MAX_PATH]; GetTempPathA(MAX_PATH, tmp_dir); GetTempFileNameA(tmp_dir, "wn", 0, tmp_path); } { auto tmp_file = Core::File::open(StringView { tmp_path, strlen(tmp_path) }, Core::File::OpenMode::Write); if (tmp_file.is_error()) { warnln("Failed to create temp file for function #{}", address.value()); continue; } (void)tmp_file.value()->write_until_depleted({ code_ptr, code_size }); } #else char tmp_path[] = "/tmp/wasm-native-XXXXXX"; int fd = mkstemp(tmp_path); if (fd < 0) { warnln("Failed to create temp file for function #{}", address.value()); continue; } { auto tmp_file = MUST(Core::File::adopt_fd(fd, Core::File::OpenMode::Write)); # if ARCH(AARCH64) && defined(AK_OS_MACOS) // Write a minimal Mach-O object file so objdump can disassemble it. struct [[gnu::packed]] { u32 magic = 0xFEEDFACF; u32 cputype = 0x0100000C; // CPU_TYPE_ARM64 u32 cpusubtype = 0; u32 filetype = 1; // MH_OBJECT u32 ncmds = 1; u32 sizeofcmds = 72 + 80; // segment + section u32 flags = 0; u32 reserved = 0; } mach_header; struct [[gnu::packed]] { u32 cmd = 0x19; // LC_SEGMENT_64 u32 cmdsize = 72 + 80; char segname[16] = {}; u64 vmaddr = 0; u64 vmsize; u64 fileoff; u64 filesize; u32 maxprot = 7; u32 initprot = 7; u32 nsects = 1; u32 flags = 0; } segment; segment.vmsize = code_size; segment.fileoff = sizeof(mach_header) + sizeof(segment) + 80; segment.filesize = code_size; struct [[gnu::packed]] { char sectname[16] = "__text"; char segname[16] = "__TEXT"; u64 addr = 0; u64 size; u32 offset; u32 align = 2; u32 reloff = 0; u32 nreloc = 0; u32 flags = 0x80000400; // S_REGULAR | S_ATTR_PURE_INSTRUCTIONS u32 reserved1 = 0; u32 reserved2 = 0; u32 reserved3 = 0; } section; static_assert(sizeof(section) == 80); section.size = code_size; section.offset = static_cast(segment.fileoff); (void)tmp_file->write_until_depleted({ &mach_header, sizeof(mach_header) }); (void)tmp_file->write_until_depleted({ &segment, sizeof(segment) }); (void)tmp_file->write_until_depleted({ §ion, sizeof(section) }); # endif (void)tmp_file->write_until_depleted({ code_ptr, code_size }); } #endif outln("Function #{}{} ({} bytes):", address.value(), export_name, code_size); fflush(stdout); #if defined(AK_OS_WINDOWS) auto result = Core::Process::spawn({ .name = "ndisasm"sv, .executable = "ndisasm"sv, .search_for_executable_in_path = true, .arguments = { "-b"sv, (sizeof(void*) == sizeof(u64) ? "64"sv : "32"sv), tmp_path }, }); #elif defined(AK_OS_MACOS) auto cmd = ByteString::formatted("/usr/bin/objdump -d {} | tail -n +7", tmp_path); auto result = Core::Process::spawn({ .name = "sh"sv, .executable = "/bin/sh"sv, .arguments = { "-c"sv, cmd }, }); #else # if ARCH(X86_64) auto cmd = ByteString::formatted("/usr/bin/objdump -D -b binary -m i386:x86-64 {} | tail -n +8", tmp_path); # elif ARCH(AARCH64) auto cmd = ByteString::formatted("/usr/bin/objdump -D -b binary -m aarch64 {} | tail -n +8", tmp_path); # else auto cmd = ByteString::formatted("/usr/bin/objdump -D -b binary {} | tail -n +8", tmp_path); # endif auto result = Core::Process::spawn({ .name = "sh"sv, .executable = "/bin/sh"sv, .arguments = { "-c"sv, cmd }, }); #endif if (!result.is_error()) (void)result.release_value().wait_for_termination(); else warnln("Failed to run disassembler: {}", result.error()); #if defined(AK_OS_WINDOWS) DeleteFileA(tmp_path); #else unlink(tmp_path); #endif outln(); } } 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())).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 (!exported_function_to_execute.is_empty()) { Optional run_address; Vector values; for (auto& entry : module_instance->exports()) { if (entry.name() == exported_function_to_execute) { if (auto addr = entry.value().get_pointer()) 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()) { warnln("Exported function is a host function, cannot run that yet"); return 1; } for (auto& param : instance->get().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 {}", param.kind_name(), values_to_push.last().type.kind_name()); return 1; } } if (print) { outln("Executing "); print_func(*run_address); outln(); } auto result = machine.invoke(g_interpreter, run_address.value(), move(values)); if (result.is_trap()) { auto trap_reason = result.trap().format(); if (trap_reason.starts_with("exit:"sv)) return -trap_reason.substring_view(5).to_number().value_or(-1); warnln("Execution trapped: {}", trap_reason); } else { if (!result.values().is_empty()) warnln("Returned:"); auto result_type = instance->get().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; }