mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 03:37:43 +00:00
LibWasm: Implement a very basic linker
This will simply "link" any given module instances and produce a list of external values that can be used to instantiate a module. Note that this is extremely basic and cannot resolve circular dependencies, and depends on the instance order.
This commit is contained in:
parent
3283c8a495
commit
35b3ae26ed
3 changed files with 206 additions and 15 deletions
|
@ -283,4 +283,86 @@ Result AbstractMachine::invoke(FunctionAddress address, Vector<Value> arguments)
|
||||||
return Configuration { m_store }.call(address, move(arguments));
|
return Configuration { m_store }.call(address, move(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Linker::link(const ModuleInstance& instance)
|
||||||
|
{
|
||||||
|
populate();
|
||||||
|
if (m_unresolved_imports.is_empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
HashTable<Name> 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<Linker::Name, ExternValue>& exports)
|
||||||
|
{
|
||||||
|
populate();
|
||||||
|
if (m_unresolved_imports.is_empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
HashTable<Name> 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<Vector<ExternValue>, 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<ExternValue> 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<ImportSection>([&](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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/HashTable.h>
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <LibWasm/Types.h>
|
#include <LibWasm/Types.h>
|
||||||
|
@ -15,6 +17,13 @@ namespace Wasm {
|
||||||
struct InstantiationError {
|
struct InstantiationError {
|
||||||
String error { "Unknown error" };
|
String error { "Unknown error" };
|
||||||
};
|
};
|
||||||
|
struct LinkError {
|
||||||
|
enum OtherErrors {
|
||||||
|
InvalidImportedModule,
|
||||||
|
};
|
||||||
|
Vector<String> missing_imports;
|
||||||
|
Vector<OtherErrors> 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, FunctionAddress);
|
||||||
TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, ExternAddress);
|
TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, ExternAddress);
|
||||||
|
@ -436,4 +445,42 @@ private:
|
||||||
Store m_store;
|
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<Name, ExternValue>&);
|
||||||
|
|
||||||
|
AK::Result<Vector<ExternValue>, LinkError> finish();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void populate();
|
||||||
|
|
||||||
|
const Module& m_module;
|
||||||
|
HashMap<Name, ExternValue> m_resolved_imports;
|
||||||
|
HashTable<Name> m_unresolved_imports;
|
||||||
|
Vector<Name> m_ordered_imports;
|
||||||
|
Optional<LinkError> m_error;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Traits<Wasm::Linker::Name> : public AK::GenericTraits<Wasm::Linker::Name> {
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
|
|
@ -11,6 +11,30 @@
|
||||||
#include <LibWasm/Printer/Printer.h>
|
#include <LibWasm/Printer/Printer.h>
|
||||||
#include <LibWasm/Types.h>
|
#include <LibWasm/Types.h>
|
||||||
|
|
||||||
|
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[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
const char* filename = nullptr;
|
const char* filename = nullptr;
|
||||||
|
@ -18,12 +42,27 @@ int main(int argc, char* argv[])
|
||||||
bool attempt_instantiate = false;
|
bool attempt_instantiate = false;
|
||||||
String exported_function_to_execute;
|
String exported_function_to_execute;
|
||||||
Vector<u64> values_to_push;
|
Vector<u64> values_to_push;
|
||||||
|
Vector<String> modules_to_link_in;
|
||||||
|
|
||||||
Core::ArgsParser parser;
|
Core::ArgsParser parser;
|
||||||
parser.add_positional_argument(filename, "File name to parse", "file");
|
parser.add_positional_argument(filename, "File name to parse", "file");
|
||||||
parser.add_option(print, "Print the parsed module", "print", 'p');
|
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(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(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 {
|
parser.add_option(Core::ArgsParser::Option {
|
||||||
.requires_argument = true,
|
.requires_argument = true,
|
||||||
.help_string = "Supply arguments to the function (default=0) (expects u64, casts to required type)",
|
.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())
|
if (!exported_function_to_execute.is_empty())
|
||||||
attempt_instantiate = true;
|
attempt_instantiate = true;
|
||||||
|
|
||||||
auto result = Core::File::open(filename, Core::OpenMode::ReadOnly);
|
auto parse_result = parse(filename);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (print && !attempt_instantiate) {
|
if (print && !attempt_instantiate) {
|
||||||
auto out_stream = Core::OutputFileStream::standard_output();
|
auto out_stream = Core::OutputFileStream::standard_output();
|
||||||
Wasm::Printer printer(out_stream);
|
Wasm::Printer printer(out_stream);
|
||||||
|
@ -65,7 +91,43 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
if (attempt_instantiate) {
|
if (attempt_instantiate) {
|
||||||
Wasm::AbstractMachine machine;
|
Wasm::AbstractMachine machine;
|
||||||
auto result = machine.instantiate(parse_result.value(), {});
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
if (result.is_error()) {
|
||||||
warnln("Module instantiation failed: {}", result.error().error);
|
warnln("Module instantiation failed: {}", result.error().error);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue