93 Commits

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

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

Closes: #46
2026-07-07 19:29:30 +02:00
CHatingPython 2519560451 refactor: remove name from function
Closes: #47
2026-07-06 22:06:53 +02:00
CHatingPython 7c74850aff feat: introduce array and get instruction 2026-07-06 10:53:48 +02:00
CHatingPython 81a439b2ad refactor: add size argument to array type
Closes: #43
2026-07-05 23:15:40 +02:00
CHatingPython b913fc0c8b refactor: rename list to array
Closes: #42
2026-07-05 22:41:53 +02:00
CHatingPython ee9eadeea8 feat: introduce sizeof instruction
Closes: #34
2026-07-05 22:33:15 +02:00
CHatingPython 642d57622a feat: introduce pointerof operation
Introduce pointerof operation and do some side-quests along the way.

Refs: #34
Closes: #17
2026-07-05 14:54:16 +02:00
CHatingPython 9690ac3617 feat: add core module to context
Closes: #45
2026-07-05 13:31:15 +02:00
CHatingPython f89f643870 feat: implement module loading
I couldn't care less to commit all the changes separately after fight
with this stupid codebase.
Also I am closing the issue since serializing contexts meant doing
something like jars which I don't want to do right now. I'll reopen the
issue though for sure clueless.

Closes: #23
2026-07-04 18:41:06 +02:00
CHatingPython 100c542fe1 refactor: serialize types in module
Refs: #23
2026-07-04 16:15:40 +02:00
CHatingPython cf284592fd fix: fix handle's copy constructors 2026-07-04 16:14:46 +02:00
CHatingPython f5ebc767ab fix: fix module's function serialization
Refs: #23
2026-07-04 13:20:08 +02:00
CHatingPython 484e16963f refactor: move thing allocator to a separate file
Closes: #44
2026-07-03 19:51:09 +02:00
CHatingPython 575054b75a feat: introduce import type
Closes: #39
2026-07-03 19:43:18 +02:00
CHatingPython a7ab214ce3 refactor: make context inherit module handle container
Refs: #39
2026-07-03 19:41:28 +02:00
CHatingPython e087c11008 feat: add types to module
Refs: #39
2026-07-03 19:41:27 +02:00
CHatingPython 2164ab0d97 refactor: move type to separate file 2026-07-03 19:41:10 +02:00
CHatingPython 11b05b8c18 fix: fix handle class
Fix handle class for headers without reference counting.
2026-07-03 18:42:06 +02:00
CHatingPython 6ec4710eaa feat: introduce reference instruction 2026-07-01 21:58:33 +02:00
CHatingPython 09252c3ec3 feat: introduce list type
Closes: #33
2026-07-01 21:39:59 +02:00
CHatingPython 3d461048cc feat: introduce reference thing type
Closes: #32
2026-07-01 16:56:57 +02:00
CHatingPython 482ab859ed refactor: rename thing_type to type
Rename thing_type to type and use shared pointer instead of value in
thing.
2026-07-01 16:25:46 +02:00
CHatingPython 42f020977e feat: introduce more integer thing types
Closes: #31
2026-06-29 20:43:00 +02:00
CHatingPython 6ab9254746 refactor: improve thing types
Change thing_t enumeration to thing_type structure.

Closes: #30
2026-06-29 20:36:42 +02:00
CHatingPython 8466902281 feat(IR): improve IR functions
Add access specifiers and parameter counts to furlang's IR functions.
2026-06-28 13:21:13 +02:00
CHatingPython 7b609f87c9 feat(furc): introduce function call expression and call instruction 2026-06-28 13:21:13 +02:00
CHatingPython 0bcbe1d7d1 feat(furc): add access specifiers to functions
Introduce public and private access specifiers to functions.

Closes: #26
2026-06-28 13:21:05 +02:00
CHatingPython 8702298cd2 refactor(furvm): improve functions
Define native functions inside the module instead of the function
itself (native functions hold only the key to the definition), introduce
private functions, add parameter count to function and fix some bugs.

Closes: #20
Closes: #25
2026-06-28 12:37:36 +02:00
CHatingPython 55932f6113 feat(furvm): add iterator functions to handle container 2026-06-28 12:36:31 +02:00
CHatingPython a3db4ef1e1 feat(furvm): add type function to thing 2026-06-28 12:35:46 +02:00
CHatingPython 079f6326a4 Merge pull request 'furc' (#24) from furc into master
Reviewed-on: KPGPMC/furlang#24
2026-06-24 16:29:25 +00:00
CHatingPython d8d81851d3 feat(furc): introduce native and import functions
Closes: #19
2026-06-24 18:26:01 +02:00
CHatingPython abeb5b390b feat(furc): introduce function declaration parameters
Closes: #16
2026-06-24 18:06:40 +02:00
CHatingPython a61f5c194b refactor(furc): improve function declarations
Refs: #16
2026-06-24 15:52:14 +02:00
CHatingPython d497e2de45 feat: introduce arrow tokens
Refs: #16
2026-06-24 15:50:37 +02:00
CHatingPython 1965ab26af Merge pull request 'Types for furc' (#22) from furc-types into master
Reviewed-on: KPGPMC/furlang#22
Closes: #15
2026-06-24 13:32:30 +00:00
CHatingPython 70b7b6a2f4 feat: introduce parse type function
Refs: #15
2026-06-24 15:30:45 +02:00
CHatingPython b5dcd6690a feat: introduce AST type class
Refs: #15
2026-06-24 15:30:28 +02:00
CHatingPython 0baf896c3a feat: introduce int32 keyword
Refs: #15
2026-06-24 15:27:29 +02:00
CHatingPython 318b3e8a65 Merge pull request 'Furvm backend for furc' (#18) from furvm-gen into master
Reviewed-on: KPGPMC/furlang#18
2026-06-24 12:58:16 +00:00
CHatingPython 7d2e748c27 feat(furc): introduce furvm backend to furc
Closes: #14
2026-06-24 14:56:49 +02:00
CHatingPython e485a7900b feat(furvm): introduce handle dispatch function 2026-06-24 14:53:50 +02:00
CHatingPython 68e1f8fda0 Merge branch 'great-refactor' into master
Closes #12
2026-06-24 04:54:30 +02:00
CHatingPython 8909a0cfff refactor(furc): bring back ADCE to post process
Refs: #12
2026-06-23 15:21:12 +02:00
CHatingPython dad2c180d6 refactor(furc): bring back SCCP to post process
Refs: #12
2026-06-23 15:15:01 +02:00
CHatingPython 4478a839b7 refactor(furc): improve the SSA optimization class
Improve and rename the SSA optimization class to post process.

Refs: #12
2026-06-23 14:54:28 +02:00
CHatingPython 9549a7b61a docs(furvm): document the thing
Refs: #12
2026-06-21 20:40:31 +02:00
CHatingPython 51483db283 docs(furvm): document the function
Refs: #12
2026-06-21 20:17:07 +02:00
CHatingPython 3759c0361f docs(furvm): document the handle 2026-06-21 20:08:41 +02:00
CHatingPython 1ead2f6592 docs(furvm): document the function
Refs: #12
2026-06-21 19:45:59 +02:00
CHatingPython b6d3387388 docs(furvm): document the executor
Refs: #12
2026-06-21 19:40:33 +02:00
CHatingPython 3a2fa32ce1 fix!: remove references to context::collect
I forgor to do that earlier lol

Refs: #12
2026-06-20 12:56:43 +02:00
CHatingPython f2294079a0 docs(furvm): document the context
Refs: #12
2026-06-20 12:42:42 +02:00
CHatingPython 39f873a984 docs(furvm): document detail folder
Refs: #12
2026-06-20 12:26:53 +02:00
CHatingPython d46aa45c1f feat(furvm): add on release callback to refcount handles
Refs: #12
2026-06-20 12:19:53 +02:00
CHatingPython 190aa8f985 refactor(furc): move arena allocator out of the parser
Refs: #12
2026-06-20 11:56:15 +02:00
CHatingPython f80abd68ad refactor(furvm): introduce a furvm.hpp header
Refs: #12
2026-06-20 11:32:01 +02:00
CHatingPython 99666495e7 refactor(furvm): improve serialization
Refs: #12
2026-06-19 15:14:25 +02:00
CHatingPython 6f023765b5 chore: clang-tidy 2026-06-19 15:13:56 +02:00
CHatingPython 65e5fdf324 refactor(furvm): move bad access exceptions to exceptions.hpp
Refs: #12
2026-06-19 14:26:09 +02:00
CHatingPython 5a1b7c5aa6 refactor(furvm): expose context thing allocator
Refs: #12
2026-06-19 14:15:05 +02:00
CHatingPython f14f3854e2 fix(furvm): fix thing allocator
Refs: #12
2026-06-19 11:55:30 +02:00
CHatingPython 6fbdb2299b refactor(furvm): refactor import function
Refs: #12
2026-06-19 11:36:20 +02:00
CHatingPython ec2c81fc56 refactor(furvm): improve the handle system
Absolute slop

Refs: #12
2026-06-18 23:58:37 +02:00
CHatingPython e9b38e95a2 refactor(furvm): integrate handle system into things
Sloppy af

Refs: #12
2026-06-17 20:19:53 +02:00
CHatingPython 00bcc0e6a5 refactor(furvm): use the handle system
Refs: #12
2026-06-17 13:59:54 +02:00
CHatingPython e8ccaa6390 feat(furvm): implement a handle
Refs: #12
2026-06-16 16:55:43 +02:00
CHatingPython c2b32c9604 refactor(furvm): rename handle suffixes to ids
Refs: #12
2026-06-16 16:21:49 +02:00
CHatingPython c9f714642c refactor(furvm): refactor thing
A pretty sloppy thing class refactor.

Refs: #12
2026-06-16 14:59:29 +02:00
CHatingPython 0dfa8c1c02 refactor(furvm): refactor executor
Refs: #12
2026-06-16 14:45:36 +02:00
CHatingPython 78acfb6ee2 refactor(furvm): refactor function
Refs: #12
2026-06-16 12:48:16 +02:00
CHatingPython 43dac1ec6d refactor(furvm): refactor mod
Refs: #12
2026-06-16 12:05:55 +02:00
CHatingPython b35a274e81 refactor(furlang/ir): rename module to mod
Refs: #12
2026-06-16 11:25:41 +02:00
61 changed files with 5716 additions and 1840 deletions
+6 -1
View File
@@ -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: "*"
+1
View File
@@ -14,6 +14,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(furlang)
add_subdirectory(furvm)
add_subdirectory(furc)
add_subdirectory(furas)
if(DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
+5
View File
@@ -0,0 +1,5 @@
file(GLOB_RECURSE FURAS_SRCS "src/**.cpp")
file(GLOB_RECURSE FURAS_HDRS "include/**.hpp")
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
target_include_directories(furas PUBLIC include/)
target_link_libraries(furas PRIVATE furlang libfurvm)
+35
View File
@@ -0,0 +1,35 @@
type arr = array $s8 10
func println $arr = native println
public func main = #main
main:
array $arr
dup
store %0
lenof
store %1
push $s32 0
loop:
dup
load %1
ge
jnz #end
body:
dup
load %0
swap
push $s8 69
set
push $s32 1
add
jmp #loop
end:
drop
load %0
call $arr println
ret
+34
View File
@@ -0,0 +1,34 @@
type arr = array $s8 10
func println $arr = native println
public func main = #main
main:
array $arr
store %0
push $s32 2
store %1
push $s32 0
loop:
dup
load %1
ge
jnz #end
body:
dup
load %0
swap
push $s8 69
set
push $s32 1
add
jmp #loop
end:
drop
load %0
call $arr println
ret
+23
View File
@@ -0,0 +1,23 @@
func println $u32 = native println
public func main = #main
main:
push $u32 0
store %0
loop_header:
load %0
push $u32 10
ge
jnz #loop_end
loop_body:
load %0
call $u32 println
load %0
push $u32 1
add
store %0
jmp #loop_header
loop_end:
ret
View File
+36
View File
@@ -0,0 +1,36 @@
#ifndef FURAS_GEN_HPP
#define FURAS_GEN_HPP
#include "furas/lexer.hpp"
#include "furvm/module.hpp"
#include <string>
namespace furas {
struct generator_error {
enum type {
Success = 0,
Eof = 1,
UnexpectedEof = -1,
UnexpectedToken = -2,
UnknownCharacter = -3,
UnknownType = -4,
} type = Success;
std::string message;
};
class generator {
public:
struct result {
generator_error error;
furvm::mod mod;
};
public:
static result generate(lexer lexer);
};
} // namespace furas
#endif // FURAS_GEN_HPP
+47
View File
@@ -0,0 +1,47 @@
#ifndef FURAS_LEXER_HPP
#define FURAS_LEXER_HPP
#include "furas/token.hpp"
#include "furlang/result.hpp"
#include <cstddef>
#include <string_view>
namespace furas {
struct lexer_location {
std::string_view filename;
std::size_t row, col;
};
struct lexer_error {
enum type {
EndOfFile = 0,
UnknownCharacter,
} type;
lexer_location location;
std::string message;
};
using token_r = furlang::result<token, lexer_error>;
class lexer {
public:
lexer(std::string_view filename, std::string_view content)
: m_filename(filename), m_content(content) {}
token_r next_token();
private:
constexpr lexer_location location() const { return { m_filename, m_cursor - m_lineStart, m_column }; }
private:
std::string_view m_filename;
std::string_view m_content;
std::size_t m_cursor = 0;
std::size_t m_lineStart = 0;
std::size_t m_column = 0;
};
} // namespace furas
#endif // FURAS_LEXER_HPP
+94
View File
@@ -0,0 +1,94 @@
#ifndef FURAS_TOKEN_HPP
#define FURAS_TOKEN_HPP
#include <cstdint>
#include <string_view>
namespace furas {
struct token {
enum type {
Identifier = 0, /**< An identifier. */
Signed, /**< A signed integer. */
Unsigned, /**< An unsigned integer. */
// Markers
Monkey, /**< Constant marker (`@`). */
Dolar, /**< Type marker(`$`). */
Sha256, /**< Label marker(`#`). */
Percent, /**< Variable marker(`%`). The more the better. */
EqSign, /**< `=` */
Dot, /**< . */
Colon, /**< `:` */
// Keywords
Func, /**< `func` keyword for defining functions. */
Type, /**< `type` keyword for defining types. */
Native, /**< `native` keyword for native functions. :v: */
Import, /**< `import` keyword for importing functions and types. */
Public, /**< `public` access specifier. */
Private, /**< `private` access specifier. */
// Instructions
Push,
Array,
Get,
Set,
Drop,
Dup,
Swap,
Clone,
Ref,
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Neq,
Lt,
Gt,
Le,
Ge,
Ptrof,
Sizeof,
Lenof,
Load,
Store,
Call,
Jmp,
Jnz,
Ret,
Count
} type = Count;
union value {
std::nullptr_t null = nullptr;
std::string_view string;
std::int64_t integer;
std::uint64_t uint;
} value;
token(enum type type)
: type(type) {}
token(enum type type, std::string_view string)
: type(type) {
value.string = string;
}
token(std::uint64_t num)
: type(Unsigned) {
value.uint = num;
}
token(std::int64_t num)
: type(Signed) {
value.integer = num;
}
};
} // namespace furas
#endif // FURAS_TOKEN_HPP
+558
View File
@@ -0,0 +1,558 @@
#include "furas/gen.hpp"
#include "furas/token.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/instruction.hpp"
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <optional>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include <vector>
namespace furas {
using namespace std::string_literals;
namespace {
struct instruction {
furvm::instruction_t fur{};
enum arg_type {
None = 0,
Type,
Constant,
Variable,
Function,
Label,
} arg = None;
};
// NOLINTBEGIN
std::unordered_map<enum token::type, instruction> instructions = {
{ token::Array, { furvm::instruction_t::Array, instruction::Type } },
{ token::Get, { furvm::instruction_t::Get } },
{ token::Set, { furvm::instruction_t::Set } },
{ token::Drop, { furvm::instruction_t::Drop } },
{ token::Dup, { furvm::instruction_t::Duplicate } },
{ token::Swap, { furvm::instruction_t::Swap } },
{ token::Clone, { furvm::instruction_t::Clone } },
{ token::Ref, { furvm::instruction_t::Reference } },
{ token::Add, { furvm::instruction_t::Add } },
{ token::Sub, { furvm::instruction_t::Sub } },
{ token::Mul, { furvm::instruction_t::Mul } },
{ token::Div, { furvm::instruction_t::Div } },
{ token::Mod, { furvm::instruction_t::Mod } },
{ token::Eq, { furvm::instruction_t::Equals } },
{ token::Neq, { furvm::instruction_t::NotEquals } },
{ token::Lt, { furvm::instruction_t::LessThan } },
{ token::Gt, { furvm::instruction_t::GreaterThan } },
{ token::Le, { furvm::instruction_t::LessEqual } },
{ token::Ge, { furvm::instruction_t::GreaterEqual } },
{ token::Ptrof, { furvm::instruction_t::Pointerof } },
{ token::Sizeof, { furvm::instruction_t::Sizeof } },
{ token::Lenof, { furvm::instruction_t::Lengthof } },
{ token::Load, { furvm::instruction_t::Load, instruction::Variable } },
{ token::Store, { furvm::instruction_t::Store, instruction::Variable } },
{ token::Call, { furvm::instruction_t::Call, instruction::Function } },
{ token::Jmp, { furvm::instruction_t::Jump, instruction::Label } },
{ token::Jnz, { furvm::instruction_t::JumpNotZero, instruction::Label } },
{ token::Ret, { furvm::instruction_t::Return } },
};
// NOLINTEND
const char* token_type(enum token::type type) {
switch (type) {
case token::Identifier: return "identifier";
case token::Signed: return "signed";
case token::Unsigned: return "unsigned";
case token::Monkey: return "'@'";
case token::Dolar: return "'$'";
case token::Sha256: return "'#'";
case token::Percent: return "'%'";
case token::EqSign: return "'='";
case token::Dot: return "'.'";
case token::Colon: return "':'";
case token::Func: return "func";
case token::Type: return "type";
case token::Native: return "native";
case token::Import: return "import";
case token::Public: return "public";
case token::Private: return "private";
case token::Push: return "push";
case token::Array: return "array";
case token::Get: return "get";
case token::Set: return "set";
case token::Drop: return "drop";
case token::Dup: return "dup";
case token::Swap: return "swap";
case token::Clone: return "clone";
case token::Ref: return "ref";
case token::Add: return "add";
case token::Sub: return "sub";
case token::Mul: return "mul";
case token::Div: return "div";
case token::Mod: return "mod";
case token::Eq: return "eq";
case token::Neq: return "neq";
case token::Lt: return "lt";
case token::Gt: return "gt";
case token::Le: return "le";
case token::Ge: return "ge";
case token::Ptrof: return "ptrof";
case token::Sizeof: return "sizeof";
case token::Lenof: return "lenof";
case token::Load: return "load";
case token::Store: return "store";
case token::Call: return "call";
case token::Jmp: return "jmp";
case token::Jnz: return "jnz";
case token::Ret: return "ret";
case token::Count: break;
}
throw std::runtime_error("unreachable");
}
struct mod_context {
struct label_context {
struct function_info {
std::string name;
furvm::function_sig signature;
bool pub;
};
static constexpr std::size_t INVALID = std::numeric_limits<std::size_t>::max();
std::size_t offset = INVALID;
std::vector<function_info> functions;
std::vector<std::size_t> unknowns;
};
furvm::mod mod;
std::unordered_map<std::pair<std::string, furvm::function_sig>,
furvm::function_h,
furlang::utility::
pair_hash<std::string, furvm::function_sig, std::hash<std::string>, furvm::detail::function_sig_hash>>
functions;
std::unordered_map<std::string, furvm::mod_type_h> types;
std::unordered_map<std::string, label_context> labels;
struct token_result {
generator_error error;
token token;
struct token* operator->() { return &token; }
struct token& operator*() { return token; }
bool operator!() const { return error.type != generator_error::Success; }
};
mod_context() {
types.emplace("s8", mod.emplace_type(furvm::mod_type::S8));
types.emplace("u8", mod.emplace_type(furvm::mod_type::U8));
types.emplace("s16", mod.emplace_type(furvm::mod_type::S16));
types.emplace("u16", mod.emplace_type(furvm::mod_type::U16));
types.emplace("s32", mod.emplace_type(furvm::mod_type::S32));
types.emplace("u32", mod.emplace_type(furvm::mod_type::U32));
types.emplace("s64", mod.emplace_type(furvm::mod_type::S64));
types.emplace("u64", mod.emplace_type(furvm::mod_type::U64));
}
static token_result next_token(lexer& lexer) {
auto token = lexer.next_token();
if (token.has_error()) {
switch (token.error().type) {
case lexer_error::EndOfFile:
return { { generator_error::UnexpectedEof, "Unexpected end of file" }, { token::Count } };
case lexer_error::UnknownCharacter:
return { { generator_error::UnknownCharacter, token.error().message }, { token::Count } };
}
}
return { { generator_error::Success }, *token };
}
static token_result eat_token(lexer& lexer, enum token::type type) {
auto token = next_token(lexer);
if (!token) return token;
if (token.token.type != type) {
return { { generator_error::UnexpectedToken,
"Expected "s + token_type(type) + ", but got " + token_type(token->type) },
{ token::Count } };
}
return { { generator_error::Success }, token.token };
}
generator_error generate(lexer& lexer) {
auto result = next_token(lexer);
if (result.error.type == generator_error::UnexpectedEof) return { generator_error::Eof };
if (!result) return result.error;
switch (result->type) {
case token::Identifier: {
std::string labelName = std::string(result->value.string);
result = eat_token(lexer, token::Colon);
if (!result) return result.error;
auto& label = labels[labelName];
if (label.offset != label_context::INVALID)
return { generator_error::UnexpectedToken, "Label "s + labelName + " already exists" };
label.offset = mod.bytecode().size();
for (auto& func : label.functions) {
furvm::function_h handle =
(func.pub ? mod.emplace_function(func.name, std::move(func.signature), label.offset)
: mod.emplace_function(std::move(func.signature), label.offset));
functions.emplace(std::make_pair(std::move(func.name), std::move(func.signature)), handle);
handle.dispatch();
}
for (auto unknown : label.unknowns) {
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(label.offset - unknown);
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode()[unknown - 1] = jmpOff;
}
label.functions = {};
label.unknowns = {};
return { generator_error::Success };
}
case token::Func:
case token::Type:
case token::Public:
case token::Private: {
bool pub = false;
if (result->type == token::Public) {
pub = true;
result = next_token(lexer);
if (!result) return { result.error };
} else if (result->type == token::Private) {
result = next_token(lexer);
if (!result) return { result.error };
}
if (result->type == token::Func) {
auto nameRes = next_token(lexer);
if (!nameRes) return nameRes.error;
furvm::function_sig signature;
result = next_token(lexer);
if (!result) return result.error;
while (result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
signature.params.push_back(it->second);
} else {
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
}
result = next_token(lexer);
if (!result) return result.error;
}
if (result->type != token::EqSign) return result.error;
result = next_token(lexer);
if (!result) return result.error;
if (result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
signature.returnType = it->second;
} else {
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
}
result = next_token(lexer);
if (!result) return result.error;
}
switch (result->type) {
case token::Sha256: {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
std::string name = std::string(nameRes->value.string);
std::string labelName = std::string(result->value.string);
std::size_t offset = 0;
if (auto it = labels.find(labelName); it != labels.end()) {
offset = it->second.offset;
} else {
labels[labelName].functions.emplace_back(
label_context::function_info{ std::move(name), std::move(signature), pub });
return { generator_error::Success };
}
furvm::function_h handle =
(pub ? mod.emplace_function(name, signature, offset) : mod.emplace_function(signature, offset));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Native: {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
std::string nativeName = std::string(result->value.string);
std::string name = std::string(nameRes->value.string);
furvm::function_h handle = (pub ? mod.emplace_function(name, signature, std::move(nativeName))
: mod.emplace_function(signature, std::move(nativeName)));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Import: {
throw std::runtime_error("unimplemented");
}
default:
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) +
", expected label name, `native`, or `import`" };
}
}
if (result->type == token::Type) {
auto nameRes = eat_token(lexer, token::Identifier);
if (!nameRes) return nameRes.error;
result = eat_token(lexer, token::EqSign);
if (!result) return result.error;
result = next_token(lexer);
if (!result) return result.error;
switch (result->type) {
case token::Import: {
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
return { generator_error::Success };
}
case token::Array: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
auto size = eat_token(lexer, token::Unsigned);
if (!size) return size.error;
furvm::mod_type_id innerId = 0;
if (auto it = types.find(std::string(typeNameRes->value.string)); it != types.end()) {
innerId = it->second.id();
} else {
return { generator_error::UnknownType,
"Unknown type "s + std::string(typeNameRes->value.string) };
}
auto type = mod.emplace_type(innerId, size->value.uint);
types.emplace(std::string(nameRes->value.string), type);
type.dispatch();
return { generator_error::Success };
}
default:
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) +
", expected either type, `import`, or `array`" };
}
}
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) + ", expected either `func` or `type`" };
}
case token::Push: {
auto result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
auto typeName = eat_token(lexer, token::Identifier);
if (!typeName) return result.error;
auto it = types.find(std::string(typeName->value.string));
if (it == types.end())
return { generator_error::UnexpectedToken, "Unknown type "s + std::string(typeName->value.string) };
// TODO: Add support for signed integers
auto value = eat_token(lexer, token::Unsigned);
if (!value) return result.error;
auto type = it->second;
switch (type->type) {
case furvm::mod_type::S8: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS8));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::U8: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU8));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::S16: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS16));
mod.bytecode().push_back(value->value.uint & 0xFF);
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
} break;
case furvm::mod_type::U16: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU16));
mod.bytecode().push_back(value->value.uint & 0xFF);
mod.bytecode().push_back((value->value.uint >> 8) & 0xFF);
} break;
case furvm::mod_type::S32: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushS32));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
case furvm::mod_type::U32: {
mod.bytecode().push_back(static_cast<furvm::byte>(furvm::instruction_t::PushU32));
mod.bytecode().push_back(value->value.uint & 0xFF);
} break;
default:
return { generator_error::UnexpectedToken, "Unexpected type "s + std::string(typeName->value.string) };
}
return { generator_error::Success };
}
case token::Array:
case token::Get:
case token::Set:
case token::Drop:
case token::Dup:
case token::Swap:
case token::Clone:
case token::Ref:
case token::Add:
case token::Sub:
case token::Mul:
case token::Div:
case token::Mod:
case token::Eq:
case token::Neq:
case token::Lt:
case token::Gt:
case token::Le:
case token::Ge:
case token::Ptrof:
case token::Sizeof:
case token::Lenof:
case token::Load:
case token::Store:
case token::Call:
case token::Jmp:
case token::Jnz:
case token::Ret: {
auto it = instructions.find(result->type);
assert(it != instructions.end());
mod.bytecode().push_back(static_cast<furvm::byte>(it->second.fur));
switch (it->second.arg) {
case instruction::None: break;
case instruction::Type: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = type->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
mod.bytecode().push_back((id >> 16) & 0xFF);
mod.bytecode().push_back((id >> 24) & 0xFF);
} break;
case instruction::Constant: {
assert(false); // TODO: Unimplemented
} break;
case instruction::Variable: {
result = eat_token(lexer, token::Percent);
if (!result) return result.error;
result = eat_token(lexer, token::Unsigned);
if (!result) return result.error;
std::uint16_t var = result->value.uint;
mod.bytecode().push_back((var >> 0) & 0xFF);
mod.bytecode().push_back((var >> 8) & 0xFF);
} break;
case instruction::Function: {
furvm::function_sig signature;
while ((result = next_token(lexer)).error.type == generator_error::Success &&
result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
signature.params.push_back(type->second);
}
if (!result || result->type != token::Identifier) return result.error;
std::string name(result->value.string);
auto func = functions.find(std::make_pair(name, signature));
if (func == functions.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = func->second.id();
mod.bytecode().push_back((id >> 0) & 0xFF);
mod.bytecode().push_back((id >> 8) & 0xFF);
} break;
case instruction::Label: {
result = eat_token(lexer, token::Sha256);
if (!result) return result.error;
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto& label = labels[std::string(result->value.string)];
auto offset = label.offset;
if (offset == label_context::INVALID) {
label.unknowns.push_back(mod.bytecode().size() + 1);
mod.bytecode().push_back(0);
return { generator_error::Success };
}
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(offset - mod.bytecode().size() - 1);
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
assert(false); // TODO: Further jumps are not implemented
}
mod.bytecode().push_back(jmpOff);
} break;
}
return { generator_error::Success };
}
case token::Signed:
case token::Unsigned:
case token::Colon:
case token::Monkey:
case token::Dolar:
case token::Sha256:
case token::Percent:
case token::EqSign:
case token::Dot:
case token::Native:
case token::Import:
case token::Count: break;
}
return { generator_error::UnexpectedToken, "Unexpected token "s + token_type(result->type) };
}
};
} // namespace
generator::result generator::generate(lexer lexer) {
mod_context context;
generator_error error;
do {
error = context.generate(lexer);
} while (error.type == generator_error::Success);
if (error.type == generator_error::Eof) return { { generator_error::Success }, std::move(context.mod) };
return { error };
}
} // namespace furas
+98
View File
@@ -0,0 +1,98 @@
#include "furas/lexer.hpp"
#include <cctype>
#include <unordered_map>
namespace furas {
using namespace std::string_literals;
token_r lexer::next_token() {
while (m_cursor < m_content.size() && std::isspace(m_content[m_cursor]) != 0) {
if (m_content[m_cursor] == '\n') {
m_lineStart = m_cursor + 1;
++m_column;
}
++m_cursor;
}
// TODO: Add support for single-line comments
// TODO: Add support for multi-line comments
if (m_cursor >= m_content.size()) return token_r{ lexer_error{ lexer_error::EndOfFile, location() } };
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
// TODO: Add support for hexadecimal and binary numeric literals
if (std::isdigit(m_content[m_cursor]) != 0) {
std::uint64_t num = 0;
while (m_cursor < m_content.size() && std::isdigit(m_content[m_cursor]) != 0) {
num *= 10;
num += m_content[m_cursor++] - '0';
}
return { num };
}
static std::unordered_map<std::string_view, enum token::type> s_tokens = {
{ "func", token::Func },
{ "type", token::Type },
{ "native", token::Native },
{ "import", token::Import },
{ "public", token::Public },
{ "private", token::Private },
{ "push", token::Push },
{ "array", token::Array },
{ "get", token::Get },
{ "set", token::Set },
{ "drop", token::Drop },
{ "dup", token::Dup },
{ "swap", token::Swap },
{ "clone", token::Clone },
{ "ref", token::Ref },
{ "add", token::Add },
{ "sub", token::Sub },
{ "mul", token::Mul },
{ "div", token::Div },
{ "mod", token::Mod },
{ "eq", token::Eq },
{ "neq", token::Neq },
{ "lt", token::Lt },
{ "gt", token::Gt },
{ "le", token::Le },
{ "ge", token::Ge },
{ "ptrof", token::Ptrof },
{ "sizeof", token::Sizeof },
{ "lenof", token::Lenof },
{ "load", token::Load },
{ "store", token::Store },
{ "call", token::Call },
{ "jmp", token::Jmp },
{ "jnz", token::Jnz },
{ "ret", token::Ret },
};
if (std::isalnum(m_content[m_cursor]) != 0) {
std::size_t begin = m_cursor++;
while (m_cursor < m_content.size() && (std::isalnum(m_content[m_cursor]) != 0 || m_content[m_cursor] == '_'))
++m_cursor;
std::string_view str = m_content.substr(begin, m_cursor - begin);
if (auto it = s_tokens.find(str); it != s_tokens.end()) return { it->second };
return { token::Identifier, str };
}
switch (m_content[m_cursor]) {
case '@': ++m_cursor; return { token::Monkey };
case '$': ++m_cursor; return { token::Dolar };
case '#': ++m_cursor; return { token::Sha256 };
case '%': ++m_cursor; return { token::Percent };
case '=': ++m_cursor; return { token::EqSign };
case '.': ++m_cursor; return { token::Dot };
case ':': ++m_cursor; return { token::Colon };
default:
return token_r{
lexer_error{ lexer_error::UnknownCharacter, location(), "Unknown character '"s + m_content[m_cursor] + "'" }
};
}
}
} // namespace furas
+39
View File
@@ -0,0 +1,39 @@
#include "furas/gen.hpp"
#include "furas/lexer.hpp"
#include <fstream>
#include <furvm/module.hpp>
#include <iostream>
int main(int argc, char** argv) { // NOLINT
if (argc < 2) {
std::cerr << "feed me more arguments daddy >_<\n";
return 1;
}
if (argc > 2) {
std::cerr << "too much O_O\n";
return 1;
}
const char* filepath = argv[1];
std::ifstream file(filepath, std::ios::binary | std::ios::ate | std::ios::in);
if (!file.is_open()) {
std::cerr << "file won't open >~<\n";
return 1;
}
std::string content;
content.resize(file.tellg());
file.seekg(0);
file.read(content.data(), static_cast<std::streamsize>(content.size()));
auto result = furas::generator::generate(furas::lexer(filepath, content));
if (result.error.type != furas::generator_error::Success) {
std::cerr << result.error.message << '\n';
return 1;
}
result.mod.serialize(std::cout);
return 0;
}
+1 -1
View File
@@ -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)
+116 -8
View File
@@ -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.
+53
View File
@@ -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
+12
View File
@@ -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.
*/
+8
View File
@@ -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
+43
View File
@@ -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
+4 -2
View File
@@ -22,9 +22,10 @@ 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;
@@ -35,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) {
@@ -45,7 +47,7 @@ private:
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;
+8 -5
View File
@@ -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;
};
+41
View File
@@ -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
-40
View File
@@ -1,40 +0,0 @@
#ifndef FURC_FRONT_SSA_HPP
#define FURC_FRONT_SSA_HPP
#include "furlang/ir/function.hpp"
#include "furlang/ir/instruction.hpp"
#include "furlang/ir/module.hpp"
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc {
namespace front {
class ssa {
using block_map_t = std::unordered_map<furlang::ir::block_index, std::vector<furlang::ir::block_index>>;
public:
static void optimize(furlang::ir::module& mod);
private:
static void optimize(const std::unique_ptr<furlang::ir::function>& func);
static void de_ssa(const std::unique_ptr<furlang::ir::function>& func);
static void constant_propagation(const std::unique_ptr<furlang::ir::function>& func);
static void dead_code_elimination(const std::unique_ptr<furlang::ir::function>& func);
static void copy_propagation(const std::unique_ptr<furlang::ir::function>& func);
static void dfs_rpo(furlang::ir::block_index block,
const block_map_t& successors,
std::unordered_set<furlang::ir::block_index>& visited,
std::vector<furlang::ir::block_index>& rpo);
};
} // namespace front
} // namespace furc
#endif // FURC_FRONT_SSA_HPP
+29
View File
@@ -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;
}
@@ -146,6 +153,14 @@ enum class keyword_token {
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) {
@@ -156,6 +171,13 @@ static inline std::ostream& operator<<(std::ostream& os, keyword_token keyword)
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;
}
@@ -168,6 +190,13 @@ static inline std::string operator+(const std::string& str, keyword_token keywor
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;
}
+24
View File
@@ -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();
}
+196
View File
@@ -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
+75 -23
View File
@@ -4,10 +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 {
@@ -16,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) {
@@ -28,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);
@@ -109,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");
}
@@ -137,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));
@@ -149,22 +185,38 @@ 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;
}
}
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) {
+9
View File
@@ -100,6 +100,13 @@ token_r lexer::next_token() {
{ "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 };
@@ -143,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;
+168 -41
View File
@@ -6,21 +6,23 @@
#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();
@@ -32,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) {
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:
@@ -109,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();
@@ -136,13 +216,13 @@ 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()));
}
@@ -160,7 +240,7 @@ ast::statement_node_r parser::parse_statement() {
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,
return m_arena->allocate_shared<ast::while_statement_node>(location,
std::move(cond.value()),
std::move(body.value()));
}
@@ -172,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;
}
@@ -204,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();
@@ -216,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 });
@@ -234,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 });
@@ -262,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));
}
@@ -287,6 +383,7 @@ enum class rhsop_info_t {
Unaryop,
Binop,
Assignment,
FuncCall,
};
struct rhsop_info {
@@ -299,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;
@@ -327,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) {
@@ -351,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);
@@ -363,12 +471,27 @@ ast::expression_node_r parser::parse_expression_rhs(ast::expression_node_p&& ini
auto opToken = next_token();
ast::expression_node_p rhs;
if (current.type != rhsop_info_t::Unaryop) {
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);
@@ -389,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;
@@ -446,15 +574,14 @@ const token_r& parser::peek_token() {
}
token_r parser::eat_token(token_t type) {
auto token = next_token();
if (const auto& token = peek_token(); token.has_error() || peek_token()->type != type) {
if (token.has_error()) return token;
if (token->type != type) {
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
+605
View File
@@ -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
-640
View File
@@ -1,640 +0,0 @@
#include "furc/front/ssa.hpp"
#include "furlang/ir/instruction.hpp"
#include "furlang/ir/operand.hpp"
#include <algorithm>
#include <cstddef>
#include <functional>
#include <memory>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc::front {
void ssa::optimize(furlang::ir::module& mod) {
for (const auto& func : mod.functions()) {
ssa::optimize(func);
ssa::constant_propagation(func);
ssa::dead_code_elimination(func);
ssa::copy_propagation(func);
ssa::dead_code_elimination(func);
ssa::de_ssa(func);
}
}
void ssa::optimize(const std::unique_ptr<furlang::ir::function>& func) {
block_map_t predecessors;
block_map_t successors;
std::unordered_map<furlang::ir::register_t, std::unordered_set<furlang::ir::block_index>> regSites;
std::unordered_map<furlang::ir::block_index, furlang::ir::block_index> idoms;
std::unordered_map<furlang::ir::register_t, std::uint32_t> regVers;
std::unordered_map<furlang::ir::register_t, std::stack<std::uint32_t>> regVerStacks;
std::unordered_map<furlang::ir::block_index, std::vector<furlang::ir::block_index>> domTree;
for (furlang::ir::block_index i = 0; i < func->blocks().size(); ++i) {
const auto& block = func->blocks()[i];
for (const auto& instr : block->instructions()) {
if (instr->has_destination()) {
if (instr->destination().type() == furlang::ir::operand_t::Register) {
regSites[instr->destination().reg()].insert(i);
}
}
}
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);
predecessors[br.block()].push_back(i);
successors[i].push_back(br.block());
} break;
case furlang::ir::instruction_t::BranchCond: {
const auto& br = dynamic_cast<const furlang::ir::branch_cond_instruction&>(*exit);
predecessors[br.if_block()].push_back(i);
predecessors[br.else_block()].push_back(i);
successors[i].push_back(br.if_block());
successors[i].push_back(br.else_block());
} break;
default: break;
}
}
std::unordered_set<furlang::ir::register_t> globalRegs;
for (furlang::ir::block_index i = 0; i < func->blocks().size(); ++i) {
const auto& block = func->blocks()[i];
for (const auto& instr : block->instructions()) {
for (const auto& operand : instr->sources()) {
if (operand->type() == furlang::ir::operand_t::Register) {
auto reg = operand->reg();
if (regSites[reg].find(i) == regSites[reg].end()) {
globalRegs.insert(reg);
}
}
}
}
for (const auto& operand : block->exit()->sources()) {
if (operand->type() == furlang::ir::operand_t::Register) {
auto reg = operand->reg();
if (regSites[reg].find(i) == regSites[reg].end()) {
globalRegs.insert(reg);
}
}
}
}
std::unordered_set<furlang::ir::block_index> visited;
std::vector<furlang::ir::block_index> rpoOrder;
dfs_rpo(0, successors, visited, rpoOrder);
std::reverse(rpoOrder.begin(), rpoOrder.end());
std::unordered_map<furlang::ir::block_index, std::size_t> rpoIndex;
for (std::size_t i = 0; i < rpoOrder.size(); ++i) {
rpoIndex[rpoOrder[i]] = i;
}
auto intersect = [&](furlang::ir::block_index block1, furlang::ir::block_index block2) {
while (block1 != block2) {
while (rpoIndex[block1] > rpoIndex[block2]) {
block1 = idoms[block1];
}
while (rpoIndex[block2] > rpoIndex[block1]) {
block2 = idoms[block2];
}
}
return block1;
};
if (rpoOrder.empty()) return;
auto entry = rpoOrder.front();
idoms[entry] = entry;
bool changed = true;
while (changed) {
changed = false;
for (std::size_t i = 1; i < rpoOrder.size(); ++i) {
auto block = rpoOrder[i];
furlang::ir::block_index newIdom = 0;
bool found = false;
for (auto pred : predecessors[block]) {
if (idoms.find(pred) == idoms.end()) continue;
if (!found) {
newIdom = pred;
found = true;
} else {
newIdom = intersect(pred, newIdom);
}
}
if (idoms.find(block) == idoms.end() || idoms[block] != newIdom) {
idoms[block] = newIdom;
changed = true;
}
}
}
std::unordered_map<furlang::ir::block_index, std::unordered_set<furlang::ir::block_index>> df;
for (auto block : rpoOrder) {
df[block] = std::unordered_set<furlang::ir::block_index>{};
}
for (auto block : rpoOrder) {
if (predecessors[block].size() < 2) continue;
for (auto pred : predecessors[block]) {
auto runner = pred;
while (runner != idoms[block]) {
df[runner].insert(block);
runner = idoms[runner];
}
}
}
std::unordered_map<furlang::ir::block_index, std::unordered_set<furlang::ir::register_t>> phis;
for (const auto& [reg, blocks] : regSites) {
if (globalRegs.find(reg) == globalRegs.end()) continue;
std::vector<furlang::ir::block_index> worklist(blocks.begin(), blocks.end());
std::unordered_set<furlang::ir::block_index> added;
while (!worklist.empty()) {
auto block = worklist.back();
worklist.pop_back();
for (auto frontier : df[block]) {
if (added.find(frontier) != added.end()) continue;
phis[frontier].insert(reg);
added.insert(frontier);
if (blocks.find(frontier) == blocks.end()) {
worklist.push_back(frontier);
}
}
}
}
for (furlang::ir::block_index i = 0; i < func->blocks().size(); ++i) {
if (phis.find(i) == phis.end()) continue;
const auto& block = func->blocks()[i];
const auto& preds = predecessors[i];
for (auto reg : phis[i]) {
auto phiInstr = std::make_unique<furlang::ir::phi_instruction>(reg);
for (auto pred : preds) {
phiInstr->labels().emplace_back(furlang::ir::operand::new_reg(reg), pred);
}
block->instructions().emplace(block->instructions().begin(), std::move(phiInstr));
}
}
for (const auto& [block, idom] : idoms) {
if (block != idom) domTree[idom].push_back(block);
}
std::function<void(furlang::ir::block_index)> renameBlock = [&](furlang::ir::block_index blockIndex) -> void {
std::unordered_map<furlang::ir::register_t, std::uint32_t> pushed;
const auto& block = func->blocks()[blockIndex];
for (auto& instr : block->instructions()) {
if (instr->type() != furlang::ir::instruction_t::Phi) continue;
auto orig = instr->destination().reg();
std::uint32_t newVer = regVers[orig]++;
instr->destination().reg().ver = newVer;
regVerStacks[orig].push(newVer);
++pushed[orig];
}
for (auto& instr : block->instructions()) {
if (instr->type() == furlang::ir::instruction_t::Phi) continue;
for (auto& operand : instr->sources()) {
if (operand->type() != furlang::ir::operand_t::Register) continue;
auto orig = operand->reg();
if (!regVerStacks[orig].empty()) {
operand->reg().ver = regVerStacks[orig].top();
}
}
if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) {
auto orig = instr->destination().reg();
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;
auto orig = operand->reg();
if (!regVerStacks[orig].empty()) {
operand->reg().ver = regVerStacks[orig].top();
}
}
for (auto succIndex : successors[blockIndex]) {
const auto& succ = func->blocks()[succIndex];
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& [op, bl] : phi.labels()) {
if (bl != blockIndex) continue;
if (regVerStacks[op.reg()].empty()) continue;
op.reg().ver = regVerStacks[op.reg()].top();
}
}
}
for (const auto& child : domTree[blockIndex]) {
renameBlock(child);
}
for (const auto& [reg, count] : pushed) {
for (std::uint32_t i = 0; i < count; ++i) {
regVerStacks[reg].pop();
}
}
};
renameBlock(entry);
}
void ssa::de_ssa(const std::unique_ptr<furlang::ir::function>& func) {
using namespace furlang::ir;
std::unordered_map<block_index, std::vector<std::unique_ptr<instruction>>> copies;
for (block_index blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) {
const auto& block = func->blocks()[blockIdx];
auto& instrs = block->instructions();
for (auto it = instrs.begin(); it != instrs.end();) {
if ((*it)->type() != furlang::ir::instruction_t::Phi) {
++it;
continue;
}
auto& phi = dynamic_cast<phi_instruction&>(**it);
auto dstReg = phi.destination().reg();
for (auto& [srcOp, label] : phi.labels()) {
copies[label].emplace_back(
std::make_unique<assign_instruction>(std::move(srcOp), operand::new_reg(dstReg)));
}
it = instrs.erase(it);
}
}
for (auto& [blockIdx, cpys] : copies) {
if (blockIdx >= func->blocks().size()) continue;
const auto& block = func->blocks()[blockIdx];
auto& instrs = block->instructions();
for (auto& copy : cpys) {
instrs.push_back(std::move(copy));
}
}
}
enum class lattice_t {
Top,
Constant,
Bottom,
};
struct lattice {
lattice_t type = lattice_t::Top;
std::uint64_t value = 0;
bool operator==(const lattice& rhs) const {
if (type != rhs.type) return false;
return type != lattice_t::Constant || value == rhs.value;
}
};
void ssa::constant_propagation(const std::unique_ptr<furlang::ir::function>& func) {
using block_idx = furlang::ir::block_index;
using reg_t = furlang::ir::register_operand;
using reg2_t = furlang::ir::register_t;
std::unordered_map<reg_t, lattice> latVals;
std::unordered_map<reg2_t, std::vector<furlang::ir::instruction*>> edges;
std::unordered_set<block_idx> executableBlocks;
std::set<std::pair<block_idx, block_idx>> executedEdges;
std::queue<std::pair<block_idx, block_idx>> cfgWorklist;
std::queue<furlang::ir::instruction*> ssaWorklist;
std::unordered_map<furlang::ir::instruction*, block_idx> blockMap;
auto getOperandLattice = [&](const furlang::ir::operand& op) -> lattice {
if (op.type() == furlang::ir::operand_t::Integer) {
lattice lat;
lat.type = lattice_t::Constant;
lat.value = op.integer();
return lat;
}
if (op.type() == furlang::ir::operand_t::Register) {
auto reg = op.reg();
if (latVals.find(reg) == latVals.end()) return { lattice_t::Top };
return latVals[reg];
}
return { lattice_t::Bottom };
};
for (block_idx blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) {
const auto& block = func->blocks()[blockIdx];
auto& instrs = block->instructions();
for (auto it = instrs.begin(); it != instrs.end(); ++it) {
const auto& instr = *it;
blockMap[instr.get()] = blockIdx;
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()] = blockIdx;
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 (executedEdges.count(edge) != 0) continue;
executedEdges.insert(edge);
bool firstVisit = (executableBlocks.find(to) == executableBlocks.end());
executableBlocks.insert(to);
const auto& block = func->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 (executableBlocks.find(blockIdx) == executableBlocks.end()) continue;
lattice newLat = { lattice_t::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 (executedEdges.count({ label, blockIdx }) == 0) continue;
lattice opLat = getOperandLattice(op);
if (opLat.type == lattice_t::Bottom) newLat.type = lattice_t::Bottom;
if (opLat.type == lattice_t::Constant) {
if (newLat.type == lattice_t::Top) {
newLat = opLat;
} else if (newLat.type == lattice_t::Constant && newLat.value != opLat.value) {
newLat.type = lattice_t::Bottom;
}
}
}
} break;
case furlang::ir::instruction_t::Assign: {
newLat = getOperandLattice(*instr->sources().front());
} break;
case furlang::ir::instruction_t::BinaryOp: {
lattice lhs = getOperandLattice(*instr->sources()[0]);
lattice rhs = getOperandLattice(*instr->sources()[1]);
if (lhs.type == lattice_t::Bottom || rhs.type == lattice_t::Bottom) {
newLat.type = lattice_t::Bottom;
} else if (lhs.type == lattice_t::Constant && rhs.type == lattice_t::Constant) {
newLat.type = lattice_t::Constant;
switch (dynamic_cast<const furlang::ir::binary_op_instruction&>(*instr).op_type()) {
case furlang::ir::binary_op_instruction_t::Add: newLat.value = lhs.value + rhs.value; break;
case furlang::ir::binary_op_instruction_t::Sub: newLat.value = lhs.value - rhs.value; break;
case furlang::ir::binary_op_instruction_t::Mul: newLat.value = lhs.value * rhs.value; break;
case furlang::ir::binary_op_instruction_t::Div: newLat.value = lhs.value / rhs.value; break;
case furlang::ir::binary_op_instruction_t::Mod: newLat.value = lhs.value % rhs.value; break;
case furlang::ir::binary_op_instruction_t::Eq:
newLat.value = (lhs.value == rhs.value) ? 1 : 0;
break;
case furlang::ir::binary_op_instruction_t::NotEq:
newLat.value = (lhs.value != rhs.value) ? 1 : 0;
break;
case furlang::ir::binary_op_instruction_t::LessThan:
newLat.value = (lhs.value < rhs.value) ? 1 : 0;
break;
case furlang::ir::binary_op_instruction_t::GreaterThan:
newLat.value = (lhs.value > rhs.value) ? 1 : 0;
break;
case furlang::ir::binary_op_instruction_t::LessEq:
newLat.value = (lhs.value <= rhs.value) ? 1 : 0;
break;
case furlang::ir::binary_op_instruction_t::GreaterEq:
newLat.value = (lhs.value >= rhs.value) ? 1 : 0;
break;
}
}
} break;
default: break;
}
if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) {
auto dst = instr->destination().reg();
if (!(latVals[dst] == newLat)) {
latVals[dst] = newLat;
for (auto* uInstr : edges[dst])
ssaWorklist.push(uInstr);
}
}
if (instr == func->blocks()[blockIdx]->exit().get()) {
auto* exit = func->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 = getOperandLattice(*exit->sources()[0]);
if (cond.type == lattice_t::Constant) {
if (cond.value != 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 < func->blocks().size(); ++i) {
if (executableBlocks.find(i) == executableBlocks.end()) {
func->blocks()[i]->instructions().clear();
continue;
}
const auto& block = func->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 (latVals[reg].type != lattice_t::Constant) continue;
*op = furlang::ir::operand::new_integer(latVals[reg].value);
}
}
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 = getOperandLattice(*exit->sources()[0]);
if (cond.type != lattice_t::Constant) continue;
block_idx target = (cond.value != 0) ? br.if_block() : br.else_block();
block->exit() = std::make_unique<furlang::ir::branch_instruction>(target);
}
}
void ssa::dead_code_elimination(const std::unique_ptr<furlang::ir::function>& func) {
using block_idx = furlang::ir::block_index;
using reg_t = furlang::ir::register_operand;
std::unordered_map<reg_t, furlang::ir::instruction*> defMap;
std::unordered_set<furlang::ir::instruction*> alive;
std::queue<furlang::ir::instruction*> worklist;
for (block_idx blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) {
const auto& block = func->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();
}
}
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 < func->blocks().size(); ++blockIdx) {
const auto& block = func->blocks()[blockIdx];
auto& instrs = block->instructions();
for (auto it = instrs.begin(); it != instrs.end();) {
if (alive.find(it->get()) == alive.end()) {
it = instrs.erase(it);
} else {
++it;
}
}
}
}
void ssa::copy_propagation(const std::unique_ptr<furlang::ir::function>& func) {
using block_idx = furlang::ir::block_index;
using reg_t = furlang::ir::register_operand;
std::unordered_map<reg_t, reg_t> aliasMap;
std::function<reg_t(const reg_t&)> findRep = [&](const reg_t& reg) -> reg_t {
auto it = aliasMap.find(reg);
if (it == aliasMap.end()) return reg;
reg_t act = findRep(it->second);
aliasMap[reg] = act;
return act;
};
for (block_idx blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) {
const auto& block = func->blocks()[blockIdx];
for (auto& instr : block->instructions()) {
if (instr->type() != furlang::ir::instruction_t::Assign) continue;
auto& srcOp = *instr->sources().front();
if (srcOp.type() != furlang::ir::operand_t::Register ||
instr->destination().type() != furlang::ir::operand_t::Register)
continue;
reg_t dstReg = instr->destination().reg();
reg_t srcReg = srcOp.reg();
reg_t repSrc = findRep(srcReg);
reg_t repDst = findRep(dstReg);
if (repSrc == repDst) continue;
aliasMap[repDst] = repSrc;
}
}
for (block_idx blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) {
const auto& block = func->blocks()[blockIdx];
for (auto& instr : block->instructions()) {
for (auto& op : instr->sources()) {
if (op->type() != furlang::ir::operand_t::Register) continue;
op->reg() = findRep(op->reg());
}
if (instr->type() != furlang::ir::instruction_t::Phi) continue;
auto& phi = dynamic_cast<furlang::ir::phi_instruction&>(*instr);
for (auto& [op, label] : phi.labels()) {
if (op.type() != furlang::ir::operand_t::Register) continue;
op.reg() = findRep(op.reg());
}
}
for (auto& op : block->exit()->sources()) {
if (op->type() != furlang::ir::operand_t::Register) continue;
op->reg() = findRep(op->reg());
}
}
}
void ssa::dfs_rpo(furlang::ir::block_index block,
const block_map_t& successors,
std::unordered_set<furlang::ir::block_index>& visited,
std::vector<furlang::ir::block_index>& rpo) {
visited.insert(block);
if (auto it = successors.find(block); it != successors.end()) {
for (auto successor : it->second) {
if (visited.find(successor) == visited.end()) {
dfs_rpo(successor, successors, visited, rpo);
}
}
}
rpo.push_back(block);
}
} // namespace furc::front
+39 -6
View File
@@ -1,25 +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/ssa.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() {
private native func print(value: int32);
func main() -> int32 {
x = 0;
y = 10;
z = 1;
while (x < y) {
x = x + z;
}
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();
@@ -30,11 +38,17 @@ int main(void) {
const auto& program = *programResult;
program->accept(generator);
auto module = std::move(generator.move_module());
furc::front::ssa::optimize(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()) {
@@ -46,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";
+49
View File
@@ -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
+41 -11
View File
@@ -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;
};
+186 -51
View File
@@ -7,6 +7,7 @@
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
@@ -19,7 +20,19 @@ namespace ir {
enum class instruction_t {
Alloca, /**< Unused */
Assign, /**< Assign */
BinaryOp, /**< Binary operation */
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 */
@@ -27,6 +40,31 @@ enum class instruction_t {
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.
*
@@ -240,78 +278,112 @@ 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; }
bool has_destination() const override { return true; }
@@ -323,13 +395,6 @@ public:
std::vector<const operand*> sources() const override { return { &m_lhs, &m_rhs }; }
/**
* @brief Returns this instruction's operation type.
*
* @return The operation type.
*/
binary_op_instruction_t op_type() const { return m_type; }
/**
* @brief Returns this instruction's left-hand-side operand.
*
@@ -351,13 +416,13 @@ public:
*/
const operand& dst() const { return m_dst; }
private:
binary_op_instruction_t m_type;
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;
}
};
@@ -616,7 +681,7 @@ private:
std::vector<std::pair<operand, block_index>> m_labels;
protected:
std::ostream& print(std::ostream& os) const override {
os << "phi " << m_dst << " =";
os << m_dst << " = phi";
bool first = true;
for (const auto& pair : m_labels) {
if (!first) os << ',';
@@ -627,6 +692,76 @@ protected:
}
};
/**
* @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
+2 -2
View File
@@ -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.
+47
View File
@@ -0,0 +1,47 @@
#ifndef FURLANG_UTILITY_HASH_HPP
#define FURLANG_UTILITY_HASH_HPP
#include <cstddef>
#include <functional>
namespace furlang {
namespace utility {
// Source - https://stackoverflow.com/a/27952689
// Posted by Yakk - Adam Nevraumont, modified by community. See post 'Timeline' for change history
// Retrieved 2026-07-07, License - CC BY-SA 4.0
static inline std::size_t hash_combine(std::size_t lhs, std::size_t rhs) {
if constexpr (sizeof(std::size_t) >= 8) {
lhs ^= rhs + 0x517cc1b727220a95 + (lhs << 6) + (lhs >> 2);
} else {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
}
return lhs;
}
// Source - https://stackoverflow.com/a/20602159
// Posted by Casey, modified by community. See post 'Timeline' for change history
// Retrieved 2026-07-07, License - CC BY-SA 3.0
template <typename T, typename U, typename FirstHash = std::hash<T>, typename SecondHash = std::hash<U>>
struct pair_hash {
std::size_t operator()(const std::pair<T, U>& pair) const {
return hash_combine(FirstHash()(pair.first), SecondHash()(pair.second));
}
};
template <typename T, typename Hash = std::hash<T>>
struct vector_hash {
std::size_t operator()(const std::vector<T>& vec) const {
std::size_t seed = 0;
for (const auto& element : vec) {
hash_combine(seed, Hash()(element));
}
return seed;
}
};
} // namespace utility
} // namespace furlang
#endif // FURLANG_UTILITY_HASH_HPP
+1 -37
View File
@@ -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. */
};
+90 -61
View File
@@ -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)...);
}
/**
* @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;
std::queue<thing_handle> m_deadThings;
std::vector<void*> m_deadThingData;
furlang::arena m_thingArena;
thing_allocator<std::byte> m_thingAllocator;
class thing_type_store m_thingTypeStore;
};
} // namespace furvm
+39
View File
@@ -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
+69
View File
@@ -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;
+53 -55
View File
@@ -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;
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
+100 -74
View File
@@ -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_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
+12
View File
@@ -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
View File
@@ -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
+448
View File
@@ -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
+72 -9
View File
@@ -12,11 +12,34 @@ enum class instruction_t : byte {
NoOperation = 0,
/**
* @brief Pushes an integer from a byte onto the stack.
*
* Pushes an integer constructed from a next byte onto the stack.
* @brief Pushes an s8 integer from a byte onto the stack.
*/
PushB2I,
PushS8,
/**
* @brief Pushes an u8 integer from a byte onto the stack.
*/
PushU8,
/**
* @brief Pushes an s16 integer from two byte onto the stack.
*/
PushS16,
/**
* @brief Pushes an u16 integer from two byte onto the stack.
*/
PushU16,
/**
* @brief Pushes an s32 integer from a byte onto the stack.
*/
PushS32,
/**
* @brief Pushes an u32 integer from a byte onto the stack.
*/
PushU32,
/**
* @brief Pushes a constant onto the stack.
@@ -25,6 +48,24 @@ enum class instruction_t : byte {
*/
PushConstant,
/**
* @brief Pushes a new array onto the stack.
*
* Type is the next 4 bytes in little-endian.
* If the type is dynamic the array's size will be popped off of the stack.
*/
Array,
/**
* @brief Pushes an element from an array onto the stack.
*/
Get,
/**
* @brief Sets an array element.
*/
Set,
/**
* @brief Pops top element from the stack.
*/
@@ -35,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.
*/
@@ -95,6 +148,21 @@ enum class instruction_t : byte {
*/
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.
*
@@ -135,11 +203,6 @@ enum class instruction_t : byte {
* @brief Pops the current call frame.
*/
Return,
/**
* @brief Pops the current call frame and pushes the first element from the previous stack onto the new stack.
*/
ReturnValue,
};
struct instruction {
+324 -18
View File
@@ -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;
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
-20
View File
@@ -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 -207
View File
@@ -1,262 +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.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator+(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Subtracts two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator-(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Multiplies two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator*(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Divides two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator/(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Modulos two things together.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator%(const thing_p& lhs, const thing_p& rhs);
/**
* @brief Compares two things for equality.
*
* @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 Compares two things for equality.
*
* @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 Compares if the left thing is less than the right thing.
*
* @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 Compares if the left thing is greater than the right thing.
*
* @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 Compares if the left thing is less than or equal to the right thing.
*
* @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 Compares if the left thing is greater than or equal to the right thing.
*
* @param lhs Left-hand-side thing.
* @param rhs Right-hand-side thing.
* @return Shared pointer to result thing.
*/
friend thing_p operator>=(const thing_p& lhs, const thing_p& rhs);
public:
/**
* @brief Returns a new thing.
*
* @param context Furvm context.
* @param args Arguments to forward to the thing constructor.
* @return Shared pointer to the new thing.
*/
template <typename... Args,
typename = std::enable_if_t<std::is_constructible_v<thing, rzecz, thing_handle, Args..., const context_p&>>>
static thing_p create(const context_p& context, Args&&... args) {
thing_handle id = context->m_things.size();
if (!context->m_deadThings.empty()) {
id = context->m_deadThings.front();
context->m_deadThings.pop();
id += 1 << GENERATION_SIZE;
}
thing_handle idx = id & ((1ULL << ((sizeof(id) * 8) - GENERATION_SIZE)) - 1);
auto th = std::make_shared<thing>(rzecz{}, id, std::forward<Args>(args)..., context);
context->m_things.emplace(context->m_things.begin() + idx, th);
return std::move(th);
}
/**
* @brief Returns a clone of the thing.
*
* @param thing Thing to clone.
* @return Shared pointer to a clone of the thing.
* @return A clone of this thing.
*/
static thing_p clone(const thing_p& thing);
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 an int32 value from this thing.
* @brief Returns the thing's type.
*
* @return The type.
*/
constexpr thing_type type() const { return m_type; }
/**
* @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.
*/
std::int32_t& int32();
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 an int32 value from this thing.
* @brief Returns the thing's value.
*
* @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
+111
View File
@@ -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
-16
View File
@@ -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
+127
View File
@@ -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
+199 -74
View File
@@ -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,63 +198,111 @@ 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 == rhs);
push_thing(lhs->equals(*rhs));
} break;
case instruction_t::NotEquals: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs != rhs);
push_thing(lhs->not_equals(*rhs));
} break;
case instruction_t::LessThan: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs < rhs);
push_thing(lhs->less_than(*rhs));
} break;
case instruction_t::GreaterThan: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs > rhs);
push_thing(lhs->greater_than(*rhs));
} break;
case instruction_t::LessEqual: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs <= rhs);
push_thing(lhs->less_equals(*rhs));
} break;
case instruction_t::GreaterEqual: {
auto rhs = pop_thing();
auto lhs = pop_thing();
push_thing(lhs >= rhs);
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)) |
@@ -179,37 +317,24 @@ void executor::step() {
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)) |
function_id funcId = static_cast<std::uint16_t>(frame.mod->byte(frame.position)) |
(static_cast<std::uint16_t>(frame.mod->byte(frame.position + 1)) << 8);
frame.position += 2;
const function_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;
}
push_frame(frame.mod, *frame.mod->function_at(funcId));
} break;
case instruction_t::Jump: {
frame.position += frame.mod->byte(frame.position++);
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->int32() != 0) frame.position += offset;
if (cond->integer() != 0) frame.position += (std::int8_t)offset;
} break;
case instruction_t::Return: {
pop_frame();
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
} break;
case instruction_t::ReturnValue: {
auto value = pop_thing();
pop_frame();
push_thing(std::move(value));
} break;
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
default: throw std::runtime_error("unknown instruction");
}
+69 -11
View File
@@ -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;
@@ -18,9 +32,9 @@ function::function(function&& other) noexcept
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
case function_t::Import: {
m_value.imp.mod = other.m_value.imp.mod;
m_value.imp.function = other.m_value.imp.function;
new (&m_value.imp) import_function(std::move(other.m_value.imp));
} break;
default: break;
}
other.m_value.position = 0;
}
@@ -28,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_signature = other.m_signature;
switch (m_type) {
case function_t::Normal: {
m_value.position = other.m_value.position;
@@ -39,13 +52,58 @@ function& function::operator=(function&& other) noexcept {
new (&m_value.native) native_function(std::move(other.m_value.native));
} break;
case function_t::Import: {
m_value.imp.mod = other.m_value.imp.mod;
m_value.imp.function = other.m_value.imp.function;
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
View File
@@ -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
View File
@@ -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
-101
View File
@@ -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
-243
View File
@@ -1,243 +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 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() = static_cast<std::int32_t>(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() = static_cast<std::int32_t>(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() = static_cast<std::int32_t>(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() = static_cast<std::int32_t>(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() = static_cast<std::int32_t>(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() = static_cast<std::int32_t>(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
View File
@@ -1,3 +1,28 @@
#include "furlang/arena.hpp"
#include "furvm/furvm.hpp"
#include "furvm/thing.hpp"
#include "furvm/thing_allocator.hpp"
#include "gtest/gtest.h" // IWYU pragma: keep
namespace {}
namespace {
// TODO: Basic program tests (e.g. for loops)
TEST(Things, Ops) {
furlang::arena arena;
furvm::thing_allocator<std::byte> alloc{ arena };
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
lhs.get<furvm::thing_type::u32>() = 6;
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
rhs.get<furvm::thing_type::u32>() = 7;
auto res = lhs.add(rhs);
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
EXPECT_EQ(lhs.get<furvm::thing_type::u32>(), 6);
EXPECT_EQ(rhs.get<furvm::thing_type::u32>(), 7);
EXPECT_EQ(res.get<furvm::thing_type::u32>(), 6 + 7);
}
} // namespace