Compare commits

...

37 Commits

Author SHA1 Message Date
CHatingPython c1b65838b0 Merge remote-tracking branch 'origin/master' 2026-06-12 09:13:35 +02:00
CHatingPython f0fe6b4179 feat(furvm): add import function 2026-06-12 09:03:46 +02:00
CHatingPython 3f6d0adef6 feat(furvm): add call instruction 2026-06-11 15:09:30 +02:00
CHatingPython 6ca5fcc995 fix(furvm): change equals to not equals in function's position and native 2026-06-11 15:09:00 +02:00
CHatingPython ba39f5ab27 feat(furvm): add basic function 2026-06-11 14:50:51 +02:00
CHatingPython 5472b77ea0 refactor(furvm): move thing operators to thing.cpp 2026-06-11 12:18:47 +02:00
CHatingPython 00ec0b8f52 build: set cmake C++ standard 2026-06-11 12:15:59 +02:00
CHatingPython 9021ae0089 feat(furvm): add collect function to context 2026-06-10 20:35:05 +02:00
CHatingPython 4d935ef75f feat(furvm): add operation instructions 2026-06-10 20:02:31 +02:00
CHatingPython 0534359503 feat(furvm): add clone function to thing
Closes #9
2026-06-10 19:32:07 +02:00
CHatingPython efe028a54f chore: improve the pre-commit hook 2026-06-10 19:30:15 +02:00
CHatingPython 1e19d83d83 feat(furvm): implement duplicate instruction 2026-06-10 19:20:38 +02:00
CHatingPython 8afeabfd69 refactor(furvm): future-proofing thing ctor and dtor 2026-06-10 16:05:53 +02:00
CHatingPython a4fd939cd2 feat(furvm): add reference counting
Refs: #10
2026-06-10 15:42:45 +02:00
CHatingPython 26d77bdc02 refactor(furvm): use generational indexes for thing
Refs: #10
2026-06-10 15:42:09 +02:00
CHatingPython d86f38d166 docs: document stack underflow exception 2026-06-10 15:10:35 +02:00
CHatingPython 874d1f14cb refactor(furvm): use an arena allocator for thing data
Refs: #10
2026-06-10 15:09:35 +02:00
CHatingPython 48c6da1134 feat(furvm): add stack underflow exception 2026-06-07 12:02:36 +02:00
CHatingPython 464b24eeed feat(furvm): implement return value instruction 2026-06-07 11:43:38 +02:00
CHatingPython e7e5f36380 feat(furvm): add step function to executor 2026-06-07 10:58:51 +02:00
CHatingPython bfdbbb0d5f refactor(furvm): better thing implementation 2026-06-06 23:16:59 +02:00
CHatingPython daf16c18e1 refactor(furvm): add shared pointer aliases 2026-06-06 23:08:20 +02:00
CHatingPython 6824015728 refactor(furvm): implement thing constructor 2026-06-06 23:05:34 +02:00
CHatingPython a077457636 feat(furvm): add a thing 2026-06-06 22:52:25 +02:00
CHatingPython 5ace40b6aa refactor(furvm): change unique pointer to shared pointer for module 2026-06-06 22:51:44 +02:00
CHatingPython a869f71f3a feat(furvm): introduce executor 2026-06-06 21:05:17 +02:00
CHatingPython 16b6b9549c feat(furvm): add push byte as int instruction 2026-06-06 17:49:49 +02:00
CHatingPython c6ec18aeb8 feat(furvm): add return instructions 2026-06-06 17:43:35 +02:00
CHatingPython 4895489511 refactor(furvm): add id to module 2026-06-06 12:53:31 +02:00
CHatingPython 5407f8cda7 refactor(furvm): remove function from fwd.hpp 2026-06-05 18:31:06 +02:00
CHatingPython 2030b66c7d docs: add missing alias comment 2026-06-05 18:17:05 +02:00
CHatingPython 0304911516 refactor: add bytecode to module 2026-06-05 18:16:54 +02:00
CHatingPython 6481c9c71d refactor(furvm): change instruction_t base to furvm::byte 2026-06-05 18:14:25 +02:00
CHatingPython 6605b3264f docs: fix typo in PushConstant instruction 2026-06-05 18:06:54 +02:00
CHatingPython 66fdd1dc9c feat(furvm): introduce module, instruction and constant 2026-06-05 17:55:40 +02:00
CHatingPython 2f9328d85a feat(furvm): introduce context 2026-06-05 12:17:00 +02:00
CHatingPython 4a3ce6040c chore: init furvm 2026-06-04 11:06:31 +02:00
19 changed files with 1624 additions and 5 deletions
+2 -4
View File
@@ -13,10 +13,8 @@ if [ -z "$FILES" ]; then
exit 0 exit 0
fi fi
for file in $FILES; do git diff -U0 --cached | \
clang-format -i "$file" python3 <(curl -s https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang/tools/clang-format/clang-format-diff.py) -p1 -i
git add "$file"
done
echo "Running clang-tidy..." echo "Running clang-tidy..."
+5 -1
View File
@@ -8,7 +8,11 @@ find_package(GTest REQUIRED)
find_package(Doxygen) find_package(Doxygen)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(furlang) add_subdirectory(furlang)
add_subdirectory(furvm)
add_subdirectory(furc) add_subdirectory(furc)
if(DOXYGEN_FOUND) if(DOXYGEN_FOUND)
@@ -17,7 +21,7 @@ if(DOXYGEN_FOUND)
set(DOXYGEN_HIDE_UNDOC_NAMESPACES NO) set(DOXYGEN_HIDE_UNDOC_NAMESPACES NO)
set(DOXYGEN_HIDE_UNDOC_RELATIONS NO) set(DOXYGEN_HIDE_UNDOC_RELATIONS NO)
set(DOXYGEN_FILE_PATTERNS "*.hpp") set(DOXYGEN_FILE_PATTERNS "*.hpp")
doxygen_add_docs(doxygen_doc ${CMAKE_SOURCE_DIR}/furc/ ${CMAKE_SOURCE_DIR}/furlang/ doxygen_add_docs(doxygen_doc ${CMAKE_SOURCE_DIR}/furlang/ ${CMAKE_SOURCE_DIR}/furvm/ ${CMAKE_SOURCE_DIR}/furc/
ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ALL WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen") COMMENT "Generating API documentation with Doxygen")
endif() endif()
+16
View File
@@ -0,0 +1,16 @@
file(GLOB_RECURSE FURVM_SRCS "src/**.cpp")
file(GLOB_RECURSE FURVM_HDRS "include/**.hpp")
add_library(libfurvm ${FURVM_SRCS} ${FURVM_HDRS})
target_include_directories(libfurvm PUBLIC include/)
target_compile_definitions(libfurvm PRIVATE LIBFURVM)
set_target_properties(libfurvm PROPERTIES PREFIX "")
target_link_libraries(libfurvm PUBLIC furlang)
add_executable(furvm src/main.cpp)
target_link_libraries(furvm PRIVATE libfurvm)
include(GoogleTest)
file(GLOB_RECURSE FURVM_TESTS "test/**.cpp")
add_executable(furvm_tests ${FURVM_TESTS})
target_link_libraries(furvm_tests PRIVATE libfurvm GTest::gtest_main)
gtest_discover_tests(furvm_tests)
+112
View File
@@ -0,0 +1,112 @@
#ifndef FURVM_CONSTANT_HPP
#define FURVM_CONSTANT_HPP
#include "furvm/fwd.hpp"
#include <exception>
#include <string_view>
namespace furvm {
/**
* @brief Bad constant access exception.
*/
class bad_constant_access : public std::exception {
public:
bad_constant_access() = default;
~bad_constant_access() override = default;
/**
* @brief Move constructor.
*/
bad_constant_access(bad_constant_access&&) noexcept = default;
/**
* @brief Move constructor.
*/
bad_constant_access& operator=(bad_constant_access&&) noexcept = default;
/**
* @brief Copy constructor.
*/
bad_constant_access(const bad_constant_access&) = default;
/**
* @brief Copy constructor.
*/
bad_constant_access& operator=(const bad_constant_access&) = default;
public:
/**
* @brief Returns a C-style string describing the cause of the error.
*
* @return The cause of the error.
*/
const char* what() const noexcept override { return "bad constant access"; }
};
enum class constant_t : std::uint8_t {
String, /**< String constant. */
};
class constant {
public:
using string_type = std::string_view; /**< String constant type. */
public:
/**
* @brief Construct a new string constant.
*
* @param string String.
*/
constant(string_type string)
: m_type(constant_t::String), m_value(string) {}
~constant() = default;
/**
* @brief Move constructor.
*/
constant(constant&&) = default;
/**
* @brief Move constructor.
*/
constant& operator=(constant&&) = default;
constant(const constant&) = delete;
constant& operator=(const constant&) = delete;
public:
/**
* @brief Returns this constant's type.
* @see constant_t
*
* @return The constant type.
*/
constexpr constant_t type() const { return m_type; }
/**
* @brief Returns this constant's string value.
* @throws bad_constant_access if this constant's type is not constant_t::String.
*
* @return The string value.
*/
constexpr string_type string() const {
require_type(constant_t::String);
return m_value.string;
}
private:
void require_type(constant_t type) const {
if (m_type != type) throw bad_constant_access();
}
private:
constant_t m_type{};
union value {
string_type string;
value(string_type sv)
: string(sv) {}
} m_value;
};
} // namespace furvm
#endif // FURVM_CONSTANT_HPP
+113
View File
@@ -0,0 +1,113 @@
#ifndef FURVM_CONTEXT_HPP
#define FURVM_CONTEXT_HPP
#include "furlang/arena.hpp"
#include "furvm/fwd.hpp"
#include "furvm/module.hpp"
#include <queue>
#include <type_traits>
#include <vector>
namespace furvm {
class context {
public:
friend class executor;
friend class thing;
public:
context();
~context() = default;
/**
* @brief Move constructor.
*/
context(context&&) noexcept = default;
/**
* @brief Move constructor.
*/
context& operator=(context&&) noexcept = default;
context(const context&) = delete;
context& operator=(const context&) = delete;
public:
/**
* @brief Adds a module to this context.
*
* @param args Arguments to forward to module's constructor.
* @return An index to the emplaced module.
*/
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<mod, module_handle, Args...>>>
constexpr const auto& emplace(Args&&... args) {
module_handle id = static_cast<module_handle>(m_modules.size());
return m_modules.emplace_back(std::make_unique<mod>(id, std::forward<Args>(args)...));
}
/**
* @brief Erases a module from this context.
*
* @param index Index to the module.
* @return Old value.
*/
mod_p erase(module_handle index) {
if (index >= m_modules.size()) return nullptr;
return std::move(m_modules[index]);
}
/**
* @brief Returns a module of this context.
*
* @param index Position of the module.
* @return The module.
*/
constexpr mod_p& operator[](module_handle index) { return m_modules[index]; }
/**
* @brief Returns a module of this context.
*
* @param index Position of the module.
* @return The module.
*/
constexpr const mod_p& operator[](module_handle index) const { return m_modules[index]; }
/**
* @brief Returns a module of this context.
*
* @param index Position of the module.
* @return The module.
*/
constexpr mod_p& at(module_handle index) { return m_modules.at(index); }
/**
* @brief Returns a module of this context.
*
* @param index Position of the module.
* @return The module.
*/
constexpr const mod_p& at(module_handle index) const { return m_modules.at(index); }
/**
* @brief Returns how many does this context have modules.
*
* @return The module count.
*/
constexpr size_t size() const { return m_modules.size(); }
public:
/**
* @brief Removes unreferenced things from the thing list.
*/
void collect();
private:
std::vector<mod_p> m_modules;
std::vector<thing_p> m_things;
std::vector<executor_p> m_executors;
std::queue<thing_handle> m_deadThings;
std::vector<void*> m_deadThingData;
furlang::arena m_thingArena;
};
} // namespace furvm
#endif // FURVM_CONTEXT_HPP
+43
View File
@@ -0,0 +1,43 @@
#ifndef FURVM_EXCEPTIONS_HPP
#define FURVM_EXCEPTIONS_HPP
#include <exception>
namespace furvm {
class stack_underflow : public std::exception {
public:
stack_underflow() = default;
~stack_underflow() override = default;
/**
* @brief Move constructor.
*/
stack_underflow(stack_underflow&&) noexcept = default;
/**
* @brief Move constructor.
*/
stack_underflow& operator=(stack_underflow&&) noexcept = default;
/**
* @brief Copy constructor.
*/
stack_underflow(const stack_underflow&) = default;
/**
* @brief Copy constructor.
*/
stack_underflow& operator=(const stack_underflow&) = default;
public:
/**
* @brief Returns a C-style string describing the cause of the error.
*
* @return The cause of the error.
*/
const char* what() const noexcept override { return "stack underflow"; }
};
} // namespace furvm
#endif // FURVM_EXCEPTIONS_HPP
+158
View File
@@ -0,0 +1,158 @@
#ifndef FURVM_EXECUTOR_HPP
#define FURVM_EXECUTOR_HPP
#include "furvm/fwd.hpp"
#include <stack>
namespace furvm {
enum class executor_flags : std::uint32_t {
Suspended = (1 << 0), /**< Execution suspended. */
Done = (1 << 1), /**< Execution is finished. */
};
static inline executor_flags operator|(executor_flags lhs, executor_flags rhs) {
return executor_flags(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}
static inline executor_flags operator&(executor_flags lhs, executor_flags rhs) {
return executor_flags(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}
static inline executor_flags operator~(executor_flags flags) {
return executor_flags(~static_cast<std::uint32_t>(flags));
}
class executor {
private:
/**
* @brief A private token for the private constructor.
*
* Also `egzekutor` in Polish translates to `executor` I think.
*/
struct egzekutor {
explicit egzekutor() = default;
};
public:
/**
* @brief Executor frame.
*
* Call frame.
*/
struct frame {
mod_p mod; /**< Shared pointer to a module with the bytecode. */
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
};
public:
/**
* @brief Private constructor.
*
* @param id
* @param context
*/
executor(egzekutor, executor_handle id, const context_p& context);
~executor();
/**
* @brief Move constructor.
*/
executor(executor&&) noexcept = default;
/**
* @brief Move constructor.
*/
executor& operator=(executor&&) noexcept = default;
executor(const executor&) = delete;
executor& operator=(const executor&) = delete;
public:
/**
* @brief Returns a new executor.
*
* @param context Furvm context.
* @return Shared pointer to the new executor.
*/
static executor_p create(const context_p& context);
public:
/**
* @brief Returns an id of this executor.
*
* @return The id.
*/
executor_handle id() const { return m_id; }
/**
* @brief Returns flags of this executor.
*
* @return The flags.
*/
executor_flags flags() const { return m_flags; }
public:
/**
* @brief Pushes a new frame.
*
* @param function Function.
*/
void push_frame(const function_p& function);
/**
* @brief Pops the top frame.
*
* @return The popped frame.
*/
frame pop_frame();
/**
* @brief Returns the top frame.
*
* @return The frame.
*/
frame frame() const;
public:
/**
* @brief Pushes a thing onto the stack.
*
* @param thing The thing to push.
*/
void push_thing(const thing_p& thing);
/**
* @brief Pushes a thing onto the stack.
*
* @param thing The thing to push.
*/
void push_thing(thing_p&& thing);
/**
* @brief Pops a thing from the stack.
*
* @return The popped thing.
*/
thing_p pop_thing();
/**
* @brief Returns the top thing on the stack.
*
* @return The thing.
*/
thing_p thing() const;
public:
/**
* @brief Executes next instruction.
*/
void step();
private:
executor_handle m_id;
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
context_p m_context;
std::stack<struct frame> m_frames;
std::stack<thing_p> m_stack;
};
} // namespace furvm
#endif // FURVM_EXECUTOR_HPP
+176
View File
@@ -0,0 +1,176 @@
#ifndef FURVM_FUNCTION_HPP
#define FURVM_FUNCTION_HPP
#include "furvm/fwd.hpp"
#include "furvm/module.hpp" // IWYU pragma: keep
#include <functional>
#include <stdexcept>
namespace furvm {
enum class function_t : std::uint8_t {
Normal = 0, /**< A normal bytecode function. */
Native, /**< A native function implemented through furvm API. */
Import, /**< A function imported from another module. */
};
using native_function = std::function<void(executor&)>;
class function {
private:
/**
* @brief A private token for the private constructor.
*
* Also `funkcja` in Polish translates to `function` from what I heard.
*/
struct funkcja {
explicit funkcja() = default;
};
public:
/**
* @brief Private constructor.
*
* @param id
* @param position
* @param mod
*/
function(funkcja, function_handle id, std::size_t position, const mod_p& mod);
/**
* @brief Private constructor.
*
* @param id
* @param native
* @param mod
*/
function(funkcja, function_handle id, const native_function& native, const mod_p& mod);
~function() = default;
/**
* @brief Move constructor.
*/
function(function&&) noexcept;
/**
* @brief Move constructor.
*/
function& operator=(function&&) noexcept;
function(const function&) = delete;
function& operator=(const function&) = delete;
public:
/**
* @brief Returns a new function.
*
* @param mod Module.
* @param args Arguments to pass to the function constructor.
* @return The new function.
*/
template <typename... Args,
typename = std::enable_if_t<std::is_constructible_v<function, funkcja, function_handle, Args..., const mod_p&>>>
static function_p create(const mod_p& mod, Args&&... args) {
function_handle id = mod->m_functions.size();
auto func = std::make_shared<function>(funkcja{}, id, std::forward<Args>(args)..., mod);
mod->m_functions.emplace(mod->m_functions.begin() + id, func);
return std::move(func);
}
public:
/**
* @brief Returns an id of this function.
*
* @return The id.
*/
constexpr function_handle id() const { return m_id; }
/**
* @brief Returns a type of this function.
*
* @return The type.
*/
constexpr function_t type() const { return m_type; }
/**
* @brief Returns a parent module of this function.
*
* @return A shared pointer to the module.
*/
const mod_p& mod() const { return m_module; }
public:
/**
* @brief Returns a value for normal function.
*
* @return The value.
*/
std::size_t position() const {
if (m_type != function_t::Normal) throw std::runtime_error("function type mismatch");
return m_value.position;
}
/**
* @brief Returns a value for native function.
*
* @return The value.
*/
const native_function& native() const {
if (m_type != function_t::Native) throw std::runtime_error("function type mismatch");
return m_value.native;
}
/**
* @brief Returns a module of imported function.
*
* @return A handle to the module.
*/
module_handle imported_module() const {
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
return m_value.imp.mod;
}
/**
* @brief Returns a module of imported function.
*
* @return A handle to the module.
*/
function_handle imported_function() const {
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
return m_value.imp.function;
}
private:
function_handle m_id;
function_t m_type;
mod_p m_module;
union value {
std::size_t position = 0;
native_function native;
struct {
module_handle mod;
function_handle function;
} imp;
value() = default;
value(std::size_t position)
: position(position) {}
value(const native_function& native)
: native(native) {}
value(module_handle mod, function_handle function)
: imp({ mod, function }) {}
~value() {}
value(value&& other) = delete;
value& operator=(value&& other) = delete;
value(const value& other) = delete;
value& operator=(const value& other) = delete;
} m_value;
};
} // namespace furvm
#endif // FURVM_FUNCTION_HPP
+184
View File
@@ -0,0 +1,184 @@
#ifndef FURVM_FWD_HPP
#define FURVM_FWD_HPP
#include <cstddef> // IWYU pragma: export
#include <cstdint> // IWYU pragma: export
#include <memory>
/**
* @brief Furlang's virtual machine.
*/
namespace furvm {
/**
* @brief A byte.
*
* There's nothing more to it.
*/
using byte = std::uint8_t;
// constant.hpp
/**
* @brief Constant index.
*
* An index to the constant in module's constant pool.
*/
using constant_index = std::uint16_t;
/**
* @enum constant_t
* @brief Constant type.
*/
enum class constant_t : std::uint8_t;
/**
* @class constant
* @brief Constant.
*/
class constant;
// instruction.hpp
/**
* @enum instruction_t
* @brief Furvm's instruction type.
*/
enum class instruction_t : byte;
/**
* @struct instruction
* @brief Furvm's instruction.
*/
struct instruction;
// function.hpp
/**
* @enum function_t
* @brief Function type.
*/
enum class function_t : std::uint8_t;
/**
* @class function
* @brief Function.
*
* A furvm function.
*/
class function;
/**
* @brief An alias to a function shared pointer.
*/
using function_p = std::shared_ptr<function>;
/**
* @brief Furvm function's index.
*/
using function_handle = std::uint16_t;
// module.hpp
/**
* @class mod
* @brief Module.
*
* A furvm module. Translation unit of furlang.
*/
class mod;
/**
* @brief An alias to a module shared pointer.
*/
using mod_p = std::shared_ptr<mod>;
/**
* @brief Furvm module's index.
*/
using module_handle = std::uint32_t;
// thing.hpp
/**
* @enum thing_t
* @brief Thing type.
*/
enum class thing_t : std::uint8_t;
/**
* @class bad_thing_access
* @brief Bad thing access exception.
*/
class bad_thing_access;
/**
* @class thing
* @brief Furvm thing.
*
* A stack element. Think of it like of a value in C++ or I guess a class in java.
*/
class thing;
/**
* @brief An alias to a thing shared pointer.
*/
using thing_p = std::shared_ptr<thing>;
/**
* @brief Furvm thing's index.
*/
using thing_handle = std::uint32_t;
// executor.hpp
/**
* @enum executor_flags
* @brief Flags of an executor.
*/
enum class executor_flags : std::uint32_t;
/**
* @class executor
* @brief Furvm executor.
*
* Furvm executors are like threads.
*/
class executor;
/**
* @brief An alias to a executor shared pointer.
*/
using executor_p = std::shared_ptr<executor>;
/**
* @brief Furvm executor's index.
*/
using executor_handle = std::uint32_t;
// context.hpp
/**
* @class context
* @brief Context.
*
* A furvm context.
*/
class context;
/**
* @brief An alias to a context shared pointer.
*/
using context_p = std::shared_ptr<context>;
// exceptions.hpp:
/**
* @class stack_underflow
* @brief Stack underflow exception.
*/
class stack_underflow;
} // namespace furvm
#endif // FURVM_FWD_HPP
+99
View File
@@ -0,0 +1,99 @@
#ifndef FURVM_INSTRUCTION_HPP
#define FURVM_INSTRUCTION_HPP
#include "furvm/fwd.hpp"
namespace furvm {
enum class instruction_t : byte {
/**
* @brief No operation.
*/
NoOperation = 0,
/**
* @brief Pushes an integer from a byte onto the stack.
*
* Pushes an integer constructed from a next byte onto the stack.
*/
PushB2I,
/**
* @brief Pushes a constant onto the stack.
*
* Pushes a constant from the constant pool denoted by two next bytes in little-endian onto the stack.
*/
PushConstant,
/**
* @brief Pops top element from the stack.
*/
Drop,
/**
* @brief Duplicates top element on the stack.
*/
Duplicate,
/**
* @brief Clones top element on the stack.
*/
Clone,
/**
* @brief Adds two things together on the stack.
*/
Add,
/**
* @brief Subtracts two things together on the stack.
*/
Sub,
/**
* @brief Multiplies two things together on the stack.
*/
Mul,
/**
* @brief Divides two things together on the stack.
*/
Div,
/**
* @brief Modulos two things together on the stack.
*/
Mod,
/**
* @brief Calls a function.
*
* Calls a function denoted by next two bytes in little-endian from current frame's module.
*/
Call,
/**
* @brief Pops the current call frame.
*/
Return,
/**
* @brief Pops the current call frame and pushes the first element from the previous stack onto the new stack.
*/
ReturnValue,
};
struct instruction {
instruction_t type; /**< Instruction type. */
/**
* @brief Instruction value.
*/
union value {
constant_index constant; /**< Constant instruction argument. */
} value; /**< Instruction value. */
};
} // namespace furvm
#endif // FURVM_INSTRUCTION_HPP
+70
View File
@@ -0,0 +1,70 @@
#ifndef FURVM_MODULE_HPP
#define FURVM_MODULE_HPP
#include "furvm/fwd.hpp"
#include <vector>
namespace furvm {
class mod {
friend class function;
public:
using bytecode_t = std::vector<byte>; /**< An alias to a vector of bytes. */
public:
/**
* @brief Construct a new module.
*
* @param id Id of this module.
* @param args Arguments to forward to bytecode's constructor.
*/
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<bytecode_t, Args...>>>
mod(module_handle id, Args&&... args)
: m_id(id), m_bytecode(std::forward<Args>(args)...) {}
~mod() = default;
/**
* @brief Move constructor.
*/
mod(mod&&) = default;
/**
* @brief Move constructor.
*/
mod& operator=(mod&&) = default;
mod(const mod&) = delete;
mod& operator=(const mod&) = delete;
public:
/**
* @brief Returns an id of this module.
*
* @return The id.
*/
constexpr module_handle id() const { return m_id; }
/**
* @brief Returns a byte from bytecode of this module.
*
* @param offset An offset of the byte.
* @return The byte.
*/
constexpr byte byte(std::size_t offset) const { return m_bytecode.at(offset); }
public:
/**
* @brief Returns a function from this module.
*
* @param id Id of the function.
* @return The function.
*/
constexpr const function_p& function_at(function_handle id) const { return m_functions.at(id); }
private:
module_handle m_id;
bytecode_t m_bytecode;
std::vector<function_p> m_functions;
};
} // namespace furvm
#endif // FURVM_MODULE_HPP
+211
View File
@@ -0,0 +1,211 @@
#ifndef FURVM_THING_HPP
#define FURVM_THING_HPP
#include "furvm/context.hpp" // IWYU pragma: keep
#include "furvm/fwd.hpp"
namespace furvm {
enum class thing_t : std::uint8_t {
Int32,
};
/**
* @brief Returns data size of a thing.
*
* @param type Type of the thing.
* @return The data size of the thing.
*/
std::size_t thing_type_size(thing_t type);
class bad_thing_access : public std::exception {
public:
bad_thing_access() = default;
~bad_thing_access() override = default;
/**
* @brief Move constructor.
*/
bad_thing_access(bad_thing_access&&) noexcept = default;
/**
* @brief Move constructor.
*/
bad_thing_access& operator=(bad_thing_access&&) noexcept = default;
/**
* @brief Copy constructor.
*/
bad_thing_access(const bad_thing_access&) = default;
/**
* @brief Copy constructor.
*/
bad_thing_access& operator=(const bad_thing_access&) = default;
public:
/**
* @brief Returns a C-style string describing the cause of the error.
*
* @return The cause of the error.
*/
const char* what() const noexcept override { return "bad thing access"; }
};
class thing final {
friend class executor;
private:
/**
* @brief A private token for the private constructor.
*
* Also `rzecz` in Polish translates to `thing` I think.
*/
struct rzecz {
explicit rzecz() = default;
};
public:
using nref_t = std::size_t; /**< Type of reference count. */
static constexpr thing_handle GENERATION_SIZE = 12; /**< Bit size of generation part in thing_handle. */
public:
/**
* @brief Private constructor.
*
* @param id
* @param type
* @param context
*/
thing(rzecz, thing_handle id, thing_t type, const context_p& context);
~thing();
/**
* @brief Move constructor.
*/
thing(thing&&) noexcept;
/**
* @brief Move constructor.
*/
thing& operator=(thing&&) noexcept;
thing(const thing&) = delete;
thing& operator=(const thing&) = delete;
public:
/**
* @brief Adds two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator+(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Subtracts two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator-(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Multiplies two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator*(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Divides two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator/(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Modulos two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator%(const thing_p& lhs, const thing_p& rhs);
public:
/**
* @brief Returns a new thing.
*
* @param context Furvm context.
* @param args Arguments to forward to the thing constructor.
* @return Shared pointer to the new thing.
*/
template <typename... Args,
typename = std::enable_if_t<std::is_constructible_v<thing, rzecz, thing_handle, Args..., const context_p&>>>
static thing_p create(const context_p& context, Args&&... args) {
thing_handle id = context->m_things.size();
if (!context->m_deadThings.empty()) {
id = context->m_deadThings.front();
context->m_deadThings.pop();
id += 1 << GENERATION_SIZE;
}
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
auto th = std::make_shared<thing>(rzecz{}, id, std::forward<Args>(args)..., context);
context->m_things.emplace(context->m_things.begin() + idx, th);
return std::move(th);
}
/**
* @brief Returns a clone of the thing.
*
* @param thing Thing to clone.
* @return Shared pointer to a clone of the thing.
*/
static thing_p clone(const thing_p& thing);
public:
/**
* @brief Returns an int32 value from this thing.
*
* @return The value.
*/
std::int32_t& int32();
/**
* @brief Returns an int32 value from this thing.
*
* @return The value.
*/
const std::int32_t& int32() const;
public:
/**
* @brief Increments reference count of this thing by one.
*/
void add_reference() { ++m_refCount; }
/**
* @brief Decrements reference count of this thing by one.
*/
void remove_reference() { --m_refCount; }
/**
* @brief Returns reference count of this thing.
*
* @return The reference count.
*/
constexpr nref_t reference_count() const { return m_refCount; }
private:
thing_handle m_id;
thing_t m_type;
context_p m_context;
nref_t m_refCount = 0;
void* m_data = nullptr;
};
} // namespace furvm
#endif // FURVM_THING_HPP
+16
View File
@@ -0,0 +1,16 @@
#include "furvm/context.hpp"
#include "furvm/thing.hpp" // IWYU pragma: keep
namespace furvm {
context::context() {}
void context::collect() {
for (auto& ref : m_things) {
if (ref->reference_count() != 0) continue;
ref = nullptr;
}
}
} // namespace furvm
+141
View File
@@ -0,0 +1,141 @@
#include "furvm/executor.hpp"
#include "furvm/context.hpp" // IWYU pragma: keep
#include "furvm/exceptions.hpp"
#include "furvm/function.hpp" // IWYU pragma: keep
#include "furvm/instruction.hpp"
#include "furvm/thing.hpp"
#include <stdexcept>
namespace furvm {
executor::executor(egzekutor, executor_handle id, const context_p& context)
: m_id(id), m_context(context) {}
executor::~executor() {
m_context->m_executors[m_id] = nullptr;
}
executor_p executor::create(const context_p& context) {
auto ex = std::make_shared<executor>(egzekutor{}, context->m_executors.size(), context);
context->m_executors.push_back(ex);
return std::move(ex);
}
void executor::push_frame(const function_p& function) {
if (function->type() != function_t::Normal) return;
m_frames.emplace((struct executor::frame){ function->mod(), function->position(), m_stack.size() });
}
struct executor::frame executor::pop_frame() {
if (m_frames.empty()) throw stack_underflow();
struct executor::frame frame = m_frames.top();
m_frames.pop();
return frame;
}
struct executor::frame executor::frame() const {
return m_frames.top();
}
void executor::push_thing(const thing_p& thing) {
thing->add_reference();
m_stack.push(thing);
}
void executor::push_thing(thing_p&& thing) {
thing->add_reference();
m_stack.push(std::move(thing));
}
thing_p executor::pop_thing() {
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
thing_p top = std::move(m_stack.top());
m_stack.pop();
top->remove_reference();
return top;
}
thing_p executor::thing() const {
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
return m_stack.top();
}
void executor::step() {
if ((m_flags & executor_flags::Suspended) == executor_flags::Suspended) return;
struct frame& frame = m_frames.top();
instruction_t instr = static_cast<instruction_t>(frame.mod->byte(frame.position++));
switch (instr) {
case instruction_t::NoOperation: break;
case instruction_t::PushB2I: {
auto thing = thing::create(m_context, thing_t::Int32);
thing->int32() = frame.mod->byte(frame.position++);
push_thing(std::move(thing));
} break;
case instruction_t::Drop: {
pop_thing();
} break;
case instruction_t::Duplicate: {
push_thing(thing());
} break;
case instruction_t::Clone: {
push_thing(std::move(thing::clone(thing())));
} break;
case instruction_t::Add: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs + rhs);
} break;
case instruction_t::Sub: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs - rhs);
} break;
case instruction_t::Mul: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs * rhs);
} break;
case instruction_t::Div: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs / rhs);
} break;
case instruction_t::Mod: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs % rhs);
} break;
case instruction_t::Call: {
function_handle 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_p& function = frame.mod->function_at(funcId);
switch (function->type()) {
case function_t::Normal: push_frame(function); break;
case function_t::Native: function->native()(*this); break;
case function_t::Import: {
const mod_p& impMod = m_context->m_modules.at(function->imported_module());
push_frame(impMod->function_at(function->imported_function()));
} break;
}
} break;
case instruction_t::Return: {
pop_frame();
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
} break;
case instruction_t::ReturnValue: {
auto value = pop_thing();
pop_frame();
push_thing(std::move(value));
} break;
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
default: throw std::runtime_error("unknown instruction");
}
}
} // namespace furvm
+43
View File
@@ -0,0 +1,43 @@
#include "furvm/function.hpp"
namespace furvm {
function::function(funkcja, function_handle id, std::size_t position, const mod_p& mod)
: m_id(id), m_type(function_t::Normal), m_module(mod), m_value(position) {}
function::function(funkcja, function_handle id, const native_function& native, const mod_p& mod)
: m_id(id), m_type(function_t::Native), m_module(mod), m_value(native) {}
function::function(function&& other) noexcept
: m_id(other.m_id), m_type(other.m_type), m_module(std::move(other.m_module)) {
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
} break;
case function_t::Native: {
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
}
other.m_value.position = 0;
}
function& function::operator=(function&& other) noexcept {
if (this == &other) return *this;
m_id = other.m_id;
m_type = other.m_type;
m_module = std::move(other.m_module);
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
} break;
case function_t::Native: {
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
}
other.m_value.position = 0;
return *this;
}
} // namespace furvm
+52
View File
@@ -0,0 +1,52 @@
#ifndef LIBFURVM
#include "furvm/context.hpp"
#include "furvm/executor.hpp"
#include "furvm/function.hpp"
#include "furvm/instruction.hpp"
#include "furvm/thing.hpp"
#include <array>
#include <cstddef>
#include <iostream>
#include <memory>
static constexpr std::array<furvm::byte, 8> s_bytecode = {
furvm::byte(furvm::instruction_t::PushB2I),
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),
};
void print(furvm::executor& exec) {
std::cout << exec.pop_thing()->int32() << '\n';
}
int main(void) {
auto context = std::make_shared<furvm::context>();
auto mainModule = context->emplace(s_bytecode.begin(), s_bytecode.end());
auto mainFunction = furvm::function::create(mainModule, 0);
auto printFunction = furvm::function::create(mainModule, print);
auto executor = furvm::executor::create(context);
executor->push_frame(mainFunction);
static constexpr std::size_t FPC = 3; // Frames per collection
std::size_t count = 0;
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
executor->step();
if ((++count % FPC) == 0) context->collect();
}
return 0;
}
#endif // LUBFURVM
+3
View File
@@ -0,0 +1,3 @@
#include "furvm/module.hpp"
namespace furvm {}
+177
View File
@@ -0,0 +1,177 @@
// NOLINTBEGIN(cppcoreguidelines-no-malloc)
#include "furvm/thing.hpp"
#include "furvm/context.hpp" // IWYU pragma: keep
#include <cstdlib>
#include <cstring>
#include <stdexcept>
namespace furvm {
std::size_t thing_type_size(thing_t type) {
switch (type) {
case thing_t::Int32: return 4;
}
return 0;
}
thing::thing(rzecz, thing_handle id, thing_t type, const context_p& context)
: m_id(id), m_type(type), m_context(context) {
std::size_t size = thing_type_size(type);
std::byte* data = nullptr;
if (!m_context->m_deadThingData.empty()) {
thing_t itType{};
for (auto it = m_context->m_deadThingData.rbegin(); it != m_context->m_deadThingData.rend(); ++it) {
std::memcpy(&itType, static_cast<std::byte*>(*it) - sizeof(itType), sizeof(itType));
if (size == thing_type_size(itType)) {
data = static_cast<std::byte*>(*it);
m_context->m_deadThingData.erase(std::next(it).base());
break;
}
}
}
if (data == nullptr) data = m_context->m_thingArena.allocate<std::byte>(sizeof(type) + size);
if (data == nullptr) throw std::runtime_error("failed to allocate data for new thing");
std::memcpy(data, &type, sizeof(type));
m_data = data + sizeof(type);
switch (m_type) {
// Primitives are zero-initialized.
default:
case thing_t::Int32: std::memset(m_data, 0, size);
}
}
thing::~thing() {
switch (m_type) {
// Primitives are not destructed.
default:
case thing_t::Int32: break;
}
m_context->m_deadThingData.push_back(m_data);
}
thing::thing(thing&& other) noexcept
: m_id(other.m_id),
m_type(other.m_type),
m_context(std::move(other.m_context)),
m_refCount(other.m_refCount),
m_data(other.m_data) {
other.m_id = {};
other.m_type = {};
other.m_refCount = {};
other.m_data = nullptr;
}
thing& thing::operator=(thing&& other) noexcept {
if (this == &other) return *this;
m_id = other.m_id;
m_type = other.m_type;
m_context = std::move(other.m_context);
m_refCount = other.m_refCount;
m_data = other.m_data;
other.m_id = {};
other.m_type = {};
other.m_refCount = {};
other.m_data = nullptr;
return *this;
}
static constexpr std::uint16_t thing_type_pair(thing_t lhs, thing_t rhs) {
return (static_cast<std::uint16_t>(lhs) << 8) | static_cast<std::uint16_t>(rhs);
}
thing_p operator+(const thing_p& lhs, const thing_p& rhs) {
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
auto res = thing::create(lhs->m_context, thing_t::Int32);
res->int32() = lhs->int32() + rhs->int32();
return res;
}
default: throw std::runtime_error("unexpected operator");
}
}
thing_p operator-(const thing_p& lhs, const thing_p& rhs) {
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
auto res = thing::create(lhs->m_context, thing_t::Int32);
res->int32() = lhs->int32() - rhs->int32();
return res;
}
default: throw std::runtime_error("unexpected operator");
}
}
thing_p operator*(const thing_p& lhs, const thing_p& rhs) {
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
auto res = thing::create(lhs->m_context, thing_t::Int32);
res->int32() = lhs->int32() * rhs->int32();
return res;
}
default: throw std::runtime_error("unexpected operator");
}
}
thing_p operator/(const thing_p& lhs, const thing_p& rhs) {
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
auto res = thing::create(lhs->m_context, thing_t::Int32);
res->int32() = lhs->int32() / rhs->int32();
return res;
}
default: throw std::runtime_error("unexpected operator");
}
}
thing_p operator%(const thing_p& lhs, const thing_p& rhs) {
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
auto res = thing::create(lhs->m_context, thing_t::Int32);
res->int32() = lhs->int32() % rhs->int32();
return res;
}
default: throw std::runtime_error("unexpected operator");
}
}
thing_p thing::clone(const thing_p& thing) {
thing_handle id = thing->m_context->m_things.size();
if (!thing->m_context->m_deadThings.empty()) {
id = thing->m_context->m_deadThings.front();
thing->m_context->m_deadThings.pop();
id += 1 << GENERATION_SIZE;
}
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
auto th = std::make_shared<class thing>(rzecz{}, id, thing->m_type, thing->m_context);
switch (thing->m_type) {
// Primitives
default: {
memcpy(th->m_data, thing->m_data, thing_type_size(thing->m_type));
}
}
thing->m_context->m_things.emplace(thing->m_context->m_things.begin() + idx, th);
return std::move(th);
}
std::int32_t& thing::int32() {
if (m_type != thing_t::Int32) throw bad_thing_access();
return *static_cast<std::int32_t*>(m_data);
}
const std::int32_t& thing::int32() const {
if (m_type != thing_t::Int32) throw bad_thing_access();
return *static_cast<std::int32_t*>(m_data);
}
} // namespace furvm
// NOLINTEND(cppcoreguidelines-no-malloc)
+3
View File
@@ -0,0 +1,3 @@
#include "gtest/gtest.h" // IWYU pragma: keep
namespace {}