/* * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include namespace Wasm { #define TRAP_IF_NOT(x) \ do { \ if (trap_if_not(x)) { \ dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ return; \ } \ } while (false) #define TRAP_IF_NOT_NORETURN(x) \ do { \ if (trap_if_not(x)) { \ dbgln_if(WASM_TRACE_DEBUG, "Trapped because {} failed, at line {}", #x, __LINE__); \ } \ } while (false) 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()]; auto old_ip = current_ip_value; interpret(configuration, current_ip_value, instruction); if (m_do_trap) return; if (current_ip_value == old_ip) // If no jump occurred ++current_ip_value; } } void Interpreter::branch_to_label(Configuration& configuration, LabelIndex index) { dbgln_if(WASM_TRACE_DEBUG, "Branch to label with index {}...", index.value()); auto label = configuration.nth_label(index.value()); TRAP_IF_NOT(label.has_value()); dbgln_if(WASM_TRACE_DEBUG, "...which is actually IP {}, and has {} result(s)", label->continuation().value(), label->arity()); auto results = pop_values(configuration, label->arity()); size_t drop_count = index.value() + 1; for (; !configuration.stack().is_empty();) { auto& entry = configuration.stack().peek(); if (entry.has>()) { if (drop_count-- == 0) break; } configuration.stack().pop(); } for (auto& result : results) configuration.stack().push(move(result)); configuration.ip() = label->continuation(); } 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); if (!memory) { m_do_trap = true; return {}; } auto& arg = instruction.arguments().get(); auto base = configuration.stack().pop().get>()->to(); if (!base.has_value()) { m_do_trap = true; return {}; } auto instance_address = base.value() + static_cast(arg.offset); if (instance_address < 0 || static_cast(instance_address + size) > memory->size()) { m_do_trap = true; 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); TRAP_IF_NOT(memory); auto& arg = instruction.arguments().get(); auto base = configuration.stack().pop().get>()->to(); TRAP_IF_NOT(base.has_value()); auto instance_address = base.value() + static_cast(arg.offset); if (instance_address < 0 || static_cast(instance_address + data.size()) > memory->size()) { m_do_trap = true; 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); TRAP_IF_NOT(instance); const FunctionType* type { nullptr }; instance->visit([&](const auto& function) { type = &function.type(); }); TRAP_IF_NOT(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()) { m_do_trap = true; return; } for (auto& entry : result.values()) configuration.stack().push(make(move(entry))); } #define BINARY_NUMERIC_OPERATION(type, operator, cast, ...) \ do { \ auto rhs = configuration.stack().pop().get>()->to(); \ auto lhs = configuration.stack().pop().get>()->to(); \ TRAP_IF_NOT(lhs.has_value()); \ TRAP_IF_NOT(rhs.has_value()); \ __VA_ARGS__; \ auto result = lhs.value() operator rhs.value(); \ dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", lhs.value(), #operator, rhs.value(), result); \ configuration.stack().push(make(cast(result))); \ return; \ } while (false) #define OVF_CHECKED_BINARY_NUMERIC_OPERATION(type, operator, cast, ...) \ do { \ auto rhs = configuration.stack().pop().get>()->to(); \ auto ulhs = configuration.stack().pop().get>()->to(); \ TRAP_IF_NOT(ulhs.has_value()); \ TRAP_IF_NOT(rhs.has_value()); \ dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = ??", ulhs.value(), #operator, rhs.value()); \ __VA_ARGS__; \ Checked lhs = ulhs.value(); \ lhs operator##= rhs.value(); \ TRAP_IF_NOT(!lhs.has_overflow()); \ auto result = lhs.value(); \ dbgln_if(WASM_TRACE_DEBUG, "{} {} {} = {}", ulhs.value(), #operator, rhs.value(), result); \ configuration.stack().push(make(cast(result))); \ return; \ } while (false) #define BINARY_PREFIX_NUMERIC_OPERATION(type, operation, cast, ...) \ do { \ auto rhs = configuration.stack().pop().get>()->to(); \ auto lhs = configuration.stack().pop().get>()->to(); \ TRAP_IF_NOT(lhs.has_value()); \ TRAP_IF_NOT(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(cast(result))); \ return; \ } while (false) #define UNARY_MAP(pop_type, operation, ...) \ do { \ auto value = configuration.stack().pop().get>()->to(); \ TRAP_IF_NOT(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)); \ TRAP_IF_NOT(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); } }; Vector> Interpreter::pop_values(Configuration& configuration, size_t count) { Vector> results; for (size_t i = 0; i < count; ++i) { auto top_of_stack = configuration.stack().pop(); if (auto value = top_of_stack.get_pointer>()) results.prepend(move(*value)); else TRAP_IF_NOT_NORETURN(value); } return results; } 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(): m_do_trap = true; return; 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