refactor(furvm): improve functions

Define native functions inside the module instead of the function
itself (native functions hold only the key to the definition), introduce
private functions, add parameter count to function and fix some bugs.

Closes: #20
Closes: #25
This commit is contained in:
2026-06-28 12:37:36 +02:00
parent 55932f6113
commit 8702298cd2
6 changed files with 157 additions and 54 deletions
+33 -17
View File
@@ -2,10 +2,12 @@
#define FURVM_FUNCTION_HPP
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp" // IWYU pragma: keep
#include <functional>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace furvm {
@@ -19,7 +21,7 @@ enum class function_t : std::uint8_t {
/**
* @brief A native function.
*/
using native_function = std::function<void(executor&)>;
using native_function = std::string;
/**
* @brief A function import.
@@ -35,31 +37,36 @@ public:
* @brief Constructs a normal function.
*
* @param name Name of the function.
* @param paramCount Paremeter count.
* @param position Offset in bytecode of the function.
*/
template <typename Name>
function(Name&& name, bytecode_pos position)
: m_name(std::forward<Name>(name)), m_type(function_t::Normal), m_value(position) {}
function(Name&& name, std::uint32_t paramCount, bytecode_pos position)
: m_name(std::forward<Name>(name)), m_type(function_t::Normal), m_paramCount(paramCount), m_value(position) {}
/**
* @brief Constructs a native function.
*
* @param name Name of the function.
* @param native Native function.
* @param paramCount Paremeter count.
* @param native Native function tag.
*/
template <typename Name>
function(Name&& name, const native_function& native)
: m_name(std::forward<Name>(name)), m_type(function_t::Native), m_value(native) {}
template <typename Name,
typename Native,
typename = std::enable_if_t<std::is_constructible_v<native_function, Native>>>
function(Name&& name, std::uint32_t paramCount, Native&& native)
: m_name(std::forward<Name>(name)),
m_type(function_t::Native),
m_paramCount(paramCount),
m_value(std::forward<Native>(native)) {}
/**
* @brief Constructs an import function.
*
* @param name Name of the function.
* @param paramCount Parameter count.
* @param imp Import function.
*/
template <typename Name>
function(Name&& name, const import_function& imp)
: m_name(std::forward<Name>(name)), m_type(function_t::Import), m_value(imp) {}
function(const mod_h& mod, const function_h& function);
/**
* @brief Destructs a function.
@@ -99,6 +106,13 @@ public:
* @return The type.
*/
constexpr function_t type() const { return m_type; }
/**
* @brief Returns this function's parameter count.
*
* @return The parameter count.
*/
constexpr std::uint32_t param_count() const { return m_paramCount; }
public:
/**
* @brief Returns normal function's value.
@@ -123,15 +137,16 @@ public:
/**
* @brief Returns import function's value.
*
* @return The name.
* @return The value.
*/
const import_function& imp() const {
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
return m_value.imp;
}
private:
std::string m_name;
function_t m_type;
std::string m_name;
function_t m_type;
std::uint32_t m_paramCount;
union value {
std::size_t position = 0;
@@ -143,8 +158,9 @@ private:
value(std::size_t position)
: position(position) {}
value(const native_function& native)
: native(native) {}
template <typename Native, typename = std::enable_if_t<std::is_constructible_v<native_function, Native>>>
value(Native&& native)
: native(std::forward<Native>(native)) {}
value(const import_function& imp)
: imp(imp) {}
+59 -4
View File
@@ -5,6 +5,7 @@
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp"
#include <functional>
#include <ostream>
#include <string>
#include <unordered_map>
@@ -20,6 +21,8 @@ public:
using bytecode_t = std::vector<byte>; /**< An alias to a vector of bytes. */
static constexpr char MAGIC[4] = { 'F', 'u', 'r', 'M' }; /** Furvm module file magic. */
using native_function = std::function<void(executor&)>;
public:
/**
* @brief Constructs a module.
@@ -71,11 +74,29 @@ public:
/**
* @brief Emplaces a function in the module's function container.
*
* Emplaces the function in module's function container and name to function map and public functions map.
*
* @param args Arguments forwarded into the container's emplace_back function.
* @return A handle to the emplaced function.
*/
template <typename... Args>
function_h emplace_function(Args&&... args) {
function_h function = m_functions.emplace_back(std::forward<Args>(args)...);
m_functionMap[function->name()] = function.id();
m_publicFunctions[function->name()] = function.id();
return std::move(function);
}
/**
* @brief Emplaces a function in the module's function container.
*
* Emplaces the function in module's function container and name to function map .
*
* @param args Arguments forwarded into the container's emplace_back function.
* @return A handle to the emplaced function.
*/
template <typename... Args>
function_h emplace_function_private(Args&&... args) {
function_h function = m_functions.emplace_back(std::forward<Args>(args)...);
m_functionMap[function->name()] = function.id();
return std::move(function);
@@ -87,8 +108,18 @@ public:
* @param function Function to insert.
*/
template <typename Function>
void push_back(Function&& function) {
m_functions.emplace_back(std::forward<Function>(function));
auto push_back(Function&& function) {
return emplace_function(std::forward<Function>(function));
}
/**
* @brief Inserts a function in the module's function container.
*
* @param function Function to insert.
*/
template <typename Function>
auto push_back_private(Function&& function) {
return emplace_function_private(std::forward<Function>(function));
}
/**
@@ -115,7 +146,7 @@ public:
*/
template <typename NameFwd>
auto function_at(NameFwd&& name) {
return function_at(m_functionMap.at(std::forward<NameFwd>(name)));
return function_at(m_publicFunctions.at(std::forward<NameFwd>(name)));
}
/**
@@ -126,7 +157,18 @@ public:
*/
template <typename NameFwd>
auto function_at(NameFwd&& name) const {
return function_at(m_functionMap.at(std::forward<NameFwd>(name)));
return function_at(m_publicFunctions.at(std::forward<NameFwd>(name)));
}
/**
* @brief Returns an id of a function from the module.
*
* @param name Name of the function.
* @return An id of the function.
*/
template <typename NameFwd>
auto get_function_id(NameFwd&& name) {
return m_functionMap.at(std::forward<NameFwd>(name));
}
/**
@@ -135,6 +177,16 @@ public:
* @param id Identifier of the function.
*/
void erase_function(function_id id) { m_functions.erase(id); }
public:
template <typename NameFwd, typename Func>
void set_native_function(NameFwd&& name, Func&& func) {
m_nativeFunctions.emplace(std::forward<NameFwd>(name), std::forward<Func>(func));
}
template <typename NameFwd>
native_function get_native_function(NameFwd&& name) const {
return m_nativeFunctions.at(std::forward<NameFwd>(name));
}
public:
/**
* @brief Prints the module in a bytecode form to an output stream.
@@ -147,7 +199,10 @@ private:
bytecode_t m_bytecode;
std::unordered_map<std::string, function_id> m_functionMap;
std::unordered_map<std::string, function_id> m_publicFunctions;
handle_container<function_h> m_functions;
std::unordered_map<std::string, native_function> m_nativeFunctions;
};
} // namespace furvm
+21 -14
View File
@@ -9,18 +9,31 @@
#include <cstdint>
#include <stdexcept>
#include <vector>
namespace furvm {
void executor::push_frame(const mod_h& mod, function function) {
mod_h modInst = mod;
while (function.type() == function_t::Import) {
function = *m_context->module_at(function.imp().mod)->function_at(function.imp().function);
modInst = m_context->module_at(function.imp().mod);
function = *modInst->function_at(function.imp().function);
}
std::vector<thing_h> args;
args.reserve(function.param_count());
for (size_t i = 0; i < function.param_count(); ++i)
args.push_back(pop_thing());
switch (function.type()) {
case function_t::Normal: {
m_frames.emplace((struct executor::frame){ mod, function.position(), m_stack.size() });
m_frames.emplace((struct executor::frame){ mod, function.position(), m_stack.size(), std::move(args) });
} break;
case function_t::Native: {
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), std::move(args) });
modInst->get_native_function(function.name())(*this);
m_frames.pop();
} break;
case function_t::Native:
default: throw std::runtime_error("unexpected function type");
}
}
@@ -54,13 +67,13 @@ thing_h executor::thing() const {
void executor::store_thing(variable_t variable, const thing_h& thing) {
auto& frame = m_frames.top();
frame.variables.resize(variable + 1);
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
frame.variables[variable] = thing;
}
void executor::store_thing(variable_t variable, thing_h&& thing) {
auto& frame = m_frames.top();
frame.variables.resize(variable + 1);
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
frame.variables[variable] = std::move(thing);
}
@@ -160,21 +173,15 @@ void executor::step() {
function_id funcId = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
frame.position += 2;
const function_h& function = frame.mod->function_at(funcId);
switch (function->type()) {
case function_t::Normal:
case function_t::Import: push_frame(frame.mod, *function); break;
case function_t::Native: function->native()(*this); break;
}
push_frame(frame.mod, *frame.mod->function_at(funcId));
} break;
case instruction_t::Jump: {
frame.position += frame.mod->byte(frame.position++);
frame.position += ((std::int8_t)frame.mod->byte(frame.position)) + 1;
} break;
case instruction_t::JumpNotZero: {
byte offset = frame.mod->byte(frame.position++);
auto cond = pop_thing();
if (cond->int32() != 0) frame.position += offset;
if (cond->int32() != 0) frame.position += (std::int8_t)offset;
} break;
case instruction_t::Return: {
pop_frame();
+24 -11
View File
@@ -1,14 +1,21 @@
#include "furvm/function.hpp"
#include "furvm/furvm.hpp"
#include <utility>
namespace furvm {
function::function(const mod_h& mod, const function_h& function)
: m_type(function_t::Import), m_paramCount(0), m_value(import_function{ mod.id(), function.id() }) {}
function::~function() {
switch (m_type) {
case function_t::Normal:
case function_t::Native:
default: break;
case function_t::Native: {
m_value.native.~native_function();
} break;
case function_t::Import: {
m_value.imp.~import_function();
} break;
@@ -16,7 +23,7 @@ function::~function() {
}
function::function(function&& other) noexcept
: m_name(std::move(other.m_name)), m_type(other.m_type) {
: m_name(std::move(other.m_name)), m_type(other.m_type), m_paramCount(other.m_paramCount) {
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -25,8 +32,9 @@ function::function(function&& other) noexcept
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
case function_t::Import: {
m_value.imp = std::move(other.m_value.imp);
new (&m_value.imp) import_function(std::move(other.m_value.imp));
} break;
default: break;
}
other.m_value.position = 0;
}
@@ -34,8 +42,9 @@ function::function(function&& other) noexcept
function& function::operator=(function&& other) noexcept {
if (this == &other) return *this;
m_name = std::move(other.m_name);
m_type = other.m_type;
m_name = std::move(other.m_name);
m_type = other.m_type;
m_paramCount = other.m_paramCount;
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -44,8 +53,9 @@ function& function::operator=(function&& other) noexcept {
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
case function_t::Import: {
m_value.imp = std::move(other.m_value.imp);
new (&m_value.imp) import_function(std::move(other.m_value.imp));
} break;
default: break;
}
other.m_value.position = 0;
@@ -53,7 +63,7 @@ function& function::operator=(function&& other) noexcept {
}
function::function(const function& other)
: m_name(other.m_name), m_type(other.m_type) {
: m_name(other.m_name), m_type(other.m_type), m_paramCount(other.m_paramCount) {
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -62,16 +72,18 @@ function::function(const function& other)
new (&m_value.native) native_function(other.m_value.native);
} break;
case function_t::Import: {
m_value.imp = other.m_value.imp;
new (&m_value.imp) import_function(other.m_value.imp);
} break;
default: break;
}
}
function& function::operator=(const function& other) {
if (this == &other) return *this;
m_name = other.m_name;
m_type = other.m_type;
m_name = other.m_name;
m_type = other.m_type;
m_paramCount = other.m_paramCount;
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -80,8 +92,9 @@ function& function::operator=(const function& other) {
new (&m_value.native) native_function(other.m_value.native);
} break;
case function_t::Import: {
m_value.imp = other.m_value.imp;
new (&m_value.imp) import_function(other.m_value.imp);
} break;
default: break;
}
return *this;
+16 -5
View File
@@ -13,22 +13,33 @@ static constexpr std::array<furvm::byte, 8> s_bytecode = {
67,
furvm::byte(furvm::instruction_t::Clone),
furvm::byte(furvm::instruction_t::Add),
furvm::byte(furvm::instruction_t::Call),
1,
0,
furvm::byte(furvm::instruction_t::Return),
};
int main(void) {
auto context = std::make_shared<furvm::context>();
furvm::mod_h mainModule = context->emplace_module("main", s_bytecode.begin(), s_bytecode.end());
furvm::function_h mainFunc = mainModule->emplace_function("main", 0);
furvm::mod_h furlangModule = context->emplace_module("furlang");
furvm::function_h printFunc = furlangModule->emplace_function("print", 1, "print");
furlangModule->set_native_function("print", [](furvm::executor& executor) {
furvm::thing_h thing = executor.load_thing(0);
switch (thing->type()) {
case furvm::thing_t::Int32: {
std::cout << thing->int32() << '\n';
} break;
}
});
mainModule->serialize(std::cout);
furvm::mod_h mainModule = context->emplace_module("main", s_bytecode.begin(), s_bytecode.end());
furvm::function_h mainFunc = mainModule->emplace_function("main", 0, 0);
mainModule->emplace_function(furlangModule, printFunc).dispatch();
furvm::executor_h executor = context->emplace_executor(context);
executor->push_frame(mainModule, *mainFunc);
static constexpr std::size_t FPC = 3; // Frames per collection
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
executor->step();
}
+4 -3
View File
@@ -12,9 +12,10 @@ std::ostream& mod::serialize(std::ostream& os) const {
detail::serialize(os, std::uint32_t(0)); // version
detail::serialize(os, function_id(m_functionMap.size()));
for (const auto& [name, id] : m_functionMap) {
detail::serialize(os, name);
function_h func = m_functions.at(id);
for (auto* pair : m_functions) {
function_h func = { pair };
bool isPublic = m_publicFunctions.find(func->name()) != m_publicFunctions.end();
detail::serialize(os, isPublic ? func->name() : ""); // private functions have empty names
detail::serialize(os, std::uint8_t(func->type()));
switch (func->type()) {
case function_t::Normal: {