From a13173ea815f671eda132731470307f63189c6b0 Mon Sep 17 00:00:00 2001 From: PyXiion Date: Mon, 9 Mar 2026 16:35:36 +0300 Subject: [PATCH 1/4] Basic VM runtime --- CMakeLists.txt | 2 + include/korka/compiler/compiler.hpp | 93 +++++++++--- include/korka/compiler/lexer.hpp | 6 +- include/korka/compiler/parser.hpp | 6 +- include/korka/shared/error.hpp | 37 ++++- include/korka/utils/byte_reader.hpp | 50 ++++++ include/korka/utils/function_traits.hpp | 24 +++ include/korka/utils/string.hpp | 2 +- include/korka/vm/op_codes.hpp | 7 +- include/korka/vm/vm_runtime.hpp | 193 +++++++++++++++++++++++- main.cpp | 15 +- src/vm/vm_runtime.cpp | 2 +- 12 files changed, 392 insertions(+), 45 deletions(-) create mode 100644 include/korka/utils/byte_reader.hpp create mode 100644 include/korka/utils/function_traits.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e2e624a..3949419 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(korka_lib include/korka/shared/types.hpp include/korka/shared/flat_map.hpp include/korka/utils/frozen_hash_string_view.hpp + include/korka/utils/byte_reader.hpp + include/korka/utils/function_traits.hpp ) target_include_directories(korka_lib diff --git a/include/korka/compiler/compiler.hpp b/include/korka/compiler/compiler.hpp index add5d3a..2decf14 100644 --- a/include/korka/compiler/compiler.hpp +++ b/include/korka/compiler/compiler.hpp @@ -1,6 +1,6 @@ #pragma once -#include "frozen/bits/elsa.h" +#include #include "korka/shared/error.hpp" #include "korka/shared/flat_map.hpp" #include "korka/utils/overloaded.hpp" @@ -55,7 +55,7 @@ namespace korka { std::vector params; type_info return_type; - vm::bytecode_builder::label label; + std::size_t start_pos; }; template @@ -65,7 +65,7 @@ namespace korka { std::array params; type_info return_type; - vm::bytecode_builder::label label; + std::size_t start_pos; }; template @@ -76,8 +76,8 @@ namespace korka { template struct param_helper> { // Map each parameter info to its corresponding C++ type - using type = vm::type_info_to_cpp_t<[]{return info_getter().return_type;}>( - vm::type_info_to_cpp_t<[]{return info_getter().params[Is].type;}>... + using type = vm::type_info_to_cpp_t<[] { return info_getter().return_type; }>( + vm::type_info_to_cpp_t<[] { return info_getter().params[Is].type; }>... ); }; @@ -96,7 +96,7 @@ namespace korka { .param_count = f.params.size(), .params{}, .return_type = f.return_type, - .label{} + .start_pos = f.start_pos }; std::ranges::copy(f.params, std::begin(info.params)); @@ -175,6 +175,18 @@ namespace korka { flat_map functions; }; + + struct function_runtime_info { + std::size_t start_pos; + }; + + template + struct function_runtime_info_with_signature : public function_runtime_info { + using signature_t = Signature; + + using function_runtime_info::start_pos; + }; + template struct const_compilation_result { std::array bytes; @@ -184,34 +196,64 @@ namespace korka { using get_signature_t = typename SignatureMapper::template get_signature_t; template - constexpr auto function() const -> get_signature_t * { - return nullptr; + constexpr auto function() const { + return function_runtime_info_with_signature>{ + functions.at(name).start_pos + }; } - SignatureMapper mapper{}; +// SignatureMapper mapper{}; }; -template -struct unique_type{}; + template + struct unique_type { + }; template struct signature_mapper; template struct signature_mapper> { + consteval static auto hash(auto &&v) -> std::size_t { + return frozen::elsa{}(v, 0); + } + + // That's how we basically map types to strings + // Function name -> hash -> unique_type + // And then we create a functor with overloading for different types + // Basically it looks like this: + // overloaded{ + // [](unique_type) -> TYPE1* { return nullptr; }, + // [](unique_type) -> TYPE2* { return nullptr; }, + // ... + // } + + // I think I could return here some meta info for internal types later, idk + constexpr static auto _overloaded = overloaded{ - ([](unique_type{}(function_info_getter(Is).name, 0)>) - -> const_function_info_to_signature_t<[] {return function_info_getter(Is);}> * - { + ([](unique_type) + -> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * { return nullptr; })... }; - template - using get_signature_t = std::remove_pointer_t{}(name, 0)>{}))>; + // Retrieve the type + template requires ([] -> bool { + constexpr bool found = requires { + { _overloaded(unique_type{}) }; + }; + if constexpr (not found) { + report_error<[] { + return format("Symbol '~' not found", name); + }>(); + } + return true; + }()) + using get_signature_t = std::remove_pointer_t{}))>; - std::tuple *...> debug1; - std::tuple{}(function_info_getter(Is).name, 0)>...> debug2; +// std::tuple *...> debug1; +// std::tuple{}(function_info_getter(Is).name, 0)>...> debug2; }; @@ -240,7 +282,10 @@ struct unique_type{}; return frozen::make_unordered_map(functions_data); }; - using sign_mapper = signature_mapper<[](std::size_t i) { return (functions().begin() + i)->second; }, std::make_index_sequence>; + // Function mapper + using sign_mapper = signature_mapper<[](std::size_t i) { + return (functions().begin() + i)->second; + }, std::make_index_sequence>; return const_compilation_result{ bytes, @@ -309,18 +354,22 @@ struct unique_type{}; // Register the function globally BEFORE processing the body // This allows the function to "see" itself (recursion) + + builder.bind_label(label); + auto func_start_pos = builder.resolve_label(label); + if (not func_start_pos) return std::unexpected{error::other_compiler_error{"Idk"}}; + auto reg_ok = m_symbols.declare_function( function.name, parameters, ret_type, - label + *func_start_pos ); if (not reg_ok) return std::unexpected{reg_ok.error()}; // Entering function scope m_symbols.push_scope(); m_current_func_ret = ret_type; - builder.bind_label(label); // Handle parameters as local variables for (auto &¶m: parameters) { @@ -484,7 +533,7 @@ struct unique_type{}; }; if constexpr (not expected()) { - report_error<[]{return expected().error();}>(); + report_error<[] { return expected().error(); }>(); return expected().error(); } else { return compilation_result_to_const<[] constexpr { return expected().value(); }>(); diff --git a/include/korka/compiler/lexer.hpp b/include/korka/compiler/lexer.hpp index d5dbb5b..f5679c5 100644 --- a/include/korka/compiler/lexer.hpp +++ b/include/korka/compiler/lexer.hpp @@ -271,12 +271,14 @@ namespace korka { template consteval auto lex() { + constexpr static std::string_view code {str}; constexpr static auto expected = [] consteval { - return lexer{static_cast(str)}.lex(); + return lexer{code}.lex(); }; if constexpr (expected()) { - return to_array<[]{return expected().value();}>(); + constexpr auto get = []{return expected().value(); }; + return (to_array)(); } else { report_error(); return expected().error(); diff --git a/include/korka/compiler/parser.hpp b/include/korka/compiler/parser.hpp index e653edd..9aa905f 100644 --- a/include/korka/compiler/parser.hpp +++ b/include/korka/compiler/parser.hpp @@ -524,9 +524,10 @@ namespace korka { } }; - template + template consteval auto parse_tokens() { constexpr static auto expected = []constexpr{ + auto tokens = tokens_getter(); return parser{std::span{tokens}}.parse(); }; @@ -540,7 +541,6 @@ namespace korka { template consteval auto parse() { - constexpr static auto tokens = lex(); - return parse_tokens(); + return parse_tokens<[] { return lex(); }>(); } } \ No newline at end of file diff --git a/include/korka/shared/error.hpp b/include/korka/shared/error.hpp index d70ea90..70d74ae 100644 --- a/include/korka/shared/error.hpp +++ b/include/korka/shared/error.hpp @@ -1,10 +1,16 @@ #pragma once +#include #include #include "korka/compiler/lex_token.hpp" #include "korka/utils/const_format.hpp" +#include "korka/utils/string.hpp" #include +#if __cplusplus >= 202400L && __cpp_static_assert >= 202306L +#define KORKA_FEATURE_FORMATTED_STATIC_ASSERT +#endif + namespace korka { namespace error { struct lexer_context { @@ -121,12 +127,29 @@ namespace korka { template consteval auto report_error() -> void { - // Idk, __cpp_static_assert check is not enough for clang - #if __cplusplus >= 202400L && __cpp_static_assert >= 202306L - static_assert(false, to_string(err_getter())); - #else - constexpr auto msg = const_string_from_string_view<[]{return to_string(err_getter());}>(); - std::ignore = ErrorMessage{}; - #endif + if constexpr (std::convertible_to) { + #ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT + static_assert(false, to_string(err_getter())); + #else + constexpr auto msg = const_string_from_string_view<[]{return to_string(err_getter());}>(); + std::ignore = ErrorMessage{}; + #endif + } else { + #ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT + static_assert(false, err_getter()); + #else + constexpr auto msg = const_string_from_string_view(); + std::ignore = ErrorMessage{}; + #endif + } + } + + template + consteval auto format_static_assert() -> void { + if constexpr (ok) { + // yay + } else { + report_error(); + } } } \ No newline at end of file diff --git a/include/korka/utils/byte_reader.hpp b/include/korka/utils/byte_reader.hpp new file mode 100644 index 0000000..378b97a --- /dev/null +++ b/include/korka/utils/byte_reader.hpp @@ -0,0 +1,50 @@ +// +// Created by pyxiion on 09.03.2026. +// + +#pragma once + +#include "korka/vm/op_codes.hpp" +#include +#include + +namespace korka::vm { + template + concept byte_readable = std::integral or std::is_enum_v; + + class byte_reader { + public: + explicit byte_reader(std::span bytes, std::size_t start_pos = 0) + : m_cursor(start_pos), m_bytes(bytes) { + + } + + template + constexpr auto read() -> T { + std::array b; + read(b); + return std::bit_cast(b); + } + + constexpr auto read(std::span out) -> void { + if (m_cursor + out.size() > m_bytes.size()) { + throw std::out_of_range("byte_reader::read: out of range"); + } + + std::copy(std::begin(m_bytes) + m_cursor, std::begin(m_bytes) + m_cursor + static_cast(out.size()), std::begin(out)); + m_cursor += out.size(); + } + + auto cursor() const noexcept -> std::size_t { + return m_cursor; + } + + auto set_cursor(std::size_t pos) noexcept -> void { + m_cursor = pos; + } + + private: + std::size_t m_cursor; + const std::span m_bytes; + }; +} \ No newline at end of file diff --git a/include/korka/utils/function_traits.hpp b/include/korka/utils/function_traits.hpp new file mode 100644 index 0000000..9e421e4 --- /dev/null +++ b/include/korka/utils/function_traits.hpp @@ -0,0 +1,24 @@ +// +// Created by pyxiion on 09.03.2026. +// + +#pragma once + +#include + +namespace korka { + template + struct function_traits; + + template + struct function_traits { + using return_type = R; + using args_tuple = std::tuple; + + constexpr static std::size_t args_count = sizeof...(Args); + }; + + template + struct function_traits : function_traits { + }; +} \ No newline at end of file diff --git a/include/korka/utils/string.hpp b/include/korka/utils/string.hpp index 69bae52..c18f879 100644 --- a/include/korka/utils/string.hpp +++ b/include/korka/utils/string.hpp @@ -26,7 +26,7 @@ namespace korka { template constexpr auto const_string_from_string_view() { - const_string str; + const_string str; std::copy_n(sv_getter().data(), str.length, str.value); return str; } diff --git a/include/korka/vm/op_codes.hpp b/include/korka/vm/op_codes.hpp index 5e27851..114f421 100644 --- a/include/korka/vm/op_codes.hpp +++ b/include/korka/vm/op_codes.hpp @@ -1,13 +1,18 @@ #pragma once +#include +#include +#include #include +#include "korka/shared/error.hpp" #include "korka/shared/types.hpp" +#include "korka/utils/overloaded.hpp" namespace korka::vm { using local_index_t = std::uint8_t; using jump_offset = std::int32_t; - enum class op_code { + enum class op_code : char { // --- Memory & Stack --- // Loads a value from tje local at index on stack // diff --git a/include/korka/vm/vm_runtime.hpp b/include/korka/vm/vm_runtime.hpp index 0a53c84..3ba5caf 100644 --- a/include/korka/vm/vm_runtime.hpp +++ b/include/korka/vm/vm_runtime.hpp @@ -4,8 +4,197 @@ #pragma once -namespace korka { +#include "korka/utils/function_traits.hpp" +#include "korka/vm/op_codes.hpp" +#include "korka/utils/byte_reader.hpp" +#include "korka/compiler/compiler.hpp" +#include +#include +#include +#include +namespace korka::vm { + using value = std::array; + template + constexpr auto box(T const &v) -> value { + static_assert(std::is_same_v or std::is_same_v, "T must be int or double"); -} // korka \ No newline at end of file + return std::bit_cast(v); + } + + template + constexpr auto unbox(value const &v) -> T { + static_assert(std::is_same_v or std::is_same_v, "T must be int or double"); + + return std::bit_cast(v); + } + + constexpr auto box_int(std::int64_t i) -> value { + return std::bit_cast(i); + } + + class context { + public: + explicit context(std::span bytes) + : m_reader(bytes) { + m_stack.reserve(64); + m_locals.resize(8); + } + +// auto run(function_info const& fi) { +// +// } + + template> + auto run(function_runtime_info_with_signature, Args &&...args) -> typename Traits::return_type { + + format_static_assert(); + + // TODO: check argument types + + // Pass arguments + std::size_t i = 0; + ([&]() { + set_local_value(i++, box(args)); + }(), ...); + + // Run until ret + while (true) { + auto op = execute_op(); + + if (op == op_code::ret) { + break; + } + } + + return pop(); + } + + auto push_value(value const &v) -> void { + m_stack.emplace_back(v); + } + + template + auto push(T const &v) -> void { + push_value(box(v)); + } + + auto pop_value() -> value { + value v = m_stack.back(); + m_stack.pop_back(); + return v; + } + + template + auto pop() -> T { + return unbox(pop_value()); + } + + auto set_local_value(local_index_t i, value const &v) -> void { + m_locals.at(i) = v; + } + + template + auto set_local(local_index_t i, T const &value) -> void { + set_local_value(i, box(value)); + } + + auto get_local_value(local_index_t i) -> value { + return m_locals.at(i); + } + + template + auto get_local(local_index_t i) -> T { + return unbox(get_local_value(i)); + } + + private: + auto execute_op() -> op_code { + auto initial_pc = m_reader.cursor(); + + const op_code code = m_reader.read(); + + switch (code) { + case op_code::lload: { + const auto index = m_reader.read(); + push_value(get_local_value(index)); + } + break; + case op_code::pload: + throw std::runtime_error("Not implemented"); + break; + case op_code::lsave: { + const auto index = m_reader.read(); + set_local_value(index, pop_value()); + } + break; + case op_code::i64_const: { + const auto v = m_reader.read(); + push(v); + } + break; + case op_code::i64_add: { + const auto b = pop(); + const auto a = pop(); + push(a + b); + } + break; + case op_code::i64_sub: { + const auto b = pop(); + const auto a = pop(); + push(a - b); + } + break; + case op_code::i64_mul: { + const auto b = pop(); + const auto a = pop(); + push(a * b); + } + break; + case op_code::i64_div: { + const auto b = pop(); + const auto a = pop(); + push(a / b); + } + break; + case op_code::jmp: { + const auto offset = m_reader.read(); + m_reader.set_cursor(initial_pc + offset); + } + break; + case op_code::jmpz: { + const auto offset = m_reader.read(); + auto v = pop(); + if (v == 0) { + m_reader.set_cursor(initial_pc + offset); + } + } + break; + case op_code::ret: + // TODO + // Works for now, since we don't have function calls inside the VM + break; + } + + return code; + } + + std::vector m_stack; + std::vector m_locals; + + byte_reader m_reader; + }; + + class runtime { + public: +// auto create_context() -> context { +// return {}; +// } + + + }; + +} // korka::vm \ No newline at end of file diff --git a/main.cpp b/main.cpp index 4aeaf21..f114726 100644 --- a/main.cpp +++ b/main.cpp @@ -1,13 +1,14 @@ #include "korka/compiler/parser.hpp" #include "korka/compiler/compiler.hpp" #include "korka/compiler/ast_walker.hpp" +#include "korka/vm/vm_runtime.hpp" #include constexpr char code[] = R"( int main() { int a = 2; if (a) { - return a; + return a * 5; } else { return 5 + a; } @@ -20,13 +21,15 @@ int foo(int a, int b) { constexpr auto compile_result = korka::compile(); -auto main_func = compile_result.function<"main">(); -static_assert(std::is_same_v); - -auto foo_func = compile_result.function<"foo">(); -static_assert(std::is_same_v); int main() { + korka::vm::context ctx{compile_result.bytes}; + + auto main_func = compile_result.function<"main">(); + auto foo_func = compile_result.function<"foo">(); + + std::println("{} {}", ctx.run(main_func), ctx.run(foo_func, 3L, 42L)); + // std::ignore = tokens; // std::println("{:n:02X}", compile_result.bytes | std::views::transform([](auto b) { return static_cast(b); })); diff --git a/src/vm/vm_runtime.cpp b/src/vm/vm_runtime.cpp index 0576628..1666489 100644 --- a/src/vm/vm_runtime.cpp +++ b/src/vm/vm_runtime.cpp @@ -2,7 +2,7 @@ // Created by pyxiion on 29.01.2026. // -#include "korka/vm/vm_runtime.hpp" +//#include "korka/vm/vm_runtime.hpp" namespace korka { } // korka \ No newline at end of file From 02c5498cdc6ed62b6aaf5203bf6e56ff489a8fcd Mon Sep 17 00:00:00 2001 From: PyXiion Date: Fri, 20 Mar 2026 10:48:58 +0300 Subject: [PATCH 2/4] Function calling --- include/korka/compiler/compiler.hpp | 111 ++++++++++++++++++++----- include/korka/shared/error.hpp | 26 +++++- include/korka/vm/bytecode_builder.hpp | 4 + include/korka/vm/op_codes.hpp | 23 ++++-- include/korka/vm/vm_runtime.hpp | 115 +++++++++++++++++++------- main.cpp | 21 ++--- 6 files changed, 229 insertions(+), 71 deletions(-) diff --git a/include/korka/compiler/compiler.hpp b/include/korka/compiler/compiler.hpp index 2decf14..aa92ccb 100644 --- a/include/korka/compiler/compiler.hpp +++ b/include/korka/compiler/compiler.hpp @@ -320,13 +320,13 @@ namespace korka { using result_t = std::expected; - constexpr auto process_node(nodes::index_t idx) -> result_t { + constexpr auto process_node(nodes::index_t idx, bool emit = true) -> result_t { const auto &node = m_nodes[idx]; return std::visit(overloaded{ [&](const nodes::decl_program &program) -> result_t { for (auto item: nodes::get_list_view(m_nodes, program.external_declarations_head)) { - auto ok = process_node(item); + auto ok = process_node(item, emit); if (not ok) { return std::unexpected{ok.error()}; } @@ -381,7 +381,7 @@ namespace korka { // Function body for (auto stmt: nodes::get_list_view(m_nodes, function.body)) { - if (auto res = process_node(stmt); !res) { + if (auto res = process_node(stmt, emit); !res) { m_symbols.pop_scope(); // clean up return res; } @@ -398,7 +398,9 @@ namespace korka { return std::visit([&](auto val) -> result_t { using T = decltype(val); if constexpr (std::is_same_v) { - builder.emit_const(val); + if (emit) { + builder.emit_const(val); + } return type_info{type::i64}; } return std::unexpected{ @@ -407,13 +409,13 @@ namespace korka { }, [&](const nodes::stmt_block &block) -> result_t { for (auto stmt: nodes::get_list_view(m_nodes, block.children_head)) { - if (auto res = process_node(stmt); !res) return res; + if (auto res = process_node(stmt, emit); !res) return res; } return {}; }, [&](const nodes::stmt_return &stmt) -> result_t { - auto actual_type = process_node(stmt.expr); + auto actual_type = process_node(stmt.expr, emit); if (!actual_type) return actual_type; // Semantic Check: Does return type match function signature? @@ -423,8 +425,9 @@ namespace korka { .message = "Function return type mismatch" }}; } - - builder.emit_op(vm::op_code::ret); + if (emit) { + builder.emit_op(vm::op_code::ret); + } return *actual_type; }, [&](const nodes::decl_var &var) -> result_t { @@ -434,11 +437,13 @@ namespace korka { } if (var.init_expr != nodes::empty_node) { - auto expr = process_node(var.init_expr); + auto expr = process_node(var.init_expr, emit); if (not expr) { return expr; } - builder.emit_save_local(ok->locals_index); + if (emit) { + builder.emit_save_local(ok->locals_index); + } } return ok->type; }, @@ -451,15 +456,17 @@ namespace korka { }}; } - builder.emit_load_local(info->locals_index); + if (emit) { + builder.emit_load_local(info->locals_index); + } return info->type; }, [&](const nodes::expr_binary &expr) -> result_t { - auto left = process_node(expr.left); + auto left = process_node(expr.left, emit); if (not left) { return left; } - auto right = process_node(expr.right); + auto right = process_node(expr.right, emit); if (not right) { return right; } @@ -470,17 +477,46 @@ namespace korka { }}; } - auto code = vm::get_op_code_for_math(*left, *right, expr.op); - if (not code) { - return std::unexpected{code.error()}; + using namespace std::literals; + constexpr std::array comparison_ops = { + "=="sv + }; + + + if (emit) { + if (std::ranges::contains(comparison_ops, expr.op)) { + if (auto *type = std::get_if(&*left)) { + switch (*type) { + case type::void_: + return std::unexpected{error::other_compiler_error{"wtf"}}; + case type::i64: { + auto &op = expr.op; + if (op == "==") { + builder.emit_op(vm::op_code::i64_cmp); + } else { + return std::unexpected{error::other_compiler_error{"Unsupported operation"}}; + } + break; + } + } + } else { + return std::unexpected{error::other_compiler_error{"Unsupported type"}}; + } + } else { + // Basic math + auto code = vm::get_op_code_for_math(*left, *right, expr.op); + if (not code) { + return std::unexpected{code.error()}; + } + builder.emit_op(*code); + } } - builder.emit_op(*code); return *left; }, [&](const nodes::stmt_if &if_) -> result_t { - auto condition_expr = process_node(if_.condition); + auto condition_expr = process_node(if_.condition, emit); if (not condition_expr) { return condition_expr; } @@ -491,14 +527,14 @@ namespace korka { if (if_.else_branch == nodes::empty_node) { builder.emit_jmp_if_zero(end_label); - auto then_branch = process_node(if_.then_branch); + auto then_branch = process_node(if_.then_branch, emit); if (not then_branch) { return then_branch; } } else { builder.emit_jmp_if_zero(else_branch_label); - auto then_branch = process_node(if_.then_branch); + auto then_branch = process_node(if_.then_branch, emit); if (not then_branch) { return then_branch; } @@ -515,6 +551,41 @@ namespace korka { builder.bind_label(end_label); return {}; }, + [&](nodes::expr_call const &call) -> result_t { + auto func = m_symbols.lookup_function(call.name); + if (!func) { + return std::unexpected{error::undefined_symbol{.identifier = call.name}}; + } + + auto &expected_params = func->params; + std::vector arg_indices; + + std::size_t pidx{}; + for (auto param_node_idx: nodes::get_list_view(m_nodes, call.args_head)) { + auto param_type = process_node(param_node_idx, /*emit=*/ false); + if (!param_type) return param_type; + + if (pidx >= expected_params.size() || expected_params[pidx].type != *param_type) { + return std::unexpected{error::function_call_param_mismatch{ + .function_name = func->name, .param_idx = pidx + }}; + } + arg_indices.emplace_back(param_node_idx); + pidx++; + } + + if (emit) { + for (auto idx: arg_indices) { + if (auto ok = process_node(idx, true); !ok) return ok; + } + + builder.emit_const(static_cast(arg_indices.size())); + + builder.emit_call(func->start_pos); + } + + return func->return_type; + }, [&](const auto &value) -> result_t { std::ignore = value; diff --git a/include/korka/shared/error.hpp b/include/korka/shared/error.hpp index 70d74ae..9bbaca2 100644 --- a/include/korka/shared/error.hpp +++ b/include/korka/shared/error.hpp @@ -83,7 +83,18 @@ namespace korka { }; constexpr auto report(const function_return_type_mismatch &err) -> std::string { - return korka::format("Compiler Error: expected ~ type to be returned, got ~", err.return_type, err.actual_type); + return korka::format("Compiler Error: expected '~' type to be returned, got '~'", err.return_type, + err.actual_type); + } + + struct function_call_param_mismatch { + std::string_view function_name; + std::size_t param_idx; + }; + + constexpr auto report(const function_call_param_mismatch &err) -> std::string { + return korka::format("Compiler Error: in function call to '~' wrong parameter at ~", + err.function_name, err.param_idx); } @@ -95,6 +106,15 @@ namespace korka { return korka::format("Compiler Error: ~", err.message); } + struct unsupported_math_op { + std::string_view type; + std::string_view op; + }; + + constexpr auto report(const unsupported_math_op &err) -> std::string { + return korka::format("Error: Unsupported math operation '~' for type '~'", err.op, err.type); + } + struct other_error { std::string_view message; }; @@ -111,7 +131,9 @@ namespace korka { error::redeclaration, error::undefined_symbol, error::unknown_type, + error::function_call_param_mismatch, error::other_compiler_error, + error::unsupported_math_op, error::other_error>; constexpr auto to_string(const error_t &err) -> std::string { @@ -131,7 +153,7 @@ namespace korka { #ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT static_assert(false, to_string(err_getter())); #else - constexpr auto msg = const_string_from_string_view<[]{return to_string(err_getter());}>(); + constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>(); std::ignore = ErrorMessage{}; #endif } else { diff --git a/include/korka/vm/bytecode_builder.hpp b/include/korka/vm/bytecode_builder.hpp index 030a7b4..22882a0 100644 --- a/include/korka/vm/bytecode_builder.hpp +++ b/include/korka/vm/bytecode_builder.hpp @@ -66,6 +66,10 @@ namespace korka::vm { constexpr auto emit_jmp_if_zero(const label &target) { record_jump(op_code::jmpz, target); } + constexpr auto emit_call(const address_t &address) { + emit_op(op_code::call); + m_data.write_many(address); + } // // constexpr auto emit_jmp_if(const label &target, reg_id_t cond) { // record_jump(op_code::jmp_if, target, cond); diff --git a/include/korka/vm/op_codes.hpp b/include/korka/vm/op_codes.hpp index 114f421..cc0d14e 100644 --- a/include/korka/vm/op_codes.hpp +++ b/include/korka/vm/op_codes.hpp @@ -11,10 +11,11 @@ namespace korka::vm { using local_index_t = std::uint8_t; using jump_offset = std::int32_t; + using address_t = std::uint32_t; enum class op_code : char { // --- Memory & Stack --- - // Loads a value from tje local at index on stack + // Loads a value from the local at index on stack // lload, @@ -41,13 +42,24 @@ namespace korka::vm { i64_mul, i64_div, + // push(pop() == pop()) + i64_cmp, + // --- Control flow --- // - Jumps - - // // + // // jmp, // jumps no matter what jmpz, // pops value and jumps if it's zero // - Other - + // Call + // foo(a, b, c) + // On stack: arg_count <- top + // A <- arguments + // B + // C + // Then VM on call puts them into locals + call, // ret }; @@ -63,7 +75,7 @@ namespace korka::vm { using type_info = std::variant; constexpr auto - get_op_code_for_math(type_info ltype, type_info rtype, std::string_view op) -> std::expected { + get_op_code_for_math(const type_info <ype, const type_info &rtype, std::string_view op) -> std::expected { if (ltype != rtype) { return std::unexpected{error::other_error{ .message = "Math operations between distinct types are not supported yet" @@ -81,8 +93,9 @@ namespace korka::vm { return op_code::i64_mul; if (op == "/") return op_code::i64_div; - return std::unexpected{error::other_error{ - .message = "Unsupported math operation for i64" + return std::unexpected{error::unsupported_math_op{ + .type = "i64", + .op = op }}; } return std::unexpected{error::other_error{ diff --git a/include/korka/vm/vm_runtime.hpp b/include/korka/vm/vm_runtime.hpp index 3ba5caf..52ff753 100644 --- a/include/korka/vm/vm_runtime.hpp +++ b/include/korka/vm/vm_runtime.hpp @@ -34,38 +34,68 @@ namespace korka::vm { return std::bit_cast(i); } + struct function_scope { + // Set on `call`, used on `ret` + std::size_t suspension_point{}; + + std::vector locals{8}; + + auto set_local_value(local_index_t i, value const &v) -> void { + locals.at(i) = v; + } + + template + auto set_local(local_index_t i, T const &value) -> void { + set_local_value(i, box(value)); + } + + auto get_local_value(local_index_t i) -> value { + return locals.at(i); + } + + template + auto get_local(local_index_t i) -> T { + return unbox(get_local_value(i)); + } + + + auto clear() -> void { + locals.clear(); + } + }; + class context { public: explicit context(std::span bytes) : m_reader(bytes) { m_stack.reserve(64); - m_locals.resize(8); } -// auto run(function_info const& fi) { -// -// } - template> - auto run(function_runtime_info_with_signature, Args &&...args) -> typename Traits::return_type { - + auto call(function_runtime_info_with_signature func, Args &&...args) -> typename Traits::return_type { format_static_assert(); + // Create a scope for the call + push_scope(); // TODO: check argument types // Pass arguments std::size_t i = 0; ([&]() { - set_local_value(i++, box(args)); + current_scope()->set_local_value(i++, box(args)); }(), ...); + m_reader.set_cursor(func.start_pos); + // Run until ret + // Check scopes size to ensure it's the original function's return while (true) { auto op = execute_op(); - if (op == op_code::ret) { + if (op == op_code::ret && m_scopes.empty()) { + m_scopes.clear(); break; } } @@ -93,34 +123,21 @@ namespace korka::vm { return unbox(pop_value()); } - auto set_local_value(local_index_t i, value const &v) -> void { - m_locals.at(i) = v; - } - - template - auto set_local(local_index_t i, T const &value) -> void { - set_local_value(i, box(value)); - } - - auto get_local_value(local_index_t i) -> value { - return m_locals.at(i); - } - - template - auto get_local(local_index_t i) -> T { - return unbox(get_local_value(i)); + template + auto pop() -> type_to_cpp_t { + return unbox>(pop_value()); } private: auto execute_op() -> op_code { auto initial_pc = m_reader.cursor(); - const op_code code = m_reader.read(); + const auto code = m_reader.read(); switch (code) { case op_code::lload: { const auto index = m_reader.read(); - push_value(get_local_value(index)); + push_value(current_scope()->get_local_value(index)); } break; case op_code::pload: @@ -128,7 +145,7 @@ namespace korka::vm { break; case op_code::lsave: { const auto index = m_reader.read(); - set_local_value(index, pop_value()); + current_scope()->set_local_value(index, pop_value()); } break; case op_code::i64_const: { @@ -160,6 +177,12 @@ namespace korka::vm { push(a / b); } break; + case op_code::i64_cmp: { + const auto b = pop(); + const auto a = pop(); + push(static_cast(a == b)); + } + break; case op_code::jmp: { const auto offset = m_reader.read(); m_reader.set_cursor(initial_pc + offset); @@ -173,17 +196,47 @@ namespace korka::vm { } } break; + case op_code::call: { + auto called_address = m_reader.read(); + current_scope()->suspension_point = m_reader.cursor(); + auto arg_count = pop(); + + push_scope(); + + // Load args + for (int i = arg_count - 1; i >= 0; --i) { + auto v = pop_value(); + current_scope()->set_local_value(i, v); + } + + m_reader.set_cursor(called_address); + } + break; case op_code::ret: - // TODO - // Works for now, since we don't have function calls inside the VM + pop_scope(); + if (current_scope()) { + m_reader.set_cursor(current_scope()->suspension_point); + } break; } return code; } + auto current_scope() -> function_scope * { + return m_scopes.empty() ? nullptr : std::addressof(m_scopes.back()); + } + + auto push_scope() -> void { + m_scopes.emplace_back(); + } + + auto pop_scope() -> void { + m_scopes.pop_back(); + } + std::vector m_stack; - std::vector m_locals; + std::vector m_scopes; byte_reader m_reader; }; diff --git a/main.cpp b/main.cpp index f114726..643fcd8 100644 --- a/main.cpp +++ b/main.cpp @@ -5,17 +5,11 @@ #include constexpr char code[] = R"( -int main() { - int a = 2; - if (a) { - return a * 5; - } else { - return 5 + a; - } -} +int fib(int n) { + if (n == 0) return 0; + if (n == 1) return 1; -int foo(int a, int b) { - return a + b; + return fib(n-1) + fib(n-2); } )"; @@ -24,11 +18,12 @@ constexpr auto compile_result = korka::compile(); int main() { korka::vm::context ctx{compile_result.bytes}; + std::println("{:n:X}", compile_result.bytes | std::views::transform([](auto b) { return static_cast(b); })); - auto main_func = compile_result.function<"main">(); - auto foo_func = compile_result.function<"foo">(); +// auto main_func = compile_result.function<"main">(); + auto fib_func = compile_result.function<"fib">(); - std::println("{} {}", ctx.run(main_func), ctx.run(foo_func, 3L, 42L)); + std::println("{}", ctx.call(fib_func, 12L)); // std::ignore = tokens; // std::println("{:n:02X}", compile_result.bytes | std::views::transform([](auto b) { return static_cast(b); })); From dd26fd9abbe53cba193bd32dee610982de8ab0af Mon Sep 17 00:00:00 2001 From: PyXiion Date: Fri, 20 Mar 2026 10:51:34 +0300 Subject: [PATCH 3/4] Fix --- CMakeLists.txt | 3 +++ main.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3949419..b4b6d04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,9 @@ target_include_directories(korka_lib add_executable(pxkorka main.cpp) target_link_libraries(pxkorka PRIVATE korka_lib) +# Ignore unused-but-set-variable shadow +target_compile_options(korka_lib PUBLIC -Wno-error=unused-but-set-variable -Wno-error=shadow) + # --- TESTS --- #if (ENABLE_TESTS) # enable_testing() diff --git a/main.cpp b/main.cpp index 643fcd8..ebcac29 100644 --- a/main.cpp +++ b/main.cpp @@ -18,8 +18,6 @@ constexpr auto compile_result = korka::compile(); int main() { korka::vm::context ctx{compile_result.bytes}; - std::println("{:n:X}", compile_result.bytes | std::views::transform([](auto b) { return static_cast(b); })); - // auto main_func = compile_result.function<"main">(); auto fib_func = compile_result.function<"fib">(); From 11f4fc5b79a61b9d2e7ba2b82c104eba261a4c2f Mon Sep 17 00:00:00 2001 From: PyXiion Date: Fri, 20 Mar 2026 10:56:16 +0300 Subject: [PATCH 4/4] GCC fix --- include/korka/compiler/compiler.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/korka/compiler/compiler.hpp b/include/korka/compiler/compiler.hpp index aa92ccb..acb2312 100644 --- a/include/korka/compiler/compiler.hpp +++ b/include/korka/compiler/compiler.hpp @@ -238,7 +238,9 @@ namespace korka { }; // Retrieve the type - template requires ([] -> bool { + template + #ifdef __clang__ // GCC crashes on this check for some reason + requires ([] -> bool { constexpr bool found = requires { { _overloaded(unique_type{}) }; }; @@ -249,6 +251,7 @@ namespace korka { } return true; }()) + #endif using get_signature_t = std::remove_pointer_t{}))>;