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-20 13:46:19 +00:00
|
|
|
inline auto maybe_op_to_string(uint32_t value) -> std::string
|
|
|
|
{
|
|
|
|
switch (static_cast<Op>(value)) {
|
2024-12-11 11:36:19 +00:00
|
|
|
/* 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 */
|
2024-11-20 13:46:19 +00:00
|
|
|
default:
|
|
|
|
return std::to_string(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
std::cout << std::format(" {:>4}: {:<12}{}\n", this->pc,
|
|
|
|
maybe_op_to_string(this->program[this->pc]), stack_repr_string(8));
|
|
|
|
}
|
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-11-19 04:06:27 +00:00
|
|
|
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();
|
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)
|
|
|
|
{
|
|
|
|
switch (builtin_id) {
|
|
|
|
case Builtin::StringConcat: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto left = stack_pop();
|
2024-12-11 11:36:19 +00:00
|
|
|
auto right = stack_pop();
|
2024-12-10 23:18:51 +00:00
|
|
|
stack_push(
|
|
|
|
String(right.as_string().value + left.as_string().value));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Builtin::StringEqual: {
|
|
|
|
assert_stack_has(2);
|
|
|
|
auto left = stack_pop();
|
2024-12-11 11:36:19 +00:00
|
|
|
auto right = stack_pop();
|
2024-12-10 23:18:51 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-12-11 12:37:26 +00:00
|
|
|
case Builtin::Print: {
|
|
|
|
assert_stack_has(1);
|
|
|
|
auto message = stack_pop().as_string().value;
|
|
|
|
std::cout << message;
|
2024-12-12 09:17:09 +00:00
|
|
|
stack_push(Null());
|
2024-12-11 12:37:26 +00:00
|
|
|
break;
|
|
|
|
}
|
2024-12-10 23:18:51 +00:00
|
|
|
}
|
|
|
|
}
|