runtime almost works

This commit is contained in:
sfja 2024-11-19 05:06:27 +01:00
parent 1e2406efce
commit 99087acd13
7 changed files with 696 additions and 245 deletions

View File

@ -1,13 +1,24 @@
import { Lexer } from "./Lexer.ts";
import { readFileSync } from 'node:fs';
import { Parser } from "./Parser.ts";
const text = readFileSync("example-no-types.slg").toString()
//const text = await Deno.readTextFile("example-no-types.slg");
const text = await Deno.readTextFile("example.slg");
const lexer = new Lexer(text);
const parser = new Parser(lexer)
while (!parser.done()) {
const result = parser.parseExpr()
console.log(result)
console.log("type\tindex\tline:col");
let current = lexer.next();
while (current) {
console.log(
`${
current.identValue ?? current.type
}\t${current.pos.index}\t${current.pos.line}:${current.pos.col}`,
);
current = lexer.next();
}
const pos = lexer.currentPos();
console.log(`eof\t${pos.index}\t${pos.line}:${pos.col}`);
//const parser = new Parser(lexer);
//while (!parser.done()) {
// const result = parser.parseExpr();
// console.log(result);
//}

View File

@ -8,9 +8,9 @@ CXX_FLAGS = \
OUT=build/sliger
CXX_HEADERS = $(shell find . -name "*.hpp")
CXX_HEADERS = $(shell find . -name "*.hpp" -type f -printf '%P\n')
CXX_SOURCES = $(shell find . -name "*.cpp")
CXX_SOURCES = $(shell find . -name "*.cpp" -type f -printf '%P\n')
CXX_OBJECTS = $(patsubst %.cpp,build/%.o,$(CXX_SOURCES))

View File

@ -8,6 +8,7 @@
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace sliger::json {
@ -197,6 +198,7 @@ inline auto tok_typ_to_string(TokTyp typ) -> std::string
case TokTyp::Colon:
return "Colon";
}
std::unreachable();
}
struct Tok {
@ -293,8 +295,8 @@ public:
auto parse_val() -> Res<std::unique_ptr<Value>>;
private:
inline auto unexpected_tok_err(TokTyp expected, std::string_view msg)
-> Res<std::unique_ptr<Value>>
inline auto unexpected_tok_err(
TokTyp expected, std::string_view msg) -> Res<std::unique_ptr<Value>>
{
return Err {
.pos = this->cur.val().pos,
@ -317,14 +319,47 @@ private:
Res<Tok> cur;
};
struct Serializable {
virtual ~Serializable() = default;
class Writer {
public:
inline auto operator<<(auto value) -> Writer&
{
this->add(value);
return *this;
}
inline void add(const char* value) { this->data.append(value); }
inline void add(std::string_view value) { this->data.append(value); }
inline void add(const std::string& value) { this->data.append(value); }
inline void add(auto value) { this->data.push_back(value); }
virtual auto to_json() const -> std::string = 0;
template <typename T, typename F>
auto add_comma_seperated(const T& values, F f)
{
for (const auto& value : values) {
f(*this, value);
}
}
inline auto done() -> std::string { return std::move(this->data); }
private:
std::string data;
};
struct ToAndFromJson {
virtual ~ToAndFromJson() = default;
virtual void to_json(Writer& writer) const = 0;
};
auto to_json(const std::derived_from<ToAndFromJson> auto& value) -> std::string
{
auto writer = Writer();
value.to_json(writer);
return writer.done();
}
template <typename T>
requires std::derived_from<T, Serializable>
requires std::derived_from<T, ToAndFromJson>
auto from_json(std::string_view json_string) -> T
{
return T::from_json(json_string);

View File

@ -1,8 +1,127 @@
#include "arch.hpp"
#include "vm.hpp"
#include <format>
#include <iostream>
#include <utility>
#include <variant>
int main()
{
using sliger::Op;
// fn add(a, b) {
// + a b
// }
//
std::cout << std::format("hello world\n");
// let result = 0;
// let i = 0;
// loop {
// if i >= 10 {
// break;
// }
// result = add(result, 5);
// }
auto program = std::vector<uint32_t> {
std::to_underlying(Op::SourceMap),
0,
0,
0,
std::to_underlying(Op::PushPtr),
15, // main
std::to_underlying(Op::Call),
0,
std::to_underlying(Op::Pop),
std::to_underlying(Op::PushPtr),
87, // .l3
std::to_underlying(Op::Jump),
std::to_underlying(Op::Pop),
/*add*/ std::to_underlying(Op::SourceMap),
19,
2,
5,
std::to_underlying(Op::Add),
std::to_underlying(Op::Return),
/*main*/ std::to_underlying(Op::SourceMap),
28,
5,
1,
std::to_underlying(Op::PushInt),
0,
std::to_underlying(Op::SourceMap),
44,
6,
1,
std::to_underlying(Op::PushInt),
0,
std::to_underlying(Op::SourceMap),
55,
7,
1,
/*.l0*/ std::to_underlying(Op::SourceMap),
66,
8,
5,
std::to_underlying(Op::LoadLocal),
1,
std::to_underlying(Op::PushInt),
0,
std::to_underlying(Op::LessThan),
std::to_underlying(Op::Not),
std::to_underlying(Op::PushPtr),
53, // .l1
std::to_underlying(Op::JumpIfFalse),
std::to_underlying(Op::SourceMap),
87,
9,
9,
std::to_underlying(Op::PushPtr),
83, // .l2
std::to_underlying(Op::Jump),
/*.l1*/ std::to_underlying(Op::SourceMap),
104,
11,
5,
std::to_underlying(Op::LoadLocal),
0,
std::to_underlying(Op::PushInt),
5,
std::to_underlying(Op::PushPtr),
9, // add
std::to_underlying(Op::Call),
2,
std::to_underlying(Op::StoreLocal),
0,
std::to_underlying(Op::SourceMap),
133,
12,
5,
std::to_underlying(Op::LoadLocal),
1,
std::to_underlying(Op::PushInt),
1,
std::to_underlying(Op::Add),
std::to_underlying(Op::StoreLocal),
1,
std::to_underlying(Op::PushPtr),
33, // .l0
std::to_underlying(Op::Jump),
/*.l2*/ std::to_underlying(Op::Pop),
std::to_underlying(Op::Pop),
std::to_underlying(Op::PushNull),
std::to_underlying(Op::Return),
/*.l3*/ std::to_underlying(Op::SourceMap),
147,
15,
1,
std::to_underlying(Op::PushInt),
};
auto vm = sliger::VM(program,
{
.flame_graph = true,
.code_coverage = true,
});
vm.run_until_done();
vm.print_stack();
}

View File

@ -1,7 +1,10 @@
#pragma once
#include <cstdint>
#include <format>
#include <iostream>
#include <string>
#include <utility>
#include <variant>
namespace sliger {
@ -14,6 +17,23 @@ enum class ValueType {
Ptr,
};
inline auto value_type_to_string(ValueType type) -> std::string
{
switch (type) {
case ValueType::Null:
return "Null";
case ValueType::Int:
return "Int";
case ValueType::Bool:
return "Bool";
case ValueType::String:
return "String";
case ValueType::Ptr:
return "Ptr";
}
std::unreachable();
}
class Values;
struct Null { };
@ -60,23 +80,143 @@ public:
inline auto type() const -> ValueType { return m_type; };
inline auto as_null() -> Null& { return std::get<Null>(value); }
inline auto as_null() const -> const Null& { return std::get<Null>(value); }
inline auto as_int() -> Int& { return std::get<Int>(value); }
inline auto as_int() const -> const Int& { return std::get<Int>(value); }
inline auto as_bool() -> Bool& { return std::get<Bool>(value); }
inline auto as_bool() const -> const Bool& { return std::get<Bool>(value); }
inline auto as_string() -> String& { return std::get<String>(value); }
inline auto as_string() const -> const String&
inline auto as_null() -> Null&
{
return std::get<String>(value);
//
try {
return std::get<Null>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Null\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_null() const -> const Null&
{
//
try {
return std::get<Null>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Null\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_ptr() -> Ptr& { return std::get<Ptr>(value); }
inline auto as_ptr() const -> const Ptr& { return std::get<Ptr>(value); }
inline auto as_int() -> Int&
{
//
try {
return std::get<Int>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Int\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_int() const -> const Int&
{
//
try {
return std::get<Int>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Int\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_bool() -> Bool&
{
//
try {
return std::get<Bool>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Bool\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_bool() const -> const Bool&
{
//
try {
return std::get<Bool>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Bool\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_string() -> String&
{
//
try {
return std::get<String>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as String\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_string() const -> const String&
{
//
try {
return std::get<String>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as String\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_ptr() -> Ptr&
{
//
try {
return std::get<Ptr>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Ptr\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto as_ptr() const -> const Ptr&
{
//
try {
return std::get<Ptr>(value);
} catch (const std::bad_variant_access& ex) {
std::cout << std::format("tried to unwrap {} as Ptr\n",
value_type_to_string(this->m_type));
throw;
}
}
inline auto to_string() const -> std::string
{
switch (this->m_type) {
case ValueType::Null:
return "null";
case ValueType::Int:
return std::to_string(as_int().value);
case ValueType::Bool:
return as_bool().value ? "true" : "false";
case ValueType::String:
return std::format("\"{}\"", as_string().value);
case ValueType::Ptr:
return std::to_string(as_ptr().value);
}
std::unreachable();
}
inline auto to_repr_string() const -> std::string
{
return std::format(
"{}({})", value_type_to_string(this->m_type), to_string());
}
private:
ValueType m_type;

View File

@ -9,206 +9,229 @@
using namespace sliger;
void VM::run()
void VM::run_until_done()
{
while (!done()) {
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;
}
case Op::LoadLocal: {
auto loc = eat_uint32();
assert_fn_stack_has(loc);
auto value = fn_stack_at(loc);
stack_push(value);
break;
}
case Op::StoreLocal: {
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) {
auto argument = stack_pop();
arguments.push_back(argument);
}
stack_push(Ptr { .value = this->pc });
stack_push(Ptr { .value = this->bp });
for (size_t i = 0; i < arguments.size(); ++i) {
auto argument
= std::move(arguments.at(arguments.size() - 1 - i));
arguments.pop_back();
stack_push(argument);
}
this->pc = fn_ptr.as_ptr().value;
break;
}
case Op::Return: {
assert_stack_has(3);
auto ret_val = 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;
break;
}
case Op::Jump: {
assert_stack_has(1);
auto addr = stack_pop();
this->pc = addr.as_ptr().value;
break;
}
case Op::JumpIfFalse: {
assert_stack_has(2);
auto cond = stack_pop();
auto addr = stack_pop();
if (cond.as_bool().value) {
this->pc = addr.as_ptr().value;
}
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();
this->current_pos = { index, line, col };
break;
}
}
run_instruction();
}
}
void VM::run_n_instructions(size_t amount)
{
for (size_t i = 0; !done() and i < amount; ++i) {
run_instruction();
}
}
void VM::run_instruction()
{
std::cout << "stack:\n";
this->print_stack();
std::cout << std::format("pc = {}\n", this->pc);
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;
}
case Op::LoadLocal: {
auto loc = eat_uint32();
assert_fn_stack_has(loc);
auto value = fn_stack_at(loc);
stack_push(value);
break;
}
case Op::StoreLocal: {
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 });
for (size_t i = arguments.size(); i > 0; --i) {
stack_push(std::move(arguments.at(i - 1)));
}
this->pc = fn_ptr.as_ptr().value;
this->bp = static_cast<uint32_t>(this->stack.size());
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();
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::JumpIfFalse: {
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::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;
}

View File

@ -1,11 +1,13 @@
#pragma once
#include "arch.hpp"
#include "json.hpp"
#include "value.hpp"
#include <cstddef>
#include <cstdint>
#include <format>
#include <iostream>
#include <string>
#include <vector>
namespace sliger {
@ -17,26 +19,113 @@ struct SourcePos {
};
struct FGNode {
FGNode(uint32_t fn, size_t parent)
: fn(fn)
, parent(parent)
{
}
uint32_t fn;
int64_t acc;
int64_t ic_start;
int64_t acc = 0;
int64_t ic_start = 0;
size_t parent;
std::vector<size_t> children;
// the vector's data may be placed all over the heap. this really really
// sucks. expect cachemisses when calling infrequently called functions. we
// might be lucky, that the current function and frequent call paths will be
// cached, but we have no way to assure this.
//
// maybe to fix this, a many-to-many relation table, using one single
// vector could be used. that would reduce cache misses, in exchange for
// table lookups.
std::vector<size_t> children = {};
};
class FlameGraphBuilder {
class FlameGraphBuilder : public json::ToAndFromJson {
public:
inline auto report_call(size_t fn, int64_t ic_start) { }
inline void report_call(uint32_t fn, int64_t ic_start)
{
size_t found = find_or_create_child(fn);
this->nodes[found].ic_start = ic_start;
this->current = found;
}
inline auto report_return(int64_t ic_end) { }
inline void report_return(int64_t ic_end)
{
int64_t diff = ic_end - this->nodes[this->current].ic_start;
this->nodes[this->current].acc += diff;
this->current = this->nodes[this->current].parent;
}
void to_json(json::Writer& writer) const override;
private:
std::vector<FGNode> nodes;
inline auto find_or_create_child(uint32_t fn) -> size_t
{
auto found_child_index = this->find_child(fn);
if (found_child_index.has_value())
return found_child_index.value();
size_t new_child_index = this->nodes.size();
this->nodes.push_back(FGNode(fn, this->current));
this->nodes[this->current].children.push_back(new_child_index);
return new_child_index;
}
inline auto find_child(uint32_t fn) const -> std::optional<size_t>
{
for (auto child_idx : this->nodes[this->current].children) {
if (fn == this->nodes[child_idx].fn) {
return child_idx;
}
}
return {};
}
void fg_node_to_json(json::Writer& writer, size_t node_index) const;
std::vector<FGNode> nodes = { FGNode(0, 0) };
size_t current = 0;
};
struct CCPosEntry {
SourcePos pos;
int64_t covers = 0;
};
class CodeCoverageBuilder : public json::ToAndFromJson {
public:
/// call when leaving a source location
inline void report_cover(SourcePos pos)
{
size_t entry_index = find_or_create_entry(pos);
this->entries[entry_index].covers += 1;
}
void to_json(json::Writer& writer) const override;
private:
inline size_t find_or_create_entry(SourcePos pos)
{
if (auto found_index = find_pos_entry(pos); found_index.has_value())
return found_index.value();
size_t new_index = this->entries.size();
this->entries.push_back({ .pos = pos });
return new_index;
}
inline std::optional<size_t> find_pos_entry(SourcePos pos) const
{
for (size_t i = 0; i < this->entries.size(); ++i)
if (this->entries[i].pos.index == pos.index)
return i;
return {};
}
std::vector<CCPosEntry> entries = {};
};
struct VMOpts {
bool flameGraph;
bool codeCoverage;
bool flame_graph;
bool code_coverage;
};
class VM {
@ -47,8 +136,34 @@ public:
, program_size(program.size())
{
}
void run();
void run_until_done();
void run_n_instructions(size_t amount);
void run_instruction();
inline auto flame_graph_json() const -> std::string
{
return json::to_json(this->flame_graph);
}
inline auto code_coverage_json() const -> std::string
{
return json::to_json(this->code_coverage);
}
inline auto view_stack() const -> const std::vector<Value>&
{
return this->stack;
}
inline void print_stack() const
{
for (const auto& value : view_stack()) {
std::cout << std::format(" {}\n", value.to_repr_string());
}
}
private:
inline void step() { this->pc += 1; }
inline auto eat_op() -> Op
@ -84,7 +199,10 @@ public:
return this->program[this->pc];
}
inline auto done() const -> bool { return this->pc >= this->program_size; }
inline auto done() const -> bool
{
return not this->halt and this->pc >= this->program_size;
}
inline auto fn_stack_at(size_t idx) -> Value&
{
@ -93,14 +211,14 @@ public:
inline void assert_fn_stack_has(size_t count)
{
if (this->stack.size() - this->bp < count) {
std::cerr << std::format("stack underflow");
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
std::exit(1);
}
}
inline void assert_stack_has(size_t count)
{
if (this->stack.size() < count) {
std::cerr << std::format("stack underflow");
std::cerr << std::format("stack underflow, pc = {}\n", this->pc);
std::exit(1);
}
}
@ -115,13 +233,12 @@ public:
inline auto assert_program_has(size_t count)
{
if (this->pc + count >= program_size) {
std::cerr << std::format("stack underflow");
if (this->pc + count > program_size) {
std::cerr << std::format("malformed program, pc = {}", this->pc);
std::exit(1);
}
}
private:
VMOpts opts;
uint32_t pc = 0;
uint32_t bp = 0;
@ -130,6 +247,12 @@ private:
std::vector<Value> stack;
std::vector<Value> pool_heap;
SourcePos current_pos = { 0, 1, 1 };
int64_t instruction_counter = 0;
bool halt = false;
FlameGraphBuilder flame_graph;
CodeCoverageBuilder code_coverage;
};
}