Compare commits

..

25 Commits

Author SHA1 Message Date
CHatingPython 549af109b9 fix: fix this mess
I am stupid
I am stupid
I am stupid
I can't believe how f- ricking stupid I am
2026-07-19 22:04:50 +02:00
CHatingPython 6dce7016ce test(furvm): introduce an add things test 2026-07-19 21:53:37 +02:00
CHatingPython a1e43576cc this code is a mess and I am losing my mind
This commit does not work and I am so down I don't even bother
committing to conventional commits until I fix this mess.
2026-07-19 21:38:00 +02:00
CHatingPython 9f950e45e7 feat(furas): introduce basic generator
Closes: #56
2026-07-17 20:43:34 +02:00
CHatingPython dfaa3081cb fix: use thing::true_type and fix module serialization 2026-07-17 20:41:27 +02:00
CHatingPython 46e92deed5 feat(furvm): introduce missing push instructions
Closes: #58
2026-07-14 12:55:34 +02:00
CHatingPython 42e71080a2 feat(furvm): introduce set instruction
Closes: #57
2026-07-14 12:40:47 +02:00
CHatingPython 03448d865e feat(furas): introduce a basic lexer
Closes: #56
2026-07-14 00:38:27 +02:00
CHatingPython cb845ca625 Merge branch 'master' into furas 2026-07-13 20:34:06 +02:00
CHatingPython 798287ef47 refactor: remove ReturnValue instruction 2026-07-13 20:33:37 +02:00
CHatingPython 466d7f003b chore: initialize furas 2026-07-13 19:12:13 +02:00
CHatingPython ff3fce53f5 feat: add return type to function
Closes: #51
2026-07-13 17:28:22 +02:00
CHatingPython 9b280fab22 refactor(furvm): improve references
Closes: #53
2026-07-12 00:03:39 +02:00
CHatingPython e1b75ccc8e feat(furvm): introduce length of operator
Closes: #54
2026-07-11 23:17:59 +02:00
CHatingPython cd715ae779 feat(furvm): introduce pointer type
Closes: #35
2026-07-11 22:44:18 +02:00
CHatingPython 8d3859ce70 refactor(furvm): improve primitive types
Improve primitive types and binary operations.

Closes: #55
2026-07-11 02:28:23 +02:00
CHatingPython 3c0588e8db refactor(furvm): improve type system
Closes: #52
2026-07-11 00:45:09 +02:00
CHatingPython abbc1714c1 refactor(furvm): enforce argument types 2026-07-09 15:02:37 +02:00
CHatingPython b7da2f3d32 feat: add missing furlang/utility/hash.hpp
I forgot to stage it from untracked lol
2026-07-08 00:20:52 +02:00
CHatingPython d49b4f5bc7 refactor: improve functions
Closes: #49
2026-07-08 00:19:37 +02:00
CHatingPython 7de645d323 refactor: replace type_p with type_ref in thing
Damn, this was such a great call.

Closes: #46
2026-07-07 19:29:30 +02:00
CHatingPython 2519560451 refactor: remove name from function
Closes: #47
2026-07-06 22:06:53 +02:00
CHatingPython 7c74850aff feat: introduce array and get instruction 2026-07-06 10:53:48 +02:00
CHatingPython 81a439b2ad refactor: add size argument to array type
Closes: #43
2026-07-05 23:15:40 +02:00
CHatingPython b913fc0c8b refactor: rename list to array
Closes: #42
2026-07-05 22:41:53 +02:00
33 changed files with 2183 additions and 553 deletions
+3 -1
View File
@@ -31,7 +31,9 @@ Checks: >
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-bugprone-forward-declaration-namespace
-cppcoreguidelines-use-enum-class,
-bugprone-forward-declaration-namespace,
-bugprone-tagged-union-member-count
WarningsAsErrors: "*"
+1
View File
@@ -14,6 +14,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(furlang)
add_subdirectory(furvm)
add_subdirectory(furc)
add_subdirectory(furas)
if(DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
+5
View File
@@ -0,0 +1,5 @@
file(GLOB_RECURSE FURAS_SRCS "src/**.cpp")
file(GLOB_RECURSE FURAS_HDRS "include/**.hpp")
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
target_include_directories(furas PUBLIC include/)
target_link_libraries(furas PRIVATE furlang libfurvm)
+35
View File
@@ -0,0 +1,35 @@
type arr = array $s8 10
func println $arr = native println
public func main = #main
main:
array $arr
dup
store %0
lenof
store %1
push $s32 0
loop:
dup
load %1
ge
jnz #end
body:
dup
load %0
swap
push $s8 69
set
push $s32 1
add
jmp #loop
end:
drop
load %0
call $arr println
ret
+34
View File
@@ -0,0 +1,34 @@
type arr = array $s8 10
func println $arr = native println
public func main = #main
main:
array $arr
store %0
push $s32 2
store %1
push $s32 0
loop:
dup
load %1
ge
jnz #end
body:
dup
load %0
swap
push $s8 69
set
push $s32 1
add
jmp #loop
end:
drop
load %0
call $arr println
ret
+23
View File
@@ -0,0 +1,23 @@
func println $u32 = native println
public func main = #main
main:
push $u32 0
store %0
loop_header:
load %0
push $u32 10
ge
jnz #loop_end
loop_body:
load %0
call $u32 println
load %0
push $u32 1
add
store %0
jmp #loop_header
loop_end:
ret
View File
+36
View File
@@ -0,0 +1,36 @@
#ifndef FURAS_GEN_HPP
#define FURAS_GEN_HPP
#include "furas/lexer.hpp"
#include "furvm/module.hpp"
#include <string>
namespace furas {
struct generator_error {
enum type {
Success = 0,
Eof = 1,
UnexpectedEof = -1,
UnexpectedToken = -2,
UnknownCharacter = -3,
UnknownType = -4,
} type = Success;
std::string message;
};
class generator {
public:
struct result {
generator_error error;
furvm::mod mod;
};
public:
static result generate(lexer lexer);
};
} // namespace furas
#endif // FURAS_GEN_HPP
+47
View File
@@ -0,0 +1,47 @@
#ifndef FURAS_LEXER_HPP
#define FURAS_LEXER_HPP
#include "furas/token.hpp"
#include "furlang/result.hpp"
#include <cstddef>
#include <string_view>
namespace furas {
struct lexer_location {
std::string_view filename;
std::size_t row, col;
};
struct lexer_error {
enum type {
EndOfFile = 0,
UnknownCharacter,
} type;
lexer_location location;
std::string message;
};
using token_r = furlang::result<token, lexer_error>;
class lexer {
public:
lexer(std::string_view filename, std::string_view content)
: m_filename(filename), m_content(content) {}
token_r next_token();
private:
constexpr lexer_location location() const { return { m_filename, m_cursor - m_lineStart, m_column }; }
private:
std::string_view m_filename;
std::string_view m_content;
std::size_t m_cursor = 0;
std::size_t m_lineStart = 0;
std::size_t m_column = 0;
};
} // namespace furas
#endif // FURAS_LEXER_HPP
+94
View File
@@ -0,0 +1,94 @@
#ifndef FURAS_TOKEN_HPP
#define FURAS_TOKEN_HPP
#include <cstdint>
#include <string_view>
namespace furas {
struct token {
enum type {
Identifier = 0, /**< An identifier. */
Signed, /**< A signed integer. */
Unsigned, /**< An unsigned integer. */
// Markers
Monkey, /**< Constant marker (`@`). */
Dolar, /**< Type marker(`$`). */
Sha256, /**< Label marker(`#`). */
Percent, /**< Variable marker(`%`). The more the better. */
EqSign, /**< `=` */
Dot, /**< . */
Colon, /**< `:` */
// Keywords
Func, /**< `func` keyword for defining functions. */
Type, /**< `type` keyword for defining types. */
Native, /**< `native` keyword for native functions. :v: */
Import, /**< `import` keyword for importing functions and types. */
Public, /**< `public` access specifier. */
Private, /**< `private` access specifier. */
// Instructions
Push,
Array,
Get,
Set,
Drop,
Dup,
Swap,
Clone,
Ref,
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Neq,
Lt,
Gt,
Le,
Ge,
Ptrof,
Sizeof,
Lenof,
Load,
Store,
Call,
Jmp,
Jnz,
Ret,
Count
} type = Count;
union value {
std::nullptr_t null = nullptr;
std::string_view string;
std::int64_t integer;
std::uint64_t uint;
} value;
token(enum type type)
: type(type) {}
token(enum type type, std::string_view string)
: type(type) {
value.string = string;
}
token(std::uint64_t num)
: type(Unsigned) {
value.uint = num;
}
token(std::int64_t num)
: type(Signed) {
value.integer = num;
}
};
} // namespace furas
#endif // FURAS_TOKEN_HPP
+558
View File
@@ -0,0 +1,558 @@
#include "furas/gen.hpp"
#include "furas/token.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/instruction.hpp"
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <optional>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include <vector>
namespace furas {
using namespace std::string_literals;
namespace {
struct instruction {
furvm::instruction_t fur{};
enum arg_type {
None = 0,
Type,
Constant,
Variable,
Function,
Label,
} arg = None;
};
// NOLINTBEGIN
std::unordered_map<enum token::type, instruction> instructions = {
{ token::Array, { furvm::instruction_t::Array, instruction::Type } },
{ token::Get, { furvm::instruction_t::Get } },
{ token::Set, { furvm::instruction_t::Set } },
{ token::Drop, { furvm::instruction_t::Drop } },
{ token::Dup, { furvm::instruction_t::Duplicate } },
{ token::Swap, { furvm::instruction_t::Swap } },
{ token::Clone, { furvm::instruction_t::Clone } },
{ token::Ref, { furvm::instruction_t::Reference } },
{ token::Add, { furvm::instruction_t::Add } },
{ token::Sub, { furvm::instruction_t::Sub } },
{ token::Mul, { furvm::instruction_t::Mul } },
{ token::Div, { furvm::instruction_t::Div } },
{ token::Mod, { furvm::instruction_t::Mod } },
{ token::Eq, { furvm::instruction_t::Equals } },
{ token::Neq, { furvm::instruction_t::NotEquals } },
{ token::Lt, { furvm::instruction_t::LessThan } },
{ token::Gt, { furvm::instruction_t::GreaterThan } },
{ token::Le, { furvm::instruction_t::LessEqual } },
{ token::Ge, { furvm::instruction_t::GreaterEqual } },
{ token::Ptrof, { furvm::instruction_t::Pointerof } },
{ token::Sizeof, { furvm::instruction_t::Sizeof } },
{ token::Lenof, { furvm::instruction_t::Lengthof } },
{ token::Load, { furvm::instruction_t::Load, instruction::Variable } },
{ token::Store, { furvm::instruction_t::Store, instruction::Variable } },
{ token::Call, { furvm::instruction_t::Call, instruction::Function } },
{ token::Jmp, { furvm::instruction_t::Jump, instruction::Label } },
{ token::Jnz, { furvm::instruction_t::JumpNotZero, instruction::Label } },
{ token::Ret, { furvm::instruction_t::Return } },
};
// NOLINTEND
const char* token_type(enum token::type type) {
switch (type) {
case token::Identifier: return "identifier";
case token::Signed: return "signed";
case token::Unsigned: return "unsigned";
case token::Monkey: return "'@'";
case token::Dolar: return "'$'";
case token::Sha256: return "'#'";
case token::Percent: return "'%'";
case token::EqSign: return "'='";
case token::Dot: return "'.'";
case token::Colon: return "':'";
case token::Func: return "func";
case token::Type: return "type";
case token::Native: return "native";
case token::Import: return "import";
case token::Public: return "public";
case token::Private: return "private";
case token::Push: return "push";
case token::Array: return "array";
case token::Get: return "get";
case token::Set: return "set";
case token::Drop: return "drop";
case token::Dup: return "dup";
case token::Swap: return "swap";
case token::Clone: return "clone";
case token::Ref: return "ref";
case token::Add: return "add";
case token::Sub: return "sub";
case token::Mul: return "mul";
case token::Div: return "div";
case token::Mod: return "mod";
case token::Eq: return "eq";
case token::Neq: return "neq";
case token::Lt: return "lt";
case token::Gt: return "gt";
case token::Le: return "le";
case token::Ge: return "ge";
case token::Ptrof: return "ptrof";
case token::Sizeof: return "sizeof";
case token::Lenof: return "lenof";
case token::Load: return "load";
case token::Store: return "store";
case token::Call: return "call";
case token::Jmp: return "jmp";
case token::Jnz: return "jnz";
case token::Ret: return "ret";
case token::Count: break;
}
throw std::runtime_error("unreachable");
}
struct mod_context {
struct label_context {
struct function_info {
std::string name;
furvm::function_sig signature;
bool pub;
};
static constexpr std::size_t INVALID = std::numeric_limits<std::size_t>::max();
std::size_t offset = INVALID;
std::vector<function_info> functions;
std::vector<std::size_t> unknowns;
};
furvm::mod mod;
std::unordered_map<std::pair<std::string, furvm::function_sig>,
furvm::function_h,
furlang::utility::
pair_hash<std::string, furvm::function_sig, std::hash<std::string>, furvm::detail::function_sig_hash>>
functions;
std::unordered_map<std::string, furvm::mod_type_h> types;
std::unordered_map<std::string, label_context> labels;
struct token_result {
generator_error error;
token token;
struct token* operator->() { return &token; }
struct token& operator*() { return token; }
bool operator!() const { return error.type != generator_error::Success; }
};
mod_context() {
types.emplace("s8", mod.emplace_type(furvm::mod_type::S8));
types.emplace("u8", mod.emplace_type(furvm::mod_type::U8));
types.emplace("s16", mod.emplace_type(furvm::mod_type::S16));
types.emplace("u16", mod.emplace_type(furvm::mod_type::U16));
types.emplace("s32", mod.emplace_type(furvm::mod_type::S32));
types.emplace("u32", mod.emplace_type(furvm::mod_type::U32));
types.emplace("s64", mod.emplace_type(furvm::mod_type::S64));
types.emplace("u64", mod.emplace_type(furvm::mod_type::U64));
}
static token_result next_token(lexer& lexer) {
auto token = lexer.next_token();
if (token.has_error()) {
switch (token.error().type) {
case lexer_error::EndOfFile:
return { { generator_error::UnexpectedEof, "Unexpected end of file" }, { token::Count } };
case lexer_error::UnknownCharacter:
return { { generator_error::UnknownCharacter, token.error().message }, { token::Count } };
}
}
return { { generator_error::Success }, *token };
}
static token_result eat_token(lexer& lexer, enum token::type type) {
auto token = next_token(lexer);
if (!token) return token;
if (token.token.type != type) {
return { { generator_error::UnexpectedToken,
"Expected "s + token_type(type) + ", but got " + token_type(token->type) },
{ token::Count } };
}
return { { generator_error::Success }, token.token };
}
generator_error generate(lexer& lexer) {
auto result = next_token(lexer);
if (result.error.type == generator_error::UnexpectedEof) return { generator_error::Eof };
if (!result) return result.error;
switch (result->type) {
case token::Identifier: {
std::string labelName = std::string(result->value.string);
result = eat_token(lexer, token::Colon);
if (!result) return result.error;
auto& label = labels[labelName];
if (label.offset != label_context::INVALID)
return { generator_error::UnexpectedToken, "Label "s + labelName + " already exists" };
label.offset = mod.bytecode().size();
for (auto& func : label.functions) {
furvm::function_h handle =
(func.pub ? mod.emplace_function(func.name, std::move(func.signature), label.offset)
: mod.emplace_function(std::move(func.signature), label.offset));
functions.emplace(std::make_pair(std::move(func.name), std::move(func.signature)), handle);
handle.dispatch();
}
for (auto unknown : label.unknowns) {
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(label.offset - unknown);
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode()[unknown - 1] = jmpOff;
}
label.functions = {};
label.unknowns = {};
return { generator_error::Success };
}
case token::Func:
case token::Type:
case token::Public:
case token::Private: {
bool pub = false;
if (result->type == token::Public) {
pub = true;
result = next_token(lexer);
if (!result) return { result.error };
} else if (result->type == token::Private) {
result = next_token(lexer);
if (!result) return { result.error };
}
if (result->type == token::Func) {
auto nameRes = next_token(lexer);
if (!nameRes) return nameRes.error;
furvm::function_sig signature;
result = next_token(lexer);
if (!result) return result.error;
while (result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
signature.params.push_back(it->second);
} else {
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
}
result = next_token(lexer);
if (!result) return result.error;
}
if (result->type != token::EqSign) return result.error;
result = next_token(lexer);
if (!result) return result.error;
if (result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
signature.returnType = it->second;
} else {
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
}
result = next_token(lexer);
if (!result) return result.error;
}
switch (result->type) {
case token::Sha256: {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
std::string name = std::string(nameRes->value.string);
std::string labelName = std::string(result->value.string);
std::size_t offset = 0;
if (auto it = labels.find(labelName); it != labels.end()) {
offset = it->second.offset;
} else {
labels[labelName].functions.emplace_back(
label_context::function_info{ std::move(name), std::move(signature), pub });
return { generator_error::Success };
}
furvm::function_h handle =
(pub ? mod.emplace_function(name, signature, offset) : mod.emplace_function(signature, offset));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Native: {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
std::string nativeName = std::string(result->value.string);
std::string name = std::string(nameRes->value.string);
furvm::function_h handle = (pub ? mod.emplace_function(name, signature, std::move(nativeName))
: mod.emplace_function(signature, std::move(nativeName)));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Import: {
throw std::runtime_error("unimplemented");
}
default:
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) +
", expected label name, `native`, or `import`" };
}
}
if (result->type == token::Type) {
auto nameRes = eat_token(lexer, token::Identifier);
if (!nameRes) return nameRes.error;
result = eat_token(lexer, token::EqSign);
if (!result) return result.error;
result = next_token(lexer);
if (!result) return result.error;
switch (result->type) {
case token::Import: {
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
return { generator_error::Success };
}
case token::Array: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
auto size = eat_token(lexer, token::Unsigned);
if (!size) return size.error;
furvm::mod_type_id innerId = 0;
if (auto it = types.find(std::string(typeNameRes->value.string)); it != types.end()) {
innerId = it->second.id();
} else {
return { generator_error::UnknownType,
"Unknown type "s + std::string(typeNameRes->value.string) };
}
auto type = mod.emplace_type(innerId, size->value.uint);
types.emplace(std::string(nameRes->value.string), type);
type.dispatch();
return { generator_error::Success };
}
default:
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) +
", expected either type, `import`, or `array`" };
}
}
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) + ", expected either `func` or `type`" };
}
case token::Push: {
auto result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
auto typeName = eat_token(lexer, token::Identifier);
if (!typeName) return result.error;
auto it = types.find(std::string(typeName->value.string));
if (it == types.end())
return { generator_error::UnexpectedToken, "Unknown type "s + std::string(typeName->value.string) };
// TODO: Add support for signed integers
auto value = eat_token(lexer, token::Unsigned);
if (!value) return result.error;
auto type = it->second;
switch (type->type) {
case furvm::mod_type::S8: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS8));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::U8: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU8));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::S16: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS16));
mod.bytecode().push_back(value->value.uint & 0xFF);
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
} break;
case furvm::mod_type::U16: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU16));
mod.bytecode().push_back(value->value.uint & 0xFF);
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
} break;
case furvm::mod_type::S32: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS32));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::U32: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU32));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
default:
return { generator_error::UnexpectedToken, "Unexpected type "s + std::string(typeName->value.string) };
}
return { generator_error::Success };
}
case token::Array:
case token::Get:
case token::Set:
case token::Drop:
case token::Dup:
case token::Swap:
case token::Clone:
case token::Ref:
case token::Add:
case token::Sub:
case token::Mul:
case token::Div:
case token::Mod:
case token::Eq:
case token::Neq:
case token::Lt:
case token::Gt:
case token::Le:
case token::Ge:
case token::Ptrof:
case token::Sizeof:
case token::Lenof:
case token::Load:
case token::Store:
case token::Call:
case token::Jmp:
case token::Jnz:
case token::Ret: {
auto it = instructions.find(result->type);
assert(it != instructions.end());
mod.bytecode().push_back(static_cast<furvm::byte>(it->second.fur));
switch (it->second.arg) {
case instruction::None: break;
case instruction::Type: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = type->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
mod.bytecode().push_back((id >> 16) & 0xFF);
mod.bytecode().push_back((id >> 24) & 0xFF);
} break;
case instruction::Constant: {
assert(false); // TODO: Unimplemented
} break;
case instruction::Variable: {
result = eat_token(lexer, token::Percent);
if (!result) return result.error;
result = eat_token(lexer, token::Unsigned);
if (!result) return result.error;
std::uint16_t var = result->value.uint;
mod.bytecode().push_back((var >> 0) & 0xFF);
mod.bytecode().push_back((var >> 8) & 0xFF);
} break;
case instruction::Function: {
furvm::function_sig signature;
while ((result = next_token(lexer)).error.type == generator_error::Success &&
result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
signature.params.push_back(type->second);
}
if (!result || result->type != token::Identifier) return result.error;
std::string name(result->value.string);
auto func = functions.find(std::make_pair(name, signature));
if (func == functions.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = func->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
} break;
case instruction::Label: {
result = eat_token(lexer, token::Sha256);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto& label = labels[std::string(result->value.string)];
auto offset = label.offset;
if (offset == label_context::INVALID) {
label.unknowns.push_back(mod.bytecode().size() + 1);
mod.bytecode().push_back(0);
return { generator_error::Success };
}
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(offset - mod.bytecode().size() - 1);
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode().push_back(jmpOff);
} break;
}
return { generator_error::Success };
}
case token::Signed:
case token::Unsigned:
case token::Colon:
case token::Monkey:
case token::Dolar:
case token::Sha256:
case token::Percent:
case token::EqSign:
case token::Dot:
case token::Native:
case token::Import:
case token::Count: break;
}
return { generator_error::UnexpectedToken, "Unexpected token "s + token_type(result->type) };
}
};
} // namespace
generator::result generator::generate(lexer lexer) {
mod_context context;
generator_error error;
do {
error = context.generate(lexer);
} while (error.type == generator_error::Success);
if (error.type == generator_error::Eof) return { { generator_error::Success }, std::move(context.mod) };
return { error };
}
} // namespace furas
+98
View File
@@ -0,0 +1,98 @@
#include "furas/lexer.hpp"
#include <cctype>
#include <unordered_map>
namespace furas {
using namespace std::string_literals;
token_r lexer::next_token() {
while (m_cursor < m_content.size() && std::isspace(m_content[m_cursor]) != 0) {
if (m_content[m_cursor] == '\n') {
m_lineStart = m_cursor + 1;
++m_column;
}
++m_cursor;
}
// TODO: Add support for single-line comments
// TODO: Add support for multi-line comments
if (m_cursor >= m_content.size()) return token_r{ lexer_error{ lexer_error::EndOfFile, location() } };
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
// TODO: Add support for hexadecimal and binary numeric literals
if (std::isdigit(m_content[m_cursor]) != 0) {
std::uint64_t num = 0;
while (m_cursor < m_content.size() && std::isdigit(m_content[m_cursor]) != 0) {
num *= 10;
num += m_content[m_cursor++] - '0';
}
return { num };
}
static std::unordered_map<std::string_view, enum token::type> s_tokens = {
{ "func", token::Func },
{ "type", token::Type },
{ "native", token::Native },
{ "import", token::Import },
{ "public", token::Public },
{ "private", token::Private },
{ "push", token::Push },
{ "array", token::Array },
{ "get", token::Get },
{ "set", token::Set },
{ "drop", token::Drop },
{ "dup", token::Dup },
{ "swap", token::Swap },
{ "clone", token::Clone },
{ "ref", token::Ref },
{ "add", token::Add },
{ "sub", token::Sub },
{ "mul", token::Mul },
{ "div", token::Div },
{ "mod", token::Mod },
{ "eq", token::Eq },
{ "neq", token::Neq },
{ "lt", token::Lt },
{ "gt", token::Gt },
{ "le", token::Le },
{ "ge", token::Ge },
{ "ptrof", token::Ptrof },
{ "sizeof", token::Sizeof },
{ "lenof", token::Lenof },
{ "load", token::Load },
{ "store", token::Store },
{ "call", token::Call },
{ "jmp", token::Jmp },
{ "jnz", token::Jnz },
{ "ret", token::Ret },
};
if (std::isalnum(m_content[m_cursor]) != 0) {
std::size_t begin = m_cursor++;
while (m_cursor < m_content.size() && (std::isalnum(m_content[m_cursor]) != 0 || m_content[m_cursor] == '_'))
++m_cursor;
std::string_view str = m_content.substr(begin, m_cursor - begin);
if (auto it = s_tokens.find(str); it != s_tokens.end()) return { it->second };
return { token::Identifier, str };
}
switch (m_content[m_cursor]) {
case '@': ++m_cursor; return { token::Monkey };
case '$': ++m_cursor; return { token::Dolar };
case '#': ++m_cursor; return { token::Sha256 };
case '%': ++m_cursor; return { token::Percent };
case '=': ++m_cursor; return { token::EqSign };
case '.': ++m_cursor; return { token::Dot };
case ':': ++m_cursor; return { token::Colon };
default:
return token_r{
lexer_error{ lexer_error::UnknownCharacter, location(), "Unknown character '"s + m_content[m_cursor] + "'" }
};
}
}
} // namespace furas
+39
View File
@@ -0,0 +1,39 @@
#include "furas/gen.hpp"
#include "furas/lexer.hpp"
#include <fstream>
#include <furvm/module.hpp>
#include <iostream>
int main(int argc, char** argv) { // NOLINT
if (argc < 2) {
std::cerr << "feed me more arguments daddy >_<\n";
return 1;
}
if (argc > 2) {
std::cerr << "too much O_O\n";
return 1;
}
const char* filepath = argv[1];
std::ifstream file(filepath, std::ios::binary | std::ios::ate | std::ios::in);
if (!file.is_open()) {
std::cerr << "file won't open >~<\n";
return 1;
}
std::string content;
content.resize(file.tellg());
file.seekg(0);
file.read(content.data(), static_cast<std::streamsize>(content.size()));
auto result = furas::generator::generate(furas::lexer(filepath, content));
if (result.error.type != furas::generator_error::Success) {
std::cerr << result.error.message << '\n';
return 1;
}
result.mod.serialize(std::cout);
return 0;
}
+9 -10
View File
@@ -2,6 +2,7 @@
#include "furlang/ir/function.hpp"
#include "furlang/ir/instruction.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include <furvm/instruction.hpp>
@@ -20,12 +21,14 @@ furvm::mod furvm_generator::generate(furlang::ir::mod& mod) {
}
void furvm_generator::generate_function(furvm::mod& mod, const furlang::ir::function& function) {
furvm::function_sig signature; // TODO: Complete
switch (function.type()) {
case furlang::ir::function_t::Normal: {
if (function.access() == furlang::ir::function_access_t::Public)
mod.emplace_function(function.name(), function.param_count(), mod.bytecode().size()).dispatch();
mod.emplace_function(function.name(), std::move(signature), mod.bytecode().size()).dispatch();
else
mod.emplace_function_private(function.name(), function.param_count(), mod.bytecode().size()).dispatch();
mod.emplace_function(std::move(signature), mod.bytecode().size()).dispatch();
function_context ctx;
for (furlang::ir::block_index idx = 0; idx < function.blocks().size(); ++idx) {
@@ -48,9 +51,9 @@ void furvm_generator::generate_function(furvm::mod& mod, const furlang::ir::func
} break;
case furlang::ir::function_t::Native: {
if (function.access() == furlang::ir::function_access_t::Public)
mod.emplace_function(function.name(), function.param_count(), function.name()).dispatch();
mod.emplace_function(function.name(), std::move(signature), function.name()).dispatch();
else
mod.emplace_function_private(function.name(), function.param_count(), function.name()).dispatch();
mod.emplace_function(std::move(signature), function.name()).dispatch();
} break;
}
}
@@ -140,17 +143,13 @@ void furvm_generator::generate_instruction(furvm::mod& mod,
generate_jump(mod, ctx, branch.else_block(), false);
} break;
case furlang::ir::instruction_t::Return: {
if (instr.sources().empty()) {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Return));
} else {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::ReturnValue));
}
} break;
case furlang::ir::instruction_t::Call: {
const auto& call = dynamic_cast<const furlang::ir::call_instruction&>(instr);
// TODO: Implement a queue for unknown functions
furvm::function_id func = mod.get_function_id(call.name());
furvm::function_id func = mod.function_at(call.name(), furvm::function_sig{}).id(); // TODO: Complete
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Call));
mod.bytecode().push_back((func >> 0) & 0xFF);
mod.bytecode().push_back((func >> 8) & 0xFF);
@@ -171,7 +170,7 @@ void furvm_generator::generate_operand(furvm::mod& mod, function_context& ctx, c
static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed");
} break;
case furlang::ir::operand_t::Integer: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushB2I));
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS32));
mod.bytecode().push_back(operand.integer());
} break;
case furlang::ir::operand_t::Variable:
+1 -1
View File
@@ -71,7 +71,7 @@ int main(void) {
[](furvm::executor& executor) { std::cout << executor.load_thing(0)->integer() << '\n'; });
furvm::executor_h executor = context->emplace_executor(context);
executor->push_frame(furvmMod, *furvmMod->function_at("main"));
executor->push_frame(furvmMod, *furvmMod->function_at("main", furvm::function_sig{}));
std::cout << "--- Interpreting:\n";
+47
View File
@@ -0,0 +1,47 @@
#ifndef FURLANG_UTILITY_HASH_HPP
#define FURLANG_UTILITY_HASH_HPP
#include <cstddef>
#include <functional>
namespace furlang {
namespace utility {
// Source - https://stackoverflow.com/a/27952689
// Posted by Yakk - Adam Nevraumont, modified by community. See post 'Timeline' for change history
// Retrieved 2026-07-07, License - CC BY-SA 4.0
static inline std::size_t hash_combine(std::size_t lhs, std::size_t rhs) {
if constexpr (sizeof(std::size_t) >= 8) {
lhs ^= rhs + 0x517cc1b727220a95 + (lhs << 6) + (lhs >> 2);
} else {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
}
return lhs;
}
// Source - https://stackoverflow.com/a/20602159
// Posted by Casey, modified by community. See post 'Timeline' for change history
// Retrieved 2026-07-07, License - CC BY-SA 3.0
template <typename T, typename U, typename FirstHash = std::hash<T>, typename SecondHash = std::hash<U>>
struct pair_hash {
std::size_t operator()(const std::pair<T, U>& pair) const {
return hash_combine(FirstHash()(pair.first), SecondHash()(pair.second));
}
};
template <typename T, typename Hash = std::hash<T>>
struct vector_hash {
std::size_t operator()(const std::vector<T>& vec) const {
std::size_t seed = 0;
for (const auto& element : vec) {
hash_combine(seed, Hash()(element));
}
return seed;
}
};
} // namespace utility
} // namespace furlang
#endif // FURLANG_UTILITY_HASH_HPP
+5 -1
View File
@@ -21,7 +21,8 @@ public:
/**
* @brief Constructs a context.
*/
context();
context()
: m_thingAllocator(m_thingArena) {}
~context() = default;
@@ -124,6 +125,8 @@ public:
* @return The thing allocator.
*/
thing_allocator<std::byte> thing_alloc() const { return m_thingAllocator; }
thing_type_store& thing_type_store() { return m_thingTypeStore; }
private:
handle_container<mod_h> m_modules;
handle_container<thing_h> m_things;
@@ -131,6 +134,7 @@ private:
furlang::arena m_thingArena;
thing_allocator<std::byte> m_thingAllocator;
class thing_type_store m_thingTypeStore;
};
} // namespace furvm
+6
View File
@@ -1,6 +1,7 @@
#ifndef FURVM_DETAIL_HANDLE_HPP
#define FURVM_DETAIL_HANDLE_HPP
#include <functional>
#include <type_traits>
namespace furvm {
@@ -27,6 +28,11 @@ struct header_has_refcount<Header,
template <typename Header>
static constexpr auto header_has_refcount_v = header_has_refcount<Header>::value;
template <typename Handle, typename IdHash = std::hash<typename Handle::id_type>>
struct handle_hash {
std::size_t operator()(const Handle& handle) const { return IdHash{}(handle.id()); }
};
} // namespace detail
} // namespace furvm
+6
View File
@@ -5,6 +5,7 @@
#include "furvm/module.hpp" // IWYU pragma: keep
#include "furvm/thing.hpp" // IWYU pragma: keep
#include <optional>
#include <stack>
#include <utility>
#include <vector>
@@ -40,6 +41,7 @@ public:
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
thing_type* returnType; /**< Return type. */
std::vector<thing_h> variables; /**< Frame variables. */
};
public:
@@ -164,6 +166,10 @@ public:
* @brief Executes next instruction.
*/
void step();
private:
thing_type thing_type_impl(mod_h mod, mod_type type) const;
thing_type* thing_type(const mod_h& mod, const mod_type& type) const;
private:
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
context_p m_context;
+39 -26
View File
@@ -1,14 +1,17 @@
#ifndef FURVM_FUNCTION_HPP
#define FURVM_FUNCTION_HPP
#include "furlang/utility/hash.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp" // IWYU pragma: keep
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace furvm {
@@ -31,33 +34,43 @@ struct import_function {
function_id function;
};
/**
* @brief Function signature.
*/
struct function_sig {
std::vector<mod_type_h> params;
std::optional<mod_type_h> returnType;
bool operator==(const function_sig& rhs) const { return params == rhs.params; }
bool operator!=(const function_sig& rhs) const { return !this->operator==(rhs); }
};
class function {
public:
/**
* @brief Constructs a normal function.
*
* @param name Name of the function.
* @param paramCount Paremeter count.
* @param signature Function's signature.
* @param position Offset in bytecode of the function.
*/
template <typename Name>
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) {}
template <typename SigFwd, typename = std::enable_if_t<std::is_constructible_v<function_sig, SigFwd>>>
function(SigFwd&& signature, bytecode_pos position)
: m_type(function_t::Normal), m_signature(std::forward<SigFwd>(signature)), m_value(position) {}
/**
* @brief Constructs a native function.
*
* @param name Name of the function.
* @param paramCount Paremeter count.
* @param signature Function's signature.
* @param native Native function tag.
*/
template <typename Name,
template <typename SigFwd,
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),
typename = std::enable_if_t<std::is_constructible_v<native_function, Native> &&
std::is_constructible_v<function_sig, SigFwd>>>
function(SigFwd&& signature, Native&& native)
: m_type(function_t::Native),
m_signature(std::forward<SigFwd>(signature)),
m_value(std::forward<Native>(native)) {}
/**
@@ -68,7 +81,7 @@ public:
*/
template <typename ModFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModFwd>>>
function(ModFwd&& mod, function_id function)
: m_type(function_t::Import), m_paramCount(0), m_value(import_function{ std::forward<ModFwd>(mod), function }) {}
: m_type(function_t::Import), m_signature(), m_value(import_function{ std::forward<ModFwd>(mod), function }) {}
/**
* @brief Constructs an import function.
@@ -103,13 +116,6 @@ public:
*/
function& operator=(const function&);
public:
/**
* @brief Returns a name of this function.
*
* @return The name.
*/
constexpr const std::string& name() const { return m_name; }
/**
* @brief Returns a type of this function.
*
@@ -118,11 +124,11 @@ public:
constexpr function_t type() const { return m_type; }
/**
* @brief Returns this function's parameter count.
* @brief Returns this function's signature.
*
* @return The parameter count.
* @return The signature.
*/
constexpr std::uint32_t param_count() const { return m_paramCount; }
function_sig signature() const { return m_signature; }
public:
/**
* @brief Returns normal function's value.
@@ -154,9 +160,8 @@ public:
return m_value.imp;
}
private:
std::string m_name;
function_t m_type;
std::uint32_t m_paramCount;
function_sig m_signature;
union value {
std::size_t position = 0;
@@ -184,6 +189,14 @@ private:
} m_value;
};
namespace detail {
struct function_sig_hash {
std::size_t operator()(const function_sig& signature) const;
};
} // namespace detail
} // namespace furvm
#endif // FURVM_FUNCTION_HPP
+12 -20
View File
@@ -115,6 +115,12 @@ using function_h = handle<function, refcount_header<function_id>>;
// module.hpp
struct mod_type;
using mod_type_id = std::uint32_t;
using mod_type_h = handle<mod_type, generic_header<mod_type_id>>;
/**
* @class mod
* @brief Module.
@@ -138,34 +144,20 @@ using mod_id = std::string;
*/
using mod_h = handle<mod, refcount_header<mod_id>>;
// thing_allocator.hpp
template <typename T>
class thing_allocator;
// thing.hpp
/**
* @enum type_t
* @brief Type of the thing's type.
*/
enum class type_t : std::uint32_t;
/**
* @struct type
* @brief Thing type.
*/
struct type;
using type_p = std::shared_ptr<type>;
using type_id = std::uint32_t;
using type_h = handle<type_p, generic_header<type_id>>;
/**
* @class bad_thing_access
* @brief Bad thing access exception.
*/
class bad_thing_access;
template <typename T>
class thing_allocator;
using thing_type_id = std::uint32_t;
/**
* @class thing
+4
View File
@@ -237,6 +237,10 @@ public:
* @brief Invalidates the handle without releasing.
*/
void dispatch() { m_value = nullptr; }
public:
bool operator==(const handle& rhs) const { return m_value == rhs.m_value; }
bool operator!=(const handle& rhs) const { return !this->operator==(rhs); }
private:
pair_type* m_value = nullptr;
};
+55 -9
View File
@@ -12,11 +12,34 @@ enum class instruction_t : byte {
NoOperation = 0,
/**
* @brief Pushes an integer from a byte onto the stack.
*
* Pushes an integer constructed from a next byte onto the stack.
* @brief Pushes an s8 integer from a byte onto the stack.
*/
PushB2I,
PushS8,
/**
* @brief Pushes an u8 integer from a byte onto the stack.
*/
PushU8,
/**
* @brief Pushes an s16 integer from two byte onto the stack.
*/
PushS16,
/**
* @brief Pushes an u16 integer from two byte onto the stack.
*/
PushU16,
/**
* @brief Pushes an s32 integer from a byte onto the stack.
*/
PushS32,
/**
* @brief Pushes an u32 integer from a byte onto the stack.
*/
PushU32,
/**
* @brief Pushes a constant onto the stack.
@@ -25,6 +48,24 @@ enum class instruction_t : byte {
*/
PushConstant,
/**
* @brief Pushes a new array onto the stack.
*
* Type is the next 4 bytes in little-endian.
* If the type is dynamic the array's size will be popped off of the stack.
*/
Array,
/**
* @brief Pushes an element from an array onto the stack.
*/
Get,
/**
* @brief Sets an array element.
*/
Set,
/**
* @brief Pops top element from the stack.
*/
@@ -35,6 +76,11 @@ enum class instruction_t : byte {
*/
Duplicate,
/**
* @brief Swaps two top elements of the stack.
*/
Swap,
/**
* @brief Clones top element on the stack.
*/
@@ -112,6 +158,11 @@ enum class instruction_t : byte {
*/
Sizeof,
/**
* @brief Pushes a length of popped-off thing onto the stack.
*/
Lengthof,
/**
* @brief Pushes a variable onto the stack.
*
@@ -152,11 +203,6 @@ enum class instruction_t : byte {
* @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 {
+162 -61
View File
@@ -1,10 +1,10 @@
#ifndef FURVM_MODULE_HPP
#define FURVM_MODULE_HPP
#include "furlang/utility/hash.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp"
#include "furvm/type.hpp" // IWYU pragma: keep
#include <functional>
#include <istream>
@@ -17,6 +17,129 @@
namespace furvm {
struct mod_type {
struct array {
mod_type_id typeId;
std::size_t size;
};
struct imprt {
mod_id modId;
mod_type_id typeId;
};
enum type {
S8 = 0,
S16,
S32,
S64,
U8,
U16,
U32,
U64,
Ptr,
Ref,
Array,
Import,
Count,
} type;
union value {
std::nullptr_t null = nullptr;
mod_type_id typeRef;
array array;
imprt imprt;
value() = default;
value(mod_type_id id)
: typeRef(id) {}
value(mod_type_id id, std::size_t size)
: array({}) {
array.typeId = id;
array.size = size;
}
template <typename ModIdFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModIdFwd>>>
value(ModIdFwd&& modId, mod_type_id typeId)
: imprt({}) {
imprt.modId = std::forward<ModIdFwd>(modId);
imprt.typeId = typeId;
}
~value() {}
value(value&& other) = delete;
value& operator=(value&& other) = delete;
value(const value& other) = delete;
value& operator=(const value& other) = delete;
} value;
mod_type(enum type type)
: type(type) {}
mod_type(enum type type, mod_type_id typeRef)
: type(type), value(typeRef) {}
mod_type(mod_type_id id, std::size_t size)
: type(Array), value(id, size) {}
template <typename ModIdFwd, typename = std::enable_if_t<std::is_constructible_v<mod_id, ModIdFwd>>>
mod_type(ModIdFwd&& modId, mod_type_id typeId)
: type(Import), value(std::forward<ModIdFwd>(modId), typeId) {}
~mod_type() {
switch (type) {
case Array: value.array.~array(); break;
case Import: value.imprt.~imprt(); break;
default: break;
}
}
mod_type(mod_type&& other) noexcept
: type(other.type) {
switch (type) {
case Array: new (&value.array) array(other.value.array); break;
case Import: new (&value.imprt) imprt(std::move(other.value.imprt)); break;
default: break;
}
other.type = Count;
}
mod_type& operator=(mod_type&& other) noexcept {
if (this == &other) return *this;
type = other.type;
switch (type) {
case Array: new (&value.array) array(other.value.array); break;
case Import: new (&value.imprt) imprt(std::move(other.value.imprt)); break;
default: break;
}
other.type = Count;
return *this;
}
mod_type(const mod_type& other)
: type(other.type) {
switch (type) {
case Array: new (&value.array) array(other.value.array); break;
case Import: new (&value.imprt) imprt(other.value.imprt); break;
default: break;
}
}
mod_type& operator=(const mod_type& other) {
if (this == &other) return *this;
type = other.type;
switch (type) {
case Array: new (&value.array) array(other.value.array); break;
case Import: new (&value.imprt) imprt(other.value.imprt); break;
default: break;
}
return *this;
}
};
class mod {
friend class function;
friend class serializer;
@@ -86,51 +209,38 @@ public:
function_h emplace_function(Args&&... args) {
function_h function;
if constexpr (std::is_constructible_v<class function, Args...>) {
function = m_functions.emplace_back(std::forward<Args>(args)...);
function = std::move(m_functions.emplace_back(std::forward<Args>(args)...));
} else {
function = m_functions.emplace(std::forward<Args>(args)...);
function = std::move(m_functions.emplace(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 .
* Emplaces the function in module's function container and name to function map.
*
* @param name Name of the function.
* @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();
template <typename NameFwd,
typename... Args,
typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd>>>
function_h emplace_function(NameFwd&& name, Args&&... args) {
function_h function;
if constexpr (std::is_constructible_v<class function, Args...>) {
function = std::move(m_functions.emplace_back(std::forward<Args>(args)...));
} else {
function = std::move(m_functions.emplace(std::forward<Args>(args)...));
}
auto pair = std::make_pair(std::forward<NameFwd>(name), function->signature());
m_functionMap[function.id()] = pair;
m_functionSigs[std::move(pair)] = function.id();
return std::move(function);
}
/**
* @brief Inserts a function in the module's function container.
*
* @param function Function to insert.
*/
template <typename 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));
}
/**
* @brief Returns a function from the module.
*
@@ -153,31 +263,13 @@ public:
* @param name Name of the function.
* @return A handle to the function.
*/
template <typename NameFwd, typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd>>>
auto function_at(NameFwd&& name) {
return function_at(m_publicFunctions.at(std::forward<NameFwd>(name)));
}
/**
* @brief Returns a function from the module.
*
* @param name Name of the function.
* @return A handle to the function.
*/
template <typename NameFwd, typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd>>>
auto function_at(NameFwd&& name) const {
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));
template <typename NameFwd,
typename SigFwd,
typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd> &&
std::is_constructible_v<function_sig, SigFwd>>>
auto function_at(NameFwd&& name, SigFwd&& signature) {
return function_at(
m_functionSigs.at(std::make_pair<>(std::forward<NameFwd>(name), std::forward<SigFwd>(signature))));
}
/**
@@ -185,7 +277,13 @@ public:
*
* @param id Identifier of the function.
*/
void erase_function(function_id id) { m_functions.erase(id); }
void erase_function(function_id id) {
m_functions.erase(id);
if (auto it = m_functionMap.find(id); it != m_functionMap.end()) {
m_functionSigs.erase(it->second);
m_functionMap.erase(it);
}
}
public:
template <typename NameFwd, typename Func>
void set_native_function(NameFwd&& name, Func&& func) {
@@ -205,7 +303,7 @@ public:
*/
template <typename... Args>
auto emplace_type(Args&&... args) {
if constexpr (std::is_constructible_v<type_p, Args...>) {
if constexpr (std::is_constructible_v<mod_type, Args...>) {
return m_types.emplace_back(std::forward<Args>(args)...);
} else {
return m_types.emplace(std::forward<Args>(args)...);
@@ -262,11 +360,14 @@ public:
private:
bytecode_t m_bytecode;
std::unordered_map<std::string, function_id> m_functionMap;
std::unordered_map<std::string, function_id> m_publicFunctions;
using pair_type = std::pair<std::string, function_sig>;
using pair_hash =
furlang::utility::pair_hash<std::string, function_sig, std::hash<std::string>, detail::function_sig_hash>;
std::unordered_map<pair_type, function_id, pair_hash> m_functionSigs;
std::unordered_map<function_id, pair_type> m_functionMap;
handle_container<function_h> m_functions;
handle_container<type_h> m_types;
handle_container<mod_type_h> m_types;
std::unordered_map<std::string, native_function> m_nativeFunctions;
};
+491 -130
View File
@@ -1,29 +1,209 @@
#ifndef FURVM_THING_HPP
#define FURVM_THING_HPP
#include "furlang/arena.hpp"
#include "furlang/utility/hash.hpp"
#include "furvm/exceptions.hpp"
#include "furvm/fwd.hpp"
#include "furvm/module.hpp"
#include "furvm/type.hpp"
#include "furvm/thing_allocator.hpp" // IWYU pragma: keep
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <functional>
#include <memory>
#include <limits>
#include <new>
#include <stdexcept>
#include <type_traits>
#include <unordered_map>
#include <utility>
namespace furvm {
struct thing_type {
using s8 = std::int8_t;
using s16 = std::int16_t;
using s32 = std::int32_t;
using s64 = std::int64_t;
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
struct array {
thing_type* type;
std::size_t size;
};
enum type { // NOLINT
S8 = 0,
S16,
S32,
S64,
U8,
U16,
U32,
U64,
Ptr,
Ref,
Array,
Count,
} type;
union value {
std::nullptr_t null = nullptr;
thing_type* typeRef;
array array;
value() = default;
value(thing_type* type)
: typeRef(type) {}
value(thing_type* type, std::size_t size)
: array({}) {
array.type = type;
array.size = size;
}
} value;
static constexpr thing_type_id INVALID_ID = std::numeric_limits<thing_type_id>::max();
thing_type_id id = INVALID_ID;
bool operator==(const thing_type& other) const {
if (type != other.type) return false;
switch (type) {
case S8:
case S16:
case S32:
case S64:
case U8:
case U16:
case U32:
case U64: return true;
case Ptr:
case Ref: return *value.typeRef == *other.value.typeRef;
case Array: return *value.array.type == *other.value.array.type && value.array.size == other.value.array.size;
case Count: break;
}
return false;
}
bool operator!=(const thing_type& other) const { return !this->operator==(other); }
static bool is_primitive(enum type type) {
switch (type) {
case S8:
case S16:
case S32:
case S64:
case U8:
case U16:
case U32:
case U64: return true;
case Ptr:
case Ref:
case Array: return false;
case Count: break;
}
throw std::runtime_error("unreachable");
}
static std::size_t primitive_size(enum type type) {
switch (type) {
case thing_type::S8: return sizeof(s8);
case thing_type::S16: return sizeof(s16);
case thing_type::S32: return sizeof(s32);
case thing_type::S64: return sizeof(s64);
case thing_type::U8: return sizeof(u8);
case thing_type::U16: return sizeof(u16);
case thing_type::U32: return sizeof(u32);
case thing_type::U64: return sizeof(u64);
case Ptr:
case Ref:
case Array: return 0;
case Count: break;
}
throw std::runtime_error("unreachable");
}
};
namespace detail {
struct thing_type_hash {
std::size_t operator()(const thing_type& type) const {
std::size_t seed = std::hash<decltype(type.type)>{}(type.type);
switch (type.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: return seed;
case thing_type::Ptr:
case thing_type::Ref: return furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.typeRef));
case thing_type::Array:
seed = furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.array.type));
seed = furlang::utility::hash_combine(seed,
std::hash<decltype(type.value.array.size)>{}(type.value.array.size));
return seed;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
};
} // namespace detail
class thing_type_store {
public:
thing_type* insert(thing_type& type) {
if (auto it = m_typeMap.find(type); it != m_typeMap.end()) return m_map[type.id = it->second];
if (type.id == thing_type::INVALID_ID) type.id = m_counter++;
thing_type* ptr = m_arena.allocate<thing_type>(type);
m_map[type.id] = ptr;
m_typeMap[type] = type.id;
return ptr;
}
thing_type* insert(const thing_type& type) {
if (auto it = m_typeMap.find(type); it != m_typeMap.end()) return m_map[it->second];
thing_type_id id = m_counter++;
thing_type* ptr = m_arena.allocate<thing_type>(type);
m_map[id] = ptr;
m_typeMap[type] = id;
return ptr;
}
thing_type* at(thing_type_id id) const {
if (auto it = m_map.find(id); it != m_map.end()) return it->second;
return nullptr;
}
private:
furlang::arena m_arena;
std::unordered_map<thing_type_id, thing_type*> m_map;
std::unordered_map<thing_type, thing_type_id, detail::thing_type_hash> m_typeMap;
thing_type_id m_counter = 0;
};
template <template <typename> typename Allocator>
class thing final {
friend class executor;
public:
using allocator_type = Allocator<std::byte>; /**< Allocator type. */
using mod_container = std::shared_ptr<handle_container<mod_h>>;
public:
union array {
std::byte flat[];
struct {
std::size_t size;
std::byte* data;
} dynamic;
};
public:
/**
* @brief Constructs a thing.
@@ -31,17 +211,9 @@ public:
* @param type Thing type.
* @param allocator Allocator for the thing's data.
*/
thing(const type& type, const mod_container& modules = nullptr, const allocator_type& allocator = {})
: thing(std::make_shared<class type>(type), modules, allocator) {}
/**
* @brief Constructs a thing.
*
* @param type Thing type.
* @param allocator Allocator for the thing's data.
*/
thing(const type_p& type, const mod_container& modules = nullptr, const allocator_type& allocator = {})
: m_type(type), m_size(compute_size(resolve_type(type, modules))), m_modules(modules), m_allocator(allocator) {
thing(const thing_type& type, const allocator_type& allocator = {})
: m_type(type), m_size(compute_size(type)), m_allocator(allocator) {
if (m_type.type == thing_type::Ref) return;
// TODO: Account for alignment
m_data = m_allocator.allocate(m_size);
std::memset(m_data, 0, m_size);
@@ -51,19 +223,15 @@ public:
* @brief Destructs a thing.
*/
~thing() {
if (m_data != nullptr && m_size > 0) m_allocator.deallocate(m_data, m_size);
if (m_type.type != thing_type::Ref && m_data != nullptr && m_size > 0) m_allocator.deallocate(m_data, m_size);
}
/**
* @brief Move constructor.
*/
thing(thing&& other) noexcept
: m_type(std::move(other.m_type)),
m_data(other.m_data),
m_size(other.m_size),
m_modules(std::move(other.m_modules)),
m_allocator(std::move(other.m_allocator)) {
other.m_type = {};
: m_type(other.m_type), m_data(other.m_data), m_size(other.m_size), m_allocator(std::move(other.m_allocator)) {
other.m_type.type = thing_type::Count;
other.m_data = nullptr;
other.m_size = 0;
}
@@ -75,9 +243,8 @@ public:
if (this == &other) return *this;
m_type = other.m_type;
m_data = other.m_data;
m_modules = std::move(other.m_modules);
m_allocator = std::move(other.m_allocator);
other.m_type = {};
other.m_type.type = thing_type::Count;
other.m_data = nullptr;
other.m_size = 0;
return *this;
@@ -85,8 +252,6 @@ public:
thing(const thing&) = delete;
thing& operator=(const thing&) = delete;
public:
void assign_mod_container(const mod_container& modules) { m_modules = modules; }
public:
/**
* @brief Returns a clone of the thing.
@@ -94,18 +259,22 @@ public:
* @return A clone of this thing.
*/
thing clone() const {
thing res(resolve_type(m_type, m_modules), m_modules, m_allocator);
switch (m_type->t) {
case type_t::Primitive:
case type_t::Reference: {
std::memcpy(res.m_data, m_data, m_size);
} break;
case type_t::List: {
copy_list(resolve_type(*m_type->list, m_modules), res.get<list_t>(), get<list_t>());
} break;
case type_t::Import: throw std::runtime_error("unreachable");
thing res(m_type, m_allocator);
switch (m_type.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
case thing_type::Ptr: std::memcpy(res.m_data, m_data, m_size); return std::move(res);
case thing_type::Array: copy_list(m_type, res.get<array>(), get<array>()); return std::move(res);
case thing_type::Ref: throw std::runtime_error("cannot clone references");
case thing_type::Count: break;
}
return std::move(res);
throw std::runtime_error("unreachable");
}
public:
/**
@@ -113,14 +282,26 @@ public:
*
* @return The type.
*/
constexpr auto type() const { return *m_type; }
constexpr thing_type type() const { return m_type; }
/**
* @brief Returns the thing's type.
* @brief Returns the thing's true type.
*
* @return The shared pointer to the type.
* If the thing is a reference, returns the referenced type.
*
* @return The true type.
*/
auto type() { return m_type; }
constexpr thing_type true_type() const { return (m_type.type == thing_type::Ref) ? *m_type.value.typeRef : m_type; }
/**
* @brief Checks if the thing is of a specified type.
*
* Compares the true type.
*
* @param type Type to compare.
* @return true if the types match.
*/
constexpr bool is(enum thing_type::type type) const { return true_type().type == type; }
public:
/**
* @brief Returns a raw data pointer.
@@ -143,8 +324,7 @@ public:
*/
template <typename T>
T& get() {
std::size_t size = m_size > 0 ? m_size : compute_size_na(resolve_type(m_type, m_modules));
if (size != sizeof(T)) throw bad_thing_access();
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
return *std::launder(reinterpret_cast<T*>(m_data));
}
@@ -155,8 +335,7 @@ public:
*/
template <typename T>
const T& get() const {
std::size_t size = m_size > 0 ? m_size : compute_size_na(resolve_type(m_type, m_modules));
if (size != sizeof(T)) throw bad_thing_access();
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
return *std::launder(reinterpret_cast<const T*>(m_data));
}
public:
@@ -253,129 +432,311 @@ public:
*
* @return The integer value.
*/
long_t integer() const {
if (m_type->t != type_t::Primitive) throw bad_thing_access();
switch (m_type->primitive) {
case sizeof(byte_t): return get<byte_t>();
case sizeof(short_t): return get<short_t>();
case sizeof(int_t): return get<int_t>();
case sizeof(long_t): return get<long_t>();
thing_type::s64 integer() const {
switch (true_type().type) {
case thing_type::S8: return get<thing_type::s8>();
case thing_type::S16: return get<thing_type::s16>();
case thing_type::S32: return get<thing_type::s32>();
case thing_type::S64: return get<thing_type::s64>();
case thing_type::U8: return get<thing_type::u8>();
case thing_type::U16: return get<thing_type::u16>();
case thing_type::U32: return get<thing_type::u32>();
case thing_type::U64: return get<thing_type::u64>();
default: throw std::runtime_error("unreachable");
}
}
thing reference() const {
thing res = { std::make_shared<class type>(m_type), m_modules, m_allocator };
res.get<reference_t>() = m_data;
return std::move(res);
}
void resize(thing_type::u64 newSize) {
if (!is(thing_type::Array)) throw bad_thing_access();
if (true_type().value.array.size > 0) throw std::runtime_error("cannot resize a static array");
thing resolve() const {
thing rsv = { resolve_type(m_type, m_modules), m_data, m_allocator };
while (rsv.type()->t == type_t::Reference)
rsv = { rsv.m_type->reference, rsv.get<reference_t>(), std::move(rsv.m_allocator) };
return rsv;
}
void resize(long_t newSize) {
if (m_type->t != type_t::List) throw bad_thing_access();
auto& list = get<list_t>();
if (newSize < 0 || newSize == list.size) return;
std::byte* newData = new std::byte[compute_size_na(resolve_type(*m_type->list, m_modules)) * newSize];
auto& array = get<union array>();
if (newSize < 0 || newSize == array.dynamic.size) return;
std::size_t innerSize = compute_size_na(*true_type().value.array.type);
std::byte* newData = new std::byte[innerSize * newSize];
std::memcpy(newData,
list.data,
compute_size_na(resolve_type(*m_type->list, m_modules)) * std::min(list.size, newSize));
list.size = newSize;
delete[] list.data;
list.data = newData;
array.dynamic.data,
innerSize * std::min(static_cast<thing_type::u64>(array.dynamic.size), newSize));
array.dynamic.size = newSize;
delete[] array.dynamic.data;
array.dynamic.data = newData;
}
thing at(long_t index) const {
if (m_type->t != type_t::List) throw bad_thing_access();
auto& list = get<list_t>();
if (index < 0 || index >= list.size) throw std::out_of_range("index out of range");
thing res = { *m_type->list, m_modules, m_allocator };
res.get<reference_t>() = list.data; // TODO: Account for padding, alignment and stuff
return std::move(res);
thing at(thing_type::u64 index) const {
if (!is(thing_type::Array)) throw bad_thing_access();
std::size_t elementSize = compute_size_na(*m_type.value.array.type);
if (m_type.value.array.size == 0) {
auto& array = get<union array>();
if (index < 0 || index >= array.dynamic.size) throw std::out_of_range("index out of range");
thing ref = { { thing_type::Ref, m_type.value.array.type }, m_allocator };
ref.m_data = array.dynamic.data + (index * elementSize);
return ref;
}
public:
static type_p resolve_type(const type_p& initType, const mod_container& modules) {
type_p rsv = initType;
while (rsv->t == type_t::Import)
rsv = *modules->at(initType->imp.mod)->type_at(initType->imp.type);
return rsv;
std::byte* data = reinterpret_cast<array*>(m_data)->flat;
if (index < 0 || index >= m_type.value.array.size) throw std::out_of_range("index out of range");
thing ref = { { thing_type::Ref, m_type.value.array.type }, m_allocator };
ref.m_data = data + (index * elementSize);
return ref;
}
thing_type::u64 length() const {
if (!is(thing_type::Array)) throw bad_thing_access();
return true_type().value.array.size == 0 ? get<array>().dynamic.size : true_type().value.array.size;
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T cast_to() const {
return visit_primitive([](auto value) { return static_cast<T>(value); });
}
/**
* @brief Changes reference thing's referenced thing.
*
* Yes.
*
* @param thing Thing.
*/
void reference(const thing& thing) {
if (m_type.type != thing_type::Ref || *m_type.value.typeRef != thing.type()) throw bad_thing_access();
m_data = thing.m_data;
}
/**
* @brief Self-explainatory.
*
* TODO: Document
*/
void assign(thing&& thing) {
class thing rhs = std::move(thing);
if (true_type() != rhs.true_type()) throw std::runtime_error("thing type mismatch");
// TODO: Move this to another function
switch (true_type().type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: std::memcpy(m_data, rhs.m_data, m_size); return;
case thing_type::Ptr:
case thing_type::Ref:
case thing_type::Array: throw std::runtime_error("unimplemented");
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
private:
static void copy_list(const type_p& innerType, list_t& dst, const list_t& src) {
dst.size = src.size;
if (dst.size <= 0) {
dst.data = nullptr;
static void copy_list(const thing_type& arrayType, array& dst, const array& src) {
if (arrayType.type != thing_type::Array || arrayType.value.array.type == nullptr)
throw std::runtime_error("invalid type");
const auto& innerType = *arrayType.value.array.type;
std::size_t elementSize = compute_size_na(innerType);
std::byte* data = nullptr;
const std::byte* srcData = nullptr;
std::size_t size = 0;
if (arrayType.value.array.size == 0) {
size = dst.dynamic.size = src.dynamic.size;
if (dst.dynamic.size < 0) {
dst.dynamic.data = nullptr;
return;
}
if (innerType == nullptr) throw std::runtime_error("inner type should not be null!");
std::size_t size = compute_size_na(innerType) * dst.size;
dst.data = new std::byte[size];
switch (innerType->t) {
case type_t::Primitive:
case type_t::Reference: std::memcpy(dst.data, src.data, size); break;
case type_t::List:
srcData = src.dynamic.data;
data = dst.dynamic.data = new std::byte[dst.dynamic.size];
} else {
data = dst.flat;
srcData = src.flat;
size = arrayType.value.array.size;
}
switch (innerType.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
case thing_type::Ptr:
case thing_type::Ref: std::memcpy(dst.flat, src.flat, size); return;
case thing_type::Array:
for (std::size_t i = 0; i < size; ++i) {
copy_list(*innerType->list,
*std::launder(reinterpret_cast<list_t*>(dst.data)),
*std::launder(reinterpret_cast<list_t*>(src.data)));
copy_list(*innerType.value.array.type,
*std::launder(reinterpret_cast<array*>(data + (i * elementSize))),
*std::launder(reinterpret_cast<const array*>(srcData + (i * elementSize))));
}
break;
case type_t::Import: throw std::runtime_error("unresolved type");
return;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
private:
static std::size_t compute_size_na(const type_p& type) {
switch (type->t) {
case type_t::Primitive: return type->primitive;
case type_t::Reference: return sizeof(reference_t);
case type_t::List: return sizeof(list_t);
case type_t::Import: throw std::runtime_error("unresolved type");
static std::size_t compute_size_na(const thing_type& type) {
switch (type.type) {
case thing_type::S8: return sizeof(thing_type::s8);
case thing_type::S16: return sizeof(thing_type::s16);
case thing_type::S32: return sizeof(thing_type::s32);
case thing_type::S64: return sizeof(thing_type::s64);
case thing_type::U8: return sizeof(thing_type::u8);
case thing_type::U16: return sizeof(thing_type::u16);
case thing_type::U32: return sizeof(thing_type::u32);
case thing_type::U64: return sizeof(thing_type::u64);
case thing_type::Ptr: return sizeof(void*);
case thing_type::Ref: return compute_size_na(*type.value.typeRef);
case thing_type::Array:
return type.value.array.size == 0 ? sizeof(array)
: compute_size_na(*type.value.array.type) * type.value.array.size;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
// NOTE: Align to 4 bytes
static std::size_t compute_size(const type_p& type) { return (compute_size_na(type) + 3) & ~3; }
private:
thing(const type_p& type, std::byte* data, const allocator_type& allocator = {})
: m_type(type), m_size(0), m_data(data), m_allocator(allocator) {}
static std::size_t compute_size(const thing_type& type) { return (compute_size_na(type) + 3) & ~3; }
private:
template <typename Func>
decltype(auto) visit_primitive(Func&& func) const {
switch (true_type().type) {
case thing_type::S8: return std::forward<Func>(func)(get<thing_type::s8>());
case thing_type::S16: return std::forward<Func>(func)(get<thing_type::s16>());
case thing_type::S32: return std::forward<Func>(func)(get<thing_type::s32>());
case thing_type::S64: return std::forward<Func>(func)(get<thing_type::s64>());
case thing_type::U8: return std::forward<Func>(func)(get<thing_type::u8>());
case thing_type::U16: return std::forward<Func>(func)(get<thing_type::u16>());
case thing_type::U32: return std::forward<Func>(func)(get<thing_type::u32>());
case thing_type::U64: return std::forward<Func>(func)(get<thing_type::u64>());
default: throw bad_thing_access();
}
}
template <typename Op>
thing binary_op(const thing& rhs, const Op& op) const {
const thing lhsRsv = resolve();
const thing rhsRsv = rhs.resolve();
if (thing_type::is_primitive(true_type().type) && thing_type::is_primitive(true_type().type)) {
static constexpr enum thing_type::type promotions[8 * 8] = {
// S8
thing_type::S8,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::U64,
// S16
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::U64,
// S32
thing_type::S32,
thing_type::S32,
thing_type::S32,
thing_type::S64,
thing_type::S32,
thing_type::S32,
thing_type::U32,
thing_type::U64,
// S64
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::U64,
// U8
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::U8,
thing_type::U16,
thing_type::U32,
thing_type::U64,
// U16
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::U16,
thing_type::U16,
thing_type::U32,
thing_type::U64,
// U32
thing_type::S32,
thing_type::S32,
thing_type::U32,
thing_type::S64,
thing_type::U32,
thing_type::U32,
thing_type::U32,
thing_type::U64,
// U64
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
};
if (lhsRsv.type().t == type_t::Primitive && rhsRsv.type().t == type_t::Primitive) {
std::size_t size = std::max(lhsRsv.type().primitive, rhsRsv.type().primitive);
enum thing_type::type resultType = promotions[true_type().type + (rhs.true_type().type * 8)];
long_t result = op(lhsRsv.integer(), rhsRsv.integer());
thing res({ size }, m_modules, m_allocator);
switch (size) {
case sizeof(byte_t): res.get<byte_t>() = result; break;
case sizeof(short_t): res.get<short_t>() = result; break;
case sizeof(int_t): res.get<int_t>() = result; break;
case sizeof(long_t): res.get<long_t>() = result; break;
default: throw std::runtime_error("unreachable");
thing res = { thing_type{ resultType }, m_allocator };
switch (resultType) {
case thing_type::S8:
res.get<thing_type::s8>() = Op{}(cast_to<thing_type::s8>(), rhs.cast_to<thing_type::s8>());
return res;
case thing_type::S16:
res.get<thing_type::s16>() = Op{}(cast_to<thing_type::s16>(), rhs.cast_to<thing_type::s16>());
return res;
case thing_type::S32:
res.get<thing_type::s32>() = Op{}(cast_to<thing_type::s32>(), rhs.cast_to<thing_type::s32>());
return res;
case thing_type::S64:
res.get<thing_type::s64>() = Op{}(cast_to<thing_type::s64>(), rhs.cast_to<thing_type::s64>());
return res;
case thing_type::U8:
res.get<thing_type::u8>() = Op{}(cast_to<thing_type::u8>(), rhs.cast_to<thing_type::u8>());
return res;
case thing_type::U16:
res.get<thing_type::u16>() = Op{}(cast_to<thing_type::u16>(), rhs.cast_to<thing_type::u16>());
return res;
case thing_type::U32:
res.get<thing_type::u32>() = Op{}(cast_to<thing_type::u32>(), rhs.cast_to<thing_type::u32>());
return res;
case thing_type::U64:
res.get<thing_type::u64>() = Op{}(cast_to<thing_type::u64>(), rhs.cast_to<thing_type::u64>());
return res;
case thing_type::Ptr: // TODO: Pointer arithmetics
case thing_type::Ref:
case thing_type::Array:
case thing_type::Count: break;
}
return std::move(res);
throw std::runtime_error("unreachable");
}
throw std::runtime_error("unexpected operation");
}
private:
type_p m_type;
thing_type m_type;
std::size_t m_size;
std::byte* m_data;
mod_container m_modules;
allocator_type m_allocator;
};
+2 -1
View File
@@ -69,8 +69,9 @@ public:
[[nodiscard]] T* allocate(std::size_t count = 1) {
for (auto it = m_deadThings->begin(); it != m_deadThings->end(); ++it) {
if (it->second != count) continue;
T* data = it->first;
m_deadThings->erase(it);
return it->first;
return data;
}
return m_arena->allocate<T>(count);
}
-141
View File
@@ -1,141 +0,0 @@
#ifndef FURVM_TYPE_HPP
#define FURVM_TYPE_HPP
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp" // IWYU pragma: keep
#include <cstdint>
namespace furvm {
enum class type_t : std::uint32_t {
Primitive = 0,
Reference,
List,
Import,
};
using primitive_type = std::uint64_t;
using reference_type = type_p;
using list_type = type_h;
struct import_type {
mod_id mod;
type_id type;
};
struct type {
type_t t;
union {
primitive_type primitive;
reference_type reference;
list_type list;
import_type imp;
};
type(type_t type)
: t(type), primitive(0) {}
type(primitive_type primitive)
: t(type_t::Primitive), primitive(primitive) {}
type(const reference_type& reference)
: t(type_t::Reference), reference(reference) {}
type(const list_type& list)
: t(type_t::List), list(list) {}
type(const import_type& imp)
: t(type_t::Import), imp(imp) {}
~type() {
switch (t) {
case type_t::Reference: reference.~reference_type(); break;
case type_t::List: list.~list_type(); break;
case type_t::Import: imp.~import_type(); break;
default: break;
}
}
type(type&& other) noexcept
: t(other.t) {
switch (t) {
case type_t::Primitive: primitive = other.primitive; break;
case type_t::Reference: reference = std::move(other.reference); break;
case type_t::List: list = std::move(other.list); break;
case type_t::Import: imp = std::move(other.imp); break;
}
}
type& operator=(type&& other) noexcept {
if (this == &other) return *this;
t = other.t;
switch (t) {
case type_t::Primitive: primitive = other.primitive; break;
case type_t::Reference: reference = std::move(other.reference); break;
case type_t::List: list = std::move(other.list); break;
case type_t::Import: imp = std::move(other.imp); break;
}
return *this;
}
type(const type& other)
: t(other.t) {
switch (t) {
case type_t::Primitive: primitive = other.primitive; break;
case type_t::Reference: reference = other.reference; break;
case type_t::List: list = other.list; break;
case type_t::Import: imp = other.imp; break;
}
}
type& operator=(const type& other) {
if (this == &other) return *this;
t = other.t;
switch (t) {
case type_t::Primitive: primitive = other.primitive; break;
case type_t::Reference: reference = other.reference; break;
case type_t::List: list = other.list; break;
case type_t::Import: imp = other.imp; break;
}
return *this;
}
};
using byte_t = std::int8_t; /**< A 1-byte integer. */
using short_t = std::int16_t; /**< A 2-byte integer. */
using int_t = std::int32_t; /**< A 4-byte integer. */
using long_t = std::int64_t; /**< An 8-byte integer. */
using reference_t = std::byte*;
struct list_t {
long_t size;
std::byte* data;
};
/**
* @brief A 1-byte integer thing type.
*/
inline static type byteType = { sizeof(byte_t) }; // NOLINT
/**
* @brief A 2-byte integer thing type.
*/
inline static type shortType = { sizeof(short_t) }; // NOLINT
/**
* @brief A 4-byte integer thing type.
*/
inline static type intType = { sizeof(int_t) }; // NOLINT
/**
* @brief An 8-byte integer thing type.
*/
inline static type longType = { sizeof(long_t) }; // NOLINT
} // namespace furvm
#endif // FURVM_TYPE_HPP
-24
View File
@@ -1,24 +0,0 @@
#include "furvm/context.hpp"
#include "furvm/thing.hpp" // IWYU pragma: keep
#include "furvm/type.hpp"
#include <cstdint>
#include <memory>
namespace furvm {
context::context()
: m_thingAllocator(m_thingArena) {
mod core;
core.emplace_type(std::make_shared<type>(byteType));
core.emplace_type(std::make_shared<type>(shortType));
core.emplace_type(std::make_shared<type>(intType));
core.emplace_type(std::make_shared<type>(longType));
static_assert(sizeof(std::uintptr_t) == 8, "Unsupported platform");
core.emplace_type(*core.type_at(3));
emplace("core", std::move(core)).dispatch();
}
} // namespace furvm
+151 -38
View File
@@ -7,12 +7,43 @@
#include "furvm/instruction.hpp"
#include "furvm/thing.hpp"
#include <cassert>
#include <cstdint>
#include <stdexcept>
#include <utility>
#include <vector>
namespace furvm {
thing_type executor::thing_type_impl(mod_h mod, mod_type type) const {
while (type.type == mod_type::Import) {
auto imprt = std::move(type.value.imprt);
mod = m_context->at(imprt.modId);
type = *mod->type_at(imprt.typeId);
}
switch (static_cast<enum thing_type::type>(type.type)) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: return { static_cast<enum thing_type::type>(type.type) };
case thing_type::Ptr: return { thing_type::Ptr, thing_type(mod, *mod->type_at(type.value.typeRef)) };
case thing_type::Array: {
return { static_cast<enum thing_type::type>(type.type),
{ thing_type(mod, *mod->type_at(type.value.array.typeId)), type.value.array.size } };
}
default: throw std::runtime_error("invalid thing type");
}
}
thing_type* executor::thing_type(const mod_h& mod, const mod_type& type) const {
struct thing_type thingType = thing_type_impl(mod, type);
return m_context->thing_type_store().insert(thingType);
}
void executor::push_frame(const mod_h& mod, function function) {
mod_h modInst = mod;
while (function.type() == function_t::Import) {
@@ -20,19 +51,28 @@ void executor::push_frame(const mod_h& mod, function function) {
function = *modInst->function_at(function.imp().function);
}
auto signature = function.signature();
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());
args.reserve(signature.params.size());
for (const auto& param : signature.params) {
auto arg = pop_thing();
if (arg->type() != *thing_type(mod, *param)) throw std::runtime_error("function argument type mismatch");
args.push_back(std::move(arg));
}
struct thing_type* returnType = nullptr;
if (function.signature().returnType.has_value())
returnType = thing_type(mod, *function.signature().returnType.value()); // NOLINT
switch (function.type()) {
case function_t::Normal: {
m_frames.emplace((struct executor::frame){ mod, function.position(), m_stack.size(), std::move(args) });
m_frames.emplace(
(struct executor::frame){ mod, function.position(), m_stack.size(), returnType, std::move(args) });
} break;
case function_t::Native: {
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), std::move(args) });
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), returnType, std::move(args) });
modInst->get_native_function(function.native())(*this);
m_frames.pop();
pop_frame();
} break;
default: throw std::runtime_error("unexpected function type");
}
@@ -42,6 +82,10 @@ struct executor::frame executor::pop_frame() {
if (m_frames.empty()) throw stack_underflow();
struct executor::frame frame = m_frames.top();
m_frames.pop();
thing_h returnValue;
if (frame.returnType != nullptr) returnValue = pop_thing();
if (m_stack.size() != frame.stackBase) throw std::runtime_error("unexhausted stack");
if (frame.returnType != nullptr) push_thing(std::move(returnValue));
return frame;
}
@@ -57,7 +101,7 @@ thing_h executor::pop_thing() {
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
thing_h top = std::move(m_stack.top());
m_stack.pop();
return top;
return std::move(top);
}
thing_h executor::thing() const {
@@ -79,7 +123,7 @@ void executor::store_thing(variable_t variable, thing_h&& thing) {
thing_h executor::load_thing(variable_t variable) const {
const auto& frame = m_frames.top();
return frame.variables[variable];
return { frame.variables[variable] };
}
void executor::step() {
@@ -90,9 +134,63 @@ void executor::step() {
instruction_t instr = static_cast<instruction_t>((*frame.mod).byte(frame.position++));
switch (instr) {
case instruction_t::NoOperation: break;
case instruction_t::PushB2I: {
push_thing({ *m_context->at("core")->type_at(2), m_context, m_context->thing_alloc() })->get<int_t>() =
frame.mod->byte(frame.position++);
case instruction_t::PushS8: {
push_thing({ (struct thing_type){ thing_type::S8 }, m_context->thing_alloc() })->get<thing_type::s8>() =
static_cast<thing_type::s8>(frame.mod->byte(frame.position++));
} break;
case instruction_t::PushU8: {
push_thing({ (struct thing_type){ thing_type::U8 }, m_context->thing_alloc() })->get<thing_type::u8>() =
static_cast<thing_type::u8>(frame.mod->byte(frame.position++));
} break;
case instruction_t::PushS16: {
thing_type::u16 value = frame.mod->byte(frame.position++);
value |= static_cast<thing_type::u16>(frame.mod->byte(frame.position++) << 8);
push_thing({ (struct thing_type){ thing_type::S16 }, m_context->thing_alloc() })->get<thing_type::s16>() =
static_cast<thing_type::s16>(value);
} break;
case instruction_t::PushU16: {
thing_type::u16 value = frame.mod->byte(frame.position++);
value |= static_cast<thing_type::u16>(frame.mod->byte(frame.position++) << 8);
push_thing({ (struct thing_type){ thing_type::U16 }, m_context->thing_alloc() })->get<thing_type::u16>() =
value;
} break;
case instruction_t::PushS32: {
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() })->get<thing_type::s32>() =
static_cast<thing_type::s32>(frame.mod->byte(frame.position++));
} break;
case instruction_t::PushU32: {
push_thing({ (struct thing_type){ thing_type::U32 }, m_context->thing_alloc() })->get<thing_type::u32>() =
static_cast<thing_type::u32>(frame.mod->byte(frame.position++));
} break;
case instruction_t::Array: {
mod_type_id typeId = static_cast<mod_type_id>(frame.mod->byte(frame.position)) |
(static_cast<mod_type_id>(frame.mod->byte(frame.position + 1)) << 8) |
(static_cast<mod_type_id>(frame.mod->byte(frame.position + 2)) << 16) |
(static_cast<mod_type_id>(frame.mod->byte(frame.position + 3)) << 24);
frame.position += 4;
const auto& type = *thing_type(frame.mod, *frame.mod->type_at(typeId));
if (type.type != thing_type::Array || type.value.array.type == nullptr || type.value.array.type == &type)
throw std::runtime_error("invalid array type");
auto array = push_thing({ type, m_context->thing_alloc() });
if (type.value.array.size == 0) {
auto sizeThing = pop_thing();
std::int64_t size = sizeThing->integer();
array->resize(size);
}
} break;
case instruction_t::Get: {
auto index = pop_thing();
auto array = pop_thing();
push_thing(array->at(index->integer()));
} break;
case instruction_t::Set: {
auto element = pop_thing();
auto index = pop_thing();
auto array = pop_thing();
array->at(index->integer()).assign(std::move(element->clone()));
} break;
case instruction_t::Drop: {
pop_thing();
@@ -100,12 +198,20 @@ void executor::step() {
case instruction_t::Duplicate: {
push_thing(thing());
} break;
case instruction_t::Swap: {
auto thing1 = pop_thing();
auto thing2 = pop_thing();
push_thing(std::move(thing1));
push_thing(std::move(thing2));
} break;
case instruction_t::Clone: {
push_thing(std::move(thing()->clone()));
} break;
case instruction_t::Reference: {
auto thing = pop_thing();
push_thing(std::move(thing->reference()));
push_thing({ (struct thing_type){ thing_type::Ref, m_context->thing_type_store().insert(thing->type()) },
m_context->thing_alloc() })
->reference(*thing);
} break;
case instruction_t::Add: {
auto rhs = pop_thing();
@@ -163,29 +269,40 @@ void executor::step() {
push_thing(lhs->greater_equals(*rhs));
} break;
case instruction_t::Pointerof: {
auto thing = pop_thing()->resolve();
auto ptr = push_thing({ *m_context->at("core")->type_at(4), m_context, m_context->thing_alloc() });
switch (furvm::thing<>::resolve_type(thing.type(), m_context)->t) {
case type_t::Primitive: ptr->get<std::uintptr_t>() = reinterpret_cast<std::uintptr_t>(thing.raw()); break;
case type_t::List:
ptr->get<std::uintptr_t>() = reinterpret_cast<std::uintptr_t>(thing.get<list_t>().data);
auto thing = pop_thing();
auto ptr =
push_thing({ (struct thing_type){ thing_type::Ptr, m_context->thing_type_store().at(thing->type().id) },
m_context->thing_alloc() });
ptr->get<void*>() = thing->raw();
} break;
case instruction_t::Sizeof: {
auto thing = pop_thing();
auto size = push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() });
switch (thing->type().type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
size->get<thing_type::u64>() = static_cast<thing_type::u64>(thing_type::primitive_size(thing->type().type));
break;
case thing_type::Ptr: size->get<thing_type::u64>() = static_cast<thing_type::u64>(sizeof(void*)); break;
case thing_type::Array:
/* TODO: Return actual memory size of the array
* By the memory size I mean the length times sizeof single element.
*/
size->get<thing_type::u64>() = thing->length();
break;
case type_t::Reference:
case type_t::Import:
default: throw std::runtime_error("unreachable");
}
} break;
case instruction_t::Sizeof: {
auto thing = pop_thing()->resolve();
auto ptr = push_thing({ *m_context->at("core")->type_at(3), m_context, m_context->thing_alloc() });
auto type = furvm::thing<>::resolve_type(thing.type(), m_context);
switch (type->t) {
case type_t::Primitive: ptr->get<long_t>() = static_cast<long_t>(type->primitive); break;
case type_t::List: ptr->get<long_t>() = thing.get<list_t>().size; break;
case type_t::Reference:
case type_t::Import:
default: throw std::runtime_error("unreachable");
}
case instruction_t::Lengthof: {
auto thing = pop_thing();
auto length = push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() });
length->get<thing_type::u64>() = thing->length();
} break;
case instruction_t::Load: {
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
@@ -206,22 +323,18 @@ void executor::step() {
push_frame(frame.mod, *frame.mod->function_at(funcId));
} break;
case instruction_t::Jump: {
frame.position += ((std::int8_t)frame.mod->byte(frame.position)) + 1;
std::int8_t offset = static_cast<std::int8_t>(frame.mod->byte(frame.position++));
frame.position += offset;
} break;
case instruction_t::JumpNotZero: {
byte offset = frame.mod->byte(frame.position++);
auto cond = pop_thing();
if (cond->get<int_t>() != 0) frame.position += (std::int8_t)offset;
if (cond->integer() != 0) frame.position += (std::int8_t)offset;
} 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");
}
+13 -7
View File
@@ -7,7 +7,7 @@
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() }) {}
: m_type(function_t::Import), m_value(import_function{ mod.id(), function.id() }) {}
function::~function() {
switch (m_type) {
@@ -23,7 +23,7 @@ function::~function() {
}
function::function(function&& other) noexcept
: m_name(std::move(other.m_name)), m_type(other.m_type), m_paramCount(other.m_paramCount) {
: m_type(other.m_type), m_signature(std::move(other.m_signature)) {
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -42,9 +42,8 @@ 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_paramCount = other.m_paramCount;
m_signature = other.m_signature;
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -63,7 +62,7 @@ function& function::operator=(function&& other) noexcept {
}
function::function(const function& other)
: m_name(other.m_name), m_type(other.m_type), m_paramCount(other.m_paramCount) {
: m_type(other.m_type), m_signature(other.m_signature) {
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -81,9 +80,8 @@ function::function(const function& other)
function& function::operator=(const function& other) {
if (this == &other) return *this;
m_name = other.m_name;
m_type = other.m_type;
m_paramCount = other.m_paramCount;
m_signature = other.m_signature;
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -100,4 +98,12 @@ function& function::operator=(const function& other) {
return *this;
}
namespace detail {
std::size_t function_sig_hash::operator()(const function_sig& signature) const {
return furlang::utility::vector_hash<mod_type_h, detail::handle_hash<mod_type_h>>{}(signature.params);
}
} // namespace detail
} // namespace furvm
+57 -7
View File
@@ -10,28 +10,78 @@
#include <memory>
#include <sstream>
static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
using namespace furvm;
switch (thing.true_type().type) {
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
case thing_type::S64: std::cout << thing.get<thing_type::s64>(); break;
case thing_type::U8: std::cout << thing.get<thing_type::u8>(); break;
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
case thing_type::Array:
std::cout << "{ ";
for (thing_type::u64 i = 0; i < thing.length(); ++i) {
if (i > 0) std::cout << ", ";
print_thing(thing.at(i));
}
std::cout << " }";
break;
default: std::cerr << "{Type not recognized}";
}
}
int main(int argc, char** argv) {
auto context = std::make_shared<furvm::context>();
#if 1 // NOLINT
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <mod.fmod>\n";
return 1;
}
auto context = std::make_shared<furvm::context>();
std::ifstream file(argv[1]);
if (!file.is_open()) {
std::cerr << "Failed to open " << argv[1] << '\n';
return 1;
}
furvm::mod_h mod = context->emplace("main", furvm::mod::load(file));
furvm::function_h func = mod->function_at("main");
furvm::mod_h mod = context->emplace("main", std::move(furvm::mod::load(file)));
auto mainFunc = mod->function_at("main", furvm::function_sig{});
#else
static const furvm::byte s_bytecode[] = {
static_cast<furvm::byte>(furvm::instruction_t::Array),
1,
0,
0,
0,
static_cast<furvm::byte>(furvm::instruction_t::Reference),
static_cast<furvm::byte>(furvm::instruction_t::Lengthof),
static_cast<furvm::byte>(furvm::instruction_t::Call),
1,
0,
static_cast<furvm::byte>(furvm::instruction_t::Drop),
static_cast<furvm::byte>(furvm::instruction_t::Return),
};
mod->set_native_function("print",
[](furvm::executor& executor) { std::cout << executor.load_thing(0)->integer() << '\n'; });
auto mod = context->emplace("main", s_bytecode, s_bytecode + sizeof(s_bytecode));
auto charType = mod->emplace_type(furvm::mod_type::S8);
auto arrayType = mod->emplace_type(charType.id(), 10);
auto u64Type = mod->emplace_type(furvm::mod_type::U64);
auto mainFunc = mod->emplace_function("main", furvm::function_sig{}, 0);
mod->emplace_function(furvm::function_sig{ { u64Type }, u64Type }, "print").dispatch();
#endif
mod->set_native_function("println", [](furvm::executor& executor) {
auto arg = executor.load_thing(0);
print_thing(*arg);
std::cout << '\n';
});
furvm::executor_h executor = context->emplace_executor(context);
executor->push_frame(mod, *func);
executor->push_frame(mod, *mainFunc);
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
executor->step();
+106 -57
View File
@@ -1,13 +1,12 @@
#include "furvm/module.hpp"
#include "furvm/detail/serialization.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/type.hpp"
#include <cstdint>
#include <cstring>
#include <ios>
#include <memory>
#include <stdexcept>
#include <utility>
@@ -17,6 +16,39 @@ std::ostream& mod::serialize(std::ostream& os) const {
os.write(MAGIC, sizeof(MAGIC));
detail::serialize(os, std::uint32_t(0)); // version
mod_type_id typeCount = mod_type_id(m_types.cend() - m_types.cbegin());
detail::serialize(os, typeCount);
for (mod_type_id id = 0; id < typeCount; ++id) {
if (!m_types.contains(id)) {
detail::serialize(os, std::uint8_t(0xFF)); // null type
continue;
}
auto type = *m_types.at(id);
detail::serialize(os, static_cast<std::uint8_t>(type.type));
switch (type.type) {
case mod_type::S8:
case mod_type::S16:
case mod_type::S32:
case mod_type::S64:
case mod_type::U8:
case mod_type::U16:
case mod_type::U32:
case mod_type::U64: break;
case mod_type::Ptr:
case mod_type::Ref: detail::serialize(os, type.value.typeRef); break;
case mod_type::Array: {
detail::serialize(os, type.value.array.typeId);
detail::serialize(os, type.value.array.size);
} break;
case mod_type::Import: {
detail::serialize(os, type.value.imprt.modId);
detail::serialize(os, type.value.imprt.typeId);
} break;
case mod_type::Count: throw std::runtime_error("unreachable");
}
}
function_id funcCount = m_functions.cend() - m_functions.cbegin();
detail::serialize(os, funcCount);
for (function_id id = 0; id < funcCount; ++id) {
@@ -27,10 +59,20 @@ std::ostream& mod::serialize(std::ostream& os) const {
continue;
}
// Function name
function_h func = m_functions.at(id);
bool isPublic = m_publicFunctions.find(func->name()) != m_publicFunctions.end();
detail::serialize(os, isPublic ? func->name() : ""); // private functions have empty names
detail::serialize(os, func->param_count());
if (auto it = m_functionMap.find(func.id()); it != m_functionMap.end()) {
detail::serialize(os, it->second.first);
} else {
detail::serialize(os, "");
}
// Function signature
detail::serialize(os, static_cast<std::uint32_t>(func->signature().params.size()));
for (std::uint32_t i = 0; i < func->signature().params.size(); ++i)
detail::serialize(os, func->signature().params[i].id());
// Function body/value
detail::serialize(os, std::uint8_t(func->type()));
switch (func->type()) {
case function_t::Normal: {
@@ -46,26 +88,6 @@ std::ostream& mod::serialize(std::ostream& os) const {
}
}
type_id typeCount = type_id(m_types.cend() - m_types.cbegin());
detail::serialize(os, typeCount);
for (type_id id = 0; id < typeCount; ++id) {
if (!m_types.contains(id)) {
detail::serialize(os, std::uint8_t(0xFF)); // null type
continue;
}
auto type = *m_types.at(id);
switch (type->t) {
case type_t::Primitive: detail::serialize(os, type->primitive); break;
case type_t::Reference: throw std::runtime_error("reference type serialization is unimplemented");
case type_t::List: detail::serialize(os, type->list.id()); break;
case type_t::Import: {
detail::serialize(os, type->imp.mod);
detail::serialize(os, type->imp.type);
} break;
}
}
detail::serialize(os, std::uint64_t(m_bytecode.size()));
return os.write(reinterpret_cast<const char*>(m_bytecode.data()), static_cast<std::streamsize>(m_bytecode.size()));
}
@@ -80,13 +102,64 @@ mod mod::load(std::istream& is) {
mod mod;
mod_type_id typeCount = 0;
detail::load(is, typeCount);
for (mod_type_id id = 0; id < typeCount; ++id) {
std::uint8_t type = 0;
detail::load(is, type);
if (type == 0xFF) continue;
switch ((enum mod_type::type)type) {
case mod_type::S8:
case mod_type::S16:
case mod_type::S32:
case mod_type::S64:
case mod_type::U8:
case mod_type::U16:
case mod_type::U32:
case mod_type::U64: {
mod_type theType = { (enum mod_type::type)type };
mod.emplace_type(id, theType).dispatch();
} break;
case mod_type::Ptr: {
mod_type_id typeId = 0;
detail::load(is, typeId);
mod.emplace_type(id, typeId).dispatch();
} break;
case mod_type::Array: {
mod_type_id typeId = 0;
detail::load(is, typeId);
std::size_t size = 0;
detail::load(is, size);
mod.emplace_type(id, typeId, size).dispatch();
} break;
case mod_type::Import: {
std::string modName;
detail::load(is, modName);
mod_type_id typeId = 0;
detail::load(is, typeId);
mod.emplace_type(id, std::move(modName), typeId).dispatch();
} break;
default: throw std::runtime_error("unknown type type");
}
}
function_id functionCount = 0;
detail::load(is, functionCount);
for (function_id id = 0; id < functionCount; ++id) {
std::string name;
detail::load(is, name);
function_sig signature;
std::uint32_t paramCount = 0;
detail::load(is, paramCount);
signature.params.reserve(paramCount);
while (signature.params.size() < paramCount) {
thing_id param{ 0 };
detail::load(is, param);
signature.params.emplace_back(mod.type_at(param));
}
std::uint8_t type = 0;
detail::load(is, type);
if (type == 0xFF) continue;
@@ -95,12 +168,18 @@ mod mod::load(std::istream& is) {
case function_t::Normal: {
decltype(std::declval<function>().position()) offset = 0;
detail::load(is, offset);
mod.emplace_function(id, std::move(name), paramCount, offset).dispatch();
if (name.empty())
mod.emplace_function(id, std::move(signature), offset).dispatch();
else
mod.emplace_function(std::move(name), id, std::move(signature), offset).dispatch();
} break;
case function_t::Native: {
std::string native;
detail::load(is, native);
mod.emplace_function(id, std::move(name), paramCount, std::move(native)).dispatch();
if (name.empty())
mod.emplace_function(id, std::move(signature), std::move(native)).dispatch();
else
mod.emplace_function(std::move(name), id, std::move(signature), std::move(native)).dispatch();
} break;
case function_t::Import: {
std::string modName;
@@ -113,36 +192,6 @@ mod mod::load(std::istream& is) {
}
}
type_id typeCount = 0;
detail::load(is, typeCount);
for (type_id id = 0; id < typeCount; ++id) {
std::uint8_t type = 0;
detail::load(is, type);
if (type == 0xFF) continue;
switch ((type_t)type) {
case type_t::Primitive: {
primitive_type primitive = 0;
detail::load(is, primitive);
mod.emplace_type(id, std::make_shared<class type>(primitive)).dispatch();
} break;
case type_t::Reference: throw std::runtime_error("reference type serialization is unimplemented");
case type_t::List: {
type_id typeId = 0;
detail::load(is, typeId);
mod.emplace_type(id, std::make_shared<class type>(mod.type_at(typeId))).dispatch();
} break;
case type_t::Import: {
std::string modName;
detail::load(is, modName);
type_id typeId = 0;
detail::load(is, typeId);
mod.emplace_type(id, std::make_shared<class type>(import_type{ std::move(modName), typeId })).dispatch();
} break;
default: throw std::runtime_error("unknown type type");
}
}
std::uint64_t bytecodeLength = 0;
detail::load(is, bytecodeLength);
mod.bytecode().resize(bytecodeLength);
+26 -1
View File
@@ -1,3 +1,28 @@
#include "furlang/arena.hpp"
#include "furvm/furvm.hpp"
#include "furvm/thing.hpp"
#include "furvm/thing_allocator.hpp"
#include "gtest/gtest.h" // IWYU pragma: keep
namespace {}
namespace {
// TODO: Basic program tests (e.g. for loops)
TEST(Things, Ops) {
furlang::arena arena;
furvm::thing_allocator<std::byte> alloc{ arena };
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
lhs.get<furvm::thing_type::u32>() = 6;
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
rhs.get<furvm::thing_type::u32>() = 7;
auto res = lhs.add(rhs);
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
EXPECT_EQ(lhs.get<furvm::thing_type::u32>(), 6);
EXPECT_EQ(rhs.get<furvm::thing_type::u32>(), 7);
EXPECT_EQ(res.get<furvm::thing_type::u32>(), 6 + 7);
}
} // namespace