#include "vm.hpp" #include "arch.hpp" #include #include #include #include #include #include #include using namespace sliger; inline auto maybe_op_to_string(uint32_t value) -> std::string { switch (static_cast(value)) { /* clang-format off */ case Op::Nop: return "Nop"; case Op::PushNull: return "PushNull"; case Op::PushInt: return "PushInt"; case Op::PushBool: return "PushBool"; case Op::PushString: return "PushString"; case Op::PushPtr: return "PushPtr"; case Op::Pop: return "Pop"; case Op::ReserveStatic: return "ReserveStatic"; case Op::LoadStatic: return "LoadStatic"; case Op::StoreStatic: return "StoreStatic"; case Op::LoadLocal: return "LoadLocal"; case Op::StoreLocal: return "StoreLocal"; case Op::Call: return "Call"; case Op::Return: return "Return"; case Op::Jump: return "Jump"; case Op::JumpIfTrue: return "JumpIfTrue"; case Op::Builtin: return "Builtin"; case Op::Add: return "Add"; case Op::Subtract: return "Subtract"; case Op::Multiply: return "Multiply"; case Op::Divide: return "Divide"; case Op::Remainder: return "Remainder"; case Op::Equal: return "Equal"; case Op::LessThan: return "LessThan"; case Op::And: return "And"; case Op::Or: return "Or"; case Op::Xor: return "Xor"; case Op::Not: return "Not"; case Op::SourceMap: return "SourceMap"; /* clang-format on */ default: return std::to_string(value); } } void VM::run_until_done() { while (!done()) { run_instruction(); } this->flame_graph.calculate_midway_result(this->instruction_counter); } void VM::run_n_instructions(size_t amount) { for (size_t i = 0; !done() and i < amount; ++i) { run_instruction(); } this->flame_graph.calculate_midway_result(this->instruction_counter); } void VM::run_instruction() { std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc, maybe_op_to_string(this->program[this->pc]), stack_repr_string(8)); auto op = eat_op(); switch (op) { case Op::Nop: // nothing break; case Op::PushNull: this->stack.push_back(Null {}); break; case Op::PushInt: { assert_program_has(1); auto value = eat_int32(); this->stack.push_back(Int { value }); break; } case Op::PushBool: { assert_program_has(1); auto value = eat_int32(); this->stack.push_back(Bool { .value = value != 0 }); break; } case Op::PushString: { assert_program_has(1); auto string_length = eat_uint32(); assert_program_has(string_length); auto value = std::string(); for (uint32_t i = 0; i < string_length; ++i) { auto ch = eat_uint32(); value.push_back(static_cast(ch)); } stack_push(String { .value = std::move(value) }); break; } case Op::PushPtr: { assert_program_has(1); auto value = eat_uint32(); this->stack.push_back(Ptr { value }); break; } case Op::Pop: { assert_stack_has(1); this->stack.pop_back(); break; } case Op::ReserveStatic: { assert_program_has(1); auto value = eat_uint32(); this->statics.reserve(value); break; } case Op::LoadStatic: { assert_program_has(1); auto loc = eat_uint32(); auto value = this->statics.at(loc); stack_push(value); break; } case Op::StoreStatic: { assert_program_has(1); auto loc = eat_uint32(); auto value = stack_pop(); this->statics.at(loc) = value; break; } case Op::LoadLocal: { assert_program_has(1); auto loc = eat_uint32(); assert_fn_stack_has(loc); auto value = fn_stack_at(loc); stack_push(value); break; } case Op::StoreLocal: { assert_program_has(1); auto loc = eat_uint32(); assert_fn_stack_has(loc + 1); auto value = stack_pop(); fn_stack_at(loc) = value; break; } case Op::Call: { assert_program_has(1); auto arg_count = eat_uint32(); assert_stack_has(arg_count + 1); auto fn_ptr = stack_pop(); auto arguments = std::vector(); for (uint32_t i = 0; i < arg_count; ++i) { arguments.push_back(stack_pop()); } stack_push(Ptr { .value = this->pc }); stack_push(Ptr { .value = this->bp }); this->pc = fn_ptr.as_ptr().value; this->bp = static_cast(this->stack.size()); for (size_t i = arguments.size(); i > 0; --i) { stack_push(std::move(arguments.at(i - 1))); } if (this->opts.flame_graph) { this->flame_graph.report_call( fn_ptr.as_ptr().value, this->instruction_counter); } break; } case Op::Return: { assert_stack_has(3); auto ret_val = stack_pop(); while (this->stack.size() > this->bp) { stack_pop(); } auto bp_val = stack_pop(); auto pc_val = stack_pop(); this->bp = bp_val.as_ptr().value; stack_push(ret_val); this->pc = pc_val.as_ptr().value; if (this->opts.flame_graph) { this->flame_graph.report_return(this->instruction_counter); } break; } case Op::Jump: { assert_stack_has(1); auto addr = stack_pop(); this->pc = addr.as_ptr().value; break; } case Op::JumpIfTrue: { assert_stack_has(2); auto addr = stack_pop(); auto cond = stack_pop(); if (cond.as_bool().value) { this->pc = addr.as_ptr().value; } break; } case Op::Builtin: { assert_program_has(1); auto builtin_id = eat_uint32(); run_builtin(static_cast(builtin_id)); break; } case Op::Add: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left + right; stack_push(Int { .value = value }); break; } case Op::Subtract: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left - right; stack_push(Int { .value = value }); break; } case Op::Multiply: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left * right; stack_push(Int { .value = value }); break; } case Op::Divide: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left / right; stack_push(Int { .value = value }); break; } case Op::Remainder: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left % right; stack_push(Int { .value = value }); break; } case Op::Equal: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left == right; stack_push(Bool { .value = value }); break; } case Op::LessThan: { assert_stack_has(2); auto right = stack_pop().as_int().value; auto left = stack_pop().as_int().value; auto value = left < right; stack_push(Bool { .value = value }); break; } case Op::And: { assert_stack_has(2); auto right = stack_pop().as_bool().value; auto left = stack_pop().as_bool().value; auto value = left && right; stack_push(Bool { .value = value }); break; } case Op::Or: { assert_stack_has(2); auto right = stack_pop().as_bool().value; auto left = stack_pop().as_bool().value; auto value = left || right; stack_push(Bool { .value = value }); break; } case Op::Xor: { assert_stack_has(2); auto right = stack_pop().as_bool().value; auto left = stack_pop().as_bool().value; auto value = (left || !right) || (!left && right); stack_push(Bool { .value = value }); break; } case Op::Not: { assert_stack_has(1); auto value = !stack_pop().as_bool().value; stack_push(Bool { .value = value }); break; } case Op::SourceMap: { assert_program_has(3); auto index = eat_int32(); auto line = eat_int32(); auto col = eat_int32(); if (opts.code_coverage) { this->code_coverage.report_cover(this->current_pos); } this->current_pos = { index, line, col }; break; } } this->instruction_counter += 1; } void VM::run_builtin(Builtin builtin_id) { switch (builtin_id) { case Builtin::StringConcat: { assert_stack_has(2); auto left = stack_pop(); auto right = stack_pop(); stack_push( String(right.as_string().value + left.as_string().value)); break; } case Builtin::StringEqual: { assert_stack_has(2); auto left = stack_pop(); auto right = stack_pop(); stack_push(Bool(right.as_string().value == left.as_string().value)); break; } case Builtin::ArraySet: { assert_stack_has(2); std::cerr << std::format("not implemented\n"); std::exit(1); break; } case Builtin::StructSet: { assert_stack_has(2); std::cerr << std::format("not implemented\n"); std::exit(1); break; } } }