slige/runtime/vm.cpp

443 lines
14 KiB
C++
Raw Normal View History

2024-11-08 11:22:42 +00:00
#include "vm.hpp"
#include "arch.hpp"
2024-11-11 14:31:54 +00:00
#include <cstdint>
#include <cstdlib>
2024-11-08 11:22:42 +00:00
#include <format>
#include <iostream>
2024-11-20 13:46:19 +00:00
#include <string>
2024-11-11 14:31:54 +00:00
#include <utility>
#include <vector>
2024-11-08 11:22:42 +00:00
using namespace sliger;
2024-11-19 04:06:27 +00:00
void VM::run_until_done()
2024-11-08 11:22:42 +00:00
{
while (!done()) {
2024-11-19 04:06:27 +00:00
run_instruction();
}
2024-11-21 03:12:07 +00:00
this->flame_graph.calculate_midway_result(this->instruction_counter);
2024-11-19 04:06:27 +00:00
}
void VM::run_n_instructions(size_t amount)
{
for (size_t i = 0; !done() and i < amount; ++i) {
2024-11-20 13:46:19 +00:00
2024-11-19 04:06:27 +00:00
run_instruction();
}
2024-11-21 03:12:07 +00:00
this->flame_graph.calculate_midway_result(this->instruction_counter);
2024-11-19 04:06:27 +00:00
}
void VM::run_instruction()
{
2024-12-12 09:17:09 +00:00
if (this->opts.print_stack_debug) {
2024-12-13 05:09:10 +00:00
// std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,
// maybe_op_to_string(this->program[this->pc]),
// stack_repr_string(8));
auto stack_frame_size = this->stack.size() - this->bp;
2024-12-12 09:17:09 +00:00
std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,
2024-12-13 05:09:10 +00:00
maybe_op_to_string(this->program[this->pc]),
stack_repr_string(stack_frame_size));
2024-12-12 09:17:09 +00:00
}
2024-11-19 04:06:27 +00:00
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<char>(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;
}
2024-12-10 23:18:51 +00:00
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;
}
2024-11-19 04:06:27 +00:00
case Op::LoadLocal: {
2024-12-10 23:18:51 +00:00
assert_program_has(1);
2024-11-19 04:06:27 +00:00
auto loc = eat_uint32();
assert_fn_stack_has(loc);
auto value = fn_stack_at(loc);
stack_push(value);
break;
}
case Op::StoreLocal: {
2024-12-10 23:18:51 +00:00
assert_program_has(1);
2024-11-19 04:06:27 +00:00
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<Value>();
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 });
2024-12-11 11:36:19 +00:00
this->pc = fn_ptr.as_ptr().value;
this->bp = static_cast<uint32_t>(this->stack.size());
2024-12-13 05:09:10 +00:00
for (auto&& arg = arguments.rbegin(); arg != arguments.rend();
2024-12-13 15:11:16 +00:00
++arg) {
2024-12-13 05:09:10 +00:00
stack_push(*arg);
2024-11-19 04:06:27 +00:00
}
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();
2024-12-11 11:36:19 +00:00
while (this->stack.size() > this->bp) {
stack_pop();
}
2024-11-19 04:06:27 +00:00
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;
}
2024-12-10 23:18:51 +00:00
case Op::JumpIfTrue: {
2024-11-19 04:06:27 +00:00
assert_stack_has(2);
auto addr = stack_pop();
auto cond = stack_pop();
2024-12-10 23:18:51 +00:00
if (cond.as_bool().value) {
2024-11-11 14:31:54 +00:00
this->pc = addr.as_ptr().value;
2024-11-18 14:01:24 +00:00
}
2024-11-19 04:06:27 +00:00
break;
}
2024-12-10 23:18:51 +00:00
case Op::Builtin: {
assert_program_has(1);
auto builtin_id = eat_uint32();
run_builtin(static_cast<Builtin>(builtin_id));
break;
}
2024-11-19 04:06:27 +00:00
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;
2024-11-08 11:22:42 +00:00
}
}
2024-11-19 04:06:27 +00:00
this->instruction_counter += 1;
2024-11-08 11:22:42 +00:00
}
2024-12-10 23:18:51 +00:00
void VM::run_builtin(Builtin builtin_id)
{
2024-12-13 05:09:10 +00:00
if (this->opts.print_stack_debug) {
std::cout << std::format(
"Running builtin {}\n", static_cast<uint32_t>(builtin_id));
}
2024-12-10 23:18:51 +00:00
switch (builtin_id) {
2024-12-13 15:03:01 +00:00
case Builtin::IntToString: {
assert_stack_has(1);
auto number = static_cast<int32_t>(stack_pop().as_int().value);
auto str = std::to_string(number);
stack_push(String(str));
break;
}
2024-12-10 23:18:51 +00:00
case Builtin::StringConcat: {
assert_stack_has(2);
2024-12-11 11:36:19 +00:00
auto right = stack_pop();
2024-12-13 05:09:10 +00:00
auto left = stack_pop();
2024-12-10 23:18:51 +00:00
stack_push(
2024-12-13 05:09:10 +00:00
String(left.as_string().value + right.as_string().value));
2024-12-10 23:18:51 +00:00
break;
}
case Builtin::StringEqual: {
assert_stack_has(2);
2024-12-11 11:36:19 +00:00
auto right = stack_pop();
2024-12-13 05:09:10 +00:00
auto left = stack_pop();
stack_push(Bool(left.as_string().value == right.as_string().value));
2024-12-11 12:37:26 +00:00
break;
}
2024-12-12 15:07:59 +00:00
case Builtin::StringCharAt: {
assert_stack_has(2);
auto index_value = stack_pop();
2024-12-13 15:03:01 +00:00
auto string_value = stack_pop();
auto index = static_cast<int32_t>(index_value.as_int().value);
auto string = string_value.as_string();
stack_push(Int(string.at(index)));
2024-12-12 15:07:59 +00:00
break;
}
case Builtin::StringLength: {
assert_stack_has(1);
auto str = stack_pop().as_string().value;
2024-12-13 05:09:10 +00:00
2024-12-12 15:07:59 +00:00
auto length = static_cast<int32_t>(str.length());
stack_push(Int(length));
break;
}
case Builtin::StringPushChar: {
assert_stack_has(2);
auto ch = stack_pop();
2024-12-13 05:09:10 +00:00
auto str = stack_pop();
2024-12-12 15:07:59 +00:00
auto new_str = std::string(str.as_string().value);
new_str.push_back(static_cast<char>(ch.as_int().value));
stack_push(String(new_str));
break;
}
2024-12-13 15:03:01 +00:00
case Builtin::StringToInt: {
assert_stack_has(1);
auto str = stack_pop().as_string().value;
auto number = atoi(str.c_str());
stack_push(Int(number));
break;
}
2024-12-12 15:07:59 +00:00
case Builtin::ArrayNew: {
auto alloc_res = this->heap.alloc<heap::AllocType::Array>();
stack_push(Ptr(alloc_res.val()));
break;
}
2024-12-13 05:09:10 +00:00
case Builtin::ArraySet: {
assert_stack_has(2);
2024-12-13 15:03:01 +00:00
auto index = stack_pop().as_int().value;
auto array_ptr = stack_pop().as_ptr().value;
auto value = stack_pop();
auto array = this->heap.at(array_ptr).val()->as_array();
array.at(index) = value;
stack_push(Null());
2024-12-13 05:09:10 +00:00
break;
}
2024-12-12 15:07:59 +00:00
case Builtin::ArrayPush: {
assert_stack_has(2);
auto value = stack_pop();
2024-12-13 05:09:10 +00:00
auto array_ptr = stack_pop().as_ptr().value;
this->heap.at(array_ptr).val()->as_array().values.push_back(value);
2024-12-12 15:07:59 +00:00
stack_push(Null());
break;
}
case Builtin::ArrayAt: {
assert_stack_has(2);
auto index = stack_pop().as_int().value;
2024-12-13 05:09:10 +00:00
auto array_ptr = stack_pop().as_ptr().value;
2024-12-12 15:07:59 +00:00
auto array = this->heap.at(array_ptr).val()->as_array();
2024-12-13 15:03:01 +00:00
stack_push(array.at(index));
2024-12-12 15:07:59 +00:00
break;
}
case Builtin::ArrayLength: {
assert_stack_has(1);
auto array_ptr = stack_pop().as_ptr().value;
2024-12-13 05:09:10 +00:00
2024-12-12 15:07:59 +00:00
auto array = this->heap.at(array_ptr).val()->as_array();
stack_push(Int(static_cast<int32_t>(array.values.size())));
break;
}
2024-12-13 05:09:10 +00:00
case Builtin::StructSet: {
assert_stack_has(2);
std::cerr << std::format("not implemented\n");
std::exit(1);
break;
}
case Builtin::Print: {
assert_stack_has(1);
auto message = stack_pop().as_string().value;
std::cout << message;
stack_push(Null());
break;
}
2024-12-10 23:18:51 +00:00
}
}
2024-12-13 15:11:16 +00:00
auto VM::stack_repr_string(size_t max_items) const -> std::string
{
auto result = std::string();
result += "";
const auto& stack = view_stack();
for (size_t i = 0; i < stack.size() and i < max_items; ++i) {
if (i != 0) {
result += " ";
}
result += std::format(
"{:<11}", stack[stack.size() - i - 1].to_repr_string());
}
if (stack.size() > max_items) {
result += std::format(" ... + {}", stack.size() - max_items);
}
return result;
};
void VM::assert_fn_stack_has(size_t count)
{
if (this->stack.size() - this->bp < count) {
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
std::exit(1);
}
}
void VM::assert_stack_has(size_t count)
{
if (this->stack.size() < count) {
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
std::exit(1);
}
}
void VM::assert_program_has(size_t count)
{
if (this->pc + count > program_size) {
std::cerr << std::format("malformed program, pc = {}", this->pc);
std::exit(1);
}
}