diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp index 6179e1b3b2..d270a0e1b5 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp @@ -283,4 +283,86 @@ Result AbstractMachine::invoke(FunctionAddress address, Vector arguments) return Configuration { m_store }.call(address, move(arguments)); } +void Linker::link(const ModuleInstance& instance) +{ + populate(); + if (m_unresolved_imports.is_empty()) + return; + + HashTable resolved_imports; + for (auto& import_ : m_unresolved_imports) { + auto it = instance.exports().find_if([&](auto& export_) { return export_.name() == import_.name; }); + if (!it.is_end()) { + resolved_imports.set(import_); + m_resolved_imports.set(import_, it->value()); + } + } + + for (auto& entry : resolved_imports) + m_unresolved_imports.remove(entry); +} + +void Linker::link(const HashMap& exports) +{ + populate(); + if (m_unresolved_imports.is_empty()) + return; + + HashTable resolved_imports; + for (auto& import_ : m_unresolved_imports) { + auto export_ = exports.get(import_); + if (export_.has_value()) { + resolved_imports.set(import_); + m_resolved_imports.set(import_, export_.value()); + } + } + + for (auto& entry : resolved_imports) + m_unresolved_imports.remove(entry); +} + +AK::Result, LinkError> Linker::finish() +{ + populate(); + if (!m_unresolved_imports.is_empty()) { + if (!m_error.has_value()) + m_error = LinkError {}; + for (auto& entry : m_unresolved_imports) + m_error->missing_imports.append(entry.name); + return *m_error; + } + + if (m_error.has_value()) + return *m_error; + + // Result must be in the same order as the module imports + Vector exports; + exports.ensure_capacity(m_ordered_imports.size()); + for (auto& import_ : m_ordered_imports) + exports.unchecked_append(*m_resolved_imports.get(import_)); + return exports; +} + +void Linker::populate() +{ + if (!m_ordered_imports.is_empty()) + return; + + // There better be at most one import section! + bool already_seen_an_import_section = false; + m_module.for_each_section_of_type([&](const ImportSection& section) { + if (already_seen_an_import_section) { + if (!m_error.has_value()) + m_error = LinkError {}; + m_error->other_errors.append(LinkError::InvalidImportedModule); + return; + } + already_seen_an_import_section = true; + for (auto& import_ : section.imports()) { + m_ordered_imports.append({ import_.module(), import_.name(), import_.description() }); + m_unresolved_imports.set(m_ordered_imports.last()); + } + }); +} + } diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index aa480ae70d..ca09458ed2 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include #include @@ -15,6 +17,13 @@ namespace Wasm { struct InstantiationError { String error { "Unknown error" }; }; +struct LinkError { + enum OtherErrors { + InvalidImportedModule, + }; + Vector missing_imports; + Vector other_errors; +}; TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, FunctionAddress); TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, ExternAddress); @@ -436,4 +445,42 @@ private: Store m_store; }; +class Linker { +public: + struct Name { + String module; + String name; + ImportSection::Import::ImportDesc type; + }; + + explicit Linker(const Module& module) + : m_module(module) + { + } + + // Link a module, the import 'module name' is ignored with this. + void link(const ModuleInstance&); + + // Link a bunch of qualified values, also matches 'module name'. + void link(const HashMap&); + + AK::Result, LinkError> finish(); + +private: + void populate(); + + const Module& m_module; + HashMap m_resolved_imports; + HashTable m_unresolved_imports; + Vector m_ordered_imports; + Optional m_error; +}; + } + +template<> +struct AK::Traits : public AK::GenericTraits { + static constexpr bool is_trivial() { return false; } + static unsigned hash(const Wasm::Linker::Name& entry) { return pair_int_hash(entry.module.hash(), entry.name.hash()); } + static bool equals(const Wasm::Linker::Name& a, const Wasm::Linker::Name& b) { return a.name == b.name && a.module == b.module; } +}; diff --git a/Userland/Utilities/wasm.cpp b/Userland/Utilities/wasm.cpp index 2100be47a8..c9d7609103 100644 --- a/Userland/Utilities/wasm.cpp +++ b/Userland/Utilities/wasm.cpp @@ -11,6 +11,30 @@ #include #include +static Optional 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; @@ -18,12 +42,27 @@ int main(int argc, char* argv[]) bool attempt_instantiate = false; String exported_function_to_execute; Vector values_to_push; + Vector modules_to_link_in; 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(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)", @@ -43,20 +82,7 @@ int main(int argc, char* argv[]) if (!exported_function_to_execute.is_empty()) attempt_instantiate = true; - auto result = Core::File::open(filename, Core::OpenMode::ReadOnly); - if (result.is_error()) { - warnln("Failed to open {}: {}", filename, result.error()); - return 1; - } - - 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 2; - } - + auto parse_result = parse(filename); if (print && !attempt_instantiate) { auto out_stream = Core::OutputFileStream::standard_output(); Wasm::Printer printer(out_stream); @@ -65,7 +91,43 @@ int main(int argc, char* argv[]) if (attempt_instantiate) { Wasm::AbstractMachine machine; - auto result = machine.instantiate(parse_result.value(), {}); + // First, resolve the linked modules + NonnullOwnPtrVector linked_instances; + Vector 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()); + } + + Wasm::Linker linker { parse_result.value() }; + for (auto& instance : linked_instances) + linker.link(instance); + 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;