diff --git a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h index badd342289..dc52dc98da 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h +++ b/Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h @@ -141,6 +141,7 @@ public: } auto& values() const { return m_values; } + auto& values() { return m_values; } auto is_trap() const { return m_is_trap; } private: @@ -314,6 +315,11 @@ public: auto is_mutable() const { return m_mutable; } auto& value() const { return m_value; } + void set_value(Value value) + { + VERIFY(is_mutable()); + m_value = move(value); + } private: bool m_mutable { false }; @@ -391,7 +397,7 @@ public: [[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& peek() const { return m_data.last(); } auto size() const { return m_data.size(); } auto& entries() const { return m_data; } diff --git a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp index 57652cb68a..1dbf4c9db3 100644 --- a/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp +++ b/Userland/Libraries/LibWasm/AbstractMachine/Interpreter.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace Wasm { @@ -67,9 +68,168 @@ ReadonlyBytes Interpreter::load_from_memory(Configuration& configuration, const dbgln("LibWasm: Memory access out of bounds (expected 0 > {} and {} > {})", instance_address, instance_address + size, memory->size()); return {}; } + dbgln_if(WASM_TRACE_DEBUG, "load({} : {}) -> stack", instance_address, size); return memory->data().bytes().slice(instance_address, size); } +void Interpreter::store_to_memory(Configuration& configuration, const Instruction& instruction, ReadonlyBytes data) +{ + auto& address = configuration.frame()->module().memories().first(); + auto memory = configuration.store().get(address); + VERIFY(memory); + auto& arg = instruction.arguments().get(); + auto base = configuration.stack().pop().get>()->to(); + VERIFY(base.has_value()); + auto instance_address = base.value() + static_cast(arg.offset); + if (instance_address < 0 || static_cast(instance_address + data.size()) > memory->size()) { + dbgln("LibWasm: Memory access out of bounds (expected 0 > {} and {} > {})", instance_address, instance_address + data.size(), memory->size()); + return; + } + dbgln_if(WASM_TRACE_DEBUG, "tempoaray({}b) -> store({})", data.size(), instance_address); + data.copy_to(memory->data().bytes().slice(instance_address, data.size())); +} + +void Interpreter::call_address(Configuration& configuration, FunctionAddress address) +{ + auto instance = configuration.store().get(address); + VERIFY(instance); + const FunctionType* type { nullptr }; + instance->visit([&](const auto& function) { type = &function.type(); }); + VERIFY(type); + Vector args; + args.ensure_capacity(type->parameters().size()); + for (size_t i = 0; i < type->parameters().size(); ++i) { + args.prepend(move(*configuration.stack().pop().get>())); + } + Configuration function_configuration { configuration.store() }; + function_configuration.depth() = configuration.depth() + 1; + auto result = function_configuration.call(address, move(args)); + if (result.is_trap()) + TODO(); + for (auto& entry : result.values()) + configuration.stack().push(make(move(entry))); +} + +#define BINARY_NUMERIC_OPERATION(type, operator, ...) \ + do { \ + auto rhs = configuration.stack().pop().get>()->to(); \ + auto lhs = configuration.stack().pop().get>()->to(); \ + VERIFY(lhs.has_value()); \ + VERIFY(rhs.has_value()); \ + auto result = lhs.value() operator rhs.value(); \ + dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", lhs.value(), #operator, rhs.value(), result); \ + configuration.stack().push(make(__VA_ARGS__(result))); \ + return; \ + } while (false) + +#define BINARY_PREFIX_NUMERIC_OPERATION(type, operation, ...) \ + do { \ + auto rhs = configuration.stack().pop().get>()->to(); \ + auto lhs = configuration.stack().pop().get>()->to(); \ + VERIFY(lhs.has_value()); \ + VERIFY(rhs.has_value()); \ + auto result = operation(lhs.value(), rhs.value()); \ + dbgln_if(WASM_TRACE_DEBUG, "{}({} {}) = {}", #operation, lhs.value(), rhs.value(), result); \ + configuration.stack().push(make(__VA_ARGS__(result))); \ + return; \ + } while (false) + +#define UNARY_MAP(pop_type, operation, ...) \ + do { \ + auto value = configuration.stack().pop().get>()->to(); \ + VERIFY(value.has_value()); \ + auto result = operation(value.value()); \ + dbgln_if(WASM_TRACE_DEBUG, "map({}) {} = {}", #operation, value.value(), result); \ + configuration.stack().push(make(__VA_ARGS__(result))); \ + return; \ + } while (false) + +#define UNARY_NUMERIC_OPERATION(type, operation) \ + UNARY_MAP(type, operation, type) + +#define LOAD_AND_PUSH(read_type, push_type) \ + do { \ + auto slice = load_from_memory(configuration, instruction, sizeof(read_type)); \ + VERIFY(slice.size() == sizeof(read_type)); \ + if constexpr (sizeof(read_type) == 1) \ + configuration.stack().push(make(static_cast(slice[0]))); \ + else \ + configuration.stack().push(make(read_value(slice))); \ + return; \ + } while (false) + +#define POP_AND_STORE(pop_type, store_type) \ + do { \ + auto value = ConvertToRaw {}(*configuration.stack().pop().get>()->to()); \ + dbgln_if(WASM_TRACE_DEBUG, "stack({}) -> temporary({}b)", value, sizeof(store_type)); \ + store_to_memory(configuration, instruction, { &value, sizeof(store_type) }); \ + return; \ + } while (false) + +template +static T read_value(ReadonlyBytes data) +{ + T value; + InputMemoryStream stream { data }; + auto ok = IsSigned ? LEB128::read_signed(stream, value) : LEB128::read_unsigned(stream, value); + VERIFY(ok); + return value; +} + +template<> +float read_value(ReadonlyBytes data) +{ + InputMemoryStream stream { data }; + LittleEndian raw_value; + stream >> raw_value; + VERIFY(!stream.has_any_error()); + return bit_cast(static_cast(raw_value)); +} + +template<> +double read_value(ReadonlyBytes data) +{ + InputMemoryStream stream { data }; + LittleEndian raw_value; + stream >> raw_value; + VERIFY(!stream.has_any_error()); + return bit_cast(static_cast(raw_value)); +} + +template +struct ConvertToRaw { + T operator()(T value) + { + return value; + } +}; + +template<> +struct ConvertToRaw { + u32 operator()(float value) + { + LittleEndian res; + ReadonlyBytes bytes { &value, sizeof(float) }; + InputMemoryStream stream { bytes }; + stream >> res; + VERIFY(!stream.has_any_error()); + return static_cast(res); + } +}; + +template<> +struct ConvertToRaw { + u64 operator()(double value) + { + LittleEndian res; + ReadonlyBytes bytes { &value, sizeof(double) }; + InputMemoryStream stream { bytes }; + stream >> res; + VERIFY(!stream.has_any_error()); + return static_cast(res); + } +}; + void Interpreter::interpret(Configuration& configuration, InstructionPointer& ip, const Instruction& instruction) { dbgln_if(WASM_TRACE_DEBUG, "Executing instruction {} at ip {}", instruction_name(instruction.opcode()), ip.value()); @@ -164,7 +324,38 @@ void Interpreter::interpret(Configuration& configuration, InstructionPointer& ip configuration.ip() = label->continuation(); return; } + case Instructions::return_.value(): { + Vector results; + auto& frame = *configuration.frame(); + results.ensure_capacity(frame.arity()); + for (size_t i = 0; i < frame.arity(); ++i) + results.prepend(configuration.stack().pop()); + // drop all locals + OwnPtr