Compare commits
27 Commits
5efcf1b9b0
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b98f77c08
|
|||
|
959d0a7773
|
|||
|
70375af068
|
|||
|
46d0de759a
|
|||
|
01964a495c
|
|||
|
21fb587e36
|
|||
|
fe65a09af9
|
|||
|
712a0d7047
|
|||
|
35d7fc4970
|
|||
|
9bf4da4fd9
|
|||
|
6c7d16631d
|
|||
|
24a9530abc
|
|||
|
0354ec1e79
|
|||
|
ddb60ca93a
|
|||
|
3aaed55485
|
|||
|
cfefca49ee
|
|||
|
21e963168e
|
|||
|
b067bbb4ea
|
|||
|
fe37bf8058
|
|||
|
977b22d8f5
|
|||
|
30a06186db
|
|||
|
5e425d7fe4
|
|||
|
12a131c2b8
|
|||
|
5403c0e1a4
|
|||
|
a1dac8ae3f
|
|||
|
a311795915
|
|||
|
090d8ad81e
|
+3
-1
@@ -17,6 +17,7 @@ Checks: >
|
|||||||
-readability-redundant-access-specifiers,
|
-readability-redundant-access-specifiers,
|
||||||
-readability-use-anyofallof,
|
-readability-use-anyofallof,
|
||||||
-readability-named-parameter,
|
-readability-named-parameter,
|
||||||
|
-readability-convert-member-functions-to-static,
|
||||||
-performance-enum-size,
|
-performance-enum-size,
|
||||||
-cppcoreguidelines-avoid-magic-numbers,
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||||
@@ -33,7 +34,8 @@ Checks: >
|
|||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
-cppcoreguidelines-use-enum-class,
|
-cppcoreguidelines-use-enum-class,
|
||||||
-bugprone-forward-declaration-namespace,
|
-bugprone-forward-declaration-namespace,
|
||||||
-bugprone-tagged-union-member-count
|
-bugprone-tagged-union-member-count,
|
||||||
|
-bugprone-unchecked-optional-access
|
||||||
|
|
||||||
WarningsAsErrors: "*"
|
WarningsAsErrors: "*"
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
[submodule "furdb/deps/isocline"]
|
[submodule "deps/isocline"]
|
||||||
path = furdb/deps/isocline
|
path = deps/isocline
|
||||||
url = https://github.com/daanx/isocline
|
url = https://github.com/daanx/isocline
|
||||||
|
|||||||
+10
-6
@@ -11,12 +11,16 @@ find_package(Doxygen)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
add_subdirectory(furlang)
|
add_subdirectory(src/furlang)
|
||||||
add_subdirectory(furvm)
|
add_subdirectory(src/furvm)
|
||||||
add_subdirectory(furc)
|
add_subdirectory(src/furc)
|
||||||
add_subdirectory(furas)
|
add_subdirectory(src/furas)
|
||||||
add_subdirectory(disfuras)
|
add_subdirectory(src/disfuras)
|
||||||
add_subdirectory(furdb)
|
add_subdirectory(src/furdb)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
set(IC_USE_CXX ON)
|
||||||
|
add_subdirectory(${CMAKE_SOURCE_DIR}/deps/isocline)
|
||||||
|
|
||||||
if(DOXYGEN_FOUND)
|
if(DOXYGEN_FOUND)
|
||||||
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
file(GLOB_RECURSE DISFURAS_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE DISFURAS_HDRS "include/**.hpp")
|
|
||||||
add_executable(disfuras ${DISFURAS_SRCS} ${DISFURAS_HDRS})
|
|
||||||
target_include_directories(disfuras PRIVATE include/)
|
|
||||||
target_link_libraries(disfuras PUBLIC furlang libfurc libfurvm)
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
type arr = array $s8 10
|
type arr = array $s8 10
|
||||||
|
type arrSlice = slice $s8
|
||||||
|
|
||||||
func println ref $arr = native println
|
func println $arrSlice = native println
|
||||||
|
|
||||||
public func main = #main
|
public func main = #main
|
||||||
main:
|
main:
|
||||||
@@ -30,5 +31,8 @@ body:
|
|||||||
end:
|
end:
|
||||||
drop
|
drop
|
||||||
load %0
|
load %0
|
||||||
call ref $arr println
|
push $u32 0
|
||||||
|
load %1
|
||||||
|
slice
|
||||||
|
call $arrSlice println
|
||||||
ret
|
ret
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
file(GLOB_RECURSE FURC_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE FURC_HDRS "include/**.hpp")
|
|
||||||
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 libfurvm)
|
|
||||||
add_executable(furc src/main.cpp)
|
|
||||||
target_link_libraries(furc PRIVATE libfurc)
|
|
||||||
|
|
||||||
include(GoogleTest)
|
|
||||||
|
|
||||||
file(GLOB_RECURSE FURC_TESTS "test/**.cpp")
|
|
||||||
add_executable(furc_tests ${FURC_TESTS})
|
|
||||||
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
|
|
||||||
# gtest_discover_tests(furc_tests)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#ifndef FURC_MIDDLE_SSA_HPP
|
|
||||||
#define FURC_MIDDLE_SSA_HPP
|
|
||||||
|
|
||||||
#include "furc/middle/ir.hpp"
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
|
|
||||||
class ssa {
|
|
||||||
ssa() = delete;
|
|
||||||
public:
|
|
||||||
static void process(ir_module& mod);
|
|
||||||
static void destruct(ir_module& mod);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furc
|
|
||||||
|
|
||||||
#endif // FURC_MIDDLE_SSA_HPP
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
/**
|
|
||||||
* Sources:
|
|
||||||
* - Practical Improvements to the Construction and Deconstruction of Static Single Assignment Form:
|
|
||||||
* https://web.archive.org/web/20100607003509/http://www.cs.rice.edu/~harv/my_papers/ssa.pdf
|
|
||||||
* - A Simple, Fast Dominance Algorithm:
|
|
||||||
* https://www.researchgate.net/publication/2569680_A_Simple_Fast_Dominance_Algorithm
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "furc/middle/ssa.hpp"
|
|
||||||
|
|
||||||
#include "furc/middle/ir.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace furc {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
struct block_info {
|
|
||||||
std::size_t order = 0;
|
|
||||||
|
|
||||||
std::unordered_set<std::size_t> preds;
|
|
||||||
std::unordered_set<std::size_t> sucs;
|
|
||||||
std::size_t idom = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void rpo_dfs(std::unordered_set<std::size_t>& visited,
|
|
||||||
std::vector<std::size_t>& order,
|
|
||||||
std::size_t block,
|
|
||||||
std::vector<block_info>& blocks) {
|
|
||||||
visited.insert(block);
|
|
||||||
for (auto succ : blocks[block].sucs) {
|
|
||||||
if (visited.find(succ) != visited.end()) continue;
|
|
||||||
rpo_dfs(visited, order, succ, blocks);
|
|
||||||
}
|
|
||||||
order.push_back(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
void compute_rpo(std::vector<block_info>& blocks, std::vector<std::size_t>& order) {
|
|
||||||
std::unordered_set<std::size_t> visited;
|
|
||||||
if (!blocks.empty()) rpo_dfs(visited, order, 0, blocks);
|
|
||||||
std::reverse(order.begin(), order.begin());
|
|
||||||
for (std::size_t i = 0; i < order.size(); ++i) {
|
|
||||||
blocks[order[i]].order = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t intersect(std::vector<block_info>& blocks, std::size_t b1, std::size_t b2) {
|
|
||||||
std::size_t finger1 = b1;
|
|
||||||
std::size_t finger2 = b2;
|
|
||||||
while (finger1 != finger2) {
|
|
||||||
while (finger1 < finger2)
|
|
||||||
finger1 = blocks[finger1].idom;
|
|
||||||
while (finger2 < finger1)
|
|
||||||
finger2 = blocks[finger2].idom;
|
|
||||||
}
|
|
||||||
return finger1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_function(ir_function& func) {
|
|
||||||
std::vector<block_info> blocks(func.blocks.size());
|
|
||||||
|
|
||||||
// 1. Compute CFG
|
|
||||||
for (std::size_t i = 0; i < func.blocks.size(); ++i) {
|
|
||||||
const auto& block = func.blocks[i];
|
|
||||||
if (block.instructions.empty()) continue;
|
|
||||||
|
|
||||||
const auto& termInstr = block.instructions.back();
|
|
||||||
switch (termInstr.type) {
|
|
||||||
case ir_instruction::Branch: {
|
|
||||||
const auto& src = termInstr.sources.front();
|
|
||||||
if (src.type != ir_operand::Block) throw std::runtime_error("invalid operand");
|
|
||||||
blocks[src.value.block].preds.insert(i);
|
|
||||||
blocks[i].sucs.insert(src.value.block);
|
|
||||||
} break;
|
|
||||||
case ir_instruction::BranchCond: {
|
|
||||||
const auto& src = termInstr.sources.front();
|
|
||||||
if (src.type != ir_operand::Block) throw std::runtime_error("invalid operand");
|
|
||||||
blocks[src.value.blockPair.first].preds.insert(i);
|
|
||||||
blocks[src.value.blockPair.second].preds.insert(i);
|
|
||||||
blocks[i].preds.insert(src.value.blockPair.first);
|
|
||||||
blocks[i].preds.insert(src.value.blockPair.second);
|
|
||||||
} break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Computing dominance tree
|
|
||||||
std::vector<std::size_t> order;
|
|
||||||
order.reserve(blocks.size());
|
|
||||||
compute_rpo(blocks, order);
|
|
||||||
|
|
||||||
blocks[order.front()].idom = order.front();
|
|
||||||
|
|
||||||
bool changed = true;
|
|
||||||
while (changed) {
|
|
||||||
changed = false;
|
|
||||||
|
|
||||||
for (std::size_t i = 1; i < order.size(); ++i) {
|
|
||||||
auto& block = blocks[order[i]];
|
|
||||||
std::size_t newIdom = -1;
|
|
||||||
bool found = false;
|
|
||||||
for (auto pred : block.preds) {
|
|
||||||
if (blocks[pred].idom == -1) continue;
|
|
||||||
newIdom = found ? intersect(blocks, pred, newIdom) : pred;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.idom != newIdom) {
|
|
||||||
block.idom = newIdom;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void ssa::process(ir_module& mod) {
|
|
||||||
for (auto* func : mod.functions)
|
|
||||||
process_function(*func);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssa::destruct(ir_module& mod) {}
|
|
||||||
|
|
||||||
} // namespace furc
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
set(IC_USE_CXX ON)
|
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/deps/isocline)
|
|
||||||
|
|
||||||
file(GLOB_RECURSE FURDB_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE FURDB_HDRS "include/**.hpp")
|
|
||||||
add_executable(furdb ${FURDB_SRCS} ${FURDB_HDRS})
|
|
||||||
target_include_directories(furdb PRIVATE include/)
|
|
||||||
target_link_libraries(furdb PUBLIC furlang libfurc libfurvm isocline)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
file(GLOB_RECURSE FURLANG_SRCS "src/**.cpp")
|
|
||||||
file(GLOB_RECURSE FURLANG_HDRS "include/**.hpp")
|
|
||||||
add_library(furlang STATIC ${FURLANG_SRCS} ${FURLANG_HDRS})
|
|
||||||
target_include_directories(furlang PUBLIC include/)
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#ifndef FURVM_CONSTANT_HPP
|
|
||||||
#define FURVM_CONSTANT_HPP
|
|
||||||
|
|
||||||
#include "furvm/exceptions.hpp"
|
|
||||||
#include "furvm/fwd.hpp"
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace furvm {
|
|
||||||
|
|
||||||
enum class constant_t : std::uint8_t {
|
|
||||||
String, /**< String constant. */
|
|
||||||
};
|
|
||||||
|
|
||||||
class constant {
|
|
||||||
public:
|
|
||||||
using string_type = std::string_view; /**< String constant type. */
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new string constant.
|
|
||||||
*
|
|
||||||
* @param string String.
|
|
||||||
*/
|
|
||||||
constant(string_type string)
|
|
||||||
: m_type(constant_t::String), m_value(string) {}
|
|
||||||
|
|
||||||
~constant() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
constant(constant&&) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Move constructor.
|
|
||||||
*/
|
|
||||||
constant& operator=(constant&&) = default;
|
|
||||||
|
|
||||||
constant(const constant&) = delete;
|
|
||||||
constant& operator=(const constant&) = delete;
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Returns this constant's type.
|
|
||||||
* @see constant_t
|
|
||||||
*
|
|
||||||
* @return The constant type.
|
|
||||||
*/
|
|
||||||
constexpr constant_t type() const { return m_type; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns this constant's string value.
|
|
||||||
* @throws bad_constant_access if this constant's type is not constant_t::String.
|
|
||||||
*
|
|
||||||
* @return The string value.
|
|
||||||
*/
|
|
||||||
constexpr string_type string() const {
|
|
||||||
require_type(constant_t::String);
|
|
||||||
return m_value.string;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
void require_type(constant_t type) const {
|
|
||||||
if (m_type != type) throw bad_constant_access();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
constant_t m_type{};
|
|
||||||
union value {
|
|
||||||
string_type string;
|
|
||||||
|
|
||||||
value(string_type sv)
|
|
||||||
: string(sv) {}
|
|
||||||
} m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace furvm
|
|
||||||
|
|
||||||
#endif // FURVM_CONSTANT_HPP
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#include "furlang/arena.hpp"
|
|
||||||
#include "furvm/furvm.hpp"
|
|
||||||
#include "furvm/thing.hpp"
|
|
||||||
|
|
||||||
#include "gtest/gtest.h" // IWYU pragma: keep
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// TODO: Basic program tests (e.g. for loops)
|
|
||||||
|
|
||||||
TEST(Things, Ops) {
|
|
||||||
furlang::arena arena;
|
|
||||||
|
|
||||||
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
|
||||||
lhs.get<furvm::thing_type::u32>() = 6;
|
|
||||||
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
|
||||||
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
|
|
||||||
@@ -24,7 +24,7 @@ struct lexer_error {
|
|||||||
std::string message;
|
std::string message;
|
||||||
};
|
};
|
||||||
|
|
||||||
using token_r = furlang::result<token, lexer_error>;
|
using token_r = furlang::result<lexer_error, token>;
|
||||||
|
|
||||||
class lexer {
|
class lexer {
|
||||||
public:
|
public:
|
||||||
@@ -34,6 +34,7 @@ struct token {
|
|||||||
// Instructions
|
// Instructions
|
||||||
Push,
|
Push,
|
||||||
Array,
|
Array,
|
||||||
|
Slice,
|
||||||
Get,
|
Get,
|
||||||
Set,
|
Set,
|
||||||
Drop,
|
Drop,
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef FURC_BACK_FURVM_HPP
|
||||||
|
#define FURC_BACK_FURVM_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class furvm_generator final {
|
||||||
|
public:
|
||||||
|
static furvm::mod generate(const ir_module& mod);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_BACK_FURVM_HPP
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -33,6 +34,16 @@ struct ir_operand {
|
|||||||
struct register_s {
|
struct register_s {
|
||||||
std::uint64_t name : 54;
|
std::uint64_t name : 54;
|
||||||
std::uint64_t ver : 10;
|
std::uint64_t ver : 10;
|
||||||
|
|
||||||
|
register_s() = default;
|
||||||
|
|
||||||
|
register_s(std::uint64_t id)
|
||||||
|
: name((id >> 10) & ((1ULL << 54) - 1)), ver((id >> 0) & ((1 << 10) - 1)) {}
|
||||||
|
|
||||||
|
register_s(std::uint64_t name, std::uint64_t ver)
|
||||||
|
: name(name), ver(ver) {}
|
||||||
|
|
||||||
|
operator std::uint64_t() const { return name << 10 | ver; }
|
||||||
} reg;
|
} reg;
|
||||||
std::uint16_t variable;
|
std::uint16_t variable;
|
||||||
std::uint16_t global;
|
std::uint16_t global;
|
||||||
@@ -55,6 +66,9 @@ struct ir_operand {
|
|||||||
value_u(std::uint16_t variable)
|
value_u(std::uint16_t variable)
|
||||||
: variable(variable) {}
|
: variable(variable) {}
|
||||||
|
|
||||||
|
value_u(register_s reg)
|
||||||
|
: reg(reg) {}
|
||||||
|
|
||||||
value_u(std::uint64_t first, std::uint64_t second)
|
value_u(std::uint64_t first, std::uint64_t second)
|
||||||
: blockPair({ first, second }) {}
|
: blockPair({ first, second }) {}
|
||||||
|
|
||||||
@@ -65,6 +79,30 @@ struct ir_operand {
|
|||||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_u, Args...>>>
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_u, Args...>>>
|
||||||
ir_operand(type_e type, Args&&... args)
|
ir_operand(type_e type, Args&&... args)
|
||||||
: type(type), value(std::forward<Args>(args)...) {}
|
: type(type), value(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
static ir_operand reg(std::uint64_t name, std::uint64_t ver) {
|
||||||
|
return { Register, value_u::register_s{ name, ver } };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ir_operand& rhs) const {
|
||||||
|
if (type != rhs.type) return false;
|
||||||
|
switch (type) {
|
||||||
|
case Integer: return value.integer == rhs.value.integer;
|
||||||
|
case Register: return value.reg.name == rhs.value.reg.name && value.reg.ver == rhs.value.reg.ver;
|
||||||
|
case Variable: return value.variable == rhs.value.variable;
|
||||||
|
case Global: return value.global == rhs.value.global;
|
||||||
|
case Function: return value.function == rhs.value.function;
|
||||||
|
case Block: return value.block == rhs.value.block;
|
||||||
|
case BlockPair:
|
||||||
|
return value.blockPair.first == rhs.value.blockPair.first &&
|
||||||
|
value.blockPair.second == rhs.value.blockPair.second;
|
||||||
|
case PhiPair:
|
||||||
|
return value.phiPair.block == rhs.value.phiPair.block &&
|
||||||
|
value.phiPair.reg.name == rhs.value.phiPair.reg.name &&
|
||||||
|
value.phiPair.reg.ver == rhs.value.phiPair.reg.ver;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ir_type {
|
struct ir_type {
|
||||||
@@ -139,6 +177,10 @@ struct ir_instruction {
|
|||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const ir_instruction& rhs) const {
|
||||||
|
return type == rhs.type && destination == rhs.destination && sources == rhs.sources;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ir_basic_block {
|
struct ir_basic_block {
|
||||||
@@ -225,9 +267,10 @@ struct ir_function : ir_scope {
|
|||||||
std::vector<ir_basic_block> blocks;
|
std::vector<ir_basic_block> blocks;
|
||||||
|
|
||||||
std::uint64_t regCount = 0;
|
std::uint64_t regCount = 0;
|
||||||
|
std::uint64_t varCount = 0;
|
||||||
|
|
||||||
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
|
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
|
||||||
return variables[name] = arena.allocate<ir_function_variable>(type, regCount++);
|
return variables[name] = arena.allocate<ir_function_variable>(type, varCount++);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ir_function from_name(std::string&& name) {
|
static ir_function from_name(std::string&& name) {
|
||||||
@@ -304,7 +347,11 @@ struct ir_context {
|
|||||||
|
|
||||||
void terminate() { add_instr(ir_instruction::Return); }
|
void terminate() { add_instr(ir_instruction::Return); }
|
||||||
|
|
||||||
void terminate(ir_operand value) { add_instr(ir_instruction::Return, value); }
|
void terminate(ir_operand value) {
|
||||||
|
ir_instruction instr = { ir_instruction::Return };
|
||||||
|
instr.sources.emplace_back(value);
|
||||||
|
add_instr(std::move(instr));
|
||||||
|
}
|
||||||
|
|
||||||
void terminate(std::uint64_t block) { add_instr(ir_instruction::Branch, ir_operand{ ir_operand::Block, block }); }
|
void terminate(std::uint64_t block) { add_instr(ir_instruction::Branch, ir_operand{ ir_operand::Block, block }); }
|
||||||
|
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* SSA destruction (out-of-SSA phase) for register-based targets based on "Mechanizing Conventional SSA for a
|
||||||
|
* Verified Destruction with Coalescing" by Delphine Demange and Yon Fernandez de Retana
|
||||||
|
* (https://dl.acm.org/doi/pdf/10.1145/2892208.2892222 09/11/2026).
|
||||||
|
*/
|
||||||
|
#ifndef FURC_MIDDLE_REG_GEN_HPP
|
||||||
|
#define FURC_MIDDLE_REG_GEN_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furc/middle/ssa.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class reg_gen {
|
||||||
|
public:
|
||||||
|
class disjoint_set {
|
||||||
|
public:
|
||||||
|
std::uint64_t find(std::uint64_t var) {
|
||||||
|
if (m_parents.find(var) == m_parents.end()) m_parents.emplace(var, var);
|
||||||
|
if (m_parents[var] == var) return var;
|
||||||
|
return find(m_parents[var]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unite(std::uint64_t var1, std::uint64_t var2) {
|
||||||
|
std::uint64_t rep1 = find(var1);
|
||||||
|
std::uint64_t rep2 = find(var2);
|
||||||
|
if (rep1 != rep2) m_parents[rep1] = rep2;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::uint64_t, std::uint64_t> m_parents;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
struct block_info {
|
||||||
|
std::unordered_set<std::uint64_t> defs;
|
||||||
|
std::unordered_set<std::uint64_t> uses;
|
||||||
|
std::unordered_set<std::uint64_t> liveIn;
|
||||||
|
std::unordered_set<std::uint64_t> liveOut;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
reg_gen(ir_function& func, ssa& ssa) {
|
||||||
|
std::vector<block_info> lifeBlocks;
|
||||||
|
live_analysis(lifeBlocks, func.blocks, ssa.cfgBlocks);
|
||||||
|
remove_interference(func.blocks, lifeBlocks);
|
||||||
|
merge(func.blocks);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static void live_analysis(std::vector<block_info>& lifeBlocks,
|
||||||
|
const std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<ssa::cfg_block>& cfgBlocks) {
|
||||||
|
lifeBlocks.resize(irBlocks.size());
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& irBlock = irBlocks[i];
|
||||||
|
auto& block = lifeBlocks[i];
|
||||||
|
|
||||||
|
for (const auto& instr : irBlock.instructions) {
|
||||||
|
for (const auto& op : instr.sources) {
|
||||||
|
if (op.type != ir_operand::Register) continue;
|
||||||
|
if (block.defs.find(op.value.reg) != block.defs.end()) continue;
|
||||||
|
block.uses.insert(op.value.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||||
|
block.defs.insert(instr.destination->value.reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& irBlock = irBlocks[i];
|
||||||
|
auto& block = lifeBlocks[i];
|
||||||
|
|
||||||
|
std::unordered_set<std::uint64_t> newSet;
|
||||||
|
for (auto succ : cfgBlocks[i].sucs) {
|
||||||
|
newSet.insert(lifeBlocks[succ].liveIn.begin(), lifeBlocks[succ].liveIn.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSet != block.liveOut) {
|
||||||
|
block.liveOut = newSet;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSet.clear();
|
||||||
|
newSet.insert(block.uses.begin(), block.uses.end());
|
||||||
|
for (const auto& var : block.liveOut) {
|
||||||
|
if (block.defs.find(var) != block.defs.end()) continue;
|
||||||
|
newSet.insert(var);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSet != block.liveIn) {
|
||||||
|
block.liveIn = newSet;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_interference(std::vector<ir_basic_block>& irBlocks, const std::vector<block_info>& lifeBlocks) {
|
||||||
|
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
auto& irBlock = irBlocks[i];
|
||||||
|
|
||||||
|
for (auto it = irBlock.instructions.begin(), end = irBlock.instructions.end();
|
||||||
|
it != end && it->type == ir_instruction::Phi;
|
||||||
|
++it) {
|
||||||
|
assert(it->destination.has_value() && it->destination->type == ir_operand::Register);
|
||||||
|
const auto& phiDst = it->destination->value.reg;
|
||||||
|
|
||||||
|
for (auto& op : it->sources) {
|
||||||
|
assert(op.type == ir_operand::PhiPair);
|
||||||
|
const auto& predBlock = lifeBlocks[op.value.phiPair.block];
|
||||||
|
auto& phiArg = op.value.phiPair.reg;
|
||||||
|
if (predBlock.liveOut.count(phiArg) == 0 || phiArg == phiDst) continue;
|
||||||
|
|
||||||
|
auto oldArg = phiArg;
|
||||||
|
phiArg.ver = 0; // TODO: Allocate temporary registers
|
||||||
|
|
||||||
|
auto& irBlock = irBlocks[op.value.phiPair.block];
|
||||||
|
assert(!irBlock.instructions.empty());
|
||||||
|
auto it = irBlock.instructions.end() - 1;
|
||||||
|
if (ir_instruction::is_terminating(it->type)) --it;
|
||||||
|
irBlock.instructions.emplace(it,
|
||||||
|
ir_instruction{ ir_instruction::Move,
|
||||||
|
ir_operand::reg(phiArg.name, phiArg.ver),
|
||||||
|
{ ir_operand::reg(oldArg.name, oldArg.ver) } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void merge(std::vector<ir_basic_block>& irBlocks) {
|
||||||
|
disjoint_set dj;
|
||||||
|
|
||||||
|
for (const auto& block : irBlocks) {
|
||||||
|
for (const auto& instr : block.instructions) {
|
||||||
|
if (instr.type != ir_instruction::Phi) break;
|
||||||
|
|
||||||
|
assert(instr.destination.has_value() && instr.destination->type == ir_operand::Register);
|
||||||
|
const auto& phiDst = instr.destination->value.reg;
|
||||||
|
dj.find(phiDst);
|
||||||
|
|
||||||
|
for (const auto& op : instr.sources) {
|
||||||
|
assert(op.type == ir_operand::PhiPair);
|
||||||
|
const auto& predBlock = op.value.phiPair.block;
|
||||||
|
const auto& phiArg = op.value.phiPair.reg;
|
||||||
|
dj.unite(phiDst, phiArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& block : irBlocks) {
|
||||||
|
auto it = block.instructions.begin();
|
||||||
|
while (it != block.instructions.end() && it->type == ir_instruction::Phi) {
|
||||||
|
it = block.instructions.erase(it);
|
||||||
|
}
|
||||||
|
for (; it != block.instructions.end(); ++it) {
|
||||||
|
for (auto& op : it->sources) {
|
||||||
|
if (op.type != ir_operand::Register) continue;
|
||||||
|
op.value.reg = dj.find(op.value.reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!it->destination.has_value() || it->destination->type != ir_operand::Register) continue;
|
||||||
|
it->destination->value.reg = dj.find(it->destination->value.reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_MIDDLE_REG_GEN_HPP
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#ifndef FURC_MIDDLE_SSA_HPP
|
||||||
|
#define FURC_MIDDLE_SSA_HPP
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
class ssa {
|
||||||
|
public:
|
||||||
|
struct cfg_block {
|
||||||
|
std::unordered_set<std::uint64_t> preds;
|
||||||
|
std::unordered_set<std::uint64_t> sucs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ssa_block {
|
||||||
|
std::size_t order = 0;
|
||||||
|
|
||||||
|
std::uint64_t idom = -1;
|
||||||
|
|
||||||
|
std::unordered_set<std::uint64_t> children; // Children of the block in dominator tree
|
||||||
|
|
||||||
|
// Dominance Frontiers
|
||||||
|
std::unordered_set<std::uint64_t> df;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct register_info {
|
||||||
|
std::unordered_set<std::uint64_t> sites; // Definition Sites
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
ssa(ir_function& func) {
|
||||||
|
registers.resize(func.regCount);
|
||||||
|
compute_cfg(func.blocks, cfgBlocks);
|
||||||
|
collect_registers(func.blocks, registers, globals);
|
||||||
|
|
||||||
|
std::vector<std::uint64_t> order;
|
||||||
|
compute_rpo(cfgBlocks, ssaBlocks, order);
|
||||||
|
|
||||||
|
build_dtree(cfgBlocks, ssaBlocks, order);
|
||||||
|
compute_dfrontiers(cfgBlocks, ssaBlocks);
|
||||||
|
|
||||||
|
ssaification(func.blocks, cfgBlocks, ssaBlocks, registers, globals);
|
||||||
|
rename(func.blocks, func.regCount, cfgBlocks, ssaBlocks, order);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
static void compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks);
|
||||||
|
|
||||||
|
static void collect_registers(const std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::vector<register_info>& registers,
|
||||||
|
std::unordered_set<std::uint64_t>& globals);
|
||||||
|
|
||||||
|
static void build_dtree(const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::size_t>& order);
|
||||||
|
|
||||||
|
static void compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks);
|
||||||
|
|
||||||
|
static void compute_rpo(std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::vector<std::size_t>& order);
|
||||||
|
|
||||||
|
static void ssaification(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<register_info>& registers,
|
||||||
|
const std::unordered_set<std::uint64_t>& globals);
|
||||||
|
|
||||||
|
static void rename(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::size_t regCount,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::uint64_t>& order);
|
||||||
|
private:
|
||||||
|
static void rename_rec(std::vector<std::uint64_t>& counters,
|
||||||
|
std::vector<std::stack<std::uint64_t>>& stacks,
|
||||||
|
std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::size_t blockIdx);
|
||||||
|
private:
|
||||||
|
static void rpo_dfs(std::unordered_set<std::size_t>& visited,
|
||||||
|
std::vector<std::size_t>& order,
|
||||||
|
std::size_t block,
|
||||||
|
const std::vector<cfg_block>& blocks);
|
||||||
|
|
||||||
|
static std::size_t intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2);
|
||||||
|
public:
|
||||||
|
std::vector<cfg_block> cfgBlocks;
|
||||||
|
std::vector<ssa_block> ssaBlocks;
|
||||||
|
std::vector<register_info> registers;
|
||||||
|
std::unordered_set<std::uint64_t> globals;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
|
|
||||||
|
#endif // FURC_MIDDLE_SSA_HPP
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
#define FURLANG_RESULT_HPP
|
#define FURLANG_RESULT_HPP
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace furlang {
|
namespace furlang {
|
||||||
@@ -43,6 +45,8 @@ public:
|
|||||||
const char* what() const noexcept override { return "bad result access"; }
|
const char* what() const noexcept override { return "bad result access"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct error_tag {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Result.
|
* @brief Result.
|
||||||
*
|
*
|
||||||
@@ -51,7 +55,7 @@ public:
|
|||||||
* @tparam R Value type.
|
* @tparam R Value type.
|
||||||
* @tparam E Error type.
|
* @tparam E Error type.
|
||||||
*/
|
*/
|
||||||
template <typename R, typename E>
|
template <typename E, typename R = void>
|
||||||
class result {
|
class result {
|
||||||
public:
|
public:
|
||||||
using value_type = std::remove_reference_t<R>; /**< Value type. */
|
using value_type = std::remove_reference_t<R>; /**< Value type. */
|
||||||
@@ -63,6 +67,10 @@ public:
|
|||||||
using error_reference = error_type&; /**< Error reference type. */
|
using error_reference = error_type&; /**< Error reference type. */
|
||||||
using error_const_reference = const error_type&; /**< Error const reference type. */
|
using error_const_reference = const error_type&; /**< Error const reference type. */
|
||||||
public:
|
public:
|
||||||
|
template <typename Other>
|
||||||
|
result(const result<E, Other>& error)
|
||||||
|
: result(error_tag{}, error.error()) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new result.
|
* @brief Construct a new result.
|
||||||
*
|
*
|
||||||
@@ -82,7 +90,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param args Variadic arguments to construct the value with.
|
* @param args Variadic arguments to construct the value with.
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
|
||||||
result(Args&&... args) {
|
result(Args&&... args) {
|
||||||
new (&m_value.result) value_type(std::forward<Args>(args)...);
|
new (&m_value.result) value_type(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
@@ -92,7 +100,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param error Error to copy.
|
* @param error Error to copy.
|
||||||
*/
|
*/
|
||||||
explicit result(const error_type& error)
|
result(error_tag tag, const error_type& error)
|
||||||
: m_error(true) {
|
: m_error(true) {
|
||||||
new (&m_value.error) error_type(error);
|
new (&m_value.error) error_type(error);
|
||||||
}
|
}
|
||||||
@@ -102,7 +110,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param error Error to move.
|
* @param error Error to move.
|
||||||
*/
|
*/
|
||||||
explicit result(error_type&& error)
|
result(error_tag tag, error_type&& error)
|
||||||
: m_error(true) {
|
: m_error(true) {
|
||||||
new (&m_value.error) error_type(std::move(error));
|
new (&m_value.error) error_type(std::move(error));
|
||||||
}
|
}
|
||||||
@@ -166,6 +174,16 @@ public:
|
|||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
template <typename ResultFwd, typename = std::enable_if_t<std::is_constructible_v<R, ResultFwd>>>
|
||||||
|
static result ok(ResultFwd&& value) {
|
||||||
|
return { std::forward<ResultFwd>(value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ErrorFwd, typename = std::enable_if_t<std::is_constructible_v<E, ErrorFwd>>>
|
||||||
|
static result error(ErrorFwd&& value) {
|
||||||
|
return { error_tag{}, std::forward<ErrorFwd>(value) };
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Checks if this result contains a value.
|
* @brief Checks if this result contains a value.
|
||||||
@@ -364,6 +382,34 @@ private:
|
|||||||
bool m_error = false;
|
bool m_error = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename E>
|
||||||
|
class result<E, void> {
|
||||||
|
public:
|
||||||
|
using value_type = std::remove_reference_t<E>;
|
||||||
|
using reference = value_type&;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
public:
|
||||||
|
result() = default;
|
||||||
|
|
||||||
|
result(const value_type& value)
|
||||||
|
: m_error(true), m_value(value) {}
|
||||||
|
|
||||||
|
result(value_type&& value)
|
||||||
|
: m_error(true), m_value(std::move(value)) {}
|
||||||
|
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_type, Args...>>>
|
||||||
|
result(Args&&... args)
|
||||||
|
: m_error(true), m_value(std::forward<Args>(args)...) {}
|
||||||
|
public:
|
||||||
|
bool has_value() const { return !m_error; }
|
||||||
|
bool has_error() const { return m_error; }
|
||||||
|
|
||||||
|
const value_type& error() const { return *m_value; }
|
||||||
|
private:
|
||||||
|
std::optional<value_type> m_value;
|
||||||
|
bool m_error = false;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace furlang
|
} // namespace furlang
|
||||||
|
|
||||||
#endif // FURLANG_RESULT_HPP
|
#endif // FURLANG_RESULT_HPP
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
|
|
||||||
|
#include "furlang/result.hpp"
|
||||||
|
#include "furlang/serialization/io.hpp"
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
using codec_encode_result_t = decltype(std::declval<Codec>().encode(std::declval<writer&>(), std::declval<const T&>()));
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
using codec_decode_result_t = decltype(std::declval<Codec>().decode(std::declval<reader&>()));
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = void>
|
||||||
|
struct is_codec : std::false_type {};
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
struct is_codec<Codec, T, std::void_t<codec_encode_result_t<Codec, T>, codec_decode_result_t<Codec, T>>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename Codec, typename T>
|
||||||
|
constexpr bool is_codec_v = is_codec<Codec, T>::value;
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||||
|
result<error> encode(Codec& codec, writer& writer, const T& value) {
|
||||||
|
return codec.encode(writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||||
|
result<error, T> decode(Codec& codec, reader& reader) {
|
||||||
|
return codec.decode(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = void>
|
||||||
|
class codec;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct codec<T, std::enable_if_t<std::is_integral_v<T>>> {
|
||||||
|
result<error> encode(writer& writer, const T& value) { return writer.write_int(value); }
|
||||||
|
result<error, T> decode(reader& reader) { return reader.read_int(T{}); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct codec<std::string> {
|
||||||
|
result<error> encode(writer& writer, const std::string& value) { return writer.write_string(value); }
|
||||||
|
|
||||||
|
result<error, std::string> decode(reader& reader) { return reader.read_string(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_CODEC_HPP
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
enum class error_code {
|
||||||
|
EndOfFile,
|
||||||
|
InvalidData,
|
||||||
|
InvalidTag,
|
||||||
|
InvalidVersion,
|
||||||
|
IntegerOverflow,
|
||||||
|
SizeLimit,
|
||||||
|
DuplicateId,
|
||||||
|
UnknownId,
|
||||||
|
TypeMismatch,
|
||||||
|
Unsupported,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct error {
|
||||||
|
error_code code;
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
std::size_t offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_ERROR_HPP
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#ifndef FURLANG_SERIALIZATION_IO_HPP
|
||||||
|
#define FURLANG_SERIALIZATION_IO_HPP
|
||||||
|
|
||||||
|
#include "furlang/result.hpp"
|
||||||
|
#include "furlang/serialization/error.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furlang {
|
||||||
|
namespace serialization {
|
||||||
|
|
||||||
|
class writer {
|
||||||
|
public:
|
||||||
|
writer() = default;
|
||||||
|
virtual ~writer() = default;
|
||||||
|
|
||||||
|
writer(writer&&) noexcept = default;
|
||||||
|
writer& operator=(writer&&) noexcept = default;
|
||||||
|
writer(const writer&) = default;
|
||||||
|
writer& operator=(const writer&) = default;
|
||||||
|
public:
|
||||||
|
virtual result<error> write_s8(std::int8_t value) = 0;
|
||||||
|
virtual result<error> write_u8(std::uint8_t value) = 0;
|
||||||
|
virtual result<error> write_s16(std::int16_t value) = 0;
|
||||||
|
virtual result<error> write_u16(std::uint16_t value) = 0;
|
||||||
|
virtual result<error> write_s32(std::int32_t value) = 0;
|
||||||
|
virtual result<error> write_u32(std::uint32_t value) = 0;
|
||||||
|
virtual result<error> write_s64(std::int64_t value) = 0;
|
||||||
|
virtual result<error> write_u64(std::uint64_t value) = 0;
|
||||||
|
|
||||||
|
result<error> write_int(std::int8_t value) { return write_s8(value); }
|
||||||
|
result<error> write_int(std::uint8_t value) { return write_u8(value); }
|
||||||
|
result<error> write_int(std::int16_t value) { return write_s16(value); }
|
||||||
|
result<error> write_int(std::uint16_t value) { return write_u16(value); }
|
||||||
|
result<error> write_int(std::int32_t value) { return write_s32(value); }
|
||||||
|
result<error> write_int(std::uint32_t value) { return write_u32(value); }
|
||||||
|
result<error> write_int(std::int64_t value) { return write_s64(value); }
|
||||||
|
result<error> write_int(std::uint64_t value) { return write_u64(value); }
|
||||||
|
|
||||||
|
virtual result<error> write_string(const char* string) = 0;
|
||||||
|
virtual result<error> write_string(std::string_view string) = 0;
|
||||||
|
virtual result<error> write_string(const std::string& string) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class reader {
|
||||||
|
public:
|
||||||
|
reader() = default;
|
||||||
|
virtual ~reader() = default;
|
||||||
|
|
||||||
|
reader(reader&&) noexcept = default;
|
||||||
|
reader& operator=(reader&&) noexcept = default;
|
||||||
|
reader(const reader&) = default;
|
||||||
|
reader& operator=(const reader&) = default;
|
||||||
|
public:
|
||||||
|
virtual result<error, std::int8_t> read_s8() = 0;
|
||||||
|
virtual result<error, std::uint8_t> read_u8() = 0;
|
||||||
|
virtual result<error, std::int16_t> read_s16() = 0;
|
||||||
|
virtual result<error, std::uint16_t> read_u16() = 0;
|
||||||
|
virtual result<error, std::int32_t> read_s32() = 0;
|
||||||
|
virtual result<error, std::uint32_t> read_u32() = 0;
|
||||||
|
virtual result<error, std::int64_t> read_s64() = 0;
|
||||||
|
virtual result<error, std::uint64_t> read_u64() = 0;
|
||||||
|
|
||||||
|
result<error, std::int8_t> read_int(std::int8_t) { return read_s8(); }
|
||||||
|
result<error, std::uint8_t> read_int(std::uint8_t) { return read_u8(); }
|
||||||
|
result<error, std::int16_t> read_int(std::int16_t) { return read_s16(); }
|
||||||
|
result<error, std::uint16_t> read_int(std::uint16_t) { return read_u16(); }
|
||||||
|
result<error, std::int32_t> read_int(std::int32_t) { return read_s32(); }
|
||||||
|
result<error, std::uint32_t> read_int(std::uint32_t) { return read_u32(); }
|
||||||
|
result<error, std::int64_t> read_int(std::int64_t) { return read_s64(); }
|
||||||
|
result<error, std::uint64_t> read_int(std::uint64_t) { return read_u64(); }
|
||||||
|
|
||||||
|
virtual result<error, std::string> read_string() = 0;
|
||||||
|
|
||||||
|
virtual std::size_t offset() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class endianness {
|
||||||
|
Little = 0,
|
||||||
|
Big = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class byte_writer : public writer {
|
||||||
|
public:
|
||||||
|
byte_writer(endianness endianness = endianness::Big)
|
||||||
|
: m_endianness(endianness) {}
|
||||||
|
public:
|
||||||
|
result<error> write_s8(std::int8_t value) override;
|
||||||
|
result<error> write_u8(std::uint8_t value) override;
|
||||||
|
result<error> write_s16(std::int16_t value) override;
|
||||||
|
result<error> write_u16(std::uint16_t value) override;
|
||||||
|
result<error> write_s32(std::int32_t value) override;
|
||||||
|
result<error> write_u32(std::uint32_t value) override;
|
||||||
|
result<error> write_s64(std::int64_t value) override;
|
||||||
|
result<error> write_u64(std::uint64_t value) override;
|
||||||
|
|
||||||
|
result<error> write_string(const char* string) override;
|
||||||
|
result<error> write_string(std::string_view string) override;
|
||||||
|
result<error> write_string(const std::string& string) override;
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error> write_integral_le(T value) {
|
||||||
|
return write_integral_le(value, std::make_index_sequence<sizeof(T)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||||
|
result<error> write_integral_le(T value, std::index_sequence<I...>) {
|
||||||
|
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||||
|
(m_bytes.push_back(usig >> (I * 8)), ...);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
void write_integral_be(T value) {
|
||||||
|
write_integral_be(value, std::make_index_sequence<sizeof(T)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||||
|
void write_integral_be(T value, std::index_sequence<I...>) {
|
||||||
|
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||||
|
(m_bytes.push_back(usig >> ((sizeof(T) - 1 - I) * 8)), ...);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
endianness m_endianness;
|
||||||
|
std::vector<std::uint8_t> m_bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class byte_reader : public reader {
|
||||||
|
public:
|
||||||
|
byte_reader(const std::uint8_t* bytes, std::size_t length, endianness endianness = endianness::Big)
|
||||||
|
: m_endianness(endianness), m_bytes(bytes), m_length(length) {}
|
||||||
|
public:
|
||||||
|
result<error, std::int8_t> read_s8() override;
|
||||||
|
result<error, std::uint8_t> read_u8() override;
|
||||||
|
result<error, std::int16_t> read_s16() override;
|
||||||
|
result<error, std::uint16_t> read_u16() override;
|
||||||
|
result<error, std::int32_t> read_s32() override;
|
||||||
|
result<error, std::uint32_t> read_u32() override;
|
||||||
|
result<error, std::int64_t> read_s64() override;
|
||||||
|
result<error, std::uint64_t> read_u64() override;
|
||||||
|
|
||||||
|
result<error, std::string> read_string() override;
|
||||||
|
|
||||||
|
std::size_t offset() const override;
|
||||||
|
private:
|
||||||
|
result<error, std::uint8_t> read_byte() {
|
||||||
|
if (m_offset >= m_length)
|
||||||
|
return result<error, std::uint8_t>::error(error{ error_code::EndOfFile, "", m_offset });
|
||||||
|
return { m_bytes[m_offset++] };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error, T> read_integral_le() {
|
||||||
|
using U = std::make_unsigned_t<T>;
|
||||||
|
U usig = 0;
|
||||||
|
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||||
|
auto res = read_byte();
|
||||||
|
if (res.has_error()) return res;
|
||||||
|
usig |= static_cast<U>(res.value()) << (i * 8);
|
||||||
|
}
|
||||||
|
return { static_cast<T>(usig) };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
result<error, T> read_integral_be() {
|
||||||
|
using U = std::make_unsigned_t<T>;
|
||||||
|
U usig = 0;
|
||||||
|
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||||
|
auto res = read_byte();
|
||||||
|
if (res.has_error()) return res;
|
||||||
|
usig |= static_cast<U>(res.value()) << ((sizeof(T) - 1 - i) * 8);
|
||||||
|
}
|
||||||
|
return { static_cast<T>(usig) };
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
endianness m_endianness;
|
||||||
|
const std::uint8_t* m_bytes;
|
||||||
|
std::size_t m_length;
|
||||||
|
std::size_t m_offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace serialization
|
||||||
|
} // namespace furlang
|
||||||
|
|
||||||
|
#endif // FURLANG_SERIALIZATION_IO_HPP
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef FURVM_CONSTANT_HPP
|
||||||
|
#define FURVM_CONSTANT_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
// TODO: Array constants
|
||||||
|
struct constant {
|
||||||
|
enum type_e {
|
||||||
|
S32 = 0,
|
||||||
|
U32,
|
||||||
|
S64,
|
||||||
|
U64,
|
||||||
|
String,
|
||||||
|
} type = S32;
|
||||||
|
union {
|
||||||
|
std::int32_t s32;
|
||||||
|
std::uint32_t u32;
|
||||||
|
std::int64_t s64;
|
||||||
|
std::uint64_t u64;
|
||||||
|
std::string_view string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_CONSTANT_HPP
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include "furvm/fwd.hpp"
|
#include "furvm/fwd.hpp"
|
||||||
#include "furvm/module.hpp" // IWYU pragma: keep
|
#include "furvm/module.hpp" // IWYU pragma: keep
|
||||||
#include "furvm/thing.hpp" // IWYU pragma: keep
|
#include "furvm/stack.hpp"
|
||||||
|
#include "furvm/thing.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
@@ -39,6 +40,8 @@ public:
|
|||||||
static constexpr executor_flags STATE_FLAGS = executor_flags::JustHit;
|
static constexpr executor_flags STATE_FLAGS = executor_flags::JustHit;
|
||||||
|
|
||||||
using new_frame_callback = std::function<void(executor&)>;
|
using new_frame_callback = std::function<void(executor&)>;
|
||||||
|
|
||||||
|
using stack_thing = thing<stack_allocator>;
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Executor frame.
|
* @brief Executor frame.
|
||||||
@@ -50,8 +53,8 @@ public:
|
|||||||
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
||||||
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
||||||
|
|
||||||
thing_type* returnType; /**< Return type. */
|
thing_type* returnType; /**< Return type. */
|
||||||
std::vector<thing<>> variables; /**< Frame variables. */
|
std::vector<stack_thing> variables; /**< Frame variables. */
|
||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
~executor() = default;
|
~executor() = default;
|
||||||
@@ -131,27 +134,27 @@ public:
|
|||||||
* @param thing Thing.
|
* @param thing Thing.
|
||||||
* @return The pushed handle.
|
* @return The pushed handle.
|
||||||
*/
|
*/
|
||||||
thing<>& push_thing(thing<>&& thing);
|
stack_thing& push_thing(stack_thing&& thing);
|
||||||
|
|
||||||
thing<>& push_thing(const thing<>& thing);
|
stack_thing& push_thing(const stack_thing& thing);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Pops a thing from the stack.
|
* @brief Pops a thing from the stack.
|
||||||
*
|
*
|
||||||
* @return A handle to the popped thing.
|
* @return A handle to the popped thing.
|
||||||
*/
|
*/
|
||||||
thing<> pop_thing();
|
stack_thing pop_thing();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the top thing on the stack.
|
* @brief Returns the top thing on the stack.
|
||||||
*
|
*
|
||||||
* @return A handle to the top thing.
|
* @return A handle to the top thing.
|
||||||
*/
|
*/
|
||||||
thing<>& top_thing();
|
stack_thing& top_thing();
|
||||||
|
|
||||||
const thing<>& top_thing() const;
|
const stack_thing& top_thing() const;
|
||||||
|
|
||||||
const std::vector<thing<>>& stack() const { return m_stack; }
|
const std::vector<stack_thing>& stack() const { return m_stack; }
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Stores a thing in a frame variable.
|
* @brief Stores a thing in a frame variable.
|
||||||
@@ -159,7 +162,7 @@ public:
|
|||||||
* @param variable Id of the variable in which the handle will be put.
|
* @param variable Id of the variable in which the handle will be put.
|
||||||
* @param thing Thing handle.
|
* @param thing Thing handle.
|
||||||
*/
|
*/
|
||||||
void store_thing(variable_t variable, const thing<>& thing);
|
void store_thing(variable_t variable, const stack_thing& thing);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stores a thing in a frame variable.
|
* @brief Stores a thing in a frame variable.
|
||||||
@@ -167,7 +170,7 @@ public:
|
|||||||
* @param variable Id of the variable in which the handle will be put.
|
* @param variable Id of the variable in which the handle will be put.
|
||||||
* @param thing Thing handle.
|
* @param thing Thing handle.
|
||||||
*/
|
*/
|
||||||
void store_thing(variable_t variable, thing<>&& thing);
|
void store_thing(variable_t variable, stack_thing&& thing);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a thing stored in a variable.
|
* @brief Returns a thing stored in a variable.
|
||||||
@@ -175,9 +178,9 @@ public:
|
|||||||
* @param variable Id of the variable from which the handle will be fetched.
|
* @param variable Id of the variable from which the handle will be fetched.
|
||||||
* @return A handle stored in the variable.
|
* @return A handle stored in the variable.
|
||||||
*/
|
*/
|
||||||
thing<>& load_thing(variable_t variable);
|
stack_thing& load_thing(variable_t variable);
|
||||||
|
|
||||||
const thing<>& load_thing(variable_t variable) const;
|
const stack_thing& load_thing(variable_t variable) const;
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Executes next instruction.
|
* @brief Executes next instruction.
|
||||||
@@ -190,11 +193,12 @@ private:
|
|||||||
private:
|
private:
|
||||||
static bool compare_thing_types(const thing_type& lhs, const thing_type& rhs);
|
static bool compare_thing_types(const thing_type& lhs, const thing_type& rhs);
|
||||||
private:
|
private:
|
||||||
executor_flags m_flags = executor_flags::Done;
|
executor_flags m_flags = executor_flags::Done;
|
||||||
context* m_context;
|
context* m_context;
|
||||||
|
furvm::stack<std::byte> m_stackStorage;
|
||||||
|
|
||||||
std::stack<frame> m_frames;
|
std::stack<frame> m_frames;
|
||||||
std::vector<thing<>> m_stack;
|
std::vector<stack_thing> m_stack;
|
||||||
|
|
||||||
new_frame_callback m_newFrameCb = nullptr;
|
new_frame_callback m_newFrameCb = nullptr;
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#ifndef FURVM_FUNCTION_HPP
|
#ifndef FURVM_FUNCTION_HPP
|
||||||
#define FURVM_FUNCTION_HPP
|
#define FURVM_FUNCTION_HPP
|
||||||
|
|
||||||
#include "furlang/utility/hash.hpp"
|
|
||||||
#include "furvm/fwd.hpp"
|
#include "furvm/fwd.hpp"
|
||||||
#include "furvm/handle.hpp" // IWYU pragma: keep
|
#include "furvm/handle.hpp" // IWYU pragma: keep
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ struct instruction {
|
|||||||
PushU32,
|
PushU32,
|
||||||
PushConstant,
|
PushConstant,
|
||||||
Array,
|
Array,
|
||||||
|
Slice,
|
||||||
Get,
|
Get,
|
||||||
Set,
|
Set,
|
||||||
Drop,
|
Drop,
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "furlang/utility/hash.hpp"
|
#include "furlang/utility/hash.hpp"
|
||||||
#include "furlang/view.hpp"
|
#include "furlang/view.hpp"
|
||||||
|
#include "furvm/constant.hpp"
|
||||||
#include "furvm/function.hpp"
|
#include "furvm/function.hpp"
|
||||||
#include "furvm/fwd.hpp"
|
#include "furvm/fwd.hpp"
|
||||||
#include "furvm/handle.hpp"
|
#include "furvm/handle.hpp"
|
||||||
@@ -10,8 +11,6 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <limits>
|
|
||||||
#include <optional>
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -28,6 +27,10 @@ struct mod_type {
|
|||||||
std::size_t size;
|
std::size_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct slice_value {
|
||||||
|
mod_type_id typeId;
|
||||||
|
};
|
||||||
|
|
||||||
struct import_value {
|
struct import_value {
|
||||||
mod_id modId;
|
mod_id modId;
|
||||||
mod_type_id typeId;
|
mod_type_id typeId;
|
||||||
@@ -45,6 +48,7 @@ struct mod_type {
|
|||||||
Ptr,
|
Ptr,
|
||||||
Ref,
|
Ref,
|
||||||
Array,
|
Array,
|
||||||
|
Slice,
|
||||||
|
|
||||||
Import,
|
Import,
|
||||||
Count,
|
Count,
|
||||||
@@ -53,6 +57,7 @@ struct mod_type {
|
|||||||
std::nullptr_t null = nullptr;
|
std::nullptr_t null = nullptr;
|
||||||
mod_type_id typeRef;
|
mod_type_id typeRef;
|
||||||
array_value array;
|
array_value array;
|
||||||
|
slice_value slice;
|
||||||
import_value imprt;
|
import_value imprt;
|
||||||
|
|
||||||
value() = default;
|
value() = default;
|
||||||
@@ -97,6 +102,7 @@ struct mod_type {
|
|||||||
~mod_type() {
|
~mod_type() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Array: value.array.~array_value(); break;
|
case Array: value.array.~array_value(); break;
|
||||||
|
case Slice: value.slice.~slice_value(); break;
|
||||||
case Import: value.imprt.~import_value(); break;
|
case Import: value.imprt.~import_value(); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -106,6 +112,7 @@ struct mod_type {
|
|||||||
: type(other.type) {
|
: type(other.type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Array: new (&value.array) array_value(other.value.array); break;
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -117,6 +124,7 @@ struct mod_type {
|
|||||||
type = other.type;
|
type = other.type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Array: new (&value.array) array_value(other.value.array); break;
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
case Import: new (&value.imprt) import_value(std::move(other.value.imprt)); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -128,6 +136,7 @@ struct mod_type {
|
|||||||
: type(other.type) {
|
: type(other.type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Array: new (&value.array) array_value(other.value.array); break;
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -138,6 +147,7 @@ struct mod_type {
|
|||||||
type = other.type;
|
type = other.type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Array: new (&value.array) array_value(other.value.array); break;
|
case Array: new (&value.array) array_value(other.value.array); break;
|
||||||
|
case Slice: new (&value.slice) slice_value(other.value.slice); break;
|
||||||
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
case Import: new (&value.imprt) import_value(other.value.imprt); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -384,6 +394,13 @@ public:
|
|||||||
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||||
return m_globalVariables[var];
|
return m_globalVariables[var];
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<constant, Args...>>>
|
||||||
|
void emplace_constant(Args&&... args) {
|
||||||
|
m_constants.emplace_back(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant& constant_at(constant_index index) const { return m_constants.at(index); }
|
||||||
public:
|
public:
|
||||||
template <typename Fwd, typename = std::enable_if_t<std::is_constructible_v<breakpoint, Fwd>>>
|
template <typename Fwd, typename = std::enable_if_t<std::is_constructible_v<breakpoint, Fwd>>>
|
||||||
void set_breakpoint(bytecode_pos pos, Fwd&& breakpoint) {
|
void set_breakpoint(bytecode_pos pos, Fwd&& breakpoint) {
|
||||||
@@ -423,6 +440,8 @@ private:
|
|||||||
|
|
||||||
std::vector<thing<>> m_globalVariables;
|
std::vector<thing<>> m_globalVariables;
|
||||||
|
|
||||||
|
std::vector<constant> m_constants;
|
||||||
|
|
||||||
std::unordered_map<std::string, native_function> m_nativeFunctions;
|
std::unordered_map<std::string, native_function> m_nativeFunctions;
|
||||||
|
|
||||||
std::unordered_map<bytecode_pos, breakpoint> m_breakpoints;
|
std::unordered_map<bytecode_pos, breakpoint> m_breakpoints;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef FURVM_STACK_HPP
|
||||||
|
#define FURVM_STACK_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <new>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct stack {
|
||||||
|
stack(std::size_t capacity = (1024ULL * 1024ULL) / sizeof(T))
|
||||||
|
: begin(new T[capacity]()), cursor(begin), capacity(capacity) {}
|
||||||
|
|
||||||
|
T* begin;
|
||||||
|
T* cursor;
|
||||||
|
std::size_t capacity;
|
||||||
|
std::stack<T*> frames;
|
||||||
|
|
||||||
|
void push_frame() { frames.push(cursor); }
|
||||||
|
|
||||||
|
void pop_frame() {
|
||||||
|
cursor = frames.top();
|
||||||
|
frames.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class stack_allocator {
|
||||||
|
public:
|
||||||
|
stack_allocator() = default;
|
||||||
|
|
||||||
|
stack_allocator(stack<T>& stack)
|
||||||
|
: m_ref(&stack) {}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
constexpr stack_allocator(const stack_allocator<U>& other) noexcept
|
||||||
|
: m_ref(other.m_ref) {}
|
||||||
|
public:
|
||||||
|
T* allocate(std::size_t n) {
|
||||||
|
if (m_ref == nullptr) throw std::bad_alloc();
|
||||||
|
if (m_ref->capacity - (m_ref->cursor - m_ref->begin) < n) throw std::bad_alloc();
|
||||||
|
|
||||||
|
T* ptr = m_ref->cursor;
|
||||||
|
m_ref->cursor += n;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* ptr, std::size_t n) {}
|
||||||
|
private:
|
||||||
|
stack<T>* m_ref = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_STACK_HPP
|
||||||
@@ -5,13 +5,14 @@
|
|||||||
#include "furlang/utility/hash.hpp"
|
#include "furlang/utility/hash.hpp"
|
||||||
#include "furvm/exceptions.hpp"
|
#include "furvm/exceptions.hpp"
|
||||||
#include "furvm/fwd.hpp"
|
#include "furvm/fwd.hpp"
|
||||||
|
#include "furvm/types.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <iterator>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -20,115 +21,6 @@
|
|||||||
|
|
||||||
namespace furvm {
|
namespace furvm {
|
||||||
|
|
||||||
struct thing_type {
|
|
||||||
using s8 = std::int8_t;
|
|
||||||
using s16 = std::int16_t;
|
|
||||||
using s32 = std::int32_t;
|
|
||||||
using s64 = std::int64_t;
|
|
||||||
using u8 = std::uint8_t;
|
|
||||||
using u16 = std::uint16_t;
|
|
||||||
using u32 = std::uint32_t;
|
|
||||||
using u64 = std::uint64_t;
|
|
||||||
|
|
||||||
struct array_value {
|
|
||||||
thing_type* type;
|
|
||||||
std::size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum type { // NOLINT
|
|
||||||
S8 = 0,
|
|
||||||
S16,
|
|
||||||
S32,
|
|
||||||
S64,
|
|
||||||
U8,
|
|
||||||
U16,
|
|
||||||
U32,
|
|
||||||
U64,
|
|
||||||
Ptr,
|
|
||||||
Ref,
|
|
||||||
Array,
|
|
||||||
|
|
||||||
Count,
|
|
||||||
} type = Count;
|
|
||||||
union value {
|
|
||||||
std::nullptr_t null = nullptr;
|
|
||||||
thing_type* typeRef;
|
|
||||||
array_value array;
|
|
||||||
|
|
||||||
value() = default;
|
|
||||||
|
|
||||||
value(thing_type* type)
|
|
||||||
: typeRef(type) {}
|
|
||||||
|
|
||||||
value(thing_type* type, std::size_t size)
|
|
||||||
: array({}) {
|
|
||||||
array.type = type;
|
|
||||||
array.size = size;
|
|
||||||
}
|
|
||||||
} value;
|
|
||||||
|
|
||||||
static constexpr thing_type_id INVALID_ID = std::numeric_limits<thing_type_id>::max();
|
|
||||||
|
|
||||||
thing_type_id id = INVALID_ID;
|
|
||||||
|
|
||||||
bool operator==(const thing_type& other) const {
|
|
||||||
if (type != other.type) return false;
|
|
||||||
switch (type) {
|
|
||||||
case S8:
|
|
||||||
case S16:
|
|
||||||
case S32:
|
|
||||||
case S64:
|
|
||||||
case U8:
|
|
||||||
case U16:
|
|
||||||
case U32:
|
|
||||||
case U64: return true;
|
|
||||||
case Ptr:
|
|
||||||
case Ref: return *value.typeRef == *other.value.typeRef;
|
|
||||||
case Array: return *value.array.type == *other.value.array.type && value.array.size == other.value.array.size;
|
|
||||||
case Count: break;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const thing_type& other) const { return !this->operator==(other); }
|
|
||||||
|
|
||||||
static bool is_primitive(enum type type) {
|
|
||||||
switch (type) {
|
|
||||||
case S8:
|
|
||||||
case S16:
|
|
||||||
case S32:
|
|
||||||
case S64:
|
|
||||||
case U8:
|
|
||||||
case U16:
|
|
||||||
case U32:
|
|
||||||
case U64: return true;
|
|
||||||
case Ptr:
|
|
||||||
case Ref:
|
|
||||||
case Array: return false;
|
|
||||||
case Count: break;
|
|
||||||
}
|
|
||||||
throw std::runtime_error("unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::size_t primitive_size(enum type type) {
|
|
||||||
switch (type) {
|
|
||||||
case thing_type::S8: return sizeof(s8);
|
|
||||||
case thing_type::S16: return sizeof(s16);
|
|
||||||
case thing_type::S32: return sizeof(s32);
|
|
||||||
case thing_type::S64: return sizeof(s64);
|
|
||||||
case thing_type::U8: return sizeof(u8);
|
|
||||||
case thing_type::U16: return sizeof(u16);
|
|
||||||
case thing_type::U32: return sizeof(u32);
|
|
||||||
case thing_type::U64: return sizeof(u64);
|
|
||||||
case Ptr:
|
|
||||||
case Ref:
|
|
||||||
case Array: return 0;
|
|
||||||
case Count: break;
|
|
||||||
}
|
|
||||||
throw std::runtime_error("unreachable");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
struct thing_type_hash {
|
struct thing_type_hash {
|
||||||
@@ -142,7 +34,8 @@ struct thing_type_hash {
|
|||||||
case thing_type::U8:
|
case thing_type::U8:
|
||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64: return seed;
|
case thing_type::U64:
|
||||||
|
case thing_type::String: return seed;
|
||||||
case thing_type::Ptr:
|
case thing_type::Ptr:
|
||||||
case thing_type::Ref: return furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.typeRef));
|
case thing_type::Ref: return furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.typeRef));
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
@@ -150,6 +43,7 @@ struct thing_type_hash {
|
|||||||
seed = furlang::utility::hash_combine(seed,
|
seed = furlang::utility::hash_combine(seed,
|
||||||
std::hash<decltype(type.value.array.size)>{}(type.value.array.size));
|
std::hash<decltype(type.value.array.size)>{}(type.value.array.size));
|
||||||
return seed;
|
return seed;
|
||||||
|
case thing_type::Slice: return furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.slice.type));
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("unreachable");
|
throw std::runtime_error("unreachable");
|
||||||
@@ -194,19 +88,139 @@ private:
|
|||||||
template <template <typename> typename Allocator>
|
template <template <typename> typename Allocator>
|
||||||
class thing final {
|
class thing final {
|
||||||
friend class executor;
|
friend class executor;
|
||||||
|
|
||||||
|
template <template <typename> class Other>
|
||||||
|
friend class thing;
|
||||||
public:
|
public:
|
||||||
using allocator_type = Allocator<std::byte>; /**< Allocator type. */
|
using allocator_type = Allocator<std::byte>; /**< Allocator type. */
|
||||||
public:
|
public:
|
||||||
|
struct string {
|
||||||
|
std::size_t size;
|
||||||
|
u8* data;
|
||||||
|
|
||||||
|
static bool matches(const thing_type& type) { return type.type == thing_type::String; }
|
||||||
|
};
|
||||||
|
|
||||||
struct dynamic_array {
|
struct dynamic_array {
|
||||||
std::size_t size;
|
std::size_t size;
|
||||||
std::byte* data;
|
std::byte* data;
|
||||||
|
|
||||||
|
static bool matches(const thing_type& type) {
|
||||||
|
return type.type == thing_type::Array && type.value.array.size == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct slice {
|
||||||
|
std::size_t length;
|
||||||
|
std::byte* data;
|
||||||
|
|
||||||
|
static bool matches(const thing_type& type) { return type.type == thing_type::Slice; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct header {
|
struct header {
|
||||||
thing_type type;
|
thing_type type;
|
||||||
};
|
};
|
||||||
|
private:
|
||||||
|
template <bool Const>
|
||||||
|
class generic_iterator {
|
||||||
|
using owner_type = std::conditional_t<Const, const thing, thing>;
|
||||||
|
using data_type = std::byte;
|
||||||
|
public:
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = thing;
|
||||||
|
using pointer = std::conditional_t<Const, const value_type*, value_type*>;
|
||||||
|
using reference = std::conditional_t<Const, const value_type&, value_type&>;
|
||||||
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
|
public:
|
||||||
|
generic_iterator() = default;
|
||||||
|
|
||||||
|
generic_iterator(owner_type* owner, data_type* ptr)
|
||||||
|
: m_owner(owner), m_ptr(ptr) {
|
||||||
|
fill();
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
reference operator*() { return m_thing; }
|
||||||
|
|
||||||
|
value_type operator*() const {
|
||||||
|
thing thing = { m_thing.type() };
|
||||||
|
thing.assign(m_thing);
|
||||||
|
return thing;
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator[](difference_type n) { return *(*this + n); }
|
||||||
|
|
||||||
|
pointer operator->() { return &m_thing; }
|
||||||
|
|
||||||
|
generic_iterator& operator+=(difference_type n) {
|
||||||
|
m_ptr += n * step_size();
|
||||||
|
fill();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_iterator& operator-=(difference_type n) {
|
||||||
|
m_ptr -= n * step_size();
|
||||||
|
fill();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_iterator operator+(difference_type n) const { return { m_owner, m_ptr + (n * step_size()) }; }
|
||||||
|
|
||||||
|
friend generic_iterator operator+(difference_type n, const generic_iterator& it) {
|
||||||
|
return { it.m_owner, it.m_ptr + (n * it.step_size()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_iterator operator-(difference_type n) const { return { m_owner, m_ptr - (n * step_size()) }; }
|
||||||
|
|
||||||
|
difference_type operator-(const generic_iterator& other) const { return (m_ptr - other.m_ptr) / step_size(); }
|
||||||
|
|
||||||
|
generic_iterator& operator++() {
|
||||||
|
m_ptr += step_size();
|
||||||
|
fill();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_iterator operator++(int) { return { m_owner, m_ptr + step_size() }; }
|
||||||
|
|
||||||
|
generic_iterator& operator--() {
|
||||||
|
m_ptr -= step_size();
|
||||||
|
fill();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
generic_iterator operator--(int) { return { m_owner, m_ptr - step_size() }; }
|
||||||
|
|
||||||
|
bool operator==(const generic_iterator& other) const {
|
||||||
|
return m_owner == other.m_owner && m_ptr == other.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const generic_iterator& other) const {
|
||||||
|
return m_owner != other.m_owner || m_ptr != other.m_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const generic_iterator& other) const { return other.m_ptr < m_ptr; }
|
||||||
|
bool operator>(const generic_iterator& other) const { return other.m_ptr > m_ptr; }
|
||||||
|
bool operator<=(const generic_iterator& other) const { return other.m_ptr <= m_ptr; }
|
||||||
|
bool operator>=(const generic_iterator& other) const { return other.m_ptr >= m_ptr; }
|
||||||
|
private:
|
||||||
|
difference_type step_size() const { return thing::compute_size_na(m_owner->inner_type()); }
|
||||||
|
|
||||||
|
void fill() {
|
||||||
|
m_thing.m_reference = true;
|
||||||
|
m_thing.m_data = m_ptr;
|
||||||
|
m_thing.m_type = &m_owner->inner_type();
|
||||||
|
m_thing.m_size = compute_size_na(m_owner->inner_type());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
owner_type* m_owner = nullptr;
|
||||||
|
data_type* m_ptr = nullptr;
|
||||||
|
thing m_thing;
|
||||||
|
};
|
||||||
|
|
||||||
|
using iterator = generic_iterator<false>;
|
||||||
|
using const_iterator = generic_iterator<true>;
|
||||||
public:
|
public:
|
||||||
thing() {}
|
thing(const allocator_type& allocator = {})
|
||||||
|
: m_allocator(allocator) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a thing.
|
* @brief Constructs a thing.
|
||||||
@@ -230,15 +244,15 @@ public:
|
|||||||
*
|
*
|
||||||
* TODO: Reword the note above.
|
* TODO: Reword the note above.
|
||||||
*/
|
*/
|
||||||
static thing make_reference(const thing& owner) {
|
template <template <typename> class Other = Allocator>
|
||||||
thing ref;
|
static thing make_reference(const thing<Other>& owner, const allocator_type& allocator = {}) {
|
||||||
|
thing ref = { allocator };
|
||||||
ref.m_reference = true;
|
ref.m_reference = true;
|
||||||
ref.m_data = owner.m_data;
|
ref.m_data = owner.m_data;
|
||||||
ref.m_type = owner.m_type;
|
ref.m_type = owner.m_type;
|
||||||
ref.m_size = owner.m_size;
|
ref.m_size = owner.m_size;
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destructs a thing.
|
* @brief Destructs a thing.
|
||||||
*/
|
*/
|
||||||
@@ -247,7 +261,16 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Move constructor.
|
* @brief Move constructor.
|
||||||
*/
|
*/
|
||||||
thing(thing&& other) noexcept { *this = std::move(other); }
|
thing(thing&& other) noexcept
|
||||||
|
: m_reference(other.m_reference),
|
||||||
|
m_type(other.m_type),
|
||||||
|
m_size(other.m_size),
|
||||||
|
m_data(other.m_data),
|
||||||
|
m_allocator(other.m_allocator) {
|
||||||
|
other.m_type = nullptr;
|
||||||
|
other.m_data = nullptr;
|
||||||
|
other.m_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Move constructor.
|
* @brief Move constructor.
|
||||||
@@ -266,7 +289,16 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
thing(const thing& other) { *this = other; }
|
thing(const thing& other)
|
||||||
|
: m_reference(other.m_reference), m_size(other.m_size), m_allocator(other.m_allocator) {
|
||||||
|
if (m_reference) {
|
||||||
|
m_type = other.m_type;
|
||||||
|
m_data = other.m_data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allocate(other.type());
|
||||||
|
other.copy(*this);
|
||||||
|
}
|
||||||
|
|
||||||
thing& operator=(const thing& other) {
|
thing& operator=(const thing& other) {
|
||||||
if (this == &other) return *this;
|
if (this == &other) return *this;
|
||||||
@@ -275,7 +307,38 @@ public:
|
|||||||
|
|
||||||
m_reference = other.m_reference;
|
m_reference = other.m_reference;
|
||||||
m_size = other.m_size;
|
m_size = other.m_size;
|
||||||
m_allocator = other.m_allocator;
|
|
||||||
|
if (m_reference) {
|
||||||
|
m_type = other.m_type;
|
||||||
|
m_data = other.m_data;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
allocate(other.type());
|
||||||
|
other.copy(*this);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <template <typename> class Other>
|
||||||
|
thing(const thing<Other>& other)
|
||||||
|
: m_reference(other.m_reference), m_size(other.m_size) {
|
||||||
|
if (m_reference) {
|
||||||
|
m_type = other.m_type;
|
||||||
|
m_data = other.m_data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
allocate(other.type());
|
||||||
|
other.copy(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <template <typename> class Other>
|
||||||
|
thing& operator=(const thing<Other>& other) {
|
||||||
|
if (this == &other) return *this;
|
||||||
|
|
||||||
|
free();
|
||||||
|
|
||||||
|
m_reference = other.m_reference;
|
||||||
|
m_size = other.m_size;
|
||||||
|
|
||||||
if (m_reference) {
|
if (m_reference) {
|
||||||
m_type = other.m_type;
|
m_type = other.m_type;
|
||||||
@@ -299,7 +362,8 @@ public:
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void copy(thing<>& dst) const {
|
template <template <typename> class Other>
|
||||||
|
void copy(thing<Other>& dst) const {
|
||||||
switch (m_type->type) {
|
switch (m_type->type) {
|
||||||
case thing_type::S8:
|
case thing_type::S8:
|
||||||
case thing_type::S16:
|
case thing_type::S16:
|
||||||
@@ -309,7 +373,8 @@ private:
|
|||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64:
|
case thing_type::U64:
|
||||||
case thing_type::Ptr: std::memcpy(dst.m_data, m_data, m_size); return;
|
case thing_type::Ptr:
|
||||||
|
case thing_type::Slice: std::memcpy(dst.m_data, m_data, m_size); return;
|
||||||
case thing_type::Array: copy_list(*m_type, dst.m_data, m_data); return;
|
case thing_type::Array: copy_list(*m_type, dst.m_data, m_data); return;
|
||||||
case thing_type::Ref: // TODO: Implement arrays of references (I think they're possible).
|
case thing_type::Ref: // TODO: Implement arrays of references (I think they're possible).
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
@@ -360,7 +425,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T& get() {
|
T& get() {
|
||||||
if (compute_size_na(*m_type) != sizeof(T)) throw bad_thing_access();
|
if (!detail::thing_traits<T>{}(*m_type)) throw bad_thing_access();
|
||||||
return *std::launder(reinterpret_cast<T*>(m_data));
|
return *std::launder(reinterpret_cast<T*>(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,9 +436,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
const T& get() const {
|
const T& get() const {
|
||||||
if (compute_size_na(*m_type) != sizeof(T)) throw bad_thing_access();
|
if (!detail::thing_traits<T>{}(*m_type)) throw bad_thing_access();
|
||||||
return *std::launder(reinterpret_cast<const T*>(m_data));
|
return *std::launder(reinterpret_cast<const T*>(m_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void set(T&& newValue) {
|
||||||
|
if (!detail::thing_traits<T>{}(*m_type)) throw bad_thing_access();
|
||||||
|
*std::launder(reinterpret_cast<T*>(m_data)) = std::forward<T>(newValue);
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Returns a sum of two things.
|
* @brief Returns a sum of two things.
|
||||||
@@ -468,60 +539,122 @@ public:
|
|||||||
*
|
*
|
||||||
* @return The integer value.
|
* @return The integer value.
|
||||||
*/
|
*/
|
||||||
thing_type::s64 integer() const {
|
s64 integer() const {
|
||||||
switch (type().type) {
|
switch (type().type) {
|
||||||
case thing_type::S8: return get<thing_type::s8>();
|
case thing_type::S8: return get<s8>();
|
||||||
case thing_type::S16: return get<thing_type::s16>();
|
case thing_type::S16: return get<s16>();
|
||||||
case thing_type::S32: return get<thing_type::s32>();
|
case thing_type::S32: return get<s32>();
|
||||||
case thing_type::S64: return get<thing_type::s64>();
|
case thing_type::S64: return get<s64>();
|
||||||
case thing_type::U8: return get<thing_type::u8>();
|
case thing_type::U8: return get<u8>();
|
||||||
case thing_type::U16: return get<thing_type::u16>();
|
case thing_type::U16: return get<u16>();
|
||||||
case thing_type::U32: return get<thing_type::u32>();
|
case thing_type::U32: return get<u32>();
|
||||||
case thing_type::U64: return get<thing_type::u64>();
|
case thing_type::U64: return get<u64>();
|
||||||
default: throw std::runtime_error("unreachable");
|
default: throw std::runtime_error("unreachable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize(thing_type::u64 newSize) {
|
void resize(u64 newSize) {
|
||||||
if (!is(thing_type::Array)) throw bad_thing_access();
|
switch (type().type) {
|
||||||
if (type().value.array.size > 0) throw std::runtime_error("cannot resize a static array");
|
case thing_type::Array: {
|
||||||
|
if (type().value.array.size > 0) throw std::runtime_error("cannot resize a static array");
|
||||||
|
|
||||||
auto& array = get<dynamic_array>();
|
auto& array = get<dynamic_array>();
|
||||||
if (newSize < 0 || newSize == array.size) return;
|
if (newSize < 0 || newSize == array.size) return;
|
||||||
std::size_t innerSize = compute_size_na(*type().value.array.type);
|
std::size_t innerSize = compute_size_na(*type().value.array.type);
|
||||||
std::byte* newData = new std::byte[innerSize * newSize];
|
std::byte* newData = new std::byte[innerSize * newSize];
|
||||||
std::memcpy(newData, array.data, innerSize * std::min(static_cast<thing_type::u64>(array.size), newSize));
|
std::memcpy(newData, array.data, innerSize * std::min(static_cast<u64>(array.size), newSize));
|
||||||
array.size = newSize;
|
array.size = newSize;
|
||||||
delete[] array.data;
|
delete[] array.data;
|
||||||
array.data = newData;
|
array.data = newData;
|
||||||
|
} break;
|
||||||
|
case thing_type::String: {
|
||||||
|
auto& string = get<struct string>();
|
||||||
|
if (newSize < 0 || newSize == string.size) return;
|
||||||
|
u8* newData = new u8[newSize];
|
||||||
|
std::memcpy(newData, string.data, std::min(static_cast<u64>(string.size), newSize));
|
||||||
|
string.size = newSize;
|
||||||
|
delete[] string.data;
|
||||||
|
string.data = newData;
|
||||||
|
} break;
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thing at(thing_type::u64 index) const {
|
thing at(u64 index) const {
|
||||||
if (!is(thing_type::Array)) throw bad_thing_access();
|
thing ref = { m_allocator };
|
||||||
|
|
||||||
thing ref = {};
|
|
||||||
ref.m_reference = true;
|
ref.m_reference = true;
|
||||||
ref.m_size = compute_size_na(*type().value.array.type);
|
ref.m_size = compute_size_na(*(ref.m_type = &inner_type()));
|
||||||
|
|
||||||
if (type().value.array.size == 0) {
|
switch (type().type) {
|
||||||
auto& array = get<dynamic_array>();
|
case thing_type::String: {
|
||||||
if (index < 0 || index >= array.size) throw std::out_of_range("index out of range");
|
auto& string = get<struct string>();
|
||||||
|
if (index < 0 || index >= string.size) throw std::out_of_range("index out of range");
|
||||||
|
|
||||||
ref.m_type = type().value.array.type;
|
ref.m_data = reinterpret_cast<std::byte*>(string.data + index);
|
||||||
ref.m_data = array.data + (index * ref.m_size);
|
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
case thing_type::Array: {
|
||||||
|
if (type().value.array.size == 0) {
|
||||||
|
auto& array = get<dynamic_array>();
|
||||||
|
if (index < 0 || index >= array.size) throw std::out_of_range("index out of range");
|
||||||
|
|
||||||
if (index < 0 || index >= type().value.array.size) throw std::out_of_range("index out of range");
|
ref.m_data = array.data + (index * ref.m_size);
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
ref.m_type = type().value.array.type;
|
if (index < 0 || index >= type().value.array.size) throw std::out_of_range("index out of range");
|
||||||
ref.m_data = m_data + (index * ref.m_size);
|
ref.m_data = m_data + (index * ref.m_size);
|
||||||
return ref;
|
return ref;
|
||||||
|
}
|
||||||
|
case thing_type::Slice: {
|
||||||
|
const auto& slice = get<struct slice>();
|
||||||
|
if (index < 0 || index >= slice.length) throw std::out_of_range("index out of range");
|
||||||
|
ref.m_data = slice.data + (index * ref.m_size);
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thing_type::u64 length() const {
|
thing slice(u64 begin, u64 len) const {
|
||||||
if (!is(thing_type::Array)) throw bad_thing_access();
|
auto& inner = inner_type();
|
||||||
return type().value.array.size == 0 ? get<dynamic_array>().size : type().value.array.size;
|
|
||||||
|
thing_type sliceType;
|
||||||
|
sliceType.type = thing_type::Slice;
|
||||||
|
sliceType.value.slice.type = &inner;
|
||||||
|
|
||||||
|
thing slice = { sliceType, m_allocator };
|
||||||
|
auto& data = slice.get<struct slice>();
|
||||||
|
|
||||||
|
if (begin >= length()) throw std::out_of_range("begin index out of range");
|
||||||
|
len = std::min(len, length() - begin);
|
||||||
|
|
||||||
|
switch (type().type) {
|
||||||
|
case thing_type::String: {
|
||||||
|
data.data = reinterpret_cast<std::byte*>(get<struct string>().data);
|
||||||
|
} break;
|
||||||
|
case thing_type::Array: {
|
||||||
|
data.data = ((type().value.array.size != 0) ? m_data : get<dynamic_array>().data);
|
||||||
|
} break;
|
||||||
|
case thing_type::Slice: {
|
||||||
|
data.data = get<struct slice>().data;
|
||||||
|
} break;
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.data += (compute_size_na(inner) * begin);
|
||||||
|
data.length = len;
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 length() const {
|
||||||
|
switch (type().type) {
|
||||||
|
case thing_type::String: return get<struct string>().size;
|
||||||
|
case thing_type::Array:
|
||||||
|
return type().value.array.size == 0 ? get<dynamic_array>().size : type().value.array.size;
|
||||||
|
case thing_type::Slice: return get<struct slice>().length;
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
@@ -547,13 +680,80 @@ public:
|
|||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64: std::memcpy(m_data, rhs.m_data, m_size); return;
|
case thing_type::U64: std::memcpy(m_data, rhs.m_data, m_size); return;
|
||||||
|
case thing_type::String:
|
||||||
case thing_type::Ptr:
|
case thing_type::Ptr:
|
||||||
case thing_type::Ref:
|
case thing_type::Ref:
|
||||||
case thing_type::Array: throw std::runtime_error("unimplemented");
|
case thing_type::Array:
|
||||||
|
case thing_type::Slice: throw std::runtime_error("unimplemented");
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("unreachable");
|
throw std::runtime_error("unreachable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void assign(const thing& rhs) {
|
||||||
|
if (type() != rhs.type()) throw std::runtime_error("thing type mismatch");
|
||||||
|
// TODO: Move this to another function
|
||||||
|
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: std::memcpy(m_data, rhs.m_data, m_size); return;
|
||||||
|
case thing_type::Ptr:
|
||||||
|
case thing_type::Ref:
|
||||||
|
case thing_type::Array:
|
||||||
|
case thing_type::Slice: throw std::runtime_error("unimplemented");
|
||||||
|
case thing_type::Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void assign(const T& value) {
|
||||||
|
if constexpr (detail::cassignable_to_thing<T, thing>::value) {
|
||||||
|
detail::thing_traits<T>::assign_to(*this, value);
|
||||||
|
} else {
|
||||||
|
get<T>() = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void assign(T&& value) { // NOLINT
|
||||||
|
if constexpr (detail::massignable_to_thing<T, thing>::value) {
|
||||||
|
detail::thing_traits<T>::assign_to(*this, std::move(value)); // NOLINT
|
||||||
|
} else {
|
||||||
|
get<T>() = std::move(value); // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
iterator begin() {
|
||||||
|
switch (type().type) {
|
||||||
|
case thing_type::String: return { this, reinterpret_cast<std::byte*>(get<struct string>().data) };
|
||||||
|
case thing_type::Array: return { this, (type().value.array.size == 0) ? get<dynamic_array>().data : m_data };
|
||||||
|
case thing_type::Slice: return { this, get<struct slice>().data };
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() { return begin() + length(); }
|
||||||
|
|
||||||
|
const_iterator cbegin() const {
|
||||||
|
switch (type().type) {
|
||||||
|
case thing_type::String: return { this, reinterpret_cast<std::byte*>(get<struct string>().data) };
|
||||||
|
case thing_type::Array: return { this, (type().value.array.size == 0) ? get<dynamic_array>().data : m_data };
|
||||||
|
case thing_type::Slice: return { this, get<struct slice>().data };
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cend() const { return cbegin() + length(); }
|
||||||
|
|
||||||
|
const_iterator begin() const { return cbegin(); }
|
||||||
|
|
||||||
|
const_iterator end() const { return cend(); }
|
||||||
private:
|
private:
|
||||||
static void copy_list(const thing_type& arrayType, void* dst, const void* src) {
|
static void copy_list(const thing_type& arrayType, void* dst, const void* src) {
|
||||||
if (arrayType.type != thing_type::Array || arrayType.value.array.type == nullptr)
|
if (arrayType.type != thing_type::Array || arrayType.value.array.type == nullptr)
|
||||||
@@ -588,8 +788,10 @@ private:
|
|||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64:
|
case thing_type::U64:
|
||||||
|
case thing_type::String:
|
||||||
case thing_type::Ptr:
|
case thing_type::Ptr:
|
||||||
case thing_type::Ref: std::memcpy(dst, src, size * elementSize); return;
|
case thing_type::Ref:
|
||||||
|
case thing_type::Slice: std::memcpy(dst, src, size * elementSize); return;
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
for (std::size_t i = 0; i < size; ++i) {
|
for (std::size_t i = 0; i < size; ++i) {
|
||||||
copy_list(*innerType.value.array.type,
|
copy_list(*innerType.value.array.type,
|
||||||
@@ -604,18 +806,20 @@ private:
|
|||||||
private:
|
private:
|
||||||
static std::size_t compute_size_na(const thing_type& type) {
|
static std::size_t compute_size_na(const thing_type& type) {
|
||||||
switch (type.type) {
|
switch (type.type) {
|
||||||
case thing_type::S8: return sizeof(thing_type::s8);
|
case thing_type::S8: return sizeof(s8);
|
||||||
case thing_type::S16: return sizeof(thing_type::s16);
|
case thing_type::S16: return sizeof(s16);
|
||||||
case thing_type::S32: return sizeof(thing_type::s32);
|
case thing_type::S32: return sizeof(s32);
|
||||||
case thing_type::S64: return sizeof(thing_type::s64);
|
case thing_type::S64: return sizeof(s64);
|
||||||
case thing_type::U8: return sizeof(thing_type::u8);
|
case thing_type::U8: return sizeof(u8);
|
||||||
case thing_type::U16: return sizeof(thing_type::u16);
|
case thing_type::U16: return sizeof(u16);
|
||||||
case thing_type::U32: return sizeof(thing_type::u32);
|
case thing_type::U32: return sizeof(u32);
|
||||||
case thing_type::U64: return sizeof(thing_type::u64);
|
case thing_type::U64: return sizeof(u64);
|
||||||
|
case thing_type::String: return sizeof(string);
|
||||||
case thing_type::Ptr: return sizeof(void*);
|
case thing_type::Ptr: return sizeof(void*);
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
return type.value.array.size == 0 ? sizeof(dynamic_array)
|
return type.value.array.size == 0 ? sizeof(dynamic_array)
|
||||||
: compute_size_na(*type.value.array.type) * type.value.array.size;
|
: compute_size_na(*type.value.array.type) * type.value.array.size;
|
||||||
|
case thing_type::Slice: return sizeof(struct slice);
|
||||||
case thing_type::Ref:
|
case thing_type::Ref:
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
@@ -629,14 +833,14 @@ private:
|
|||||||
template <typename Func>
|
template <typename Func>
|
||||||
decltype(auto) visit_primitive(Func&& func) const {
|
decltype(auto) visit_primitive(Func&& func) const {
|
||||||
switch (type().type) {
|
switch (type().type) {
|
||||||
case thing_type::S8: return std::forward<Func>(func)(get<thing_type::s8>());
|
case thing_type::S8: return std::forward<Func>(func)(get<s8>());
|
||||||
case thing_type::S16: return std::forward<Func>(func)(get<thing_type::s16>());
|
case thing_type::S16: return std::forward<Func>(func)(get<s16>());
|
||||||
case thing_type::S32: return std::forward<Func>(func)(get<thing_type::s32>());
|
case thing_type::S32: return std::forward<Func>(func)(get<s32>());
|
||||||
case thing_type::S64: return std::forward<Func>(func)(get<thing_type::s64>());
|
case thing_type::S64: return std::forward<Func>(func)(get<s64>());
|
||||||
case thing_type::U8: return std::forward<Func>(func)(get<thing_type::u8>());
|
case thing_type::U8: return std::forward<Func>(func)(get<u8>());
|
||||||
case thing_type::U16: return std::forward<Func>(func)(get<thing_type::u16>());
|
case thing_type::U16: return std::forward<Func>(func)(get<u16>());
|
||||||
case thing_type::U32: return std::forward<Func>(func)(get<thing_type::u32>());
|
case thing_type::U32: return std::forward<Func>(func)(get<u32>());
|
||||||
case thing_type::U64: return std::forward<Func>(func)(get<thing_type::u64>());
|
case thing_type::U64: return std::forward<Func>(func)(get<u64>());
|
||||||
default: throw bad_thing_access();
|
default: throw bad_thing_access();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -723,33 +927,19 @@ private:
|
|||||||
|
|
||||||
thing res = { thing_type{ resultType }, m_allocator };
|
thing res = { thing_type{ resultType }, m_allocator };
|
||||||
switch (resultType) {
|
switch (resultType) {
|
||||||
case thing_type::S8:
|
case thing_type::S8: res.get<s8>() = Op{}(cast_to<s8>(), rhs.cast_to<s8>()); return res;
|
||||||
res.get<thing_type::s8>() = Op{}(cast_to<thing_type::s8>(), rhs.cast_to<thing_type::s8>());
|
case thing_type::S16: res.get<s16>() = Op{}(cast_to<s16>(), rhs.cast_to<s16>()); return res;
|
||||||
return res;
|
case thing_type::S32: res.get<s32>() = Op{}(cast_to<s32>(), rhs.cast_to<s32>()); return res;
|
||||||
case thing_type::S16:
|
case thing_type::S64: res.get<s64>() = Op{}(cast_to<s64>(), rhs.cast_to<s64>()); return res;
|
||||||
res.get<thing_type::s16>() = Op{}(cast_to<thing_type::s16>(), rhs.cast_to<thing_type::s16>());
|
case thing_type::U8: res.get<u8>() = Op{}(cast_to<u8>(), rhs.cast_to<u8>()); return res;
|
||||||
return res;
|
case thing_type::U16: res.get<u16>() = Op{}(cast_to<u16>(), rhs.cast_to<u16>()); return res;
|
||||||
case thing_type::S32:
|
case thing_type::U32: res.get<u32>() = Op{}(cast_to<u32>(), rhs.cast_to<u32>()); return res;
|
||||||
res.get<thing_type::s32>() = Op{}(cast_to<thing_type::s32>(), rhs.cast_to<thing_type::s32>());
|
case thing_type::U64: res.get<u64>() = Op{}(cast_to<u64>(), rhs.cast_to<u64>()); return res;
|
||||||
return res;
|
case thing_type::String:
|
||||||
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::Ptr: // TODO: Pointer arithmetics
|
||||||
case thing_type::Ref:
|
case thing_type::Ref:
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
|
case thing_type::Slice:
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("unreachable");
|
throw std::runtime_error("unreachable");
|
||||||
@@ -774,17 +964,57 @@ private:
|
|||||||
m_data = nullptr;
|
m_data = nullptr;
|
||||||
m_type = nullptr;
|
m_type = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thing_type& inner_type() const {
|
||||||
|
switch (type().type) {
|
||||||
|
case thing_type::String: {
|
||||||
|
static thing_type s_inner = { thing_type::U8 };
|
||||||
|
return s_inner;
|
||||||
|
}
|
||||||
|
case thing_type::Array: return *type().value.array.type;
|
||||||
|
case thing_type::Slice: return *type().value.slice.type;
|
||||||
|
default: throw bad_thing_access();
|
||||||
|
}
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
// A flag indicating whether the thing instance owns the data, or not.
|
// A flag indicating whether the thing instance owns the data, or not.
|
||||||
bool m_reference = false;
|
bool m_reference = false;
|
||||||
|
|
||||||
thing_type* m_type = nullptr;
|
const thing_type* m_type = nullptr;
|
||||||
std::size_t m_size = 0;
|
std::size_t m_size = 0;
|
||||||
std::byte* m_data = nullptr;
|
std::byte* m_data = nullptr;
|
||||||
|
|
||||||
allocator_type m_allocator;
|
allocator_type m_allocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<std::string_view> {
|
||||||
|
template <template <typename...> class Allocator>
|
||||||
|
static void assign_to(thing<Allocator>& thing, const std::string_view& value) {
|
||||||
|
using Thing = furvm::thing<Allocator>;
|
||||||
|
|
||||||
|
auto& string = thing.template get<typename Thing::string>();
|
||||||
|
string.data = new furvm::u8[string.size = value.length()];
|
||||||
|
std::memcpy(string.data, value.data(), value.length());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<std::string> {
|
||||||
|
template <template <typename...> class Allocator>
|
||||||
|
static void assign_to(thing<Allocator>& thing, const std::string& value) {
|
||||||
|
using Thing = furvm::thing<Allocator>;
|
||||||
|
|
||||||
|
auto& string = thing.template get<typename Thing::string>();
|
||||||
|
string.data = new furvm::u8[string.size = value.length()];
|
||||||
|
std::memcpy(string.data, value.data(), value.length());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
} // namespace furvm
|
} // namespace furvm
|
||||||
|
|
||||||
#endif // FURVM_THING_HPP
|
#endif // FURVM_THING_HPP
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
#ifndef FURVM_TYPES_HPP
|
||||||
|
#define FURVM_TYPES_HPP
|
||||||
|
|
||||||
|
#include "furvm/fwd.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace furvm {
|
||||||
|
|
||||||
|
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 thing_type {
|
||||||
|
struct array_value {
|
||||||
|
thing_type* type;
|
||||||
|
std::size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct slice_value {
|
||||||
|
thing_type* type;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum type { // NOLINT
|
||||||
|
S8 = 0,
|
||||||
|
S16,
|
||||||
|
S32,
|
||||||
|
S64,
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
|
U64,
|
||||||
|
String,
|
||||||
|
Ptr,
|
||||||
|
Ref,
|
||||||
|
Array,
|
||||||
|
Slice,
|
||||||
|
|
||||||
|
Count,
|
||||||
|
} type = Count;
|
||||||
|
union value {
|
||||||
|
std::nullptr_t null = nullptr;
|
||||||
|
thing_type* typeRef;
|
||||||
|
array_value array;
|
||||||
|
slice_value slice;
|
||||||
|
|
||||||
|
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:
|
||||||
|
case String: 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 Slice: return *value.slice.type == *other.value.slice.type;
|
||||||
|
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 String:
|
||||||
|
case Ptr:
|
||||||
|
case Ref:
|
||||||
|
case Array:
|
||||||
|
case Slice: 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 thing_type::String:
|
||||||
|
case Ptr:
|
||||||
|
case Ref:
|
||||||
|
case Array:
|
||||||
|
case Slice: return 0;
|
||||||
|
case Count: break;
|
||||||
|
}
|
||||||
|
throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T, typename = void>
|
||||||
|
struct overrides_thing_type_matching : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct overrides_thing_type_matching<T, std::void_t<decltype(T::matches(std::declval<const thing_type&>()))>>
|
||||||
|
: std::is_same<decltype(T::matches(std::declval<const thing_type&>())), bool> {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct thing_traits {
|
||||||
|
bool operator()(const thing_type& type) const {
|
||||||
|
if constexpr (overrides_thing_type_matching<T>::value) {
|
||||||
|
return T::matches(type);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s8> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S8; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u8> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U8; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s16> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S16; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u16> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U16; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s32> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S32; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u32> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U32; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<s64> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::S64; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct thing_traits<u64> {
|
||||||
|
bool operator()(const thing_type& type) const { return type.type == thing_type::U64; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Inner>
|
||||||
|
struct thing_traits<Inner*> {
|
||||||
|
bool operator()(const thing_type& type) const {
|
||||||
|
return (type.type == thing_type::Ptr || type.type == thing_type::Ref) &&
|
||||||
|
thing_traits<Inner>{}(*type.value.typeRef);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Thing, typename = void>
|
||||||
|
struct cassignable_to_thing : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing>
|
||||||
|
struct cassignable_to_thing<T,
|
||||||
|
Thing,
|
||||||
|
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<const T&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing, typename = void>
|
||||||
|
struct massignable_to_thing : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T, typename Thing>
|
||||||
|
struct massignable_to_thing<T,
|
||||||
|
Thing,
|
||||||
|
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<T&&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
} // namespace furvm
|
||||||
|
|
||||||
|
#endif // FURVM_TYPES_HPP
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
file(GLOB_RECURSE DISFURAS_SRCS "${CMAKE_SOURCE_DIR}/src/disfuras/**.cpp")
|
||||||
|
add_executable(disfuras ${DISFURAS_SRCS})
|
||||||
|
target_include_directories(disfuras PRIVATE ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_link_libraries(disfuras PUBLIC furlang libfurc libfurvm)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
file(GLOB_RECURSE FURAS_SRCS "${CMAKE_SOURCE_DIR}/src/furas/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURAS_HDRS "${CMAKE_SOURCE_DIR}/include/furas/**.hpp")
|
||||||
|
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
|
||||||
|
target_include_directories(furas PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_link_libraries(furas PRIVATE furlang libfurvm)
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "furvm/function.hpp"
|
#include "furvm/function.hpp"
|
||||||
#include "furvm/fwd.hpp"
|
#include "furvm/fwd.hpp"
|
||||||
#include "furvm/instruction.hpp"
|
#include "furvm/instruction.hpp"
|
||||||
|
#include "furvm/module.hpp"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -24,6 +25,7 @@ namespace {
|
|||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
std::unordered_map<enum token::type, furvm::instruction_t> instructions = {
|
std::unordered_map<enum token::type, furvm::instruction_t> instructions = {
|
||||||
{ token::Array, furvm::instruction_t::Array },
|
{ token::Array, furvm::instruction_t::Array },
|
||||||
|
{ token::Slice, furvm::instruction_t::Slice },
|
||||||
{ token::Get, furvm::instruction_t::Get },
|
{ token::Get, furvm::instruction_t::Get },
|
||||||
{ token::Set, furvm::instruction_t::Set },
|
{ token::Set, furvm::instruction_t::Set },
|
||||||
{ token::Drop, furvm::instruction_t::Drop },
|
{ token::Drop, furvm::instruction_t::Drop },
|
||||||
@@ -77,6 +79,7 @@ const char* token_type(enum token::type type) {
|
|||||||
case token::Allocate: return "allocate";
|
case token::Allocate: return "allocate";
|
||||||
case token::Push: return "push";
|
case token::Push: return "push";
|
||||||
case token::Array: return "array";
|
case token::Array: return "array";
|
||||||
|
case token::Slice: return "slice";
|
||||||
case token::Get: return "get";
|
case token::Get: return "get";
|
||||||
case token::Set: return "set";
|
case token::Set: return "set";
|
||||||
case token::Drop: return "drop";
|
case token::Drop: return "drop";
|
||||||
@@ -160,6 +163,8 @@ struct mod_context {
|
|||||||
case furvm::mod_type::Array:
|
case furvm::mod_type::Array:
|
||||||
return lhs->value.array.size == rhs->value.array.size &&
|
return lhs->value.array.size == rhs->value.array.size &&
|
||||||
compare_types(mod.type_at(lhs->value.array.typeId), mod.type_at(rhs->value.array.typeId));
|
compare_types(mod.type_at(lhs->value.array.typeId), mod.type_at(rhs->value.array.typeId));
|
||||||
|
case furvm::mod_type::Slice:
|
||||||
|
return compare_types(mod.type_at(lhs->value.slice.typeId), mod.type_at(rhs->value.slice.typeId));
|
||||||
case furvm::mod_type::Import:
|
case furvm::mod_type::Import:
|
||||||
return lhs->value.imprt.modId == rhs->value.imprt.modId &&
|
return lhs->value.imprt.modId == rhs->value.imprt.modId &&
|
||||||
lhs->value.imprt.typeId == rhs->value.imprt.typeId;
|
lhs->value.imprt.typeId == rhs->value.imprt.typeId;
|
||||||
@@ -252,6 +257,15 @@ struct mod_context {
|
|||||||
|
|
||||||
return mod.emplace_type(inner.id(), size->value.uint);
|
return mod.emplace_type(inner.id(), size->value.uint);
|
||||||
}
|
}
|
||||||
|
case token::Slice: {
|
||||||
|
auto result = next_token(lexer);
|
||||||
|
|
||||||
|
if (!result) throw std::runtime_error("error");
|
||||||
|
|
||||||
|
auto inner = eat_type(lexer, result.value);
|
||||||
|
|
||||||
|
return mod.emplace_type(furvm::mod_type::Slice, inner.id());
|
||||||
|
}
|
||||||
default: throw std::runtime_error("error");
|
default: throw std::runtime_error("error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -463,6 +477,7 @@ struct mod_context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case token::Array:
|
case token::Array:
|
||||||
|
case token::Slice:
|
||||||
case token::Get:
|
case token::Get:
|
||||||
case token::Set:
|
case token::Set:
|
||||||
case token::Drop:
|
case token::Drop:
|
||||||
@@ -19,7 +19,7 @@ token_r lexer::next_token() {
|
|||||||
// TODO: Add support for single-line comments
|
// TODO: Add support for single-line comments
|
||||||
// TODO: Add support for multi-line comments
|
// TODO: Add support for multi-line comments
|
||||||
|
|
||||||
if (m_cursor >= m_content.size()) return token_r{ lexer_error{ lexer_error::EndOfFile, location() } };
|
if (m_cursor >= m_content.size()) return token_r::error(lexer_error{ lexer_error::EndOfFile, location() });
|
||||||
|
|
||||||
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
|
// TODO: Add support for negative integers (I am positive thanks to stasiu :v:)
|
||||||
// TODO: Add support for hexadecimal and binary numeric literals
|
// TODO: Add support for hexadecimal and binary numeric literals
|
||||||
@@ -43,6 +43,7 @@ token_r lexer::next_token() {
|
|||||||
|
|
||||||
{ "push", token::Push },
|
{ "push", token::Push },
|
||||||
{ "array", token::Array },
|
{ "array", token::Array },
|
||||||
|
{ "slice", token::Slice },
|
||||||
{ "get", token::Get },
|
{ "get", token::Get },
|
||||||
{ "set", token::Set },
|
{ "set", token::Set },
|
||||||
{ "drop", token::Drop },
|
{ "drop", token::Drop },
|
||||||
@@ -92,9 +93,9 @@ token_r lexer::next_token() {
|
|||||||
case '.': ++m_cursor; return { token::Dot };
|
case '.': ++m_cursor; return { token::Dot };
|
||||||
case ':': ++m_cursor; return { token::Colon };
|
case ':': ++m_cursor; return { token::Colon };
|
||||||
default:
|
default:
|
||||||
return token_r{
|
return token_r::error(lexer_error{ lexer_error::UnknownCharacter,
|
||||||
lexer_error{ lexer_error::UnknownCharacter, location(), "Unknown character '"s + m_content[m_cursor] + "'" }
|
location(),
|
||||||
};
|
"Unknown character '"s + m_content[m_cursor] + "'" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
file(GLOB_RECURSE FURC_SRCS "${CMAKE_SOURCE_DIR}/src/furc/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURC_HDRS "${CMAKE_SOURCE_DIR}/include/furc/**.hpp")
|
||||||
|
add_library(libfurc ${FURC_SRCS} ${FURC_HDRS})
|
||||||
|
target_include_directories(libfurc PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_compile_definitions(libfurc PRIVATE LIBFURC)
|
||||||
|
set_target_properties(libfurc PROPERTIES PREFIX "")
|
||||||
|
target_link_libraries(libfurc PUBLIC furlang libfurvm)
|
||||||
|
add_executable(furc "${CMAKE_SOURCE_DIR}/src/furc/main.cpp")
|
||||||
|
target_link_libraries(furc PRIVATE libfurc)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
add_executable(furc_tests "${CMAKE_SOURCE_DIR}/test/furc/ssa.cpp")
|
||||||
|
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
|
||||||
|
gtest_discover_tests(furc_tests)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include "furc/back/furvm/target.hpp"
|
||||||
|
|
||||||
|
#include "furvm/function.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
furvm::mod furvm_generator::generate(const ir_module& mod) {
|
||||||
|
if (!mod.variables.empty()) throw std::runtime_error("global variables are not supported for furvm yet");
|
||||||
|
|
||||||
|
furvm::mod furvmMod;
|
||||||
|
for (const auto& func : mod.functions) {
|
||||||
|
furvmMod.emplace_function(furvm::function_sig{ {}, {} }, furvmMod.bytecode().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return furvmMod;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
|
#ifndef LIBFURC
|
||||||
|
|
||||||
#include "furc/front/lexer.hpp"
|
#include "furc/front/lexer.hpp"
|
||||||
#include "furc/front/parser.hpp"
|
#include "furc/front/parser.hpp"
|
||||||
#include "furc/middle/ir.hpp"
|
#include "furc/middle/ir.hpp"
|
||||||
|
#include "furc/middle/reg_gen.hpp"
|
||||||
|
#include "furc/middle/ssa.hpp"
|
||||||
#include "furlang/arena.hpp"
|
#include "furlang/arena.hpp"
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
@@ -16,6 +20,12 @@ int main(void) {
|
|||||||
furc::lexer lexer = { "<AK>", content };
|
furc::lexer lexer = { "<AK>", content };
|
||||||
furc::parser parser = { std::move(lexer), arena };
|
furc::parser parser = { std::move(lexer), arena };
|
||||||
furc::ir_module irModule = furc::ir_generator::generate(parser.parse());
|
furc::ir_module irModule = furc::ir_generator::generate(parser.parse());
|
||||||
|
for (auto& func : irModule.functions) {
|
||||||
|
furc::ssa ssa(*func);
|
||||||
|
furc::reg_gen gen(*func, ssa);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // LIBFURC
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* Sources:
|
||||||
|
* - Practical Improvements to the Construction and Deconstruction of Static Single Assignment Form:
|
||||||
|
* https://web.archive.org/web/20100607003509/http://www.cs.rice.edu/~harv/my_papers/ssa.pdf
|
||||||
|
* - A Simple, Fast Dominance Algorithm:
|
||||||
|
* https://www.researchgate.net/publication/2569680_A_Simple_Fast_Dominance_Algorithm
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "furc/middle/ssa.hpp"
|
||||||
|
|
||||||
|
#include "furc/middle/ir.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <stack>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc {
|
||||||
|
|
||||||
|
void ssa::compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks) {
|
||||||
|
cfgBlocks.resize(irBlocks.size());
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& block = irBlocks[i];
|
||||||
|
if (block.instructions.empty()) continue;
|
||||||
|
|
||||||
|
const auto& termInstr = block.instructions.back();
|
||||||
|
switch (termInstr.type) {
|
||||||
|
case ir_instruction::Branch: {
|
||||||
|
const auto& dst = termInstr.destination.value();
|
||||||
|
assert(dst.type == ir_operand::Block);
|
||||||
|
cfgBlocks[dst.value.block].preds.insert(i);
|
||||||
|
cfgBlocks[i].sucs.insert(dst.value.block);
|
||||||
|
} break;
|
||||||
|
case ir_instruction::BranchCond: {
|
||||||
|
const auto& dst = termInstr.destination.value();
|
||||||
|
assert(dst.type == ir_operand::BlockPair);
|
||||||
|
cfgBlocks[dst.value.blockPair.first].preds.insert(i);
|
||||||
|
cfgBlocks[dst.value.blockPair.second].preds.insert(i);
|
||||||
|
cfgBlocks[i].sucs.insert(dst.value.blockPair.first);
|
||||||
|
cfgBlocks[i].sucs.insert(dst.value.blockPair.second);
|
||||||
|
} break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::collect_registers(const std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::vector<register_info>& registers,
|
||||||
|
std::unordered_set<std::uint64_t>& globals) {
|
||||||
|
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
|
||||||
|
const auto& block = irBlocks[i];
|
||||||
|
for (const auto& instr : block.instructions) {
|
||||||
|
for (const auto& src : instr.sources) {
|
||||||
|
if (src.type != ir_operand::Register) continue;
|
||||||
|
const auto& reg = registers.at(src.value.reg.name);
|
||||||
|
if (reg.sites.find(i) != reg.sites.end()) continue;
|
||||||
|
globals.insert(src.value.reg.name);
|
||||||
|
}
|
||||||
|
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||||
|
registers[instr.destination->value.reg.name].sites.insert(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::build_dtree(const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::size_t>& order) {
|
||||||
|
ssaBlocks[order.front()].idom = order.front();
|
||||||
|
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (auto it = order.begin() + 1; it != order.end(); ++it) {
|
||||||
|
static constexpr std::uint64_t INVALID = std::numeric_limits<std::uint64_t>::max();
|
||||||
|
|
||||||
|
std::uint64_t newIdom = -1;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (std::uint64_t pred : cfgBlocks[*it].preds) {
|
||||||
|
if (ssaBlocks[pred].idom == INVALID) continue;
|
||||||
|
newIdom = found ? intersect(ssaBlocks, pred, newIdom) : pred;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (ssaBlocks[*it].idom != newIdom) {
|
||||||
|
ssaBlocks[*it].idom = newIdom;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks) {
|
||||||
|
for (std::uint64_t i = 0; i < ssaBlocks.size(); ++i) {
|
||||||
|
if (cfgBlocks[i].preds.size() < 2) continue;
|
||||||
|
const auto& cfgBlock = cfgBlocks[i];
|
||||||
|
auto& ssaBlock = ssaBlocks[i];
|
||||||
|
|
||||||
|
for (std::uint64_t worker : cfgBlock.preds) {
|
||||||
|
while (worker != ssaBlock.idom) {
|
||||||
|
ssaBlocks[worker].df.insert(i);
|
||||||
|
worker = ssaBlocks[worker].idom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::compute_rpo(std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::vector<std::size_t>& order) {
|
||||||
|
std::unordered_set<std::size_t> visited;
|
||||||
|
if (!cfgBlocks.empty()) rpo_dfs(visited, order, 0, cfgBlocks);
|
||||||
|
std::reverse(order.begin(), order.end());
|
||||||
|
ssaBlocks.resize(cfgBlocks.size());
|
||||||
|
for (std::size_t i = 0; i < order.size(); ++i) {
|
||||||
|
ssaBlocks[order[i]].order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::ssaification(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<register_info>& registers,
|
||||||
|
const std::unordered_set<std::uint64_t>& globals) {
|
||||||
|
std::vector<std::uint64_t> worklist;
|
||||||
|
|
||||||
|
for (std::uint64_t i = 0; i < registers.size(); ++i) {
|
||||||
|
const auto& reg = registers[i];
|
||||||
|
if (reg.sites.size() < 2 || globals.find(i) == globals.end()) continue;
|
||||||
|
worklist.insert(worklist.end(), reg.sites.begin(), reg.sites.end());
|
||||||
|
|
||||||
|
std::unordered_set<std::uint64_t> done;
|
||||||
|
while (!worklist.empty()) {
|
||||||
|
const auto blockIdx = worklist.back();
|
||||||
|
worklist.pop_back();
|
||||||
|
|
||||||
|
for (auto frontier : ssaBlocks[blockIdx].df) {
|
||||||
|
if (done.find(frontier) != done.end()) continue;
|
||||||
|
done.insert(frontier);
|
||||||
|
|
||||||
|
auto& target = irBlocks[frontier];
|
||||||
|
ir_instruction instr = { ir_instruction::Phi, ir_operand{ ir_operand::Register, i } };
|
||||||
|
for (const auto& pred : cfgBlocks[frontier].preds)
|
||||||
|
instr.sources.emplace_back(ir_operand::PhiPair, i, pred);
|
||||||
|
target.instructions.emplace(target.instructions.begin(), std::move(instr));
|
||||||
|
|
||||||
|
if (reg.sites.find(frontier) == reg.sites.end()) worklist.push_back(frontier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::rename(std::vector<ir_basic_block>& irBlocks,
|
||||||
|
std::size_t regCount,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
std::vector<ssa_block>& ssaBlocks,
|
||||||
|
const std::vector<std::uint64_t>& order) {
|
||||||
|
std::vector<std::uint64_t> counters;
|
||||||
|
std::vector<std::stack<std::uint64_t>> stacks;
|
||||||
|
|
||||||
|
counters.resize(regCount);
|
||||||
|
stacks.resize(regCount);
|
||||||
|
|
||||||
|
for (auto it = order.begin() + 1; it != order.end(); ++it) {
|
||||||
|
std::uint64_t parent = ssaBlocks[*it].idom;
|
||||||
|
if (parent != std::numeric_limits<std::uint64_t>::max()) ssaBlocks[parent].children.emplace(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, order.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::rename_rec(std::vector<std::uint64_t>& counters,
|
||||||
|
std::vector<std::stack<std::uint64_t>>& stacks,
|
||||||
|
std::vector<ir_basic_block>& irBlocks,
|
||||||
|
const std::vector<cfg_block>& cfgBlocks,
|
||||||
|
const std::vector<ssa_block>& ssaBlocks,
|
||||||
|
std::size_t blockIdx) {
|
||||||
|
std::unordered_map<std::uint64_t, std::size_t> pushed;
|
||||||
|
|
||||||
|
auto& block = irBlocks[blockIdx];
|
||||||
|
for (auto& instr : block.instructions) {
|
||||||
|
if (instr.type == ir_instruction::Phi) {
|
||||||
|
auto reg = instr.destination->value.reg.name;
|
||||||
|
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
|
||||||
|
++pushed[reg];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& op : instr.sources) {
|
||||||
|
if (op.type != ir_operand::Register) continue;
|
||||||
|
auto reg = op.value.reg.name;
|
||||||
|
op.value.reg.ver = stacks[reg].top();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||||
|
auto reg = instr.destination->value.reg.name;
|
||||||
|
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
|
||||||
|
++pushed[reg];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto succIdx : cfgBlocks[blockIdx].sucs) {
|
||||||
|
auto& succ = irBlocks[succIdx];
|
||||||
|
for (auto& instr : succ.instructions) {
|
||||||
|
if (instr.type != ir_instruction::Phi) break;
|
||||||
|
for (auto& op : instr.sources) {
|
||||||
|
if (op.value.phiPair.block != blockIdx) continue;
|
||||||
|
op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::uint64_t child : ssaBlocks[blockIdx].children)
|
||||||
|
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, child);
|
||||||
|
|
||||||
|
for (auto [reg, count] : pushed)
|
||||||
|
while ((count--) > 0)
|
||||||
|
stacks[reg].pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssa::rpo_dfs(std::unordered_set<std::size_t>& visited,
|
||||||
|
std::vector<std::size_t>& order,
|
||||||
|
std::size_t block,
|
||||||
|
const std::vector<cfg_block>& blocks) {
|
||||||
|
visited.insert(block);
|
||||||
|
for (auto succ : blocks[block].sucs) {
|
||||||
|
if (visited.find(succ) != visited.end()) continue;
|
||||||
|
rpo_dfs(visited, order, succ, blocks);
|
||||||
|
}
|
||||||
|
order.push_back(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ssa::intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2) {
|
||||||
|
while (b1 != b2) {
|
||||||
|
while (m_blocks[b1].order > m_blocks[b2].order)
|
||||||
|
b1 = m_blocks[b1].idom;
|
||||||
|
while (m_blocks[b2].order > m_blocks[b1].order)
|
||||||
|
b2 = m_blocks[b2].idom;
|
||||||
|
}
|
||||||
|
return b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 5. Renaming
|
||||||
|
// std::vector<std::uint64_t> counters;
|
||||||
|
// std::vector<std::stack<std::uint64_t>> stacks;
|
||||||
|
//
|
||||||
|
// counters.resize(func.regCount);
|
||||||
|
// stacks.resize(func.regCount);
|
||||||
|
//
|
||||||
|
// for (std::size_t i = 1; i < order.size(); ++i) {
|
||||||
|
// std::size_t parent = blocks[order[i]].idom;
|
||||||
|
// if (parent != std::numeric_limits<std::size_t>::max()) blocks[parent].children.emplace(order[i]);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// auto rename = [&counters, &stacks, &blocks, &func](auto& self, std::size_t blockIdx) -> void {
|
||||||
|
// std::unordered_map<std::size_t, std::size_t> pushed;
|
||||||
|
//
|
||||||
|
// auto& block = func.blocks[blockIdx];
|
||||||
|
// for (auto& instr : block.instructions) {
|
||||||
|
// if (instr.type == ir_instruction::Phi) {
|
||||||
|
// auto reg = instr.destination->value.reg.name;
|
||||||
|
// auto idx = counters[reg]++;
|
||||||
|
// instr.destination->value.reg.ver = idx;
|
||||||
|
// stacks[reg].push(idx);
|
||||||
|
// ++pushed[reg];
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (auto& op : instr.sources) {
|
||||||
|
// if (op.type != ir_operand::Register) continue;
|
||||||
|
// auto reg = op.value.reg.name;
|
||||||
|
// op.value.reg.ver = stacks[reg].top();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||||
|
// auto reg = instr.destination->value.reg.name;
|
||||||
|
// auto idx = counters[reg]++;
|
||||||
|
// instr.destination->value.reg.ver = idx;
|
||||||
|
// stacks[reg].push(idx);
|
||||||
|
// ++pushed[reg];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (auto succIdx : blocks[blockIdx].sucs) {
|
||||||
|
// auto& succ = func.blocks[succIdx];
|
||||||
|
// for (auto& instr : succ.instructions) {
|
||||||
|
// if (instr.type != ir_instruction::Phi) break;
|
||||||
|
// for (auto& op : instr.sources) {
|
||||||
|
// if (op.value.phiPair.block != blockIdx) continue;
|
||||||
|
// op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (std::size_t child : blocks[blockIdx].children)
|
||||||
|
// self(self, child);
|
||||||
|
//
|
||||||
|
// for (auto [reg, count] : pushed)
|
||||||
|
// while (count--)
|
||||||
|
// stacks[reg].pop();
|
||||||
|
// };
|
||||||
|
// rename(rename, order.front());
|
||||||
|
// }
|
||||||
|
|
||||||
|
} // namespace furc
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
file(GLOB_RECURSE FURDB_SRCS "${CMAKE_SOURCE_DIR}/src/furdb/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURDB_HDRS "${CMAKE_SOURCE_DIR}/include/furdb/**.hpp")
|
||||||
|
add_executable(furdb ${FURDB_SRCS} ${FURDB_HDRS})
|
||||||
|
target_include_directories(furdb PRIVATE ${CMAKE_SOURCE_DIR}/include/)
|
||||||
|
target_link_libraries(furdb PUBLIC furlang libfurc libfurvm isocline)
|
||||||
@@ -107,6 +107,11 @@ static void print_type(const furvm::thing_type& type) {
|
|||||||
std::cout << type.value.array.size;
|
std::cout << type.value.array.size;
|
||||||
std::cout << ")";
|
std::cout << ")";
|
||||||
break;
|
break;
|
||||||
|
case furvm::thing_type::Slice:
|
||||||
|
std::cout << "slice(";
|
||||||
|
print_type(*type.value.slice.type);
|
||||||
|
std::cout << ")";
|
||||||
|
break;
|
||||||
case furvm::thing_type::Count: break;
|
case furvm::thing_type::Count: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,21 +121,26 @@ static void print_thing(const furvm::thing<>& thing) {
|
|||||||
print_type(thing.type());
|
print_type(thing.type());
|
||||||
std::cout << ") ";
|
std::cout << ") ";
|
||||||
switch (thing.type().type) {
|
switch (thing.type().type) {
|
||||||
case furvm::thing_type::S8: std::cout << std::to_string(thing.get<furvm::thing_type::s8>()); break;
|
case furvm::thing_type::S8: std::cout << std::to_string(thing.get<furvm::s8>()); break;
|
||||||
case furvm::thing_type::S16: std::cout << thing.get<furvm::thing_type::s16>(); break;
|
case furvm::thing_type::S16: std::cout << thing.get<furvm::s16>(); break;
|
||||||
case furvm::thing_type::S32: std::cout << thing.get<furvm::thing_type::s32>(); break;
|
case furvm::thing_type::S32: std::cout << thing.get<furvm::s32>(); break;
|
||||||
case furvm::thing_type::S64: std::cout << thing.get<furvm::thing_type::s64>(); break;
|
case furvm::thing_type::S64: std::cout << thing.get<furvm::s64>(); break;
|
||||||
case furvm::thing_type::U8: std::cout << std::to_string(thing.get<furvm::thing_type::u8>()); break;
|
case furvm::thing_type::U8: std::cout << std::to_string(thing.get<furvm::u8>()); break;
|
||||||
case furvm::thing_type::U16: std::cout << thing.get<furvm::thing_type::u16>(); break;
|
case furvm::thing_type::U16: std::cout << thing.get<furvm::u16>(); break;
|
||||||
case furvm::thing_type::U32: std::cout << thing.get<furvm::thing_type::u32>(); break;
|
case furvm::thing_type::U32: std::cout << thing.get<furvm::u32>(); break;
|
||||||
case furvm::thing_type::U64: std::cout << thing.get<furvm::thing_type::u64>(); break;
|
case furvm::thing_type::U64: std::cout << thing.get<furvm::u64>(); break;
|
||||||
case furvm::thing_type::Ptr: std::cout << thing.get<const void*>(); break;
|
case furvm::thing_type::Ptr: std::cout << thing.get<const void*>(); break;
|
||||||
case furvm::thing_type::Array: {
|
case furvm::thing_type::Array:
|
||||||
if (thing.type().value.array.size == 0) std::cout << thing.length();
|
case furvm::thing_type::Slice: {
|
||||||
std::cout << "{ ";
|
if (thing.length() == 0) {
|
||||||
for (std::size_t i = 0; i < thing.length(); ++i) {
|
std::cout << "{}";
|
||||||
if (i > 0) std::cout << ", ";
|
break;
|
||||||
print_thing(thing.at(i));
|
}
|
||||||
|
|
||||||
|
std::cout << '(' << thing.length() << ") { ";
|
||||||
|
for (const auto& el : thing) {
|
||||||
|
print_thing(el);
|
||||||
|
std::cout << ", ";
|
||||||
}
|
}
|
||||||
std::cout << " }";
|
std::cout << " }";
|
||||||
} break;
|
} break;
|
||||||
@@ -49,6 +49,8 @@ void context::print_instruction() const {
|
|||||||
"push",
|
"push",
|
||||||
// Array
|
// Array
|
||||||
"array",
|
"array",
|
||||||
|
// Slice
|
||||||
|
"slice",
|
||||||
// Get
|
// Get
|
||||||
"get",
|
"get",
|
||||||
// Set
|
// Set
|
||||||
@@ -15,17 +15,17 @@ static void print_thing(const furvm::thing<>& thing) {
|
|||||||
using namespace furvm;
|
using namespace furvm;
|
||||||
|
|
||||||
switch (thing.type().type) {
|
switch (thing.type().type) {
|
||||||
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
|
case thing_type::S8: std::cout << thing.cast_to<s16>(); break;
|
||||||
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
|
case thing_type::S16: std::cout << thing.get<s16>(); break;
|
||||||
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
|
case thing_type::S32: std::cout << thing.get<s32>(); break;
|
||||||
case thing_type::S64: std::cout << thing.get<thing_type::s64>(); break;
|
case thing_type::S64: std::cout << thing.get<s64>(); break;
|
||||||
case thing_type::U8: std::cout << thing.get<thing_type::u8>(); break;
|
case thing_type::U8: std::cout << thing.get<u8>(); break;
|
||||||
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
|
case thing_type::U16: std::cout << thing.get<u16>(); break;
|
||||||
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
|
case thing_type::U32: std::cout << thing.get<u32>(); break;
|
||||||
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
|
case thing_type::U64: std::cout << thing.get<u64>(); break;
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
std::cout << "{ ";
|
std::cout << "{ ";
|
||||||
for (thing_type::u64 i = 0; i < thing.length(); ++i) {
|
for (u64 i = 0; i < thing.length(); ++i) {
|
||||||
if (i > 0) std::cout << ", ";
|
if (i > 0) std::cout << ", ";
|
||||||
print_thing(thing.at(i));
|
print_thing(thing.at(i));
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
file(GLOB_RECURSE FURLANG_SRCS "${CMAKE_SOURCE_DIR}/src/furlang/**.cpp")
|
||||||
|
file(GLOB_RECURSE FURLANG_HDRS "${CMAKE_SOURCE_DIR}/include/furlang/**.hpp")
|
||||||
|
add_library(furlang STATIC ${FURLANG_SRCS} ${FURLANG_HDRS})
|
||||||
|
target_include_directories(furlang PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#include "furlang/serialization/io.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace furlang::serialization {
|
||||||
|
|
||||||
|
result<error> byte_writer::write_s8(std::int8_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_u8(std::uint8_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_s16(std::int16_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_u16(std::uint16_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_s32(std::int32_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_u32(std::uint32_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_s64(std::int64_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_u64(std::uint64_t value) {
|
||||||
|
if (m_endianness == endianness::Little)
|
||||||
|
write_integral_le(value);
|
||||||
|
else
|
||||||
|
write_integral_be(value);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_string(const char* string) {
|
||||||
|
write_u16(std::strlen(string));
|
||||||
|
m_bytes.insert(m_bytes.end(), string, string + std::strlen(string));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_string(std::string_view string) {
|
||||||
|
write_u16(string.length());
|
||||||
|
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error> byte_writer::write_string(const std::string& string) {
|
||||||
|
write_u16(string.length());
|
||||||
|
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::int8_t> byte_reader::read_s8() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::int8_t>() : read_integral_be<std::int8_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::uint8_t> byte_reader::read_u8() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::uint8_t>() : read_integral_be<std::uint8_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::int16_t> byte_reader::read_s16() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::int16_t>() : read_integral_be<std::int16_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::uint16_t> byte_reader::read_u16() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::uint16_t>() : read_integral_be<std::uint16_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::int32_t> byte_reader::read_s32() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::int32_t>() : read_integral_be<std::int32_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::uint32_t> byte_reader::read_u32() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::uint32_t>() : read_integral_be<std::uint32_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::int64_t> byte_reader::read_s64() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::int64_t>() : read_integral_be<std::int64_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::uint64_t> byte_reader::read_u64() {
|
||||||
|
return m_endianness == endianness::Little ? read_integral_le<std::uint64_t>() : read_integral_be<std::uint64_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
result<error, std::string> byte_reader::read_string() {
|
||||||
|
std::string str;
|
||||||
|
auto length = read_u16();
|
||||||
|
if (length.has_error()) return length;
|
||||||
|
str.resize(length.value());
|
||||||
|
if (m_offset + str.length() >= m_length)
|
||||||
|
return result<error, std::string>::error(error{ error_code::EndOfFile, "", m_length });
|
||||||
|
str.assign(reinterpret_cast<const char*>(m_bytes) + m_offset, str.length());
|
||||||
|
m_offset += str.length();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t byte_reader::offset() const {
|
||||||
|
return m_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furlang::serialization
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
file(GLOB_RECURSE FURVM_SRCS "src/**.cpp")
|
file(GLOB_RECURSE FURVM_SRCS "${CMAKE_SOURCE_DIR}/src/furvm/**.cpp")
|
||||||
file(GLOB_RECURSE FURVM_HDRS "include/**.hpp")
|
file(GLOB_RECURSE FURVM_HDRS "${CMAKE_SOURCE_DIR}/include/furvm/**.hpp")
|
||||||
add_library(libfurvm ${FURVM_SRCS} ${FURVM_HDRS})
|
add_library(libfurvm ${FURVM_SRCS} ${FURVM_HDRS})
|
||||||
target_include_directories(libfurvm PUBLIC include/)
|
target_include_directories(libfurvm PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||||
target_compile_definitions(libfurvm PRIVATE LIBFURVM)
|
target_compile_definitions(libfurvm PRIVATE LIBFURVM)
|
||||||
set_target_properties(libfurvm PROPERTIES PREFIX "")
|
set_target_properties(libfurvm PROPERTIES PREFIX "")
|
||||||
target_link_libraries(libfurvm PUBLIC furlang)
|
target_link_libraries(libfurvm PUBLIC furlang)
|
||||||
add_executable(furvm src/main.cpp)
|
add_executable(furvm "${CMAKE_SOURCE_DIR}/src/furvm/main.cpp")
|
||||||
target_link_libraries(furvm PRIVATE libfurvm)
|
target_link_libraries(furvm PRIVATE libfurvm)
|
||||||
|
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
|
|
||||||
file(GLOB_RECURSE FURVM_TESTS "test/**.cpp")
|
file(GLOB_RECURSE FURVM_TESTS "${CMAKE_SOURCE_DIR}/test/furvm/**.cpp")
|
||||||
add_executable(furvm_tests ${FURVM_TESTS})
|
add_executable(furvm_tests ${FURVM_TESTS})
|
||||||
target_link_libraries(furvm_tests PRIVATE libfurvm GTest::gtest_main)
|
target_link_libraries(furvm_tests PRIVATE libfurvm GTest::gtest_main)
|
||||||
gtest_discover_tests(furvm_tests)
|
gtest_discover_tests(furvm_tests)
|
||||||
@@ -31,13 +31,16 @@ thing_type executor::thing_type_impl(mod_h mod, mod_type type) const {
|
|||||||
case thing_type::U8:
|
case thing_type::U8:
|
||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64: return { static_cast<enum thing_type::type>(type.type) };
|
case thing_type::U64:
|
||||||
|
case thing_type::String: return { static_cast<enum thing_type::type>(type.type) };
|
||||||
case thing_type::Ptr: return { thing_type::Ptr, mod_to_thing_type(mod, *mod->type_at(type.value.typeRef)) };
|
case thing_type::Ptr: return { thing_type::Ptr, mod_to_thing_type(mod, *mod->type_at(type.value.typeRef)) };
|
||||||
case thing_type::Ref: return { thing_type::Ref, mod_to_thing_type(mod, *mod->type_at(type.value.typeRef)) };
|
case thing_type::Ref: return { thing_type::Ref, mod_to_thing_type(mod, *mod->type_at(type.value.typeRef)) };
|
||||||
case thing_type::Array: {
|
case thing_type::Array: {
|
||||||
return { static_cast<enum thing_type::type>(type.type),
|
return { static_cast<enum thing_type::type>(type.type),
|
||||||
{ mod_to_thing_type(mod, *mod->type_at(type.value.array.typeId)), type.value.array.size } };
|
{ mod_to_thing_type(mod, *mod->type_at(type.value.array.typeId)), type.value.array.size } };
|
||||||
}
|
}
|
||||||
|
case thing_type::Slice:
|
||||||
|
return { thing_type::Slice, mod_to_thing_type(mod, *mod->type_at(type.value.slice.typeId)) };
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("invalid thing type");
|
throw std::runtime_error("invalid thing type");
|
||||||
@@ -58,32 +61,36 @@ bool executor::compare_thing_types(const thing_type& lhs, const thing_type& rhs)
|
|||||||
case thing_type::U8:
|
case thing_type::U8:
|
||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64: return true;
|
case thing_type::U64:
|
||||||
|
case thing_type::String: return true;
|
||||||
case thing_type::Ptr:
|
case thing_type::Ptr:
|
||||||
case thing_type::Ref: return compare_thing_types(*lhs.value.typeRef, *rhs.value.typeRef);
|
case thing_type::Ref: return compare_thing_types(*lhs.value.typeRef, *rhs.value.typeRef);
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
return lhs.value.array.size == rhs.value.array.size &&
|
return lhs.value.array.size == rhs.value.array.size &&
|
||||||
compare_thing_types(*lhs.value.array.type, *rhs.value.array.type);
|
compare_thing_types(*lhs.value.array.type, *rhs.value.array.type);
|
||||||
|
case thing_type::Slice: return compare_thing_types(*lhs.value.slice.type, *rhs.value.slice.type);
|
||||||
case thing_type::Count: break;
|
case thing_type::Count: break;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("unreachable");
|
throw std::runtime_error("unreachable");
|
||||||
}
|
}
|
||||||
|
|
||||||
void executor::push_frame(const mod_h& mod, function function) {
|
void executor::push_frame(const mod_h& mod, function function) {
|
||||||
|
m_stackStorage.push_frame();
|
||||||
|
|
||||||
mod_h modInst = mod;
|
mod_h modInst = mod;
|
||||||
while (function.type() == function_t::Import) {
|
while (function.type() == function_t::Import) {
|
||||||
modInst = m_context->at(function.imp().mod);
|
modInst = m_context->at(function.imp().mod);
|
||||||
function = *modInst->function_at(function.imp().function);
|
function = *modInst->function_at(function.imp().function);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto signature = function.signature();
|
auto signature = function.signature();
|
||||||
std::vector<thing<>> args;
|
std::vector<stack_thing> args;
|
||||||
args.reserve(signature.params.size());
|
args.reserve(signature.params.size());
|
||||||
for (const auto& param : signature.params) {
|
for (const auto& param : signature.params) {
|
||||||
auto arg = pop_thing();
|
auto arg = pop_thing();
|
||||||
if (compare_thing_types(arg.type(), *mod_to_thing_type(mod, *param)))
|
if (!compare_thing_types(arg.type(), *mod_to_thing_type(mod, *param)))
|
||||||
throw std::runtime_error("function argument type mismatch");
|
throw std::runtime_error("function argument type mismatch");
|
||||||
args.push_back(std::move(arg));
|
args.emplace_back(std::move(arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
struct thing_type* returnType = nullptr;
|
struct thing_type* returnType = nullptr;
|
||||||
@@ -117,7 +124,7 @@ struct executor::frame executor::pop_frame() {
|
|||||||
if (m_frames.empty()) throw stack_underflow();
|
if (m_frames.empty()) throw stack_underflow();
|
||||||
struct executor::frame frame = m_frames.top();
|
struct executor::frame frame = m_frames.top();
|
||||||
m_frames.pop();
|
m_frames.pop();
|
||||||
std::optional<thing<>> returnValue;
|
std::optional<stack_thing> returnValue;
|
||||||
if (frame.returnType != nullptr) {
|
if (frame.returnType != nullptr) {
|
||||||
returnValue = pop_thing();
|
returnValue = pop_thing();
|
||||||
if (returnValue->type() != *frame.returnType) throw std::runtime_error("function return type mismatch");
|
if (returnValue->type() != *frame.returnType) throw std::runtime_error("function return type mismatch");
|
||||||
@@ -132,6 +139,8 @@ struct executor::frame executor::pop_frame() {
|
|||||||
}
|
}
|
||||||
m_flags = m_flags & ~executor_flags::JustHit;
|
m_flags = m_flags & ~executor_flags::JustHit;
|
||||||
|
|
||||||
|
m_stackStorage.pop_frame();
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,49 +148,49 @@ struct executor::frame executor::top_frame() const {
|
|||||||
return m_frames.top();
|
return m_frames.top();
|
||||||
}
|
}
|
||||||
|
|
||||||
thing<>& executor::push_thing(thing<>&& thing) {
|
executor::stack_thing& executor::push_thing(stack_thing&& thing) {
|
||||||
return m_stack.emplace_back(std::move(thing));
|
return m_stack.emplace_back(std::move(thing));
|
||||||
}
|
}
|
||||||
|
|
||||||
thing<>& executor::push_thing(const thing<>& thing) {
|
executor::stack_thing& executor::push_thing(const stack_thing& thing) {
|
||||||
return m_stack.emplace_back(thing);
|
return m_stack.emplace_back(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
thing<> executor::pop_thing() {
|
executor::stack_thing executor::pop_thing() {
|
||||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||||
auto top = std::move(m_stack.back());
|
auto top = std::move(m_stack.back());
|
||||||
m_stack.pop_back();
|
m_stack.pop_back();
|
||||||
return std::move(top);
|
return std::move(top);
|
||||||
}
|
}
|
||||||
|
|
||||||
thing<>& executor::top_thing() {
|
executor::stack_thing& executor::top_thing() {
|
||||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||||
return m_stack.back();
|
return m_stack.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
const thing<>& executor::top_thing() const {
|
const executor::stack_thing& executor::top_thing() const {
|
||||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||||
return m_stack.back();
|
return m_stack.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void executor::store_thing(variable_t variable, const thing<>& thing) {
|
void executor::store_thing(variable_t variable, const stack_thing& thing) {
|
||||||
auto& frame = m_frames.top();
|
auto& frame = m_frames.top();
|
||||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||||
frame.variables[variable] = thing;
|
frame.variables[variable] = thing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void executor::store_thing(variable_t variable, thing<>&& thing) {
|
void executor::store_thing(variable_t variable, stack_thing&& thing) {
|
||||||
auto& frame = m_frames.top();
|
auto& frame = m_frames.top();
|
||||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||||
frame.variables[variable] = std::move(thing);
|
frame.variables[variable] = std::move(thing);
|
||||||
}
|
}
|
||||||
|
|
||||||
thing<>& executor::load_thing(variable_t variable) {
|
executor::stack_thing& executor::load_thing(variable_t variable) {
|
||||||
auto& frame = m_frames.top();
|
auto& frame = m_frames.top();
|
||||||
return frame.variables[variable];
|
return frame.variables[variable];
|
||||||
}
|
}
|
||||||
|
|
||||||
const thing<>& executor::load_thing(variable_t variable) const {
|
const executor::stack_thing& executor::load_thing(variable_t variable) const {
|
||||||
const auto& frame = m_frames.top();
|
const auto& frame = m_frames.top();
|
||||||
return frame.variables[variable];
|
return frame.variables[variable];
|
||||||
}
|
}
|
||||||
@@ -204,30 +213,53 @@ void executor::step() {
|
|||||||
switch (instr.type) {
|
switch (instr.type) {
|
||||||
case instruction_t::NoOperation: break;
|
case instruction_t::NoOperation: break;
|
||||||
case instruction_t::PushS8: {
|
case instruction_t::PushS8: {
|
||||||
push_thing({ (struct thing_type){ thing_type::S8 } }).get<thing_type::s8>() = instr.arg.s8;
|
push_thing({ (struct thing_type){ thing_type::S8 }, { m_stackStorage } }).get<s8>() = instr.arg.s8;
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushU8: {
|
case instruction_t::PushU8: {
|
||||||
push_thing({ (struct thing_type){ thing_type::U8 } }).get<thing_type::u8>() = instr.arg.u8;
|
push_thing({ (struct thing_type){ thing_type::U8 }, { m_stackStorage } }).get<u8>() = instr.arg.u8;
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushS16: {
|
case instruction_t::PushS16: {
|
||||||
push_thing({ (struct thing_type){ thing_type::S16 } }).get<thing_type::s16>() = instr.arg.s16;
|
push_thing({ (struct thing_type){ thing_type::S16 }, { m_stackStorage } }).get<s16>() = instr.arg.s16;
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushU16: {
|
case instruction_t::PushU16: {
|
||||||
push_thing({ (struct thing_type){ thing_type::U16 } }).get<thing_type::u16>() = instr.arg.u16;
|
push_thing({ (struct thing_type){ thing_type::U16 }, { m_stackStorage } }).get<u16>() = instr.arg.u16;
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushS32: {
|
case instruction_t::PushS32: {
|
||||||
push_thing({ (struct thing_type){ thing_type::S32 } }).get<thing_type::s32>() = instr.arg.s8; // NOLINT
|
push_thing({ (struct thing_type){ thing_type::S32 }, { m_stackStorage } }).get<s32>() = instr.arg.s8; // NOLINT
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushU32: {
|
case instruction_t::PushU32: {
|
||||||
push_thing({ (struct thing_type){ thing_type::U32 } }).get<thing_type::u32>() =
|
push_thing({ (struct thing_type){ thing_type::U32 }, { m_stackStorage } }).get<u32>() =
|
||||||
static_cast<thing_type::u32>(instr.arg.u8);
|
static_cast<u32>(instr.arg.u8);
|
||||||
|
} break;
|
||||||
|
case instruction_t::PushConstant: {
|
||||||
|
auto constant = frame.mod->constant_at(instr.arg.u16);
|
||||||
|
switch (constant.type) {
|
||||||
|
case constant::S32:
|
||||||
|
push_thing({ (struct thing_type){ thing_type::S32 }, { m_stackStorage } }).get<s32>() =
|
||||||
|
constant.s32; // NOLINT
|
||||||
|
break;
|
||||||
|
case constant::U32:
|
||||||
|
push_thing({ (struct thing_type){ thing_type::U32 }, { m_stackStorage } }).get<u32>() =
|
||||||
|
constant.u32; // NOLINT
|
||||||
|
break;
|
||||||
|
case constant::S64:
|
||||||
|
push_thing({ (struct thing_type){ thing_type::S64 }, { m_stackStorage } }).get<s64>() =
|
||||||
|
constant.s64; // NOLINT
|
||||||
|
break;
|
||||||
|
case constant::U64:
|
||||||
|
push_thing({ (struct thing_type){ thing_type::U64 }, { m_stackStorage } }).get<u64>() =
|
||||||
|
constant.u64; // NOLINT
|
||||||
|
break;
|
||||||
|
case constant::String: throw std::runtime_error("unimplemented");
|
||||||
|
default: throw std::runtime_error("invalid constant");
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Array: {
|
case instruction_t::Array: {
|
||||||
const auto& type = *mod_to_thing_type(frame.mod, *frame.mod->type_at(instr.arg.u32));
|
const auto& type = *mod_to_thing_type(frame.mod, *frame.mod->type_at(instr.arg.u32));
|
||||||
if (type.type != thing_type::Array || type.value.array.type == nullptr || type.value.array.type == &type)
|
if (type.type != thing_type::Array || type.value.array.type == nullptr || type.value.array.type == &type)
|
||||||
throw std::runtime_error("invalid array type");
|
throw std::runtime_error("invalid array type");
|
||||||
|
|
||||||
auto& array = push_thing({ type });
|
auto& array = push_thing({ type, { m_stackStorage } });
|
||||||
|
|
||||||
if (type.value.array.size == 0) {
|
if (type.value.array.size == 0) {
|
||||||
auto sizeThing = pop_thing();
|
auto sizeThing = pop_thing();
|
||||||
@@ -235,6 +267,12 @@ void executor::step() {
|
|||||||
array.resize(size);
|
array.resize(size);
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
case instruction_t::Slice: {
|
||||||
|
auto length = pop_thing();
|
||||||
|
auto start = pop_thing();
|
||||||
|
auto array = pop_thing();
|
||||||
|
push_thing(array.slice(start.integer(), length.integer()));
|
||||||
|
} break;
|
||||||
case instruction_t::Get: {
|
case instruction_t::Get: {
|
||||||
auto index = pop_thing();
|
auto index = pop_thing();
|
||||||
auto array = pop_thing();
|
auto array = pop_thing();
|
||||||
@@ -262,7 +300,7 @@ void executor::step() {
|
|||||||
push_thing(top_thing());
|
push_thing(top_thing());
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Reference: {
|
case instruction_t::Reference: {
|
||||||
push_thing(thing<>::make_reference(pop_thing()));
|
push_thing(stack_thing::make_reference(pop_thing(), { m_stackStorage }));
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Add: {
|
case instruction_t::Add: {
|
||||||
auto rhs = pop_thing();
|
auto rhs = pop_thing();
|
||||||
@@ -321,12 +359,13 @@ void executor::step() {
|
|||||||
} break;
|
} break;
|
||||||
case instruction_t::Pointerof: {
|
case instruction_t::Pointerof: {
|
||||||
auto thing = pop_thing();
|
auto thing = pop_thing();
|
||||||
push_thing({ (struct thing_type){ thing_type::Ptr, m_context->tt_store().at(thing.type().id) } }).get<void*>() =
|
push_thing(
|
||||||
thing.raw();
|
{ (struct thing_type){ thing_type::Ptr, m_context->tt_store().at(thing.type().id) }, { m_stackStorage } })
|
||||||
|
.get<void*>() = thing.raw();
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Sizeof: {
|
case instruction_t::Sizeof: {
|
||||||
auto thing = pop_thing();
|
auto thing = pop_thing();
|
||||||
auto& size = push_thing({ (struct thing_type){ thing_type::U64 } });
|
auto& size = push_thing({ (struct thing_type){ thing_type::U64 }, { m_stackStorage } });
|
||||||
switch (thing.type().type) {
|
switch (thing.type().type) {
|
||||||
case thing_type::S8:
|
case thing_type::S8:
|
||||||
case thing_type::S16:
|
case thing_type::S16:
|
||||||
@@ -335,34 +374,32 @@ void executor::step() {
|
|||||||
case thing_type::U8:
|
case thing_type::U8:
|
||||||
case thing_type::U16:
|
case thing_type::U16:
|
||||||
case thing_type::U32:
|
case thing_type::U32:
|
||||||
case thing_type::U64:
|
case thing_type::U64: size.get<u64>() = static_cast<u64>(thing_type::primitive_size(thing.type().type)); break;
|
||||||
size.get<thing_type::u64>() = static_cast<thing_type::u64>(thing_type::primitive_size(thing.type().type));
|
case thing_type::Ptr: size.get<u64>() = static_cast<u64>(sizeof(void*)); break;
|
||||||
break;
|
|
||||||
case thing_type::Ptr: size.get<thing_type::u64>() = static_cast<thing_type::u64>(sizeof(void*)); break;
|
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
/* TODO: Return actual memory size of the array
|
/* TODO: Return actual memory size of the array
|
||||||
* By the memory size I mean the length times sizeof single element.
|
* By the memory size I mean the length times sizeof single element.
|
||||||
*/
|
*/
|
||||||
size.get<thing_type::u64>() = thing.length();
|
size.get<u64>() = thing.length();
|
||||||
break;
|
break;
|
||||||
default: throw std::runtime_error("unreachable");
|
default: throw std::runtime_error("unreachable");
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Lengthof: {
|
case instruction_t::Lengthof: {
|
||||||
auto thing = pop_thing();
|
auto thing = pop_thing();
|
||||||
push_thing({ (struct thing_type){ thing_type::U64 } }).get<thing_type::u64>() = thing.length();
|
push_thing({ (struct thing_type){ thing_type::U64 }, { m_stackStorage } }).get<u64>() = thing.length();
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Load: {
|
case instruction_t::Load: {
|
||||||
push_thing(std::move(thing<>::make_reference(load_thing(instr.arg.u16))));
|
push_thing(std::move(stack_thing::make_reference(load_thing(instr.arg.u16), { m_stackStorage })));
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Store: {
|
case instruction_t::Store: {
|
||||||
store_thing(instr.arg.u16, std::move(pop_thing()));
|
store_thing(instr.arg.u16, std::move(pop_thing()));
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::LoadGlobal: {
|
case instruction_t::LoadGlobal: {
|
||||||
push_thing(thing<>::make_reference(frame.mod->load_global_variable(instr.arg.u16)));
|
push_thing(stack_thing::make_reference(frame.mod->load_global_variable(instr.arg.u16), { m_stackStorage }));
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::StoreGlobal: {
|
case instruction_t::StoreGlobal: {
|
||||||
frame.mod->store_global_variable(instr.arg.u16, std::move(pop_thing()));
|
frame.mod->store_global_variable(instr.arg.u16, pop_thing());
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::Call: {
|
case instruction_t::Call: {
|
||||||
push_frame(frame.mod, *frame.mod->function_at(instr.arg.u16));
|
push_frame(frame.mod, *frame.mod->function_at(instr.arg.u16));
|
||||||
@@ -377,7 +414,6 @@ void executor::step() {
|
|||||||
pop_frame();
|
pop_frame();
|
||||||
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
|
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
|
||||||
} break;
|
} break;
|
||||||
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
|
|
||||||
default: throw std::runtime_error("unknown instruction");
|
default: throw std::runtime_error("unknown instruction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ const std::size_t instruction_argument::s_sizes[instruction_argument::Count] = {
|
|||||||
// U32
|
// U32
|
||||||
1,
|
1,
|
||||||
// Constant:
|
// Constant:
|
||||||
4,
|
2,
|
||||||
// Type:
|
// Type:
|
||||||
4,
|
4,
|
||||||
// Variable:
|
// Variable:
|
||||||
@@ -83,6 +83,8 @@ const instruction_argument_t instruction::s_arguments[instruction::Count] = {
|
|||||||
instruction_argument::Constant,
|
instruction_argument::Constant,
|
||||||
// Array:
|
// Array:
|
||||||
instruction_argument::Type,
|
instruction_argument::Type,
|
||||||
|
// Slice:
|
||||||
|
instruction_argument::None,
|
||||||
// Get:
|
// Get:
|
||||||
instruction_argument::None,
|
instruction_argument::None,
|
||||||
// Set:
|
// Set:
|
||||||
@@ -14,17 +14,25 @@ static void print_thing(const furvm::thing<>& thing) {
|
|||||||
using namespace furvm;
|
using namespace furvm;
|
||||||
|
|
||||||
switch (thing.type().type) {
|
switch (thing.type().type) {
|
||||||
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
|
case thing_type::S8: std::cout << thing.cast_to<s16>(); break;
|
||||||
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
|
case thing_type::S16: std::cout << thing.get<s16>(); break;
|
||||||
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
|
case thing_type::S32: std::cout << thing.get<s32>(); break;
|
||||||
case thing_type::S64: std::cout << thing.get<thing_type::s64>(); break;
|
case thing_type::S64: std::cout << thing.get<s64>(); break;
|
||||||
case thing_type::U8: std::cout << thing.get<thing_type::u8>(); break;
|
case thing_type::U8: std::cout << thing.get<u8>(); break;
|
||||||
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
|
case thing_type::U16: std::cout << thing.get<u16>(); break;
|
||||||
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
|
case thing_type::U32: std::cout << thing.get<u32>(); break;
|
||||||
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
|
case thing_type::U64: std::cout << thing.get<u64>(); break;
|
||||||
case thing_type::Array:
|
case thing_type::Array:
|
||||||
std::cout << "{ ";
|
std::cout << "{ ";
|
||||||
for (thing_type::u64 i = 0; i < thing.length(); ++i) {
|
for (u64 i = 0; i < thing.length(); ++i) {
|
||||||
|
if (i > 0) std::cout << ", ";
|
||||||
|
print_thing(thing.at(i));
|
||||||
|
}
|
||||||
|
std::cout << " }";
|
||||||
|
break;
|
||||||
|
case thing_type::Slice:
|
||||||
|
std::cout << "Slice [" << thing.length() << "] { ";
|
||||||
|
for (u64 i = 0; i < thing.length(); ++i) {
|
||||||
if (i > 0) std::cout << ", ";
|
if (i > 0) std::cout << ", ";
|
||||||
print_thing(thing.at(i));
|
print_thing(thing.at(i));
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,19 @@ std::ostream& mod::serialize(std::ostream& os) const {
|
|||||||
os.write(MAGIC, sizeof(MAGIC));
|
os.write(MAGIC, sizeof(MAGIC));
|
||||||
detail::serialize(os, std::uint32_t(0)); // version
|
detail::serialize(os, std::uint32_t(0)); // version
|
||||||
|
|
||||||
|
detail::serialize(os, static_cast<constant_index>(m_constants.size()));
|
||||||
|
for (const auto& constant : m_constants) {
|
||||||
|
detail::serialize(os, constant.type);
|
||||||
|
switch (constant.type) {
|
||||||
|
case constant::S32:
|
||||||
|
case constant::U32: detail::serialize(os, constant.u32); break;
|
||||||
|
case constant::S64:
|
||||||
|
case constant::U64: detail::serialize(os, constant.u64); break;
|
||||||
|
case constant::String: throw std::runtime_error("unimplemented");
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod_type_id typeCount = mod_type_id(m_types.cend() - m_types.cbegin());
|
mod_type_id typeCount = mod_type_id(m_types.cend() - m_types.cbegin());
|
||||||
detail::serialize(os, typeCount);
|
detail::serialize(os, typeCount);
|
||||||
for (mod_type_id id = 0; id < typeCount; ++id) {
|
for (mod_type_id id = 0; id < typeCount; ++id) {
|
||||||
@@ -41,6 +54,9 @@ std::ostream& mod::serialize(std::ostream& os) const {
|
|||||||
detail::serialize(os, type.value.array.typeId);
|
detail::serialize(os, type.value.array.typeId);
|
||||||
detail::serialize(os, type.value.array.size);
|
detail::serialize(os, type.value.array.size);
|
||||||
} break;
|
} break;
|
||||||
|
case mod_type::Slice: {
|
||||||
|
detail::serialize(os, type.value.slice.typeId);
|
||||||
|
} break;
|
||||||
case mod_type::Import: {
|
case mod_type::Import: {
|
||||||
detail::serialize(os, type.value.imprt.modId);
|
detail::serialize(os, type.value.imprt.modId);
|
||||||
detail::serialize(os, type.value.imprt.typeId);
|
detail::serialize(os, type.value.imprt.typeId);
|
||||||
@@ -104,6 +120,22 @@ mod mod::load(std::istream& is) {
|
|||||||
|
|
||||||
mod mod;
|
mod mod;
|
||||||
|
|
||||||
|
constant_index constantCount = 0;
|
||||||
|
detail::load(is, constantCount);
|
||||||
|
mod.m_constants.reserve(constantCount);
|
||||||
|
for (; constantCount != 0; --constantCount) {
|
||||||
|
constant constant{};
|
||||||
|
detail::load(is, reinterpret_cast<std::uint32_t&>(constant.type));
|
||||||
|
switch (constant.type) {
|
||||||
|
case constant::S32:
|
||||||
|
case constant::U32: detail::load(is, constant.u32); break;
|
||||||
|
case constant::S64:
|
||||||
|
case constant::U64: detail::load(is, constant.u64); break;
|
||||||
|
case constant::String: throw std::runtime_error("unimplemented");
|
||||||
|
default: throw std::runtime_error("unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod_type_id typeCount = 0;
|
mod_type_id typeCount = 0;
|
||||||
detail::load(is, typeCount);
|
detail::load(is, typeCount);
|
||||||
for (mod_type_id id = 0; id < typeCount; ++id) {
|
for (mod_type_id id = 0; id < typeCount; ++id) {
|
||||||
@@ -140,6 +172,11 @@ mod mod::load(std::istream& is) {
|
|||||||
detail::load(is, size);
|
detail::load(is, size);
|
||||||
mod.emplace_type(id, typeId, size).dispatch();
|
mod.emplace_type(id, typeId, size).dispatch();
|
||||||
} break;
|
} break;
|
||||||
|
case mod_type::Slice: {
|
||||||
|
mod_type_id typeId = 0;
|
||||||
|
detail::load(is, typeId);
|
||||||
|
mod.emplace_type(id, mod_type::Slice, typeId).dispatch();
|
||||||
|
} break;
|
||||||
case mod_type::Import: {
|
case mod_type::Import: {
|
||||||
std::string modName;
|
std::string modName;
|
||||||
detail::load(is, modName);
|
detail::load(is, modName);
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// NOLINTBEGIN(readability-identifier-naming)
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include <furc/middle/ir.hpp>
|
||||||
|
#include <furc/middle/ssa.hpp>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace furc::test {
|
||||||
|
|
||||||
|
class SsaCfg : public testing::Test {
|
||||||
|
public:
|
||||||
|
SsaCfg()
|
||||||
|
: p_context(&p_function) {}
|
||||||
|
protected:
|
||||||
|
void compute() { ssa::compute_cfg(p_function.blocks, p_cfgBlocks); }
|
||||||
|
protected:
|
||||||
|
std::vector<ssa::cfg_block> p_cfgBlocks;
|
||||||
|
|
||||||
|
ir_function p_function;
|
||||||
|
ir_context p_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SsaCfg, Diamond) {
|
||||||
|
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
|
||||||
|
|
||||||
|
// Entry
|
||||||
|
p_context.terminate(null, 1, 2);
|
||||||
|
p_context.new_next();
|
||||||
|
// A
|
||||||
|
p_context.terminate(3);
|
||||||
|
p_context.new_next();
|
||||||
|
// B
|
||||||
|
p_context.terminate(3);
|
||||||
|
p_context.new_next();
|
||||||
|
// C
|
||||||
|
p_context.terminate(4);
|
||||||
|
p_context.new_next();
|
||||||
|
// Exit
|
||||||
|
p_context.terminate();
|
||||||
|
|
||||||
|
compute();
|
||||||
|
|
||||||
|
EXPECT_EQ(p_cfgBlocks[0].sucs.size(), 2);
|
||||||
|
EXPECT_NE(p_cfgBlocks[0].sucs.find(1), p_cfgBlocks[0].sucs.end());
|
||||||
|
EXPECT_NE(p_cfgBlocks[0].sucs.find(2), p_cfgBlocks[0].sucs.end());
|
||||||
|
EXPECT_EQ(p_cfgBlocks[3].preds, p_cfgBlocks[0].sucs);
|
||||||
|
EXPECT_EQ(p_cfgBlocks[3].sucs.size(), 1);
|
||||||
|
EXPECT_NE(p_cfgBlocks[3].sucs.find(4), p_cfgBlocks[3].sucs.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
class SsaDom : public testing::Test {
|
||||||
|
public:
|
||||||
|
SsaDom()
|
||||||
|
: p_context(&p_function) {}
|
||||||
|
protected:
|
||||||
|
void compute() {
|
||||||
|
ssa::compute_cfg(p_function.blocks, p_cfgBlocks);
|
||||||
|
p_ssaRegisters.resize(p_function.regCount);
|
||||||
|
ssa::collect_registers(p_function.blocks, p_ssaRegisters, p_ssaGlobals);
|
||||||
|
|
||||||
|
std::vector<std::uint64_t> order;
|
||||||
|
ssa::compute_rpo(p_cfgBlocks, p_ssaBlocks, order);
|
||||||
|
|
||||||
|
ssa::build_dtree(p_cfgBlocks, p_ssaBlocks, order);
|
||||||
|
ssa::compute_dfrontiers(p_cfgBlocks, p_ssaBlocks);
|
||||||
|
|
||||||
|
ssa::ssaification(p_function.blocks, p_cfgBlocks, p_ssaBlocks, p_ssaRegisters, p_ssaGlobals);
|
||||||
|
ssa::rename(p_function.blocks, p_function.regCount, p_cfgBlocks, p_ssaBlocks, order);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
std::vector<ssa::cfg_block> p_cfgBlocks;
|
||||||
|
std::vector<ssa::ssa_block> p_ssaBlocks;
|
||||||
|
std::vector<ssa::register_info> p_ssaRegisters;
|
||||||
|
std::unordered_set<std::uint64_t> p_ssaGlobals;
|
||||||
|
|
||||||
|
ir_function p_function;
|
||||||
|
ir_context p_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(SsaDom, Diamond) {
|
||||||
|
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
|
||||||
|
|
||||||
|
// Entry
|
||||||
|
p_context.terminate(null, 1, 2);
|
||||||
|
p_context.new_next();
|
||||||
|
// A
|
||||||
|
p_context.add_instr(ir_instruction{ ir_instruction::Move,
|
||||||
|
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||||
|
{ null } });
|
||||||
|
p_context.terminate(3);
|
||||||
|
p_context.new_next();
|
||||||
|
// B
|
||||||
|
p_context.add_instr(ir_instruction{ ir_instruction::Move,
|
||||||
|
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||||
|
{ null } });
|
||||||
|
p_context.terminate(3);
|
||||||
|
p_context.new_next();
|
||||||
|
// C
|
||||||
|
p_context.add_instr(ir_instruction{ ir_instruction::Add,
|
||||||
|
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||||
|
{ ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) } } });
|
||||||
|
p_context.new_next();
|
||||||
|
// Exit
|
||||||
|
p_context.terminate();
|
||||||
|
|
||||||
|
p_function.regCount = 1;
|
||||||
|
|
||||||
|
compute();
|
||||||
|
|
||||||
|
EXPECT_TRUE(p_ssaBlocks[0].df.empty());
|
||||||
|
|
||||||
|
EXPECT_EQ(p_ssaBlocks[1].idom, 0);
|
||||||
|
EXPECT_EQ(p_ssaBlocks[1].df.size(), 1);
|
||||||
|
EXPECT_NE(p_ssaBlocks[1].df.find(3), p_ssaBlocks[1].df.end());
|
||||||
|
|
||||||
|
EXPECT_EQ(p_ssaBlocks[2].idom, 0);
|
||||||
|
EXPECT_EQ(p_ssaBlocks[2].df.size(), 1);
|
||||||
|
EXPECT_NE(p_ssaBlocks[2].df.find(3), p_ssaBlocks[2].df.end());
|
||||||
|
|
||||||
|
EXPECT_EQ(p_ssaBlocks[3].idom, 0);
|
||||||
|
EXPECT_TRUE(p_ssaBlocks[3].df.empty());
|
||||||
|
|
||||||
|
EXPECT_EQ(p_ssaBlocks[4].idom, 3);
|
||||||
|
EXPECT_TRUE(p_ssaBlocks[4].df.empty());
|
||||||
|
|
||||||
|
// Renaming
|
||||||
|
EXPECT_EQ(p_function.blocks[1].instructions.front(),
|
||||||
|
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 3), { null } }));
|
||||||
|
EXPECT_EQ(p_function.blocks[2].instructions.front(),
|
||||||
|
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 2), { null } }));
|
||||||
|
EXPECT_EQ(p_function.blocks[3].instructions.front(),
|
||||||
|
(ir_instruction{ ir_instruction::Phi,
|
||||||
|
ir_operand::reg(0, 0),
|
||||||
|
{ ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 2 }, 2 },
|
||||||
|
ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 3 }, 1 } } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace furc::test
|
||||||
|
|
||||||
|
// NOLINTEND(readability-identifier-naming)
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#include "furvm/exceptions.hpp"
|
||||||
|
#include "furvm/furvm.hpp"
|
||||||
|
#include "furvm/thing.hpp"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h" // IWYU pragma: keep
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// TODO: Basic program tests (e.g. for loops)
|
||||||
|
|
||||||
|
TEST(ThingOps, Add) {
|
||||||
|
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
||||||
|
lhs.get<furvm::u32>() = 6;
|
||||||
|
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
||||||
|
rhs.get<furvm::u32>() = 7;
|
||||||
|
|
||||||
|
auto res = lhs.add(rhs);
|
||||||
|
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
|
||||||
|
EXPECT_EQ(lhs.get<furvm::u32>(), 6);
|
||||||
|
EXPECT_EQ(rhs.get<furvm::u32>(), 7);
|
||||||
|
EXPECT_EQ(res.get<furvm::u32>(), 6 + 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThingOps, Array) {
|
||||||
|
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||||
|
|
||||||
|
static constexpr std::size_t LENGTH = 10;
|
||||||
|
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||||
|
|
||||||
|
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||||
|
EXPECT_EQ(array.length(), LENGTH);
|
||||||
|
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||||
|
auto el = array.at(i);
|
||||||
|
el.get<furvm::u32>() = values[i];
|
||||||
|
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThingOps, Slice) {
|
||||||
|
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||||
|
|
||||||
|
static constexpr std::size_t LENGTH = 10;
|
||||||
|
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||||
|
|
||||||
|
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||||
|
EXPECT_EQ(array.length(), LENGTH);
|
||||||
|
furvm::thing slice = array.slice(0, LENGTH);
|
||||||
|
EXPECT_EQ(slice.length(), LENGTH);
|
||||||
|
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||||
|
auto el = slice.at(i);
|
||||||
|
el.get<furvm::u32>() = values[i];
|
||||||
|
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||||
|
EXPECT_EQ(slice.at(i).integer(), array.at(i).integer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThingOps, String) {
|
||||||
|
static constexpr std::string_view STRING = "femboje na topie";
|
||||||
|
static constexpr auto LENGTH = STRING.length();
|
||||||
|
|
||||||
|
furvm::thing string{ furvm::thing_type{ furvm::thing_type::String } };
|
||||||
|
string.assign(STRING);
|
||||||
|
EXPECT_EQ(string.length(), LENGTH);
|
||||||
|
furvm::thing slice = string.slice(0, LENGTH);
|
||||||
|
EXPECT_EQ(slice.length(), LENGTH);
|
||||||
|
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||||
|
auto el = slice.at(i);
|
||||||
|
EXPECT_EQ(slice.at(i).get<furvm::u8>(), STRING[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThingOps, Iterators) {
|
||||||
|
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||||
|
|
||||||
|
static constexpr std::size_t LENGTH = 10;
|
||||||
|
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||||
|
|
||||||
|
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||||
|
EXPECT_EQ(array.length(), LENGTH);
|
||||||
|
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||||
|
auto el = array.at(i);
|
||||||
|
el.get<furvm::u32>() = values[i];
|
||||||
|
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t itCount = 0;
|
||||||
|
for (auto it = array.begin(); it != array.end(); ++it, ++itCount) {
|
||||||
|
auto idx = it - array.begin();
|
||||||
|
ASSERT_EQ(idx, itCount);
|
||||||
|
ASSERT_LT(idx, LENGTH);
|
||||||
|
EXPECT_EQ(it->integer(), values[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThingOps, Access) {
|
||||||
|
furvm::thing thing{ furvm::thing_type{ furvm::thing_type::U8 } };
|
||||||
|
EXPECT_NO_THROW(thing.get<furvm::u8>());
|
||||||
|
EXPECT_THROW(thing.get<furvm::s8>(), furvm::bad_thing_access);
|
||||||
|
EXPECT_THROW(thing.get<furvm::s32>(), furvm::bad_thing_access);
|
||||||
|
EXPECT_THROW(thing.get<void*>(), furvm::bad_thing_access);
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(thing.assign<furvm::u8>(67));
|
||||||
|
EXPECT_THROW(thing.assign<furvm::s32>(1337), furvm::bad_thing_access);
|
||||||
|
EXPECT_EQ(thing.get<furvm::u8>(), 67);
|
||||||
|
EXPECT_NO_THROW(thing.set<furvm::u8>(67)); // He talkin' 'bout sum 6-7 while I want sixty ni-
|
||||||
|
EXPECT_THROW(thing.set<furvm::s32>(1337), furvm::bad_thing_access);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user