From 99087acd130f0d5c4576aca4a884f01d593e3eec Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 19 Nov 2024 05:06:27 +0100 Subject: [PATCH] runtime almost works --- compiler/main.ts | 25 ++- runtime/Makefile | 4 +- runtime/json.hpp | 47 +++++- runtime/main.cpp | 121 ++++++++++++- runtime/value.hpp | 168 ++++++++++++++++-- runtime/vm.cpp | 421 ++++++++++++++++++++++++---------------------- runtime/vm.hpp | 155 +++++++++++++++-- 7 files changed, 696 insertions(+), 245 deletions(-) diff --git a/compiler/main.ts b/compiler/main.ts index 9d92784..37bd9db 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -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); +//} diff --git a/runtime/Makefile b/runtime/Makefile index b77d26e..6be4c0c 100644 --- a/runtime/Makefile +++ b/runtime/Makefile @@ -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)) diff --git a/runtime/json.hpp b/runtime/json.hpp index 1d543cb..8789356 100644 --- a/runtime/json.hpp +++ b/runtime/json.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include 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>; private: - inline auto unexpected_tok_err(TokTyp expected, std::string_view msg) - -> Res> + inline auto unexpected_tok_err( + TokTyp expected, std::string_view msg) -> Res> { return Err { .pos = this->cur.val().pos, @@ -317,14 +319,47 @@ private: Res 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 + 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 auto& value) -> std::string +{ + auto writer = Writer(); + value.to_json(writer); + return writer.done(); +} + template - requires std::derived_from + requires std::derived_from auto from_json(std::string_view json_string) -> T { return T::from_json(json_string); diff --git a/runtime/main.cpp b/runtime/main.cpp index 1eec9af..98843d2 100644 --- a/runtime/main.cpp +++ b/runtime/main.cpp @@ -1,8 +1,127 @@ +#include "arch.hpp" +#include "vm.hpp" #include #include +#include +#include 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 { + 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(); } diff --git a/runtime/value.hpp b/runtime/value.hpp index 33b60a3..59cf55c 100644 --- a/runtime/value.hpp +++ b/runtime/value.hpp @@ -1,7 +1,10 @@ #pragma once #include +#include +#include #include +#include #include 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(value); } - inline auto as_null() const -> const Null& { return std::get(value); } - - inline auto as_int() -> Int& { return std::get(value); } - inline auto as_int() const -> const Int& { return std::get(value); } - - inline auto as_bool() -> Bool& { return std::get(value); } - inline auto as_bool() const -> const Bool& { return std::get(value); } - - inline auto as_string() -> String& { return std::get(value); } - inline auto as_string() const -> const String& + inline auto as_null() -> Null& { - return std::get(value); + // + try { + return std::get(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(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(value); } - inline auto as_ptr() const -> const Ptr& { return std::get(value); } + inline auto as_int() -> Int& + { + // + try { + return std::get(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(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(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(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(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(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(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(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; diff --git a/runtime/vm.cpp b/runtime/vm.cpp index e67d579..01ed15d 100644 --- a/runtime/vm.cpp +++ b/runtime/vm.cpp @@ -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(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(); - 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(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(); + 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(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; +} diff --git a/runtime/vm.hpp b/runtime/vm.hpp index 87314ad..86964ba 100644 --- a/runtime/vm.hpp +++ b/runtime/vm.hpp @@ -1,11 +1,13 @@ #pragma once #include "arch.hpp" +#include "json.hpp" #include "value.hpp" #include #include #include #include +#include #include 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 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 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 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 + { + 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 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 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 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& + { + 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 stack; std::vector pool_heap; SourcePos current_pos = { 0, 1, 1 }; + int64_t instruction_counter = 0; + + bool halt = false; + + FlameGraphBuilder flame_graph; + CodeCoverageBuilder code_coverage; }; }