Compare commits

..

49 Commits

Author SHA1 Message Date
CHatingPython 3b98f77c08 chore: flat out the file structure 2026-09-11 18:48:16 +02:00
CHatingPython 959d0a7773 feat(furc): implement SSA destruction 2026-09-11 18:12:54 +02:00
CHatingPython 70375af068 test(furc/ssa): implement tests for SSA 2026-09-10 21:49:25 +02:00
CHatingPython 46d0de759a feat(furc/ssa): implement SSA stage 2026-09-10 21:48:45 +02:00
CHatingPython 01964a495c feat(furc/ir): add equality comparison to ir classes 2026-09-10 21:47:47 +02:00
CHatingPython 21fb587e36 fix(furc/ir): use separate counter for variables 2026-09-09 14:11:45 +02:00
CHatingPython fe65a09af9 feat(furlang): add serialization 2026-09-06 15:51:51 +02:00
CHatingPython 712a0d7047 chore: change clang-tidy 2026-09-06 15:09:50 +02:00
CHatingPython 35d7fc4970 refactor(furlang): improve results 2026-09-06 15:09:34 +02:00
CHatingPython 9bf4da4fd9 feat(furvm): add string type and improve assigning
Closes: #61
2026-09-06 01:21:14 +02:00
CHatingPython 6c7d16631d feat(furvm): allow setting thing's value 2026-09-04 21:28:28 +02:00
CHatingPython 24a9530abc refactor(furvm): improve thing value access 2026-09-04 21:28:18 +02:00
CHatingPython 0354ec1e79 refactor(furvm): move thing type to types.hpp 2026-09-04 20:59:14 +02:00
CHatingPython ddb60ca93a fix(furdb): add slices to furdb 2026-09-04 18:17:55 +02:00
CHatingPython 3aaed55485 feat(furvm): introduce thing iterators
Refs: #38
2026-09-04 18:17:45 +02:00
CHatingPython cfefca49ee test(furvm): add array and slice tests
Refs: #38
2026-09-04 00:16:58 +02:00
CHatingPython 21e963168e feat(furas): add slice type and instruction 2026-09-03 23:56:19 +02:00
CHatingPython b067bbb4ea feat(furvm): introduce slice instruction 2026-09-03 23:55:57 +02:00
CHatingPython fe37bf8058 feat(furvm): introduce slice type
Refs: #63
2026-09-03 23:55:31 +02:00
CHatingPython 977b22d8f5 fix(furvm): add stack.hpp
I forgot to add it earlier
2026-09-03 22:42:59 +02:00
CHatingPython 30a06186db feat(furvm): introduce stack storage
Closes: #62
2026-09-01 22:33:18 +02:00
CHatingPython 5e425d7fe4 feat(furvm): implement constants
Closes: #13
2026-09-01 17:18:01 +02:00
CHatingPython 12a131c2b8 feat(furc/SSA): insert phi nodes 2026-08-18 17:50:34 +02:00
CHatingPython 5403c0e1a4 feat(furc): actually use SSA 2026-08-18 17:49:43 +02:00
CHatingPython a1dac8ae3f chore(clangd): remove optional access checking 2026-08-18 17:49:20 +02:00
CHatingPython a311795915 fix(furc/IR): fix terminate function 2026-08-18 17:48:51 +02:00
CHatingPython 090d8ad81e feat(furc/SSA): compute dominance frontiers 2026-08-18 13:55:39 +02:00
CHatingPython 5efcf1b9b0 feat(furc/SSA): compute dominance tree 2026-08-18 00:16:32 +02:00
CHatingPython 136663aeab feat(furc/IR): add global variables 2026-08-18 00:15:43 +02:00
CHatingPython 56ea4ba2ab feat(furdb): add stack info 2026-08-17 21:14:10 +02:00
CHatingPython dab7f73216 feat(furvm): expose the stack 2026-08-17 21:13:02 +02:00
CHatingPython 1d4e8bb1d0 feat(furdb): add isocline for better command line 2026-08-17 12:14:30 +02:00
CHatingPython ed84a65757 fix(furdb): improve printing 2026-08-17 11:39:35 +02:00
CHatingPython 26efd110f1 feat(furdb): add continue and next commands 2026-08-17 11:35:49 +02:00
CHatingPython dd96f95d77 fix(furvm): add JustHit executor flag 2026-08-17 11:34:37 +02:00
CHatingPython 70cbb003f2 feat(furdb): introduce info command 2026-08-17 11:21:22 +02:00
CHatingPython 665982049d fix(furdb): improve program execution 2026-08-17 11:20:44 +02:00
CHatingPython 195d154474 fix(furvm): improve executor flags 2026-08-17 11:20:14 +02:00
CHatingPython 26d935e22a feat(furdb): add function for printing instruction 2026-08-17 10:59:00 +02:00
CHatingPython a4966a057c feat(furdb): add break command 2026-08-16 20:01:52 +02:00
CHatingPython b3ce4fa0ed feat(furvm): add breakpoints 2026-08-16 19:59:46 +02:00
CHatingPython 29dd9abb3f fix(furvm): more fixing 2026-08-15 23:13:45 +02:00
CHatingPython ffe1208775 refactor(furvm): cleanup some bugs
Or we could just pretend that there were no bugs clueless
2026-08-15 22:41:32 +02:00
CHatingPython a382b92cf0 refactor(furvm): improve things
Refs: #62
2026-08-15 22:15:35 +02:00
CHatingPython c59f0252ed refactor(furvm): remove thing arena from context 2026-08-15 12:14:24 +02:00
CHatingPython 5c841a1428 feat(furvm): add header to thing data
Refs: #62
2026-08-15 12:10:38 +02:00
CHatingPython 6d176e58f3 refactor(furvm): remove thing allocator
Refs: #62
2026-08-15 12:00:18 +02:00
CHatingPython 6737ed86b0 fix(furvm, furas): fix function signature matching 2026-08-15 00:32:57 +02:00
CHatingPython 4ede6f3476 feat(disfuras): add global variables 2026-08-15 00:32:02 +02:00
87 changed files with 3623 additions and 1320 deletions
+3 -1
View File
@@ -17,6 +17,7 @@ Checks: >
-readability-redundant-access-specifiers,
-readability-use-anyofallof,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-performance-enum-size,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
@@ -33,7 +34,8 @@ Checks: >
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-use-enum-class,
-bugprone-forward-declaration-namespace,
-bugprone-tagged-union-member-count
-bugprone-tagged-union-member-count,
-bugprone-unchecked-optional-access
WarningsAsErrors: "*"
+6 -5
View File
@@ -4,16 +4,16 @@ set -e
BUILD_DIR="build"
echo "Running clang-format..."
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp)$' || true)
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp)$' | grep -v 'deps/' || true)
if [ -z "$FILES" ]; then
echo "No C/C++ files to check"
exit 0
fi
git diff -U0 --cached | \
echo "Running clang-format..."
git diff -U0 --cached -- $FILES | \
python3 <(curl -s https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/main/clang/tools/clang-format/clang-format-diff.py) -p1 -i
echo "Running clang-tidy..."
@@ -21,7 +21,8 @@ echo "Running clang-tidy..."
for file in $FILES; do
clang-tidy \
"$file" \
--header-filter="^(?!.*deps/).*" \
-p "$BUILD_DIR"
done
echo "pre-commit checks passed"
echo "pre-commit checks passed"
+3
View File
@@ -0,0 +1,3 @@
[submodule "deps/isocline"]
path = deps/isocline
url = https://github.com/daanx/isocline
+10 -6
View File
@@ -11,12 +11,16 @@ find_package(Doxygen)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(furlang)
add_subdirectory(furvm)
add_subdirectory(furc)
add_subdirectory(furas)
add_subdirectory(disfuras)
add_subdirectory(furdb)
add_subdirectory(src/furlang)
add_subdirectory(src/furvm)
add_subdirectory(src/furc)
add_subdirectory(src/furas)
add_subdirectory(src/disfuras)
add_subdirectory(src/furdb)
# Dependencies
set(IC_USE_CXX ON)
add_subdirectory(${CMAKE_SOURCE_DIR}/deps/isocline)
if(DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
+1
View File
@@ -0,0 +1 @@
Checks: '-*'
Vendored Submodule
+1
Submodule deps/isocline added at 8d6dc1ef95
-5
View File
@@ -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 arrSlice = slice $s8
func println $arr = native println
func println $arrSlice = native println
public func main = #main
main:
@@ -30,5 +31,8 @@ body:
end:
drop
load %0
call $arr println
push $u32 0
load %1
slice
call $arrSlice println
ret
-5
View File
@@ -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)
-16
View File
@@ -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)
-5
View File
@@ -1,5 +0,0 @@
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)
-55
View File
@@ -1,55 +0,0 @@
#include "command.hpp"
#include <cctype>
#include <cstddef>
#include <iostream>
#include <random>
#include <utility>
command_info command::parse(std::string_view line) {
command_info info;
while (!line.empty() && std::isspace(line.front()) != 0)
line = line.substr(1);
std::size_t length = 0;
while (line.size() > length && std::isalnum(line.at(length)) != 0)
++length;
info.commandName = line.substr(0, length);
line = line.substr(length);
while (true) {
while (!line.empty() && std::isspace(line.front()) != 0)
line = line.substr(1);
if (line.empty()) break;
length = 0;
while (line.size() > length && std::isalnum(line.at(length)) != 0)
++length;
info.args.push_back(line.substr(0, length));
line = line.substr(length);
}
return std::move(info);
}
void quit_command::execute(context& ctx, const command_info& info) {
static std::random_device s_rd;
static std::mt19937 s_gen(s_rd());
static std::uniform_int_distribution<> s_dis(1, 1000);
int roll = s_dis(s_gen);
std::cout << "Farewell";
if (roll == 1) {
std::cout << ", stasiu pensive";
} else if (roll <= 100) {
std::cout << ", kromek-man";
}
std::cout << "!\n";
ctx.running = false;
}
void run_command::execute(context& ctx, const command_info& info) {
ctx.run();
std::cout << "Execution finished\n";
}
-9
View File
@@ -1,9 +0,0 @@
#include "context.hpp"
void context::run() {
if ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done)
executor->push_frame(mod, *mainFunction);
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
executor->step();
}
}
-4
View File
@@ -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/)
-76
View File
@@ -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
-769
View File
@@ -1,769 +0,0 @@
#ifndef FURVM_THING_HPP
#define FURVM_THING_HPP
#include "furlang/arena.hpp"
#include "furlang/utility/hash.hpp"
#include "furvm/exceptions.hpp"
#include "furvm/fwd.hpp"
#include "furvm/thing_allocator.hpp" // IWYU pragma: keep
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <functional>
#include <limits>
#include <new>
#include <stdexcept>
#include <type_traits>
#include <unordered_map>
#include <utility>
namespace furvm {
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 {
struct thing_type_hash {
std::size_t operator()(const thing_type& type) const {
std::size_t seed = std::hash<decltype(type.type)>{}(type.type);
switch (type.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: return seed;
case thing_type::Ptr:
case thing_type::Ref: return furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.typeRef));
case thing_type::Array:
seed = furlang::utility::hash_combine(seed, thing_type_hash{}(*type.value.array.type));
seed = furlang::utility::hash_combine(seed,
std::hash<decltype(type.value.array.size)>{}(type.value.array.size));
return seed;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
};
} // namespace detail
class thing_type_store {
public:
thing_type* insert(thing_type& type) {
if (auto it = m_typeMap.find(type); it != m_typeMap.end()) return m_map[type.id = it->second];
if (type.id == thing_type::INVALID_ID) type.id = m_counter++;
thing_type* ptr = m_arena.allocate<thing_type>(type);
m_map[type.id] = ptr;
m_typeMap[type] = type.id;
return ptr;
}
thing_type* insert(const thing_type& type) {
if (auto it = m_typeMap.find(type); it != m_typeMap.end()) return m_map[it->second];
thing_type_id id = m_counter++;
thing_type* ptr = m_arena.allocate<thing_type>(type);
m_map[id] = ptr;
m_typeMap[type] = id;
return ptr;
}
thing_type* at(thing_type_id id) const {
if (auto it = m_map.find(id); it != m_map.end()) return it->second;
return nullptr;
}
private:
furlang::arena m_arena;
std::unordered_map<thing_type_id, thing_type*> m_map;
std::unordered_map<thing_type, thing_type_id, detail::thing_type_hash> m_typeMap;
thing_type_id m_counter = 0;
};
template <template <typename> typename Allocator>
class thing final {
friend class executor;
public:
using allocator_type = Allocator<std::byte>; /**< Allocator type. */
public:
struct dynamic_array {
std::size_t size;
std::byte* data;
};
public:
thing() {}
/**
* @brief Constructs a thing.
*
* @param type Thing type.
* @param allocator Allocator for the thing's data.
*/
thing(const thing_type& type, const allocator_type& allocator = {})
: m_type(type), m_size(compute_size(type)), m_allocator(allocator) {
if (m_type.type == thing_type::Ref) return;
// TODO: Account for alignment
m_data = m_allocator.allocate(m_size);
std::memset(m_data, 0, m_size);
}
/**
* @brief Destructs a thing.
*/
~thing() {
if (m_type.type != thing_type::Ref && m_data != nullptr && m_size > 0) m_allocator.deallocate(m_data, m_size);
}
/**
* @brief Move constructor.
*/
thing(thing&& other) noexcept
: m_type(other.m_type), m_data(other.m_data), m_size(other.m_size), m_allocator(std::move(other.m_allocator)) {
other.m_type.type = thing_type::Count;
other.m_data = nullptr;
other.m_size = 0;
}
/**
* @brief Move constructor.
*/
thing& operator=(thing&& other) noexcept {
if (this == &other) return *this;
m_type = other.m_type;
m_size = other.m_size;
m_data = other.m_data;
m_allocator = std::move(other.m_allocator);
other.m_type.type = thing_type::Count;
other.m_data = nullptr;
other.m_size = 0;
return *this;
}
thing(const thing& other)
: m_type(other.m_type), m_size(other.m_size), m_allocator(other.m_allocator) {
if (m_type.type == thing_type::Ref) {
m_data = other.m_data;
return;
}
m_data = m_allocator.allocate(m_size);
other.copy(*this);
}
thing& operator=(const thing& other) {
if (this == &other) return *this;
m_type = other.m_type;
m_size = other.m_size;
m_allocator = std::move(other.m_allocator);
if (m_type.type == thing_type::Ref) {
m_data = other.m_data;
return *this;
}
m_data = m_allocator.allocate(m_size);
other.copy(*this);
return *this;
}
public:
/**
* @brief Returns a clone of the thing.
*
* @return A clone of this thing.
*/
thing clone() const {
thing res(m_type, m_allocator);
copy(res);
}
private:
void copy(thing<>& dst) const {
switch (m_type.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
case thing_type::Ptr: std::memcpy(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::Ref: throw std::runtime_error("cannot copy references");
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
public:
/**
* @brief Returns the thing's type.
*
* @return The type.
*/
constexpr thing_type type() const { return m_type; }
/**
* @brief Returns the thing's true type.
*
* If the thing is a reference, returns the referenced type.
*
* @return The true type.
*/
constexpr thing_type true_type() const { return (m_type.type == thing_type::Ref) ? *m_type.value.typeRef : m_type; }
/**
* @brief Checks if the thing is of a specified type.
*
* Compares the true type.
*
* @param type Type to compare.
* @return true if the types match.
*/
constexpr bool is(enum thing_type::type type) const { return true_type().type == type; }
public:
/**
* @brief Returns a raw data pointer.
*
* @return The data pointer.
*/
void* raw() { return m_data; }
/**
* @brief Returns a raw data pointer.
*
* @return The data pointer.
*/
const void* raw() const { return m_data; }
public:
/**
* @brief Returns the thing's value.
*
* @return The value.
*/
template <typename T>
T& get() {
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
return *std::launder(reinterpret_cast<T*>(m_data));
}
/**
* @brief Returns the thing's value.
*
* @return The value.
*/
template <typename T>
const T& get() const {
if (compute_size_na(m_type) != sizeof(T)) throw bad_thing_access();
return *std::launder(reinterpret_cast<const T*>(m_data));
}
public:
/**
* @brief Returns a sum of two things.
*
* @param rhs Thing to sum with this thing (right-hand-side).
* @return The sum.
*/
thing add(const thing& rhs) const { return binary_op(rhs, std::plus<>{}); }
/**
* @brief Returns a difference of two things.
*
* @param rhs Thing to subtract from this thing (right-hand-side).
* @return The sum.
*/
thing sub(const thing& rhs) const { return binary_op(rhs, std::minus<>{}); }
/**
* @brief Returns a product of two things.
*
* @param rhs Thing to multiply with this thing (right-hand-side).
* @return The product.
*/
thing mul(const thing& rhs) const { return binary_op(rhs, std::multiplies<>{}); }
/**
* @brief Returns a quotient of two things.
*
* @param rhs Thing to divide this thing by (right-hand-side).
* @return The quotient.
*/
thing div(const thing& rhs) const { return binary_op(rhs, std::divides<>{}); }
/**
* @brief Returns a remainder of two things.
*
* @param rhs Thing to divide this thing by (right-hand-side).
* @return The remainder.
*/
thing mod(const thing& rhs) const { return binary_op(rhs, std::modulus<>{}); }
/**
* @brief Compares two things for equality.
*
* @param rhs Thing to compare this thing with (right-hand-side).
* @return A boolean result of the comparison in a thing form.
*/
thing equals(const thing& rhs) const { return binary_op(rhs, std::equal_to<>{}); }
/**
* @brief Compares two things for inequality.
*
* @param rhs Thing to compare this thing with (right-hand-side).
* @return A boolean result of the comparison in a thing form.
*/
thing not_equals(const thing& rhs) const { return binary_op(rhs, std::not_equal_to<>{}); }
/**
* @brief Compares if this thing is less than an another thing.
*
* @param rhs The another thing.
* @return A boolean result of the comparison in a thing form.
*/
thing less_than(const thing& rhs) const { return binary_op(rhs, std::less<>{}); }
/**
* @brief Compares if this thing is greater than an another thing.
*
* @param rhs The another thing.
* @return A boolean result of the comparison in a thing form.
*/
thing greater_than(const thing& rhs) const { return binary_op(rhs, std::greater<>{}); }
/**
* @brief Compares if this thing is less than or equal to an another thing.
*
* @param rhs The another thing.
* @return A boolean result of the comparison in a thing form.
*/
thing less_equals(const thing& rhs) const { return binary_op(rhs, std::less_equal<>{}); }
/**
* @brief Compares if this thing is greater than or equal to an another thing.
*
* @param rhs The another thing.
* @return A boolean result of the comparison in a thing form.
*/
thing greater_equals(const thing& rhs) const { return binary_op(rhs, std::greater_equal<>{}); }
public:
/**
* @brief Returns a largest integer value of the thing.
*
* @return The integer value.
*/
thing_type::s64 integer() const {
switch (true_type().type) {
case thing_type::S8: return get<thing_type::s8>();
case thing_type::S16: return get<thing_type::s16>();
case thing_type::S32: return get<thing_type::s32>();
case thing_type::S64: return get<thing_type::s64>();
case thing_type::U8: return get<thing_type::u8>();
case thing_type::U16: return get<thing_type::u16>();
case thing_type::U32: return get<thing_type::u32>();
case thing_type::U64: return get<thing_type::u64>();
default: throw std::runtime_error("unreachable");
}
}
void resize(thing_type::u64 newSize) {
if (!is(thing_type::Array)) throw bad_thing_access();
if (true_type().value.array.size > 0) throw std::runtime_error("cannot resize a static array");
auto& array = get<dynamic_array>();
if (newSize < 0 || newSize == array.size) return;
std::size_t innerSize = compute_size_na(*true_type().value.array.type);
std::byte* newData = new std::byte[innerSize * newSize];
std::memcpy(newData, array.data, innerSize * std::min(static_cast<thing_type::u64>(array.size), newSize));
array.size = newSize;
delete[] array.data;
array.data = newData;
}
thing at(thing_type::u64 index) const {
if (!is(thing_type::Array)) throw bad_thing_access();
std::size_t elementSize = compute_size_na(*true_type().value.array.type);
if (true_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");
thing ref = { { thing_type::Ref, true_type().value.array.type }, m_allocator };
ref.m_data = array.data + (index * elementSize);
return ref;
}
if (index < 0 || index >= true_type().value.array.size) throw std::out_of_range("index out of range");
thing ref = { { thing_type::Ref, true_type().value.array.type }, m_allocator };
ref.m_data = m_data + (index * elementSize);
return ref;
}
thing_type::u64 length() const {
if (!is(thing_type::Array)) throw bad_thing_access();
return true_type().value.array.size == 0 ? get<dynamic_array>().size : true_type().value.array.size;
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
T cast_to() const {
return visit_primitive([](auto value) { return static_cast<T>(value); });
}
/**
* @brief Changes reference thing's referenced thing.
*
* Yes.
*
* @param thing Thing.
*/
void reference(const thing& thing) {
if (m_type.type != thing_type::Ref || *m_type.value.typeRef != thing.type()) throw bad_thing_access();
m_data = thing.m_data;
}
/**
* @brief Self-explainatory.
*
* TODO: Document
*/
void assign(thing&& thing) {
class thing rhs = std::move(thing);
if (true_type() != rhs.true_type()) throw std::runtime_error("thing type mismatch");
// TODO: Move this to another function
switch (true_type().type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: std::memcpy(m_data, rhs.m_data, m_size); return;
case thing_type::Ptr:
case thing_type::Ref:
case thing_type::Array: throw std::runtime_error("unimplemented");
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
private:
static void copy_list(const thing_type& arrayType, void* dst, const void* src) {
if (arrayType.type != thing_type::Array || arrayType.value.array.type == nullptr)
throw std::runtime_error("invalid type");
const auto& innerType = *arrayType.value.array.type;
std::size_t elementSize = compute_size_na(innerType);
std::size_t size = 0;
if (arrayType.value.array.size == 0) {
const dynamic_array& srcDynArr = *std::launder(reinterpret_cast<const dynamic_array*>(src));
dynamic_array& dstDynArr = *std::launder(reinterpret_cast<dynamic_array*>(dst));
size = dstDynArr.size = srcDynArr.size;
if (dstDynArr.size < 0) {
dstDynArr.data = nullptr;
return;
}
src = srcDynArr.data;
dst = dstDynArr.data = new std::byte[dstDynArr.size];
} else {
size = arrayType.value.array.size;
}
switch (innerType.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
case thing_type::Ptr:
case thing_type::Ref: std::memcpy(dst, src, size * elementSize); return;
case thing_type::Array:
for (std::size_t i = 0; i < size; ++i) {
copy_list(*innerType.value.array.type,
reinterpret_cast<std::byte*>(dst) + (i * elementSize),
reinterpret_cast<const std::byte*>(src) + (i * elementSize));
}
return;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
private:
static std::size_t compute_size_na(const thing_type& type) {
switch (type.type) {
case thing_type::S8: return sizeof(thing_type::s8);
case thing_type::S16: return sizeof(thing_type::s16);
case thing_type::S32: return sizeof(thing_type::s32);
case thing_type::S64: return sizeof(thing_type::s64);
case thing_type::U8: return sizeof(thing_type::u8);
case thing_type::U16: return sizeof(thing_type::u16);
case thing_type::U32: return sizeof(thing_type::u32);
case thing_type::U64: return sizeof(thing_type::u64);
case thing_type::Ptr: return sizeof(void*);
case thing_type::Ref: return compute_size_na(*type.value.typeRef);
case thing_type::Array:
return type.value.array.size == 0 ? sizeof(dynamic_array)
: compute_size_na(*type.value.array.type) * type.value.array.size;
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
// NOTE: Align to 4 bytes
static std::size_t compute_size(const thing_type& type) { return (compute_size_na(type) + 3) & ~3; }
private:
template <typename Func>
decltype(auto) visit_primitive(Func&& func) const {
switch (true_type().type) {
case thing_type::S8: return std::forward<Func>(func)(get<thing_type::s8>());
case thing_type::S16: return std::forward<Func>(func)(get<thing_type::s16>());
case thing_type::S32: return std::forward<Func>(func)(get<thing_type::s32>());
case thing_type::S64: return std::forward<Func>(func)(get<thing_type::s64>());
case thing_type::U8: return std::forward<Func>(func)(get<thing_type::u8>());
case thing_type::U16: return std::forward<Func>(func)(get<thing_type::u16>());
case thing_type::U32: return std::forward<Func>(func)(get<thing_type::u32>());
case thing_type::U64: return std::forward<Func>(func)(get<thing_type::u64>());
default: throw bad_thing_access();
}
}
template <typename Op>
thing binary_op(const thing& rhs, const Op& op) const {
if (thing_type::is_primitive(true_type().type) && thing_type::is_primitive(true_type().type)) {
static constexpr enum thing_type::type promotions[8 * 8] = {
// S8
thing_type::S8,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::U64,
// S16
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::U64,
// S32
thing_type::S32,
thing_type::S32,
thing_type::S32,
thing_type::S64,
thing_type::S32,
thing_type::S32,
thing_type::U32,
thing_type::U64,
// S64
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::S64,
thing_type::U64,
// U8
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::U8,
thing_type::U16,
thing_type::U32,
thing_type::U64,
// U16
thing_type::S16,
thing_type::S16,
thing_type::S32,
thing_type::S64,
thing_type::U16,
thing_type::U16,
thing_type::U32,
thing_type::U64,
// U32
thing_type::S32,
thing_type::S32,
thing_type::U32,
thing_type::S64,
thing_type::U32,
thing_type::U32,
thing_type::U32,
thing_type::U64,
// U64
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
thing_type::U64,
};
enum thing_type::type resultType = promotions[true_type().type + (rhs.true_type().type * 8)];
thing res = { thing_type{ resultType }, m_allocator };
switch (resultType) {
case thing_type::S8:
res.get<thing_type::s8>() = Op{}(cast_to<thing_type::s8>(), rhs.cast_to<thing_type::s8>());
return res;
case thing_type::S16:
res.get<thing_type::s16>() = Op{}(cast_to<thing_type::s16>(), rhs.cast_to<thing_type::s16>());
return res;
case thing_type::S32:
res.get<thing_type::s32>() = Op{}(cast_to<thing_type::s32>(), rhs.cast_to<thing_type::s32>());
return res;
case thing_type::S64:
res.get<thing_type::s64>() = Op{}(cast_to<thing_type::s64>(), rhs.cast_to<thing_type::s64>());
return res;
case thing_type::U8:
res.get<thing_type::u8>() = Op{}(cast_to<thing_type::u8>(), rhs.cast_to<thing_type::u8>());
return res;
case thing_type::U16:
res.get<thing_type::u16>() = Op{}(cast_to<thing_type::u16>(), rhs.cast_to<thing_type::u16>());
return res;
case thing_type::U32:
res.get<thing_type::u32>() = Op{}(cast_to<thing_type::u32>(), rhs.cast_to<thing_type::u32>());
return res;
case thing_type::U64:
res.get<thing_type::u64>() = Op{}(cast_to<thing_type::u64>(), rhs.cast_to<thing_type::u64>());
return res;
case thing_type::Ptr: // TODO: Pointer arithmetics
case thing_type::Ref:
case thing_type::Array:
case thing_type::Count: break;
}
throw std::runtime_error("unreachable");
}
throw std::runtime_error("unexpected operation");
}
private:
thing_type m_type;
std::size_t m_size = 0;
std::byte* m_data = nullptr;
allocator_type m_allocator;
};
} // namespace furvm
#endif // FURVM_THING_HPP
-113
View File
@@ -1,113 +0,0 @@
#ifndef FURVM_THING_ALLOCATOR_HPP
#define FURVM_THING_ALLOCATOR_HPP
#include "furlang/arena.hpp"
#include <vector>
namespace furvm {
template <typename T>
class thing_allocator {
template <typename>
friend class thing_allocator;
using dead_things = std::vector<std::pair<T*, std::size_t>>;
public:
using value_type = T; /**< Value type. */
public:
thing_allocator() = default;
/**
* @brief Constructs a thing allocator.
*
* @param arena Base arena allocator.
*/
explicit thing_allocator(furlang::arena& arena) noexcept
: m_arena(&arena), m_deadThings(std::make_shared<dead_things>()) {}
/**
* @brief Move constructor.
*/
template <typename U>
thing_allocator(thing_allocator<U>&& other) noexcept
: m_arena(std::move(other.m_arena)), m_deadThings(std::move(other.m_deadThings)) {}
/**
* @brief Move constructor.
*/
template <typename U>
thing_allocator& operator=(thing_allocator<U>&& other) noexcept {
if (this == &other) return *this;
m_arena = std::move(other.m_arena);
m_deadThings = std::move(other.m_deadThings);
return *this;
}
/**
* @brief Copy constructor.
*/
template <typename U>
thing_allocator(const thing_allocator<U>& other) noexcept
: m_arena(other.m_arena), m_deadThings(other.m_deadThings) {}
/**
* @brief Copy constructor.
*/
template <typename U>
thing_allocator& operator=(const thing_allocator<U>& other) noexcept {
if (this == &other) return *this;
m_arena = other.m_arena;
m_deadThings = other.m_deadThings;
return *this;
}
public:
/**
* @brief Returns a free chunk of memory.
*
* @param count Count of the things that must fit inside the chunk.
* @return The chunk.
*/
[[nodiscard]] T* allocate(std::size_t count = 1) {
for (auto it = m_deadThings->begin(); it != m_deadThings->end(); ++it) {
if (it->second != count) continue;
T* data = it->first;
m_deadThings->erase(it);
return data;
}
return m_arena->allocate<T>(count);
}
/**
* @brief Recycles the pointer.
*/
void deallocate(T* ptr, std::size_t count) noexcept { m_deadThings->emplace_back(ptr, count); }
public:
/**
* @brief Compares two thing allocators for equality.
*
* @return true if the two things are equal.
*/
template <typename U>
bool operator==(const thing_allocator<U>& other) const noexcept {
return m_arena == other.m_arena && m_deadThings == other.m_deadThings;
}
/**
* @brief Compares two thing allocators for inequality.
*
* @return true if the two things are not equal.
*/
template <typename U>
bool operator!=(const thing_allocator<U>& other) const noexcept {
return m_arena != other.m_arena || m_deadThings != other.m_deadThings;
}
private:
furlang::arena* m_arena = nullptr;
std::shared_ptr<dead_things> m_deadThings;
};
} // namespace furvm
#endif // FURVM_THING_ALLOCATOR_HPP
-28
View File
@@ -1,28 +0,0 @@
#include "furlang/arena.hpp"
#include "furvm/furvm.hpp"
#include "furvm/thing.hpp"
#include "furvm/thing_allocator.hpp"
#include "gtest/gtest.h" // IWYU pragma: keep
namespace {
// TODO: Basic program tests (e.g. for loops)
TEST(Things, Ops) {
furlang::arena arena;
furvm::thing_allocator<std::byte> alloc{ arena };
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
lhs.get<furvm::thing_type::u32>() = 6;
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 }, alloc };
rhs.get<furvm::thing_type::u32>() = 7;
auto res = lhs.add(rhs);
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
EXPECT_EQ(lhs.get<furvm::thing_type::u32>(), 6);
EXPECT_EQ(rhs.get<furvm::thing_type::u32>(), 7);
EXPECT_EQ(res.get<furvm::thing_type::u32>(), 6 + 7);
}
} // namespace
@@ -24,7 +24,7 @@ struct lexer_error {
std::string message;
};
using token_r = furlang::result<token, lexer_error>;
using token_r = furlang::result<lexer_error, token>;
class lexer {
public:
@@ -34,6 +34,7 @@ struct token {
// Instructions
Push,
Array,
Slice,
Get,
Set,
Drop,
+16
View File
@@ -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 <optional>
#include <stack>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
@@ -22,6 +23,7 @@ struct ir_operand {
Integer = 0,
Register,
Variable,
Global,
Function,
Block,
BlockPair,
@@ -32,8 +34,19 @@ struct ir_operand {
struct register_s {
std::uint64_t name : 54;
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;
std::uint16_t variable;
std::uint16_t global;
std::uint64_t function;
std::uint64_t block;
struct block_pair_s {
@@ -53,6 +66,9 @@ struct ir_operand {
value_u(std::uint16_t variable)
: variable(variable) {}
value_u(register_s reg)
: reg(reg) {}
value_u(std::uint64_t first, std::uint64_t second)
: blockPair({ first, second }) {}
@@ -63,6 +79,30 @@ struct ir_operand {
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<value_u, Args...>>>
ir_operand(type_e type, 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 {
@@ -137,6 +177,10 @@ struct ir_instruction {
default: return false;
}
}
bool operator==(const ir_instruction& rhs) const {
return type == rhs.type && destination == rhs.destination && sources == rhs.sources;
}
};
struct ir_basic_block {
@@ -172,7 +216,7 @@ struct ir_module_variable : ir_variable {
std::uint16_t name;
ir_operand operand() const final { return { ir_operand::Variable, name }; }
ir_operand operand() const final { return { ir_operand::Global, name }; }
};
struct ir_function_variable : ir_variable {
@@ -181,7 +225,7 @@ struct ir_function_variable : ir_variable {
std::uint64_t name;
ir_operand operand() const final { return { ir_operand::Register, name }; }
ir_operand operand() const final { return { ir_operand::Variable, name }; }
};
struct ir_scope {
@@ -223,9 +267,10 @@ struct ir_function : ir_scope {
std::vector<ir_basic_block> blocks;
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 {
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) {
@@ -302,7 +347,11 @@ struct ir_context {
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 }); }
+177
View File
@@ -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
+101
View File
@@ -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
#include <exception>
#include <optional>
#include <ostream>
#include <type_traits>
#include <utility>
namespace furlang {
@@ -43,6 +45,8 @@ public:
const char* what() const noexcept override { return "bad result access"; }
};
struct error_tag {};
/**
* @brief Result.
*
@@ -51,7 +55,7 @@ public:
* @tparam R Value type.
* @tparam E Error type.
*/
template <typename R, typename E>
template <typename E, typename R = void>
class result {
public:
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_const_reference = const error_type&; /**< Error const reference type. */
public:
template <typename Other>
result(const result<E, Other>& error)
: result(error_tag{}, error.error()) {}
/**
* @brief Construct a new result.
*
@@ -82,7 +90,7 @@ public:
*
* @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) {
new (&m_value.result) value_type(std::forward<Args>(args)...);
}
@@ -92,7 +100,7 @@ public:
*
* @param error Error to copy.
*/
explicit result(const error_type& error)
result(error_tag tag, const error_type& error)
: m_error(true) {
new (&m_value.error) error_type(error);
}
@@ -102,7 +110,7 @@ public:
*
* @param error Error to move.
*/
explicit result(error_type&& error)
result(error_tag tag, error_type&& error)
: m_error(true) {
new (&m_value.error) error_type(std::move(error));
}
@@ -166,6 +174,16 @@ public:
}
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:
/**
* @brief Checks if this result contains a value.
@@ -364,6 +382,34 @@ private:
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
#endif // FURLANG_RESULT_HPP
#endif // FURLANG_RESULT_HPP
+58
View File
@@ -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
+33
View File
@@ -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
+190
View File
@@ -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
+31
View File
@@ -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
@@ -1,13 +1,11 @@
#ifndef FURVM_CONTEXT_HPP
#define FURVM_CONTEXT_HPP
#include "furlang/arena.hpp"
#include "furvm/executor.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp"
#include "furvm/module.hpp" // IWYU pragma: keep
#include "furvm/thing.hpp" // IWYU pragma: keep
#include "furvm/thing_allocator.hpp"
#include <cstddef>
#include <utility>
@@ -22,8 +20,7 @@ public:
/**
* @brief Constructs a context.
*/
context()
: m_thingAllocator(m_thingArena) {}
context() {}
~context() = default;
@@ -70,21 +67,22 @@ public:
const std::vector<executor>& executors() const { return m_executors; }
public:
/**
* @brief Returns context's thing allocator.
*
* @return The thing allocator.
*/
thing_allocator<std::byte> thing_alloc() const { return m_thingAllocator; }
thing_type_store& tt_store() { return m_thingTypeStore; }
public:
template <typename... Args>
thing<> allocate_thing(Args&&... args) {
thing<> thing = { std::forward<Args>(args)... };
m_heap.push_back(thing.raw());
return std::move(thing);
}
private:
handle_container<mod_h> m_modules;
std::vector<executor> m_executors;
furlang::arena m_thingArena;
thing_allocator<std::byte> m_thingAllocator;
class thing_type_store m_thingTypeStore;
class thing_type_store m_thingTypeStore;
// A list of things on the heap
std::vector<std::byte*> m_heap;
};
} // namespace furvm
@@ -3,11 +3,11 @@
#include "furvm/fwd.hpp"
#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 <stack>
#include <utility>
#include <vector>
namespace furvm {
@@ -15,6 +15,8 @@ namespace furvm {
enum class executor_flags : std::uint32_t {
Suspended = (1 << 0), /**< Execution suspended. */
Done = (1 << 1), /**< Execution is finished. */
JustHit = (1 << 16), /**< Executor just hit a breakpoint. */
};
static inline executor_flags operator|(executor_flags lhs, executor_flags rhs) {
@@ -29,13 +31,17 @@ static inline executor_flags operator~(executor_flags flags) {
return executor_flags(~static_cast<std::uint32_t>(flags));
}
using executor_callback = std::function<void(executor&)>;
class executor {
friend class context;
private:
executor(context* context)
: m_context(context) {}
public:
static constexpr executor_flags STATE_FLAGS = executor_flags::JustHit;
using new_frame_callback = std::function<void(executor&)>;
using stack_thing = thing<stack_allocator>;
public:
/**
* @brief Executor frame.
@@ -47,8 +53,8 @@ public:
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
thing_type* returnType; /**< Return type. */
std::vector<thing<>> variables; /**< Frame variables. */
thing_type* returnType; /**< Return type. */
std::vector<stack_thing> variables; /**< Frame variables. */
};
public:
~executor() = default;
@@ -84,6 +90,17 @@ public:
* @return The flags.
*/
executor_flags flags() const { return m_flags; }
bool done() const { return (m_flags & executor_flags::Done) == executor_flags::Done; }
bool suspended() const { return (m_flags & executor_flags::Suspended) == executor_flags::Suspended; }
void unsuspend() { m_flags = m_flags & ~executor_flags::Suspended; }
void clear_flags() {
m_flags = m_flags & STATE_FLAGS;
m_flags = m_frames.empty() ? executor_flags::Done : furvm::executor_flags{ 0 };
}
public:
/**
* @brief Pushes a new frame.
@@ -117,25 +134,27 @@ public:
* @param thing Thing.
* @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.
*
* @return A handle to the popped thing.
*/
thing<> pop_thing();
stack_thing pop_thing();
/**
* @brief Returns the top thing on the stack.
*
* @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<stack_thing>& stack() const { return m_stack; }
public:
/**
* @brief Stores a thing in a frame variable.
@@ -143,7 +162,7 @@ public:
* @param variable Id of the variable in which the handle will be put.
* @param thing Thing handle.
*/
void store_thing(variable_t variable, const thing<>& thing);
void store_thing(variable_t variable, const stack_thing& thing);
/**
* @brief Stores a thing in a frame variable.
@@ -151,7 +170,7 @@ public:
* @param variable Id of the variable in which the handle will be put.
* @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.
@@ -159,9 +178,9 @@ public:
* @param variable Id of the variable from which the handle will be fetched.
* @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:
/**
* @brief Executes next instruction.
@@ -171,16 +190,17 @@ private:
thing_type thing_type_impl(mod_h mod, mod_type type) const;
thing_type* mod_to_thing_type(const mod_h& mod, const mod_type& type) const;
thing<> make_reference(const thing<>& thing) const;
private:
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
context* m_context;
static bool compare_thing_types(const thing_type& lhs, const thing_type& rhs);
private:
executor_flags m_flags = executor_flags::Done;
context* m_context;
furvm::stack<std::byte> m_stackStorage;
std::stack<frame> m_frames;
std::stack<thing<>> m_stack;
std::stack<frame> m_frames;
std::vector<stack_thing> m_stack;
executor_callback m_newFrameCb = nullptr;
new_frame_callback m_newFrameCb = nullptr;
};
} // namespace furvm
@@ -1,7 +1,6 @@
#ifndef FURVM_FUNCTION_HPP
#define FURVM_FUNCTION_HPP
#include "furlang/utility/hash.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp" // IWYU pragma: keep
@@ -140,11 +140,6 @@ using mod_id = std::string;
*/
using mod_h = handle<mod, refcount_header<mod_id>>;
// thing_allocator.hpp
template <typename T>
class thing_allocator;
// thing.hpp
/**
@@ -161,7 +156,7 @@ using thing_type_id = std::uint32_t;
*
* A stack element. Think of it like of a value in C++ or I guess a class in java.
*/
template <template <typename> typename Allocator = thing_allocator>
template <template <typename> typename Allocator = std::allocator>
class thing;
/**
@@ -56,6 +56,7 @@ struct instruction {
PushU32,
PushConstant,
Array,
Slice,
Get,
Set,
Drop,
@@ -3,6 +3,7 @@
#include "furlang/utility/hash.hpp"
#include "furlang/view.hpp"
#include "furvm/constant.hpp"
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/handle.hpp"
@@ -10,8 +11,6 @@
#include <functional>
#include <istream>
#include <limits>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
@@ -28,6 +27,10 @@ struct mod_type {
std::size_t size;
};
struct slice_value {
mod_type_id typeId;
};
struct import_value {
mod_id modId;
mod_type_id typeId;
@@ -45,6 +48,7 @@ struct mod_type {
Ptr,
Ref,
Array,
Slice,
Import,
Count,
@@ -53,6 +57,7 @@ struct mod_type {
std::nullptr_t null = nullptr;
mod_type_id typeRef;
array_value array;
slice_value slice;
import_value imprt;
value() = default;
@@ -97,6 +102,7 @@ struct mod_type {
~mod_type() {
switch (type) {
case Array: value.array.~array_value(); break;
case Slice: value.slice.~slice_value(); break;
case Import: value.imprt.~import_value(); break;
default: break;
}
@@ -106,6 +112,7 @@ struct mod_type {
: type(other.type) {
switch (type) {
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;
default: break;
}
@@ -117,6 +124,7 @@ struct mod_type {
type = other.type;
switch (type) {
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;
default: break;
}
@@ -128,6 +136,7 @@ struct mod_type {
: type(other.type) {
switch (type) {
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;
default: break;
}
@@ -138,6 +147,7 @@ struct mod_type {
type = other.type;
switch (type) {
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;
default: break;
}
@@ -145,6 +155,11 @@ struct mod_type {
}
};
struct breakpoint {
std::function<void(executor&, void*)> callback;
void* data = nullptr;
};
class mod {
friend class function;
friend class serializer;
@@ -379,6 +394,22 @@ public:
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
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:
template <typename Fwd, typename = std::enable_if_t<std::is_constructible_v<breakpoint, Fwd>>>
void set_breakpoint(bytecode_pos pos, Fwd&& breakpoint) {
m_breakpoints[pos] = std::forward<Fwd>(breakpoint);
}
bool has_breakpoint(bytecode_pos pos) const { return m_breakpoints.find(pos) != m_breakpoints.end(); }
const breakpoint& breakpoint_at(bytecode_pos pos) const { return m_breakpoints.at(pos); }
public:
/**
* @brief Prints the module in a bytecode form to an output stream.
@@ -409,7 +440,11 @@ private:
std::vector<thing<>> m_globalVariables;
std::vector<constant> m_constants;
std::unordered_map<std::string, native_function> m_nativeFunctions;
std::unordered_map<bytecode_pos, breakpoint> m_breakpoints;
};
} // namespace furvm
+56
View File
@@ -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
File diff suppressed because it is too large Load Diff
+226
View File
@@ -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
+4
View File
@@ -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)
@@ -110,6 +110,10 @@ static const char* typeNames[furvm::instruction::Count] = {
"load",
// Store:
"store",
// LoadGlobal:
"loadg",
// StoreGlobal:
"storeg",
// Call:
"call",
// Jump:
@@ -132,7 +136,7 @@ int main(int argc, char** argv) {
furvm::mod mod;
try {
mod = furvm::mod::load(file);
} catch (std::exception ex) {
} catch (const std::exception& ex) {
std::cerr << "Failed to load module " << argv[1] << ": " << ex.what() << '\n';
return 1;
}
@@ -154,7 +158,7 @@ int main(int argc, char** argv) {
std::unordered_set<std::size_t> deadLabels;
std::size_t labelCounter = 0;
std::cout << "; Functions:\n";
std::cout << "\n; Functions:\n";
for (const auto& ptr : mod.functions()) {
const auto& [header, func] = *ptr;
auto it = mod.function_map().find(header.id());
@@ -187,7 +191,14 @@ int main(int argc, char** argv) {
std::cout << '\n';
}
std::cout << "; Bytecode:\n";
if (mod.get_global_variable_count() > 0) {
std::cout << "\n; Global Variables:\n";
for (std::uint16_t i = 0; i < mod.get_global_variable_count(); ++i) {
std::cout << "allocate __g" << i << '\n';
}
}
std::cout << "\n; Bytecode:\n";
// Label pass:
for (std::size_t off = 0; off < mod.bytecode().size();) {
furvm::instruction instr{};
@@ -217,6 +228,7 @@ int main(int argc, char** argv) {
case furvm::instruction_argument::Constant: throw std::runtime_error("unimplemented");
case furvm::instruction_argument::Type: std::cout << " $__t" << instr.arg.u16; break;
case furvm::instruction_argument::Variable: std::cout << " %" << instr.arg.u16; break;
case furvm::instruction_argument::GlobalVariable: std::cout << " %__g" << instr.arg.u16; break;
case furvm::instruction_argument::Function: std::cout << ' ' << funcNames[instr.arg.s16]; break;
case furvm::instruction_argument::Offset: std::cout << " #" << labels[instr.arg.s16]; break;
case furvm::instruction_argument::Count: break;
+5
View File
@@ -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)
+119 -72
View File
@@ -4,6 +4,7 @@
#include "furvm/function.hpp"
#include "furvm/fwd.hpp"
#include "furvm/instruction.hpp"
#include "furvm/module.hpp"
#include <cassert>
#include <cstdint>
@@ -24,6 +25,7 @@ namespace {
// NOLINTBEGIN
std::unordered_map<enum token::type, furvm::instruction_t> instructions = {
{ token::Array, furvm::instruction_t::Array },
{ token::Slice, furvm::instruction_t::Slice },
{ token::Get, furvm::instruction_t::Get },
{ token::Set, furvm::instruction_t::Set },
{ 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::Push: return "push";
case token::Array: return "array";
case token::Slice: return "slice";
case token::Get: return "get";
case token::Set: return "set";
case token::Drop: return "drop";
@@ -126,14 +129,10 @@ struct mod_context {
std::vector<std::size_t> unknowns;
};
furvm::mod mod;
std::unordered_map<std::pair<std::string, furvm::function_sig>,
furvm::function_h,
furlang::utility::
pair_hash<std::string, furvm::function_sig, std::hash<std::string>, furvm::detail::function_sig_hash>>
functions;
std::unordered_map<std::string, furvm::mod_type_h> types;
std::unordered_map<std::string, std::uint16_t> variables;
furvm::mod mod;
std::unordered_map<std::string, std::vector<furvm::function_h>> functions;
std::unordered_map<std::string, furvm::mod_type_h> types;
std::unordered_map<std::string, std::uint16_t> variables;
std::unordered_map<std::string, label_context> labels;
@@ -147,6 +146,48 @@ struct mod_context {
bool operator!() const { return error.type != generator_error::Success; }
};
bool compare_types(const furvm::mod_type_h& lhs, const furvm::mod_type_h& rhs) const {
if (lhs->type != rhs->type) return false;
switch (lhs->type) {
case furvm::mod_type::S8:
case furvm::mod_type::S16:
case furvm::mod_type::S32:
case furvm::mod_type::S64:
case furvm::mod_type::U8:
case furvm::mod_type::U16:
case furvm::mod_type::U32:
case furvm::mod_type::U64: return true;
case furvm::mod_type::Ptr:
case furvm::mod_type::Ref:
return compare_types(mod.type_at(lhs->value.typeRef), mod.type_at(rhs->value.typeRef));
case furvm::mod_type::Array:
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));
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:
return lhs->value.imprt.modId == rhs->value.imprt.modId &&
lhs->value.imprt.typeId == rhs->value.imprt.typeId;
case furvm::mod_type::Count: break;
}
throw std::runtime_error("unreachable");
}
furvm::function_h find_function(const std::string& name, const furvm::function_sig& signature) const {
if (auto it = functions.find(name); it != functions.end()) {
for (const auto& func : it->second) {
if (func->signature().params.size() != signature.params.size()) continue;
std::size_t idx = 0;
while (idx < signature.params.size()) {
if (!compare_types(func->signature().params[idx], signature.params[idx])) break;
++idx;
}
if (idx == signature.params.size()) return func;
}
}
return {};
}
mod_context() {
types.emplace("s8", mod.emplace_type(furvm::mod_type::S8));
types.emplace("u8", mod.emplace_type(furvm::mod_type::U8));
@@ -182,6 +223,53 @@ struct mod_context {
return { { generator_error::Success }, token.value };
}
furvm::mod_type_h eat_type(lexer& lexer, const token& tok) {
switch (tok.type) {
case token::Dolar: {
auto result = eat_token(lexer, token::Identifier);
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
return it->second;
}
throw std::runtime_error("unknown type");
}
case token::Import: {
throw std::runtime_error("unimplemented");
// auto typeNameRes = eat_token(lexer, token::Identifier);
// if (!typeNameRes) return typeNameRes.error;
// return { generator_error::Success };
}
case token::Ref: {
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::Ref, inner.id());
}
case token::Array: {
auto result = next_token(lexer);
if (!result) throw std::runtime_error("error");
auto inner = eat_type(lexer, result.value);
auto size = eat_token(lexer, token::Unsigned);
if (!size) throw std::runtime_error("error");
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");
}
}
generator_error generate(lexer& lexer) {
auto result = next_token(lexer);
if (result.error.type == generator_error::UnexpectedEof) return { generator_error::Eof };
@@ -203,8 +291,7 @@ struct mod_context {
(func.pub ? mod.emplace_function(func.name, std::move(func.signature), label.offset)
: mod.emplace_function(std::move(func.signature), label.offset));
functions.emplace(std::make_pair(std::move(func.name), std::move(func.signature)), handle);
functions[std::move(func.name)].push_back(handle);
handle.dispatch();
}
for (auto unknown : label.unknowns) {
@@ -251,14 +338,8 @@ struct mod_context {
result = next_token(lexer);
if (!result) return result.error;
while (result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
if (auto it = types.find(std::string(result->value.string)); it != types.end()) {
signature.params.push_back(it->second);
} else {
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
}
while (result->type != token::EqSign) {
signature.params.push_back(eat_type(lexer, result.value));
result = next_token(lexer);
if (!result) return result.error;
@@ -280,13 +361,15 @@ struct mod_context {
if (!result) return result.error;
}
std::string name = std::string(nameRes->value.string);
if (!find_function(name, signature).empty()) throw std::runtime_error("function already defined");
switch (result->type) {
case token::Sha256: {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
std::string name = std::string(nameRes->value.string);
std::string labelName = std::string(result->value.string);
std::size_t offset = 0;
if (auto it = labels.find(labelName); it != labels.end()) {
@@ -299,10 +382,9 @@ struct mod_context {
furvm::function_h handle =
(pub ? mod.emplace_function(name, signature, offset) : mod.emplace_function(signature, offset));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
functions[name].push_back(handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Native: {
@@ -315,10 +397,9 @@ struct mod_context {
furvm::function_h handle = (pub ? mod.emplace_function(name, signature, std::move(nativeName))
: mod.emplace_function(signature, std::move(nativeName)));
functions.emplace(std::make_pair(name, std::move(signature)), handle);
functions[name].push_back(handle);
handle.dispatch();
return { generator_error::Success };
}
case token::Import: {
@@ -340,41 +421,10 @@ struct mod_context {
result = next_token(lexer);
if (!result) return result.error;
switch (result->type) {
case token::Import: {
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
return { generator_error::Success };
}
case token::Array: {
result = eat_token(lexer, token::Dolar);
if (!result) return result.error;
auto typeNameRes = eat_token(lexer, token::Identifier);
if (!typeNameRes) return typeNameRes.error;
auto size = eat_token(lexer, token::Unsigned);
if (!size) return size.error;
furvm::mod_type_id innerId = 0;
if (auto it = types.find(std::string(typeNameRes->value.string)); it != types.end()) {
innerId = it->second.id();
} else {
return { generator_error::UnknownType,
"Unknown type "s + std::string(typeNameRes->value.string) };
}
auto type = mod.emplace_type(innerId, size->value.uint);
types.emplace(std::string(nameRes->value.string), type);
type.dispatch();
return { generator_error::Success };
}
default:
return { generator_error::UnexpectedToken,
"Unexpected token "s + token_type(result->type) +
", expected either type, `import`, or `array`" };
}
auto type = eat_type(lexer, result.value);
types.emplace(std::string(nameRes->value.string), std::move(type));
return { generator_error::Success };
}
return { generator_error::UnexpectedToken,
@@ -427,6 +477,7 @@ struct mod_context {
}
case token::Array:
case token::Slice:
case token::Get:
case token::Set:
case token::Drop:
@@ -450,6 +501,8 @@ struct mod_context {
case token::Lenof:
case token::Load:
case token::Store:
case token::LoadGlobal:
case token::StoreGlobal:
case token::Call:
case token::Jmp:
case token::Jnz:
@@ -493,22 +546,16 @@ struct mod_context {
case furvm::instruction_argument::Function: {
furvm::function_sig signature;
while ((result = next_token(lexer)).error.type == generator_error::Success &&
result->type == token::Dolar) {
result = eat_token(lexer, token::Identifier);
if (!result) return result.error;
auto type = types.find(std::string(result->value.string));
if (type == types.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
signature.params.push_back(type->second);
result->type != token::Identifier) {
signature.params.push_back(eat_type(lexer, result.value));
}
if (!result || result->type != token::Identifier) return result.error;
std::string name(result->value.string);
auto func = functions.find(std::make_pair(name, signature));
if (func == functions.end())
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
auto id = func->second.id();
instr.arg.u16 = id;
auto func = find_function(name, signature);
if (func.empty())
return { generator_error::UnknownType, "Unknown function "s + std::string(result->value.string) };
instr.arg.u16 = func.id();
} break;
case furvm::instruction_argument::Offset: {
result = eat_token(lexer, token::Sha256);
+5 -4
View File
@@ -19,7 +19,7 @@ token_r lexer::next_token() {
// TODO: Add support for single-line comments
// TODO: Add support for multi-line comments
if (m_cursor >= m_content.size()) return token_r{ lexer_error{ lexer_error::EndOfFile, location() } };
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 hexadecimal and binary numeric literals
@@ -43,6 +43,7 @@ token_r lexer::next_token() {
{ "push", token::Push },
{ "array", token::Array },
{ "slice", token::Slice },
{ "get", token::Get },
{ "set", token::Set },
{ "drop", token::Drop },
@@ -92,9 +93,9 @@ token_r lexer::next_token() {
case '.': ++m_cursor; return { token::Dot };
case ':': ++m_cursor; return { token::Colon };
default:
return token_r{
lexer_error{ lexer_error::UnknownCharacter, location(), "Unknown character '"s + m_content[m_cursor] + "'" }
};
return token_r::error(lexer_error{ lexer_error::UnknownCharacter,
location(),
"Unknown character '"s + m_content[m_cursor] + "'" });
}
}
+15
View File
@@ -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)
+20
View File
@@ -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
+10
View File
@@ -1,6 +1,10 @@
#ifndef LIBFURC
#include "furc/front/lexer.hpp"
#include "furc/front/parser.hpp"
#include "furc/middle/ir.hpp"
#include "furc/middle/reg_gen.hpp"
#include "furc/middle/ssa.hpp"
#include "furlang/arena.hpp"
int main(void) {
@@ -16,6 +20,12 @@ int main(void) {
furc::lexer lexer = { "<AK>", content };
furc::parser parser = { std::move(lexer), arena };
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;
}
#endif // LIBFURC
+307
View File
@@ -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
+5
View File
@@ -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)
+214
View File
@@ -0,0 +1,214 @@
#include "command.hpp"
#include "furvm/executor.hpp"
#include "furvm/function.hpp"
#include "furvm/instruction.hpp"
#include "furvm/module.hpp"
#include "furvm/thing.hpp"
#include <cctype>
#include <cstddef>
#include <iostream>
#include <random>
#include <string>
#include <utility>
command_info command::parse(std::string_view line) {
command_info info;
while (!line.empty() && std::isspace(line.front()) != 0)
line = line.substr(1);
std::size_t length = 0;
while (line.size() > length && std::isalnum(line.at(length)) != 0)
++length;
info.commandName = line.substr(0, length);
line = line.substr(length);
while (true) {
while (!line.empty() && std::isspace(line.front()) != 0)
line = line.substr(1);
if (line.empty()) break;
length = 0;
while (line.size() > length && std::isalnum(line.at(length)) != 0)
++length;
info.args.push_back(line.substr(0, length));
line = line.substr(length);
}
return std::move(info);
}
void quit_command::execute(context& ctx, const command_info& info) {
static std::random_device s_rd;
static std::mt19937 s_gen(s_rd());
static std::uniform_int_distribution<> s_dis(1, 1000);
int roll = s_dis(s_gen);
std::cout << "Farewell";
if (roll == 1) {
std::cout << ", stasiu pensive";
} else if (roll <= 100) {
std::cout << ", kromek-man";
}
std::cout << "!\n";
ctx.running = false;
}
void run_command::execute(context& ctx, const command_info& info) {
if (!ctx.executor->done()) {
std::cout << "A program is currently running, do you want to kill it? (y/N) ";
std::string answer;
if (!std::getline(std::cin, answer)) {
ctx.running = false;
return;
}
if (answer != "y" && answer != "Y" && answer != "yes") return;
ctx.kill();
}
std::cout << "Running the program\n";
ctx.run();
}
void continue_command::execute(context& ctx, const command_info& info) {
ctx.run();
}
void next_command::execute(context& ctx, const command_info& info) {
if (ctx.executor->done()) {
std::cout << "No program's currently running\n";
return;
}
ctx.executor->unsuspend();
ctx.executor->step();
ctx.print_instruction();
}
static void print_type(const furvm::thing_type& type) {
switch (type.type) {
case furvm::thing_type::S8: std::cout << "s8"; break;
case furvm::thing_type::S16: std::cout << "s16"; break;
case furvm::thing_type::S32: std::cout << "s32"; break;
case furvm::thing_type::S64: std::cout << "s64"; break;
case furvm::thing_type::U8: std::cout << "u8"; break;
case furvm::thing_type::U16: std::cout << "u16"; break;
case furvm::thing_type::U32: std::cout << "u32"; break;
case furvm::thing_type::U64: std::cout << "u64"; break;
case furvm::thing_type::Ptr: std::cout << "ptr"; break;
case furvm::thing_type::Ref: std::cout << "ref"; break;
case furvm::thing_type::Array:
std::cout << "array(";
print_type(*type.value.array.type);
std::cout << ", ";
if (type.value.array.size == 0)
std::cout << "dynamic";
else
std::cout << type.value.array.size;
std::cout << ")";
break;
case furvm::thing_type::Slice:
std::cout << "slice(";
print_type(*type.value.slice.type);
std::cout << ")";
break;
case furvm::thing_type::Count: break;
}
}
static void print_thing(const furvm::thing<>& thing) {
std::cout << '(';
print_type(thing.type());
std::cout << ") ";
switch (thing.type().type) {
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::s16>(); break;
case furvm::thing_type::S32: std::cout << thing.get<furvm::s32>(); 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::u8>()); break;
case furvm::thing_type::U16: std::cout << thing.get<furvm::u16>(); break;
case furvm::thing_type::U32: std::cout << thing.get<furvm::u32>(); 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::Array:
case furvm::thing_type::Slice: {
if (thing.length() == 0) {
std::cout << "{}";
break;
}
std::cout << '(' << thing.length() << ") { ";
for (const auto& el : thing) {
print_thing(el);
std::cout << ", ";
}
std::cout << " }";
} break;
case furvm::thing_type::Ref:
case furvm::thing_type::Count: break;
}
}
static void breakpoint_hit(furvm::executor& executor, void* data) {
std::cout << "Breakpoint hit\n";
context* ctx = reinterpret_cast<context*>(data);
ctx->halt = true;
ctx->print_instruction();
}
void break_command::execute(context& ctx, const command_info& info) {
if (info.args.empty()) {
std::cerr << "Usage: " << info.commandName << " <function name>\n";
return;
}
std::size_t count = 0;
for (const auto& [id, sigPair] : ctx.mod->function_map()) {
if (sigPair.first != info.args[0]) continue;
const auto& func = ctx.mod->function_at(id);
if (func->type() != furvm::function_t::Normal) continue;
count += 1;
ctx.mod->set_breakpoint(func->position(), furvm::breakpoint{ breakpoint_hit, &ctx });
}
if (count == 0) {
std::cerr << "No function \"" << info.args[0] << "\" found!\n";
return;
}
std::cout << "Breakpoint set in " << count << " places\n";
}
void info_command::execute(context& ctx, const command_info& info) {
if (info.args.empty()) {
std::cout << "Possible arguments:\n";
std::cout << "- variables\n";
std::cout << "- stack\n";
} else if (info.args[0] == "variables") {
if (ctx.executor->done()) {
std::cerr << "Not running\n";
return;
}
std::cout << "Variables:\n";
const auto& frame = ctx.executor->top_frame();
for (std::size_t i = 0; i < frame.variables.size(); ++i) {
// TODO: Add debug information to modules
std::cout << "- %" << i << " = ";
print_thing(frame.variables[i]);
std::cout << '\n';
}
} else if (info.args[0] == "stack") {
if (ctx.executor->done()) {
std::cerr << "Not running\n";
return;
}
std::cout << "Stack from top to bottom:\n";
const auto& frame = ctx.executor->top_frame();
for (std::size_t begin = frame.stackBase, i = ctx.executor->stack().size(); i > begin;) {
std::cout << "- ";
print_thing(ctx.executor->stack()[--i]);
std::cout << '\n';
}
} else {
std::cerr << "Unexpected argument \"" << info.args[0] << "\"\n";
}
}
@@ -36,4 +36,24 @@ public:
void execute(context& ctx, const command_info& info) override;
};
class continue_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class next_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class break_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
class info_command final : public command {
public:
void execute(context& ctx, const command_info& info) override;
};
#endif // COMMAND_HPP
+137
View File
@@ -0,0 +1,137 @@
#include "context.hpp"
#include <furvm/executor.hpp>
#include <furvm/instruction.hpp>
#include <iostream>
#include <stdexcept>
void context::kill() const {
while (!executor->done())
executor->pop_frame();
}
void context::init() {
kill();
executor->push_frame(mod, *mainFunction);
executor->clear_flags();
}
void context::run() {
if (executor->done()) init();
executor->unsuspend();
halt = false;
while (!executor->done() && !halt)
executor->step();
if (executor->done()) {
std::cout << "Execution finished\n";
halt = true;
}
}
void context::print_instruction() const {
static const char* s_instrNames[furvm::instruction::Count] = {
// NoOperation
"nop",
// PushS8
"push $s8",
// PushU8
"push $u8",
// PushS16
"push $s16",
// PushU16
"push $U16",
// PushS32
"push $S32",
// PushU32
"push $U32",
// PushConstant
"push",
// Array
"array",
// Slice
"slice",
// Get
"get",
// Set
"set",
// Drop
"drop",
// Duplicate
"dup",
// Swap
"swap",
// Clone
"clone",
// Reference
"ref",
// Add
"add",
// Sub
"sub",
// Mul
"mul",
// Div
"div",
// Mod
"mod",
// Equals
"eq",
// NotEquals
"ne",
// LessThan
"lt",
// GreaterThan
"gt",
// LessEqual
"le",
// GreaterEqual
"ge",
// Pointerof
"pointerof",
// Sizeof
"sizeof",
// Lengthof
"lenof",
// Load
"load",
// Store
"store",
// LoadGlobal
"loadg",
// StoreGlobal
"storeg",
// Call
"call",
// Jump
"jmp",
// JumpNotZero
"jnz",
// Return
"ret",
};
furvm::instruction instr{};
instr.read(mod->bytecode_view().subview(executor->top_frame().position));
std::cout << s_instrNames[instr.type];
if (instr.arg.type != furvm::instruction_argument_t::None) {
std::cout << ' ';
switch (instr.arg.type) {
case furvm::instruction_argument::S8: std::cout << std::to_string(instr.arg.s8); break;
case furvm::instruction_argument::U8: std::cout << std::to_string(instr.arg.u8); break;
case furvm::instruction_argument::S16: std::cout << instr.arg.s16; break;
case furvm::instruction_argument::U16: std::cout << instr.arg.u16; break;
case furvm::instruction_argument::S32: std::cout << instr.arg.s32; break;
case furvm::instruction_argument::U32: std::cout << instr.arg.u32; break;
case furvm::instruction_argument::None:
case furvm::instruction_argument::Count: break;
case furvm::instruction_argument::Constant: throw std::runtime_error("unimplemented");
case furvm::instruction_argument::Type: std::cout << "$__t" << instr.arg.u32; break;
case furvm::instruction_argument::Variable: std::cout << "%" << instr.arg.u16; break;
case furvm::instruction_argument::GlobalVariable: std::cout << "%__g" << instr.arg.u16; break;
case furvm::instruction_argument::Function: std::cout << "<a function>"; break;
case furvm::instruction_argument::Offset: std::cout << std::to_string(instr.arg.s8); break;
}
}
std::cout << '\n';
}
@@ -13,8 +13,14 @@ struct context {
furvm::function_h mainFunction;
bool running = true;
bool halt = true;
void kill() const;
void init();
void run();
void print_instruction() const;
};
#endif // CONTEXT_HPP
+24 -14
View File
@@ -1,6 +1,7 @@
#include "command.hpp"
#include "context.hpp"
#include "furvm/function.hpp"
#include "isocline.h"
#include <fstream>
#include <furvm/fwd.hpp>
@@ -10,21 +11,21 @@
#include <utility>
// taken from furvm uwu :3 ^^ nya~ ngh~
static void print_thing(const furvm::thing<furvm::thing_allocator>& thing) {
static void print_thing(const furvm::thing<>& thing) {
using namespace furvm;
switch (thing.true_type().type) {
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
case thing_type::S64: std::cout << thing.get<thing_type::s64>(); break;
case thing_type::U8: std::cout << thing.get<thing_type::u8>(); break;
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
switch (thing.type().type) {
case thing_type::S8: std::cout << thing.cast_to<s16>(); break;
case thing_type::S16: std::cout << thing.get<s16>(); break;
case thing_type::S32: std::cout << thing.get<s32>(); break;
case thing_type::S64: std::cout << thing.get<s64>(); break;
case thing_type::U8: std::cout << thing.get<u8>(); break;
case thing_type::U16: std::cout << thing.get<u16>(); break;
case thing_type::U32: std::cout << thing.get<u32>(); break;
case thing_type::U64: std::cout << thing.get<u64>(); break;
case thing_type::Array:
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));
}
@@ -43,8 +44,14 @@ int main(int argc, char** argv) {
}
static std::unordered_map<std::string_view, command*> s_commands;
s_commands["quit"] = s_commands["q"] = new quit_command();
s_commands["exit"] = s_commands["quit"] = s_commands["q"] = new quit_command();
s_commands["run"] = s_commands["r"] = new run_command();
s_commands["continue"] = s_commands["c"] = new continue_command();
s_commands["next"] = s_commands["n"] = new next_command();
s_commands["break"] = s_commands["b"] = new break_command();
s_commands["info"] = s_commands["i"] = new info_command();
ic_set_history(nullptr, -1);
try {
std::ifstream file(argv[1], std::ios::binary | std::ios::in);
@@ -68,8 +75,11 @@ int main(int argc, char** argv) {
std::string prevInput;
while (ctx.running) {
std::swap(inputLine, prevInput);
std::cout << "(furdb) ";
if (!std::getline(std::cin, inputLine)) break;
char* inputRaw = ic_readline("furdb");
if (inputRaw == nullptr) break;
inputLine = inputRaw;
free(inputRaw); // NOLINT
if (inputLine.empty()) {
inputLine = prevInput;
+4
View File
@@ -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/)
+137
View File
@@ -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_HDRS "include/**.hpp")
file(GLOB_RECURSE FURVM_SRCS "${CMAKE_SOURCE_DIR}/src/furvm/**.cpp")
file(GLOB_RECURSE FURVM_HDRS "${CMAKE_SOURCE_DIR}/include/furvm/**.hpp")
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)
set_target_properties(libfurvm PROPERTIES PREFIX "")
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)
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})
target_link_libraries(furvm_tests PRIVATE libfurvm GTest::gtest_main)
gtest_discover_tests(furvm_tests)
gtest_discover_tests(furvm_tests)
+124 -56
View File
@@ -31,14 +31,19 @@ thing_type executor::thing_type_impl(mod_h mod, mod_type type) const {
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64: return { static_cast<enum thing_type::type>(type.type) };
case thing_type::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::Ref: return { thing_type::Ref, mod_to_thing_type(mod, *mod->type_at(type.value.typeRef)) };
case thing_type::Array: {
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 } };
}
default: throw std::runtime_error("invalid thing type");
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;
}
throw std::runtime_error("invalid thing type");
}
thing_type* executor::mod_to_thing_type(const mod_h& mod, const mod_type& type) const {
@@ -46,28 +51,46 @@ thing_type* executor::mod_to_thing_type(const mod_h& mod, const mod_type& type)
return m_context->tt_store().insert(thingType);
}
thing<> executor::make_reference(const thing<>& thing) const {
furvm::thing<> ref = { (struct thing_type){ thing_type::Ref, m_context->tt_store().insert(thing.type()) },
m_context->thing_alloc() };
ref.reference(thing);
return std::move(ref);
bool executor::compare_thing_types(const thing_type& lhs, const thing_type& rhs) {
if (lhs.type != rhs.type) return false;
switch (lhs.type) {
case thing_type::S8:
case thing_type::S16:
case thing_type::S32:
case thing_type::S64:
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
case thing_type::String: return true;
case thing_type::Ptr:
case thing_type::Ref: return compare_thing_types(*lhs.value.typeRef, *rhs.value.typeRef);
case thing_type::Array:
return lhs.value.array.size == rhs.value.array.size &&
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;
}
throw std::runtime_error("unreachable");
}
void executor::push_frame(const mod_h& mod, function function) {
m_stackStorage.push_frame();
mod_h modInst = mod;
while (function.type() == function_t::Import) {
modInst = m_context->at(function.imp().mod);
function = *modInst->function_at(function.imp().function);
}
auto signature = function.signature();
std::vector<thing<>> args;
auto signature = function.signature();
std::vector<stack_thing> args;
args.reserve(signature.params.size());
for (const auto& param : signature.params) {
auto arg = pop_thing();
if (arg.true_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");
args.push_back(std::move(arg));
args.emplace_back(std::move(arg));
}
struct thing_type* returnType = nullptr;
@@ -88,19 +111,36 @@ void executor::push_frame(const mod_h& mod, function function) {
default: throw std::runtime_error("unexpected function type");
}
if (m_newFrameCb) m_newFrameCb(*this);
if (!m_frames.empty()) {
m_flags = m_flags & ~executor_flags::Done;
} else {
m_flags = m_flags | executor_flags::Done;
}
m_flags = m_flags & ~executor_flags::JustHit;
}
struct executor::frame executor::pop_frame() {
if (m_frames.empty()) throw stack_underflow();
struct executor::frame frame = m_frames.top();
m_frames.pop();
std::optional<thing<>> returnValue;
std::optional<stack_thing> returnValue;
if (frame.returnType != nullptr) {
returnValue = pop_thing();
if (returnValue->type() != *frame.returnType) throw std::runtime_error("function return type mismatch");
}
if (m_stack.size() != frame.stackBase) throw std::runtime_error("unexhausted stack");
if (returnValue.has_value()) push_thing(std::move(returnValue.value()));
if (!m_frames.empty()) {
m_flags = m_flags & ~executor_flags::Done;
} else {
m_flags = m_flags | executor_flags::Done;
}
m_flags = m_flags & ~executor_flags::JustHit;
m_stackStorage.pop_frame();
return frame;
}
@@ -108,49 +148,49 @@ struct executor::frame executor::top_frame() const {
return m_frames.top();
}
thing<>& executor::push_thing(thing<>&& thing) {
return m_stack.emplace(std::move(thing));
executor::stack_thing& executor::push_thing(stack_thing&& thing) {
return m_stack.emplace_back(std::move(thing));
}
thing<>& executor::push_thing(const thing<>& thing) {
return m_stack.emplace(thing);
executor::stack_thing& executor::push_thing(const stack_thing& 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();
auto top = std::move(m_stack.top());
m_stack.pop();
auto top = std::move(m_stack.back());
m_stack.pop_back();
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();
return m_stack.top();
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();
return m_stack.top();
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();
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
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();
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
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();
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();
return frame.variables[variable];
}
@@ -160,40 +200,66 @@ void executor::step() {
struct frame& frame = m_frames.top();
if ((m_flags & executor_flags::JustHit) != executor_flags::JustHit && frame.mod->has_breakpoint(frame.position)) {
m_flags = m_flags | executor_flags::Suspended | executor_flags::JustHit;
const auto& bp = frame.mod->breakpoint_at(frame.position);
bp.callback(*this, bp.data);
return;
}
m_flags = m_flags & ~executor_flags::JustHit;
instruction instr{};
frame.position += instr.read(frame.mod->bytecode_view().subview(frame.position));
switch (instr.type) {
case instruction_t::NoOperation: break;
case instruction_t::PushS8: {
push_thing({ (struct thing_type){ thing_type::S8 }, m_context->thing_alloc() }).get<thing_type::s8>() =
instr.arg.s8;
push_thing({ (struct thing_type){ thing_type::S8 }, { m_stackStorage } }).get<s8>() = instr.arg.s8;
} break;
case instruction_t::PushU8: {
push_thing({ (struct thing_type){ thing_type::U8 }, m_context->thing_alloc() }).get<thing_type::u8>() =
instr.arg.u8;
push_thing({ (struct thing_type){ thing_type::U8 }, { m_stackStorage } }).get<u8>() = instr.arg.u8;
} break;
case instruction_t::PushS16: {
push_thing({ (struct thing_type){ thing_type::S16 }, m_context->thing_alloc() }).get<thing_type::s16>() =
instr.arg.s16;
push_thing({ (struct thing_type){ thing_type::S16 }, { m_stackStorage } }).get<s16>() = instr.arg.s16;
} break;
case instruction_t::PushU16: {
push_thing({ (struct thing_type){ thing_type::U16 }, m_context->thing_alloc() }).get<thing_type::u16>() =
instr.arg.u16;
push_thing({ (struct thing_type){ thing_type::U16 }, { m_stackStorage } }).get<u16>() = instr.arg.u16;
} break;
case instruction_t::PushS32: {
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() }).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;
case instruction_t::PushU32: {
push_thing({ (struct thing_type){ thing_type::U32 }, m_context->thing_alloc() }).get<thing_type::u32>() =
static_cast<thing_type::u32>(instr.arg.u8);
push_thing({ (struct thing_type){ thing_type::U32 }, { m_stackStorage } }).get<u32>() =
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;
case instruction_t::Array: {
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)
throw std::runtime_error("invalid array type");
auto& array = push_thing({ type, m_context->thing_alloc() });
auto& array = push_thing({ type, { m_stackStorage } });
if (type.value.array.size == 0) {
auto sizeThing = pop_thing();
@@ -201,6 +267,12 @@ void executor::step() {
array.resize(size);
}
} 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: {
auto index = pop_thing();
auto array = pop_thing();
@@ -228,7 +300,7 @@ void executor::step() {
push_thing(top_thing());
} break;
case instruction_t::Reference: {
push_thing(std::move(make_reference(pop_thing())));
push_thing(stack_thing::make_reference(pop_thing(), { m_stackStorage }));
} break;
case instruction_t::Add: {
auto rhs = pop_thing();
@@ -287,13 +359,13 @@ void executor::step() {
} break;
case instruction_t::Pointerof: {
auto thing = pop_thing();
push_thing({ (struct thing_type){ thing_type::Ptr, m_context->tt_store().at(thing.type().id) },
m_context->thing_alloc() })
push_thing(
{ (struct thing_type){ thing_type::Ptr, m_context->tt_store().at(thing.type().id) }, { m_stackStorage } })
.get<void*>() = thing.raw();
} break;
case instruction_t::Sizeof: {
auto thing = pop_thing();
auto& size = push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() });
auto& size = push_thing({ (struct thing_type){ thing_type::U64 }, { m_stackStorage } });
switch (thing.type().type) {
case thing_type::S8:
case thing_type::S16:
@@ -302,35 +374,32 @@ void executor::step() {
case thing_type::U8:
case thing_type::U16:
case thing_type::U32:
case thing_type::U64:
size.get<thing_type::u64>() = static_cast<thing_type::u64>(thing_type::primitive_size(thing.type().type));
break;
case thing_type::Ptr: size.get<thing_type::u64>() = static_cast<thing_type::u64>(sizeof(void*)); break;
case thing_type::U64: size.get<u64>() = static_cast<u64>(thing_type::primitive_size(thing.type().type)); break;
case thing_type::Ptr: size.get<u64>() = static_cast<u64>(sizeof(void*)); break;
case thing_type::Array:
/* TODO: Return actual memory size of the array
* By the memory size I mean the length times sizeof single element.
*/
size.get<thing_type::u64>() = thing.length();
size.get<u64>() = thing.length();
break;
default: throw std::runtime_error("unreachable");
}
} break;
case instruction_t::Lengthof: {
auto thing = pop_thing();
push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() }).get<thing_type::u64>() =
thing.length();
auto thing = pop_thing();
push_thing({ (struct thing_type){ thing_type::U64 }, { m_stackStorage } }).get<u64>() = thing.length();
} break;
case instruction_t::Load: {
push_thing(make_reference(load_thing(instr.arg.u16)));
push_thing(std::move(stack_thing::make_reference(load_thing(instr.arg.u16), { m_stackStorage })));
} break;
case instruction_t::Store: {
store_thing(instr.arg.u16, std::move(pop_thing()));
} break;
case instruction_t::LoadGlobal: {
push_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;
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;
case instruction_t::Call: {
push_frame(frame.mod, *frame.mod->function_at(instr.arg.u16));
@@ -345,7 +414,6 @@ void executor::step() {
pop_frame();
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
} break;
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
default: throw std::runtime_error("unknown instruction");
}
}
@@ -22,7 +22,7 @@ const std::size_t instruction_argument::s_sizes[instruction_argument::Count] = {
// U32
1,
// Constant:
4,
2,
// Type:
4,
// Variable:
@@ -83,6 +83,8 @@ const instruction_argument_t instruction::s_arguments[instruction::Count] = {
instruction_argument::Constant,
// Array:
instruction_argument::Type,
// Slice:
instruction_argument::None,
// Get:
instruction_argument::None,
// Set:
+18 -10
View File
@@ -13,18 +13,26 @@
static void print_thing(const furvm::thing<>& thing) {
using namespace furvm;
switch (thing.true_type().type) {
case thing_type::S8: std::cout << thing.cast_to<thing_type::s16>(); break;
case thing_type::S16: std::cout << thing.get<thing_type::s16>(); break;
case thing_type::S32: std::cout << thing.get<thing_type::s32>(); break;
case thing_type::S64: std::cout << thing.get<thing_type::s64>(); break;
case thing_type::U8: std::cout << thing.get<thing_type::u8>(); break;
case thing_type::U16: std::cout << thing.get<thing_type::u16>(); break;
case thing_type::U32: std::cout << thing.get<thing_type::u32>(); break;
case thing_type::U64: std::cout << thing.get<thing_type::u64>(); break;
switch (thing.type().type) {
case thing_type::S8: std::cout << thing.cast_to<s16>(); break;
case thing_type::S16: std::cout << thing.get<s16>(); break;
case thing_type::S32: std::cout << thing.get<s32>(); break;
case thing_type::S64: std::cout << thing.get<s64>(); break;
case thing_type::U8: std::cout << thing.get<u8>(); break;
case thing_type::U16: std::cout << thing.get<u16>(); break;
case thing_type::U32: std::cout << thing.get<u32>(); break;
case thing_type::U64: std::cout << thing.get<u64>(); break;
case thing_type::Array:
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 << ", ";
print_thing(thing.at(i));
}
@@ -16,6 +16,19 @@ std::ostream& mod::serialize(std::ostream& os) const {
os.write(MAGIC, sizeof(MAGIC));
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());
detail::serialize(os, typeCount);
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.size);
} break;
case mod_type::Slice: {
detail::serialize(os, type.value.slice.typeId);
} break;
case mod_type::Import: {
detail::serialize(os, type.value.imprt.modId);
detail::serialize(os, type.value.imprt.typeId);
@@ -104,6 +120,22 @@ mod mod::load(std::istream& is) {
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;
detail::load(is, typeCount);
for (mod_type_id id = 0; id < typeCount; ++id) {
@@ -140,6 +172,11 @@ mod mod::load(std::istream& is) {
detail::load(is, size);
mod.emplace_type(id, typeId, size).dispatch();
} 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: {
std::string modName;
detail::load(is, modName);
+141
View File
@@ -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)
+110
View File
@@ -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