mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 10:56:30 +00:00
runtime almost works
This commit is contained in:
parent
1e2406efce
commit
99087acd13
@ -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);
|
||||
//}
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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);
|
||||
|
121
runtime/main.cpp
121
runtime/main.cpp
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
421
runtime/vm.cpp
421
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<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;
|
||||
}
|
||||
|
155
runtime/vm.hpp
155
runtime/vm.hpp
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user