From a1bf4f16f370c83f53a72a55a958073c97ad8e87 Mon Sep 17 00:00:00 2001 From: PyXiion Date: Mon, 30 Mar 2026 18:34:38 +0300 Subject: [PATCH] Implement bindings --- CMakeLists.txt | 8 +- include/korka/compiler/binding.hpp | 145 ++++++++ include/korka/compiler/binding_wrapper.hpp | 37 ++ include/korka/compiler/compiler.hpp | 409 +++++---------------- include/korka/compiler/info.hpp | 61 +++ include/korka/compiler/result.hpp | 223 +++++++++++ include/korka/utils/function_traits.hpp | 8 + include/korka/vm/bytecode_builder.hpp | 5 + include/korka/vm/context_base.hpp | 101 +++++ include/korka/vm/op_codes.hpp | 17 +- include/korka/vm/value.hpp | 25 ++ include/korka/vm/vm_runtime.hpp | 121 ++---- main.cpp | 42 ++- 13 files changed, 777 insertions(+), 425 deletions(-) create mode 100644 include/korka/compiler/binding.hpp create mode 100644 include/korka/compiler/binding_wrapper.hpp create mode 100644 include/korka/compiler/info.hpp create mode 100644 include/korka/compiler/result.hpp create mode 100644 include/korka/vm/context_base.hpp create mode 100644 include/korka/vm/value.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b6d04..f67a53f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(pxkorka ) -set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -48,6 +48,12 @@ add_library(korka_lib include/korka/utils/frozen_hash_string_view.hpp include/korka/utils/byte_reader.hpp include/korka/utils/function_traits.hpp + include/korka/compiler/binding.hpp + include/korka/compiler/info.hpp + include/korka/vm/value.hpp + include/korka/compiler/result.hpp + include/korka/compiler/binding_wrapper.hpp + include/korka/vm/context_base.hpp ) target_include_directories(korka_lib diff --git a/include/korka/compiler/binding.hpp b/include/korka/compiler/binding.hpp new file mode 100644 index 0000000..972feb2 --- /dev/null +++ b/include/korka/compiler/binding.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "frozen/unordered_map.h" +#include "info.hpp" +#include "korka/utils/function_traits.hpp" +#include "korka/vm/op_codes.hpp" +#include "korka/vm/value.hpp" +#include "korka/compiler/binding_wrapper.hpp" +#include +#include + +namespace korka { + template + struct function_binding { + vm_external_function_id id{}; + std::string_view name{}; + + std::size_t param_count{}; + std::array param_types{}; + + vm_external_function_type &wrapper; + + vm::type_info return_type; + + + template + static consteval auto from_function(vm_external_function_id idx, Wrapped wrapped) -> function_binding { + using function_type = std::remove_cvref_t; + using traits = function_traits; + using rtype = typename traits::return_type; + using args = typename traits::args_tuple; + + constexpr std::size_t arg_count = traits::args_count; + + static_assert(arg_count <= NMaxArgs, "Param count mismatch"); + + auto ptypes = [](std::index_sequence) { + return std::array{ + vm::cpp_t_to_type_info>()... + }; + }(std::make_index_sequence()); + + return { + .id = idx, + .name = wrapped.name, + .param_count = arg_count, + .param_types = ptypes, + .wrapper = wrapped.external_func, + .return_type = vm::cpp_t_to_type_info() + }; + } + }; + + template + class bindings { + using binding_type = function_binding; + using array_type = std::array, NBindings>; + using map_type = frozen::unordered_map; + + public: + constexpr explicit bindings(const array_type &array) + : m_map(frozen::make_unordered_map(array)) {} + + constexpr auto get(std::string_view name) -> std::optional { + if (not m_map.contains(name)) { + return std::nullopt; + } + return m_map.at(name); + } + + // runtime + auto get_callable_by_id(vm_external_function_id id) const -> vm_external_function_type * { + for (auto &&[k, v]: m_map) { + if (v.id == id) { + return std::addressof(v.wrapper); + } + } + return nullptr; + } + + private: + map_type m_map; + }; + + template + class bindings<0, NMaxArgs> { + using binding_type = function_binding; + using array_type = std::array, 0>; + + public: + constexpr explicit bindings() = default; + + constexpr explicit bindings(const array_type &) {}; + + constexpr auto get(std::string_view) -> std::optional { + return std::nullopt; + } + + auto get_callable_by_id(vm_external_function_id) const -> vm_external_function_type * { + return nullptr; + } + }; + + class const_bindings { + + }; + + consteval auto make_bindings(auto ...wrapped_functions) { + constexpr auto func_max_args = [] { + std::size_t max{}; + ((max = std::max(max, function_traits< + typename std::decay_t::signature_t + >::args_count)), ...); + return max; + }(); + + using binding_type = function_binding; + + vm_external_function_id i{}; + return bindings{ + std::array, sizeof...(wrapped_functions)>{ + std::pair{ + wrapped_functions.name, + binding_type::from_function(i++, wrapped_functions) + }... + }}; + } + + template + struct wrapped_function { + using signature_t = Signature; + + vm_external_function_type &external_func; + std::string_view name; + }; + + template + consteval auto wrap(std::string_view name) { + return wrapped_function>{ + binding_wrapper, + name + }; + } + +} \ No newline at end of file diff --git a/include/korka/compiler/binding_wrapper.hpp b/include/korka/compiler/binding_wrapper.hpp new file mode 100644 index 0000000..9fa5e44 --- /dev/null +++ b/include/korka/compiler/binding_wrapper.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "korka/vm/context_base.hpp" + +namespace korka { + using vm_external_function_id = std::uint16_t; + using vm_external_function_type = void(vm::context_base &context); + + template + auto binding_wrapper(vm::context_base &ctx) -> void { + using function_type = std::remove_cvref_t; + using traits = function_traits; + using rtype = typename traits::return_type; + using args_tuple = typename traits::args_tuple; + constexpr auto arg_count = traits::args_count; + + // Load args from stack + std::array retrieved_args; + for (int i = arg_count - 1; i >= 0; --i) { + retrieved_args[i] = ctx.pop_value(); + } + + // Unbox them into types + auto args = [&](std::index_sequence) { + return args_tuple{ + vm::unbox>(retrieved_args[I])... + }; + }(std::make_index_sequence()); + + if constexpr (std::is_void_v) { + std::apply(Func, std::move(args)); + } else { + rtype v = std::apply(Func, std::move(args)); + auto boxed = vm::box(v); + ctx.push_value(boxed); + } + } +} \ No newline at end of file diff --git a/include/korka/compiler/compiler.hpp b/include/korka/compiler/compiler.hpp index acb2312..4ec9e03 100644 --- a/include/korka/compiler/compiler.hpp +++ b/include/korka/compiler/compiler.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include "frozen/unordered_map.h" +#include "korka/compiler/binding.hpp" #include "korka/shared/error.hpp" #include "korka/shared/flat_map.hpp" #include "korka/utils/overloaded.hpp" @@ -12,294 +14,19 @@ #include #include #include +#include "info.hpp" +#include "binding.hpp" +#include "result.hpp" namespace korka { - struct void_t { - }; - - using vm::type_info; - - constexpr auto string_to_type(std::string_view name) -> type { - if (name == "int") return type::i64; - else if (name == "void") return type::void_; - // TODO: other types - return type::i64; - } - - constexpr auto type_to_string(type t) -> std::string_view { - switch (t) { - case type::void_: - return "void"; - case type::i64: - return "int"; - } - } - - struct variable_info { - std::string_view name; - type_info type; - - std::size_t locals_index; - - static constexpr auto from_node(const nodes::decl_var &node) -> variable_info { - return { - .name = node.var_name, - .type{}, - .locals_index{} - }; - } - }; - - struct function_info { - std::string_view name; - std::vector params; - type_info return_type; - - std::size_t start_pos; - }; - - template - struct const_function_info { - std::string_view name; - std::size_t param_count; - std::array params; - type_info return_type; - - std::size_t start_pos; - }; - - template - struct _extract_function_signature { - template - struct param_helper; - - 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 = typename param_helper< - std::make_index_sequence - >::type; - }; - - template - using const_function_info_to_signature_t = typename _extract_function_signature::type; - - template - constexpr auto function_info_to_const(const function_info &f) { - const_function_info info{ - .name = f.name, - .param_count = f.params.size(), - .params{}, - .return_type = f.return_type, - .start_pos = f.start_pos - }; - - std::ranges::copy(f.params, std::begin(info.params)); - return info; - } - - struct symbol_table { - struct scope { - flat_map variables; - - std::size_t current_locals_size{}; - }; - std::vector scopes; - flat_map functions; - - constexpr auto push_scope() -> void { scopes.emplace_back(); } - - constexpr auto pop_scope() -> void { scopes.pop_back(); } - - constexpr auto declare_var(std::string_view name, const type_info &type) -> std::expected { - if (scopes.empty()) { - return std::unexpected{error::other_compiler_error{ - .message = "No scope" - }}; - } - - auto ¤t = scopes.back(); - if (current.variables.contains(name)) { - return std::unexpected{error::redeclaration{ - .identifier = name - }}; - } - - variable_info info{ - .name = name, - .type = type, - .locals_index = current.current_locals_size++ - }; - - current.variables[name] = info; - return info; - } - - constexpr auto declare_function(std::string_view name, auto &&...args) -> std::expected { - functions.emplace(std::piecewise_construct, - std::forward_as_tuple(name), - std::forward_as_tuple(name, std::forward(args)...)); - return {}; - } - - constexpr auto lookup_variable(std::string_view name) -> std::optional { - for (auto &scp: std::ranges::reverse_view(scopes)) { - if (auto var_it = scp.variables.find(name); var_it != std::end(scp.variables)) { - return var_it->second; - } - } - return std::nullopt; - } - - constexpr auto lookup_function(std::string_view name) -> std::optional { - if (auto func_it = functions.find(name); func_it != std::end(functions)) { - return func_it->second; - } - return std::nullopt; - } - - constexpr auto clear() -> void { - scopes.clear(); - functions.clear(); - } - }; - - - struct compilation_result { - std::vector bytes; - 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; - frozen::unordered_map, NFunctions> functions; - - template - using get_signature_t = typename SignatureMapper::template get_signature_t; - - template - constexpr auto function() const { - return function_runtime_info_with_signature>{ - functions.at(name).start_pos - }; - } - -// SignatureMapper mapper{}; - }; - - 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) - -> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * { - return nullptr; - })... - }; - - // Retrieve the type - template - #ifdef __clang__ // GCC crashes on this check for some reason - requires ([] -> bool { - constexpr bool found = requires { - { _overloaded(unique_type{}) }; - }; - if constexpr (not found) { - report_error<[] { - return format("Symbol '~' not found", name); - }>(); - } - return true; - }()) - #endif - using get_signature_t = std::remove_pointer_t{}))>; - -// std::tuple *...> debug1; -// std::tuple{}(function_info_getter(Is).name, 0)>...> debug2; - }; - - - template - constexpr auto compilation_result_to_const() { - // --- BYTES --- - constexpr static auto bytes = to_array<[] { return r().bytes; }>(); - - // --- FUNCTIONS --- - constexpr static auto function_count = []() constexpr { - return r().functions.size(); - }(); - constexpr static auto max_params_n = []() constexpr { - std::size_t n{}; - for (auto &&f: r().functions) { - n = std::max(n, f.second.params.size()); - } - return n; - }(); - constexpr static auto functions = []() constexpr { - std::array>, function_count> functions_data{}; - std::size_t i{}; - for (auto &&[key, value]: r().functions) { - functions_data[i++] = (std::make_pair(key, function_info_to_const(value))); - } - return frozen::make_unordered_map(functions_data); - }; - - // 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, - functions() - }; - } - + template class compiler { + using bindings_container = bindings; + public: - constexpr compiler(std::span nodes, nodes::index_t root_node) - : m_nodes(nodes), m_root_node(root_node) {} + constexpr compiler(std::span nodes, nodes::index_t root_node, const bindings_container &bindings) + : m_nodes(nodes), m_root_node(root_node), m_bindings(bindings) { + } constexpr auto compile() -> std::expected { m_symbols.push_scope(); @@ -318,6 +45,9 @@ namespace korka { symbol_table m_symbols; vm::bytecode_builder builder; + // External bindings + bindings_container m_bindings; + // Info for ast walker std::optional m_current_func_ret; @@ -418,20 +148,27 @@ namespace korka { }, [&](const nodes::stmt_return &stmt) -> result_t { - auto actual_type = process_node(stmt.expr, emit); - if (!actual_type) return actual_type; - - // Semantic Check: Does return type match function signature? - if (m_current_func_ret && *actual_type != *m_current_func_ret) { - // TODO: proper error - return std::unexpected{error::other_compiler_error{ - .message = "Function return type mismatch" - }}; - } - if (emit) { - builder.emit_op(vm::op_code::ret); + if (stmt.expr != empty_node) { + auto actual_type = process_node(stmt.expr, emit); + if (!actual_type) return actual_type; + + // Semantic Check: Does return type match function signature? + if (m_current_func_ret && *actual_type != *m_current_func_ret) { + // TODO: proper error + return std::unexpected{error::other_compiler_error{ + .message = "Function return type mismatch" + }}; + } + if (emit) { + builder.emit_op(vm::op_code::ret); + } + return *actual_type; + } else { + if (emit) { + builder.emit_op(vm::op_code::ret); + } + return korka::type_info{korka::type::void_}; } - return *actual_type; }, [&](const nodes::decl_var &var) -> result_t { auto ok = m_symbols.declare_var(var.var_name, string_to_type(var.type_name)); @@ -556,25 +293,49 @@ namespace korka { }, [&](nodes::expr_call const &call) -> result_t { auto func = m_symbols.lookup_function(call.name); - if (!func) { + auto bindings_func = m_bindings.get(call.name); + + + if (not func and not bindings_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; + // Internal function + if (func) { + auto &expected_params = func->params; - 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 - }}; + 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 = call.name, .param_idx = pidx + }}; + } + arg_indices.emplace_back(param_node_idx); + pidx++; + } + } else { // External function + auto &expected_params = bindings_func->param_types; + + 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, false); + if (not param_type) return param_type; + + if (pidx >= expected_params.size() or expected_params[pidx] != *param_type) { + return std::unexpected{error::function_call_param_mismatch{ + .function_name = call.name, .param_idx = pidx + }}; + } + + arg_indices.emplace_back(param_node_idx); + pidx++; } - arg_indices.emplace_back(param_node_idx); - pidx++; } if (emit) { @@ -582,15 +343,26 @@ namespace korka { if (auto ok = process_node(idx, true); !ok) return ok; } - builder.emit_const(static_cast(arg_indices.size())); - - builder.emit_call(func->start_pos); + if (func) { + builder.emit_const(static_cast(arg_indices.size())); + builder.emit_call(func->start_pos); + } else { + builder.emit_trap(bindings_func->id); + } } - return func->return_type; + if (func) { + return func->return_type; + } else { + return bindings_func->return_type; + } + }, + [&](const stmt_expr &expr) -> result_t { + return process_node(expr.expr, emit); }, [&](const auto &value) -> result_t { + throw 0; std::ignore = value; return std::unexpected{error::other_compiler_error{ "Not implemented" @@ -600,11 +372,10 @@ namespace korka { } }; - template + template consteval static auto compile_nodes() { - constexpr static auto expected = [] constexpr { - return compiler{nodes, root}.compile(); - }; + + constexpr static auto expected = [] { return compiler{nodes, root, *bindings}.compile(); }; if constexpr (not expected()) { report_error<[] { return expected().error(); }>(); @@ -614,10 +385,10 @@ namespace korka { } } - template + template consteval static auto compile() { constexpr static auto nodes_root = parse(); - return compile_nodes(); + return compile_nodes(); } } // namespace korka \ No newline at end of file diff --git a/include/korka/compiler/info.hpp b/include/korka/compiler/info.hpp new file mode 100644 index 0000000..807b2d7 --- /dev/null +++ b/include/korka/compiler/info.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include "korka/shared/error.hpp" +#include +#include "korka/vm/op_codes.hpp" +#include "korka/compiler/parser.hpp" + +namespace korka { + + using vm::type_info; + + constexpr auto string_to_type(std::string_view name) -> type { + if (name == "int") return type::i64; + else if (name == "void") return type::void_; + // TODO: other types + return type::i64; + } + + constexpr auto type_to_string(type t) -> std::string_view { + switch (t) { + case type::void_: + return "void"; + case type::i64: + return "int"; + } + } + + struct variable_info { + std::string_view name{}; + type_info type; + + std::size_t locals_index{}; + + static constexpr auto from_node(const nodes::decl_var &node) -> variable_info { + return { + .name = node.var_name, + .type{}, + .locals_index{} + }; + } + }; + + struct function_info { + std::string_view name; + std::vector params; + type_info return_type; + + std::size_t start_pos; + }; + + template + struct const_function_info { + std::string_view name{}; + std::size_t param_count{}; + std::array params{}; + type_info return_type{}; + + std::size_t start_pos{}; + }; +} \ No newline at end of file diff --git a/include/korka/compiler/result.hpp b/include/korka/compiler/result.hpp new file mode 100644 index 0000000..2fd25eb --- /dev/null +++ b/include/korka/compiler/result.hpp @@ -0,0 +1,223 @@ +// +// Created by pyxiion on 20/03/2026. +// +#pragma once + +#include +#include "korka/vm/op_codes.hpp" +#include "korka/compiler/info.hpp" +#include "korka/shared/flat_map.hpp" +#include "korka/utils/frozen_hash_string_view.hpp" + +namespace korka { + template + struct _extract_function_signature { + template + struct param_helper; + + 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 = typename param_helper< + std::make_index_sequence + >::type; + }; + + template + using const_function_info_to_signature_t = typename _extract_function_signature::type; + + template + constexpr auto function_info_to_const(const function_info &f) { + const_function_info info{ + .name = f.name, + .param_count = f.params.size(), + .params{}, + .return_type = f.return_type, + .start_pos = f.start_pos + }; + + std::ranges::copy(f.params, std::begin(info.params)); + return info; + } + + struct symbol_table { + struct scope { + flat_map variables; + + std::size_t current_locals_size{}; + }; + std::vector scopes; + flat_map functions; + + constexpr auto push_scope() -> void { scopes.emplace_back(); } + + constexpr auto pop_scope() -> void { scopes.pop_back(); } + + constexpr auto declare_var(std::string_view name, const type_info &type) -> std::expected { + if (scopes.empty()) { + return std::unexpected{error::other_compiler_error{ + .message = "No scope" + }}; + } + + auto ¤t = scopes.back(); + if (current.variables.contains(name)) { + return std::unexpected{error::redeclaration{ + .identifier = name + }}; + } + + variable_info info{ + .name = name, + .type = type, + .locals_index = current.current_locals_size++ + }; + + current.variables[name] = info; + return info; + } + + constexpr auto declare_function(std::string_view name, auto &&...args) -> std::expected { + functions.emplace(std::piecewise_construct, + std::forward_as_tuple(name), + std::forward_as_tuple(name, std::forward(args)...)); + return {}; + } + + constexpr auto lookup_variable(std::string_view name) -> std::optional { + for (auto &scp: std::ranges::reverse_view(scopes)) { + if (auto var_it = scp.variables.find(name); var_it != std::end(scp.variables)) { + return var_it->second; + } + } + return std::nullopt; + } + + constexpr auto lookup_function(std::string_view name) -> std::optional { + if (auto func_it = functions.find(name); func_it != std::end(functions)) { + return func_it->second; + } + return std::nullopt; + } + + constexpr auto clear() -> void { + scopes.clear(); + functions.clear(); + } + }; + + + struct compilation_result { + std::vector bytes; + 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; + frozen::unordered_map, NFunctions> functions; + + template + using get_signature_t = typename SignatureMapper::template get_signature_t; + + template + constexpr auto function() const { + return function_runtime_info_with_signature>{ + functions.at(name).start_pos + }; + } + }; + + 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); + } + + constexpr static auto _overloaded = overloaded{ + ([](unique_type) + -> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * { + return nullptr; + })... + }; + + // Retrieve the type + template + #ifdef __clang__ // GCC crashes on this check for some reason + requires ([] -> bool { + constexpr bool found = requires { + { _overloaded(unique_type{}) }; + }; + if constexpr (not found) { + report_error<[] { + return format("Symbol '~' not found", name); + }>(); + } + return true; + }()) + #endif + using get_signature_t = std::remove_pointer_t{}))>; + }; + + + template + constexpr auto compilation_result_to_const() { + // --- BYTES --- + constexpr static auto bytes = to_array<[] { return r().bytes; }>(); + + // --- FUNCTIONS --- + constexpr static auto function_count = []() constexpr { + return r().functions.size(); + }(); + constexpr static auto max_params_n = []() constexpr { + std::size_t n{}; + for (auto &&f: r().functions) { + n = std::max(n, f.second.params.size()); + } + return n; + }(); + constexpr static auto functions = []() constexpr { + std::array>, function_count> functions_data{}; + std::size_t i{}; + for (auto &&[key, value]: r().functions) { + functions_data[i++] = (std::make_pair(key, function_info_to_const(value))); + } + return frozen::make_unordered_map(functions_data); + }; + + // 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, + functions() + }; + } +} \ No newline at end of file diff --git a/include/korka/utils/function_traits.hpp b/include/korka/utils/function_traits.hpp index 9e421e4..b667c22 100644 --- a/include/korka/utils/function_traits.hpp +++ b/include/korka/utils/function_traits.hpp @@ -16,9 +16,17 @@ namespace korka { using args_tuple = std::tuple; constexpr static std::size_t args_count = sizeof...(Args); + + /** + * callback(Args*... = nullptr) + */ + static constexpr auto process_args(auto &&callback) { + callback(std::add_pointer_t{}...); + } }; template struct function_traits : function_traits { + }; } \ No newline at end of file diff --git a/include/korka/vm/bytecode_builder.hpp b/include/korka/vm/bytecode_builder.hpp index 22882a0..702768c 100644 --- a/include/korka/vm/bytecode_builder.hpp +++ b/include/korka/vm/bytecode_builder.hpp @@ -70,6 +70,11 @@ namespace korka::vm { emit_op(op_code::call); m_data.write_many(address); } + + constexpr auto emit_trap(vm_external_function_id function_id) { + emit_op(op_code::trap); + m_data.write_many(function_id); + } // // 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/context_base.hpp b/include/korka/vm/context_base.hpp new file mode 100644 index 0000000..5f7c875 --- /dev/null +++ b/include/korka/vm/context_base.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include "korka/utils/function_traits.hpp" +#include "korka/vm/op_codes.hpp" +#include "korka/utils/byte_reader.hpp" +#include "korka/compiler/result.hpp" +#include "korka/vm/value.hpp" +#include "korka/compiler/binding.hpp" +#include +#include +#include +#include + +namespace korka::vm { + template + concept bindings_concepts = requires(const T &t) { + { t.get_callable_by_id(0) }; + }; + + 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_base { + public: + explicit context_base(std::span bytes) + : m_reader(bytes) { + m_stack.reserve(64); + } + + 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()); + } + + template + auto pop() -> type_to_cpp_t { + return unbox>(pop_value()); + } + + protected: + + 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_scopes; + + byte_reader m_reader; + }; +} \ No newline at end of file diff --git a/include/korka/vm/op_codes.hpp b/include/korka/vm/op_codes.hpp index cc0d14e..868183c 100644 --- a/include/korka/vm/op_codes.hpp +++ b/include/korka/vm/op_codes.hpp @@ -60,7 +60,10 @@ namespace korka::vm { // C // Then VM on call puts them into locals call, // - ret + ret, + + // call external function + trap, // }; template @@ -130,4 +133,16 @@ namespace korka::vm { template using type_info_to_cpp_t = decltype(_type_info_to_cpp()); + + + template + constexpr auto cpp_t_to_type_info() -> type_info { + if constexpr (std::is_same_v) { + return {type::void_}; + } else if constexpr (std::is_same_v) { + return {type::i64}; + } else { + static_assert(false, "Unsupported type"); + } + } } \ No newline at end of file diff --git a/include/korka/vm/value.hpp b/include/korka/vm/value.hpp new file mode 100644 index 0000000..0d6ca0d --- /dev/null +++ b/include/korka/vm/value.hpp @@ -0,0 +1,25 @@ +// +// Created by pyxiion on 20/03/2026. +// + +#pragma once +#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"); + + 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); + } +} \ No newline at end of file diff --git a/include/korka/vm/vm_runtime.hpp b/include/korka/vm/vm_runtime.hpp index 52ff753..6b13713 100644 --- a/include/korka/vm/vm_runtime.hpp +++ b/include/korka/vm/vm_runtime.hpp @@ -7,68 +7,22 @@ #include "korka/utils/function_traits.hpp" #include "korka/vm/op_codes.hpp" #include "korka/utils/byte_reader.hpp" -#include "korka/compiler/compiler.hpp" +#include "korka/compiler/result.hpp" +#include "korka/vm/value.hpp" +#include "korka/compiler/binding.hpp" +#include "korka/vm/context_base.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"); - - 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); - } - - 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 { + template> + class context : public context_base { public: - explicit context(std::span bytes) - : m_reader(bytes) { - m_stack.reserve(64); + explicit context(std::span bytes, bindings_t binds = {}) + : context_base(bytes), m_bindings(binds) { + } template> @@ -100,35 +54,17 @@ namespace korka::vm { } } - 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()); + using return_type = typename Traits::return_type; + if constexpr (std::is_void_v) { + return; + } else { + return pop(); + } } - template - auto pop() -> type_to_cpp_t { - return unbox>(pop_value()); - } + protected: + bindings_t m_bindings; - private: auto execute_op() -> op_code { auto initial_pc = m_reader.cursor(); @@ -218,27 +154,16 @@ namespace korka::vm { m_reader.set_cursor(current_scope()->suspension_point); } break; + case op_code::trap: { + auto id = m_reader.read(); + auto func = m_bindings.get_callable_by_id(id); + (*func)(*this); + } + 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_scopes; - - byte_reader m_reader; }; class runtime { diff --git a/main.cpp b/main.cpp index ebcac29..ed16b81 100644 --- a/main.cpp +++ b/main.cpp @@ -1,27 +1,57 @@ +#include "korka/compiler/binding.hpp" #include "korka/compiler/parser.hpp" #include "korka/compiler/compiler.hpp" #include "korka/compiler/ast_walker.hpp" #include "korka/vm/vm_runtime.hpp" #include +#include + +auto fib(std::int64_t n) -> std::int64_t { + if (n == 0) return 0; + if (n == 1) return 1; + + return fib(n - 1) + fib(n - 2); +} + +auto print_n(std::int64_t n) -> void { + std::cout << n << '\n'; +} constexpr char code[] = R"( int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; - return fib(n-1) + fib(n-2); + return fib(n-1) + cpp_fib(n-2); +} + +void print_fib(int n) { + int result = fib(n); + + print_n(result); + return; } )"; -constexpr auto compile_result = korka::compile(); +constexpr auto bindings = korka::make_bindings( + korka::wrap("cpp_fib"), + korka::wrap("print_n") +); +constexpr auto compile_result = korka::compile(); + +constexpr auto script_fib = compile_result.function<"fib">(); +constexpr auto script_print_fib = compile_result.function<"print_fib">(); int main() { - korka::vm::context ctx{compile_result.bytes}; -// auto main_func = compile_result.function<"main">(); - auto fib_func = compile_result.function<"fib">(); + korka::vm::context ctx{compile_result.bytes, bindings}; + + auto result = ctx.call(script_fib, 12L); + std::cout << result << '\n'; + + ctx.call(script_print_fib, 16L); - std::println("{}", ctx.call(fib_func, 12L)); +// 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); }));