forked from KPGPMC/furlang
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
549af109b9
|
|||
|
6dce7016ce
|
|||
|
a1e43576cc
|
|||
|
9f950e45e7
|
|||
|
dfaa3081cb
|
|||
|
46e92deed5
|
|||
|
42e71080a2
|
|||
|
03448d865e
|
|||
|
cb845ca625
|
|||
|
798287ef47
|
|||
|
466d7f003b
|
|||
|
ff3fce53f5
|
|||
|
9b280fab22
|
|||
|
e1b75ccc8e
|
|||
|
cd715ae779
|
|||
|
8d3859ce70
|
|||
|
3c0588e8db
|
|||
|
abbc1714c1
|
|||
|
b7da2f3d32
|
|||
|
d49b4f5bc7
|
|||
|
7de645d323
|
|||
|
2519560451
|
|||
|
7c74850aff
|
|||
|
81a439b2ad
|
|||
|
b913fc0c8b
|
|||
|
ee9eadeea8
|
|||
|
642d57622a
|
|||
|
9690ac3617
|
|||
|
f89f643870
|
|||
|
100c542fe1
|
|||
|
cf284592fd
|
|||
|
f5ebc767ab
|
|||
|
484e16963f
|
|||
|
575054b75a
|
|||
|
a7ab214ce3
|
|||
|
e087c11008
|
|||
|
2164ab0d97
|
|||
|
11b05b8c18
|
|||
|
6ec4710eaa
|
|||
|
09252c3ec3
|
|||
|
3d461048cc
|
|||
|
482ab859ed
|
|||
|
42f020977e
|
|||
|
6ab9254746
|
|||
|
8466902281
|
|||
|
7b609f87c9
|
|||
|
0bcbe1d7d1
|
|||
|
8702298cd2
|
|||
|
55932f6113
|
|||
|
a3db4ef1e1
|
|||
| 079f6326a4 | |||
|
d8d81851d3
|
|||
|
abeb5b390b
|
|||
|
a61f5c194b
|
|||
|
d497e2de45
|
|||
| 1965ab26af | |||
|
70b7b6a2f4
|
|||
|
b5dcd6690a
|
|||
|
0baf896c3a
|
|||
| 318b3e8a65 | |||
|
7d2e748c27
|
|||
|
e485a7900b
|
|||
|
68e1f8fda0
|
|||
|
8909a0cfff
|
|||
|
dad2c180d6
|
|||
|
4478a839b7
|
|||
|
9549a7b61a
|
|||
|
51483db283
|
|||
|
3759c0361f
|
|||
|
1ead2f6592
|
|||
|
b6d3387388
|
|||
|
3a2fa32ce1
|
|||
|
f2294079a0
|
|||
|
39f873a984
|
|||
|
d46aa45c1f
|
|||
|
190aa8f985
|
|||
|
f80abd68ad
|
|||
|
99666495e7
|
|||
|
6f023765b5
|
|||
|
65e5fdf324
|
|||
|
5a1b7c5aa6
|
|||
|
f14f3854e2
|
|||
|
6fbdb2299b
|
|||
|
ec2c81fc56
|
|||
|
e9b38e95a2
|
|||
|
00bcc0e6a5
|
|||
|
e8ccaa6390
|
|||
|
c2b32c9604
|
|||
|
c9f714642c
|
|||
|
0dfa8c1c02
|
|||
|
78acfb6ee2
|
|||
|
43dac1ec6d
|
|||
|
b35a274e81
|
|||
|
63ea593c1e
|
|||
|
8dfc880c07
|
|||
|
eec25e3aea
|
|||
|
e809eb82c7
|
|||
|
9fccaefbfc
|
|||
|
a262014348
|
|||
|
986015bc09
|
|||
|
ac1e226b4b
|
|||
|
d0f3637539
|
|||
|
f1f62fad8c
|
|||
|
065a2af699
|
|||
|
767970c69b
|
|||
|
97c01bc96c
|
|||
|
abd975be23
|
|||
|
be386cf230
|
|||
|
a14252bd1d
|
|||
|
6b92e19b23
|
|||
|
865691df26
|
|||
|
b4fda8d7d0
|
|||
|
3f6fcc56ff
|
|||
|
c1b65838b0
|
|||
| eaa74fd573 | |||
| 9cd54c1c60 |
+6
-1
@@ -11,6 +11,7 @@ Checks: >
|
||||
-bugprone-assignment-in-if-condition,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-multi-level-implicit-pointer-conversion,
|
||||
-bugprone-unintended-char-ostream-output,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-magic-numbers,
|
||||
-readability-redundant-access-specifiers,
|
||||
@@ -28,7 +29,11 @@ Checks: >
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
|
||||
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-use-enum-class,
|
||||
-bugprone-forward-declaration-namespace,
|
||||
-bugprone-tagged-union-member-count
|
||||
|
||||
WarningsAsErrors: "*"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
TBD
|
||||
The commits must be as good as CHatingPython's.
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ add_library(libfurc ${FURC_SRCS} ${FURC_HDRS})
|
||||
target_include_directories(libfurc PUBLIC include/)
|
||||
target_compile_definitions(libfurc PRIVATE LIBFURC)
|
||||
set_target_properties(libfurc PROPERTIES PREFIX "")
|
||||
target_link_libraries(libfurc PUBLIC furlang)
|
||||
target_link_libraries(libfurc PUBLIC furlang libfurvm)
|
||||
add_executable(furc src/main.cpp)
|
||||
target_link_libraries(furc PRIVATE libfurc)
|
||||
|
||||
|
||||
@@ -4,11 +4,38 @@
|
||||
#include "furc/ast/node.hpp"
|
||||
#include "furc/ast/statement.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
namespace ast {
|
||||
|
||||
/**
|
||||
* @brief Type class for AST declarations.
|
||||
*/
|
||||
class type {
|
||||
public:
|
||||
template <typename NameFwd, typename = std::enable_if_t<std::is_constructible_v<std::string, NameFwd>>>
|
||||
type(NameFwd&& name)
|
||||
: m_name(std::forward<NameFwd>(name)) {}
|
||||
public:
|
||||
const std::string& name() const { return m_name; }
|
||||
private:
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
enum class declaration_access_t {
|
||||
Implicit = 0, /**< Implicit access. */
|
||||
Public, /**< Public access. */
|
||||
Private, /**< Private access. */
|
||||
};
|
||||
|
||||
static inline bool same_access(declaration_access_t lhs, declaration_access_t rhs) {
|
||||
return lhs == declaration_access_t::Implicit || lhs == rhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Declaration node type.
|
||||
*/
|
||||
@@ -26,9 +53,10 @@ public:
|
||||
* @brief Construct a new declaration AST node.
|
||||
*
|
||||
* @param location Node location.
|
||||
* @param access Declaration access.
|
||||
*/
|
||||
declaration_node(struct location location)
|
||||
: abstract_node(location) {}
|
||||
declaration_node(struct location location, declaration_access_t access)
|
||||
: abstract_node(location), p_access(access) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's category.
|
||||
@@ -50,8 +78,31 @@ public:
|
||||
* @return The declaration type.
|
||||
*/
|
||||
virtual declaration_node_t declaration_type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the declaration's access.
|
||||
*
|
||||
* @return Access.
|
||||
*/
|
||||
declaration_access_t access() const { return p_access; }
|
||||
protected:
|
||||
bool equal(const node& rhs) const override;
|
||||
protected:
|
||||
declaration_access_t p_access;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parameter of function declaration AST node.
|
||||
*/
|
||||
struct function_declaration_param {
|
||||
std::string name;
|
||||
type type;
|
||||
};
|
||||
|
||||
enum class function_declaration_node_t {
|
||||
Normal = 0,
|
||||
Import,
|
||||
Native,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,10 +115,20 @@ public:
|
||||
*
|
||||
* @param location Node location.
|
||||
* @param name Name of the function.
|
||||
* @param type Return type of the function.
|
||||
*/
|
||||
template <typename T>
|
||||
function_declaration_node(struct location location, T&& name)
|
||||
: declaration_node(location), p_name(std::forward<T>(name)) {}
|
||||
template <typename T, typename ParamsFwd>
|
||||
function_declaration_node(struct location location,
|
||||
declaration_access_t access,
|
||||
T&& name,
|
||||
std::optional<type>&& returnType,
|
||||
ParamsFwd&& params,
|
||||
function_declaration_node_t type = function_declaration_node_t::Normal)
|
||||
: declaration_node(location, access),
|
||||
p_name(std::forward<T>(name)),
|
||||
p_returnType(std::move(returnType)),
|
||||
p_params(std::forward<ParamsFwd>(params)),
|
||||
p_type(type) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's declaration type.
|
||||
@@ -82,6 +143,27 @@ public:
|
||||
* @return Name of the function.
|
||||
*/
|
||||
std::string name() const { return p_name; }
|
||||
|
||||
/**
|
||||
* @brief Returns function's return type.
|
||||
*
|
||||
* @return Function's return type.
|
||||
*/
|
||||
const std::optional<type>& return_type() const { return p_returnType; }
|
||||
|
||||
/**
|
||||
* @brief Returns function's parameters.
|
||||
*
|
||||
* @return Function's parameters.
|
||||
*/
|
||||
const std::vector<function_declaration_param>& params() const { return p_params; }
|
||||
|
||||
/**
|
||||
* @brief Returns function's type.
|
||||
*
|
||||
* @return Function's type.
|
||||
*/
|
||||
function_declaration_node_t type() const { return p_type; }
|
||||
public:
|
||||
void accept(visitor& visitor) const override;
|
||||
|
||||
@@ -93,6 +175,21 @@ protected:
|
||||
* @brief Name of the function.
|
||||
*/
|
||||
std::string p_name;
|
||||
|
||||
/**
|
||||
* @brief Return type of the function.
|
||||
*/
|
||||
std::optional<class type> p_returnType;
|
||||
|
||||
/**
|
||||
* @brief Parameters of the function.
|
||||
*/
|
||||
std::vector<function_declaration_param> p_params;
|
||||
|
||||
/**
|
||||
* @brief Type of the function declaration.
|
||||
*/
|
||||
function_declaration_node_t p_type;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -105,11 +202,22 @@ public:
|
||||
*
|
||||
* @param location Node location.
|
||||
* @param name Name of the function.
|
||||
* @param type Return type of the function.
|
||||
* @param body Body of the function.
|
||||
*/
|
||||
template <typename T>
|
||||
function_definition_node(struct location location, T&& name, body&& body)
|
||||
: function_declaration_node(location, std::forward<T>(name)), m_body(std::move(body)) {}
|
||||
template <typename T, typename ParamsFwd>
|
||||
function_definition_node(struct location location,
|
||||
declaration_access_t access,
|
||||
T&& name,
|
||||
std::optional<class type>&& type,
|
||||
ParamsFwd&& params,
|
||||
body&& body)
|
||||
: function_declaration_node(location,
|
||||
access,
|
||||
std::forward<T>(name),
|
||||
std::move(type),
|
||||
std::forward<ParamsFwd>(params)),
|
||||
m_body(std::move(body)) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's declaration type.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "furc/ast/node.hpp"
|
||||
#include "furc/ast/statement.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
namespace ast {
|
||||
|
||||
@@ -16,6 +18,7 @@ enum class expression_node_t {
|
||||
Unaryop, /**< Unary operation expression */
|
||||
Binop, /**< Binary operation expression */
|
||||
VarAssign, /**< Variable assignment expression */
|
||||
FuncCall, /**< Function call expression. */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,6 +113,8 @@ enum class unaryop_expression_node_t {
|
||||
PostfixIncrement, /**< Postfix increment */
|
||||
PrefixDecrement, /**< Prefix decrement */
|
||||
PostfixDecrement, /**< Postfix decrement */
|
||||
Pointerof, /**< Pointerof */
|
||||
Sizeof, /**< Sizeof */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -350,6 +355,54 @@ private:
|
||||
expression_node_p m_rhs;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Function call expression AST node.
|
||||
*/
|
||||
class function_call_expression_node final : public expression_node {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new function call expression AST node.
|
||||
*
|
||||
* @param location Node location.
|
||||
* @param func Left-hand-side expression.
|
||||
* @param args Function arguments.
|
||||
*/
|
||||
function_call_expression_node(struct location location,
|
||||
expression_node_p&& func,
|
||||
std::vector<expression_node_p>&& args)
|
||||
: expression_node(location), m_func(std::move(func)), m_args(std::move(args)) {}
|
||||
|
||||
/**
|
||||
* @brief Returns this node's left-hand-side expression.
|
||||
*
|
||||
* @return The left-hand-side expression.
|
||||
*/
|
||||
const expression_node_p& func() const { return m_func; }
|
||||
|
||||
/**
|
||||
* @brief Returns this node's argument expressions.
|
||||
*
|
||||
* @return The argument expressions.
|
||||
*/
|
||||
const std::vector<expression_node_p>& args() const { return m_args; }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's expression type.
|
||||
*
|
||||
* @return expression_node_t::FuncCall.
|
||||
*/
|
||||
expression_node_t expression_type() const override { return expression_node_t::FuncCall; }
|
||||
public:
|
||||
void accept(visitor& visitor) const override;
|
||||
|
||||
std::ostream& print(std::ostream& os) const override;
|
||||
protected:
|
||||
bool equal(const node& rhs) const override;
|
||||
private:
|
||||
expression_node_p m_func;
|
||||
std::vector<expression_node_p> m_args;
|
||||
};
|
||||
|
||||
} // namespace ast
|
||||
} // namespace furc
|
||||
|
||||
|
||||
@@ -71,6 +71,10 @@ using expression_node_p = node_p<expression_node>; /**< Alias for a shared point
|
||||
|
||||
using expression_node_r = node_r<expression_node>; /**< Alias for expression_node result */
|
||||
|
||||
class type;
|
||||
|
||||
using type_r = furlang::result<type, error>; /**< Alias for AST type result */
|
||||
|
||||
class declaration_node;
|
||||
|
||||
using declaration_node_p = node_p<declaration_node>; /**< Alias for a shared pointer to declaration_node. */
|
||||
@@ -147,6 +151,14 @@ using var_assign_expression_node_p =
|
||||
using var_assign_expression_node_r =
|
||||
node_r<var_assign_expression_node>; /**< Alias for var_assign_expression_node result */
|
||||
|
||||
class function_call_expression_node;
|
||||
|
||||
using function_call_expression_node_p =
|
||||
node_p<function_call_expression_node>; /**< Alias for a shared pointer to function_call_expression_node. */
|
||||
|
||||
using function_call_expression_node_r =
|
||||
node_r<function_call_expression_node>; /**< Alias for function_call_expression_node result. */
|
||||
|
||||
/**
|
||||
* @brief List of statements.
|
||||
*/
|
||||
@@ -235,6 +247,12 @@ using compound_statement_node_p =
|
||||
|
||||
using compound_statement_node_r = node_r<compound_statement_node>; /**< Alias for compound_statement_node result */
|
||||
|
||||
class while_statement_node;
|
||||
|
||||
using while_statement_node_p = node_p<while_statement_node>; /**< Alias for a shared pointer to while_statement_node. */
|
||||
|
||||
using while_statement_node_r = node_r<while_statement_node>; /**< Alias for while_statement_node result */
|
||||
|
||||
} // namespace ast
|
||||
} // namespace furc
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef FURC_AST_STATEMENT_HPP
|
||||
#define FURC_AST_STATEMENT_HPP
|
||||
|
||||
#include "furc/ast/fwd.hpp"
|
||||
#include "furc/ast/node.hpp"
|
||||
|
||||
#include <optional>
|
||||
@@ -17,6 +18,7 @@ enum class statement_node_t {
|
||||
Return, /**< Return statement */
|
||||
If, /**< If statement */
|
||||
Compound, /**< Compound statement */
|
||||
While, /**< While loop statement. */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -191,6 +193,51 @@ private:
|
||||
struct body m_body; /**< The body handle. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief while statement AST node.
|
||||
*/
|
||||
class while_statement_node final : public statement_node, public abstract_node {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new while statement AST node.
|
||||
*
|
||||
* @param location Node location.
|
||||
* @param body Body handle.
|
||||
*/
|
||||
while_statement_node(struct location location, expression_node_p&& cond, statement_node_p&& body)
|
||||
: abstract_node(location), m_cond(std::move(cond)), m_body(std::move(body)) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's condition expression.
|
||||
*
|
||||
* @return The condition expression.
|
||||
*/
|
||||
const expression_node_p& condition() const { return m_cond; }
|
||||
|
||||
/**
|
||||
* @brief Returns this node's body handle.
|
||||
*
|
||||
* @return The body handle.
|
||||
*/
|
||||
const statement_node_p& body() const { return m_body; }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this node's statement type.
|
||||
*
|
||||
* @return statement_node_t::while.
|
||||
*/
|
||||
statement_node_t statement_type() const override { return statement_node_t::While; }
|
||||
public:
|
||||
void accept(visitor& visitor) const override;
|
||||
|
||||
std::ostream& print(std::ostream& os) const override;
|
||||
protected:
|
||||
bool equal(const node& rhs) const override;
|
||||
private:
|
||||
expression_node_p m_cond; /**< The condition expression. */
|
||||
statement_node_p m_body; /**< The body handle. */
|
||||
};
|
||||
|
||||
} // namespace ast
|
||||
} // namespace furc
|
||||
|
||||
|
||||
@@ -82,6 +82,14 @@ public:
|
||||
*/
|
||||
virtual void visit(const var_assign_expression_node& node) {}
|
||||
|
||||
/**
|
||||
* @brief Visit a function_call_expression_node.
|
||||
* @see function_call_expression_node
|
||||
*
|
||||
* @param node Node.
|
||||
*/
|
||||
virtual void visit(const function_call_expression_node& node) {}
|
||||
|
||||
/**
|
||||
* @brief Visit a function_declaration_node.
|
||||
* @see function_declaration_node
|
||||
@@ -122,6 +130,14 @@ public:
|
||||
*/
|
||||
virtual void visit(const compound_statement_node& node) {}
|
||||
|
||||
/**
|
||||
* @brief Visit a while_statement_node.
|
||||
* @see while_statement_node
|
||||
*
|
||||
* @param node Node.
|
||||
*/
|
||||
virtual void visit(const while_statement_node& node) {}
|
||||
|
||||
/**
|
||||
* @brief Visit an AST error.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef FURC_BACK_FURVM_HPP
|
||||
#define FURC_BACK_FURVM_HPP
|
||||
|
||||
#include "furlang/ir/operand.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <furlang/ir/function.hpp>
|
||||
#include <furlang/ir/instruction.hpp>
|
||||
#include <furlang/ir/module.hpp>
|
||||
#include <furvm/fwd.hpp>
|
||||
#include <furvm/module.hpp>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
namespace back {
|
||||
|
||||
class furvm_generator {
|
||||
public:
|
||||
furvm_generator() = default;
|
||||
public:
|
||||
static furvm::mod generate(furlang::ir::mod& mod);
|
||||
private:
|
||||
static void generate_function(furvm::mod& mod, const furlang::ir::function& function);
|
||||
|
||||
struct function_context {
|
||||
std::unordered_map<furlang::ir::block_index, std::size_t> blockOffsets;
|
||||
std::unordered_map<furlang::ir::block_index, std::vector<std::size_t>> incompleteJumps;
|
||||
|
||||
std::unordered_map<furlang::ir::register_operand, furvm::variable_t> variables;
|
||||
furvm::variable_t variableCounter{ 0 };
|
||||
};
|
||||
|
||||
static void generate_instruction(furvm::mod& mod, function_context& ctx, const furlang::ir::instruction& instr);
|
||||
static void generate_operand(furvm::mod& mod, function_context& ctx, const furlang::ir::operand& operand);
|
||||
|
||||
static void generate_jump(furvm::mod& mod, function_context& ctx, furlang::ir::block_index block, bool conditional);
|
||||
};
|
||||
|
||||
} // namespace back
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_BACK_FURVM_HPP
|
||||
@@ -22,11 +22,13 @@ public:
|
||||
ir_generator(const ir_generator&) = delete;
|
||||
ir_generator& operator=(const ir_generator&) = delete;
|
||||
public:
|
||||
furlang::ir::module&& move_module() { return std::move(m_module); }
|
||||
furlang::ir::mod&& move_module() { return std::move(m_module); }
|
||||
public:
|
||||
void visit(const ast::function_definition_node& funcDef) override;
|
||||
void visit(const ast::function_declaration_node& funcDecl) override;
|
||||
void visit(const ast::return_statement_node& returnStmt) override;
|
||||
void visit(const ast::if_statement_node& node) override;
|
||||
void visit(const ast::while_statement_node& node) override;
|
||||
void visit(const ast::compound_statement_node& node) override;
|
||||
void visit(const ast::string_literal_node& node) override;
|
||||
void visit(const ast::integer_literal_node& node) override;
|
||||
@@ -34,6 +36,7 @@ public:
|
||||
void visit(const ast::unary_op_expression_node& node) override;
|
||||
void visit(const ast::binary_op_expression_node& node) override;
|
||||
void visit(const ast::var_assign_expression_node& node) override;
|
||||
void visit(const ast::function_call_expression_node& node) override;
|
||||
private:
|
||||
template <typename T, typename... Args>
|
||||
void push(Args&&... args) {
|
||||
@@ -42,9 +45,9 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
furlang::ir::block_index push_block();
|
||||
furlang::ir::block_index push_block(bool validate = true);
|
||||
private:
|
||||
furlang::ir::module m_module;
|
||||
furlang::ir::mod m_module;
|
||||
std::unique_ptr<furlang::ir::function> m_currentFunction;
|
||||
std::shared_ptr<furlang::ir::block> m_currentBlock;
|
||||
ir_register m_registerCounter = 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef FURC_FRONT_PARSER_HPP
|
||||
#define FURC_FRONT_PARSER_HPP
|
||||
|
||||
#include "furc/ast/declaration.hpp"
|
||||
#include "furc/ast/fwd.hpp"
|
||||
#include "furc/front/lexer.hpp"
|
||||
#include "furlang/arena.hpp"
|
||||
@@ -23,7 +24,7 @@ public:
|
||||
* @param filename Filename for debugging.
|
||||
* @param content Content.
|
||||
*/
|
||||
parser(std::string_view filename, std::string_view content);
|
||||
parser(furlang::arena& arena, std::string_view filename, std::string_view content);
|
||||
|
||||
/**
|
||||
* @brief Construct a new parser from file.
|
||||
@@ -32,7 +33,8 @@ public:
|
||||
*
|
||||
* @param filename Name of the file.
|
||||
*/
|
||||
parser(std::string_view filename);
|
||||
parser(furlang::arena& arena, std::string_view filename);
|
||||
|
||||
~parser() = default;
|
||||
|
||||
/**
|
||||
@@ -40,13 +42,12 @@ public:
|
||||
*/
|
||||
parser(parser&&) = default;
|
||||
|
||||
parser(const parser&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
parser& operator=(parser&&) = default;
|
||||
|
||||
parser(const parser&) = delete;
|
||||
parser& operator=(const parser&) = delete;
|
||||
public:
|
||||
/**
|
||||
@@ -56,6 +57,8 @@ public:
|
||||
*/
|
||||
ast::program_node_r parse() &;
|
||||
private:
|
||||
ast::type_r parse_type();
|
||||
|
||||
ast::declaration_node_r parse_declaration();
|
||||
ast::statement_node_r parse_statement();
|
||||
ast::expression_node_r parse_expression(std::uint32_t precedence = 16);
|
||||
@@ -73,7 +76,7 @@ private:
|
||||
std::string m_filename;
|
||||
std::string m_content;
|
||||
lexer m_lexer;
|
||||
furlang::arena m_arena;
|
||||
furlang::arena* m_arena;
|
||||
std::vector<token_r> m_peekBuffer;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef FURC_FRONT_POST_PROCESS_HPP
|
||||
#define FURC_FRONT_POST_PROCESS_HPP
|
||||
|
||||
#include "furlang/ir/module.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
namespace front {
|
||||
|
||||
/**
|
||||
* @brief Post process pipeline.
|
||||
*/
|
||||
class post_process {
|
||||
public:
|
||||
enum stage { // NOLINT
|
||||
Ssa,
|
||||
Sccp,
|
||||
Adce,
|
||||
DeSsa,
|
||||
};
|
||||
public:
|
||||
post_process() = default;
|
||||
~post_process() = default;
|
||||
|
||||
post_process(post_process&&) noexcept = default;
|
||||
post_process& operator=(post_process&&) noexcept = default;
|
||||
post_process(const post_process&) = delete;
|
||||
post_process& operator=(const post_process&) = delete;
|
||||
public:
|
||||
void push_stage(stage stage) { m_stages.push_back(stage); }
|
||||
public:
|
||||
void process(furlang::ir::mod& mod);
|
||||
private:
|
||||
std::vector<stage> m_stages;
|
||||
};
|
||||
|
||||
} // namespace front
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_FRONT_POST_PROCESS_HPP
|
||||
@@ -54,6 +54,9 @@ enum class token_t {
|
||||
GreaterThan, /**< `>` */
|
||||
LessEq, /**< `<=` */
|
||||
GreaterEq, /**< `>=` */
|
||||
|
||||
SlimArrow, /**< `->` */
|
||||
FatArrow, /**< `=>` */
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, token_t type) {
|
||||
@@ -92,6 +95,8 @@ static inline std::ostream& operator<<(std::ostream& os, token_t type) {
|
||||
case token_t::GreaterThan: return os << ">";
|
||||
case token_t::LessEq: return os << "<=";
|
||||
case token_t::GreaterEq: return os << ">=";
|
||||
case token_t::SlimArrow: return os << "->";
|
||||
case token_t::FatArrow: return os << "=>";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
@@ -132,6 +137,8 @@ static inline std::string operator+(const std::string& str, token_t type) {
|
||||
case token_t::GreaterThan: return str + ">";
|
||||
case token_t::LessEq: return str + "<=";
|
||||
case token_t::GreaterEq: return str + ">=";
|
||||
case token_t::SlimArrow: return str + "->";
|
||||
case token_t::FatArrow: return str + "=>";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -140,11 +147,20 @@ static inline std::string operator+(const std::string& str, token_t type) {
|
||||
* @brief Keyword token.
|
||||
*/
|
||||
enum class keyword_token {
|
||||
None, /**< None */
|
||||
Func, /**< `func` */
|
||||
Return, /**< `return` */
|
||||
If, /**< `if` */
|
||||
Else, /**< `else` */
|
||||
None, /**< None */
|
||||
Func, /**< `func` */
|
||||
Return, /**< `return` */
|
||||
If, /**< `if` */
|
||||
Else, /**< `else` */
|
||||
While, /**< `while` */
|
||||
Import, /**< `import` */
|
||||
Native, /**< `native` */
|
||||
Public, /**< `public` */
|
||||
Private, /**< `private` */
|
||||
Pointerof, /**< `pointerof` */
|
||||
Sizeof, /**< `sizeof` */
|
||||
|
||||
Int32, /**< `int32` */
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, keyword_token keyword) {
|
||||
@@ -154,6 +170,14 @@ static inline std::ostream& operator<<(std::ostream& os, keyword_token keyword)
|
||||
case keyword_token::Return: return os << "return";
|
||||
case keyword_token::If: return os << "if";
|
||||
case keyword_token::Else: return os << "else";
|
||||
case keyword_token::While: return os << "while";
|
||||
case keyword_token::Import: return os << "import";
|
||||
case keyword_token::Native: return os << "native";
|
||||
case keyword_token::Public: return os << "public";
|
||||
case keyword_token::Private: return os << "private";
|
||||
case keyword_token::Pointerof: return os << "pointerof";
|
||||
case keyword_token::Sizeof: return os << "sizeof";
|
||||
case keyword_token::Int32: return os << "int32";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
@@ -165,6 +189,14 @@ static inline std::string operator+(const std::string& str, keyword_token keywor
|
||||
case keyword_token::Return: return str + "return";
|
||||
case keyword_token::If: return str + "if";
|
||||
case keyword_token::Else: return str + "else";
|
||||
case keyword_token::While: return str + "while";
|
||||
case keyword_token::Import: return str + "import";
|
||||
case keyword_token::Native: return str + "native";
|
||||
case keyword_token::Public: return str + "public";
|
||||
case keyword_token::Private: return str + "private";
|
||||
case keyword_token::Pointerof: return str + "pointerof";
|
||||
case keyword_token::Sizeof: return str + "sizeof";
|
||||
case keyword_token::Int32: return str + "int32";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ std::ostream& operator<<(std::ostream& os, unaryop_expression_node_t type) {
|
||||
case unaryop_expression_node_t::PostfixIncrement: return os << "++";
|
||||
case unaryop_expression_node_t::PrefixDecrement:
|
||||
case unaryop_expression_node_t::PostfixDecrement: return os << "--";
|
||||
case unaryop_expression_node_t::Pointerof: return os << "pointerof";
|
||||
case unaryop_expression_node_t::Sizeof: return os << "sizeof";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
@@ -54,6 +56,8 @@ std::ostream& unary_op_expression_node::print(std::ostream& os) const {
|
||||
case unaryop_expression_node_t::PrefixDecrement: return os << '(' << m_type << *m_node << ')';
|
||||
case unaryop_expression_node_t::PostfixIncrement:
|
||||
case unaryop_expression_node_t::PostfixDecrement: return os << '(' << *m_node << m_type << ')';
|
||||
case unaryop_expression_node_t::Pointerof: return os << "pointerof " << *m_node;
|
||||
case unaryop_expression_node_t::Sizeof: return os << "sizeof " << *m_node;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
@@ -108,6 +112,26 @@ bool var_assign_expression_node::equal(const node& rhsNode) const {
|
||||
return expression_node::equal(rhsNode) && m_compound == rhs.m_compound && m_lhs == rhs.m_lhs && m_rhs == rhs.m_rhs;
|
||||
}
|
||||
|
||||
void function_call_expression_node::accept(visitor& visitor) const {
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
std::ostream& function_call_expression_node::print(std::ostream& os) const {
|
||||
os << *m_func << '(';
|
||||
bool first = true;
|
||||
for (const auto& arg : m_args) {
|
||||
if (!first) os << ", ";
|
||||
first = false;
|
||||
os << *arg;
|
||||
}
|
||||
return os << ')';
|
||||
}
|
||||
|
||||
bool function_call_expression_node::equal(const node& rhsNode) const {
|
||||
const auto& rhs = dynamic_cast<const function_call_expression_node&>(rhsNode);
|
||||
return expression_node::equal(rhsNode) && m_func == rhs.m_func && m_args == rhs.m_args;
|
||||
}
|
||||
|
||||
bool declaration_node::equal(const node& rhs) const {
|
||||
return declaration_type() == dynamic_cast<const declaration_node&>(rhs).declaration_type();
|
||||
}
|
||||
@@ -186,6 +210,18 @@ bool compound_statement_node::equal(const node& rhs) const {
|
||||
return statement_node::equal(rhs) && m_body == dynamic_cast<const compound_statement_node&>(rhs).m_body;
|
||||
}
|
||||
|
||||
void while_statement_node::accept(visitor& visitor) const {
|
||||
visitor.visit(*this);
|
||||
}
|
||||
|
||||
std::ostream& while_statement_node::print(std::ostream& os) const {
|
||||
return os << m_body;
|
||||
}
|
||||
|
||||
bool while_statement_node::equal(const node& rhs) const {
|
||||
return statement_node::equal(rhs) && m_body == dynamic_cast<const while_statement_node&>(rhs).m_body;
|
||||
}
|
||||
|
||||
void program_node::accept(visitor& visitor) const {
|
||||
for (const auto& decl : m_declarations) {
|
||||
decl->accept(visitor);
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "furc/back/furvm.hpp"
|
||||
|
||||
#include "furlang/ir/function.hpp"
|
||||
#include "furlang/ir/instruction.hpp"
|
||||
#include "furvm/function.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <furvm/instruction.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furc::back {
|
||||
|
||||
furvm::mod furvm_generator::generate(furlang::ir::mod& mod) {
|
||||
furvm::mod vmMod;
|
||||
|
||||
for (const auto& function : mod.functions()) {
|
||||
generate_function(vmMod, *function);
|
||||
}
|
||||
|
||||
return vmMod;
|
||||
}
|
||||
|
||||
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(), std::move(signature), mod.bytecode().size()).dispatch();
|
||||
else
|
||||
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) {
|
||||
if (auto it = ctx.incompleteJumps.find(idx); it != ctx.incompleteJumps.end()) {
|
||||
for (std::size_t offset : it->second) {
|
||||
mod.bytecode()[offset] = mod.bytecode().size() - offset - 1;
|
||||
}
|
||||
ctx.incompleteJumps.erase(it);
|
||||
}
|
||||
|
||||
ctx.blockOffsets[idx] = mod.bytecode().size();
|
||||
for (const auto& instr : function.blocks()[idx]->instructions())
|
||||
generate_instruction(mod, ctx, *instr);
|
||||
generate_instruction(mod, ctx, *function.blocks()[idx]->exit());
|
||||
}
|
||||
} break;
|
||||
case furlang::ir::function_t::Import: {
|
||||
throw std::runtime_error("unimplemented");
|
||||
// mod.emplace_function_private(function.name(), function.param_count(), mod.bytecode().size()).dispatch();
|
||||
} break;
|
||||
case furlang::ir::function_t::Native: {
|
||||
if (function.access() == furlang::ir::function_access_t::Public)
|
||||
mod.emplace_function(function.name(), std::move(signature), function.name()).dispatch();
|
||||
else
|
||||
mod.emplace_function(std::move(signature), function.name()).dispatch();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline furvm::instruction_t op_type(furlang::ir::instruction_t type) {
|
||||
switch (type) {
|
||||
// Unary
|
||||
case furlang::ir::instruction_t::Pointerof: return furvm::instruction_t::Pointerof;
|
||||
case furlang::ir::instruction_t::Sizeof: return furvm::instruction_t::Sizeof;
|
||||
|
||||
// Binary
|
||||
case furlang::ir::instruction_t::Add: return furvm::instruction_t::Add;
|
||||
case furlang::ir::instruction_t::Sub: return furvm::instruction_t::Sub;
|
||||
case furlang::ir::instruction_t::Mul: return furvm::instruction_t::Mul;
|
||||
case furlang::ir::instruction_t::Div: return furvm::instruction_t::Div;
|
||||
case furlang::ir::instruction_t::Mod: return furvm::instruction_t::Mod;
|
||||
case furlang::ir::instruction_t::Eq: return furvm::instruction_t::Equals;
|
||||
case furlang::ir::instruction_t::NotEq: return furvm::instruction_t::NotEquals;
|
||||
case furlang::ir::instruction_t::LessThan: return furvm::instruction_t::LessThan;
|
||||
case furlang::ir::instruction_t::GreaterThan: return furvm::instruction_t::GreaterThan;
|
||||
case furlang::ir::instruction_t::LessEq: return furvm::instruction_t::LessEqual;
|
||||
case furlang::ir::instruction_t::GreaterEq: return furvm::instruction_t::GreaterEqual;
|
||||
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
void furvm_generator::generate_instruction(furvm::mod& mod,
|
||||
function_context& ctx,
|
||||
const furlang::ir::instruction& instr) {
|
||||
for (const auto& operand : instr.sources())
|
||||
generate_operand(mod, ctx, *operand);
|
||||
|
||||
switch (instr.type()) {
|
||||
case furlang::ir::instruction_t::Assign: {
|
||||
if (ctx.variables.find(instr.destination().reg()) == ctx.variables.end()) {
|
||||
ctx.variables[instr.destination().reg()] = ctx.variableCounter++;
|
||||
}
|
||||
auto var = ctx.variables[instr.destination().reg()];
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Store));
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed");
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Add:
|
||||
case furlang::ir::instruction_t::Sub:
|
||||
case furlang::ir::instruction_t::Mul:
|
||||
case furlang::ir::instruction_t::Div:
|
||||
case furlang::ir::instruction_t::Mod:
|
||||
case furlang::ir::instruction_t::Eq:
|
||||
case furlang::ir::instruction_t::NotEq:
|
||||
case furlang::ir::instruction_t::LessThan:
|
||||
case furlang::ir::instruction_t::GreaterThan:
|
||||
case furlang::ir::instruction_t::LessEq:
|
||||
case furlang::ir::instruction_t::GreaterEq: {
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(op_type(instr.type())));
|
||||
|
||||
if (ctx.variables.find(instr.destination().reg()) == ctx.variables.end()) {
|
||||
ctx.variables[instr.destination().reg()] = ctx.variableCounter++;
|
||||
}
|
||||
auto var = ctx.variables[instr.destination().reg()];
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Store));
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed");
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Pointerof:
|
||||
case furlang::ir::instruction_t::Sizeof: {
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(op_type(instr.type())));
|
||||
|
||||
if (ctx.variables.find(instr.destination().reg()) == ctx.variables.end()) {
|
||||
ctx.variables[instr.destination().reg()] = ctx.variableCounter++;
|
||||
}
|
||||
auto var = ctx.variables[instr.destination().reg()];
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Store));
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
static_assert(sizeof(var) == 2, "sizeof(furvm::variable_t) has changed");
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Branch: {
|
||||
const auto& branch = dynamic_cast<const furlang::ir::branch_instruction&>(instr);
|
||||
generate_jump(mod, ctx, branch.block(), false);
|
||||
} break;
|
||||
case furlang::ir::instruction_t::BranchCond: {
|
||||
const auto& branch = dynamic_cast<const furlang::ir::branch_cond_instruction&>(instr);
|
||||
generate_jump(mod, ctx, branch.if_block(), true);
|
||||
generate_jump(mod, ctx, branch.else_block(), false);
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Return: {
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Return));
|
||||
} 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.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);
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Alloca: throw std::runtime_error("unimplemented instruction");
|
||||
case furlang::ir::instruction_t::Phi: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
void furvm_generator::generate_operand(furvm::mod& mod, function_context& ctx, const furlang::ir::operand& operand) {
|
||||
switch (operand.type()) {
|
||||
case furlang::ir::operand_t::Register: {
|
||||
if (ctx.variables.find(operand.reg()) == ctx.variables.end()) throw std::runtime_error("unregistered register");
|
||||
auto var = ctx.variables[operand.reg()];
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::Load));
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
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::PushS32));
|
||||
mod.bytecode().push_back(operand.integer());
|
||||
} break;
|
||||
case furlang::ir::operand_t::Variable:
|
||||
case furlang::ir::operand_t::String: throw std::runtime_error("unimplemented operand");
|
||||
case furlang::ir::operand_t::None: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
void furvm_generator::generate_jump(furvm::mod& mod,
|
||||
function_context& ctx,
|
||||
furlang::ir::block_index block,
|
||||
bool conditional) {
|
||||
mod.bytecode().push_back(
|
||||
static_cast<furvm::byte>(conditional ? furvm::instruction_t::JumpNotZero : furvm::instruction_t::Jump));
|
||||
if (auto it = ctx.blockOffsets.find(block); it != ctx.blockOffsets.end()) {
|
||||
mod.bytecode().push_back(it->second - mod.bytecode().size() - 1);
|
||||
} else {
|
||||
ctx.incompleteJumps[block].push_back(mod.bytecode().size());
|
||||
mod.bytecode().push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furc::back
|
||||
+106
-25
@@ -4,8 +4,14 @@
|
||||
#include "furc/ast/expression.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
||||
#include "furlang/ir/function.hpp"
|
||||
#include "furlang/ir/instruction.hpp"
|
||||
#include "furlang/ir/operand.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace furc::front {
|
||||
|
||||
@@ -14,7 +20,11 @@ namespace ir = furlang::ir;
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::function_definition_node& funcDef) {
|
||||
m_currentFunction = std::make_unique<furlang::ir::function>(std::string(funcDef.name()));
|
||||
furlang::ir::function_access_t access = (funcDef.access() == ast::declaration_access_t::Public)
|
||||
? furlang::ir::function_access_t::Public
|
||||
: furlang::ir::function_access_t::Private;
|
||||
|
||||
m_currentFunction = std::make_unique<furlang::ir::function>(std::string(funcDef.name()), access, 0);
|
||||
|
||||
push_block();
|
||||
for (const auto& stmt : funcDef.body().statements) {
|
||||
@@ -26,6 +36,21 @@ void ir_generator::visit(const ast::function_definition_node& funcDef) {
|
||||
m_module.push(std::move(m_currentFunction));
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::function_declaration_node& funcDecl) {
|
||||
if (funcDecl.type() == ast::function_declaration_node_t::Normal) return;
|
||||
|
||||
furlang::ir::function_t type = funcDecl.type() == ast::function_declaration_node_t::Import
|
||||
? furlang::ir::function_t::Import
|
||||
: furlang::ir::function_t::Native;
|
||||
|
||||
furlang::ir::function_access_t access = (funcDecl.access() == ast::declaration_access_t::Public)
|
||||
? furlang::ir::function_access_t::Public
|
||||
: furlang::ir::function_access_t::Private;
|
||||
|
||||
m_module.push(
|
||||
std::make_unique<furlang::ir::function>(std::string(funcDecl.name()), access, funcDecl.params().size(), type));
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::return_statement_node& returnStmt) {
|
||||
if (returnStmt.value().has_value()) {
|
||||
returnStmt.value().value()->accept(*this);
|
||||
@@ -55,6 +80,33 @@ void ir_generator::visit(const ast::if_statement_node& node) {
|
||||
push_block(); // merge block
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::while_statement_node& node) {
|
||||
node.condition()->accept(*this);
|
||||
ir_register cond = m_registerCounter - 1;
|
||||
std::shared_ptr<ir::block> entry = m_currentBlock;
|
||||
ir::block_index headerIdx = m_currentFunction->blocks().size();
|
||||
|
||||
push_block(false); // loop header
|
||||
push<ir::branch_instruction>(m_currentFunction->blocks().size());
|
||||
|
||||
push_block(); // loop condition
|
||||
node.condition()->accept(*this);
|
||||
std::shared_ptr<ir::block> condBlock = m_currentBlock;
|
||||
ir_register cond2 = m_registerCounter - 1;
|
||||
|
||||
push_block(false); // loop body
|
||||
node.body()->accept(*this);
|
||||
push<ir::branch_instruction>(headerIdx);
|
||||
|
||||
entry->emplace<ir::branch_cond_instruction>(ir::operand::new_reg(cond),
|
||||
headerIdx,
|
||||
m_currentFunction->blocks().size());
|
||||
condBlock->emplace<ir::branch_cond_instruction>(ir::operand::new_reg(cond2),
|
||||
m_currentFunction->blocks().size() - 1,
|
||||
m_currentFunction->blocks().size());
|
||||
push_block(); // merge block
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::compound_statement_node& node) {
|
||||
for (const auto& stmt : node.body().statements) {
|
||||
stmt.value()->accept(*this);
|
||||
@@ -80,23 +132,36 @@ void ir_generator::visit(const ast::var_read_expression_node& node) {
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit(const ast::unary_op_expression_node& node) {
|
||||
throw std::runtime_error("unimplemented");
|
||||
static inline furlang::ir::instruction_t unary_op_instruction_t(ast::unaryop_expression_node_t type) {
|
||||
switch (type) {
|
||||
case ast::unaryop_expression_node_t::Pointerof: return furlang::ir::instruction_t::Pointerof;
|
||||
case ast::unaryop_expression_node_t::Sizeof: return furlang::ir::instruction_t::Sizeof;
|
||||
default: throw std::runtime_error("unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
static inline furlang::ir::binary_op_instruction_t binary_op_instruction_t(ast::binop_expression_node_t type) {
|
||||
void ir_generator::visit(const ast::unary_op_expression_node& node) {
|
||||
node.get_node()->accept(*this);
|
||||
ir_register src = m_registerCounter - 1;
|
||||
ir_register dst = m_registerCounter++;
|
||||
push<furlang::ir::unary_instruction>(unary_op_instruction_t(node.type()),
|
||||
ir::operand::new_reg(src),
|
||||
ir::operand::new_reg(dst));
|
||||
}
|
||||
|
||||
static inline furlang::ir::instruction_t binary_op_instruction_t(ast::binop_expression_node_t type) {
|
||||
switch (type) {
|
||||
case ast::binop_expression_node_t::Add: return furlang::ir::binary_op_instruction_t::Add;
|
||||
case ast::binop_expression_node_t::Sub: return furlang::ir::binary_op_instruction_t::Sub;
|
||||
case ast::binop_expression_node_t::Mul: return furlang::ir::binary_op_instruction_t::Mul;
|
||||
case ast::binop_expression_node_t::Div: return furlang::ir::binary_op_instruction_t::Div;
|
||||
case ast::binop_expression_node_t::Mod: return furlang::ir::binary_op_instruction_t::Mod;
|
||||
case ast::binop_expression_node_t::Equal: return furlang::ir::binary_op_instruction_t::Eq;
|
||||
case ast::binop_expression_node_t::NotEqual: return furlang::ir::binary_op_instruction_t::NotEq;
|
||||
case ast::binop_expression_node_t::LessThan: return furlang::ir::binary_op_instruction_t::LessThan;
|
||||
case ast::binop_expression_node_t::GreaterThan: return furlang::ir::binary_op_instruction_t::GreaterThan;
|
||||
case ast::binop_expression_node_t::LessEqual: return furlang::ir::binary_op_instruction_t::LessEq;
|
||||
case ast::binop_expression_node_t::GreaterEqual: return furlang::ir::binary_op_instruction_t::GreaterEq;
|
||||
case ast::binop_expression_node_t::Add: return furlang::ir::instruction_t::Add;
|
||||
case ast::binop_expression_node_t::Sub: return furlang::ir::instruction_t::Sub;
|
||||
case ast::binop_expression_node_t::Mul: return furlang::ir::instruction_t::Mul;
|
||||
case ast::binop_expression_node_t::Div: return furlang::ir::instruction_t::Div;
|
||||
case ast::binop_expression_node_t::Mod: return furlang::ir::instruction_t::Mod;
|
||||
case ast::binop_expression_node_t::Equal: return furlang::ir::instruction_t::Eq;
|
||||
case ast::binop_expression_node_t::NotEqual: return furlang::ir::instruction_t::NotEq;
|
||||
case ast::binop_expression_node_t::LessThan: return furlang::ir::instruction_t::LessThan;
|
||||
case ast::binop_expression_node_t::GreaterThan: return furlang::ir::instruction_t::GreaterThan;
|
||||
case ast::binop_expression_node_t::LessEqual: return furlang::ir::instruction_t::LessEq;
|
||||
case ast::binop_expression_node_t::GreaterEqual: return furlang::ir::instruction_t::GreaterEq;
|
||||
case ast::binop_expression_node_t::None:
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
@@ -108,7 +173,7 @@ void ir_generator::visit(const ast::binary_op_expression_node& node) {
|
||||
node.rhs()->accept(*this);
|
||||
ir_register rhs = m_registerCounter - 1;
|
||||
ir_register dst = m_registerCounter++;
|
||||
push<furlang::ir::binary_op_instruction>(binary_op_instruction_t(node.type()),
|
||||
push<furlang::ir::binary_instruction>(binary_op_instruction_t(node.type()),
|
||||
ir::operand::new_reg(lhs),
|
||||
ir::operand::new_reg(rhs),
|
||||
ir::operand::new_reg(dst));
|
||||
@@ -120,26 +185,42 @@ void ir_generator::visit(const ast::var_assign_expression_node& node) {
|
||||
assert(node.lhs()->expression_type() == ast::expression_node_t::VarRead);
|
||||
auto lhs = std::dynamic_pointer_cast<ast::var_read_expression_node>(node.lhs());
|
||||
|
||||
ir_register reg = 0;
|
||||
if (auto it = m_variables.find(lhs->get_name()); it != m_variables.end()) {
|
||||
reg = it->second;
|
||||
} else {
|
||||
m_variables[lhs->get_name()] = reg = m_registerCounter++;
|
||||
}
|
||||
ir_register reg = m_registerCounter++;
|
||||
|
||||
auto compound = node.compound();
|
||||
if (compound != ast::binop_expression_node_t::None) {
|
||||
push<ir::binary_op_instruction>(binary_op_instruction_t(compound),
|
||||
push<ir::binary_instruction>(binary_op_instruction_t(compound),
|
||||
ir::operand::new_reg(reg),
|
||||
ir::operand::new_reg(rhs),
|
||||
ir::operand::new_reg(reg));
|
||||
} else {
|
||||
push<ir::assign_instruction>(ir::operand::new_reg(rhs), ir::operand::new_reg(reg));
|
||||
}
|
||||
|
||||
if (auto it = m_variables.find(lhs->get_name()); it != m_variables.end()) {
|
||||
push<ir::assign_instruction>(ir::operand::new_reg(reg), ir::operand::new_reg(it->second));
|
||||
} else {
|
||||
m_variables[lhs->get_name()] = reg;
|
||||
}
|
||||
}
|
||||
|
||||
furlang::ir::block_index ir_generator::push_block() {
|
||||
if (!m_currentFunction->blocks().empty() && !m_currentFunction->blocks().back()->has_exit()) {
|
||||
void ir_generator::visit(const ast::function_call_expression_node& node) {
|
||||
std::vector<ir::operand> args;
|
||||
args.reserve(node.args().size());
|
||||
for (const auto& arg : node.args()) {
|
||||
arg->accept(*this);
|
||||
args.push_back(ir::operand::new_reg(m_registerCounter - 1));
|
||||
}
|
||||
if (node.func()->expression_type() != ast::expression_node_t::VarRead)
|
||||
throw std::runtime_error("invalid function call left-hand-side expression");
|
||||
|
||||
push<ir::call_instruction>(dynamic_cast<const ast::var_read_expression_node&>(*node.func()).get_name(),
|
||||
ir::operand::new_reg(m_registerCounter++),
|
||||
std::move(args));
|
||||
}
|
||||
|
||||
furlang::ir::block_index ir_generator::push_block(bool validate) {
|
||||
if (validate && !m_currentFunction->blocks().empty() && !m_currentFunction->blocks().back()->has_exit()) {
|
||||
throw std::runtime_error(
|
||||
"block " + std::to_string(m_currentFunction->blocks().size() - 1) + " is lacking an exit");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "furc/front/lexer.hpp"
|
||||
|
||||
#include "furc/front/token.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
@@ -97,6 +99,14 @@ token_r lexer::next_token() {
|
||||
{ "return", keyword_token::Return },
|
||||
{ "if", keyword_token::If },
|
||||
{ "else", keyword_token::Else },
|
||||
{ "while", keyword_token::While },
|
||||
{ "import", keyword_token::Import },
|
||||
{ "native", keyword_token::Native },
|
||||
{ "public", keyword_token::Public },
|
||||
{ "private", keyword_token::Private },
|
||||
{ "pointerof", keyword_token::Pointerof },
|
||||
{ "sizeof", keyword_token::Sizeof },
|
||||
{ "int32", keyword_token::Int32 },
|
||||
};
|
||||
|
||||
if (auto it = s_keywords.find(value); it != s_keywords.end()) return { location, it->second };
|
||||
@@ -140,6 +150,8 @@ token_r lexer::next_token() {
|
||||
{ ">", token_t::GreaterThan },
|
||||
{ "<=", token_t::LessEq },
|
||||
{ ">=", token_t::GreaterEq },
|
||||
{ "->", token_t::SlimArrow },
|
||||
{ "=>", token_t::FatArrow },
|
||||
};
|
||||
|
||||
token_t type = token_t::None;
|
||||
|
||||
+192
-46
@@ -2,24 +2,27 @@
|
||||
|
||||
#include "furc/ast/declaration.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/expression.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/program.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/fwd.hpp"
|
||||
#include "furc/ast/literal.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/program.hpp" // IWYU pragma: keep
|
||||
#include "furc/ast/statement.hpp" // IWYU pragma: keep
|
||||
#include "furc/front/token.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace furc::front {
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
parser::parser(std::string_view filename, std::string_view content)
|
||||
: m_filename(filename), m_content(content), m_lexer(m_filename, m_content) {}
|
||||
parser::parser(furlang::arena& arena, std::string_view filename, std::string_view content)
|
||||
: m_filename(filename), m_content(content), m_lexer(m_filename, m_content), m_arena(&arena) {}
|
||||
|
||||
parser::parser(std::string_view filename)
|
||||
: m_filename(filename) {
|
||||
parser::parser(furlang::arena& arena, std::string_view filename)
|
||||
: m_filename(filename), m_arena(&arena) {
|
||||
std::ifstream file(m_filename, std::ios_base::binary | std::ios_base::ate);
|
||||
if (!file.is_open()) throw std::runtime_error("failed to open file "s.append(m_filename));
|
||||
std::streampos size = file.tellg();
|
||||
@@ -31,53 +34,131 @@ parser::parser(std::string_view filename)
|
||||
}
|
||||
|
||||
ast::program_node_r parser::parse() & {
|
||||
auto program = m_arena.allocate_shared<ast::program_node>(location{ m_filename });
|
||||
auto program = m_arena->allocate_shared<ast::program_node>(location{ m_filename });
|
||||
|
||||
while (peek_token().has_value()) {
|
||||
auto decl = parse_declaration();
|
||||
if (decl.has_error()) {
|
||||
program = nullptr;
|
||||
} else if (program != nullptr) {
|
||||
program->push(std::move(decl.value()));
|
||||
}
|
||||
if (decl.has_error()) return ast::program_node_r(ast::error{ decl.error().location });
|
||||
program->push(std::move(decl.value()));
|
||||
}
|
||||
|
||||
return program != nullptr ? std::move(program) : ast::program_node_r(ast::error{ location{ m_filename } });
|
||||
return program;
|
||||
}
|
||||
|
||||
ast::type_r parser::parse_type() {
|
||||
auto token = eat_token(token_t::Keyword);
|
||||
if (token.has_error() || token.value()->keyword != keyword_token::Int32)
|
||||
return ast::type_r(ast::error{ token.error().location });
|
||||
return ast::type("" + token.value()->keyword);
|
||||
}
|
||||
|
||||
ast::declaration_node_r parser::parse_declaration() {
|
||||
const auto& first = peek_token();
|
||||
if (first.has_error()) return ast::declaration_node_r(ast::error{ first.error().location });
|
||||
switch (first->type) {
|
||||
case token_t::Keyword: {
|
||||
auto first = next_token();
|
||||
token firstToken = *first;
|
||||
|
||||
ast::declaration_access_t accessOverride = ast::declaration_access_t::Implicit;
|
||||
switch ((*first)->keyword) {
|
||||
default: break;
|
||||
case keyword_token::Public:
|
||||
case keyword_token::Private: {
|
||||
if ((*first)->keyword == keyword_token::Public) accessOverride = ast::declaration_access_t::Public;
|
||||
if ((*first)->keyword == keyword_token::Private) accessOverride = ast::declaration_access_t::Private;
|
||||
auto kw = eat_token(token_t::Keyword);
|
||||
if (kw.has_error()) return ast::declaration_node_r(ast::error{ kw.error().location });
|
||||
firstToken = *kw;
|
||||
} break;
|
||||
}
|
||||
|
||||
ast::function_declaration_node_t funcDeclType{};
|
||||
|
||||
auto kw = next_token();
|
||||
if (kw.has_error()) return ast::declaration_node_r(ast::error{ kw.error().location });
|
||||
firstToken = *kw;
|
||||
switch (firstToken->keyword) {
|
||||
case keyword_token::Import:
|
||||
case keyword_token::Native: {
|
||||
funcDeclType = (firstToken->keyword == keyword_token::Import) ? ast::function_declaration_node_t::Import
|
||||
: ast::function_declaration_node_t::Native;
|
||||
|
||||
auto kw = eat_token(token_t::Keyword);
|
||||
if (kw.has_error()) return ast::declaration_node_r(ast::error{ kw.error().location });
|
||||
firstToken = *kw;
|
||||
if (firstToken.value.keyword != keyword_token::Func)
|
||||
return ast::declaration_node_r(ast::error{ firstToken.location });
|
||||
}
|
||||
case keyword_token::Func: {
|
||||
auto name = eat_token(token_t::Identifier);
|
||||
if (name.has_error()) return ast::declaration_node_r(ast::error{ name.error().location });
|
||||
|
||||
auto tok = eat_token(token_t::LParen);
|
||||
if (tok.has_error()) return ast::declaration_node_r(ast::error{ tok.error().location });
|
||||
|
||||
std::vector<ast::function_declaration_param> params;
|
||||
if (peek_token().has_value() && peek_token()->type != token_t::RParen) {
|
||||
while (true) {
|
||||
auto name = eat_token(token_t::Identifier);
|
||||
if (name.has_error()) return ast::declaration_node_r(ast::error{ name.error().location });
|
||||
auto colon = eat_token(token_t::Colon);
|
||||
if (colon.has_error()) return ast::declaration_node_r(ast::error{ colon.error().location });
|
||||
auto type = parse_type();
|
||||
if (type.has_error()) return ast::declaration_node_r(ast::error{ type.error().location });
|
||||
|
||||
params.push_back(
|
||||
ast::function_declaration_param{ std::string(name->value.string), std::move(*type) });
|
||||
|
||||
auto comma = eat_token(token_t::Comma);
|
||||
if (comma.has_error()) break;
|
||||
}
|
||||
}
|
||||
|
||||
tok = eat_token(token_t::RParen);
|
||||
if (tok.has_error()) return ast::declaration_node_r(ast::error{ tok.error().location });
|
||||
|
||||
std::optional<ast::type> returnType;
|
||||
if (peek_token().has_value() && peek_token()->type == token_t::SlimArrow) {
|
||||
auto tok = next_token();
|
||||
auto type = parse_type();
|
||||
if (type.has_error()) return ast::declaration_node_r(ast::error{ tok->location });
|
||||
returnType = *type;
|
||||
}
|
||||
|
||||
auto access = (funcDeclType == ast::function_declaration_node_t::Import)
|
||||
? ast::declaration_access_t::Private
|
||||
: accessOverride;
|
||||
if (access == ast::declaration_access_t::Implicit) access = ast::declaration_access_t::Public;
|
||||
if (!ast::same_access(accessOverride, access)) return ast::declaration_node_r(ast::error{ tok->location });
|
||||
|
||||
const auto& peek = peek_token();
|
||||
if (peek.has_error()) return ast::declaration_node_r(ast::error{ peek.error().location });
|
||||
switch (peek->type) {
|
||||
case token_t::LBrace: {
|
||||
ast::body_r body = parse_body();
|
||||
if (body.has_error()) return ast::declaration_node_r(ast::error{ body.error().location });
|
||||
return m_arena.allocate_shared<ast::function_definition_node>(first->location,
|
||||
if (funcDeclType != ast::function_declaration_node_t::Normal)
|
||||
return ast::declaration_node_r(ast::error{ body->begin });
|
||||
return m_arena->allocate_shared<ast::function_definition_node>(firstToken.location,
|
||||
access,
|
||||
name->value.string,
|
||||
std::move(returnType),
|
||||
std::move(params),
|
||||
std::move(body.value()));
|
||||
}
|
||||
case token_t::Semicolon: {
|
||||
m_peekBuffer.clear();
|
||||
return m_arena.allocate_shared<ast::function_declaration_node>(first->location, name->value.string);
|
||||
return m_arena->allocate_shared<ast::function_declaration_node>(firstToken.location,
|
||||
access,
|
||||
name->value.string,
|
||||
std::move(returnType),
|
||||
std::move(params),
|
||||
funcDeclType);
|
||||
}
|
||||
default: return ast::declaration_node_r(ast::error{ tok->location });
|
||||
}
|
||||
}
|
||||
default: return ast::declaration_node_r(ast::error{ first->location });
|
||||
default: return ast::declaration_node_r(ast::error{ firstToken.location });
|
||||
}
|
||||
}
|
||||
case token_t::None:
|
||||
@@ -108,13 +189,13 @@ ast::statement_node_r parser::parse_statement() {
|
||||
auto tok = next_token();
|
||||
if (peek_token()->type == token_t::Semicolon) {
|
||||
next_token();
|
||||
return m_arena.allocate_shared<ast::return_statement_node>(location);
|
||||
return m_arena->allocate_shared<ast::return_statement_node>(location);
|
||||
}
|
||||
|
||||
auto value = parse_expression();
|
||||
auto err = eat_token(token_t::Semicolon);
|
||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
||||
return m_arena.allocate_shared<ast::return_statement_node>(location, std::move(value.value()));
|
||||
return m_arena->allocate_shared<ast::return_statement_node>(location, std::move(value.value()));
|
||||
}
|
||||
case keyword_token::If: {
|
||||
auto tok = next_token();
|
||||
@@ -135,16 +216,34 @@ ast::statement_node_r parser::parse_statement() {
|
||||
|
||||
auto elseBody = parse_statement();
|
||||
if (elseBody.has_error()) return ast::statement_node_r(ast::error{ elseBody.error().location });
|
||||
return m_arena.allocate_shared<ast::if_statement_node>(location,
|
||||
return m_arena->allocate_shared<ast::if_statement_node>(location,
|
||||
std::move(cond.value()),
|
||||
std::move(then.value()),
|
||||
std::move(elseBody.value()));
|
||||
}
|
||||
|
||||
return m_arena.allocate_shared<ast::if_statement_node>(location,
|
||||
return m_arena->allocate_shared<ast::if_statement_node>(location,
|
||||
std::move(cond.value()),
|
||||
std::move(then.value()));
|
||||
}
|
||||
case keyword_token::While: {
|
||||
auto tok = next_token();
|
||||
auto err = eat_token(token_t::LParen);
|
||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
||||
|
||||
auto cond = parse_expression();
|
||||
if (cond.has_error()) return ast::statement_node_r(ast::error{ cond.error().location });
|
||||
|
||||
err = eat_token(token_t::RParen);
|
||||
if (err.has_error()) return ast::statement_node_r(ast::error{ err.error().location });
|
||||
|
||||
auto body = parse_statement();
|
||||
if (body.has_error()) return ast::statement_node_r(ast::error{ body.error().location });
|
||||
|
||||
return m_arena->allocate_shared<ast::while_statement_node>(location,
|
||||
std::move(cond.value()),
|
||||
std::move(body.value()));
|
||||
}
|
||||
case keyword_token::None:
|
||||
case keyword_token::Func:
|
||||
default: break;
|
||||
@@ -153,7 +252,7 @@ ast::statement_node_r parser::parse_statement() {
|
||||
case token_t::LBrace: {
|
||||
auto body = parse_body();
|
||||
if (body.has_error()) return ast::statement_node_r(ast::error{ body.error().location });
|
||||
return m_arena.allocate_shared<ast::compound_statement_node>(location, std::move(body.value()));
|
||||
return m_arena->allocate_shared<ast::compound_statement_node>(location, std::move(body.value()));
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
@@ -185,7 +284,7 @@ ast::expression_node_r parser::parse_expression_primary() {
|
||||
case token_t::Identifier: {
|
||||
auto tok = next_token();
|
||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
||||
return m_arena.allocate_shared<ast::var_read_expression_node>(tok->location, (*tok)->string);
|
||||
return m_arena->allocate_shared<ast::var_read_expression_node>(tok->location, (*tok)->string);
|
||||
}
|
||||
case token_t::LParen: {
|
||||
auto tok = next_token();
|
||||
@@ -197,12 +296,12 @@ ast::expression_node_r parser::parse_expression_primary() {
|
||||
case token_t::String: {
|
||||
auto tok = next_token();
|
||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
||||
return m_arena.allocate_shared<ast::string_literal_node>(tok->location, (*tok)->string);
|
||||
return m_arena->allocate_shared<ast::string_literal_node>(tok->location, (*tok)->string);
|
||||
}
|
||||
case token_t::Integer: {
|
||||
auto tok = next_token();
|
||||
if (tok.has_error()) return ast::expression_node_r(ast::error{ tok.error().location });
|
||||
return m_arena.allocate_shared<ast::integer_literal_node>(tok->location, (*tok)->integer);
|
||||
return m_arena->allocate_shared<ast::integer_literal_node>(tok->location, (*tok)->integer);
|
||||
}
|
||||
default: {
|
||||
return ast::expression_node_r(ast::error{ tok->location });
|
||||
@@ -215,27 +314,43 @@ struct unaryop_info {
|
||||
std::uint32_t precedence;
|
||||
};
|
||||
|
||||
ast::expression_node_r parser::parse_expression_unary(std::uint32_t precedence) {
|
||||
static inline std::optional<unaryop_info> get_unaryop_info(const token_r& token) {
|
||||
static std::unordered_map<token_t, unaryop_info> s_prefixes = {
|
||||
{ token_t::Plus, unaryop_info{ ast::unaryop_expression_node_t::Positive, 3 } },
|
||||
{ token_t::Minus, unaryop_info{ ast::unaryop_expression_node_t::Negative, 3 } },
|
||||
{ token_t::Plus, unaryop_info{ ast::unaryop_expression_node_t::Positive, 2 } },
|
||||
{ token_t::Minus, unaryop_info{ ast::unaryop_expression_node_t::Negative, 2 } },
|
||||
{ token_t::DPlus, unaryop_info{ ast::unaryop_expression_node_t::PrefixIncrement, 2 } },
|
||||
{ token_t::DMinus, unaryop_info{ ast::unaryop_expression_node_t::PrefixDecrement, 2 } },
|
||||
};
|
||||
static std::unordered_map<keyword_token, unaryop_info> s_keywords = {
|
||||
{ keyword_token::Pointerof, unaryop_info{ ast::unaryop_expression_node_t::Pointerof, 2 } },
|
||||
{ keyword_token::Sizeof, unaryop_info{ ast::unaryop_expression_node_t::Sizeof, 2 } },
|
||||
};
|
||||
|
||||
if (token->type == token_t::Keyword) {
|
||||
auto it = s_keywords.find(token->value.keyword);
|
||||
if (it == s_keywords.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto it = s_prefixes.find(token->type);
|
||||
if (it == s_prefixes.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ast::expression_node_r parser::parse_expression_unary(std::uint32_t precedence) {
|
||||
std::shared_ptr<ast::unary_op_expression_node> result;
|
||||
while (true) {
|
||||
auto it = s_prefixes.find(peek_token()->type);
|
||||
if (it == s_prefixes.end()) break;
|
||||
auto current = it->second;
|
||||
auto opt = get_unaryop_info(peek_token());
|
||||
if (!opt.has_value()) break;
|
||||
unaryop_info current = opt.value();
|
||||
if (current.precedence >= precedence) break;
|
||||
auto token = next_token();
|
||||
|
||||
ast::expression_node_p expression;
|
||||
|
||||
auto nextIt = s_prefixes.find(peek_token()->type);
|
||||
if (nextIt != s_prefixes.end()) {
|
||||
auto next = nextIt->second;
|
||||
opt = get_unaryop_info(peek_token());
|
||||
if (opt.has_value()) {
|
||||
auto next = opt.value();
|
||||
auto expr = parse_expression_unary(current.precedence + 1);
|
||||
if (expr.has_error()) {
|
||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
||||
@@ -243,7 +358,7 @@ ast::expression_node_r parser::parse_expression_unary(std::uint32_t precedence)
|
||||
expression = std::move(std::move(expr.value()));
|
||||
}
|
||||
|
||||
result = m_arena.allocate_shared<ast::unary_op_expression_node>(token->location,
|
||||
result = m_arena->allocate_shared<ast::unary_op_expression_node>(token->location,
|
||||
current.type,
|
||||
std::move(expression));
|
||||
}
|
||||
@@ -268,6 +383,7 @@ enum class rhsop_info_t {
|
||||
Unaryop,
|
||||
Binop,
|
||||
Assignment,
|
||||
FuncCall,
|
||||
};
|
||||
|
||||
struct rhsop_info {
|
||||
@@ -280,6 +396,8 @@ struct rhsop_info {
|
||||
ast::binop_expression_node_t assignment;
|
||||
};
|
||||
|
||||
bool has_rhs() const { return type == rhsop_info_t::Binop || type == rhsop_info_t::Assignment; }
|
||||
|
||||
static rhsop_info create(ast::unaryop_expression_node_t type, std::uint32_t precedence) {
|
||||
rhsop_info info{};
|
||||
info.type = rhsop_info_t::Unaryop;
|
||||
@@ -308,6 +426,14 @@ struct rhsop_info {
|
||||
info.assignment = compound;
|
||||
return info;
|
||||
}
|
||||
|
||||
static rhsop_info create_function_call() {
|
||||
rhsop_info info{};
|
||||
info.type = rhsop_info_t::FuncCall;
|
||||
info.precedence = 1;
|
||||
info.associativity = associativity::Left;
|
||||
return info;
|
||||
}
|
||||
};
|
||||
|
||||
ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& init, std::uint32_t precedence) {
|
||||
@@ -332,6 +458,7 @@ ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& ini
|
||||
{ token_t::GreaterThan, rhsop_info::create(ast::binop_expression_node_t::GreaterThan, 9, associativity::Left) },
|
||||
{ token_t::LessEq, rhsop_info::create(ast::binop_expression_node_t::LessEqual, 9, associativity::Left) },
|
||||
{ token_t::GreaterEq, rhsop_info::create(ast::binop_expression_node_t::GreaterEqual, 9, associativity::Left) },
|
||||
{ token_t::LParen, rhsop_info::create_function_call() },
|
||||
};
|
||||
|
||||
ast::expression_node_p lhs = std::move(init);
|
||||
@@ -343,13 +470,28 @@ ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& ini
|
||||
if (current.precedence >= precedence) return lhs;
|
||||
auto opToken = next_token();
|
||||
|
||||
ast::expression_node_p rhs;
|
||||
if (current.type != rhsop_info_t::Unaryop) {
|
||||
ast::expression_node_p rhs;
|
||||
std::vector<ast::expression_node_p> params;
|
||||
if (current.has_rhs()) {
|
||||
auto expr = parse_expression_unary(current.precedence + 1); // unary prefix is always right-associative
|
||||
if (expr.has_error()) {
|
||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
||||
}
|
||||
rhs = std::move(expr.value());
|
||||
} else if (current.type == rhsop_info_t::FuncCall && peek_token().has_value()) {
|
||||
if (peek_token()->type != token_t::RParen) {
|
||||
while (true) {
|
||||
auto expr = parse_expression_unary(16);
|
||||
if (expr.has_error()) {
|
||||
return ast::expression_node_r(ast::error{ expr.error().location });
|
||||
}
|
||||
params.emplace_back(std::move(expr.value()));
|
||||
|
||||
if (eat_token(token_t::Comma).has_error()) break;
|
||||
}
|
||||
}
|
||||
auto enclosing = eat_token(token_t::RParen);
|
||||
if (enclosing.has_error()) return ast::expression_node_r(ast::error{ enclosing.error().location });
|
||||
}
|
||||
|
||||
auto nextIt = s_rhsops.find(peek_token()->type);
|
||||
@@ -370,22 +512,27 @@ ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& ini
|
||||
|
||||
switch (current.type) {
|
||||
case rhsop_info_t::Unaryop:
|
||||
lhs = m_arena.allocate_shared<ast::unary_op_expression_node>(opToken->location,
|
||||
lhs = m_arena->allocate_shared<ast::unary_op_expression_node>(opToken->location,
|
||||
current.unary,
|
||||
std::move(lhs));
|
||||
break;
|
||||
case rhsop_info_t::Binop:
|
||||
lhs = m_arena.allocate_shared<ast::binary_op_expression_node>(opToken->location,
|
||||
lhs = m_arena->allocate_shared<ast::binary_op_expression_node>(opToken->location,
|
||||
current.binary,
|
||||
std::move(lhs),
|
||||
std::move(rhs));
|
||||
break;
|
||||
case rhsop_info_t::Assignment:
|
||||
lhs = m_arena.allocate_shared<ast::var_assign_expression_node>(opToken->location,
|
||||
lhs = m_arena->allocate_shared<ast::var_assign_expression_node>(opToken->location,
|
||||
current.assignment,
|
||||
std::move(lhs),
|
||||
std::move(rhs));
|
||||
break;
|
||||
case rhsop_info_t::FuncCall:
|
||||
lhs = m_arena->allocate_shared<ast::function_call_expression_node>(opToken->location,
|
||||
std::move(lhs),
|
||||
std::move(params));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lhs;
|
||||
@@ -427,15 +574,14 @@ const token_r& parser::peek_token() {
|
||||
}
|
||||
|
||||
token_r parser::eat_token(token_t type) {
|
||||
auto token = next_token();
|
||||
if (token.has_error()) return token;
|
||||
if (token->type != type) {
|
||||
if (const auto& token = peek_token(); token.has_error() || peek_token()->type != type) {
|
||||
if (token.has_error()) return token;
|
||||
if (token->type == token_t::None)
|
||||
return token_r(token_error{ token->location, token_error_t::UnexpectedToken, ", expected " + type });
|
||||
return token_r(
|
||||
token_error{ token->location, token_error_t::UnexpectedToken, ""s + token->type + ", expected " + type });
|
||||
}
|
||||
return token;
|
||||
return next_token();
|
||||
}
|
||||
|
||||
} // namespace furc::front
|
||||
@@ -0,0 +1,605 @@
|
||||
#include "furc/front/post_process.hpp"
|
||||
|
||||
#include "furlang/ir/function.hpp"
|
||||
#include "furlang/ir/instruction.hpp"
|
||||
#include "furlang/ir/operand.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furc::front {
|
||||
|
||||
using block_idx = furlang::ir::block_index;
|
||||
using register_t = furlang::ir::register_t;
|
||||
using register_op = furlang::ir::register_operand;
|
||||
|
||||
static constexpr block_idx INVALID_BLOCK = std::numeric_limits<block_idx>::max();
|
||||
|
||||
struct block_info {
|
||||
std::size_t rpoIndex{ 0 };
|
||||
|
||||
std::vector<block_idx> predecessors;
|
||||
std::vector<block_idx> successors;
|
||||
|
||||
block_idx idom = INVALID_BLOCK;
|
||||
std::vector<block_idx> doms;
|
||||
std::unordered_set<block_idx> domFrontiers;
|
||||
};
|
||||
|
||||
struct register_info {
|
||||
std::unordered_set<block_idx> defSites;
|
||||
std::stack<register_t> renameStack;
|
||||
std::uint32_t nextVersion{ 0 };
|
||||
};
|
||||
|
||||
struct function_context {
|
||||
explicit function_context(furlang::ir::function* function)
|
||||
: function(function) {
|
||||
build_cfg();
|
||||
compute_rpo();
|
||||
}
|
||||
|
||||
void build_cfg() {
|
||||
for (block_idx idx = 0; idx < function->blocks().size(); ++idx) {
|
||||
const auto& block = function->blocks()[idx];
|
||||
|
||||
for (const auto& instr : block->instructions()) {
|
||||
for (const auto& operand : instr->sources()) {
|
||||
if (operand->type() != furlang::ir::operand_t::Register) continue;
|
||||
auto reg = operand->reg();
|
||||
if (registers[reg].defSites.find(idx) != registers[reg].defSites.end()) continue;
|
||||
globalRegisters.insert(reg);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& instr : block->instructions()) {
|
||||
if (!instr->has_destination() || instr->destination().type() != furlang::ir::operand_t::Register)
|
||||
continue;
|
||||
auto reg = instr->destination().reg();
|
||||
registers[reg].defSites.insert(idx);
|
||||
}
|
||||
|
||||
for (const auto& operand : block->exit()->sources()) {
|
||||
if (operand->type() != furlang::ir::operand_t::Register) continue;
|
||||
auto reg = operand->reg();
|
||||
if (registers[reg].defSites.find(idx) != registers[reg].defSites.end()) continue;
|
||||
globalRegisters.insert(reg);
|
||||
}
|
||||
|
||||
const auto& exit = block->exit();
|
||||
switch (exit->type()) {
|
||||
case furlang::ir::instruction_t::Branch: {
|
||||
const auto& br = dynamic_cast<const furlang::ir::branch_instruction&>(*exit);
|
||||
blocks[br.block()].predecessors.push_back(idx);
|
||||
blocks[idx].successors.push_back(br.block());
|
||||
} break;
|
||||
case furlang::ir::instruction_t::BranchCond: {
|
||||
const auto& br = dynamic_cast<const furlang::ir::branch_cond_instruction&>(*exit);
|
||||
blocks[br.if_block()].predecessors.push_back(idx);
|
||||
blocks[br.else_block()].predecessors.push_back(idx);
|
||||
blocks[idx].successors.push_back(br.if_block());
|
||||
blocks[idx].successors.push_back(br.else_block());
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compute_rpo() {
|
||||
std::unordered_set<block_idx> visited;
|
||||
|
||||
auto dfs = [&](auto& self, block_idx block) -> void {
|
||||
visited.insert(block);
|
||||
for (auto succ : blocks[block].successors) {
|
||||
if (visited.find(succ) != visited.end()) continue;
|
||||
self(self, succ);
|
||||
}
|
||||
rpoOrder.push_back(block);
|
||||
};
|
||||
|
||||
if (!function->blocks().empty()) dfs(dfs, 0);
|
||||
|
||||
std::reverse(rpoOrder.begin(), rpoOrder.end());
|
||||
for (std::size_t i = 0; i < rpoOrder.size(); ++i) {
|
||||
blocks[rpoOrder[i]].rpoIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
void compute_dominance() {
|
||||
if (rpoOrder.empty()) return;
|
||||
|
||||
const block_idx entry = rpoOrder.front();
|
||||
blocks[entry].idom = entry;
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
|
||||
for (block_idx idx : rpoOrder) {
|
||||
if (idx == entry) continue;
|
||||
|
||||
block_idx newIdom = INVALID_BLOCK;
|
||||
bool found = false;
|
||||
for (auto pred : blocks[idx].predecessors) {
|
||||
if (blocks[pred].idom == INVALID_BLOCK) continue;
|
||||
if (found) {
|
||||
newIdom = intersect(pred, newIdom);
|
||||
} else {
|
||||
newIdom = pred;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (blocks[idx].idom != newIdom) {
|
||||
blocks[idx].idom = newIdom;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto idx : rpoOrder) {
|
||||
if (idx == entry) continue;
|
||||
const block_idx parent = blocks[idx].idom;
|
||||
if (parent != INVALID_BLOCK) blocks[parent].doms.push_back(idx);
|
||||
}
|
||||
|
||||
for (auto idx : rpoOrder) {
|
||||
if (blocks[idx].predecessors.size() < 2) continue;
|
||||
for (auto cur : blocks[idx].predecessors) {
|
||||
while (cur != blocks[idx].idom) {
|
||||
blocks[cur].domFrontiers.insert(idx);
|
||||
cur = blocks[cur].idom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furlang::ir::function* function;
|
||||
std::vector<block_idx> rpoOrder;
|
||||
std::unordered_map<block_idx, block_info> blocks;
|
||||
std::unordered_map<register_t, register_info> registers;
|
||||
std::unordered_set<register_t> globalRegisters;
|
||||
private:
|
||||
block_idx intersect(block_idx block1, block_idx block2) {
|
||||
while (block1 != block2) {
|
||||
while (blocks[block1].rpoIndex > blocks[block2].rpoIndex)
|
||||
block1 = blocks[block1].idom;
|
||||
while (blocks[block2].rpoIndex > blocks[block1].rpoIndex)
|
||||
block2 = blocks[block2].idom;
|
||||
}
|
||||
return block1;
|
||||
}
|
||||
};
|
||||
|
||||
static void ssa_stage_rename_block(function_context& ctx,
|
||||
block_idx idx,
|
||||
std::unordered_map<register_t, std::uint32_t>& regVers,
|
||||
std::unordered_map<register_t, std::stack<std::uint32_t>>& regVerStacks) {
|
||||
std::unordered_map<register_t, std::uint32_t> pushed;
|
||||
|
||||
const auto& block = ctx.function->blocks()[idx];
|
||||
auto it = block->instructions().begin();
|
||||
for (; it != block->instructions().end(); ++it) {
|
||||
auto& instr = *it;
|
||||
if (instr->type() != furlang::ir::instruction_t::Phi) break;
|
||||
|
||||
const register_t orig = instr->destination().reg();
|
||||
const std::uint32_t newVer = regVers[orig]++;
|
||||
|
||||
instr->destination().reg().ver = newVer;
|
||||
regVerStacks[orig].push(newVer);
|
||||
++pushed[orig];
|
||||
}
|
||||
|
||||
for (; it != block->instructions().end(); ++it) {
|
||||
auto& instr = *it;
|
||||
for (auto& operand : instr->sources()) {
|
||||
if (operand->type() != furlang::ir::operand_t::Register) continue;
|
||||
|
||||
const register_t orig = operand->reg();
|
||||
if (regVerStacks[orig].empty()) continue;
|
||||
operand->reg().ver = regVerStacks[orig].top();
|
||||
}
|
||||
|
||||
if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) {
|
||||
const register_t orig = instr->destination().reg();
|
||||
const std::uint32_t newVer = regVers[orig]++;
|
||||
|
||||
instr->destination().reg().ver = newVer;
|
||||
regVerStacks[orig].push(newVer);
|
||||
++pushed[orig];
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& operand : block->exit()->sources()) {
|
||||
if (operand->type() != furlang::ir::operand_t::Register) continue;
|
||||
|
||||
const auto orig = operand->reg();
|
||||
if (regVerStacks[orig].empty()) continue;
|
||||
operand->reg().ver = regVerStacks[orig].top();
|
||||
}
|
||||
|
||||
for (auto succIdx : ctx.blocks[idx].successors) {
|
||||
const auto& succ = ctx.function->blocks()[succIdx];
|
||||
for (auto& instr : succ->instructions()) {
|
||||
if (instr->type() != furlang::ir::instruction_t::Phi) break;
|
||||
|
||||
auto& phi = dynamic_cast<furlang::ir::phi_instruction&>(*instr);
|
||||
for (auto& pair : phi.labels()) {
|
||||
if (pair.second != idx) continue;
|
||||
|
||||
auto orig = pair.first.reg();
|
||||
if (auto it = regVerStacks.find(orig); it != regVerStacks.end())
|
||||
pair.first.reg().ver = it->second.top();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& child : ctx.blocks[idx].doms) {
|
||||
ssa_stage_rename_block(ctx, child, regVers, regVerStacks);
|
||||
}
|
||||
|
||||
for (const auto& [reg, count] : pushed) {
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
regVerStacks[reg].pop();
|
||||
}
|
||||
}
|
||||
|
||||
static void ssa_stage(function_context& ctx) {
|
||||
ctx.compute_dominance();
|
||||
|
||||
std::vector<block_idx> worklist;
|
||||
for (const auto& [reg, info] : ctx.registers) {
|
||||
if (info.defSites.size() < 2 || ctx.globalRegisters.find(reg) == ctx.globalRegisters.end()) continue;
|
||||
|
||||
worklist.clear();
|
||||
worklist.insert(worklist.end(), info.defSites.begin(), info.defSites.end());
|
||||
|
||||
std::unordered_set<block_idx> added;
|
||||
while (!worklist.empty()) {
|
||||
const auto idx = worklist.back();
|
||||
worklist.pop_back();
|
||||
for (auto frontier : ctx.blocks[idx].domFrontiers) {
|
||||
if (added.find(frontier) != added.end()) continue;
|
||||
added.insert(frontier);
|
||||
|
||||
const auto& target = ctx.function->blocks()[frontier];
|
||||
const auto& preds = ctx.blocks[frontier].predecessors;
|
||||
|
||||
auto instr = std::make_unique<furlang::ir::phi_instruction>(reg);
|
||||
for (auto pred : preds) {
|
||||
instr->labels().emplace_back(furlang::ir::operand::new_reg(reg), pred);
|
||||
}
|
||||
target->instructions().emplace(target->instructions().begin(), std::move(instr));
|
||||
|
||||
if (info.defSites.find(frontier) == info.defSites.end()) worklist.push_back(frontier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<register_t, std::uint32_t> regVers;
|
||||
std::unordered_map<register_t, std::stack<std::uint32_t>> regVerStacks;
|
||||
ssa_stage_rename_block(ctx, ctx.rpoOrder.front(), regVers, regVerStacks);
|
||||
}
|
||||
|
||||
static void dessa_stage(function_context& ctx) {
|
||||
for (block_idx idx = 0; idx < ctx.function->blocks().size(); ++idx) {
|
||||
const auto& block = ctx.function->blocks()[idx];
|
||||
auto& instrs = block->instructions();
|
||||
for (auto it = instrs.begin(); it != instrs.end() && (*it)->type() == furlang::ir::instruction_t::Phi;
|
||||
it = instrs.erase(it)) {
|
||||
auto& phi = dynamic_cast<furlang::ir::phi_instruction&>(**it);
|
||||
auto dstReg = phi.destination().reg();
|
||||
for (auto& [srcOp, label] : phi.labels()) {
|
||||
ctx.function->blocks()[label]->instructions().push_back(
|
||||
std::make_unique<furlang::ir::assign_instruction>(furlang::ir::operand::new_reg(srcOp.reg()),
|
||||
furlang::ir::operand::new_reg(dstReg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct sccp_lattice {
|
||||
enum lattice_t { // NOLINT
|
||||
Top,
|
||||
Constant,
|
||||
Bottom,
|
||||
} type = Top;
|
||||
std::uint64_t constant = 0;
|
||||
|
||||
bool operator==(const sccp_lattice& other) const {
|
||||
return type == other.type && (type != Constant || constant == other.constant);
|
||||
}
|
||||
|
||||
bool operator!=(const sccp_lattice& other) const { return !this->operator==(other); }
|
||||
};
|
||||
|
||||
sccp_lattice sccp_stage_get_lattice(std::unordered_map<register_op, sccp_lattice>& latticeValues,
|
||||
const furlang::ir::operand& op) {
|
||||
if (op.type() == furlang::ir::operand_t::Integer) {
|
||||
sccp_lattice lat;
|
||||
lat.type = sccp_lattice::Constant;
|
||||
lat.constant = op.integer();
|
||||
return lat;
|
||||
}
|
||||
if (op.type() == furlang::ir::operand_t::Register) {
|
||||
auto reg = op.reg();
|
||||
if (auto it = latticeValues.find(reg); it != latticeValues.end()) return it->second;
|
||||
return { sccp_lattice::Top };
|
||||
}
|
||||
return { sccp_lattice::Bottom };
|
||||
};
|
||||
|
||||
static void sccp_stage(function_context& ctx) {
|
||||
using lattice = sccp_lattice;
|
||||
|
||||
std::unordered_map<register_op, lattice> latticeValues;
|
||||
std::unordered_map<register_t, std::vector<furlang::ir::instruction*>> edges;
|
||||
std::unordered_set<block_idx> execBlocks;
|
||||
std::set<std::pair<block_idx, block_idx>> execEdges;
|
||||
|
||||
std::queue<std::pair<block_idx, block_idx>> cfgWorklist;
|
||||
std::queue<furlang::ir::instruction*> ssaWorklist;
|
||||
|
||||
std::unordered_map<furlang::ir::instruction*, block_idx> blockMap;
|
||||
|
||||
for (block_idx idx = 0; idx < ctx.function->blocks().size(); ++idx) {
|
||||
const auto& block = ctx.function->blocks()[idx];
|
||||
auto& instrs = block->instructions();
|
||||
for (auto it = instrs.begin(); it != instrs.end(); ++it) {
|
||||
const auto& instr = *it;
|
||||
blockMap[instr.get()] = idx;
|
||||
for (const auto& op : instr->sources()) {
|
||||
if (op->type() != furlang::ir::operand_t::Register) continue;
|
||||
edges[op->reg()].push_back(instr.get());
|
||||
}
|
||||
}
|
||||
|
||||
blockMap[block->exit().get()] = idx;
|
||||
for (const auto& op : block->exit()->sources()) {
|
||||
if (op->type() != furlang::ir::operand_t::Register) continue;
|
||||
edges[op->reg()].push_back(block->exit().get());
|
||||
}
|
||||
}
|
||||
|
||||
cfgWorklist.push({ 0, 0 });
|
||||
while (!cfgWorklist.empty() || !ssaWorklist.empty()) {
|
||||
if (!cfgWorklist.empty()) {
|
||||
auto edge = cfgWorklist.front();
|
||||
cfgWorklist.pop();
|
||||
block_idx from = edge.first;
|
||||
block_idx to = edge.second;
|
||||
|
||||
if (execEdges.count(edge) != 0) continue;
|
||||
execEdges.insert(edge);
|
||||
|
||||
bool firstVisit = (execBlocks.find(to) == execBlocks.end());
|
||||
execBlocks.insert(to);
|
||||
|
||||
const auto& block = ctx.function->blocks()[to];
|
||||
if (firstVisit) {
|
||||
for (auto& instr : block->instructions()) {
|
||||
ssaWorklist.push(instr.get());
|
||||
}
|
||||
ssaWorklist.push(block->exit().get());
|
||||
} else {
|
||||
for (auto& instr : block->instructions()) {
|
||||
if (instr->type() != furlang::ir::instruction_t::Phi) break;
|
||||
ssaWorklist.push(instr.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ssaWorklist.empty()) {
|
||||
auto* instr = ssaWorklist.front();
|
||||
ssaWorklist.pop();
|
||||
|
||||
block_idx blockIdx = blockMap[instr];
|
||||
if (execBlocks.find(blockIdx) == execBlocks.end()) continue;
|
||||
|
||||
lattice newLat = { lattice::Top };
|
||||
|
||||
switch (instr->type()) {
|
||||
case furlang::ir::instruction_t::Phi: {
|
||||
auto& phi = dynamic_cast<furlang::ir::phi_instruction&>(*instr);
|
||||
for (const auto& [op, label] : phi.labels()) {
|
||||
if (execEdges.count({ label, blockIdx }) == 0) continue;
|
||||
lattice opLat = sccp_stage_get_lattice(latticeValues, op);
|
||||
if (opLat.type == lattice::Bottom) newLat.type = lattice::Bottom;
|
||||
if (opLat.type == lattice::Constant) {
|
||||
if (newLat.type == lattice::Top) {
|
||||
newLat = opLat;
|
||||
} else if (newLat.type == lattice::Constant && newLat.constant != opLat.constant) {
|
||||
newLat.type = lattice::Bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Assign: {
|
||||
newLat = sccp_stage_get_lattice(latticeValues, *instr->sources().front());
|
||||
} break;
|
||||
case furlang::ir::instruction_t::Add:
|
||||
case furlang::ir::instruction_t::Sub:
|
||||
case furlang::ir::instruction_t::Mul:
|
||||
case furlang::ir::instruction_t::Div:
|
||||
case furlang::ir::instruction_t::Mod:
|
||||
case furlang::ir::instruction_t::Eq:
|
||||
case furlang::ir::instruction_t::NotEq:
|
||||
case furlang::ir::instruction_t::LessThan:
|
||||
case furlang::ir::instruction_t::GreaterThan:
|
||||
case furlang::ir::instruction_t::LessEq:
|
||||
case furlang::ir::instruction_t::GreaterEq: {
|
||||
lattice lhs = sccp_stage_get_lattice(latticeValues, *instr->sources()[0]);
|
||||
lattice rhs = sccp_stage_get_lattice(latticeValues, *instr->sources()[1]);
|
||||
|
||||
if (lhs.type == lattice::Bottom || rhs.type == lattice::Bottom) {
|
||||
newLat.type = lattice::Bottom;
|
||||
} else if (lhs.type == lattice::Constant && rhs.type == lattice::Constant) {
|
||||
newLat.type = lattice::Constant;
|
||||
switch (instr->type()) {
|
||||
case furlang::ir::instruction_t::Add: newLat.constant = lhs.constant + rhs.constant; break;
|
||||
case furlang::ir::instruction_t::Sub: newLat.constant = lhs.constant - rhs.constant; break;
|
||||
case furlang::ir::instruction_t::Mul: newLat.constant = lhs.constant * rhs.constant; break;
|
||||
case furlang::ir::instruction_t::Div: newLat.constant = lhs.constant / rhs.constant; break;
|
||||
case furlang::ir::instruction_t::Mod: newLat.constant = lhs.constant % rhs.constant; break;
|
||||
case furlang::ir::instruction_t::Eq:
|
||||
newLat.constant = (lhs.constant == rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
case furlang::ir::instruction_t::NotEq:
|
||||
newLat.constant = (lhs.constant != rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
case furlang::ir::instruction_t::LessThan:
|
||||
newLat.constant = (lhs.constant < rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
case furlang::ir::instruction_t::GreaterThan:
|
||||
newLat.constant = (lhs.constant > rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
case furlang::ir::instruction_t::LessEq:
|
||||
newLat.constant = (lhs.constant <= rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
case furlang::ir::instruction_t::GreaterEq:
|
||||
newLat.constant = (lhs.constant >= rhs.constant) ? 1 : 0;
|
||||
break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) {
|
||||
auto dst = instr->destination().reg();
|
||||
if (!(latticeValues[dst] == newLat)) {
|
||||
latticeValues[dst] = newLat;
|
||||
for (auto* uInstr : edges[dst])
|
||||
ssaWorklist.push(uInstr);
|
||||
}
|
||||
}
|
||||
|
||||
if (instr == ctx.function->blocks()[blockIdx]->exit().get()) {
|
||||
auto* exit = ctx.function->blocks()[blockIdx]->exit().get();
|
||||
if (exit->type() == furlang::ir::instruction_t::Branch) {
|
||||
auto& br = dynamic_cast<furlang::ir::branch_instruction&>(*exit);
|
||||
cfgWorklist.push({ blockIdx, br.block() });
|
||||
} else if (exit->type() == furlang::ir::instruction_t::BranchCond) {
|
||||
auto& br = dynamic_cast<furlang::ir::branch_cond_instruction&>(*exit);
|
||||
lattice cond = sccp_stage_get_lattice(latticeValues, *exit->sources()[0]);
|
||||
|
||||
if (cond.type == lattice::Constant) {
|
||||
if (cond.constant != 0)
|
||||
cfgWorklist.push({ blockIdx, br.if_block() });
|
||||
else
|
||||
cfgWorklist.push({ blockIdx, br.else_block() });
|
||||
} else {
|
||||
cfgWorklist.push({ blockIdx, br.if_block() });
|
||||
cfgWorklist.push({ blockIdx, br.else_block() });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (block_idx i = 0; i < ctx.function->blocks().size(); ++i) {
|
||||
if (execBlocks.find(i) == execBlocks.end()) {
|
||||
ctx.function->blocks()[i]->instructions().clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& block = ctx.function->blocks()[i];
|
||||
|
||||
for (auto& instr : block->instructions()) {
|
||||
for (auto& op : instr->sources()) {
|
||||
if (op->type() != furlang::ir::operand_t::Register) continue;
|
||||
auto reg = op->reg();
|
||||
if (latticeValues[reg].type != lattice::Constant) continue;
|
||||
*op = furlang::ir::operand::new_integer(latticeValues[reg].constant);
|
||||
}
|
||||
}
|
||||
|
||||
auto* exit = block->exit().get();
|
||||
if (exit->type() != furlang::ir::instruction_t::BranchCond) continue;
|
||||
auto& br = dynamic_cast<furlang::ir::branch_cond_instruction&>(*exit);
|
||||
lattice cond = sccp_stage_get_lattice(latticeValues, *exit->sources()[0]);
|
||||
if (cond.type != lattice::Constant) continue;
|
||||
block_idx target = (cond.constant != 0) ? br.if_block() : br.else_block();
|
||||
block->exit() = std::make_unique<furlang::ir::branch_instruction>(target);
|
||||
}
|
||||
}
|
||||
|
||||
static void adce_stage(function_context& ctx) {
|
||||
std::unordered_map<register_op, furlang::ir::instruction*> defMap;
|
||||
std::unordered_set<furlang::ir::instruction*> alive;
|
||||
std::queue<furlang::ir::instruction*> worklist;
|
||||
|
||||
for (block_idx blockIdx = 0; blockIdx < ctx.function->blocks().size(); ++blockIdx) {
|
||||
const auto& block = ctx.function->blocks()[blockIdx];
|
||||
|
||||
for (auto& instr : block->instructions()) {
|
||||
if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) {
|
||||
defMap[instr->destination().reg()] = instr.get();
|
||||
}
|
||||
if (instr->type() == furlang::ir::instruction_t::Call) {
|
||||
// TODO: Check if the function has side effects
|
||||
if (alive.insert(instr.get()).second) worklist.push(instr.get());
|
||||
}
|
||||
}
|
||||
|
||||
auto* exit = block->exit().get();
|
||||
alive.insert(exit);
|
||||
worklist.push(exit);
|
||||
}
|
||||
|
||||
while (!worklist.empty()) {
|
||||
const auto* instr = worklist.front();
|
||||
worklist.pop();
|
||||
|
||||
for (const auto& op : instr->sources()) {
|
||||
if (op->type() != furlang::ir::operand_t::Register) continue;
|
||||
auto src = op->reg();
|
||||
|
||||
if (defMap.find(src) == defMap.end()) continue;
|
||||
auto* defInstr = defMap[src];
|
||||
if (alive.insert(defInstr).second) {
|
||||
worklist.push(defInstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (block_idx blockIdx = 0; blockIdx < ctx.function->blocks().size(); ++blockIdx) {
|
||||
const auto& block = ctx.function->blocks()[blockIdx];
|
||||
auto& instrs = block->instructions();
|
||||
|
||||
auto it = instrs.begin();
|
||||
while (it != instrs.end()) {
|
||||
it = (alive.find(it->get()) != alive.end()) ? it + 1 : instrs.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void post_process::process(furlang::ir::mod& mod) {
|
||||
for (const auto& func : mod.functions()) {
|
||||
if (!func || func->blocks().empty()) continue;
|
||||
function_context ctx{ func.get() };
|
||||
|
||||
for (const auto& stage : m_stages) {
|
||||
switch (stage) {
|
||||
case Ssa: ssa_stage(ctx); break;
|
||||
case Sccp: sccp_stage(ctx); break;
|
||||
case Adce: adce_stage(ctx); break;
|
||||
case DeSsa: dessa_stage(ctx); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furc::front
|
||||
+45
-13
@@ -1,28 +1,33 @@
|
||||
#ifndef LIBFURC
|
||||
|
||||
#include "furc/ast/program.hpp"
|
||||
#include "furc/back/furvm.hpp"
|
||||
#include "furc/front/ir_generator.hpp"
|
||||
#include "furc/front/parser.hpp"
|
||||
#include "furc/front/post_process.hpp"
|
||||
#include "furlang/arena.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <furvm/furvm.hpp>
|
||||
#include <iostream>
|
||||
|
||||
int main(void) {
|
||||
try {
|
||||
std::string programStr = R"(
|
||||
func main() {
|
||||
x = 5;
|
||||
x -= 3;
|
||||
if (x < 3) {
|
||||
y = x * 2;
|
||||
w = y;
|
||||
} else {
|
||||
y = x - 3;
|
||||
private native func print(value: int32);
|
||||
|
||||
func main() -> int32 {
|
||||
x = 0;
|
||||
y = 10;
|
||||
z = 1;
|
||||
while (x < y) {
|
||||
x = x + z;
|
||||
}
|
||||
w = x - y;
|
||||
z = x + y;
|
||||
print(sizeof x);
|
||||
}
|
||||
)";
|
||||
furc::front::parser parser("<TEMP>", programStr);
|
||||
furlang::arena arena{};
|
||||
furc::front::parser parser(arena, "<TEMP>", programStr);
|
||||
furc::front::ir_generator generator;
|
||||
|
||||
auto programResult = parser.parse();
|
||||
@@ -33,9 +38,17 @@ int main(void) {
|
||||
const auto& program = *programResult;
|
||||
program->accept(generator);
|
||||
|
||||
auto module = std::move(generator.move_module());
|
||||
auto mod = std::move(generator.move_module());
|
||||
|
||||
furc::front::post_process postProcess;
|
||||
postProcess.push_stage(furc::front::post_process::Ssa);
|
||||
postProcess.push_stage(furc::front::post_process::Sccp);
|
||||
postProcess.push_stage(furc::front::post_process::Adce);
|
||||
postProcess.push_stage(furc::front::post_process::DeSsa);
|
||||
postProcess.process(mod);
|
||||
|
||||
std::cout << "Generated IR:\n";
|
||||
for (const auto& function : module.functions()) {
|
||||
for (const auto& function : mod.functions()) {
|
||||
std::cout << function->name() << ":\n";
|
||||
furlang::ir::block_index blockIndex = 0;
|
||||
for (const auto& block : function->blocks()) {
|
||||
@@ -47,6 +60,25 @@ int main(void) {
|
||||
}
|
||||
}
|
||||
|
||||
auto context = std::make_shared<furvm::context>();
|
||||
auto furvmMod = context->emplace("main", furc::back::furvm_generator::generate(mod));
|
||||
|
||||
std::ofstream file("./a.fmod", std::ios::binary);
|
||||
furvmMod->serialize(file);
|
||||
file.close();
|
||||
|
||||
furvmMod->set_native_function("print",
|
||||
[](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", furvm::function_sig{}));
|
||||
|
||||
std::cout << "--- Interpreting:\n";
|
||||
|
||||
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
|
||||
executor->step();
|
||||
}
|
||||
|
||||
return 0;
|
||||
} catch (...) {
|
||||
std::cerr << "Caught an exception in main!\n";
|
||||
|
||||
@@ -106,6 +106,55 @@ private:
|
||||
region* m_tail = nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class arena_allocator {
|
||||
template <typename>
|
||||
friend class arena_allocator;
|
||||
public:
|
||||
using value_type = T;
|
||||
public:
|
||||
explicit arena_allocator(arena& arena) noexcept
|
||||
: m_arena(&arena) {}
|
||||
|
||||
template <typename U>
|
||||
arena_allocator(const arena_allocator<U>& other) noexcept
|
||||
: m_arena(other.m_arena) {}
|
||||
|
||||
template <typename U>
|
||||
arena_allocator& operator=(const arena_allocator<U>& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = other.m_arena;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
arena_allocator(arena_allocator<U>&& other) noexcept
|
||||
: m_arena(std::move(other.m_arena)) {}
|
||||
|
||||
template <typename U>
|
||||
arena_allocator& operator=(arena_allocator<U>&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = std::move(other.m_arena);
|
||||
return *this;
|
||||
}
|
||||
public:
|
||||
[[nodiscard]] T* allocate(std::size_t count = 1) { return m_arena->allocate<T>(count); }
|
||||
|
||||
void deallocate(T* ptr, std::size_t count) noexcept {}
|
||||
public:
|
||||
template <typename U>
|
||||
bool operator==(const arena_allocator<U>& other) const noexcept {
|
||||
return m_arena == other.m_arena;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
bool operator!=(const arena_allocator<U>& other) const noexcept {
|
||||
return m_arena != other.m_arena;
|
||||
}
|
||||
private:
|
||||
arena* m_arena;
|
||||
};
|
||||
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_ARENA_HPP
|
||||
@@ -3,12 +3,25 @@
|
||||
|
||||
#include "furlang/ir/block.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
enum class function_t : std::uint8_t {
|
||||
Normal = 0,
|
||||
Import,
|
||||
Native,
|
||||
};
|
||||
|
||||
enum class function_access_t : std::uint8_t {
|
||||
Public = 0,
|
||||
Private,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IR function.
|
||||
*
|
||||
@@ -22,18 +35,11 @@ public:
|
||||
/**
|
||||
* @brief Construct a new IR function.
|
||||
*
|
||||
* @param name Name to copy.
|
||||
* @param name Name to forward.
|
||||
*/
|
||||
function(const std::string& name)
|
||||
: m_name(name) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a new IR function.
|
||||
*
|
||||
* @param name Name to move.
|
||||
*/
|
||||
function(std::string&& name)
|
||||
: m_name(std::move(name)) {}
|
||||
template <typename StringFwd, typename = std::enable_if_t<std::is_constructible_v<std::string, StringFwd>>>
|
||||
function(StringFwd&& name, function_access_t access, std::uint32_t paramCount, function_t type = function_t::Normal)
|
||||
: m_name(std::forward<StringFwd>(name)), m_access(access), m_paramCount(paramCount), m_type(type) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this function's name.
|
||||
@@ -42,6 +48,27 @@ public:
|
||||
*/
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
/**
|
||||
* @brief Returns the function's access.
|
||||
*
|
||||
* @return The access.
|
||||
*/
|
||||
function_access_t access() const { return m_access; }
|
||||
|
||||
/**
|
||||
* @brief Returns this function's parameter count.
|
||||
*
|
||||
* @return The parameter count.
|
||||
*/
|
||||
std::uint32_t param_count() const { return m_paramCount; }
|
||||
|
||||
/**
|
||||
* @brief Returns this function's type.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
function_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Pushes and returns a new IR block.
|
||||
*
|
||||
@@ -57,6 +84,9 @@ public:
|
||||
const std::vector<value_type>& blocks() const { return m_blocks; }
|
||||
private:
|
||||
std::string m_name;
|
||||
function_access_t m_access;
|
||||
function_t m_type;
|
||||
std::uint32_t m_paramCount;
|
||||
std::vector<value_type> m_blocks;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
@@ -14,15 +18,53 @@ namespace ir {
|
||||
* @brief IR instruction type.
|
||||
*/
|
||||
enum class instruction_t {
|
||||
Alloca, /**< Unused */
|
||||
Assign, /**< Assign */
|
||||
BinaryOp, /**< Binary operation */
|
||||
Call, /**< Call */
|
||||
Branch, /**< Branch */
|
||||
BranchCond, /**< Conditional branch */
|
||||
Return, /**< Return */
|
||||
Alloca, /**< Unused */
|
||||
Assign, /**< Assign */
|
||||
Add, /**< Addition */
|
||||
Sub, /**< Subtraction */
|
||||
Mul, /**< Multiplication */
|
||||
Div, /**< Division */
|
||||
Mod, /**< Modulo */
|
||||
Eq, /**< Equal */
|
||||
NotEq, /**< Not equal */
|
||||
LessThan, /**< Less than */
|
||||
GreaterThan, /**< Greater than */
|
||||
LessEq, /**< Less or equal */
|
||||
GreaterEq, /**< Greater or equal */
|
||||
Pointerof, /**< Pointerof */
|
||||
Sizeof, /**< Sizeof */
|
||||
Call, /**< Call */
|
||||
Branch, /**< Branch */
|
||||
BranchCond, /**< Conditional branch */
|
||||
Return, /**< Return */
|
||||
Phi, /**< Phi function */
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, instruction_t type) {
|
||||
switch (type) {
|
||||
case instruction_t::Alloca: return os << "alloca";
|
||||
case instruction_t::Assign: return os << "assign";
|
||||
case instruction_t::Add: return os << "add";
|
||||
case instruction_t::Sub: return os << "sub";
|
||||
case instruction_t::Mul: return os << "mul";
|
||||
case instruction_t::Div: return os << "div";
|
||||
case instruction_t::Mod: return os << "mod";
|
||||
case instruction_t::Eq: return os << "eq";
|
||||
case instruction_t::NotEq: return os << "notEq";
|
||||
case instruction_t::LessThan: return os << "lessThan";
|
||||
case instruction_t::GreaterThan: return os << "greaterThan";
|
||||
case instruction_t::LessEq: return os << "lessEq";
|
||||
case instruction_t::GreaterEq: return os << "greaterEq";
|
||||
case instruction_t::Pointerof: return os << "pointerof";
|
||||
case instruction_t::Sizeof: return os << "sizeof";
|
||||
case instruction_t::Call: return os << "call";
|
||||
case instruction_t::Branch: return os << "branch";
|
||||
case instruction_t::BranchCond: return os << "branchCond";
|
||||
case instruction_t::Return: return os << "return";
|
||||
case instruction_t::Phi: return os << "phi";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an instruction type exits.
|
||||
*
|
||||
@@ -66,6 +108,41 @@ public:
|
||||
* @return The type.
|
||||
*/
|
||||
virtual instruction_t type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns whether this instruction has a destination operand.
|
||||
*
|
||||
* @return true if this instruction has the destination operand.
|
||||
*/
|
||||
virtual bool has_destination() const { return false; }
|
||||
|
||||
/**
|
||||
* @brief Returns destination operand of this instruction.
|
||||
*
|
||||
* @return The destination operand.
|
||||
*/
|
||||
virtual operand& destination() { throw std::runtime_error("instruction type mismatch"); }
|
||||
|
||||
/**
|
||||
* @brief Returns destination operand of this instruction.
|
||||
*
|
||||
* @return The destination operand.
|
||||
*/
|
||||
virtual const operand& destination() const { throw std::runtime_error("instruction type mismatch"); }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of source operands of this instruction.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
virtual std::vector<operand*> sources() { return {}; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of source operands of this instruction.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
virtual std::vector<const operand*> sources() const { return {}; }
|
||||
public:
|
||||
/**
|
||||
* @brief Prints an instruction to an output stream.
|
||||
@@ -158,18 +235,39 @@ public:
|
||||
instruction_t type() const override { return instruction_t::Assign; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's source.
|
||||
* @brief Returns whether this instruction has a destination operand.
|
||||
*
|
||||
* @return The source.
|
||||
* @return true
|
||||
*/
|
||||
const operand& source() const { return m_source; }
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination.
|
||||
*
|
||||
* @return The destination.
|
||||
*/
|
||||
const operand& destination() const { return m_destination; }
|
||||
operand& destination() override { return m_destination; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination.
|
||||
*
|
||||
* @return The destination.
|
||||
*/
|
||||
const operand& destination() const override { return m_destination; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of this instruction's source operands.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
std::vector<operand*> sources() override { return { &m_source }; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of this instruction's source operands.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
std::vector<const operand*> sources() const override { return { &m_source }; }
|
||||
private:
|
||||
operand m_source;
|
||||
operand m_destination;
|
||||
@@ -180,85 +278,122 @@ protected:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Binary operation instruction type
|
||||
* @brief Generic unary instruction
|
||||
*/
|
||||
enum class binary_op_instruction_t {
|
||||
Add, /**< Addition */
|
||||
Sub, /**< Subtraction */
|
||||
Mul, /**< Multiplication */
|
||||
Div, /**< Division */
|
||||
Mod, /**< Modulo */
|
||||
class unary_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new unary instruction.
|
||||
*
|
||||
* @param type Instruction type.
|
||||
* @param src Source operand.
|
||||
* @param dst Destination operand.
|
||||
*/
|
||||
unary_instruction(instruction_t type, operand&& src, operand&& dst)
|
||||
: m_type(type), m_src(std::move(src)), m_dst(std::move(dst)) {}
|
||||
|
||||
Eq, /**< Equal */
|
||||
NotEq, /**< Not equal */
|
||||
LessThan, /**< Less than */
|
||||
GreaterThan, /**< Greater than */
|
||||
LessEq, /**< Less or equal */
|
||||
GreaterEq, /**< Greater or equal */
|
||||
~unary_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
unary_instruction(unary_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
unary_instruction& operator=(unary_instruction&&) = default;
|
||||
|
||||
unary_instruction(const unary_instruction&) = delete;
|
||||
|
||||
unary_instruction& operator=(const unary_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return The instruction type.
|
||||
*/
|
||||
instruction_t type() const override { return m_type; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_src }; }
|
||||
|
||||
std::vector<const operand*> sources() const override { return { &m_src }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's source operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& src() const { return m_src; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& dst() const { return m_dst; }
|
||||
private:
|
||||
instruction_t m_type;
|
||||
operand m_src;
|
||||
operand m_dst;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override { return os << m_dst << " = " << m_type << ' ' << m_src; }
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, binary_op_instruction_t type) {
|
||||
switch (type) {
|
||||
case binary_op_instruction_t::Add: return os << '+';
|
||||
case binary_op_instruction_t::Sub: return os << '-';
|
||||
case binary_op_instruction_t::Mul: return os << '*';
|
||||
case binary_op_instruction_t::Div: return os << '/';
|
||||
case binary_op_instruction_t::Mod: return os << '%';
|
||||
case binary_op_instruction_t::Eq: return os << "==";
|
||||
case binary_op_instruction_t::NotEq: return os << "!=";
|
||||
case binary_op_instruction_t::LessThan: return os << '<';
|
||||
case binary_op_instruction_t::GreaterThan: return os << '>';
|
||||
case binary_op_instruction_t::LessEq: return os << "<=";
|
||||
case binary_op_instruction_t::GreaterEq: return os << ">=";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Binary operation instruction
|
||||
* @brief Generic binary instruction
|
||||
*/
|
||||
class binary_op_instruction final : public instruction {
|
||||
class binary_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new binary operation instruction.
|
||||
*
|
||||
* @param type Operation type.
|
||||
* @param type Instruction type.
|
||||
* @param lhs Left-hand-side operand.
|
||||
* @param rhs Right-hand-side operand.
|
||||
* @param dst Destination operand.
|
||||
*/
|
||||
binary_op_instruction(binary_op_instruction_t type, operand&& lhs, operand&& rhs, operand&& dst)
|
||||
binary_instruction(instruction_t type, operand&& lhs, operand&& rhs, operand&& dst)
|
||||
: m_type(type), m_lhs(std::move(lhs)), m_rhs(std::move(rhs)), m_dst(std::move(dst)) {}
|
||||
|
||||
~binary_op_instruction() override = default;
|
||||
~binary_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
binary_op_instruction(binary_op_instruction&&) = default;
|
||||
binary_instruction(binary_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
binary_op_instruction& operator=(binary_op_instruction&&) = default;
|
||||
binary_instruction& operator=(binary_instruction&&) = default;
|
||||
|
||||
binary_op_instruction(const binary_op_instruction&) = delete;
|
||||
binary_instruction(const binary_instruction&) = delete;
|
||||
|
||||
binary_op_instruction& operator=(const binary_op_instruction&) = delete;
|
||||
binary_instruction& operator=(const binary_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::BinaryOp.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::BinaryOp; }
|
||||
instruction_t type() const override { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's operation type.
|
||||
*
|
||||
* @return The operation type.
|
||||
*/
|
||||
binary_op_instruction_t op_type() const { return m_type; }
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_lhs, &m_rhs }; }
|
||||
|
||||
std::vector<const operand*> sources() const override { return { &m_lhs, &m_rhs }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's left-hand-side operand.
|
||||
@@ -281,13 +416,13 @@ public:
|
||||
*/
|
||||
const operand& dst() const { return m_dst; }
|
||||
private:
|
||||
binary_op_instruction_t m_type;
|
||||
operand m_lhs /**< Left-hand-side operand */;
|
||||
operand m_rhs /**< Right-hand-side operand */;
|
||||
operand m_dst /**< Destination operand */;
|
||||
instruction_t m_type;
|
||||
operand m_lhs /**< Left-hand-side operand */;
|
||||
operand m_rhs /**< Right-hand-side operand */;
|
||||
operand m_dst /**< Destination operand */;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
return os << "binop(" << m_type << ") " << m_lhs << ", " << m_rhs << ", " << m_dst;
|
||||
return os << m_dst << " = " << m_lhs << ' ' << m_type << ' ' << m_rhs;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -374,6 +509,9 @@ public:
|
||||
public:
|
||||
instruction_t type() const override { return instruction_t::BranchCond; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_condition }; }
|
||||
std::vector<const operand*> sources() const override { return { &m_condition }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's condition operand.
|
||||
*
|
||||
@@ -442,6 +580,16 @@ public:
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Return; }
|
||||
|
||||
std::vector<operand*> sources() override {
|
||||
if (m_value.has_value()) return { &*m_value };
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
if (m_value.has_value()) return { &*m_value };
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's return value operand.
|
||||
*
|
||||
@@ -458,6 +606,162 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Phi instruction
|
||||
*
|
||||
* Used to implement the phi node in the SSA graph.
|
||||
*/
|
||||
class phi_instruction final : public instruction {
|
||||
public:
|
||||
phi_instruction(register_operand dst)
|
||||
: m_dst(operand::new_reg(dst)) {}
|
||||
|
||||
~phi_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
phi_instruction(phi_instruction&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
phi_instruction& operator=(phi_instruction&&) noexcept = default;
|
||||
|
||||
phi_instruction(const phi_instruction&) = delete;
|
||||
|
||||
phi_instruction& operator=(const phi_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Phi.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Phi; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
std::vector<const operand*> srcs;
|
||||
srcs.reserve(m_labels.size());
|
||||
for (const auto& [op, _block] : m_labels)
|
||||
srcs.push_back(&op);
|
||||
return srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's labels.
|
||||
*
|
||||
* @return The labels.
|
||||
*/
|
||||
std::vector<std::pair<operand, block_index>>& labels() { return m_labels; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's labels.
|
||||
*
|
||||
* @return The labels.
|
||||
*/
|
||||
const std::vector<std::pair<operand, block_index>>& labels() const { return m_labels; }
|
||||
private:
|
||||
operand m_dst;
|
||||
std::vector<std::pair<operand, block_index>> m_labels;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
os << m_dst << " = phi";
|
||||
bool first = true;
|
||||
for (const auto& pair : m_labels) {
|
||||
if (!first) os << ',';
|
||||
first = false;
|
||||
os << ' ' << pair.second << ": " << pair.first;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Function call instruction.
|
||||
*/
|
||||
class call_instruction final : public instruction {
|
||||
public:
|
||||
template <typename NameFwd, typename ArgsFwd>
|
||||
call_instruction(NameFwd&& name, operand&& dst, ArgsFwd&& args)
|
||||
: m_name(std::forward<NameFwd>(name)), m_dst(std::move(dst)), m_args(std::forward<ArgsFwd>(args)) {}
|
||||
|
||||
~call_instruction() override = default;
|
||||
|
||||
call_instruction(call_instruction&&) noexcept = default;
|
||||
call_instruction& operator=(call_instruction&&) noexcept = default;
|
||||
call_instruction(const call_instruction&) = delete;
|
||||
call_instruction& operator=(const call_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Call.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Call; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
std::vector<const operand*> srcs;
|
||||
srcs.reserve(m_args.size());
|
||||
for (const auto& op : m_args)
|
||||
srcs.push_back(&op);
|
||||
return srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's callee name.
|
||||
*
|
||||
* @return The name.
|
||||
*/
|
||||
const std::string& name() const { return m_name; }
|
||||
private:
|
||||
std::string m_name;
|
||||
operand m_dst;
|
||||
std::vector<operand> m_args;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
os << "call " << m_name << '(';
|
||||
bool first = true;
|
||||
for (const auto& op : m_args) {
|
||||
if (!first) os << ", ";
|
||||
first = false;
|
||||
os << op;
|
||||
}
|
||||
return os << ") = " << m_dst;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ namespace ir {
|
||||
/**
|
||||
* @brief IR module
|
||||
*/
|
||||
class module {
|
||||
class mod {
|
||||
public:
|
||||
using value_type = std::unique_ptr<function>; /**< Value type. */
|
||||
public:
|
||||
module() = default;
|
||||
mod() = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes and returns a new IR function.
|
||||
|
||||
@@ -19,11 +19,37 @@ enum class operand_t {
|
||||
String, /**< String */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Alias to a register type.
|
||||
*/
|
||||
using register_t = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief Register operand alias.
|
||||
* @see operand_t::Register
|
||||
*/
|
||||
using register_operand = std::uint32_t;
|
||||
struct register_operand {
|
||||
register_t reg = 0;
|
||||
std::uint32_t ver = 0;
|
||||
|
||||
register_operand() = default;
|
||||
|
||||
register_operand(register_t reg)
|
||||
: reg(reg) {}
|
||||
|
||||
register_operand(register_t reg, std::uint32_t ver)
|
||||
: reg(reg), ver(ver) {}
|
||||
|
||||
operator std::uint32_t&() { return reg; }
|
||||
operator const std::uint32_t&() const { return reg; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const register_operand& op) {
|
||||
return os << op.reg << '_' << op.ver;
|
||||
}
|
||||
|
||||
bool operator==(const register_operand& rhs) const { return reg == rhs.reg && ver == rhs.ver; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Variable operand alias.
|
||||
* @see operand_t::Variable
|
||||
@@ -102,6 +128,19 @@ public:
|
||||
operand(const operand&) = delete;
|
||||
operand& operator=(const operand&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new register operand.
|
||||
*
|
||||
* @param value Value of the new register operand.
|
||||
* @return The register operand.
|
||||
*/
|
||||
static operand new_reg(register_t value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::Register;
|
||||
operand.m_value.reg = { value, 0 };
|
||||
return operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new register operand.
|
||||
*
|
||||
@@ -163,6 +202,13 @@ public:
|
||||
*/
|
||||
operand_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Returns this operand's register value.
|
||||
*
|
||||
* @return The register value.
|
||||
*/
|
||||
register_operand& reg() { return m_value.reg; }
|
||||
|
||||
/**
|
||||
* @brief Returns this operand's register value.
|
||||
*
|
||||
@@ -228,4 +274,17 @@ private:
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<furlang::ir::register_operand> {
|
||||
std::size_t operator()(const furlang::ir::register_operand& op) const noexcept {
|
||||
std::size_t h1 = std::hash<decltype(op.reg)>{}(op.reg);
|
||||
std::size_t h2 = std::hash<std::uint32_t>{}(op.ver);
|
||||
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // FURLANG_IR_OPERAND_HPP
|
||||
@@ -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
|
||||
@@ -1,49 +1,13 @@
|
||||
#ifndef FURVM_CONSTANT_HPP
|
||||
#define FURVM_CONSTANT_HPP
|
||||
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <string_view>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
/**
|
||||
* @brief Bad constant access exception.
|
||||
*/
|
||||
class bad_constant_access : public std::exception {
|
||||
public:
|
||||
bad_constant_access() = default;
|
||||
~bad_constant_access() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_constant_access(bad_constant_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_constant_access& operator=(bad_constant_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_constant_access(const bad_constant_access&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_constant_access& operator=(const bad_constant_access&) = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a C-style string describing the cause of the error.
|
||||
*
|
||||
* @return The cause of the error.
|
||||
*/
|
||||
const char* what() const noexcept override { return "bad constant access"; }
|
||||
};
|
||||
|
||||
enum class constant_t : std::uint8_t {
|
||||
String, /**< String constant. */
|
||||
};
|
||||
|
||||
@@ -2,21 +2,28 @@
|
||||
#define FURVM_CONTEXT_HPP
|
||||
|
||||
#include "furlang/arena.hpp"
|
||||
#include "furvm/executor.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/module.hpp"
|
||||
#include "furvm/handle.hpp"
|
||||
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||
#include "furvm/thing_allocator.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
class context {
|
||||
class context : public handle_container<mod_h> {
|
||||
public:
|
||||
friend class executor;
|
||||
friend class thing;
|
||||
public:
|
||||
context();
|
||||
/**
|
||||
* @brief Constructs a context.
|
||||
*/
|
||||
context()
|
||||
: m_thingAllocator(m_thingArena) {}
|
||||
|
||||
~context() = default;
|
||||
|
||||
/**
|
||||
@@ -33,79 +40,101 @@ public:
|
||||
context& operator=(const context&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Adds a module to this context.
|
||||
* @brief Emplaces an executor in the context.
|
||||
*
|
||||
* @param args Arguments to forward to module's constructor.
|
||||
* @return An index to the emplaced module.
|
||||
* @param args Arguments forwarded to executor's constructor.
|
||||
* @return A handle to the emplaced executor.
|
||||
*/
|
||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<mod, module_handle, Args...>>>
|
||||
constexpr const auto& emplace(Args&&... args) {
|
||||
module_handle id = static_cast<module_handle>(m_modules.size());
|
||||
return m_modules.emplace_back(std::make_unique<mod>(id, std::forward<Args>(args)...));
|
||||
template <typename... Args>
|
||||
auto emplace_executor(Args&&... args) {
|
||||
return m_executors.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a module from this context.
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param index Index to the module.
|
||||
* @return Old value.
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
mod_p erase(module_handle index) {
|
||||
if (index >= m_modules.size()) return nullptr;
|
||||
return std::move(m_modules[index]);
|
||||
template <typename... Args>
|
||||
auto executor_at(Args&&... args) {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a module of this context.
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param index Position of the module.
|
||||
* @return The module.
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
constexpr mod_p& operator[](module_handle index) { return m_modules[index]; }
|
||||
template <typename... Args>
|
||||
auto executor_at(Args&&... args) const {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a module of this context.
|
||||
* @brief Erases an executor from the context.
|
||||
*
|
||||
* @param index Position of the module.
|
||||
* @return The module.
|
||||
* @param args Id of the executor.
|
||||
*/
|
||||
constexpr const mod_p& operator[](module_handle index) const { return m_modules[index]; }
|
||||
|
||||
/**
|
||||
* @brief Returns a module of this context.
|
||||
*
|
||||
* @param index Position of the module.
|
||||
* @return The module.
|
||||
*/
|
||||
constexpr mod_p& at(module_handle index) { return m_modules.at(index); }
|
||||
|
||||
/**
|
||||
* @brief Returns a module of this context.
|
||||
*
|
||||
* @param index Position of the module.
|
||||
* @return The module.
|
||||
*/
|
||||
constexpr const mod_p& at(module_handle index) const { return m_modules.at(index); }
|
||||
|
||||
/**
|
||||
* @brief Returns how many does this context have modules.
|
||||
*
|
||||
* @return The module count.
|
||||
*/
|
||||
constexpr size_t size() const { return m_modules.size(); }
|
||||
template <typename... Args>
|
||||
void erase_executor(Args&&... args) {
|
||||
m_executors.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Removes unreferenced things from the thing list.
|
||||
*/
|
||||
void collect();
|
||||
private:
|
||||
std::vector<mod_p> m_modules;
|
||||
std::vector<thing_p> m_things;
|
||||
std::vector<executor_p> m_executors;
|
||||
template <typename... Args>
|
||||
auto emplace_thing(Args&&... args) {
|
||||
return m_things.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::queue<thing_handle> m_deadThings;
|
||||
std::vector<void*> m_deadThingData;
|
||||
furlang::arena m_thingArena;
|
||||
/**
|
||||
* @brief Returns a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
* @return A handle to the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto thing_at(Args&&... args) {
|
||||
return m_things.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
* @return A handle to the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto thing_at(Args&&... args) const {
|
||||
return m_things.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
void erase_thing(Args&&... args) {
|
||||
m_things.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns context's thing allocator.
|
||||
*
|
||||
* @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;
|
||||
handle_container<executor_h> m_executors;
|
||||
|
||||
furlang::arena m_thingArena;
|
||||
thing_allocator<std::byte> m_thingAllocator;
|
||||
class thing_type_store m_thingTypeStore;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef FURVM_DETAIL_HANDLE_HPP
|
||||
#define FURVM_DETAIL_HANDLE_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
namespace furvm {
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* @brief Default specialization for header_has_refcount type trait.
|
||||
*/
|
||||
template <typename Header, typename = void>
|
||||
struct header_has_refcount : std::false_type {};
|
||||
|
||||
/**
|
||||
* @brief Specialization for header_has_refcount type trait.
|
||||
*/
|
||||
template <typename Header>
|
||||
struct header_has_refcount<Header,
|
||||
std::void_t<decltype(std::declval<Header&>().acquire()),
|
||||
decltype(std::declval<Header&>().release()),
|
||||
decltype(std::declval<Header&>().reference_count())>> : std::true_type {};
|
||||
|
||||
/**
|
||||
* @brief An alias for header_has_refcount's value.
|
||||
*/
|
||||
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
|
||||
|
||||
#endif // FURVM_DETAIL_HANDLE_HPP
|
||||
@@ -0,0 +1,176 @@
|
||||
#ifndef FURVM_DETAIL_SERIALIZATION_HPP
|
||||
#define FURVM_DETAIL_SERIALIZATION_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace furvm {
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::int8_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::int16_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::int32_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::int64_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes an integer.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value Integer.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, std::uint64_t value);
|
||||
|
||||
/**
|
||||
* @brief Serializes a string.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param value String.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os, const std::string& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::int8_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::int16_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::int32_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::int64_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::uint8_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::uint16_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::uint32_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes an integer.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value Integer.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::uint64_t& value);
|
||||
|
||||
/**
|
||||
* @brief Deserializes a string.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @param value String.
|
||||
* @return The input stream.
|
||||
*/
|
||||
std::istream& load(std::istream& is, std::string& value);
|
||||
|
||||
} // namespace detail
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_DETAIL_SERIALIZATION_HPP
|
||||
@@ -5,6 +5,75 @@
|
||||
|
||||
namespace furvm {
|
||||
|
||||
class bad_thing_access : public std::exception {
|
||||
public:
|
||||
bad_thing_access() = default;
|
||||
~bad_thing_access() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_thing_access(bad_thing_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_thing_access& operator=(bad_thing_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_thing_access(const bad_thing_access&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_thing_access& operator=(const bad_thing_access&) = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a C-style string describing the cause of the error.
|
||||
*
|
||||
* @return The cause of the error.
|
||||
*/
|
||||
const char* what() const noexcept override { return "bad thing access"; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Bad constant access exception.
|
||||
*/
|
||||
class bad_constant_access : public std::exception {
|
||||
public:
|
||||
bad_constant_access() = default;
|
||||
~bad_constant_access() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_constant_access(bad_constant_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_constant_access& operator=(bad_constant_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_constant_access(const bad_constant_access&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_constant_access& operator=(const bad_constant_access&) = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a C-style string describing the cause of the error.
|
||||
*
|
||||
* @return The cause of the error.
|
||||
*/
|
||||
const char* what() const noexcept override { return "bad constant access"; }
|
||||
};
|
||||
|
||||
class stack_underflow : public std::exception {
|
||||
public:
|
||||
stack_underflow() = default;
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
#define FURVM_EXECUTOR_HPP
|
||||
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
@@ -26,15 +30,6 @@ static inline executor_flags operator~(executor_flags flags) {
|
||||
}
|
||||
|
||||
class executor {
|
||||
private:
|
||||
/**
|
||||
* @brief A private token for the private constructor.
|
||||
*
|
||||
* Also `egzekutor` in Polish translates to `executor` I think.
|
||||
*/
|
||||
struct egzekutor {
|
||||
explicit egzekutor() = default;
|
||||
};
|
||||
public:
|
||||
/**
|
||||
* @brief Executor frame.
|
||||
@@ -42,22 +37,23 @@ public:
|
||||
* Call frame.
|
||||
*/
|
||||
struct frame {
|
||||
mod_p mod; /**< Shared pointer to a module with the bytecode. */
|
||||
mod_h mod; /**< Handle to the frame's module. */
|
||||
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
||||
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
||||
|
||||
std::vector<thing_p> variables; /**< Frame variables. */
|
||||
thing_type* returnType; /**< Return type. */
|
||||
std::vector<thing_h> variables; /**< Frame variables. */
|
||||
};
|
||||
public:
|
||||
/**
|
||||
* @brief Private constructor.
|
||||
* @brief Returns a new executor.
|
||||
*
|
||||
* @param id
|
||||
* @param context
|
||||
* @param context Context.
|
||||
*/
|
||||
executor(egzekutor, executor_handle id, const context_p& context);
|
||||
executor(const context_p& context)
|
||||
: m_context(context) {}
|
||||
|
||||
~executor();
|
||||
~executor() = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
@@ -69,24 +65,16 @@ public:
|
||||
*/
|
||||
executor& operator=(executor&&) noexcept = default;
|
||||
|
||||
executor(const executor&) = delete;
|
||||
executor& operator=(const executor&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a new executor.
|
||||
*
|
||||
* @param context Furvm context.
|
||||
* @return Shared pointer to the new executor.
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
static executor_p create(const context_p& context);
|
||||
public:
|
||||
/**
|
||||
* @brief Returns an id of this executor.
|
||||
*
|
||||
* @return The id.
|
||||
*/
|
||||
executor_handle id() const { return m_id; }
|
||||
executor(const executor&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
executor& operator=(const executor&) = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns flags of this executor.
|
||||
*
|
||||
@@ -97,9 +85,10 @@ public:
|
||||
/**
|
||||
* @brief Pushes a new frame.
|
||||
*
|
||||
* @param function Function.
|
||||
* @param mod Handle to the frame's module.
|
||||
* @param function Frame's function.
|
||||
*/
|
||||
void push_frame(const function_p& function);
|
||||
void push_frame(const mod_h& mod, function function);
|
||||
|
||||
/**
|
||||
* @brief Pops the top frame.
|
||||
@@ -116,68 +105,77 @@ public:
|
||||
frame frame() const;
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes a thing onto the stack.
|
||||
* @brief Pushes a thing handle onto the stack.
|
||||
*
|
||||
* @param thing The thing to push.
|
||||
* @param handle Thing handle.
|
||||
*/
|
||||
void push_thing(const thing_p& thing);
|
||||
template <typename HandleFwd>
|
||||
void push_thing(HandleFwd&& handle) {
|
||||
m_stack.emplace(std::forward<HandleFwd>(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pushes a thing onto the stack.
|
||||
*
|
||||
* @param thing The thing to push.
|
||||
* Registers a new thing and pushes its handle onto the stack.
|
||||
*
|
||||
* @param thing Thing.
|
||||
* @return The pushed handle.
|
||||
*/
|
||||
void push_thing(thing_p&& thing);
|
||||
thing_h push_thing(class thing<>&& thing);
|
||||
|
||||
/**
|
||||
* @brief Pops a thing from the stack.
|
||||
*
|
||||
* @return The popped thing.
|
||||
* @return A handle to the popped thing.
|
||||
*/
|
||||
thing_p pop_thing();
|
||||
thing_h pop_thing();
|
||||
|
||||
/**
|
||||
* @brief Returns the top thing on the stack.
|
||||
*
|
||||
* @return The thing.
|
||||
* @return A handle to the top thing.
|
||||
*/
|
||||
thing_p thing() const;
|
||||
thing_h thing() const;
|
||||
public:
|
||||
/**
|
||||
* @brief Stores a thing in a variable.
|
||||
* @brief Stores a thing in a frame variable.
|
||||
*
|
||||
* @param variable Variable to store the thing in.
|
||||
* @param thing Thing to store.
|
||||
* @param variable Id of the variable in which the handle will be put.
|
||||
* @param thing Thing handle.
|
||||
*/
|
||||
void store_thing(variable_t variable, const thing_p& thing);
|
||||
void store_thing(variable_t variable, const thing_h& thing);
|
||||
|
||||
/**
|
||||
* @brief Stores a thing in a variable.
|
||||
* @brief Stores a thing in a frame variable.
|
||||
*
|
||||
* @param variable Variable to store the thing in.
|
||||
* @param thing Thing to store.
|
||||
* @param variable Id of the variable in which the handle will be put.
|
||||
* @param thing Thing handle.
|
||||
*/
|
||||
void store_thing(variable_t variable, thing_p&& thing);
|
||||
void store_thing(variable_t variable, thing_h&& thing);
|
||||
|
||||
/**
|
||||
* @brief Returns a thing stored in a variable.
|
||||
*
|
||||
* @param variable Variable where the thing is stored.
|
||||
* @return The thing stored in the variable.
|
||||
* @param variable Id of the variable from which the handle will be fetched.
|
||||
* @return A handle stored in the variable.
|
||||
*/
|
||||
thing_p load_thing(variable_t variable) const;
|
||||
thing_h load_thing(variable_t variable) const;
|
||||
public:
|
||||
/**
|
||||
* @brief Executes next instruction.
|
||||
*/
|
||||
void step();
|
||||
private:
|
||||
executor_handle m_id;
|
||||
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
|
||||
context_p m_context;
|
||||
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;
|
||||
|
||||
std::stack<struct frame> m_frames;
|
||||
std::stack<thing_p> m_stack;
|
||||
std::stack<thing_h> m_stack;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#ifndef FURVM_FUNCTION_HPP
|
||||
#define FURVM_FUNCTION_HPP
|
||||
|
||||
#include "furlang/utility/hash.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||
#include "furvm/handle.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
@@ -15,38 +21,80 @@ enum class function_t : std::uint8_t {
|
||||
Import, /**< A function imported from another module. */
|
||||
};
|
||||
|
||||
using native_function = std::function<void(executor&)>;
|
||||
/**
|
||||
* @brief A native function.
|
||||
*/
|
||||
using native_function = std::string;
|
||||
|
||||
/**
|
||||
* @brief A function import.
|
||||
*/
|
||||
struct import_function {
|
||||
mod_id mod;
|
||||
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 {
|
||||
private:
|
||||
/**
|
||||
* @brief A private token for the private constructor.
|
||||
*
|
||||
* Also `funkcja` in Polish translates to `function` from what I heard.
|
||||
*/
|
||||
struct funkcja {
|
||||
explicit funkcja() = default;
|
||||
};
|
||||
public:
|
||||
/**
|
||||
* @brief Private constructor.
|
||||
* @brief Constructs a normal function.
|
||||
*
|
||||
* @param id
|
||||
* @param position
|
||||
* @param mod
|
||||
* @param signature Function's signature.
|
||||
* @param position Offset in bytecode of the function.
|
||||
*/
|
||||
function(funkcja, function_handle id, std::size_t position, const mod_p& mod);
|
||||
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 Private constructor.
|
||||
* @brief Constructs a native function.
|
||||
*
|
||||
* @param id
|
||||
* @param native
|
||||
* @param mod
|
||||
* @param signature Function's signature.
|
||||
* @param native Native function tag.
|
||||
*/
|
||||
function(funkcja, function_handle id, const native_function& native, const mod_p& mod);
|
||||
template <typename SigFwd,
|
||||
typename Native,
|
||||
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)) {}
|
||||
|
||||
~function() = default;
|
||||
/**
|
||||
* @brief Constructs an import function.
|
||||
*
|
||||
* @param mod Module's id.
|
||||
* @param function Function's id.
|
||||
*/
|
||||
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_signature(), m_value(import_function{ std::forward<ModFwd>(mod), function }) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an import function.
|
||||
*
|
||||
* @param mod Module.
|
||||
* @param function Function.
|
||||
*/
|
||||
function(const mod_h& mod, const function_h& function);
|
||||
|
||||
/**
|
||||
* @brief Destructs a function.
|
||||
*/
|
||||
~function();
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
@@ -58,33 +106,16 @@ public:
|
||||
*/
|
||||
function& operator=(function&&) noexcept;
|
||||
|
||||
function(const function&) = delete;
|
||||
function& operator=(const function&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a new function.
|
||||
*
|
||||
* @param mod Module.
|
||||
* @param args Arguments to pass to the function constructor.
|
||||
* @return The new function.
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
template <typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<function, funkcja, function_handle, Args..., const mod_p&>>>
|
||||
static function_p create(const mod_p& mod, Args&&... args) {
|
||||
function_handle id = mod->m_functions.size();
|
||||
function(const function&);
|
||||
|
||||
auto func = std::make_shared<function>(funkcja{}, id, std::forward<Args>(args)..., mod);
|
||||
mod->m_functions.emplace(mod->m_functions.begin() + id, func);
|
||||
return std::move(func);
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns an id of this function.
|
||||
*
|
||||
* @return The id.
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
constexpr function_handle id() const { return m_id; }
|
||||
|
||||
function& operator=(const function&);
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a type of this function.
|
||||
*
|
||||
@@ -93,14 +124,14 @@ public:
|
||||
constexpr function_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Returns a parent module of this function.
|
||||
* @brief Returns this function's signature.
|
||||
*
|
||||
* @return A shared pointer to the module.
|
||||
* @return The signature.
|
||||
*/
|
||||
const mod_p& mod() const { return m_module; }
|
||||
function_sig signature() const { return m_signature; }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a value for normal function.
|
||||
* @brief Returns normal function's value.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
@@ -110,7 +141,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a value for native function.
|
||||
* @brief Returns native function's value.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
@@ -120,47 +151,34 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a module of imported function.
|
||||
* @brief Returns import function's value.
|
||||
*
|
||||
* @return A handle to the module.
|
||||
* @return The value.
|
||||
*/
|
||||
module_handle imported_module() const {
|
||||
const import_function& imp() const {
|
||||
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
|
||||
return m_value.imp.mod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a module of imported function.
|
||||
*
|
||||
* @return A handle to the module.
|
||||
*/
|
||||
function_handle imported_function() const {
|
||||
if (m_type != function_t::Import) throw std::runtime_error("function type mismatch");
|
||||
return m_value.imp.function;
|
||||
return m_value.imp;
|
||||
}
|
||||
private:
|
||||
function_handle m_id;
|
||||
function_t m_type;
|
||||
mod_p m_module;
|
||||
function_t m_type;
|
||||
function_sig m_signature;
|
||||
|
||||
union value {
|
||||
std::size_t position = 0;
|
||||
native_function native;
|
||||
struct {
|
||||
module_handle mod;
|
||||
function_handle function;
|
||||
} imp;
|
||||
import_function imp;
|
||||
|
||||
value() = default;
|
||||
|
||||
value(std::size_t position)
|
||||
: position(position) {}
|
||||
|
||||
value(const native_function& native)
|
||||
: native(native) {}
|
||||
template <typename Native, typename = std::enable_if_t<std::is_constructible_v<native_function, Native>>>
|
||||
value(Native&& native)
|
||||
: native(std::forward<Native>(native)) {}
|
||||
|
||||
value(module_handle mod, function_handle function)
|
||||
: imp({ mod, function }) {}
|
||||
value(const import_function& imp)
|
||||
: imp(imp) {}
|
||||
|
||||
~value() {}
|
||||
|
||||
@@ -171,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
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef FURVM_HPP
|
||||
#define FURVM_HPP
|
||||
|
||||
#include "furvm/context.hpp" // IWYU pragma: export
|
||||
#include "furvm/executor.hpp" // IWYU pragma: export
|
||||
#include "furvm/function.hpp" // IWYU pragma: export
|
||||
#include "furvm/fwd.hpp" // IWYU pragma: export
|
||||
#include "furvm/handle.hpp" // IWYU pragma: export
|
||||
#include "furvm/instruction.hpp" // IWYU pragma: export
|
||||
#include "furvm/thing.hpp" // IWYU pragma: export
|
||||
|
||||
#endif // FURVM_HPP
|
||||
+73
-20
@@ -4,6 +4,7 @@
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstdint> // IWYU pragma: export
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Furlang's virtual machine.
|
||||
@@ -17,6 +18,40 @@ namespace furvm {
|
||||
*/
|
||||
using byte = std::uint8_t;
|
||||
|
||||
/**
|
||||
* @brief An offset into bytecode.
|
||||
*/
|
||||
using bytecode_pos = std::uint64_t;
|
||||
|
||||
/**
|
||||
* @brief Handle header with reference count.
|
||||
*/
|
||||
template <typename Id>
|
||||
class refcount_header;
|
||||
|
||||
/**
|
||||
* @brief Generic handle header.
|
||||
*/
|
||||
template <typename Id>
|
||||
class generic_header;
|
||||
|
||||
/**
|
||||
* @brief Generic furvm object handle.
|
||||
*
|
||||
* @tparam Value Type of the handle's value.
|
||||
* @tparam Header Type of the handle's header.
|
||||
*/
|
||||
template <typename Value, typename Header>
|
||||
class handle;
|
||||
|
||||
/**
|
||||
* @brief Container for the handles.
|
||||
*
|
||||
* @tparam Handle Type of the container's handle.
|
||||
*/
|
||||
template <typename Handle, typename = void>
|
||||
class handle_container;
|
||||
|
||||
// constant.hpp
|
||||
|
||||
/**
|
||||
@@ -68,18 +103,24 @@ enum class function_t : std::uint8_t;
|
||||
*/
|
||||
class function;
|
||||
|
||||
/**
|
||||
* @brief An alias to a function shared pointer.
|
||||
*/
|
||||
using function_p = std::shared_ptr<function>;
|
||||
|
||||
/**
|
||||
* @brief Furvm function's index.
|
||||
*/
|
||||
using function_handle = std::uint16_t;
|
||||
using function_id = std::uint16_t;
|
||||
|
||||
/**
|
||||
* @brief A handle to a furvm function.
|
||||
*/
|
||||
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.
|
||||
@@ -94,17 +135,21 @@ class mod;
|
||||
using mod_p = std::shared_ptr<mod>;
|
||||
|
||||
/**
|
||||
* @brief Furvm module's index.
|
||||
* @brief An alias for a module's identifier.
|
||||
*/
|
||||
using module_handle = std::uint32_t;
|
||||
|
||||
// thing.hpp
|
||||
using mod_id = std::string;
|
||||
|
||||
/**
|
||||
* @enum thing_t
|
||||
* @brief Thing type.
|
||||
* @brief A handle to a furvm module.
|
||||
*/
|
||||
enum class thing_t : std::uint8_t;
|
||||
using mod_h = handle<mod, refcount_header<mod_id>>;
|
||||
|
||||
// thing_allocator.hpp
|
||||
|
||||
template <typename T>
|
||||
class thing_allocator;
|
||||
|
||||
// thing.hpp
|
||||
|
||||
/**
|
||||
* @class bad_thing_access
|
||||
@@ -112,23 +157,26 @@ enum class thing_t : std::uint8_t;
|
||||
*/
|
||||
class bad_thing_access;
|
||||
|
||||
using thing_type_id = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @class thing
|
||||
* @brief Furvm thing.
|
||||
*
|
||||
* A stack element. Think of it like of a value in C++ or I guess a class in java.
|
||||
*/
|
||||
template <template <typename> typename Allocator = thing_allocator>
|
||||
class thing;
|
||||
|
||||
/**
|
||||
* @brief An alias to a thing shared pointer.
|
||||
*/
|
||||
using thing_p = std::shared_ptr<thing>;
|
||||
|
||||
/**
|
||||
* @brief Furvm thing's index.
|
||||
*/
|
||||
using thing_handle = std::uint32_t;
|
||||
using thing_id = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief A handle to a thing.
|
||||
*/
|
||||
using thing_h = handle<thing<thing_allocator>, refcount_header<thing_id>>;
|
||||
|
||||
// executor.hpp
|
||||
|
||||
@@ -159,7 +207,12 @@ using executor_p = std::shared_ptr<executor>;
|
||||
/**
|
||||
* @brief Furvm executor's index.
|
||||
*/
|
||||
using executor_handle = std::uint32_t;
|
||||
using executor_id = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief A handle to an executor.
|
||||
*/
|
||||
using executor_h = handle<executor, refcount_header<executor_id>>;
|
||||
|
||||
// context.hpp
|
||||
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#ifndef FURVM_HANDLE_HPP
|
||||
#define FURVM_HANDLE_HPP
|
||||
|
||||
#include "furvm/detail/handle.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
// TODO: Implement generational indexes
|
||||
|
||||
template <typename Id>
|
||||
class refcount_header {
|
||||
public:
|
||||
using id_type = Id; /**< Id type. */
|
||||
using refcount_type = std::uint32_t; /**< Reference count type. */
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a reference counting header.
|
||||
*
|
||||
* @param id Identifier of the handle's value.
|
||||
* @param refCount Handle's reference count.
|
||||
* @param onRelease Callback function.
|
||||
*/
|
||||
template <typename IdFwd, typename Func>
|
||||
refcount_header(IdFwd&& id, refcount_type refCount, Func&& onRelease)
|
||||
: m_id(std::forward<IdFwd>(id)), m_refCount(refCount), m_onRelease(std::forward<Func>(onRelease)) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the header's reference count.
|
||||
*
|
||||
* @return The reference count.
|
||||
*/
|
||||
refcount_type reference_count() const { return m_refCount; }
|
||||
|
||||
/**
|
||||
* @brief Increments the header's reference count.
|
||||
*/
|
||||
void acquire() { ++m_refCount; }
|
||||
|
||||
/**
|
||||
* @brief Decrements the header's reference count.
|
||||
*
|
||||
* If the reference count reaches 0, the onRelease callback passed in the constructor will be called.
|
||||
*/
|
||||
void release() {
|
||||
--m_refCount;
|
||||
if (m_refCount == 0) m_onRelease(m_id);
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the header's identifier.
|
||||
*
|
||||
* @return The identifier.
|
||||
*/
|
||||
id_type id() const { return m_id; }
|
||||
private:
|
||||
id_type m_id;
|
||||
std::atomic<refcount_type> m_refCount;
|
||||
std::function<void(const id_type&)> m_onRelease;
|
||||
};
|
||||
|
||||
template <typename Id>
|
||||
class generic_header {
|
||||
public:
|
||||
using id_type = Id; /**< Id type. */
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a generic header.
|
||||
*
|
||||
* @param id Identifier of the handle.
|
||||
*/
|
||||
generic_header(id_type id)
|
||||
: m_id(id) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the header's identifier.
|
||||
*/
|
||||
id_type id() const { return m_id; }
|
||||
private:
|
||||
id_type m_id;
|
||||
};
|
||||
|
||||
template <typename Value, typename Header = refcount_header<std::uint32_t>>
|
||||
class handle {
|
||||
public:
|
||||
using value_type = Value; /** Value type. */
|
||||
using reference = Value&; /** Reference type. */
|
||||
using const_reference = const Value&; /** Constant reference type. */
|
||||
using pointer = Value*; /** Pointer type. */
|
||||
using const_pointer = const Value*; /** Constant pointer type. */
|
||||
public:
|
||||
using id_type = typename Header::id_type; /** Id type of the header. */
|
||||
|
||||
using header_type = Header;
|
||||
public:
|
||||
using pair_type = std::pair<Header, Value>; /** Type of a header-value pair. */
|
||||
public:
|
||||
handle() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a handle.
|
||||
*
|
||||
* @param value A pointer to the header-value pair.
|
||||
*/
|
||||
handle(pair_type* value)
|
||||
: m_value(value) {
|
||||
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||
m_value->first.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructs a handle.
|
||||
*/
|
||||
~handle() {
|
||||
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||
if (m_value != nullptr) m_value->first.release();
|
||||
}
|
||||
m_value = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
handle(handle&& other) noexcept
|
||||
: m_value(other.m_value) {
|
||||
other.m_value = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
handle& operator=(handle&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_value = other.m_value;
|
||||
other.m_value = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
handle(const handle& other)
|
||||
: m_value(other.m_value) {
|
||||
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||
m_value->first.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
handle& operator=(const handle& other) {
|
||||
if (this == &other) return *this;
|
||||
m_value = other.m_value;
|
||||
if constexpr (detail::header_has_refcount_v<Header>) {
|
||||
m_value->first.acquire();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns an identifier of the handle's header.
|
||||
*
|
||||
* @return The header's identifier.
|
||||
*/
|
||||
id_type id() const { return m_value->first.id(); }
|
||||
|
||||
/**
|
||||
* @brief Returns whether the handle is empty.
|
||||
*
|
||||
* @return true if the handle is empty.
|
||||
*/
|
||||
bool empty() const { return m_value == nullptr; }
|
||||
|
||||
/**
|
||||
* @brief Returns the handle's header reference count.
|
||||
*
|
||||
* @return The reference count.
|
||||
*/
|
||||
template <typename U = Header, typename = std::enable_if_t<detail::header_has_refcount_v<U>>>
|
||||
auto reference_count() const {
|
||||
return m_value->first.reference_count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the handle's value.
|
||||
*
|
||||
* @return The value pointer.
|
||||
*/
|
||||
pointer operator->() { return &m_value->second; }
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the handle's value.
|
||||
*
|
||||
* @return The value pointer.
|
||||
*/
|
||||
const_pointer operator->() const { return &m_value->second; }
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the handle's value.
|
||||
*
|
||||
* @return The value reference.
|
||||
*/
|
||||
reference operator*() { return m_value->second; }
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the handle's value.
|
||||
*
|
||||
* @return The value reference.
|
||||
*/
|
||||
const_reference operator*() const { return m_value->second; }
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the handle's value.
|
||||
*
|
||||
* @return The value reference.
|
||||
*/
|
||||
reference value() { return m_value->second; }
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the handle's value.
|
||||
*
|
||||
* @return The value reference.
|
||||
*/
|
||||
const_reference value() const { return m_value->second; }
|
||||
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;
|
||||
};
|
||||
|
||||
template <typename Handle>
|
||||
class handle_container<Handle, std::enable_if_t<!std::is_integral_v<typename Handle::id_type>>> {
|
||||
private:
|
||||
using pair_type = typename Handle::pair_type; /**< Handle's pair type. */
|
||||
public:
|
||||
using value_type = std::remove_cv_t<std::remove_reference_t<Handle>>; /**< Handle type. */
|
||||
using const_value = std::add_const_t<value_type>; /**< Constant handle type. */
|
||||
|
||||
using id_type = typename Handle::id_type; /**< Handle's header identifier type. */
|
||||
public:
|
||||
handle_container() = default;
|
||||
~handle_container() = default;
|
||||
|
||||
handle_container(handle_container&&) noexcept = default;
|
||||
handle_container& operator=(handle_container&&) noexcept = default;
|
||||
|
||||
handle_container(const handle_container&) = delete;
|
||||
handle_container& operator=(const handle_container&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces a new value.
|
||||
*
|
||||
* @param id Identifier of the emplaced value.
|
||||
* @param args Arguments passed to the Handle's value type constructor.
|
||||
* @return A handle to the emplaced value.
|
||||
*/
|
||||
template <typename IdFwd,
|
||||
typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<typename pair_type::second_type, Args...>>>
|
||||
value_type emplace(IdFwd&& id, Args&&... args) {
|
||||
id_type idFwd = std::forward<IdFwd>(id);
|
||||
if (auto it = m_pairs.find(idFwd); it != m_pairs.end()) delete it->second;
|
||||
auto pair = new pair_type(std::piecewise_construct,
|
||||
std::forward_as_tuple(idFwd, 0, [&](const id_type& id) { erase(id); }),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
m_pairs.emplace(std::move(idFwd), pair);
|
||||
return { pair };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the container's value.
|
||||
*
|
||||
* @param id Idenfifier of the value.
|
||||
* @return The value.
|
||||
*/
|
||||
template <typename IdFwd>
|
||||
value_type at(IdFwd&& id) {
|
||||
return { m_pairs.at(std::forward<IdFwd>(id)) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to the container's value.
|
||||
*
|
||||
* @param id Idenfifier of the value.
|
||||
* @return The value.
|
||||
*/
|
||||
template <typename IdFwd>
|
||||
const_value at(IdFwd&& id) const {
|
||||
return { m_pairs.at(std::forward<IdFwd>(id)) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a value from the container.
|
||||
*
|
||||
* @param id Identifier of the value.
|
||||
*/
|
||||
template <typename IdFwd>
|
||||
void erase(IdFwd&& id) {
|
||||
auto it = m_pairs.find(std::forward<IdFwd>(id));
|
||||
if (it == m_pairs.end()) return;
|
||||
delete it->second;
|
||||
m_pairs.erase(it);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a handle exists inside.
|
||||
*
|
||||
* @param id Identifier of the handle.
|
||||
* @return true if the handle exists insdie of this container.
|
||||
*/
|
||||
template <typename IdFwd>
|
||||
constexpr bool contains(IdFwd&& id) const {
|
||||
return m_pairs.find(std::forward<IdFwd>(id)) != m_pairs.end();
|
||||
}
|
||||
private:
|
||||
std::unordered_map<id_type, pair_type*> m_pairs;
|
||||
};
|
||||
|
||||
template <typename Handle>
|
||||
class handle_container<Handle, std::enable_if_t<std::is_integral_v<typename Handle::id_type>>> {
|
||||
private:
|
||||
using pair_type = typename Handle::pair_type; /**< Handle's pair type. */
|
||||
public:
|
||||
using value_type = std::remove_cv_t<std::remove_reference_t<Handle>>; /**< Handle type. */
|
||||
using const_value = std::add_const_t<value_type>; /**< Constant handle type. */
|
||||
|
||||
using id_type = typename Handle::id_type; /**< Handle's header identifier type. */
|
||||
public:
|
||||
handle_container() = default;
|
||||
~handle_container() = default;
|
||||
|
||||
handle_container(handle_container&&) noexcept = default;
|
||||
handle_container& operator=(handle_container&&) noexcept = default;
|
||||
|
||||
handle_container(const handle_container&) = delete;
|
||||
handle_container& operator=(const handle_container&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces a new value.
|
||||
*
|
||||
* @param id Identifier of the emplaced value.
|
||||
* @param args Arguments passed to the Handle's value type constructor.
|
||||
* @return A handle to the emplaced value.
|
||||
*/
|
||||
template <typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<typename pair_type::second_type, Args...>>>
|
||||
value_type emplace(id_type id, Args&&... args) {
|
||||
if (id >= m_pairs.size()) {
|
||||
m_pairs.resize(id + 1, nullptr);
|
||||
} else if (m_pairs[id] != nullptr) {
|
||||
delete m_pairs[id];
|
||||
}
|
||||
|
||||
pair_type* newPair = nullptr;
|
||||
if constexpr (detail::header_has_refcount_v<typename Handle::header_type>) {
|
||||
newPair = new pair_type(std::piecewise_construct,
|
||||
std::forward_as_tuple(id, 0, [&](const id_type& id) { erase(id); }),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
} else {
|
||||
newPair = new pair_type(std::piecewise_construct,
|
||||
std::forward_as_tuple(id),
|
||||
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
m_pairs[id] = newPair;
|
||||
return { newPair };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emplaces a new value.
|
||||
*
|
||||
* Emplaces a new value with an automatically-assigned identifier.
|
||||
*
|
||||
* @param args Arguments passed to the Handle's value type constructor.
|
||||
* @return A handle to the emplaced value.
|
||||
*/
|
||||
template <typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<typename Handle::pair_type::second_type, Args...>>>
|
||||
value_type emplace_back(Args&&... args) {
|
||||
return emplace(static_cast<id_type>(m_pairs.size()), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to a value.
|
||||
*
|
||||
* @param id Identifier of the value.
|
||||
* @return The value.
|
||||
*/
|
||||
value_type at(id_type id) { return { m_pairs.at(id) }; }
|
||||
|
||||
/**
|
||||
* @brief Returns a handle to a value.
|
||||
*
|
||||
* @param id Identifier of the value.
|
||||
* @return The value.
|
||||
*/
|
||||
const_value at(id_type id) const { return { m_pairs.at(id) }; }
|
||||
|
||||
/**
|
||||
* @brief Erases a value from the container.
|
||||
*
|
||||
* @param id Identifier of the value.
|
||||
*/
|
||||
void erase(id_type id) {
|
||||
if (id >= m_pairs.size()) return;
|
||||
delete m_pairs[id];
|
||||
m_pairs[id] = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a handle exists inside.
|
||||
*
|
||||
* @param id Identifier of the handle.
|
||||
* @return true if the handle exists insdie of this container.
|
||||
*/
|
||||
constexpr bool contains(id_type id) const { return id < m_pairs.size() && m_pairs[id] != nullptr; }
|
||||
public:
|
||||
auto begin() { return m_pairs.begin(); }
|
||||
auto begin() const { return m_pairs.begin(); }
|
||||
auto cbegin() const { return m_pairs.cbegin(); }
|
||||
|
||||
auto end() { return m_pairs.end(); }
|
||||
auto end() const { return m_pairs.end(); }
|
||||
auto cend() const { return m_pairs.cend(); }
|
||||
private:
|
||||
std::vector<pair_type*> m_pairs;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_HANDLE_HPP
|
||||
@@ -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,11 +76,23 @@ enum class instruction_t : byte {
|
||||
*/
|
||||
Duplicate,
|
||||
|
||||
/**
|
||||
* @brief Swaps two top elements of the stack.
|
||||
*/
|
||||
Swap,
|
||||
|
||||
/**
|
||||
* @brief Clones top element on the stack.
|
||||
*/
|
||||
Clone,
|
||||
|
||||
/**
|
||||
* @brief Pushes a new reference onto the stack.
|
||||
*
|
||||
* Pops the top thing from the stack and pushes its reference.
|
||||
*/
|
||||
Reference,
|
||||
|
||||
/**
|
||||
* @brief Adds two things together on the stack.
|
||||
*/
|
||||
@@ -65,6 +118,51 @@ enum class instruction_t : byte {
|
||||
*/
|
||||
Mod,
|
||||
|
||||
/**
|
||||
* @brief Compares two top-most things from the stack for equality.
|
||||
*/
|
||||
Equals,
|
||||
|
||||
/**
|
||||
* @brief Compares two top-most things from the stack for inequality.
|
||||
*/
|
||||
NotEquals,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is less than the second top-most thing.
|
||||
*/
|
||||
LessThan,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is greater than the second top-most thing.
|
||||
*/
|
||||
GreaterThan,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is less than or equal to the second top-most thing.
|
||||
*/
|
||||
LessEqual,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is greater than or equal to the second top-most thing.
|
||||
*/
|
||||
GreaterEqual,
|
||||
|
||||
/**
|
||||
* @brief Pushes a pointer of popped-off thing onto the stack.
|
||||
*/
|
||||
Pointerof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a size of popped-off thing onto the stack.
|
||||
*/
|
||||
Sizeof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a length of popped-off thing onto the stack.
|
||||
*/
|
||||
Lengthof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a variable onto the stack.
|
||||
*
|
||||
@@ -86,15 +184,25 @@ enum class instruction_t : byte {
|
||||
*/
|
||||
Call,
|
||||
|
||||
/**
|
||||
* @brief Jumps to an instruction relative to the current instruction.
|
||||
*
|
||||
* Jumps to an instruction relative to the current instruction with offset denoted by next byte.
|
||||
*/
|
||||
Jump,
|
||||
|
||||
/**
|
||||
* @brief Jumps to an instruction relative to the current instruction if top thing on the stack is not zero.
|
||||
*
|
||||
* Jumps to an instruction relative to the current instruction with offset denoted by next byte if the top thing on
|
||||
* the stack is not zero (is true).
|
||||
*/
|
||||
JumpNotZero,
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
|
||||
+325
-19
@@ -1,27 +1,164 @@
|
||||
#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 <functional>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
public:
|
||||
using bytecode_t = std::vector<byte>; /**< An alias to a vector of bytes. */
|
||||
|
||||
static constexpr char MAGIC[4] = { 'F', 'u', 'r', 'M' }; /** Furvm module file magic. */
|
||||
|
||||
using native_function = std::function<void(executor&)>;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new module.
|
||||
* @brief Constructs a module.
|
||||
*
|
||||
* @param id Id of this module.
|
||||
* @param args Arguments to forward to bytecode's constructor.
|
||||
* @param name Name of the module.
|
||||
* @param args Arguments forwarded to bytecode's constructor.
|
||||
*/
|
||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<bytecode_t, Args...>>>
|
||||
mod(module_handle id, Args&&... args)
|
||||
: m_id(id), m_bytecode(std::forward<Args>(args)...) {}
|
||||
mod(Args&&... args)
|
||||
: m_bytecode(std::forward<Args>(args)...) {}
|
||||
|
||||
~mod() = default;
|
||||
|
||||
@@ -38,13 +175,6 @@ public:
|
||||
mod(const mod&) = delete;
|
||||
mod& operator=(const mod&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns an id of this module.
|
||||
*
|
||||
* @return The id.
|
||||
*/
|
||||
constexpr module_handle id() const { return m_id; }
|
||||
|
||||
/**
|
||||
* @brief Returns a byte from bytecode of this module.
|
||||
*
|
||||
@@ -52,18 +182,194 @@ public:
|
||||
* @return The byte.
|
||||
*/
|
||||
constexpr byte byte(std::size_t offset) const { return m_bytecode.at(offset); }
|
||||
|
||||
/**
|
||||
* @brief Returns the module's bytecode.
|
||||
*
|
||||
* @return A reference to the bytecode.
|
||||
*/
|
||||
constexpr bytecode_t& bytecode() { return m_bytecode; }
|
||||
|
||||
/**
|
||||
* @brief Returns the module's bytecode.
|
||||
*
|
||||
* @return A constant reference to the bytecode.
|
||||
*/
|
||||
constexpr const bytecode_t& bytecode() const { return m_bytecode; }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a function from this module.
|
||||
* @brief Emplaces a function in the module's function container.
|
||||
*
|
||||
* @param id Id of the function.
|
||||
* @return The function.
|
||||
* Emplaces the function in module's function container and name to function map and public functions map.
|
||||
*
|
||||
* @param args Arguments forwarded into the container's emplace_back function.
|
||||
* @return A handle to the emplaced function.
|
||||
*/
|
||||
constexpr const function_p& function_at(function_handle id) const { return m_functions.at(id); }
|
||||
template <typename... Args>
|
||||
function_h emplace_function(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)...));
|
||||
}
|
||||
return std::move(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emplaces a function in the module's function container.
|
||||
*
|
||||
* Emplaces the function in module's function container and name to function map.
|
||||
*
|
||||
* @param 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 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 Returns a function from the module.
|
||||
*
|
||||
* @param id Identifier of the function.
|
||||
* @return A handle to the function.
|
||||
*/
|
||||
auto function_at(function_id id) { return m_functions.at(id); }
|
||||
|
||||
/**
|
||||
* @brief Returns a function from the module.
|
||||
*
|
||||
* @param id Identifier of the function.
|
||||
* @return A handle to the function.
|
||||
*/
|
||||
auto function_at(function_id id) const { return m_functions.at(id); }
|
||||
|
||||
/**
|
||||
* @brief Returns a function from the module.
|
||||
*
|
||||
* @param name Name of the function.
|
||||
* @return A handle to the function.
|
||||
*/
|
||||
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))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a function from the module's function container.
|
||||
*
|
||||
* @param id Identifier of the function.
|
||||
*/
|
||||
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) {
|
||||
m_nativeFunctions.emplace(std::forward<NameFwd>(name), std::forward<Func>(func));
|
||||
}
|
||||
|
||||
template <typename NameFwd>
|
||||
native_function get_native_function(NameFwd&& name) const {
|
||||
return m_nativeFunctions.at(std::forward<NameFwd>(name));
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces a type in the context.
|
||||
*
|
||||
* @param args Arguments forwarded to the type constructor.
|
||||
* @return The emplaced type.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto emplace_type(Args&&... 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)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a type from the context.
|
||||
*
|
||||
* @param args type's id.
|
||||
* @return A handle to the type.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto type_at(Args&&... args) {
|
||||
return m_types.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a type from the context.
|
||||
*
|
||||
* @param args type's id.
|
||||
* @return A handle to the type.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto type_at(Args&&... args) const {
|
||||
return m_types.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a type from the context.
|
||||
*
|
||||
* @param args type's id.
|
||||
*/
|
||||
template <typename... Args>
|
||||
void erase_type(Args&&... args) {
|
||||
m_types.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Prints the module in a bytecode form to an output stream.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @return The output stream.
|
||||
*/
|
||||
std::ostream& serialize(std::ostream& os) const;
|
||||
|
||||
/**
|
||||
* @brief Loads a module in a bytecode form from an input stream.
|
||||
*
|
||||
* @param is Input stream.
|
||||
* @return The loaded module.
|
||||
*/
|
||||
static mod load(std::istream& is);
|
||||
private:
|
||||
module_handle m_id;
|
||||
bytecode_t m_bytecode;
|
||||
std::vector<function_p> m_functions;
|
||||
bytecode_t m_bytecode;
|
||||
|
||||
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<mod_type_h> m_types;
|
||||
|
||||
std::unordered_map<std::string, native_function> m_nativeFunctions;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef FURVM_SERIALIZER_HPP
|
||||
#define FURVM_SERIALIZER_HPP
|
||||
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
class serializer {
|
||||
public:
|
||||
static constexpr byte MODULE_MAGIC[4] = { 'F', 'u', 'r', 'M' };
|
||||
static constexpr std::uint32_t VERSION = 0;
|
||||
public:
|
||||
static bool serialize_module(std::ostream& os, const mod_p& mod);
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_SERIALIZER_HPP
|
||||
+688
-154
@@ -1,209 +1,743 @@
|
||||
#ifndef FURVM_THING_HPP
|
||||
#define FURVM_THING_HPP
|
||||
|
||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
||||
#include "furlang/arena.hpp"
|
||||
#include "furlang/utility/hash.hpp"
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/thing_allocator.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
enum class thing_t : std::uint8_t {
|
||||
Int32,
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Returns data size of a thing.
|
||||
*
|
||||
* @param type Type of the thing.
|
||||
* @return The data size of the thing.
|
||||
*/
|
||||
std::size_t thing_type_size(thing_t type);
|
||||
namespace detail {
|
||||
|
||||
class bad_thing_access : public std::exception {
|
||||
public:
|
||||
bad_thing_access() = default;
|
||||
~bad_thing_access() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_thing_access(bad_thing_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
bad_thing_access& operator=(bad_thing_access&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_thing_access(const bad_thing_access&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
bad_thing_access& operator=(const bad_thing_access&) = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a C-style string describing the cause of the error.
|
||||
*
|
||||
* @return The cause of the error.
|
||||
*/
|
||||
const char* what() const noexcept override { return "bad thing access"; }
|
||||
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;
|
||||
private:
|
||||
/**
|
||||
* @brief A private token for the private constructor.
|
||||
*
|
||||
* Also `rzecz` in Polish translates to `thing` I think.
|
||||
*/
|
||||
struct rzecz {
|
||||
explicit rzecz() = default;
|
||||
public:
|
||||
using allocator_type = Allocator<std::byte>; /**< Allocator type. */
|
||||
public:
|
||||
union array {
|
||||
std::byte flat[];
|
||||
struct {
|
||||
std::size_t size;
|
||||
std::byte* data;
|
||||
} dynamic;
|
||||
};
|
||||
public:
|
||||
using nref_t = std::size_t; /**< Type of reference count. */
|
||||
|
||||
static constexpr thing_handle GENERATION_SIZE = 12; /**< Bit size of generation part in thing_handle. */
|
||||
public:
|
||||
/**
|
||||
* @brief Private constructor.
|
||||
* @brief Constructs a thing.
|
||||
*
|
||||
* @param id
|
||||
* @param type
|
||||
* @param context
|
||||
* @param type Thing type.
|
||||
* @param allocator Allocator for the thing's data.
|
||||
*/
|
||||
thing(rzecz, thing_handle id, thing_t type, const context_p& context);
|
||||
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);
|
||||
}
|
||||
|
||||
~thing();
|
||||
/**
|
||||
* @brief Destructs a thing.
|
||||
*/
|
||||
~thing() {
|
||||
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&&) noexcept;
|
||||
thing(thing&& other) noexcept
|
||||
: 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
thing& operator=(thing&&) noexcept;
|
||||
thing& operator=(thing&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_type = other.m_type;
|
||||
m_data = other.m_data;
|
||||
m_allocator = std::move(other.m_allocator);
|
||||
other.m_type.type = thing_type::Count;
|
||||
other.m_data = nullptr;
|
||||
other.m_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
thing(const thing&) = delete;
|
||||
thing& operator=(const thing&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Adds two things together.
|
||||
* @brief Returns a clone of the thing.
|
||||
*
|
||||
* @param lhs Left-hand-side thing.
|
||||
* @param rhs Right-hand-side thing.
|
||||
* @return Shared pointer to result thing.
|
||||
* @return A clone of this thing.
|
||||
*/
|
||||
friend thing_p operator+(const thing_p& lhs, const thing_p& rhs);
|
||||
|
||||
/**
|
||||
* @brief Subtracts two things together.
|
||||
*
|
||||
* @param lhs Left-hand-side thing.
|
||||
* @param rhs Right-hand-side thing.
|
||||
* @return Shared pointer to result thing.
|
||||
*/
|
||||
friend thing_p operator-(const thing_p& lhs, const thing_p& rhs);
|
||||
|
||||
/**
|
||||
* @brief Multiplies two things together.
|
||||
*
|
||||
* @param lhs Left-hand-side thing.
|
||||
* @param rhs Right-hand-side thing.
|
||||
* @return Shared pointer to result thing.
|
||||
*/
|
||||
friend thing_p operator*(const thing_p& lhs, const thing_p& rhs);
|
||||
|
||||
/**
|
||||
* @brief Divides two things together.
|
||||
*
|
||||
* @param lhs Left-hand-side thing.
|
||||
* @param rhs Right-hand-side thing.
|
||||
* @return Shared pointer to result thing.
|
||||
*/
|
||||
friend thing_p operator/(const thing_p& lhs, const thing_p& rhs);
|
||||
|
||||
/**
|
||||
* @brief Modulos two things together.
|
||||
*
|
||||
* @param lhs Left-hand-side thing.
|
||||
* @param rhs Right-hand-side thing.
|
||||
* @return Shared pointer to result thing.
|
||||
*/
|
||||
friend thing_p operator%(const thing_p& lhs, const thing_p& rhs);
|
||||
thing clone() const {
|
||||
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;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a new thing.
|
||||
* @brief Returns the thing's type.
|
||||
*
|
||||
* @param context Furvm context.
|
||||
* @param args Arguments to forward to the thing constructor.
|
||||
* @return Shared pointer to the new thing.
|
||||
* @return The type.
|
||||
*/
|
||||
template <typename... Args,
|
||||
typename = std::enable_if_t<std::is_constructible_v<thing, rzecz, thing_handle, Args..., const context_p&>>>
|
||||
static thing_p create(const context_p& context, Args&&... args) {
|
||||
thing_handle id = context->m_things.size();
|
||||
if (!context->m_deadThings.empty()) {
|
||||
id = context->m_deadThings.front();
|
||||
context->m_deadThings.pop();
|
||||
id += 1 << GENERATION_SIZE;
|
||||
}
|
||||
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
|
||||
constexpr thing_type type() const { return m_type; }
|
||||
|
||||
auto th = std::make_shared<thing>(rzecz{}, id, std::forward<Args>(args)..., context);
|
||||
context->m_things.emplace(context->m_things.begin() + idx, th);
|
||||
return std::move(th);
|
||||
/**
|
||||
* @brief Returns the thing's true type.
|
||||
*
|
||||
* If the thing is a reference, returns the referenced type.
|
||||
*
|
||||
* @return The true 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.
|
||||
*
|
||||
* @return The data pointer.
|
||||
*/
|
||||
void* raw() { return m_data; }
|
||||
|
||||
/**
|
||||
* @brief Returns a raw data pointer.
|
||||
*
|
||||
* @return The data pointer.
|
||||
*/
|
||||
const void* raw() const { return m_data; }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns the thing's value.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
template <typename T>
|
||||
T& get() {
|
||||
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
|
||||
return *std::launder(reinterpret_cast<T*>(m_data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a clone of the thing.
|
||||
*
|
||||
* @param thing Thing to clone.
|
||||
* @return Shared pointer to a clone of the thing.
|
||||
*/
|
||||
static thing_p clone(const thing_p& thing);
|
||||
public:
|
||||
/**
|
||||
* @brief Returns an int32 value from this thing.
|
||||
* @brief Returns the thing's value.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
std::int32_t& int32();
|
||||
|
||||
/**
|
||||
* @brief Returns an int32 value from this thing.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
const std::int32_t& int32() const;
|
||||
template <typename T>
|
||||
const T& get() const {
|
||||
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
|
||||
return *std::launder(reinterpret_cast<const T*>(m_data));
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Increments reference count of this thing by one.
|
||||
*/
|
||||
void add_reference() { ++m_refCount; }
|
||||
|
||||
/**
|
||||
* @brief Decrements reference count of this thing by one.
|
||||
*/
|
||||
void remove_reference() { --m_refCount; }
|
||||
|
||||
/**
|
||||
* @brief Returns reference count of this thing.
|
||||
* @brief Returns a sum of two things.
|
||||
*
|
||||
* @return The reference count.
|
||||
* @param rhs Thing to sum with this thing (right-hand-side).
|
||||
* @return The sum.
|
||||
*/
|
||||
constexpr nref_t reference_count() const { return m_refCount; }
|
||||
thing add(const thing& rhs) const { return binary_op(rhs, std::plus<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Returns a difference of two things.
|
||||
*
|
||||
* @param rhs Thing to subtract from this thing (right-hand-side).
|
||||
* @return The sum.
|
||||
*/
|
||||
thing sub(const thing& rhs) const { return binary_op(rhs, std::minus<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Returns a product of two things.
|
||||
*
|
||||
* @param rhs Thing to multiply with this thing (right-hand-side).
|
||||
* @return The product.
|
||||
*/
|
||||
thing mul(const thing& rhs) const { return binary_op(rhs, std::multiplies<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Returns a quotient of two things.
|
||||
*
|
||||
* @param rhs Thing to divide this thing by (right-hand-side).
|
||||
* @return The quotient.
|
||||
*/
|
||||
thing div(const thing& rhs) const { return binary_op(rhs, std::divides<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Returns a remainder of two things.
|
||||
*
|
||||
* @param rhs Thing to divide this thing by (right-hand-side).
|
||||
* @return The remainder.
|
||||
*/
|
||||
thing mod(const thing& rhs) const { return binary_op(rhs, std::modulus<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares two things for equality.
|
||||
*
|
||||
* @param rhs Thing to compare this thing with (right-hand-side).
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing equals(const thing& rhs) const { return binary_op(rhs, std::equal_to<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares two things for inequality.
|
||||
*
|
||||
* @param rhs Thing to compare this thing with (right-hand-side).
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing not_equals(const thing& rhs) const { return binary_op(rhs, std::not_equal_to<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares if this thing is less than an another thing.
|
||||
*
|
||||
* @param rhs The another thing.
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing less_than(const thing& rhs) const { return binary_op(rhs, std::less<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares if this thing is greater than an another thing.
|
||||
*
|
||||
* @param rhs The another thing.
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing greater_than(const thing& rhs) const { return binary_op(rhs, std::greater<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares if this thing is less than or equal to an another thing.
|
||||
*
|
||||
* @param rhs The another thing.
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing less_equals(const thing& rhs) const { return binary_op(rhs, std::less_equal<>{}); }
|
||||
|
||||
/**
|
||||
* @brief Compares if this thing is greater than or equal to an another thing.
|
||||
*
|
||||
* @param rhs The another thing.
|
||||
* @return A boolean result of the comparison in a thing form.
|
||||
*/
|
||||
thing greater_equals(const thing& rhs) const { return binary_op(rhs, std::greater_equal<>{}); }
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a largest integer value of the thing.
|
||||
*
|
||||
* @return The integer value.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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,
|
||||
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(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;
|
||||
}
|
||||
|
||||
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:
|
||||
thing_handle m_id;
|
||||
thing_t m_type;
|
||||
context_p m_context;
|
||||
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");
|
||||
|
||||
nref_t m_refCount = 0;
|
||||
void* m_data = nullptr;
|
||||
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;
|
||||
}
|
||||
|
||||
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.value.array.type,
|
||||
*std::launder(reinterpret_cast<array*>(data + (i * elementSize))),
|
||||
*std::launder(reinterpret_cast<const array*>(srcData + (i * elementSize))));
|
||||
}
|
||||
return;
|
||||
case thing_type::Count: break;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
private:
|
||||
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 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 {
|
||||
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,
|
||||
};
|
||||
|
||||
enum thing_type::type resultType = promotions[true_type().type + (rhs.true_type().type * 8)];
|
||||
|
||||
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;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
throw std::runtime_error("unexpected operation");
|
||||
}
|
||||
private:
|
||||
thing_type m_type;
|
||||
std::size_t m_size;
|
||||
std::byte* m_data;
|
||||
|
||||
allocator_type m_allocator;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#ifndef FURVM_THING_ALLOCATOR_HPP
|
||||
#define FURVM_THING_ALLOCATOR_HPP
|
||||
|
||||
#include "furlang/arena.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
template <typename T>
|
||||
class thing_allocator {
|
||||
template <typename>
|
||||
friend class thing_allocator;
|
||||
|
||||
using dead_things = std::vector<std::pair<T*, std::size_t>>;
|
||||
public:
|
||||
using value_type = T; /**< Value type. */
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a thing allocator.
|
||||
*
|
||||
* @param arena Base arena allocator.
|
||||
*/
|
||||
explicit thing_allocator(furlang::arena& arena) noexcept
|
||||
: m_arena(&arena), m_deadThings(std::make_shared<dead_things>()) {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator(thing_allocator<U>&& other) noexcept
|
||||
: m_arena(std::move(other.m_arena)), m_deadThings(std::move(other.m_deadThings)) {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator& operator=(thing_allocator<U>&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = std::move(other.m_arena);
|
||||
m_deadThings = std::move(other.m_deadThings);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator(const thing_allocator<U>& other) noexcept
|
||||
: m_arena(other.m_arena), m_deadThings(other.m_deadThings) {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator& operator=(const thing_allocator<U>& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = other.m_arena;
|
||||
m_deadThings = other.m_deadThings;
|
||||
return *this;
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a free chunk of memory.
|
||||
*
|
||||
* @param count Count of the things that must fit inside the chunk.
|
||||
* @return The chunk.
|
||||
*/
|
||||
[[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 data;
|
||||
}
|
||||
return m_arena->allocate<T>(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recycles the pointer.
|
||||
*/
|
||||
void deallocate(T* ptr, std::size_t count) noexcept { m_deadThings->emplace_back(ptr, count); }
|
||||
public:
|
||||
/**
|
||||
* @brief Compares two thing allocators for equality.
|
||||
*
|
||||
* @return true if the two things are equal.
|
||||
*/
|
||||
template <typename U>
|
||||
bool operator==(const thing_allocator<U>& other) const noexcept {
|
||||
return m_arena == other.m_arena && m_deadThings == other.m_deadThings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two thing allocators for inequality.
|
||||
*
|
||||
* @return true if the two things are not equal.
|
||||
*/
|
||||
template <typename U>
|
||||
bool operator!=(const thing_allocator<U>& other) const noexcept {
|
||||
return m_arena != other.m_arena || m_deadThings != other.m_deadThings;
|
||||
}
|
||||
private:
|
||||
furlang::arena* m_arena;
|
||||
|
||||
std::shared_ptr<dead_things> m_deadThings;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_THING_ALLOCATOR_HPP
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "furvm/context.hpp"
|
||||
|
||||
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||
|
||||
namespace furvm {
|
||||
|
||||
context::context() {}
|
||||
|
||||
void context::collect() {
|
||||
for (auto& ref : m_things) {
|
||||
if (ref->reference_count() != 0) continue;
|
||||
ref = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "furvm/detail/serialization.hpp"
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
namespace furvm::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T, std::size_t... I>
|
||||
void serialize_integral_impl(std::ostream& os, std::make_unsigned_t<T> u_value, std::index_sequence<I...>) {
|
||||
((os << static_cast<char>((u_value >> (I * 8)) & 0xFF)), ...);
|
||||
}
|
||||
|
||||
template <typename T, std::size_t... I>
|
||||
void load_integral_impl(const char* bs, std::make_unsigned_t<T>& u_value, std::index_sequence<I...>) {
|
||||
using UnsignedT = std::make_unsigned_t<T>;
|
||||
((u_value |= (static_cast<UnsignedT>(static_cast<std::uint8_t>(bs[I])) << (I * 8))), ...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& serialize_integral(std::ostream& os, T value) {
|
||||
static_assert(std::is_integral_v<T>, "Type must be an integral type.");
|
||||
using UnsignedT = std::make_unsigned_t<T>;
|
||||
|
||||
serialize_integral_impl<T>(os, static_cast<UnsignedT>(value), std::make_index_sequence<sizeof(T)>{});
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::istream& load_integral(std::istream& is, T& value) {
|
||||
static_assert(std::is_integral_v<T>, "Type must be an integral type.");
|
||||
|
||||
char bs[sizeof(T)];
|
||||
if (!is.read(bs, sizeof(T))) {
|
||||
return is;
|
||||
}
|
||||
|
||||
using UnsignedT = std::make_unsigned_t<T>;
|
||||
UnsignedT uVal = 0;
|
||||
|
||||
load_integral_impl<T>(bs, uVal, std::make_index_sequence<sizeof(T)>{});
|
||||
|
||||
value = static_cast<T>(uVal);
|
||||
return is;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::int8_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::int16_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::int32_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::int64_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::uint8_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::uint16_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::uint32_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, std::uint64_t value) {
|
||||
return serialize_integral(os, value);
|
||||
}
|
||||
|
||||
std::ostream& serialize(std::ostream& os, const std::string& value) {
|
||||
return serialize(os, static_cast<std::uint16_t>(value.size()))
|
||||
.write(value.data(), static_cast<std::streamsize>(value.size()));
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::int8_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::int16_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::int32_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::int64_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::uint8_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::uint16_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::uint32_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::uint64_t& value) {
|
||||
return load_integral(is, value);
|
||||
}
|
||||
|
||||
std::istream& load(std::istream& is, std::string& value) {
|
||||
std::uint16_t length = 0;
|
||||
load(is, length);
|
||||
|
||||
value.resize(length);
|
||||
return is.read(value.data(), static_cast<std::streamsize>(length));
|
||||
}
|
||||
|
||||
} // namespace furvm::detail
|
||||
+233
-68
@@ -7,33 +7,85 @@
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
executor::executor(egzekutor, executor_handle id, const context_p& context)
|
||||
: m_id(id), m_context(context) {}
|
||||
|
||||
executor::~executor() {
|
||||
m_context->m_executors[m_id] = nullptr;
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
executor_p executor::create(const context_p& context) {
|
||||
auto ex = std::make_shared<executor>(egzekutor{}, context->m_executors.size(), context);
|
||||
context->m_executors.push_back(ex);
|
||||
return std::move(ex);
|
||||
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 function_p& function) {
|
||||
if (function->type() != function_t::Normal) return;
|
||||
m_frames.emplace((struct executor::frame){ function->mod(), function->position(), m_stack.size() });
|
||||
void executor::push_frame(const mod_h& mod, function function) {
|
||||
mod_h modInst = mod;
|
||||
while (function.type() == function_t::Import) {
|
||||
modInst = m_context->at(function.imp().mod);
|
||||
function = *modInst->function_at(function.imp().function);
|
||||
}
|
||||
|
||||
auto signature = function.signature();
|
||||
std::vector<thing_h> args;
|
||||
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(), returnType, std::move(args) });
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), returnType, std::move(args) });
|
||||
modInst->get_native_function(function.native())(*this);
|
||||
pop_frame();
|
||||
} break;
|
||||
default: throw std::runtime_error("unexpected function type");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -41,52 +93,37 @@ struct executor::frame executor::frame() const {
|
||||
return m_frames.top();
|
||||
}
|
||||
|
||||
void executor::push_thing(const thing_p& thing) {
|
||||
thing->add_reference();
|
||||
m_stack.push(thing);
|
||||
thing_h executor::push_thing(::furvm::thing<>&& thing) {
|
||||
return m_stack.emplace(m_context->emplace_thing(std::move(thing)));
|
||||
}
|
||||
|
||||
void executor::push_thing(thing_p&& thing) {
|
||||
thing->add_reference();
|
||||
m_stack.push(std::move(thing));
|
||||
}
|
||||
|
||||
thing_p executor::pop_thing() {
|
||||
thing_h executor::pop_thing() {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
thing_p top = std::move(m_stack.top());
|
||||
thing_h top = std::move(m_stack.top());
|
||||
m_stack.pop();
|
||||
top->remove_reference();
|
||||
return top;
|
||||
return std::move(top);
|
||||
}
|
||||
|
||||
thing_p executor::thing() const {
|
||||
thing_h executor::thing() const {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
return m_stack.top();
|
||||
}
|
||||
|
||||
void executor::store_thing(variable_t variable, const thing_p& thing) {
|
||||
void executor::store_thing(variable_t variable, const thing_h& thing) {
|
||||
auto& frame = m_frames.top();
|
||||
frame.variables.resize(variable + 1);
|
||||
if (frame.variables[variable] != nullptr) {
|
||||
frame.variables[variable]->remove_reference();
|
||||
}
|
||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||
frame.variables[variable] = thing;
|
||||
thing->add_reference();
|
||||
}
|
||||
|
||||
void executor::store_thing(variable_t variable, thing_p&& thing) {
|
||||
void executor::store_thing(variable_t variable, thing_h&& thing) {
|
||||
auto& frame = m_frames.top();
|
||||
frame.variables.resize(variable + 1);
|
||||
if (frame.variables[variable] != nullptr) {
|
||||
frame.variables[variable]->remove_reference();
|
||||
}
|
||||
thing->add_reference();
|
||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||
frame.variables[variable] = std::move(thing);
|
||||
}
|
||||
|
||||
thing_p executor::load_thing(variable_t variable) const {
|
||||
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() {
|
||||
@@ -94,13 +131,66 @@ void executor::step() {
|
||||
|
||||
struct frame& frame = m_frames.top();
|
||||
|
||||
instruction_t instr = static_cast<instruction_t>(frame.mod->byte(frame.position++));
|
||||
instruction_t instr = static_cast<instruction_t>((*frame.mod).byte(frame.position++));
|
||||
switch (instr) {
|
||||
case instruction_t::NoOperation: break;
|
||||
case instruction_t::PushB2I: {
|
||||
auto thing = thing::create(m_context, thing_t::Int32);
|
||||
thing->int32() = frame.mod->byte(frame.position++);
|
||||
push_thing(std::move(thing));
|
||||
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();
|
||||
@@ -108,68 +198,143 @@ 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(thing())));
|
||||
push_thing(std::move(thing()->clone()));
|
||||
} break;
|
||||
case instruction_t::Reference: {
|
||||
auto thing = pop_thing();
|
||||
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();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs + rhs);
|
||||
push_thing(lhs->add(*rhs));
|
||||
} break;
|
||||
case instruction_t::Sub: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs - rhs);
|
||||
push_thing(lhs->sub(*rhs));
|
||||
} break;
|
||||
case instruction_t::Mul: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs * rhs);
|
||||
push_thing(lhs->mul(*rhs));
|
||||
} break;
|
||||
case instruction_t::Div: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs / rhs);
|
||||
push_thing(lhs->div(*rhs));
|
||||
} break;
|
||||
case instruction_t::Mod: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs % rhs);
|
||||
push_thing(lhs->mod(*rhs));
|
||||
} break;
|
||||
case instruction_t::Equals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::NotEquals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->not_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::LessThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->less_than(*rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->greater_than(*rhs));
|
||||
} break;
|
||||
case instruction_t::LessEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->less_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->greater_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::Pointerof: {
|
||||
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;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
} break;
|
||||
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)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
push_thing(load_thing(variable));
|
||||
} break;
|
||||
case instruction_t::Store: {
|
||||
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
store_thing(variable, std::move(pop_thing()));
|
||||
} break;
|
||||
case instruction_t::Call: {
|
||||
function_handle funcId = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
|
||||
const function_p& function = frame.mod->function_at(funcId);
|
||||
switch (function->type()) {
|
||||
case function_t::Normal: push_frame(function); break;
|
||||
case function_t::Native: function->native()(*this); break;
|
||||
case function_t::Import: {
|
||||
const mod_p& impMod = m_context->m_modules.at(function->imported_module());
|
||||
push_frame(impMod->function_at(function->imported_function()));
|
||||
} break;
|
||||
}
|
||||
function_id funcId = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
push_frame(frame.mod, *frame.mod->function_at(funcId));
|
||||
} break;
|
||||
case instruction_t::Jump: {
|
||||
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->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");
|
||||
}
|
||||
|
||||
+74
-8
@@ -1,15 +1,29 @@
|
||||
#include "furvm/function.hpp"
|
||||
|
||||
#include "furvm/furvm.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
function::function(funkcja, function_handle id, std::size_t position, const mod_p& mod)
|
||||
: m_id(id), m_type(function_t::Normal), m_module(mod), m_value(position) {}
|
||||
function::function(const mod_h& mod, const function_h& function)
|
||||
: m_type(function_t::Import), m_value(import_function{ mod.id(), function.id() }) {}
|
||||
|
||||
function::function(funkcja, function_handle id, const native_function& native, const mod_p& mod)
|
||||
: m_id(id), m_type(function_t::Native), m_module(mod), m_value(native) {}
|
||||
function::~function() {
|
||||
switch (m_type) {
|
||||
case function_t::Normal:
|
||||
default: break;
|
||||
case function_t::Native: {
|
||||
m_value.native.~native_function();
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
m_value.imp.~import_function();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
function::function(function&& other) noexcept
|
||||
: m_id(other.m_id), m_type(other.m_type), m_module(std::move(other.m_module)) {
|
||||
: 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;
|
||||
@@ -17,6 +31,10 @@ function::function(function&& other) noexcept
|
||||
case function_t::Native: {
|
||||
new (&m_value.native) native_function(std::move(other.m_value.native));
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
new (&m_value.imp) import_function(std::move(other.m_value.imp));
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
other.m_value.position = 0;
|
||||
}
|
||||
@@ -24,9 +42,8 @@ function::function(function&& other) noexcept
|
||||
function& function::operator=(function&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
|
||||
m_id = other.m_id;
|
||||
m_type = other.m_type;
|
||||
m_module = std::move(other.m_module);
|
||||
m_type = other.m_type;
|
||||
m_signature = other.m_signature;
|
||||
switch (m_type) {
|
||||
case function_t::Normal: {
|
||||
m_value.position = other.m_value.position;
|
||||
@@ -34,10 +51,59 @@ function& function::operator=(function&& other) noexcept {
|
||||
case function_t::Native: {
|
||||
new (&m_value.native) native_function(std::move(other.m_value.native));
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
new (&m_value.imp) import_function(std::move(other.m_value.imp));
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
other.m_value.position = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
function::function(const function& other)
|
||||
: m_type(other.m_type), m_signature(other.m_signature) {
|
||||
switch (m_type) {
|
||||
case function_t::Normal: {
|
||||
m_value.position = other.m_value.position;
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
new (&m_value.native) native_function(other.m_value.native);
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
new (&m_value.imp) import_function(other.m_value.imp);
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
function& function::operator=(const function& other) {
|
||||
if (this == &other) return *this;
|
||||
|
||||
m_type = other.m_type;
|
||||
m_signature = other.m_signature;
|
||||
switch (m_type) {
|
||||
case function_t::Normal: {
|
||||
m_value.position = other.m_value.position;
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
new (&m_value.native) native_function(other.m_value.native);
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
new (&m_value.imp) import_function(other.m_value.imp);
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
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
|
||||
+68
-23
@@ -1,45 +1,90 @@
|
||||
#ifndef LIBFURVM
|
||||
|
||||
#include "furvm/context.hpp"
|
||||
#include "furvm/executor.hpp"
|
||||
#include "furvm/function.hpp"
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/serializer.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
#include "furvm/detail/serialization.hpp"
|
||||
#include "furvm/furvm.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
static constexpr std::array<furvm::byte, 8> s_bytecode = {
|
||||
furvm::byte(furvm::instruction_t::PushB2I),
|
||||
67,
|
||||
furvm::byte(furvm::instruction_t::Clone),
|
||||
furvm::byte(furvm::instruction_t::Add),
|
||||
furvm::byte(furvm::instruction_t::Return),
|
||||
};
|
||||
static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
|
||||
using namespace furvm;
|
||||
|
||||
int main(void) {
|
||||
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>();
|
||||
|
||||
auto mainModule = context->emplace(s_bytecode.begin(), s_bytecode.end());
|
||||
#if 1 // NOLINT
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <mod.fmod>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto mainFunction = furvm::function::create(mainModule, 0);
|
||||
std::ifstream file(argv[1]);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open " << argv[1] << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream file("./test.furm", std::ios::binary);
|
||||
furvm::serializer::serialize_module(file, mainModule);
|
||||
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),
|
||||
};
|
||||
|
||||
auto executor = furvm::executor::create(context);
|
||||
executor->push_frame(mainFunction);
|
||||
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';
|
||||
});
|
||||
|
||||
static constexpr std::size_t FPC = 3; // Frames per collection
|
||||
furvm::executor_h executor = context->emplace_executor(context);
|
||||
executor->push_frame(mod, *mainFunc);
|
||||
|
||||
std::size_t count = 0;
|
||||
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
|
||||
executor->step();
|
||||
if ((++count % FPC) == 0) context->collect();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
+201
-1
@@ -1,3 +1,203 @@
|
||||
#include "furvm/module.hpp"
|
||||
|
||||
namespace furvm {}
|
||||
#include "furvm/detail/serialization.hpp"
|
||||
#include "furvm/function.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ios>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
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) {
|
||||
if (!m_functions.contains(id)) {
|
||||
detail::serialize(os, "");
|
||||
detail::serialize(os, 0);
|
||||
detail::serialize(os, std::uint8_t(0xFF)); // null function
|
||||
continue;
|
||||
}
|
||||
|
||||
// Function name
|
||||
function_h func = m_functions.at(id);
|
||||
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: {
|
||||
detail::serialize(os, func->position());
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
detail::serialize(os, func->native());
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
detail::serialize(os, func->imp().mod);
|
||||
detail::serialize(os, func->imp().function);
|
||||
} 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()));
|
||||
}
|
||||
|
||||
mod mod::load(std::istream& is) {
|
||||
std::uint32_t magic = 0;
|
||||
detail::load(is, magic);
|
||||
std::uint32_t version = 0;
|
||||
detail::load(is, version);
|
||||
if (std::memcmp(&magic, MAGIC, sizeof(MAGIC)) != 0) throw std::runtime_error("invalid magic");
|
||||
if (version != 0) throw std::runtime_error("unsupported version");
|
||||
|
||||
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;
|
||||
|
||||
switch ((function_t)type) {
|
||||
case function_t::Normal: {
|
||||
decltype(std::declval<function>().position()) offset = 0;
|
||||
detail::load(is, offset);
|
||||
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);
|
||||
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;
|
||||
detail::load(is, modName);
|
||||
function_id function = 0;
|
||||
detail::load(is, function);
|
||||
mod.emplace_function(id, std::move(modName), function).dispatch();
|
||||
} break;
|
||||
default: throw std::runtime_error("unknown function type");
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t bytecodeLength = 0;
|
||||
detail::load(is, bytecodeLength);
|
||||
mod.bytecode().resize(bytecodeLength);
|
||||
is.read(reinterpret_cast<char*>(mod.bytecode().data()), static_cast<std::streamsize>(bytecodeLength));
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#include "furvm/serializer.hpp"
|
||||
|
||||
#include "furvm/function.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <istream>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
template <typename T>
|
||||
static inline void write_raw(std::ostream& os, const T& value) {
|
||||
os.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void read_raw(std::istream& is, T& value) {
|
||||
is.read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
static inline void write_u64(std::ostream& os, std::uint64_t value) {
|
||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 16ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 24ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 32ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 40ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 48ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 56ULL) & 0xFF));
|
||||
}
|
||||
|
||||
static inline void read_u64(std::istream& is, std::uint64_t& value) {
|
||||
value = (static_cast<std::uint64_t>(is.get()) << 0) | (static_cast<std::uint64_t>(is.get()) << 8) |
|
||||
(static_cast<std::uint64_t>(is.get()) << 16) | (static_cast<std::uint64_t>(is.get()) << 24) |
|
||||
(static_cast<std::uint64_t>(is.get()) << 32) | (static_cast<std::uint64_t>(is.get()) << 40) |
|
||||
(static_cast<std::uint64_t>(is.get()) << 48) | (static_cast<std::uint64_t>(is.get()) << 56);
|
||||
}
|
||||
|
||||
static inline void write_u32(std::ostream& os, std::uint32_t value) {
|
||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 16ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 24ULL) & 0xFF));
|
||||
}
|
||||
|
||||
static inline void read_u32(std::istream& is, std::uint32_t& value) {
|
||||
value = static_cast<std::uint32_t>(is.get() << 0) | static_cast<std::uint32_t>(is.get() << 8) |
|
||||
static_cast<std::uint32_t>(is.get() << 16) | static_cast<std::uint32_t>(is.get() << 24);
|
||||
}
|
||||
|
||||
static inline void write_u16(std::ostream& os, std::uint16_t value) {
|
||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
||||
os.put(static_cast<char>((value >> 8ULL) & 0xFF));
|
||||
}
|
||||
|
||||
static inline void read_u16(std::istream& is, std::uint16_t& value) {
|
||||
value = static_cast<std::uint16_t>(is.get() << 0) | static_cast<std::uint16_t>(is.get() << 8);
|
||||
}
|
||||
|
||||
static inline void write_u8(std::ostream& os, std::uint8_t value) {
|
||||
os.put(static_cast<char>((value >> 0ULL) & 0xFF));
|
||||
}
|
||||
|
||||
static inline void read_u8(std::istream& is, std::uint8_t& value) {
|
||||
value = static_cast<std::uint8_t>(is.get() << 0);
|
||||
}
|
||||
|
||||
bool serializer::serialize_module(std::ostream& os, const mod_p& mod) {
|
||||
os.write(reinterpret_cast<const char*>(MODULE_MAGIC), sizeof(MODULE_MAGIC));
|
||||
write_u32(os, VERSION);
|
||||
|
||||
write_u32(os, mod->m_functions.size());
|
||||
for (const auto& func : mod->m_functions) {
|
||||
write_u16(os, func->id());
|
||||
write_u8(os, static_cast<std::uint8_t>(func->type()));
|
||||
switch (func->type()) {
|
||||
case function_t::Normal: {
|
||||
write_u64(os, func->position());
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
// TODO: Replace with a reference to the function's name from constant pool
|
||||
throw std::runtime_error("cannot serialize native functions yet");
|
||||
} break;
|
||||
case function_t::Import: {
|
||||
// TODO: Replace with a reference to the module's and function's name from constant pool
|
||||
throw std::runtime_error("cannot serialize import functions yet");
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
write_u64(os, mod->m_bytecode.size());
|
||||
os.write(reinterpret_cast<const char*>(mod->m_bytecode.data()),
|
||||
static_cast<std::streamsize>(mod->m_bytecode.size()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
@@ -1,177 +0,0 @@
|
||||
// NOLINTBEGIN(cppcoreguidelines-no-malloc)
|
||||
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
std::size_t thing_type_size(thing_t type) {
|
||||
switch (type) {
|
||||
case thing_t::Int32: return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
thing::thing(rzecz, thing_handle id, thing_t type, const context_p& context)
|
||||
: m_id(id), m_type(type), m_context(context) {
|
||||
std::size_t size = thing_type_size(type);
|
||||
std::byte* data = nullptr;
|
||||
if (!m_context->m_deadThingData.empty()) {
|
||||
thing_t itType{};
|
||||
for (auto it = m_context->m_deadThingData.rbegin(); it != m_context->m_deadThingData.rend(); ++it) {
|
||||
std::memcpy(&itType, static_cast<std::byte*>(*it) - sizeof(itType), sizeof(itType));
|
||||
if (size == thing_type_size(itType)) {
|
||||
data = static_cast<std::byte*>(*it);
|
||||
m_context->m_deadThingData.erase(std::next(it).base());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data == nullptr) data = m_context->m_thingArena.allocate<std::byte>(sizeof(type) + size);
|
||||
|
||||
if (data == nullptr) throw std::runtime_error("failed to allocate data for new thing");
|
||||
std::memcpy(data, &type, sizeof(type));
|
||||
m_data = data + sizeof(type);
|
||||
|
||||
switch (m_type) {
|
||||
// Primitives are zero-initialized.
|
||||
default:
|
||||
case thing_t::Int32: std::memset(m_data, 0, size);
|
||||
}
|
||||
}
|
||||
|
||||
thing::~thing() {
|
||||
switch (m_type) {
|
||||
// Primitives are not destructed.
|
||||
default:
|
||||
case thing_t::Int32: break;
|
||||
}
|
||||
|
||||
m_context->m_deadThingData.push_back(m_data);
|
||||
}
|
||||
|
||||
thing::thing(thing&& other) noexcept
|
||||
: m_id(other.m_id),
|
||||
m_type(other.m_type),
|
||||
m_context(std::move(other.m_context)),
|
||||
m_refCount(other.m_refCount),
|
||||
m_data(other.m_data) {
|
||||
other.m_id = {};
|
||||
other.m_type = {};
|
||||
other.m_refCount = {};
|
||||
other.m_data = nullptr;
|
||||
}
|
||||
|
||||
thing& thing::operator=(thing&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_id = other.m_id;
|
||||
m_type = other.m_type;
|
||||
m_context = std::move(other.m_context);
|
||||
m_refCount = other.m_refCount;
|
||||
m_data = other.m_data;
|
||||
|
||||
other.m_id = {};
|
||||
other.m_type = {};
|
||||
other.m_refCount = {};
|
||||
other.m_data = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static constexpr std::uint16_t thing_type_pair(thing_t lhs, thing_t rhs) {
|
||||
return (static_cast<std::uint16_t>(lhs) << 8) | static_cast<std::uint16_t>(rhs);
|
||||
}
|
||||
|
||||
thing_p operator+(const thing_p& lhs, const thing_p& rhs) {
|
||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
||||
res->int32() = lhs->int32() + rhs->int32();
|
||||
return res;
|
||||
}
|
||||
default: throw std::runtime_error("unexpected operator");
|
||||
}
|
||||
}
|
||||
|
||||
thing_p operator-(const thing_p& lhs, const thing_p& rhs) {
|
||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
||||
res->int32() = lhs->int32() - rhs->int32();
|
||||
return res;
|
||||
}
|
||||
default: throw std::runtime_error("unexpected operator");
|
||||
}
|
||||
}
|
||||
|
||||
thing_p operator*(const thing_p& lhs, const thing_p& rhs) {
|
||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
||||
res->int32() = lhs->int32() * rhs->int32();
|
||||
return res;
|
||||
}
|
||||
default: throw std::runtime_error("unexpected operator");
|
||||
}
|
||||
}
|
||||
|
||||
thing_p operator/(const thing_p& lhs, const thing_p& rhs) {
|
||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
||||
res->int32() = lhs->int32() / rhs->int32();
|
||||
return res;
|
||||
}
|
||||
default: throw std::runtime_error("unexpected operator");
|
||||
}
|
||||
}
|
||||
|
||||
thing_p operator%(const thing_p& lhs, const thing_p& rhs) {
|
||||
switch (thing_type_pair(lhs->m_type, rhs->m_type)) {
|
||||
case thing_type_pair(thing_t::Int32, thing_t::Int32): {
|
||||
auto res = thing::create(lhs->m_context, thing_t::Int32);
|
||||
res->int32() = lhs->int32() % rhs->int32();
|
||||
return res;
|
||||
}
|
||||
default: throw std::runtime_error("unexpected operator");
|
||||
}
|
||||
}
|
||||
|
||||
thing_p thing::clone(const thing_p& thing) {
|
||||
thing_handle id = thing->m_context->m_things.size();
|
||||
if (!thing->m_context->m_deadThings.empty()) {
|
||||
id = thing->m_context->m_deadThings.front();
|
||||
thing->m_context->m_deadThings.pop();
|
||||
id += 1 << GENERATION_SIZE;
|
||||
}
|
||||
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
|
||||
|
||||
auto th = std::make_shared<class thing>(rzecz{}, id, thing->m_type, thing->m_context);
|
||||
switch (thing->m_type) {
|
||||
// Primitives
|
||||
default: {
|
||||
memcpy(th->m_data, thing->m_data, thing_type_size(thing->m_type));
|
||||
}
|
||||
}
|
||||
|
||||
thing->m_context->m_things.emplace(thing->m_context->m_things.begin() + idx, th);
|
||||
return std::move(th);
|
||||
}
|
||||
|
||||
std::int32_t& thing::int32() {
|
||||
if (m_type != thing_t::Int32) throw bad_thing_access();
|
||||
return *static_cast<std::int32_t*>(m_data);
|
||||
}
|
||||
|
||||
const std::int32_t& thing::int32() const {
|
||||
if (m_type != thing_t::Int32) throw bad_thing_access();
|
||||
return *static_cast<std::int32_t*>(m_data);
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
// NOLINTEND(cppcoreguidelines-no-malloc)
|
||||
+26
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user