mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
LibWasm: Add basic support for module instantiation and execution stubs
This adds very basic support for module instantiation/allocation, as well as a stub for an interpreter (and executions APIs). The 'wasm' utility is further expanded to instantiate, and attempt executing the first non-imported function in the module. Note that as the execution is a stub, the expected result is a zero. Regardless, this will allow future commits to implement the JS WebAssembly API. :^)
This commit is contained in:
parent
2b755f1fbf
commit
4d9246ac9d
Notes:
sideshowbarker
2024-07-18 18:13:02 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/4d9246ac9de Pull-request: https://github.com/SerenityOS/serenity/pull/7021
8 changed files with 938 additions and 1 deletions
295
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp
Normal file
295
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
||||
#include <LibWasm/AbstractMachine/Configuration.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
Optional<FunctionAddress> Store::allocate(ModuleInstance& module, const Module::Function& function)
|
||||
{
|
||||
FunctionAddress address { m_functions.size() };
|
||||
if (function.type().value() > module.types().size())
|
||||
return {};
|
||||
|
||||
auto& type = module.types()[function.type().value()];
|
||||
m_functions.empend(WasmFunction { type, module, function });
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<FunctionAddress> Store::allocate(const HostFunction& function)
|
||||
{
|
||||
FunctionAddress address { m_functions.size() };
|
||||
m_functions.empend(HostFunction { function });
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<TableAddress> Store::allocate(const TableType& type)
|
||||
{
|
||||
TableAddress address { m_tables.size() };
|
||||
Vector<Optional<Reference>> elements;
|
||||
elements.resize(type.limits().min());
|
||||
m_tables.empend(TableInstance { type, move(elements) });
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<MemoryAddress> Store::allocate(const MemoryType& type)
|
||||
{
|
||||
MemoryAddress address { m_memories.size() };
|
||||
m_memories.empend(MemoryInstance { type });
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<GlobalAddress> Store::allocate(const GlobalType& type, Value value)
|
||||
{
|
||||
GlobalAddress address { m_globals.size() };
|
||||
m_globals.append(GlobalInstance { move(value), type.is_mutable() });
|
||||
return address;
|
||||
}
|
||||
|
||||
FunctionInstance* Store::get(FunctionAddress address)
|
||||
{
|
||||
auto value = address.value();
|
||||
if (m_functions.size() <= value)
|
||||
return nullptr;
|
||||
return &m_functions[value];
|
||||
}
|
||||
|
||||
TableInstance* Store::get(TableAddress address)
|
||||
{
|
||||
auto value = address.value();
|
||||
if (m_tables.size() <= value)
|
||||
return nullptr;
|
||||
return &m_tables[value];
|
||||
}
|
||||
|
||||
MemoryInstance* Store::get(MemoryAddress address)
|
||||
{
|
||||
auto value = address.value();
|
||||
if (m_memories.size() <= value)
|
||||
return nullptr;
|
||||
return &m_memories[value];
|
||||
}
|
||||
|
||||
GlobalInstance* Store::get(GlobalAddress address)
|
||||
{
|
||||
auto value = address.value();
|
||||
if (m_globals.size() <= value)
|
||||
return nullptr;
|
||||
return &m_globals[value];
|
||||
}
|
||||
|
||||
InstantiationResult AbstractMachine::instantiate(const Module& module, Vector<ExternValue> externs)
|
||||
{
|
||||
Optional<InstantiationResult> instantiation_result;
|
||||
|
||||
module.for_each_section_of_type<TypeSection>([&](const TypeSection& section) {
|
||||
m_module_instance.types() = section.types();
|
||||
});
|
||||
|
||||
// TODO: Validate stuff
|
||||
|
||||
Vector<Value> global_values;
|
||||
ModuleInstance auxiliary_instance;
|
||||
|
||||
// FIXME: Check that imports/extern match
|
||||
|
||||
for (auto& entry : externs) {
|
||||
if (auto* ptr = entry.get_pointer<GlobalAddress>())
|
||||
auxiliary_instance.globals().append(*ptr);
|
||||
}
|
||||
|
||||
module.for_each_section_of_type<GlobalSection>([&](auto& global_section) {
|
||||
for (auto& entry : global_section.entries()) {
|
||||
auto frame = make<Frame>(
|
||||
auxiliary_instance,
|
||||
Vector<Value> {},
|
||||
entry.expression(),
|
||||
1);
|
||||
Configuration config { m_store };
|
||||
config.set_frame(move(frame));
|
||||
auto result = config.execute();
|
||||
// What if this traps?
|
||||
if (result.is_trap())
|
||||
instantiation_result = InstantiationError { "Global value construction trapped" };
|
||||
else
|
||||
global_values.append(result.values().first());
|
||||
}
|
||||
});
|
||||
|
||||
if (auto result = allocate_all(module, externs, global_values); result.is_error()) {
|
||||
return result.error();
|
||||
}
|
||||
|
||||
module.for_each_section_of_type<ElementSection>([&](const ElementSection& element_section) {
|
||||
auto frame = make<Frame>(
|
||||
m_module_instance,
|
||||
Vector<Value> {},
|
||||
element_section.function().offset(),
|
||||
1);
|
||||
Configuration config { m_store };
|
||||
config.set_frame(move(frame));
|
||||
auto result = config.execute();
|
||||
// What if this traps?
|
||||
VERIFY(!result.is_trap());
|
||||
size_t offset = 0;
|
||||
result.values().first().value().visit(
|
||||
[&](const auto& value) { offset = value; },
|
||||
[&](const FunctionAddress&) { VERIFY_NOT_REACHED(); },
|
||||
[&](const ExternAddress&) { VERIFY_NOT_REACHED(); });
|
||||
// FIXME: Module::get(*Index)
|
||||
auto table_address = m_module_instance.tables().at(element_section.function().table().value());
|
||||
if (auto table_instance = m_store.get(table_address)) {
|
||||
auto& init = element_section.function().init();
|
||||
for (size_t i = 0; i < init.size(); ++i)
|
||||
table_instance->elements()[offset + i] = Reference { Reference::Func { init[i].value() } }; // HACK!
|
||||
}
|
||||
});
|
||||
|
||||
module.for_each_section_of_type<DataSection>([&](const DataSection& data_section) {
|
||||
for (auto& segment : data_section.data()) {
|
||||
segment.value().visit(
|
||||
[&](const DataSection::Data::Active& data) {
|
||||
auto frame = make<Frame>(
|
||||
m_module_instance,
|
||||
Vector<Value> {},
|
||||
data.offset,
|
||||
1);
|
||||
Configuration config { m_store };
|
||||
config.set_frame(move(frame));
|
||||
auto result = config.execute();
|
||||
size_t offset = 0;
|
||||
result.values().first().value().visit(
|
||||
[&](const auto& value) { offset = value; },
|
||||
[&](const FunctionAddress&) { instantiation_result = InstantiationError { "Data segment offset returned an address" }; },
|
||||
[&](const ExternAddress&) { instantiation_result = InstantiationError { "Data segment offset returned an address" }; });
|
||||
if (instantiation_result.has_value() && instantiation_result->is_error())
|
||||
return;
|
||||
if (m_module_instance.memories().size() <= data.index.value()) {
|
||||
instantiation_result = InstantiationError { String::formatted("Data segment referenced out-of-bounds memory ({}) of max {} entries", data.index.value(), m_module_instance.memories().size()) };
|
||||
return;
|
||||
}
|
||||
auto address = m_module_instance.memories()[data.index.value()];
|
||||
if (auto instance = m_store.get(address)) {
|
||||
if (instance->type().limits().max().value_or(data.init.size() + offset + 1) <= data.init.size() + offset) {
|
||||
instantiation_result = InstantiationError { String::formatted("Data segment attempted to write to out-of-bounds memory ({}) of max {} bytes", data.init.size() + offset, instance->type().limits().max().value()) };
|
||||
return;
|
||||
}
|
||||
instance->grow(data.init.size() + offset - instance->size());
|
||||
instance->data().overwrite(offset, data.init.data(), data.init.size());
|
||||
}
|
||||
},
|
||||
[&](const DataSection::Data::Passive&) {
|
||||
// FIXME: What do we do here?
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.for_each_section_of_type<StartSection>([&](auto&) {
|
||||
instantiation_result = InstantiationError { "Start section not yet implemented" };
|
||||
// FIXME: Invoke the start function.
|
||||
});
|
||||
|
||||
return instantiation_result.value_or({});
|
||||
}
|
||||
|
||||
InstantiationResult AbstractMachine::allocate_all(const Module& module, Vector<ExternValue>& externs, Vector<Value>& global_values)
|
||||
{
|
||||
Optional<InstantiationResult> result;
|
||||
|
||||
for (auto& entry : externs) {
|
||||
entry.visit(
|
||||
[&](const FunctionAddress& address) { m_module_instance.functions().append(address); },
|
||||
[&](const TableAddress& address) { m_module_instance.tables().append(address); },
|
||||
[&](const MemoryAddress& address) { m_module_instance.memories().append(address); },
|
||||
[&](const GlobalAddress& address) { m_module_instance.globals().append(address); });
|
||||
}
|
||||
|
||||
// FIXME: What if this fails?
|
||||
|
||||
for (auto& func : module.functions()) {
|
||||
auto address = m_store.allocate(m_module_instance, func);
|
||||
VERIFY(address.has_value());
|
||||
m_module_instance.functions().append(*address);
|
||||
}
|
||||
|
||||
module.for_each_section_of_type<TableSection>([&](const TableSection& section) {
|
||||
for (auto& table : section.tables()) {
|
||||
auto table_address = m_store.allocate(table.type());
|
||||
VERIFY(table_address.has_value());
|
||||
m_module_instance.tables().append(*table_address);
|
||||
}
|
||||
});
|
||||
|
||||
module.for_each_section_of_type<MemorySection>([&](const MemorySection& section) {
|
||||
for (auto& memory : section.memories()) {
|
||||
auto memory_address = m_store.allocate(memory.type());
|
||||
VERIFY(memory_address.has_value());
|
||||
m_module_instance.memories().append(*memory_address);
|
||||
}
|
||||
});
|
||||
|
||||
module.for_each_section_of_type<GlobalSection>([&](const GlobalSection& section) {
|
||||
size_t index = 0;
|
||||
for (auto& entry : section.entries()) {
|
||||
auto address = m_store.allocate(entry.type(), global_values[index]);
|
||||
VERIFY(address.has_value());
|
||||
m_module_instance.globals().append(*address);
|
||||
index++;
|
||||
}
|
||||
});
|
||||
|
||||
module.for_each_section_of_type<ExportSection>([&](const ExportSection& section) {
|
||||
for (auto& entry : section.entries()) {
|
||||
Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress, Empty> address { Empty {} };
|
||||
entry.description().visit(
|
||||
[&](const FunctionIndex& index) {
|
||||
if (m_module_instance.functions().size() > index.value())
|
||||
address = FunctionAddress { m_module_instance.functions()[index.value()] };
|
||||
else
|
||||
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.functions().size());
|
||||
},
|
||||
[&](const TableIndex& index) {
|
||||
if (m_module_instance.tables().size() > index.value())
|
||||
address = TableAddress { m_module_instance.tables()[index.value()] };
|
||||
else
|
||||
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.tables().size());
|
||||
},
|
||||
[&](const MemoryIndex& index) {
|
||||
if (m_module_instance.memories().size() > index.value())
|
||||
address = MemoryAddress { m_module_instance.memories()[index.value()] };
|
||||
else
|
||||
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.memories().size());
|
||||
},
|
||||
[&](const GlobalIndex& index) {
|
||||
if (m_module_instance.globals().size() > index.value())
|
||||
address = GlobalAddress { m_module_instance.globals()[index.value()] };
|
||||
else
|
||||
dbgln("Failed to export '{}', the exported address ({}) was out of bounds (min: 0, max: {})", entry.name(), index.value(), m_module_instance.globals().size());
|
||||
});
|
||||
|
||||
if (address.has<Empty>()) {
|
||||
result = InstantiationError { "An export could not be resolved" };
|
||||
continue;
|
||||
}
|
||||
|
||||
m_module_instance.exports().append(ExportInstance {
|
||||
entry.name(),
|
||||
move(address).downcast<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result.value_or({});
|
||||
}
|
||||
|
||||
Result AbstractMachine::invoke(FunctionAddress address, Vector<Value> arguments)
|
||||
{
|
||||
return Configuration { m_store }.call(address, move(arguments));
|
||||
}
|
||||
|
||||
}
|
391
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
Normal file
391
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h
Normal file
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Result.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
struct InstantiationError {
|
||||
String error { "Unknown error" };
|
||||
};
|
||||
using InstantiationResult = Result<void, InstantiationError>;
|
||||
|
||||
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, TableAddress);
|
||||
TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, GlobalAddress);
|
||||
TYPEDEF_DISTINCT_NUMERIC_GENERAL(u64, true, true, false, false, false, true, MemoryAddress);
|
||||
|
||||
// FIXME: These should probably be made generic/virtual if/when we decide to do something more
|
||||
// fancy than just a dumb interpreter.
|
||||
class Value {
|
||||
public:
|
||||
using AnyValueType = Variant<i32, i64, float, double, FunctionAddress, ExternAddress>;
|
||||
explicit Value(AnyValueType value)
|
||||
: m_value(move(value))
|
||||
, m_type(ValueType::I32)
|
||||
{
|
||||
if (m_value.has<i32>())
|
||||
m_type = ValueType { ValueType::I32 };
|
||||
else if (m_value.has<i64>())
|
||||
m_type = ValueType { ValueType::I64 };
|
||||
else if (m_value.has<float>())
|
||||
m_type = ValueType { ValueType::F32 };
|
||||
else if (m_value.has<double>())
|
||||
m_type = ValueType { ValueType::F64 };
|
||||
else if (m_value.has<FunctionAddress>())
|
||||
m_type = ValueType { ValueType::FunctionReference };
|
||||
else if (m_value.has<ExternAddress>())
|
||||
m_type = ValueType { ValueType::ExternReference };
|
||||
else
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(sizeof(T) <= sizeof(u64)) explicit Value(ValueType type, T raw_value)
|
||||
: m_value(0)
|
||||
, m_type(type)
|
||||
{
|
||||
switch (type.kind()) {
|
||||
case ValueType::Kind::ExternReference:
|
||||
m_value = ExternAddress { bit_cast<u64>(raw_value) };
|
||||
break;
|
||||
case ValueType::Kind::FunctionReference:
|
||||
m_value = FunctionAddress { bit_cast<u64>(raw_value) };
|
||||
break;
|
||||
case ValueType::Kind::I32:
|
||||
m_value = static_cast<i32>(bit_cast<i64>(raw_value));
|
||||
break;
|
||||
case ValueType::Kind::I64:
|
||||
m_value = static_cast<i64>(bit_cast<u64>(raw_value));
|
||||
break;
|
||||
case ValueType::Kind::F32:
|
||||
m_value = static_cast<float>(bit_cast<double>(raw_value));
|
||||
break;
|
||||
case ValueType::Kind::F64:
|
||||
m_value = bit_cast<double>(raw_value);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
Value(const Value& value)
|
||||
: m_value(AnyValueType { value.m_value })
|
||||
, m_type(value.m_type)
|
||||
{
|
||||
}
|
||||
|
||||
Value(Value&& value)
|
||||
: m_value(move(value.m_value))
|
||||
, m_type(move(value.m_type))
|
||||
{
|
||||
}
|
||||
|
||||
auto& type() const { return m_type; }
|
||||
auto& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
AnyValueType m_value;
|
||||
ValueType m_type;
|
||||
};
|
||||
|
||||
struct Trap {
|
||||
// Empty value type
|
||||
};
|
||||
|
||||
class Result {
|
||||
public:
|
||||
explicit Result(Vector<Value> values)
|
||||
: m_values(move(values))
|
||||
{
|
||||
}
|
||||
|
||||
Result(Trap)
|
||||
: m_is_trap(true)
|
||||
{
|
||||
}
|
||||
|
||||
auto& values() const { return m_values; }
|
||||
auto is_trap() const { return m_is_trap; }
|
||||
|
||||
private:
|
||||
Vector<Value> m_values;
|
||||
bool m_is_trap { false };
|
||||
};
|
||||
|
||||
using ExternValue = Variant<FunctionAddress, TableAddress, MemoryAddress, GlobalAddress>;
|
||||
|
||||
class ExportInstance {
|
||||
public:
|
||||
explicit ExportInstance(String name, ExternValue value)
|
||||
: m_name(move(name))
|
||||
, m_value(move(value))
|
||||
{
|
||||
}
|
||||
|
||||
auto& name() const { return m_name; }
|
||||
auto& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
String m_name;
|
||||
ExternValue m_value;
|
||||
};
|
||||
|
||||
class ModuleInstance {
|
||||
public:
|
||||
explicit ModuleInstance(
|
||||
Vector<FunctionType> types, Vector<FunctionAddress> function_addresses, Vector<TableAddress> table_addresses,
|
||||
Vector<MemoryAddress> memory_addresses, Vector<GlobalAddress> global_addresses, Vector<ExportInstance> exports)
|
||||
: m_types(move(types))
|
||||
, m_functions(move(function_addresses))
|
||||
, m_tables(move(table_addresses))
|
||||
, m_memories(move(memory_addresses))
|
||||
, m_globals(move(global_addresses))
|
||||
, m_exports(move(exports))
|
||||
{
|
||||
}
|
||||
|
||||
ModuleInstance() = default;
|
||||
|
||||
auto& types() const { return m_types; }
|
||||
auto& functions() const { return m_functions; }
|
||||
auto& tables() const { return m_tables; }
|
||||
auto& memories() const { return m_memories; }
|
||||
auto& globals() const { return m_globals; }
|
||||
auto& exports() const { return m_exports; }
|
||||
|
||||
auto& types() { return m_types; }
|
||||
auto& functions() { return m_functions; }
|
||||
auto& tables() { return m_tables; }
|
||||
auto& memories() { return m_memories; }
|
||||
auto& globals() { return m_globals; }
|
||||
auto& exports() { return m_exports; }
|
||||
|
||||
private:
|
||||
Vector<FunctionType> m_types;
|
||||
Vector<FunctionAddress> m_functions;
|
||||
Vector<TableAddress> m_tables;
|
||||
Vector<MemoryAddress> m_memories;
|
||||
Vector<GlobalAddress> m_globals;
|
||||
Vector<ExportInstance> m_exports;
|
||||
};
|
||||
|
||||
class WasmFunction {
|
||||
public:
|
||||
explicit WasmFunction(const FunctionType& type, const ModuleInstance& module, const Module::Function& code)
|
||||
: m_type(type)
|
||||
, m_module(module)
|
||||
, m_code(code)
|
||||
{
|
||||
}
|
||||
|
||||
auto& type() const { return m_type; }
|
||||
auto& module() const { return m_module; }
|
||||
auto& code() const { return m_code; }
|
||||
|
||||
private:
|
||||
const FunctionType& m_type;
|
||||
const ModuleInstance& m_module;
|
||||
const Module::Function& m_code;
|
||||
};
|
||||
|
||||
class HostFunction {
|
||||
public:
|
||||
explicit HostFunction(FlatPtr ptr, const FunctionType& type)
|
||||
: m_ptr(ptr)
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
auto ptr() const { return m_ptr; }
|
||||
auto& type() const { return m_type; }
|
||||
|
||||
private:
|
||||
FlatPtr m_ptr { 0 };
|
||||
const FunctionType& m_type;
|
||||
};
|
||||
|
||||
using FunctionInstance = Variant<WasmFunction, HostFunction>;
|
||||
|
||||
class Reference {
|
||||
public:
|
||||
struct Null {
|
||||
ValueType type;
|
||||
};
|
||||
struct Func {
|
||||
FunctionAddress address;
|
||||
};
|
||||
struct Extern {
|
||||
ExternAddress address;
|
||||
};
|
||||
|
||||
using RefType = Variant<Null, Func, Extern>;
|
||||
explicit Reference(RefType ref)
|
||||
: m_ref(move(ref))
|
||||
{
|
||||
}
|
||||
|
||||
auto& ref() const { return m_ref; }
|
||||
|
||||
private:
|
||||
RefType m_ref;
|
||||
};
|
||||
|
||||
class TableInstance {
|
||||
public:
|
||||
explicit TableInstance(const TableType& type, Vector<Optional<Reference>> elements)
|
||||
: m_elements(move(elements))
|
||||
, m_type(type)
|
||||
{
|
||||
}
|
||||
|
||||
auto& elements() const { return m_elements; }
|
||||
auto& elements() { return m_elements; }
|
||||
auto& type() const { return m_type; }
|
||||
|
||||
private:
|
||||
Vector<Optional<Reference>> m_elements;
|
||||
const TableType& m_type;
|
||||
};
|
||||
|
||||
class MemoryInstance {
|
||||
public:
|
||||
explicit MemoryInstance(const MemoryType& type)
|
||||
: m_type(type)
|
||||
{
|
||||
grow(m_type.limits().min());
|
||||
}
|
||||
|
||||
auto& type() const { return m_type; }
|
||||
auto size() const { return m_size; }
|
||||
auto& data() const { return m_data; }
|
||||
auto& data() { return m_data; }
|
||||
|
||||
void grow(size_t new_size) { m_data.grow(new_size); }
|
||||
|
||||
private:
|
||||
const MemoryType& m_type;
|
||||
size_t m_size { 0 };
|
||||
ByteBuffer m_data;
|
||||
};
|
||||
|
||||
class GlobalInstance {
|
||||
public:
|
||||
explicit GlobalInstance(Value value, bool is_mutable)
|
||||
: m_mutable(is_mutable)
|
||||
, m_value(move(value))
|
||||
{
|
||||
}
|
||||
|
||||
auto is_mutable() const { return m_mutable; }
|
||||
auto& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
bool m_mutable { false };
|
||||
Value m_value;
|
||||
};
|
||||
|
||||
class Store {
|
||||
public:
|
||||
Store() = default;
|
||||
|
||||
Optional<FunctionAddress> allocate(ModuleInstance& module, const Module::Function& function);
|
||||
Optional<FunctionAddress> allocate(const HostFunction&);
|
||||
Optional<TableAddress> allocate(const TableType&);
|
||||
Optional<MemoryAddress> allocate(const MemoryType&);
|
||||
Optional<GlobalAddress> allocate(const GlobalType&, Value);
|
||||
|
||||
FunctionInstance* get(FunctionAddress);
|
||||
TableInstance* get(TableAddress);
|
||||
MemoryInstance* get(MemoryAddress);
|
||||
GlobalInstance* get(GlobalAddress);
|
||||
|
||||
private:
|
||||
Vector<FunctionInstance> m_functions;
|
||||
Vector<TableInstance> m_tables;
|
||||
Vector<MemoryInstance> m_memories;
|
||||
Vector<GlobalInstance> m_globals;
|
||||
};
|
||||
|
||||
class Label {
|
||||
public:
|
||||
explicit Label(InstructionPointer continuation)
|
||||
: m_continuation(continuation)
|
||||
{
|
||||
}
|
||||
|
||||
auto continuation() const { return m_continuation; }
|
||||
|
||||
private:
|
||||
InstructionPointer m_continuation;
|
||||
};
|
||||
|
||||
class Frame {
|
||||
AK_MAKE_NONCOPYABLE(Frame);
|
||||
|
||||
public:
|
||||
explicit Frame(const ModuleInstance& module, Vector<Value> locals, const Expression& expression, size_t arity)
|
||||
: m_module(module)
|
||||
, m_locals(move(locals))
|
||||
, m_expression(expression)
|
||||
, m_arity(arity)
|
||||
{
|
||||
}
|
||||
|
||||
auto& module() const { return m_module; }
|
||||
auto& locals() const { return m_locals; }
|
||||
auto& expression() const { return m_expression; }
|
||||
auto arity() const { return m_arity; }
|
||||
|
||||
private:
|
||||
const ModuleInstance& m_module;
|
||||
Vector<Value> m_locals;
|
||||
const Expression& m_expression;
|
||||
size_t m_arity { 0 };
|
||||
};
|
||||
|
||||
class Stack {
|
||||
public:
|
||||
using EntryType = Variant<NonnullOwnPtr<Value>, NonnullOwnPtr<Label>, NonnullOwnPtr<Frame>>;
|
||||
Stack() = default;
|
||||
|
||||
[[nodiscard]] bool is_empty() const { return m_data.is_empty(); }
|
||||
void push(EntryType entry) { m_data.append(move(entry)); }
|
||||
auto pop() { return m_data.take_last(); }
|
||||
auto& last() { return m_data.last(); }
|
||||
|
||||
auto size() const { return m_data.size(); }
|
||||
auto& entries() const { return m_data; }
|
||||
|
||||
private:
|
||||
Vector<EntryType> m_data;
|
||||
};
|
||||
|
||||
class AbstractMachine {
|
||||
public:
|
||||
explicit AbstractMachine() = default;
|
||||
|
||||
// Load and instantiate a module, and link it into this interpreter.
|
||||
InstantiationResult instantiate(const Module&, Vector<ExternValue>);
|
||||
Result invoke(FunctionAddress, Vector<Value>);
|
||||
|
||||
auto& module_instance() const { return m_module_instance; }
|
||||
auto& module_instance() { return m_module_instance; }
|
||||
auto& store() const { return m_store; }
|
||||
auto& store() { return m_store; }
|
||||
|
||||
private:
|
||||
InstantiationResult allocate_all(const Module&, Vector<ExternValue>&, Vector<Value>& global_values);
|
||||
ModuleInstance m_module_instance;
|
||||
Store m_store;
|
||||
};
|
||||
|
||||
}
|
77
Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp
Normal file
77
Userland/Libraries/LibWasm/AbstractMachine/Configuration.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWasm/AbstractMachine/Configuration.h>
|
||||
#include <LibWasm/AbstractMachine/Interpreter.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
Optional<Label> Configuration::nth_label(size_t i)
|
||||
{
|
||||
for (auto& entry : m_stack.entries()) {
|
||||
if (auto ptr = entry.get_pointer<NonnullOwnPtr<Label>>()) {
|
||||
if (i == 0)
|
||||
return **ptr;
|
||||
--i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Result Configuration::call(FunctionAddress address, Vector<Value> arguments)
|
||||
{
|
||||
auto* function = m_store.get(address);
|
||||
if (!function)
|
||||
return Trap {};
|
||||
if (auto* wasm_function = function->get_pointer<WasmFunction>()) {
|
||||
Vector<Value> locals;
|
||||
locals.ensure_capacity(arguments.size() + wasm_function->code().locals().size());
|
||||
for (auto& value : arguments)
|
||||
locals.append(Value { value });
|
||||
for (auto& type : wasm_function->code().locals())
|
||||
locals.empend(type, 0ull);
|
||||
|
||||
auto frame = make<Frame>(
|
||||
wasm_function->module(),
|
||||
move(locals),
|
||||
wasm_function->code().body(),
|
||||
wasm_function->type().results().size());
|
||||
|
||||
set_frame(move(frame));
|
||||
return execute();
|
||||
}
|
||||
|
||||
// It better be a host function, else something is really wrong.
|
||||
auto& host_function = function->get<HostFunction>();
|
||||
auto result = bit_cast<HostFunctionType>(host_function.ptr())(m_store, arguments);
|
||||
auto count = host_function.type().results().size();
|
||||
if (count == 0)
|
||||
return Result { Vector<Value> {} };
|
||||
if (count == 1)
|
||||
return Result { Vector<Value> { Value { host_function.type().results().first(), result } } };
|
||||
TODO();
|
||||
}
|
||||
|
||||
Result Configuration::execute()
|
||||
{
|
||||
Interpreter interpreter;
|
||||
interpreter.interpret(*this);
|
||||
|
||||
Vector<NonnullOwnPtr<Value>> results;
|
||||
for (size_t i = 0; i < m_current_frame->arity(); ++i)
|
||||
results.append(move(stack().pop().get<NonnullOwnPtr<Value>>()));
|
||||
auto label = stack().pop();
|
||||
// ASSERT: label == current frame
|
||||
if (!label.has<NonnullOwnPtr<Label>>())
|
||||
return Trap {};
|
||||
Vector<Value> results_moved;
|
||||
results_moved.ensure_capacity(results.size());
|
||||
for (auto& entry : results)
|
||||
results_moved.unchecked_append(move(*entry));
|
||||
return Result { move(results_moved) };
|
||||
}
|
||||
|
||||
}
|
49
Userland/Libraries/LibWasm/AbstractMachine/Configuration.h
Normal file
49
Userland/Libraries/LibWasm/AbstractMachine/Configuration.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
typedef u64 (*HostFunctionType)(Store&, Vector<Value>&);
|
||||
|
||||
class Configuration {
|
||||
public:
|
||||
explicit Configuration(Store& store)
|
||||
: m_store(store)
|
||||
{
|
||||
}
|
||||
|
||||
Optional<Label> nth_label(size_t);
|
||||
void set_frame(NonnullOwnPtr<Frame> frame)
|
||||
{
|
||||
m_current_frame = frame.ptr();
|
||||
m_stack.push(move(frame));
|
||||
m_stack.push(make<Label>(m_current_frame->expression().instructions().size() - 1));
|
||||
}
|
||||
auto& frame() const { return m_current_frame; }
|
||||
auto& frame() { return m_current_frame; }
|
||||
auto& ip() const { return m_ip; }
|
||||
auto& ip() { return m_ip; }
|
||||
auto& depth() const { return m_depth; }
|
||||
auto& depth() { return m_depth; }
|
||||
auto& stack() const { return m_stack; }
|
||||
auto& stack() { return m_stack; }
|
||||
|
||||
Result call(FunctionAddress, Vector<Value> arguments);
|
||||
Result execute();
|
||||
|
||||
private:
|
||||
Store& m_store;
|
||||
Frame* m_current_frame { nullptr };
|
||||
Stack m_stack;
|
||||
size_t m_depth { 0 };
|
||||
InstructionPointer m_ip;
|
||||
};
|
||||
|
||||
}
|
23
Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp
Normal file
23
Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
||||
#include <LibWasm/AbstractMachine/Configuration.h>
|
||||
#include <LibWasm/AbstractMachine/Interpreter.h>
|
||||
#include <LibWasm/Opcode.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
void Interpreter::interpret(Configuration& configuration)
|
||||
{
|
||||
// FIXME: Interpret stuff
|
||||
dbgln("FIXME: Interpret stuff!");
|
||||
// Push some dummy values
|
||||
for (size_t i = 0; i < configuration.frame()->arity(); ++i)
|
||||
configuration.stack().push(make<Value>(0));
|
||||
}
|
||||
|
||||
}
|
17
Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h
Normal file
17
Userland/Libraries/LibWasm/AbstractMachine/Interpreter.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWasm/AbstractMachine/Configuration.h>
|
||||
|
||||
namespace Wasm {
|
||||
|
||||
struct Interpreter {
|
||||
void interpret(Configuration&);
|
||||
};
|
||||
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
set(SOURCES
|
||||
AbstractMachine/AbstractMachine.cpp
|
||||
AbstractMachine/Configuration.cpp
|
||||
AbstractMachine/Interpreter.cpp
|
||||
Parser/Parser.cpp
|
||||
Printer/Printer.cpp
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/FileStream.h>
|
||||
#include <LibWasm/AbstractMachine/AbstractMachine.h>
|
||||
#include <LibWasm/Printer/Printer.h>
|
||||
#include <LibWasm/Types.h>
|
||||
|
||||
|
@ -14,12 +15,19 @@ int main(int argc, char* argv[])
|
|||
{
|
||||
const char* filename = nullptr;
|
||||
bool print = false;
|
||||
bool attempt_instantiate = false;
|
||||
bool attemp_execute = false;
|
||||
|
||||
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(attemp_execute, "Attempt to execute a function from the module (implies -i)", "execute", 'e');
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (attemp_execute)
|
||||
attempt_instantiate = true;
|
||||
|
||||
auto result = Core::File::open(filename, Core::OpenMode::ReadOnly);
|
||||
if (result.is_error()) {
|
||||
warnln("Failed to open {}: {}", filename, result.error());
|
||||
|
@ -34,11 +42,85 @@ int main(int argc, char* argv[])
|
|||
return 2;
|
||||
}
|
||||
|
||||
if (print) {
|
||||
if (print && !attempt_instantiate) {
|
||||
auto out_stream = Core::OutputFileStream::standard_output();
|
||||
Wasm::Printer printer(out_stream);
|
||||
printer.print(parse_result.value());
|
||||
}
|
||||
|
||||
if (attempt_instantiate) {
|
||||
Wasm::AbstractMachine machine;
|
||||
auto result = machine.instantiate(parse_result.value(), {});
|
||||
if (result.is_error()) {
|
||||
warnln("Module instantiation failed: {}", result.error().error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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 : machine.module_instance().functions()) {
|
||||
print_func(address);
|
||||
}
|
||||
}
|
||||
|
||||
if (attemp_execute) {
|
||||
Optional<Wasm::FunctionAddress> run_address;
|
||||
Vector<Wasm::Value> values;
|
||||
// Pick a function that takes no args :P
|
||||
for (auto& address : machine.module_instance().functions()) {
|
||||
auto fn = machine.store().get(address);
|
||||
if (!fn)
|
||||
continue;
|
||||
if (auto ptr = fn->get_pointer<Wasm::WasmFunction>()) {
|
||||
const Wasm::FunctionType& ty = ptr->type();
|
||||
for (auto& param : ty.parameters()) {
|
||||
values.append(Wasm::Value { param, 0ull });
|
||||
}
|
||||
run_address = address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!run_address.has_value()) {
|
||||
warnln("No nullary function, sorry :(");
|
||||
return 1;
|
||||
}
|
||||
outln("Executing ");
|
||||
print_func(*run_address);
|
||||
outln();
|
||||
|
||||
auto result = machine.invoke(run_address.value(), move(values));
|
||||
if (!result.values().is_empty())
|
||||
warnln("Returned:");
|
||||
for (auto& value : result.values()) {
|
||||
value.value().visit(
|
||||
[&](const auto& value) {
|
||||
if constexpr (requires { value.value(); })
|
||||
out(" -> addr{} ", value.value());
|
||||
else
|
||||
out(" -> {} ", value);
|
||||
});
|
||||
Wasm::Printer printer { stream };
|
||||
printer.print(value.type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue