/* * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Wasm { void Interpreter::interpret(Configuration& configuration) { auto& instructions = configuration.frame()->expression().instructions(); auto max_ip_value = InstructionPointer { instructions.size() }; auto& current_ip_value = configuration.ip(); while (current_ip_value < max_ip_value) { auto& instruction = instructions[current_ip_value.value()]; interpret(configuration, current_ip_value, instruction); ++current_ip_value; } } void Interpreter::branch_to_label(Configuration& configuration, LabelIndex index) { auto label = configuration.nth_label(index.value()); VERIFY(label.has_value()); NonnullOwnPtrVector results; // Pop results in order for (size_t i = 0; i < label->arity(); ++i) results.append(move(configuration.stack().pop().get>())); size_t drop_count = index.value() + 1; if (label->continuation() < configuration.ip()) --drop_count; for (; !configuration.stack().is_empty();) { auto entry = configuration.stack().pop(); if (entry.has>()) { if (drop_count-- == 0) break; } } // Push results in reverse for (size_t i = results.size(); i > 0; --i) configuration.stack().push(move(static_cast>&>(results)[i - 1])); configuration.ip() = label->continuation() + 1; } ReadonlyBytes Interpreter::load_from_memory(Configuration& configuration, const Instruction& instruction, size_t size) { 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 + size) > memory->size()) { 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()); if constexpr (WASM_TRACE_DEBUG) configuration.dump_stack(); switch (instruction.opcode().value()) { case Instructions::unreachable.value(): VERIFY_NOT_REACHED(); // FIXME: This is definitely not right :) case Instructions::nop.value(): return; case Instructions::local_get.value(): configuration.stack().push(make(configuration.frame()->locals()[instruction.arguments().get().value()])); return; case Instructions::local_set.value(): { auto entry = configuration.stack().pop(); configuration.frame()->locals()[instruction.arguments().get().value()] = move(*entry.get>()); return; } case Instructions::i32_const.value(): configuration.stack().push(make(ValueType { ValueType::I32 }, static_cast(instruction.arguments().get()))); return; case Instructions::i64_const.value(): configuration.stack().push(make(ValueType { ValueType::I64 }, instruction.arguments().get())); return; case Instructions::f32_const.value(): configuration.stack().push(make(ValueType { ValueType::F32 }, static_cast(instruction.arguments().get()))); return; case Instructions::f64_const.value(): configuration.stack().push(make(ValueType { ValueType::F64 }, instruction.arguments().get())); return; case Instructions::block.value(): { size_t arity = 0; auto& args = instruction.arguments().get(); if (args.block_type.kind() != BlockType::Empty) arity = 1; configuration.stack().push(make