Compare commits
82 Commits
224468446d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b98f77c08
|
|||
|
959d0a7773
|
|||
|
70375af068
|
|||
|
46d0de759a
|
|||
|
01964a495c
|
|||
|
21fb587e36
|
|||
|
fe65a09af9
|
|||
|
712a0d7047
|
|||
|
35d7fc4970
|
|||
|
9bf4da4fd9
|
|||
|
6c7d16631d
|
|||
|
24a9530abc
|
|||
|
0354ec1e79
|
|||
|
ddb60ca93a
|
|||
|
3aaed55485
|
|||
|
cfefca49ee
|
|||
|
21e963168e
|
|||
|
b067bbb4ea
|
|||
|
fe37bf8058
|
|||
|
977b22d8f5
|
|||
|
30a06186db
|
|||
|
5e425d7fe4
|
|||
|
12a131c2b8
|
|||
|
5403c0e1a4
|
|||
|
a1dac8ae3f
|
|||
|
a311795915
|
|||
|
090d8ad81e
|
|||
|
5efcf1b9b0
|
|||
|
136663aeab
|
|||
|
56ea4ba2ab
|
|||
|
dab7f73216
|
|||
|
1d4e8bb1d0
|
|||
|
ed84a65757
|
|||
|
26efd110f1
|
|||
|
dd96f95d77
|
|||
|
70cbb003f2
|
|||
|
665982049d
|
|||
|
195d154474
|
|||
|
26d935e22a
|
|||
|
a4966a057c
|
|||
|
b3ce4fa0ed
|
|||
|
29dd9abb3f
|
|||
|
ffe1208775
|
|||
|
a382b92cf0
|
|||
|
c59f0252ed
|
|||
|
5c841a1428
|
|||
|
6d176e58f3
|
|||
|
6737ed86b0
|
|||
|
4ede6f3476
|
|||
|
c841427eff
|
|||
|
a685ecf226
|
|||
|
84b70077c1
|
|||
|
21e92dcb4b
|
|||
|
713aec199f
|
|||
|
15218d7530
|
|||
|
d419b62225
|
|||
|
c68763f785
|
|||
|
dcefe20d47
|
|||
|
989fcb7199
|
|||
|
4cffd27a33
|
|||
|
f9b910ae13
|
|||
|
7a7a1ec64c
|
|||
|
99dba99db2
|
|||
|
cc88d6b6a0
|
|||
|
0e916c314a
|
|||
|
b9d50c62af
|
|||
|
4b9f58b34f
|
|||
|
a420ebf69f
|
|||
|
e1ffa8a11d
|
|||
|
2126b03069
|
|||
|
47e8b1bf51
|
|||
|
7ebc27cab9
|
|||
|
1db13b4e10
|
|||
|
84c6795ff8
|
|||
|
212b1e2384
|
|||
|
ecdf6944a7
|
|||
|
79b6b9742d
|
|||
|
2d40b9337c
|
|||
|
db485af7f6
|
|||
|
854343a5a3
|
|||
|
3faafe371f
|
|||
|
4604f5186e
|
+3
-1
@@ -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: "*"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "deps/isocline"]
|
||||
path = deps/isocline
|
||||
url = https://github.com/daanx/isocline
|
||||
+10
-4
@@ -11,10 +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(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)
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
Checks: '-*'
|
||||
+1
Submodule deps/isocline added at 8d6dc1ef95
@@ -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
|
||||
@@ -1,22 +1,24 @@
|
||||
func println $u32 = native println
|
||||
|
||||
allocate uwu
|
||||
|
||||
public func main = #main
|
||||
main:
|
||||
push $u32 0
|
||||
store %0
|
||||
storeg %uwu
|
||||
loop_header:
|
||||
load %0
|
||||
loadg %uwu
|
||||
push $u32 10
|
||||
ge
|
||||
jnz #loop_end
|
||||
loop_body:
|
||||
load %0
|
||||
loadg %uwu
|
||||
call $u32 println
|
||||
|
||||
load %0
|
||||
loadg %uwu
|
||||
push $u32 1
|
||||
add
|
||||
store %0
|
||||
storeg %uwu
|
||||
|
||||
jmp #loop_header
|
||||
loop_end:
|
||||
@@ -0,0 +1,7 @@
|
||||
func println $s32 = native println
|
||||
|
||||
public func main = #main
|
||||
main:
|
||||
push $s32 69
|
||||
call $s32 println
|
||||
ret
|
||||
@@ -1,5 +0,0 @@
|
||||
file(GLOB_RECURSE FURAS_SRCS "src/**.cpp")
|
||||
file(GLOB_RECURSE FURAS_HDRS "include/**.hpp")
|
||||
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
|
||||
target_include_directories(furas PUBLIC include/)
|
||||
target_link_libraries(furas PRIVATE furlang libfurvm)
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main(void) {
|
||||
std::cout << "Farewell, stasiu!\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
file(GLOB_RECURSE FURLANG_SRCS "src/**.cpp")
|
||||
file(GLOB_RECURSE FURLANG_HDRS "include/**.hpp")
|
||||
add_library(furlang STATIC ${FURLANG_SRCS} ${FURLANG_HDRS})
|
||||
target_include_directories(furlang PUBLIC include/)
|
||||
@@ -1,87 +0,0 @@
|
||||
#ifndef FURLANG_IR_BLOCK_HPP
|
||||
#define FURLANG_IR_BLOCK_HPP
|
||||
|
||||
#include "furlang/ir/instruction.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
/**
|
||||
* @brief Basic block.
|
||||
*
|
||||
* A basic block of IR instructions. https://en.wikipedia.org/wiki/Basic_block
|
||||
*/
|
||||
class block {
|
||||
public:
|
||||
using value_type = std::unique_ptr<instruction>; /**< Value type */
|
||||
public:
|
||||
block() = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces a new instruction.
|
||||
*
|
||||
* Emplaces a new instruction, if exit instruction hasn't been emplaced in this block yet.
|
||||
*
|
||||
* @tparam T Type of the instruction to emplace.
|
||||
* @param args Arguments to call the constructor with.
|
||||
* @return true if the instruction has been emplaced successfully.
|
||||
*/
|
||||
template <typename T, typename... Args, typename = std::enable_if_t<std::is_base_of_v<instruction, T>>>
|
||||
bool emplace(Args&&... args) {
|
||||
if (has_exit()) return false;
|
||||
auto instr = std::make_unique<T>(std::forward<Args>(args)...);
|
||||
if (is_exit_instruction(instr->type())) {
|
||||
m_exit = std::move(instr);
|
||||
} else {
|
||||
m_instructions.emplace_back(std::move(instr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this block's instructions.
|
||||
*
|
||||
* @return The instructions.
|
||||
*/
|
||||
std::vector<value_type>& instructions() { return m_instructions; }
|
||||
|
||||
/**
|
||||
* @brief Returns this block's instructions.
|
||||
*
|
||||
* @return The instructions.
|
||||
*/
|
||||
const std::vector<value_type>& instructions() const { return m_instructions; }
|
||||
|
||||
/**
|
||||
* @brief Checks whether an exit instruction has been emplaced in this block yet.
|
||||
*
|
||||
* @return true if the exit instruction has been emplaced.
|
||||
*/
|
||||
bool has_exit() const { return m_exit != nullptr; }
|
||||
|
||||
/**
|
||||
* @brief Returns this block's exit instruction.
|
||||
*
|
||||
* @return The exit instruction.
|
||||
*/
|
||||
value_type& exit() { return m_exit; }
|
||||
|
||||
/**
|
||||
* @brief Returns this block's exit instruction.
|
||||
*
|
||||
* @return The exit instruction.
|
||||
*/
|
||||
const value_type& exit() const { return m_exit; }
|
||||
private:
|
||||
std::vector<value_type> m_instructions;
|
||||
value_type m_exit;
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_IR_BLOCK_HPP
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef FURLANG_IR_FUNCTION_HPP
|
||||
#define FURLANG_IR_FUNCTION_HPP
|
||||
|
||||
#include "furlang/ir/block.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
enum class function_t : std::uint8_t {
|
||||
Normal = 0,
|
||||
Import,
|
||||
Native,
|
||||
};
|
||||
|
||||
enum class function_access_t : std::uint8_t {
|
||||
Public = 0,
|
||||
Private,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IR function.
|
||||
*
|
||||
* Consists of a name and blocks.
|
||||
* @see block
|
||||
*/
|
||||
class function {
|
||||
public:
|
||||
using value_type = std::shared_ptr<block>; /**< Value type. */
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new IR function.
|
||||
*
|
||||
* @param name Name to forward.
|
||||
*/
|
||||
template <typename StringFwd, typename = std::enable_if_t<std::is_constructible_v<std::string, StringFwd>>>
|
||||
function(StringFwd&& name, function_access_t access, std::uint32_t paramCount, function_t type = function_t::Normal)
|
||||
: m_name(std::forward<StringFwd>(name)), m_access(access), m_paramCount(paramCount), m_type(type) {}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this function's name.
|
||||
*
|
||||
* @return The name.
|
||||
*/
|
||||
const std::string& name() const { return m_name; }
|
||||
|
||||
/**
|
||||
* @brief Returns the function's access.
|
||||
*
|
||||
* @return The access.
|
||||
*/
|
||||
function_access_t access() const { return m_access; }
|
||||
|
||||
/**
|
||||
* @brief Returns this function's parameter count.
|
||||
*
|
||||
* @return The parameter count.
|
||||
*/
|
||||
std::uint32_t param_count() const { return m_paramCount; }
|
||||
|
||||
/**
|
||||
* @brief Returns this function's type.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
function_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Pushes and returns a new IR block.
|
||||
*
|
||||
* @return The new IR block.
|
||||
*/
|
||||
value_type push() { return m_blocks.emplace_back(std::make_shared<block>()); }
|
||||
|
||||
/**
|
||||
* @brief Returns this function's IR blocks.
|
||||
*
|
||||
* @return The IR blocks.
|
||||
*/
|
||||
const std::vector<value_type>& blocks() const { return m_blocks; }
|
||||
private:
|
||||
std::string m_name;
|
||||
function_access_t m_access;
|
||||
function_t m_type;
|
||||
std::uint32_t m_paramCount;
|
||||
std::vector<value_type> m_blocks;
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_IR_FUNCTION_HPP
|
||||
@@ -1,767 +0,0 @@
|
||||
#ifndef FURLANG_IR_INSTRUCTION_HPP
|
||||
#define FURLANG_IR_INSTRUCTION_HPP
|
||||
|
||||
#include "furlang/ir/operand.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
/**
|
||||
* @brief IR instruction type.
|
||||
*/
|
||||
enum class instruction_t {
|
||||
Alloca, /**< Unused */
|
||||
Assign, /**< Assign */
|
||||
Add, /**< Addition */
|
||||
Sub, /**< Subtraction */
|
||||
Mul, /**< Multiplication */
|
||||
Div, /**< Division */
|
||||
Mod, /**< Modulo */
|
||||
Eq, /**< Equal */
|
||||
NotEq, /**< Not equal */
|
||||
LessThan, /**< Less than */
|
||||
GreaterThan, /**< Greater than */
|
||||
LessEq, /**< Less or equal */
|
||||
GreaterEq, /**< Greater or equal */
|
||||
Pointerof, /**< Pointerof */
|
||||
Sizeof, /**< Sizeof */
|
||||
Call, /**< Call */
|
||||
Branch, /**< Branch */
|
||||
BranchCond, /**< Conditional branch */
|
||||
Return, /**< Return */
|
||||
Phi, /**< Phi function */
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, instruction_t type) {
|
||||
switch (type) {
|
||||
case instruction_t::Alloca: return os << "alloca";
|
||||
case instruction_t::Assign: return os << "assign";
|
||||
case instruction_t::Add: return os << "add";
|
||||
case instruction_t::Sub: return os << "sub";
|
||||
case instruction_t::Mul: return os << "mul";
|
||||
case instruction_t::Div: return os << "div";
|
||||
case instruction_t::Mod: return os << "mod";
|
||||
case instruction_t::Eq: return os << "eq";
|
||||
case instruction_t::NotEq: return os << "notEq";
|
||||
case instruction_t::LessThan: return os << "lessThan";
|
||||
case instruction_t::GreaterThan: return os << "greaterThan";
|
||||
case instruction_t::LessEq: return os << "lessEq";
|
||||
case instruction_t::GreaterEq: return os << "greaterEq";
|
||||
case instruction_t::Pointerof: return os << "pointerof";
|
||||
case instruction_t::Sizeof: return os << "sizeof";
|
||||
case instruction_t::Call: return os << "call";
|
||||
case instruction_t::Branch: return os << "branch";
|
||||
case instruction_t::BranchCond: return os << "branchCond";
|
||||
case instruction_t::Return: return os << "return";
|
||||
case instruction_t::Phi: return os << "phi";
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an instruction type exits.
|
||||
*
|
||||
* @param type Instruction type.
|
||||
* @return true if the instruction type exits.
|
||||
*/
|
||||
static inline bool is_exit_instruction(instruction_t type) {
|
||||
switch (type) {
|
||||
case instruction_t::Branch:
|
||||
case instruction_t::BranchCond:
|
||||
case instruction_t::Return: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief IR instruction
|
||||
*/
|
||||
class instruction {
|
||||
public:
|
||||
instruction() = default;
|
||||
virtual ~instruction() = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
instruction(instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
instruction& operator=(instruction&&) = default;
|
||||
|
||||
instruction(const instruction&) = delete;
|
||||
|
||||
instruction& operator=(const instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
virtual instruction_t type() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns whether this instruction has a destination operand.
|
||||
*
|
||||
* @return true if this instruction has the destination operand.
|
||||
*/
|
||||
virtual bool has_destination() const { return false; }
|
||||
|
||||
/**
|
||||
* @brief Returns destination operand of this instruction.
|
||||
*
|
||||
* @return The destination operand.
|
||||
*/
|
||||
virtual operand& destination() { throw std::runtime_error("instruction type mismatch"); }
|
||||
|
||||
/**
|
||||
* @brief Returns destination operand of this instruction.
|
||||
*
|
||||
* @return The destination operand.
|
||||
*/
|
||||
virtual const operand& destination() const { throw std::runtime_error("instruction type mismatch"); }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of source operands of this instruction.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
virtual std::vector<operand*> sources() { return {}; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of source operands of this instruction.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
virtual std::vector<const operand*> sources() const { return {}; }
|
||||
public:
|
||||
/**
|
||||
* @brief Prints an instruction to an output stream.
|
||||
*
|
||||
* Equivalent to calling instruction.print(os).
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param instruction Instruction to print.
|
||||
* @return The output stream.
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const instruction& instruction) { return instruction.print(os); }
|
||||
protected:
|
||||
/**
|
||||
* @brief Prints this instruction to an output stream.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @return The output stream.
|
||||
*/
|
||||
virtual std::ostream& print(std::ostream& os) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Alloca instruction
|
||||
*/
|
||||
class alloca_instruction final : public instruction {
|
||||
public:
|
||||
alloca_instruction() {}
|
||||
|
||||
~alloca_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
alloca_instruction(alloca_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
alloca_instruction& operator=(alloca_instruction&&) = default;
|
||||
|
||||
alloca_instruction(const alloca_instruction&) = delete;
|
||||
|
||||
alloca_instruction& operator=(const alloca_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Alloca.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Alloca; }
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override { return os << "alloca"; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Assign instruction
|
||||
*/
|
||||
class assign_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new assign instruction.
|
||||
*
|
||||
* @param src Source operand.
|
||||
* @param dst Destination operand.
|
||||
*/
|
||||
assign_instruction(operand&& src, operand&& dst)
|
||||
: m_source(std::move(src)), m_destination(std::move(dst)) {}
|
||||
|
||||
~assign_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
assign_instruction(assign_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
assign_instruction& operator=(assign_instruction&&) = default;
|
||||
|
||||
assign_instruction(const assign_instruction&) = delete;
|
||||
|
||||
assign_instruction& operator=(const assign_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Assign.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Assign; }
|
||||
|
||||
/**
|
||||
* @brief Returns whether this instruction has a destination operand.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination.
|
||||
*
|
||||
* @return The destination.
|
||||
*/
|
||||
operand& destination() override { return m_destination; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination.
|
||||
*
|
||||
* @return The destination.
|
||||
*/
|
||||
const operand& destination() const override { return m_destination; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of this instruction's source operands.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
std::vector<operand*> sources() override { return { &m_source }; }
|
||||
|
||||
/**
|
||||
* @brief Returns a list of this instruction's source operands.
|
||||
*
|
||||
* @return The list of source operands.
|
||||
*/
|
||||
std::vector<const operand*> sources() const override { return { &m_source }; }
|
||||
private:
|
||||
operand m_source;
|
||||
operand m_destination;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override { return os << m_destination << " = " << m_source; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generic unary instruction
|
||||
*/
|
||||
class unary_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new unary instruction.
|
||||
*
|
||||
* @param type Instruction type.
|
||||
* @param src Source operand.
|
||||
* @param dst Destination operand.
|
||||
*/
|
||||
unary_instruction(instruction_t type, operand&& src, operand&& dst)
|
||||
: m_type(type), m_src(std::move(src)), m_dst(std::move(dst)) {}
|
||||
|
||||
~unary_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
unary_instruction(unary_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
unary_instruction& operator=(unary_instruction&&) = default;
|
||||
|
||||
unary_instruction(const unary_instruction&) = delete;
|
||||
|
||||
unary_instruction& operator=(const unary_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return The instruction type.
|
||||
*/
|
||||
instruction_t type() const override { return m_type; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_src }; }
|
||||
|
||||
std::vector<const operand*> sources() const override { return { &m_src }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's source operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& src() const { return m_src; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& dst() const { return m_dst; }
|
||||
private:
|
||||
instruction_t m_type;
|
||||
operand m_src;
|
||||
operand m_dst;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override { return os << m_dst << " = " << m_type << ' ' << m_src; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generic binary instruction
|
||||
*/
|
||||
class binary_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new binary operation instruction.
|
||||
*
|
||||
* @param type Instruction type.
|
||||
* @param lhs Left-hand-side operand.
|
||||
* @param rhs Right-hand-side operand.
|
||||
* @param dst Destination operand.
|
||||
*/
|
||||
binary_instruction(instruction_t type, operand&& lhs, operand&& rhs, operand&& dst)
|
||||
: m_type(type), m_lhs(std::move(lhs)), m_rhs(std::move(rhs)), m_dst(std::move(dst)) {}
|
||||
|
||||
~binary_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
binary_instruction(binary_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
binary_instruction& operator=(binary_instruction&&) = default;
|
||||
|
||||
binary_instruction(const binary_instruction&) = delete;
|
||||
|
||||
binary_instruction& operator=(const binary_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::BinaryOp.
|
||||
*/
|
||||
instruction_t type() const override { return m_type; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_lhs, &m_rhs }; }
|
||||
|
||||
std::vector<const operand*> sources() const override { return { &m_lhs, &m_rhs }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's left-hand-side operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& lhs() const { return m_lhs; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's right-hand-side operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& rhs() const { return m_rhs; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& dst() const { return m_dst; }
|
||||
private:
|
||||
instruction_t m_type;
|
||||
operand m_lhs /**< Left-hand-side operand */;
|
||||
operand m_rhs /**< Right-hand-side operand */;
|
||||
operand m_dst /**< Destination operand */;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
return os << m_dst << " = " << m_lhs << ' ' << m_type << ' ' << m_rhs;
|
||||
}
|
||||
};
|
||||
|
||||
using block_index = std::uint64_t; /**< IR block index alias */
|
||||
|
||||
/**
|
||||
* @brief Branch instruction
|
||||
*/
|
||||
class branch_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new branch instruction.
|
||||
*
|
||||
* @param block Destination block index.
|
||||
*/
|
||||
branch_instruction(block_index block)
|
||||
: m_block(block) {}
|
||||
|
||||
~branch_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
branch_instruction(branch_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
branch_instruction& operator=(branch_instruction&&) = default;
|
||||
|
||||
branch_instruction(const branch_instruction&) = delete;
|
||||
|
||||
branch_instruction& operator=(const branch_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Branch.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Branch; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination block index.
|
||||
*
|
||||
* @return The destination block index.
|
||||
*/
|
||||
block_index block() const { return m_block; }
|
||||
private:
|
||||
block_index m_block; /**< Destination block index. */
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override { return os << "branch #" << m_block; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Conditional branch instruction
|
||||
*/
|
||||
class branch_cond_instruction final : public instruction {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new conditional branch instruction.
|
||||
*
|
||||
* @param condition Condition operand.
|
||||
* @param ifBlock Destination block index.
|
||||
* @param elseBlock Else block index.
|
||||
*/
|
||||
branch_cond_instruction(operand&& condition, block_index ifBlock, block_index elseBlock)
|
||||
: m_condition(std::move(condition)), m_ifBlock(ifBlock), m_elseBlock(elseBlock) {}
|
||||
|
||||
~branch_cond_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
branch_cond_instruction(branch_cond_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
branch_cond_instruction& operator=(branch_cond_instruction&&) = default;
|
||||
|
||||
branch_cond_instruction(const branch_cond_instruction&) = delete;
|
||||
|
||||
branch_cond_instruction& operator=(const branch_cond_instruction&) = delete;
|
||||
public:
|
||||
instruction_t type() const override { return instruction_t::BranchCond; }
|
||||
|
||||
std::vector<operand*> sources() override { return { &m_condition }; }
|
||||
std::vector<const operand*> sources() const override { return { &m_condition }; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's condition operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const operand& condition() const { return m_condition; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination block index.
|
||||
*
|
||||
* @return The destination block index.
|
||||
*/
|
||||
block_index if_block() const { return m_ifBlock; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's else block index.
|
||||
*
|
||||
* @return The else block index.
|
||||
*/
|
||||
block_index else_block() const { return m_elseBlock; }
|
||||
private:
|
||||
operand m_condition /**< Condition operand. */;
|
||||
block_index m_ifBlock /**< Destination block index. */;
|
||||
block_index m_elseBlock /**< Else block index. */;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
return os << "branch_cond " << m_condition << ", #" << m_ifBlock << ", #" << m_elseBlock;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return instruction
|
||||
*/
|
||||
class return_instruction final : public instruction {
|
||||
public:
|
||||
return_instruction() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct a new return instruction.
|
||||
*
|
||||
* @param value Return value operand.
|
||||
*/
|
||||
return_instruction(operand&& value)
|
||||
: m_value(std::move(value)) {}
|
||||
|
||||
~return_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
return_instruction(return_instruction&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
return_instruction& operator=(return_instruction&&) = default;
|
||||
|
||||
return_instruction(const return_instruction&) = delete;
|
||||
|
||||
return_instruction& operator=(const return_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Return.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Return; }
|
||||
|
||||
std::vector<operand*> sources() override {
|
||||
if (m_value.has_value()) return { &*m_value };
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
if (m_value.has_value()) return { &*m_value };
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's return value operand.
|
||||
*
|
||||
* @return The operand.
|
||||
*/
|
||||
const std::optional<operand>& value() const { return m_value; }
|
||||
private:
|
||||
std::optional<operand> m_value; /**< The return value operand. */
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
os << "return";
|
||||
if (m_value.has_value()) os << ' ' << m_value.value();
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Phi instruction
|
||||
*
|
||||
* Used to implement the phi node in the SSA graph.
|
||||
*/
|
||||
class phi_instruction final : public instruction {
|
||||
public:
|
||||
phi_instruction(register_operand dst)
|
||||
: m_dst(operand::new_reg(dst)) {}
|
||||
|
||||
~phi_instruction() override = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
phi_instruction(phi_instruction&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
phi_instruction& operator=(phi_instruction&&) noexcept = default;
|
||||
|
||||
phi_instruction(const phi_instruction&) = delete;
|
||||
|
||||
phi_instruction& operator=(const phi_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Phi.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Phi; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
std::vector<const operand*> srcs;
|
||||
srcs.reserve(m_labels.size());
|
||||
for (const auto& [op, _block] : m_labels)
|
||||
srcs.push_back(&op);
|
||||
return srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's labels.
|
||||
*
|
||||
* @return The labels.
|
||||
*/
|
||||
std::vector<std::pair<operand, block_index>>& labels() { return m_labels; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's labels.
|
||||
*
|
||||
* @return The labels.
|
||||
*/
|
||||
const std::vector<std::pair<operand, block_index>>& labels() const { return m_labels; }
|
||||
private:
|
||||
operand m_dst;
|
||||
std::vector<std::pair<operand, block_index>> m_labels;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
os << m_dst << " = phi";
|
||||
bool first = true;
|
||||
for (const auto& pair : m_labels) {
|
||||
if (!first) os << ',';
|
||||
first = false;
|
||||
os << ' ' << pair.second << ": " << pair.first;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Function call instruction.
|
||||
*/
|
||||
class call_instruction final : public instruction {
|
||||
public:
|
||||
template <typename NameFwd, typename ArgsFwd>
|
||||
call_instruction(NameFwd&& name, operand&& dst, ArgsFwd&& args)
|
||||
: m_name(std::forward<NameFwd>(name)), m_dst(std::move(dst)), m_args(std::forward<ArgsFwd>(args)) {}
|
||||
|
||||
~call_instruction() override = default;
|
||||
|
||||
call_instruction(call_instruction&&) noexcept = default;
|
||||
call_instruction& operator=(call_instruction&&) noexcept = default;
|
||||
call_instruction(const call_instruction&) = delete;
|
||||
call_instruction& operator=(const call_instruction&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this instruction's type.
|
||||
*
|
||||
* @return instruction_t::Call.
|
||||
*/
|
||||
instruction_t type() const override { return instruction_t::Call; }
|
||||
|
||||
bool has_destination() const override { return true; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
operand& destination() override { return m_dst; }
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's destination register.
|
||||
*
|
||||
* @return The register.
|
||||
*/
|
||||
const operand& destination() const override { return m_dst; }
|
||||
|
||||
std::vector<const operand*> sources() const override {
|
||||
std::vector<const operand*> srcs;
|
||||
srcs.reserve(m_args.size());
|
||||
for (const auto& op : m_args)
|
||||
srcs.push_back(&op);
|
||||
return srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this instruction's callee name.
|
||||
*
|
||||
* @return The name.
|
||||
*/
|
||||
const std::string& name() const { return m_name; }
|
||||
private:
|
||||
std::string m_name;
|
||||
operand m_dst;
|
||||
std::vector<operand> m_args;
|
||||
protected:
|
||||
std::ostream& print(std::ostream& os) const override {
|
||||
os << m_dst << " = " << m_name << '(';
|
||||
bool first = true;
|
||||
for (std::size_t i = 0; i < m_args.size(); ++i) {
|
||||
if (!first) os << ", ";
|
||||
first = false;
|
||||
}
|
||||
return os << ')';
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_IR_INSTRUCTION_HPP
|
||||
@@ -1,52 +0,0 @@
|
||||
#ifndef FURLANG_IR_MODULE_HPP
|
||||
#define FURLANG_IR_MODULE_HPP
|
||||
|
||||
#include "furlang/ir/function.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
/**
|
||||
* @brief IR module
|
||||
*/
|
||||
class mod {
|
||||
public:
|
||||
using value_type = std::unique_ptr<function>; /**< Value type. */
|
||||
public:
|
||||
mod() = default;
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes and returns a new IR function.
|
||||
*
|
||||
* @param args Arguments to call the constructor with.
|
||||
* @return The new IR function.
|
||||
*/
|
||||
template <typename... Args>
|
||||
value_type& push(Args&&... args) {
|
||||
return m_functions.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns this module's functions.
|
||||
*
|
||||
* @return The functions.
|
||||
*/
|
||||
std::vector<value_type>& functions() { return m_functions; }
|
||||
|
||||
/**
|
||||
* @brief Returns this module's functions.
|
||||
*
|
||||
* @return The functions.
|
||||
*/
|
||||
const std::vector<value_type>& functions() const { return m_functions; }
|
||||
private:
|
||||
std::vector<value_type> m_functions;
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_IR_MODULE_HPP
|
||||
@@ -1,290 +0,0 @@
|
||||
#ifndef FURLANG_IR_OPERAND_HPP
|
||||
#define FURLANG_IR_OPERAND_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace furlang {
|
||||
namespace ir {
|
||||
|
||||
/**
|
||||
* @brief Operand type
|
||||
*/
|
||||
enum class operand_t {
|
||||
None, /**< None */
|
||||
Register, /**< Register */
|
||||
Variable, /**< Variable */
|
||||
Integer, /**< Integer */
|
||||
String, /**< String */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Alias to a register type.
|
||||
*/
|
||||
using register_t = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief Register operand alias.
|
||||
* @see operand_t::Register
|
||||
*/
|
||||
struct register_operand {
|
||||
register_t reg = 0;
|
||||
std::uint32_t ver = 0;
|
||||
|
||||
register_operand() = default;
|
||||
|
||||
register_operand(register_t reg)
|
||||
: reg(reg) {}
|
||||
|
||||
register_operand(register_t reg, std::uint32_t ver)
|
||||
: reg(reg), ver(ver) {}
|
||||
|
||||
operator std::uint32_t&() { return reg; }
|
||||
operator const std::uint32_t&() const { return reg; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const register_operand& op) {
|
||||
return os << op.reg << '_' << op.ver;
|
||||
}
|
||||
|
||||
bool operator==(const register_operand& rhs) const { return reg == rhs.reg && ver == rhs.ver; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Variable operand alias.
|
||||
* @see operand_t::Variable
|
||||
*/
|
||||
using variable_operand = std::string;
|
||||
/**
|
||||
* @brief Integer operand alias.
|
||||
* @see operand_t::Integer
|
||||
*/
|
||||
using integer_operand = std::uint64_t;
|
||||
/**
|
||||
* @brief String operand alias.
|
||||
* @see operand_t::String
|
||||
*/
|
||||
using string_operand = std::string;
|
||||
|
||||
/**
|
||||
* @brief IR operand
|
||||
*/
|
||||
class operand {
|
||||
public:
|
||||
~operand() {
|
||||
if (m_type == operand_t::String) {
|
||||
m_value.string.~basic_string();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
operand(operand&& other) noexcept
|
||||
: m_type(other.m_type) {
|
||||
switch (m_type) {
|
||||
case operand_t::None: break;
|
||||
case operand_t::Register: {
|
||||
m_value.reg = other.m_value.reg;
|
||||
} break;
|
||||
case operand_t::Variable: {
|
||||
new (&m_value.variable) variable_operand(std::move(other.m_value.variable));
|
||||
} break;
|
||||
case operand_t::Integer: {
|
||||
m_value.integer = other.m_value.integer;
|
||||
} break;
|
||||
case operand_t::String: {
|
||||
new (&m_value.string) string_operand(std::move(other.m_value.string));
|
||||
} break;
|
||||
}
|
||||
other.m_value.destroy(other.m_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
operand& operator=(operand&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_type = other.m_type;
|
||||
switch (m_type) {
|
||||
case operand_t::None: break;
|
||||
case operand_t::Register: {
|
||||
m_value.reg = other.m_value.reg;
|
||||
} break;
|
||||
case operand_t::Variable: {
|
||||
new (&m_value.variable) variable_operand(std::move(other.m_value.variable));
|
||||
} break;
|
||||
case operand_t::Integer: {
|
||||
m_value.integer = other.m_value.integer;
|
||||
} break;
|
||||
case operand_t::String: {
|
||||
new (&m_value.string) string_operand(std::move(other.m_value.string));
|
||||
} break;
|
||||
}
|
||||
other.m_value.destroy(other.m_type);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operand(const operand&) = delete;
|
||||
operand& operator=(const operand&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new register operand.
|
||||
*
|
||||
* @param value Value of the new register operand.
|
||||
* @return The register operand.
|
||||
*/
|
||||
static operand new_reg(register_t value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::Register;
|
||||
operand.m_value.reg = { value, 0 };
|
||||
return operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new register operand.
|
||||
*
|
||||
* @param value Value of the new register operand.
|
||||
* @return The register operand.
|
||||
*/
|
||||
static operand new_reg(register_operand value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::Register;
|
||||
operand.m_value.reg = value;
|
||||
return operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new variable operand.
|
||||
*
|
||||
* @param value Value of the new variable operand.
|
||||
* @return The variable operand.
|
||||
*/
|
||||
template <typename T>
|
||||
static operand new_variable(T&& value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::Variable;
|
||||
new (&operand.m_value.variable) variable_operand(std::forward<T>(value));
|
||||
return operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new integer operand.
|
||||
*
|
||||
* @param value Value of the new integer operand.
|
||||
* @return The integer operand.
|
||||
*/
|
||||
static operand new_integer(integer_operand value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::Integer;
|
||||
operand.m_value.integer = value;
|
||||
return operand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new string operand.
|
||||
*
|
||||
* @param value Value of the new string operand.
|
||||
* @return The string operand.
|
||||
*/
|
||||
template <typename T>
|
||||
static operand new_string(T&& value) {
|
||||
operand operand;
|
||||
operand.m_type = operand_t::String;
|
||||
new (&operand.m_value.string) string_operand(std::forward<T>(value));
|
||||
return operand;
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this operand's type.
|
||||
*
|
||||
* @return The operand type.
|
||||
*/
|
||||
operand_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Returns this operand's register value.
|
||||
*
|
||||
* @return The register value.
|
||||
*/
|
||||
register_operand& reg() { return m_value.reg; }
|
||||
|
||||
/**
|
||||
* @brief Returns this operand's register value.
|
||||
*
|
||||
* @return The register value.
|
||||
*/
|
||||
register_operand reg() const { return m_value.reg; }
|
||||
|
||||
/**
|
||||
* @brief Returns this operand's integer value.
|
||||
*
|
||||
* @return The integer value.
|
||||
*/
|
||||
integer_operand integer() const { return m_value.integer; }
|
||||
public:
|
||||
/**
|
||||
* @brief Prints an operand to an output stream.
|
||||
*
|
||||
* @param os Output stream.
|
||||
* @param operand Operand to print.
|
||||
* @return The output stream.
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const operand& operand) {
|
||||
switch (operand.m_type) {
|
||||
case operand_t::None: return os << "none";
|
||||
case operand_t::Register: return os << '%' << operand.m_value.reg;
|
||||
case operand_t::Variable: return os << operand.m_value.variable;
|
||||
case operand_t::Integer: return os << operand.m_value.integer;
|
||||
case operand_t::String: return os << '"' << operand.m_value.string << '"';
|
||||
}
|
||||
return os;
|
||||
}
|
||||
private:
|
||||
operand() = default;
|
||||
private:
|
||||
operand_t m_type = operand_t::None;
|
||||
union value {
|
||||
std::nullptr_t null = nullptr;
|
||||
register_operand reg;
|
||||
variable_operand variable;
|
||||
integer_operand integer;
|
||||
string_operand string;
|
||||
|
||||
void destroy(operand_t type) {
|
||||
switch (type) {
|
||||
case operand_t::String: {
|
||||
string.~basic_string();
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
null = nullptr;
|
||||
}
|
||||
|
||||
value() = default;
|
||||
~value() {}
|
||||
|
||||
value(value&&) noexcept = delete;
|
||||
value& operator=(value&&) noexcept = delete;
|
||||
value(const value&) = delete;
|
||||
value& operator=(const value&) = delete;
|
||||
} m_value;
|
||||
};
|
||||
|
||||
} // namespace ir
|
||||
} // namespace furlang
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<furlang::ir::register_operand> {
|
||||
std::size_t operator()(const furlang::ir::register_operand& op) const noexcept {
|
||||
std::size_t h1 = std::hash<decltype(op.reg)>{}(op.reg);
|
||||
std::size_t h2 = std::hash<std::uint32_t>{}(op.ver);
|
||||
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // FURLANG_IR_OPERAND_HPP
|
||||
@@ -1,76 +0,0 @@
|
||||
#ifndef FURVM_CONSTANT_HPP
|
||||
#define FURVM_CONSTANT_HPP
|
||||
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
enum class constant_t : std::uint8_t {
|
||||
String, /**< String constant. */
|
||||
};
|
||||
|
||||
class constant {
|
||||
public:
|
||||
using string_type = std::string_view; /**< String constant type. */
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new string constant.
|
||||
*
|
||||
* @param string String.
|
||||
*/
|
||||
constant(string_type string)
|
||||
: m_type(constant_t::String), m_value(string) {}
|
||||
|
||||
~constant() = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
constant(constant&&) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
constant& operator=(constant&&) = default;
|
||||
|
||||
constant(const constant&) = delete;
|
||||
constant& operator=(const constant&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns this constant's type.
|
||||
* @see constant_t
|
||||
*
|
||||
* @return The constant type.
|
||||
*/
|
||||
constexpr constant_t type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Returns this constant's string value.
|
||||
* @throws bad_constant_access if this constant's type is not constant_t::String.
|
||||
*
|
||||
* @return The string value.
|
||||
*/
|
||||
constexpr string_type string() const {
|
||||
require_type(constant_t::String);
|
||||
return m_value.string;
|
||||
}
|
||||
private:
|
||||
void require_type(constant_t type) const {
|
||||
if (m_type != type) throw bad_constant_access();
|
||||
}
|
||||
private:
|
||||
constant_t m_type{};
|
||||
union value {
|
||||
string_type string;
|
||||
|
||||
value(string_type sv)
|
||||
: string(sv) {}
|
||||
} m_value;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_CONSTANT_HPP
|
||||
@@ -1,142 +0,0 @@
|
||||
#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>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
class context : public handle_container<mod_h> {
|
||||
public:
|
||||
friend class executor;
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a context.
|
||||
*/
|
||||
context()
|
||||
: m_thingAllocator(m_thingArena) {}
|
||||
|
||||
~context() = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
context(context&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
context& operator=(context&&) noexcept = default;
|
||||
|
||||
context(const context&) = delete;
|
||||
context& operator=(const context&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces an executor in the context.
|
||||
*
|
||||
* @param args Arguments forwarded to executor's constructor.
|
||||
* @return A handle to the emplaced executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto emplace_executor(Args&&... args) {
|
||||
return m_executors.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto executor_at(Args&&... args) {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto executor_at(Args&&... args) const {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases an executor from the context.
|
||||
*
|
||||
* @param args Id of the executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
void erase_executor(Args&&... args) {
|
||||
m_executors.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
public:
|
||||
template <typename... Args>
|
||||
auto emplace_thing(Args&&... args) {
|
||||
return m_things.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
* @return A handle to the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto thing_at(Args&&... args) {
|
||||
return m_things.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
* @return A handle to the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto thing_at(Args&&... args) const {
|
||||
return m_things.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases a thing from the context.
|
||||
*
|
||||
* @param args Id of the thing.
|
||||
*/
|
||||
template <typename... Args>
|
||||
void erase_thing(Args&&... args) {
|
||||
m_things.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns context's thing allocator.
|
||||
*
|
||||
* @return The thing allocator.
|
||||
*/
|
||||
thing_allocator<std::byte> thing_alloc() const { return m_thingAllocator; }
|
||||
|
||||
thing_type_store& tt_store() { return m_thingTypeStore; }
|
||||
private:
|
||||
handle_container<mod_h> m_modules;
|
||||
handle_container<thing_h> m_things;
|
||||
handle_container<executor_h> m_executors;
|
||||
|
||||
furlang::arena m_thingArena;
|
||||
thing_allocator<std::byte> m_thingAllocator;
|
||||
class thing_type_store m_thingTypeStore;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_CONTEXT_HPP
|
||||
@@ -1,221 +0,0 @@
|
||||
#ifndef FURVM_INSTRUCTION_HPP
|
||||
#define FURVM_INSTRUCTION_HPP
|
||||
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
namespace furvm {
|
||||
|
||||
enum class instruction_t : byte {
|
||||
/**
|
||||
* @brief No operation.
|
||||
*/
|
||||
NoOperation = 0,
|
||||
|
||||
/**
|
||||
* @brief Pushes an s8 integer from a byte onto the stack.
|
||||
*/
|
||||
PushS8,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u8 integer from a byte onto the stack.
|
||||
*/
|
||||
PushU8,
|
||||
|
||||
/**
|
||||
* @brief Pushes an s16 integer from two byte onto the stack.
|
||||
*/
|
||||
PushS16,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u16 integer from two byte onto the stack.
|
||||
*/
|
||||
PushU16,
|
||||
|
||||
/**
|
||||
* @brief Pushes an s32 integer from a byte onto the stack.
|
||||
*/
|
||||
PushS32,
|
||||
|
||||
/**
|
||||
* @brief Pushes an u32 integer from a byte onto the stack.
|
||||
*/
|
||||
PushU32,
|
||||
|
||||
/**
|
||||
* @brief Pushes a constant onto the stack.
|
||||
*
|
||||
* Pushes a constant from the constant pool denoted by two next bytes in little-endian onto the stack.
|
||||
*/
|
||||
PushConstant,
|
||||
|
||||
/**
|
||||
* @brief Pushes a new array onto the stack.
|
||||
*
|
||||
* Type is the next 4 bytes in little-endian.
|
||||
* If the type is dynamic the array's size will be popped off of the stack.
|
||||
*/
|
||||
Array,
|
||||
|
||||
/**
|
||||
* @brief Pushes an element from an array onto the stack.
|
||||
*/
|
||||
Get,
|
||||
|
||||
/**
|
||||
* @brief Sets an array element.
|
||||
*/
|
||||
Set,
|
||||
|
||||
/**
|
||||
* @brief Pops top element from the stack.
|
||||
*/
|
||||
Drop,
|
||||
|
||||
/**
|
||||
* @brief Duplicates top element on the stack.
|
||||
*/
|
||||
Duplicate,
|
||||
|
||||
/**
|
||||
* @brief Swaps two top elements of the stack.
|
||||
*/
|
||||
Swap,
|
||||
|
||||
/**
|
||||
* @brief Clones top element on the stack.
|
||||
*/
|
||||
Clone,
|
||||
|
||||
/**
|
||||
* @brief Pushes a new reference onto the stack.
|
||||
*
|
||||
* Pops the top thing from the stack and pushes its reference.
|
||||
*/
|
||||
Reference,
|
||||
|
||||
/**
|
||||
* @brief Adds two things together on the stack.
|
||||
*/
|
||||
Add,
|
||||
|
||||
/**
|
||||
* @brief Subtracts two things together on the stack.
|
||||
*/
|
||||
Sub,
|
||||
|
||||
/**
|
||||
* @brief Multiplies two things together on the stack.
|
||||
*/
|
||||
Mul,
|
||||
|
||||
/**
|
||||
* @brief Divides two things together on the stack.
|
||||
*/
|
||||
Div,
|
||||
|
||||
/**
|
||||
* @brief Modulos two things together on the stack.
|
||||
*/
|
||||
Mod,
|
||||
|
||||
/**
|
||||
* @brief Compares two top-most things from the stack for equality.
|
||||
*/
|
||||
Equals,
|
||||
|
||||
/**
|
||||
* @brief Compares two top-most things from the stack for inequality.
|
||||
*/
|
||||
NotEquals,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is less than the second top-most thing.
|
||||
*/
|
||||
LessThan,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is greater than the second top-most thing.
|
||||
*/
|
||||
GreaterThan,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is less than or equal to the second top-most thing.
|
||||
*/
|
||||
LessEqual,
|
||||
|
||||
/**
|
||||
* @brief Compares if the first top-most thing is greater than or equal to the second top-most thing.
|
||||
*/
|
||||
GreaterEqual,
|
||||
|
||||
/**
|
||||
* @brief Pushes a pointer of popped-off thing onto the stack.
|
||||
*/
|
||||
Pointerof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a size of popped-off thing onto the stack.
|
||||
*/
|
||||
Sizeof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a length of popped-off thing onto the stack.
|
||||
*/
|
||||
Lengthof,
|
||||
|
||||
/**
|
||||
* @brief Pushes a variable onto the stack.
|
||||
*
|
||||
* Fetches a variable denoted by next two bytes in little-endian and pushes it onto the stack.
|
||||
*/
|
||||
Load,
|
||||
|
||||
/**
|
||||
* @brief Stores an element from the stack in a variable.
|
||||
*
|
||||
* Pops a thing from the stack and stores it in a variable denoted by next two bytes in little-endian.
|
||||
*/
|
||||
Store,
|
||||
|
||||
/**
|
||||
* @brief Calls a function.
|
||||
*
|
||||
* Calls a function denoted by next two bytes in little-endian from current frame's module.
|
||||
*/
|
||||
Call,
|
||||
|
||||
/**
|
||||
* @brief Jumps to an instruction relative to the current instruction.
|
||||
*
|
||||
* Jumps to an instruction relative to the current instruction with offset denoted by next byte.
|
||||
*/
|
||||
Jump,
|
||||
|
||||
/**
|
||||
* @brief Jumps to an instruction relative to the current instruction if top thing on the stack is not zero.
|
||||
*
|
||||
* Jumps to an instruction relative to the current instruction with offset denoted by next byte if the top thing on
|
||||
* the stack is not zero (is true).
|
||||
*/
|
||||
JumpNotZero,
|
||||
|
||||
/**
|
||||
* @brief Pops the current call frame.
|
||||
*/
|
||||
Return,
|
||||
};
|
||||
|
||||
struct instruction {
|
||||
instruction_t type; /**< Instruction type. */
|
||||
|
||||
/**
|
||||
* @brief Instruction value.
|
||||
*/
|
||||
union value {
|
||||
constant_index constant; /**< Constant instruction argument. */
|
||||
} value; /**< Instruction value. */
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_INSTRUCTION_HPP
|
||||
@@ -1,738 +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;
|
||||
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:
|
||||
/**
|
||||
* @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_data = other.m_data;
|
||||
m_allocator = std::move(other.m_allocator);
|
||||
other.m_type.type = thing_type::Count;
|
||||
other.m_data = nullptr;
|
||||
other.m_size = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
thing(const thing&) = delete;
|
||||
thing& operator=(const thing&) = delete;
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a clone of the thing.
|
||||
*
|
||||
* @return A clone of this thing.
|
||||
*/
|
||||
thing clone() const {
|
||||
thing res(m_type, m_allocator);
|
||||
switch (m_type.type) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64:
|
||||
case thing_type::Ptr: std::memcpy(res.m_data, m_data, m_size); return std::move(res);
|
||||
case thing_type::Array: copy_list(m_type, res.m_data, m_data); return std::move(res);
|
||||
case thing_type::Ref: throw std::runtime_error("cannot clone references");
|
||||
case thing_type::Count: break;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns 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(*m_type.value.array.type);
|
||||
if (m_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, m_type.value.array.type }, m_allocator };
|
||||
ref.m_data = array.data + (index * elementSize);
|
||||
return ref;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= m_type.value.array.size) throw std::out_of_range("index out of range");
|
||||
thing ref = { { thing_type::Ref, m_type.value.array.type }, m_allocator };
|
||||
ref.m_data = 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;
|
||||
std::byte* m_data;
|
||||
|
||||
allocator_type m_allocator;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_THING_HPP
|
||||
@@ -1,111 +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:
|
||||
/**
|
||||
* @brief Constructs a thing allocator.
|
||||
*
|
||||
* @param arena Base arena allocator.
|
||||
*/
|
||||
explicit thing_allocator(furlang::arena& arena) noexcept
|
||||
: m_arena(&arena), m_deadThings(std::make_shared<dead_things>()) {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator(thing_allocator<U>&& other) noexcept
|
||||
: m_arena(std::move(other.m_arena)), m_deadThings(std::move(other.m_deadThings)) {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator& operator=(thing_allocator<U>&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = std::move(other.m_arena);
|
||||
m_deadThings = std::move(other.m_deadThings);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator(const thing_allocator<U>& other) noexcept
|
||||
: m_arena(other.m_arena), m_deadThings(other.m_deadThings) {}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
template <typename U>
|
||||
thing_allocator& operator=(const thing_allocator<U>& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
m_arena = other.m_arena;
|
||||
m_deadThings = other.m_deadThings;
|
||||
return *this;
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a free chunk of memory.
|
||||
*
|
||||
* @param count Count of the things that must fit inside the chunk.
|
||||
* @return The chunk.
|
||||
*/
|
||||
[[nodiscard]] T* allocate(std::size_t count = 1) {
|
||||
for (auto it = m_deadThings->begin(); it != m_deadThings->end(); ++it) {
|
||||
if (it->second != count) continue;
|
||||
T* data = it->first;
|
||||
m_deadThings->erase(it);
|
||||
return data;
|
||||
}
|
||||
return m_arena->allocate<T>(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recycles the pointer.
|
||||
*/
|
||||
void deallocate(T* ptr, std::size_t count) noexcept { m_deadThings->emplace_back(ptr, count); }
|
||||
public:
|
||||
/**
|
||||
* @brief Compares two thing allocators for equality.
|
||||
*
|
||||
* @return true if the two things are equal.
|
||||
*/
|
||||
template <typename U>
|
||||
bool operator==(const thing_allocator<U>& other) const noexcept {
|
||||
return m_arena == other.m_arena && m_deadThings == other.m_deadThings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two thing allocators for inequality.
|
||||
*
|
||||
* @return true if the two things are not equal.
|
||||
*/
|
||||
template <typename U>
|
||||
bool operator!=(const thing_allocator<U>& other) const noexcept {
|
||||
return m_arena != other.m_arena || m_deadThings != other.m_deadThings;
|
||||
}
|
||||
private:
|
||||
furlang::arena* m_arena;
|
||||
|
||||
std::shared_ptr<dead_things> m_deadThings;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_THING_ALLOCATOR_HPP
|
||||
@@ -1,342 +0,0 @@
|
||||
#include "furvm/executor.hpp"
|
||||
|
||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/function.hpp" // IWYU pragma: keep
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
thing_type executor::thing_type_impl(mod_h mod, mod_type type) const {
|
||||
while (type.type == mod_type::Import) {
|
||||
auto imprt = std::move(type.value.imprt);
|
||||
mod = m_context->at(imprt.modId);
|
||||
type = *mod->type_at(imprt.typeId);
|
||||
}
|
||||
switch (static_cast<enum thing_type::type>(type.type)) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64: return { static_cast<enum thing_type::type>(type.type) };
|
||||
case thing_type::Ptr: return { thing_type::Ptr, 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");
|
||||
}
|
||||
}
|
||||
|
||||
thing_type* executor::mod_to_thing_type(const mod_h& mod, const mod_type& type) const {
|
||||
struct thing_type thingType = thing_type_impl(mod, type);
|
||||
return m_context->tt_store().insert(thingType);
|
||||
}
|
||||
|
||||
void executor::push_frame(const mod_h& mod, function function) {
|
||||
mod_h modInst = mod;
|
||||
while (function.type() == function_t::Import) {
|
||||
modInst = m_context->at(function.imp().mod);
|
||||
function = *modInst->function_at(function.imp().function);
|
||||
}
|
||||
|
||||
auto signature = function.signature();
|
||||
std::vector<thing_h> args;
|
||||
args.reserve(signature.params.size());
|
||||
for (const auto& param : signature.params) {
|
||||
auto arg = pop_thing();
|
||||
if (arg->type() != *mod_to_thing_type(mod, *param)) throw std::runtime_error("function argument type mismatch");
|
||||
args.push_back(std::move(arg));
|
||||
}
|
||||
|
||||
struct thing_type* returnType = nullptr;
|
||||
if (function.signature().returnType.has_value())
|
||||
returnType = mod_to_thing_type(mod, *function.signature().returnType.value()); // NOLINT
|
||||
|
||||
switch (function.type()) {
|
||||
case function_t::Normal: {
|
||||
m_frames.emplace(
|
||||
(struct executor::frame){ mod, function.position(), m_stack.size(), returnType, std::move(args) });
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), returnType, std::move(args) });
|
||||
modInst->get_native_function(function.native())(*this);
|
||||
pop_frame();
|
||||
} break;
|
||||
default: throw std::runtime_error("unexpected function type");
|
||||
}
|
||||
}
|
||||
|
||||
struct executor::frame executor::pop_frame() {
|
||||
if (m_frames.empty()) throw stack_underflow();
|
||||
struct executor::frame frame = m_frames.top();
|
||||
m_frames.pop();
|
||||
thing_h returnValue;
|
||||
if (frame.returnType != nullptr) returnValue = pop_thing();
|
||||
if (m_stack.size() != frame.stackBase) throw std::runtime_error("unexhausted stack");
|
||||
if (frame.returnType != nullptr) push_thing(std::move(returnValue));
|
||||
return frame;
|
||||
}
|
||||
|
||||
struct executor::frame executor::frame() const {
|
||||
return m_frames.top();
|
||||
}
|
||||
|
||||
thing_h executor::push_thing(::furvm::thing<>&& thing) {
|
||||
return m_stack.emplace(m_context->emplace_thing(std::move(thing)));
|
||||
}
|
||||
|
||||
thing_h executor::pop_thing() {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
thing_h top = std::move(m_stack.top());
|
||||
m_stack.pop();
|
||||
return std::move(top);
|
||||
}
|
||||
|
||||
thing_h executor::thing() const {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
return m_stack.top();
|
||||
}
|
||||
|
||||
void executor::store_thing(variable_t variable, const thing_h& 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_h&& thing) {
|
||||
auto& frame = m_frames.top();
|
||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||
frame.variables[variable] = std::move(thing);
|
||||
}
|
||||
|
||||
thing_h executor::load_thing(variable_t variable) const {
|
||||
const auto& frame = m_frames.top();
|
||||
return { frame.variables[variable] };
|
||||
}
|
||||
|
||||
void executor::step() {
|
||||
if ((m_flags & executor_flags::Suspended) == executor_flags::Suspended) return;
|
||||
|
||||
struct frame& frame = m_frames.top();
|
||||
|
||||
instruction_t instr = static_cast<instruction_t>((*frame.mod).byte_at(frame.position++));
|
||||
switch (instr) {
|
||||
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>() =
|
||||
static_cast<thing_type::s8>(frame.mod->byte_at(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushU8: {
|
||||
push_thing({ (struct thing_type){ thing_type::U8 }, m_context->thing_alloc() })->get<thing_type::u8>() =
|
||||
static_cast<thing_type::u8>(frame.mod->byte_at(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushS16: {
|
||||
thing_type::u16 value = frame.mod->byte_at(frame.position++);
|
||||
value |= static_cast<thing_type::u16>(frame.mod->byte_at(frame.position++) << 8);
|
||||
push_thing({ (struct thing_type){ thing_type::S16 }, m_context->thing_alloc() })->get<thing_type::s16>() =
|
||||
static_cast<thing_type::s16>(value);
|
||||
} break;
|
||||
case instruction_t::PushU16: {
|
||||
thing_type::u16 value = frame.mod->byte_at(frame.position++);
|
||||
value |= static_cast<thing_type::u16>(frame.mod->byte_at(frame.position++) << 8);
|
||||
push_thing({ (struct thing_type){ thing_type::U16 }, m_context->thing_alloc() })->get<thing_type::u16>() =
|
||||
value;
|
||||
} break;
|
||||
case instruction_t::PushS32: {
|
||||
push_thing({ (struct thing_type){ thing_type::S32 }, m_context->thing_alloc() })->get<thing_type::s32>() =
|
||||
static_cast<thing_type::s32>(frame.mod->byte_at(frame.position++));
|
||||
} break;
|
||||
case instruction_t::PushU32: {
|
||||
push_thing({ (struct thing_type){ thing_type::U32 }, m_context->thing_alloc() })->get<thing_type::u32>() =
|
||||
static_cast<thing_type::u32>(frame.mod->byte_at(frame.position++));
|
||||
} break;
|
||||
case instruction_t::Array: {
|
||||
mod_type_id typeId = static_cast<mod_type_id>(frame.mod->byte_at(frame.position)) |
|
||||
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 1)) << 8) |
|
||||
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 2)) << 16) |
|
||||
(static_cast<mod_type_id>(frame.mod->byte_at(frame.position + 3)) << 24);
|
||||
frame.position += 4;
|
||||
|
||||
const auto& type = *mod_to_thing_type(frame.mod, *frame.mod->type_at(typeId));
|
||||
if (type.type != thing_type::Array || type.value.array.type == nullptr || type.value.array.type == &type)
|
||||
throw std::runtime_error("invalid array type");
|
||||
|
||||
auto array = push_thing({ type, m_context->thing_alloc() });
|
||||
|
||||
if (type.value.array.size == 0) {
|
||||
auto sizeThing = pop_thing();
|
||||
std::int64_t size = sizeThing->integer();
|
||||
array->resize(size);
|
||||
}
|
||||
} break;
|
||||
case instruction_t::Get: {
|
||||
auto index = pop_thing();
|
||||
auto array = pop_thing();
|
||||
push_thing(array->at(index->integer()));
|
||||
} break;
|
||||
case instruction_t::Set: {
|
||||
auto element = pop_thing();
|
||||
auto index = pop_thing();
|
||||
auto array = pop_thing();
|
||||
array->at(index->integer()).assign(std::move(element->clone()));
|
||||
} break;
|
||||
case instruction_t::Drop: {
|
||||
pop_thing();
|
||||
} break;
|
||||
case instruction_t::Duplicate: {
|
||||
push_thing(thing());
|
||||
} break;
|
||||
case instruction_t::Swap: {
|
||||
auto thing1 = pop_thing();
|
||||
auto thing2 = pop_thing();
|
||||
push_thing(std::move(thing1));
|
||||
push_thing(std::move(thing2));
|
||||
} break;
|
||||
case instruction_t::Clone: {
|
||||
push_thing(std::move(thing()->clone()));
|
||||
} break;
|
||||
case instruction_t::Reference: {
|
||||
auto thing = pop_thing();
|
||||
push_thing({ (struct thing_type){ thing_type::Ref, m_context->tt_store().insert(thing->type()) },
|
||||
m_context->thing_alloc() })
|
||||
->reference(*thing);
|
||||
} break;
|
||||
case instruction_t::Add: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->add(*rhs));
|
||||
} break;
|
||||
case instruction_t::Sub: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->sub(*rhs));
|
||||
} break;
|
||||
case instruction_t::Mul: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->mul(*rhs));
|
||||
} break;
|
||||
case instruction_t::Div: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->div(*rhs));
|
||||
} break;
|
||||
case instruction_t::Mod: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->mod(*rhs));
|
||||
} break;
|
||||
case instruction_t::Equals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::NotEquals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->not_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::LessThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->less_than(*rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->greater_than(*rhs));
|
||||
} break;
|
||||
case instruction_t::LessEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->less_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs->greater_equals(*rhs));
|
||||
} break;
|
||||
case instruction_t::Pointerof: {
|
||||
auto thing = pop_thing();
|
||||
auto ptr = push_thing({ (struct thing_type){ thing_type::Ptr, m_context->tt_store().at(thing->type().id) },
|
||||
m_context->thing_alloc() });
|
||||
ptr->get<void*>() = thing->raw();
|
||||
} break;
|
||||
case instruction_t::Sizeof: {
|
||||
auto thing = pop_thing();
|
||||
auto size = push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() });
|
||||
switch (thing->type().type) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64:
|
||||
size->get<thing_type::u64>() = static_cast<thing_type::u64>(thing_type::primitive_size(thing->type().type));
|
||||
break;
|
||||
case thing_type::Ptr: size->get<thing_type::u64>() = static_cast<thing_type::u64>(sizeof(void*)); break;
|
||||
case thing_type::Array:
|
||||
/* TODO: Return actual memory size of the array
|
||||
* By the memory size I mean the length times sizeof single element.
|
||||
*/
|
||||
size->get<thing_type::u64>() = thing->length();
|
||||
break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
} break;
|
||||
case instruction_t::Lengthof: {
|
||||
auto thing = pop_thing();
|
||||
auto length = push_thing({ (struct thing_type){ thing_type::U64 }, m_context->thing_alloc() });
|
||||
length->get<thing_type::u64>() = thing->length();
|
||||
} break;
|
||||
case instruction_t::Load: {
|
||||
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
push_thing(load_thing(variable));
|
||||
} break;
|
||||
case instruction_t::Store: {
|
||||
variable_t variable = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
store_thing(variable, std::move(pop_thing()));
|
||||
} break;
|
||||
case instruction_t::Call: {
|
||||
function_id funcId = static_cast<std::uint16_t>(frame.mod->byte_at(frame.position)) |
|
||||
(static_cast<std::uint16_t>(frame.mod->byte_at(frame.position + 1)) << 8);
|
||||
frame.position += 2;
|
||||
push_frame(frame.mod, *frame.mod->function_at(funcId));
|
||||
} break;
|
||||
case instruction_t::Jump: {
|
||||
std::int8_t offset = static_cast<std::int8_t>(frame.mod->byte_at(frame.position++));
|
||||
frame.position += offset;
|
||||
} break;
|
||||
case instruction_t::JumpNotZero: {
|
||||
byte offset = frame.mod->byte_at(frame.position++);
|
||||
auto cond = pop_thing();
|
||||
if (cond->integer() != 0) frame.position += (std::int8_t)offset;
|
||||
} break;
|
||||
case instruction_t::Return: {
|
||||
pop_frame();
|
||||
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
|
||||
} break;
|
||||
case instruction_t::PushConstant: throw std::runtime_error("unimplemented");
|
||||
default: throw std::runtime_error("unknown instruction");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
@@ -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:
|
||||
@@ -23,16 +23,18 @@ struct token {
|
||||
Colon, /**< `:` */
|
||||
|
||||
// Keywords
|
||||
Func, /**< `func` keyword for defining functions. */
|
||||
Type, /**< `type` keyword for defining types. */
|
||||
Native, /**< `native` keyword for native functions. :v: */
|
||||
Import, /**< `import` keyword for importing functions and types. */
|
||||
Public, /**< `public` access specifier. */
|
||||
Private, /**< `private` access specifier. */
|
||||
Func, /**< `func` keyword for defining functions. */
|
||||
Type, /**< `type` keyword for defining types. */
|
||||
Native, /**< `native` keyword for native functions. :v: */
|
||||
Import, /**< `import` keyword for importing functions and types. */
|
||||
Public, /**< `public` access specifier. */
|
||||
Private, /**< `private` access specifier. */
|
||||
Allocate, /**< `allocate` keyword for global variables. */
|
||||
|
||||
// Instructions
|
||||
Push,
|
||||
Array,
|
||||
Slice,
|
||||
Get,
|
||||
Set,
|
||||
Drop,
|
||||
@@ -56,6 +58,8 @@ struct token {
|
||||
Lenof,
|
||||
Load,
|
||||
Store,
|
||||
LoadGlobal,
|
||||
StoreGlobal,
|
||||
Call,
|
||||
Jmp,
|
||||
Jnz,
|
||||
@@ -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
|
||||
@@ -0,0 +1,346 @@
|
||||
#ifndef FURC_FRONT_AST_HPP
|
||||
#define FURC_FRONT_AST_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
|
||||
struct comp_stmt_node;
|
||||
struct if_stmt_node;
|
||||
struct while_stmt_node;
|
||||
struct return_stmt_node;
|
||||
struct var_decl_node;
|
||||
struct func_decl_node;
|
||||
struct var_read_expr_node;
|
||||
struct func_call_expr_node;
|
||||
struct group_expr_node;
|
||||
struct binary_op_expr_node;
|
||||
struct unary_op_expr_node;
|
||||
struct if_expr_node;
|
||||
struct int_lit_node;
|
||||
struct char_lit_node;
|
||||
|
||||
struct ast_visitor {
|
||||
ast_visitor() = default;
|
||||
virtual ~ast_visitor() = default;
|
||||
|
||||
ast_visitor(ast_visitor&&) noexcept = default;
|
||||
ast_visitor& operator=(ast_visitor&&) noexcept = default;
|
||||
|
||||
ast_visitor(const ast_visitor&) = default;
|
||||
ast_visitor& operator=(const ast_visitor&) = default;
|
||||
|
||||
virtual void visit_comp_stmt_node(const comp_stmt_node& node) {}
|
||||
virtual void visit_if_stmt_node(const if_stmt_node& node) {}
|
||||
virtual void visit_while_stmt_node(const while_stmt_node& node) {}
|
||||
virtual void visit_return_stmt_node(const return_stmt_node& node) {}
|
||||
|
||||
virtual void visit_var_decl_node(const var_decl_node& node) {}
|
||||
virtual void visit_func_decl_node(const func_decl_node& node) {}
|
||||
|
||||
virtual void visit_var_read_expr_node(const var_read_expr_node& node) {}
|
||||
virtual void visit_func_call_expr_node(const func_call_expr_node& node) {}
|
||||
virtual void visit_group_expr_node(const group_expr_node& node) {}
|
||||
virtual void visit_binary_op_expr_node(const binary_op_expr_node& node) {}
|
||||
virtual void visit_unary_op_expr_node(const unary_op_expr_node& node) {}
|
||||
virtual void visit_if_expr_node(const if_expr_node& node) {}
|
||||
|
||||
virtual void visit_int_lit_node(const int_lit_node& node) {}
|
||||
virtual void visit_char_lit_node(const char_lit_node& node) {}
|
||||
};
|
||||
|
||||
struct ast_type {
|
||||
enum type_e {
|
||||
Void = 0,
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
S32,
|
||||
U32,
|
||||
S64,
|
||||
U64,
|
||||
} type = Void;
|
||||
};
|
||||
|
||||
class ast_node {
|
||||
public:
|
||||
enum category_e {
|
||||
Statement,
|
||||
Declaration,
|
||||
Expression,
|
||||
Literal,
|
||||
};
|
||||
public:
|
||||
ast_node() = default;
|
||||
virtual ~ast_node() = default;
|
||||
|
||||
ast_node(ast_node&&) noexcept = default;
|
||||
ast_node& operator=(ast_node&&) noexcept = default;
|
||||
|
||||
ast_node(const ast_node&) = delete;
|
||||
ast_node& operator=(const ast_node&) = delete;
|
||||
public:
|
||||
virtual category_e category() const = 0;
|
||||
|
||||
virtual void accept(ast_visitor& visitor) const = 0;
|
||||
};
|
||||
|
||||
using ast_node_cat = ast_node::category_e;
|
||||
|
||||
class stmt_node : public ast_node {
|
||||
public:
|
||||
enum stmt_type_e {
|
||||
Declaration = 0,
|
||||
Expression,
|
||||
|
||||
Compound,
|
||||
If,
|
||||
While,
|
||||
Return,
|
||||
};
|
||||
public:
|
||||
category_e category() const override { return ast_node_cat::Statement; }
|
||||
|
||||
virtual stmt_type_e stmt_type() const = 0;
|
||||
};
|
||||
|
||||
struct comp_stmt_node final : public stmt_node {
|
||||
stmt_type_e stmt_type() const override { return Compound; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_comp_stmt_node(*this); }
|
||||
|
||||
std::vector<stmt_node*> stmts;
|
||||
};
|
||||
|
||||
class expr_node;
|
||||
|
||||
struct if_stmt_node final : public stmt_node {
|
||||
stmt_type_e stmt_type() const override { return If; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_if_stmt_node(*this); }
|
||||
|
||||
expr_node* cond = nullptr;
|
||||
stmt_node* thenBranch = nullptr;
|
||||
stmt_node* elseBranch = nullptr;
|
||||
};
|
||||
|
||||
struct while_stmt_node final : public stmt_node {
|
||||
stmt_type_e stmt_type() const override { return While; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_while_stmt_node(*this); }
|
||||
|
||||
expr_node* cond = nullptr;
|
||||
stmt_node* body = nullptr;
|
||||
};
|
||||
|
||||
struct return_stmt_node final : public stmt_node {
|
||||
stmt_type_e stmt_type() const override { return Return; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_return_stmt_node(*this); }
|
||||
|
||||
expr_node* value = nullptr;
|
||||
};
|
||||
|
||||
class decl_node : public stmt_node {
|
||||
public:
|
||||
enum decl_type_e {
|
||||
Variable,
|
||||
Function
|
||||
};
|
||||
public:
|
||||
category_e category() const override { return ast_node_cat::Declaration; }
|
||||
|
||||
stmt_type_e stmt_type() const override { return Declaration; }
|
||||
|
||||
virtual decl_type_e decl_type() const = 0;
|
||||
};
|
||||
|
||||
struct var_decl_node final : public decl_node {
|
||||
decl_type_e decl_type() const override { return Variable; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_var_decl_node(*this); }
|
||||
|
||||
std::string name;
|
||||
ast_type type;
|
||||
expr_node* init = nullptr;
|
||||
};
|
||||
|
||||
struct func_decl_node final : public decl_node {
|
||||
decl_type_e decl_type() const override { return Function; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_func_decl_node(*this); }
|
||||
|
||||
struct def_s {
|
||||
comp_stmt_node body;
|
||||
std::vector<expr_node*> preConds;
|
||||
std::vector<expr_node*> postConds;
|
||||
};
|
||||
|
||||
std::string name;
|
||||
ast_type type;
|
||||
std::vector<var_decl_node> params;
|
||||
std::optional<def_s> def;
|
||||
};
|
||||
|
||||
class expr_node : public stmt_node {
|
||||
public:
|
||||
enum expr_type_e {
|
||||
Literal,
|
||||
|
||||
VarRead,
|
||||
FunctionCall,
|
||||
Group,
|
||||
BinaryOp,
|
||||
UnaryOp,
|
||||
If,
|
||||
};
|
||||
public:
|
||||
category_e category() const override { return ast_node_cat::Expression; }
|
||||
|
||||
stmt_type_e stmt_type() const override { return Expression; }
|
||||
|
||||
virtual expr_type_e expr_type() const = 0;
|
||||
};
|
||||
|
||||
struct var_read_expr_node final : public expr_node {
|
||||
expr_type_e expr_type() const override { return VarRead; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_var_read_expr_node(*this); }
|
||||
|
||||
std::string name;
|
||||
|
||||
var_read_expr_node(std::string&& name)
|
||||
: name(std::move(name)) {}
|
||||
};
|
||||
|
||||
struct func_call_expr_node final : public expr_node {
|
||||
expr_type_e expr_type() const override { return FunctionCall; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_func_call_expr_node(*this); }
|
||||
|
||||
expr_node* lhs = nullptr;
|
||||
std::vector<expr_node*> args;
|
||||
};
|
||||
|
||||
struct group_expr_node final : public expr_node {
|
||||
expr_type_e expr_type() const override { return Group; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_group_expr_node(*this); }
|
||||
|
||||
expr_node* inner = nullptr;
|
||||
};
|
||||
|
||||
struct binary_op_expr_node final : public expr_node {
|
||||
enum binary_op_type {
|
||||
Add = 0,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
|
||||
Shl,
|
||||
Shr,
|
||||
BinAnd,
|
||||
BinOr,
|
||||
BinXor,
|
||||
And,
|
||||
Or,
|
||||
|
||||
Equals,
|
||||
NotEquals,
|
||||
LessThan,
|
||||
LessEquals,
|
||||
GreaterThan,
|
||||
GreaterEquals,
|
||||
};
|
||||
|
||||
expr_type_e expr_type() const override { return BinaryOp; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_binary_op_expr_node(*this); }
|
||||
|
||||
expr_node* lhs = nullptr;
|
||||
expr_node* rhs = nullptr;
|
||||
binary_op_type type = Add;
|
||||
};
|
||||
|
||||
struct unary_op_expr_node final : public expr_node {
|
||||
enum unary_op_type {
|
||||
Positive = 0,
|
||||
Negative,
|
||||
PreInc,
|
||||
PreDec,
|
||||
PostInc,
|
||||
PostDec,
|
||||
BinNot,
|
||||
Not,
|
||||
|
||||
Sizeof,
|
||||
Pointerof,
|
||||
Lengthof,
|
||||
};
|
||||
|
||||
expr_type_e expr_type() const override { return UnaryOp; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_unary_op_expr_node(*this); }
|
||||
|
||||
expr_node* lhs = nullptr;
|
||||
unary_op_type type = Positive;
|
||||
};
|
||||
|
||||
struct if_expr_node final : public expr_node {
|
||||
expr_type_e expr_type() const override { return If; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_if_expr_node(*this); }
|
||||
|
||||
expr_node* cond = nullptr;
|
||||
expr_node* thenExpr = nullptr;
|
||||
expr_node* elseExpr = nullptr;
|
||||
};
|
||||
|
||||
class lit_node : public expr_node {
|
||||
public:
|
||||
enum lit_type_e {
|
||||
Integer,
|
||||
Char,
|
||||
};
|
||||
public:
|
||||
category_e category() const override { return ast_node_cat::Literal; }
|
||||
|
||||
expr_type_e expr_type() const override { return Literal; }
|
||||
|
||||
virtual lit_type_e lit_type() const = 0;
|
||||
};
|
||||
|
||||
struct int_lit_node final : public lit_node {
|
||||
int_lit_node(std::uint64_t value)
|
||||
: value(value) {}
|
||||
|
||||
lit_type_e lit_type() const override { return Integer; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_int_lit_node(*this); }
|
||||
|
||||
std::uint64_t value;
|
||||
};
|
||||
|
||||
struct char_lit_node final : public lit_node {
|
||||
char_lit_node(char value)
|
||||
: value(value) {}
|
||||
|
||||
lit_type_e lit_type() const override { return Char; }
|
||||
|
||||
void accept(ast_visitor& visitor) const override { visitor.visit_char_lit_node(*this); }
|
||||
|
||||
char value;
|
||||
};
|
||||
|
||||
struct ast {
|
||||
std::vector<decl_node*> decls;
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_FRONT_AST_HPP
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef FURC_FRONT_LEXER_HPP
|
||||
#define FURC_FRONT_LEXER_HPP
|
||||
|
||||
#include "furc/front/token.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <string_view>
|
||||
|
||||
namespace furc {
|
||||
|
||||
class lexer {
|
||||
public:
|
||||
lexer(std::string_view filepath, std::string_view content)
|
||||
: m_filepath(filepath), m_content(content) {}
|
||||
|
||||
~lexer() = default;
|
||||
|
||||
lexer(lexer&&) noexcept = default;
|
||||
lexer& operator=(lexer&&) noexcept = default;
|
||||
|
||||
lexer(const lexer&) = delete;
|
||||
lexer& operator=(const lexer&) = delete;
|
||||
public:
|
||||
token next_token();
|
||||
token peek_token(std::size_t offset = 0);
|
||||
private:
|
||||
token get_token();
|
||||
|
||||
void next();
|
||||
constexpr char get(std::size_t offset = 0) const;
|
||||
void skip_spaces();
|
||||
|
||||
constexpr token::location location() const;
|
||||
private:
|
||||
std::string_view m_filepath;
|
||||
std::string_view m_content;
|
||||
std::size_t m_cursor = 0;
|
||||
std::size_t m_row = 0;
|
||||
std::size_t m_lineStart = 0;
|
||||
|
||||
std::deque<token> m_peekToken;
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_FRONT_LEXER_HPP
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef FURC_FRONT_PARSER_HPP
|
||||
#define FURC_FRONT_PARSER_HPP
|
||||
|
||||
#include "furc/front/ast.hpp"
|
||||
#include "furc/front/lexer.hpp"
|
||||
#include "furlang/arena.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furc {
|
||||
|
||||
class parser {
|
||||
public:
|
||||
parser(lexer&& lexer, furlang::arena& arena)
|
||||
: m_lexer(std::move(lexer)), m_arena(&arena) {}
|
||||
|
||||
~parser() = default;
|
||||
|
||||
parser(parser&&) noexcept = default;
|
||||
parser& operator=(parser&&) noexcept = default;
|
||||
|
||||
parser(const parser&) = delete;
|
||||
parser& operator=(const parser&) = delete;
|
||||
public:
|
||||
ast parse();
|
||||
private:
|
||||
stmt_node* parse_stmt();
|
||||
decl_node* parse_decl();
|
||||
expr_node* parse_expr();
|
||||
|
||||
ast_type parse_type();
|
||||
comp_stmt_node parse_comp();
|
||||
|
||||
expr_node* parse_expr_primary();
|
||||
expr_node* parse_expr_unary();
|
||||
expr_node* parse_expr_right(expr_node* lhs, std::uint32_t precedence = 15);
|
||||
private:
|
||||
template <typename... Types>
|
||||
token eat_token(Types... types) {
|
||||
auto token = m_lexer.next_token();
|
||||
if (((token.type == types) || ...)) return token;
|
||||
throw std::runtime_error("unexpected token");
|
||||
}
|
||||
private:
|
||||
lexer m_lexer;
|
||||
furlang::arena* m_arena;
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_FRONT_PARSER_HPP
|
||||
@@ -0,0 +1,218 @@
|
||||
#ifndef FURC_FRONT_TOKEN_HPP
|
||||
#define FURC_FRONT_TOKEN_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
|
||||
namespace furc {
|
||||
|
||||
struct token {
|
||||
enum type {
|
||||
Identifier = 0,
|
||||
Integer,
|
||||
String,
|
||||
Char,
|
||||
|
||||
LParen, /**< `(` */
|
||||
RParen, /**< `)` */
|
||||
LBrace, /**< `{` */
|
||||
RBrace, /**< `}` */
|
||||
LBracket, /**< `[` */
|
||||
RBracket, /**< `]` */
|
||||
Semicolon, /**< `;` */
|
||||
Colon, /**< `:` */
|
||||
Comma, /**< `,` */
|
||||
Dot, /**< `.` */
|
||||
|
||||
Plus, /**< `+` */
|
||||
Minus, /**< `-` */
|
||||
Star, /**< `*` */
|
||||
Slash, /**< `/` */
|
||||
Percent, /**< `%` */
|
||||
DblLT, /**< `<<` */
|
||||
DblGT, /**< `>>` */
|
||||
Ampersand, /**< `&` */
|
||||
Pipe, /**< `|` */
|
||||
Hat, /**< `^` */
|
||||
DblAmpersand, /**< `&&` */
|
||||
DblPipe, /**< `||` */
|
||||
|
||||
DblPlus, /**< `++` */
|
||||
DblMinus, /**< `--` */
|
||||
Tilde, /**< `~` */
|
||||
ExMark, /**< `!` */
|
||||
CatEars, /**< `^^` */
|
||||
|
||||
Equals, /**< `=` */
|
||||
PlusEquals, /**< `+=` */
|
||||
MinusEquals, /**< `-=` */
|
||||
StarEquals, /**< `*=` */
|
||||
SlashEquals, /**< `/=` */
|
||||
PercentEquals, /**< `%=` */
|
||||
AmpersandEquals, /**< `&=` */
|
||||
PipeEquals, /**< `|=` */
|
||||
HatEquals, /**< `^=` */
|
||||
|
||||
DblEquals, /**< `==` */
|
||||
ExEquals, /**< `!=` */
|
||||
LessThan, /**< `<` */
|
||||
LessEquals, /**< `<=` */
|
||||
GreaterThan, /**< `>` */
|
||||
GreaterEquals, /**< `>=` */
|
||||
|
||||
SlimArrow, /**< `->` */
|
||||
// My brother just another me
|
||||
FatArrow, /**< `=>` */
|
||||
|
||||
Monkey, /**< `@` */
|
||||
Sha256, /**< `#` */
|
||||
|
||||
Func, /**< `func` */
|
||||
Return, /**< `return` */
|
||||
If, /**< `if` */
|
||||
Else, /**< `else` */
|
||||
While, /**< `while` */
|
||||
Public, /**< `public` */
|
||||
Private, /**< `private` */
|
||||
Pre, /**< `pre` */
|
||||
Post, /**< `post` */
|
||||
|
||||
Pointerof, /**< `pointerof` */
|
||||
Sizeof, /**< `sizeof` */
|
||||
Lengthof, /**< `lengthof` */
|
||||
|
||||
S8, /**< `s8` */
|
||||
U8, /**< `u8` */
|
||||
S16, /**< `s16` */
|
||||
U16, /**< `u16` */
|
||||
S32, /**< `s32` */
|
||||
U32, /**< `u32` */
|
||||
S64, /**< `s64` */
|
||||
U64, /**< `u64` */
|
||||
|
||||
// Errors:
|
||||
UnexpectedCharacter,
|
||||
UnexpectedEOF,
|
||||
InvalidInteger,
|
||||
EndOfFile,
|
||||
} type;
|
||||
union value {
|
||||
std::nullptr_t null = nullptr;
|
||||
std::uint64_t integer;
|
||||
std::string_view string;
|
||||
char character;
|
||||
} value;
|
||||
|
||||
struct location {
|
||||
std::string_view filepath;
|
||||
std::size_t row = 0;
|
||||
std::size_t col = 0;
|
||||
} loc;
|
||||
|
||||
token(location loc, enum type type)
|
||||
: loc(loc), type(type) {}
|
||||
|
||||
token(location loc, std::uint64_t integer)
|
||||
: loc(loc), type(Integer) {
|
||||
value.integer = integer;
|
||||
}
|
||||
|
||||
token(location loc, enum type type, std::string_view string)
|
||||
: loc(loc), type(type) {
|
||||
value.string = string;
|
||||
}
|
||||
|
||||
token(location loc, enum type type, char character)
|
||||
: loc(loc), type(type) {
|
||||
value.character = character;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const token& token) {
|
||||
switch (token.type) {
|
||||
case token::Identifier: return os << token.value.string;
|
||||
case token::String: return os << '"' << token.value.string << '"';
|
||||
case token::Char: return os << '\'' << token.value.character << '\'';
|
||||
case token::Integer: return os << token.value.integer;
|
||||
case token::LParen: return os << "(";
|
||||
case token::RParen: return os << ")";
|
||||
case token::LBrace: return os << "{";
|
||||
case token::RBrace: return os << "}";
|
||||
case token::LBracket: return os << "[";
|
||||
case token::RBracket: return os << "]";
|
||||
case token::Semicolon: return os << ";";
|
||||
case token::Colon: return os << ":";
|
||||
case token::Comma: return os << ",";
|
||||
case token::Dot: return os << ".";
|
||||
case token::Plus: return os << "+";
|
||||
case token::Minus: return os << "-";
|
||||
case token::Star: return os << "*";
|
||||
case token::Slash: return os << "/";
|
||||
case token::Percent: return os << "%";
|
||||
case token::DblLT: return os << "<<";
|
||||
case token::DblGT: return os << ">>";
|
||||
case token::Ampersand: return os << "&";
|
||||
case token::Pipe: return os << "|";
|
||||
case token::Hat: return os << "^";
|
||||
case token::DblAmpersand: return os << "&&";
|
||||
case token::DblPipe: return os << "||";
|
||||
case token::DblPlus: return os << "++";
|
||||
case token::DblMinus: return os << "--";
|
||||
case token::Tilde: return os << "~";
|
||||
case token::ExMark: return os << "!";
|
||||
case token::CatEars: return os << "^^";
|
||||
case token::Equals: return os << "=";
|
||||
case token::PlusEquals: return os << "+=";
|
||||
case token::MinusEquals: return os << "-=";
|
||||
case token::StarEquals: return os << "*=";
|
||||
case token::SlashEquals: return os << "/=";
|
||||
case token::PercentEquals: return os << "%=";
|
||||
case token::AmpersandEquals: return os << "&=";
|
||||
case token::PipeEquals: return os << "|=";
|
||||
case token::HatEquals: return os << "^=";
|
||||
case token::DblEquals: return os << "==";
|
||||
case token::ExEquals: return os << "!=";
|
||||
case token::LessThan: return os << "<";
|
||||
case token::LessEquals: return os << "<=";
|
||||
case token::GreaterThan: return os << ">";
|
||||
case token::GreaterEquals: return os << ">=";
|
||||
case token::SlimArrow: return os << "->";
|
||||
case token::FatArrow: return os << "=>";
|
||||
case token::Monkey: return os << "@";
|
||||
case token::Sha256: return os << "#";
|
||||
case token::Func: return os << "func";
|
||||
case token::Return: return os << "return";
|
||||
case token::If: return os << "if";
|
||||
case token::Else: return os << "else";
|
||||
case token::While: return os << "while";
|
||||
case token::Public: return os << "public";
|
||||
case token::Private: return os << "private";
|
||||
case token::Pre: return os << "pre";
|
||||
case token::Post: return os << "post";
|
||||
case token::Pointerof: return os << "pointerof";
|
||||
case token::Sizeof: return os << "sizeof";
|
||||
case token::Lengthof: return os << "lengthof";
|
||||
case token::S8: return os << "s8";
|
||||
case token::U8: return os << "u8";
|
||||
case token::S16: return os << "s16";
|
||||
case token::U16: return os << "u16";
|
||||
case token::S32: return os << "s32";
|
||||
case token::U32: return os << "u32";
|
||||
case token::S64: return os << "s64";
|
||||
case token::U64: return os << "u64";
|
||||
case token::UnexpectedCharacter: return os << "Unexpected character `" << token.value.character << "`";
|
||||
case token::UnexpectedEOF: return os << "Unexpected End Of File";
|
||||
case token::InvalidInteger: return os << "Invalid Integer";
|
||||
case token::EndOfFile: return os << "End Of File";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
using token_t = enum token::type;
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_FRONT_TOKEN_HPP
|
||||
@@ -0,0 +1,455 @@
|
||||
#ifndef FURC_MIDDLE_IR_HPP
|
||||
#define FURC_MIDDLE_IR_HPP
|
||||
|
||||
#include "furc/front/ast.hpp"
|
||||
#include "furlang/arena.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
|
||||
struct ir_operand {
|
||||
enum type_e {
|
||||
Integer = 0,
|
||||
Register,
|
||||
Variable,
|
||||
Global,
|
||||
Function,
|
||||
Block,
|
||||
BlockPair,
|
||||
PhiPair,
|
||||
} type;
|
||||
union value_u {
|
||||
std::uint64_t integer;
|
||||
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 {
|
||||
std::uint64_t first;
|
||||
std::uint64_t second;
|
||||
} blockPair;
|
||||
struct phi_pair_s {
|
||||
register_s reg;
|
||||
std::uint64_t block;
|
||||
} phiPair;
|
||||
|
||||
value_u() = default;
|
||||
|
||||
value_u(std::uint64_t integer)
|
||||
: integer(integer) {}
|
||||
|
||||
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 }) {}
|
||||
|
||||
value_u(register_s reg, std::uint64_t block)
|
||||
: phiPair({ reg, block }) {}
|
||||
} value;
|
||||
|
||||
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 {
|
||||
enum type_e {
|
||||
Void = 0,
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
S32,
|
||||
U32,
|
||||
S64,
|
||||
U64,
|
||||
} type = Void;
|
||||
};
|
||||
|
||||
// TODO: Add data types to instructions (like mov QWORD ... in x86 assembly)
|
||||
struct ir_instruction {
|
||||
enum type_e {
|
||||
Move = 0,
|
||||
Call,
|
||||
Branch,
|
||||
BranchCond,
|
||||
Return,
|
||||
Phi,
|
||||
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
|
||||
Shl,
|
||||
Shr,
|
||||
BinAnd,
|
||||
BinOr,
|
||||
BinXor,
|
||||
And,
|
||||
Or,
|
||||
|
||||
Eq,
|
||||
NotEq,
|
||||
LessThan,
|
||||
LessEq,
|
||||
GreaterThan,
|
||||
GreaterEq,
|
||||
|
||||
Positive,
|
||||
Negative,
|
||||
Increment,
|
||||
Decrement,
|
||||
BinNot,
|
||||
Not,
|
||||
|
||||
Sizeof,
|
||||
Pointerof,
|
||||
Lenof,
|
||||
} type;
|
||||
std::optional<ir_operand> destination;
|
||||
std::vector<ir_operand> sources;
|
||||
|
||||
ir_instruction(type_e type,
|
||||
std::optional<ir_operand> destination = {},
|
||||
std::initializer_list<ir_operand> sources = {})
|
||||
: type(type), destination(destination), sources(sources) {}
|
||||
|
||||
static constexpr bool is_terminating(type_e type) {
|
||||
switch (type) {
|
||||
case Branch:
|
||||
case BranchCond:
|
||||
case Return: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const ir_instruction& rhs) const {
|
||||
return type == rhs.type && destination == rhs.destination && sources == rhs.sources;
|
||||
}
|
||||
};
|
||||
|
||||
struct ir_basic_block {
|
||||
std::vector<ir_instruction> instructions;
|
||||
|
||||
bool is_terminated() const {
|
||||
return !instructions.empty() && ir_instruction::is_terminating(instructions.back().type);
|
||||
}
|
||||
};
|
||||
|
||||
struct ir_variable {
|
||||
ir_variable() = default;
|
||||
|
||||
ir_variable(ir_type type)
|
||||
: type(type) {}
|
||||
|
||||
virtual ~ir_variable() = default;
|
||||
|
||||
ir_variable(ir_variable&&) noexcept = default;
|
||||
ir_variable& operator=(ir_variable&&) noexcept = default;
|
||||
|
||||
ir_variable(const ir_variable&) = default;
|
||||
ir_variable& operator=(const ir_variable&) = default;
|
||||
|
||||
ir_type type;
|
||||
|
||||
virtual ir_operand operand() const = 0;
|
||||
};
|
||||
|
||||
struct ir_module_variable : ir_variable {
|
||||
ir_module_variable(ir_type type, std::uint16_t name)
|
||||
: ir_variable(type), name(name) {}
|
||||
|
||||
std::uint16_t name;
|
||||
|
||||
ir_operand operand() const final { return { ir_operand::Global, name }; }
|
||||
};
|
||||
|
||||
struct ir_function_variable : ir_variable {
|
||||
ir_function_variable(ir_type type, std::uint64_t name)
|
||||
: ir_variable(type), name(name) {}
|
||||
|
||||
std::uint64_t name;
|
||||
|
||||
ir_operand operand() const final { return { ir_operand::Variable, name }; }
|
||||
};
|
||||
|
||||
struct ir_scope {
|
||||
ir_scope() = default;
|
||||
virtual ~ir_scope() = default;
|
||||
|
||||
ir_scope(ir_scope&&) noexcept = default;
|
||||
ir_scope& operator=(ir_scope&&) noexcept = default;
|
||||
|
||||
ir_scope(const ir_scope&) = default;
|
||||
ir_scope& operator=(const ir_scope&) = default;
|
||||
|
||||
ir_scope* previous = nullptr;
|
||||
|
||||
std::unordered_map<std::string, ir_variable*> variables;
|
||||
|
||||
const ir_variable* variable(const std::string& name) const {
|
||||
if (auto it = variables.find(name); it != variables.end()) return it->second;
|
||||
return (previous != nullptr) ? previous->variable(name) : nullptr;
|
||||
}
|
||||
|
||||
virtual const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) = 0;
|
||||
};
|
||||
|
||||
struct ir_function : ir_scope {
|
||||
enum type_e {
|
||||
Normal = 0,
|
||||
Import,
|
||||
Native,
|
||||
} type = Normal;
|
||||
enum access_e {
|
||||
Public = 0,
|
||||
Private,
|
||||
} access = Public;
|
||||
|
||||
std::string name;
|
||||
std::vector<ir_type> params;
|
||||
ir_type retType;
|
||||
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, varCount++);
|
||||
}
|
||||
|
||||
static ir_function from_name(std::string&& name) {
|
||||
ir_function func;
|
||||
func.name = std::move(name);
|
||||
return func;
|
||||
}
|
||||
};
|
||||
|
||||
struct ir_module : ir_scope {
|
||||
std::vector<ir_function*> functions;
|
||||
furlang::arena arena;
|
||||
|
||||
std::uint16_t varCount = 0;
|
||||
|
||||
const ir_variable* allocate(furlang::arena& arena, const std::string& name, ir_type type) final {
|
||||
return variables[name] = arena.allocate<ir_module_variable>(type, varCount);
|
||||
}
|
||||
|
||||
ir_function* add_function(ir_function&& function) {
|
||||
return functions.emplace_back(arena.allocate<ir_function>(std::move(function)));
|
||||
}
|
||||
};
|
||||
|
||||
struct ir_context {
|
||||
ir_context(ir_function* function)
|
||||
: function(function) {
|
||||
if (function->blocks.empty()) new_last();
|
||||
blockPtr = &function->blocks.front();
|
||||
}
|
||||
|
||||
~ir_context() {
|
||||
if (blockPtr == nullptr) return;
|
||||
if (!blockPtr->is_terminated()) {
|
||||
if (blockIdx + 1 == function->blocks.size()) {
|
||||
add_instr(ir_instruction::Return);
|
||||
} else {
|
||||
add_instr(ir_instruction::Branch, ir_operand{ ir_operand::Block, blockIdx + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ir_context(ir_context&& other) noexcept
|
||||
: function(other.function), blockIdx(other.blockIdx), blockPtr(other.blockPtr) {
|
||||
other.function = nullptr;
|
||||
other.blockIdx = 0;
|
||||
other.blockPtr = nullptr;
|
||||
}
|
||||
|
||||
ir_context& operator=(ir_context&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
function = other.function;
|
||||
blockIdx = other.blockIdx;
|
||||
blockPtr = other.blockPtr;
|
||||
|
||||
other.function = nullptr;
|
||||
other.blockIdx = 0;
|
||||
other.blockPtr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ir_context(const ir_context&) = delete;
|
||||
ir_context& operator=(const ir_context&) = delete;
|
||||
|
||||
template <typename... Args, typename = std::enable_if_t<std::is_constructible_v<ir_instruction, Args...>>>
|
||||
ir_instruction& add_instr(Args&&... args) {
|
||||
auto it = blockPtr->instructions.end();
|
||||
if (!blockPtr->instructions.empty() && ir_instruction::is_terminating(blockPtr->instructions.back().type)) --it;
|
||||
it = blockPtr->instructions.emplace(it, std::forward<Args>(args)...);
|
||||
if (ir_instruction::is_terminating(it->type) && it + 1 != blockPtr->instructions.end())
|
||||
blockPtr->instructions.pop_back();
|
||||
return *it;
|
||||
}
|
||||
|
||||
void terminate() { add_instr(ir_instruction::Return); }
|
||||
|
||||
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 }); }
|
||||
|
||||
ir_instruction* terminate(ir_operand cond, std::uint64_t thenBranch, std::uint64_t elseBranch) {
|
||||
return &add_instr(ir_instruction{ ir_instruction::BranchCond,
|
||||
ir_operand{ ir_operand::BlockPair, thenBranch, elseBranch },
|
||||
{ cond } });
|
||||
}
|
||||
|
||||
ir_context& new_next() {
|
||||
if (blockPtr->instructions.empty()) return *this;
|
||||
auto it = function->blocks.begin() + static_cast<std::ptrdiff_t>(++blockIdx);
|
||||
if (!blockPtr->is_terminated()) terminate(blockIdx);
|
||||
blockPtr = &*function->blocks.emplace(it);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ir_context& new_last() {
|
||||
blockIdx = function->blocks.size();
|
||||
blockPtr = &*function->blocks.emplace(function->blocks.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
ir_context& go(std::uint64_t block) {
|
||||
blockIdx = std::min(block, function->blocks.size() - 1);
|
||||
blockPtr = function->blocks.data() + static_cast<std::ptrdiff_t>(blockIdx);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ir_context& go_next() { return go(blockIdx + 1); }
|
||||
ir_context& go_last() { return go(std::min<std::uint64_t>(0, function->blocks.size() - 1)); }
|
||||
|
||||
ir_operand last_register() const { return { ir_operand::Register, function->regCount - 1 }; }
|
||||
ir_operand next_register() const { return { ir_operand::Register, function->regCount++ }; }
|
||||
|
||||
static ir_operand block_op(std::uint64_t blockIdx) { return { ir_operand::Block, blockIdx }; }
|
||||
|
||||
ir_function* function = nullptr;
|
||||
std::uint64_t blockIdx = 0;
|
||||
ir_basic_block* blockPtr = nullptr;
|
||||
};
|
||||
|
||||
class ir_generator final : public ast_visitor {
|
||||
public:
|
||||
ir_generator()
|
||||
: m_initContext(m_module.add_function(ir_function::from_name("module$init"))) {}
|
||||
|
||||
void finalize() {
|
||||
m_module.functions.front()->blocks.emplace_back().instructions.push_back(
|
||||
ir_instruction{ ir_instruction::Return });
|
||||
}
|
||||
|
||||
ir_module build() {
|
||||
m_scope = nullptr;
|
||||
m_context = {};
|
||||
m_initContext.blockPtr = nullptr;
|
||||
return std::move(m_module);
|
||||
}
|
||||
|
||||
static ir_module generate(const ast_node& node) {
|
||||
ir_generator gen;
|
||||
node.accept(gen);
|
||||
gen.finalize();
|
||||
return gen.build();
|
||||
}
|
||||
|
||||
static ir_module generate(const ast& tree) {
|
||||
ir_generator gen;
|
||||
for (const auto& node : tree.decls)
|
||||
node->accept(gen);
|
||||
gen.finalize();
|
||||
return gen.build();
|
||||
}
|
||||
private:
|
||||
void visit_comp_stmt_node(const comp_stmt_node& node) override;
|
||||
void visit_if_stmt_node(const if_stmt_node& node) override;
|
||||
void visit_while_stmt_node(const while_stmt_node& node) override;
|
||||
void visit_return_stmt_node(const return_stmt_node& node) override;
|
||||
void visit_var_decl_node(const var_decl_node& node) override;
|
||||
void visit_func_decl_node(const func_decl_node& node) override;
|
||||
void visit_var_read_expr_node(const var_read_expr_node& node) override;
|
||||
void visit_func_call_expr_node(const func_call_expr_node& node) override;
|
||||
void visit_group_expr_node(const group_expr_node& node) override;
|
||||
void visit_binary_op_expr_node(const binary_op_expr_node& node) override;
|
||||
void visit_unary_op_expr_node(const unary_op_expr_node& node) override;
|
||||
void visit_if_expr_node(const if_expr_node& node) override;
|
||||
void visit_int_lit_node(const int_lit_node& node) override;
|
||||
void visit_char_lit_node(const char_lit_node& node) override;
|
||||
private:
|
||||
ir_context& context() { return m_context.top(); }
|
||||
private:
|
||||
ir_module m_module;
|
||||
ir_scope* m_scope = &m_module;
|
||||
std::stack<ir_context> m_context;
|
||||
|
||||
ir_context m_initContext;
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_MIDDLE_IR_HPP
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* SSA destruction (out-of-SSA phase) for register-based targets based on "Mechanizing Conventional SSA for a
|
||||
* Verified Destruction with Coalescing" by Delphine Demange and Yon Fernandez de Retana
|
||||
* (https://dl.acm.org/doi/pdf/10.1145/2892208.2892222 09/11/2026).
|
||||
*/
|
||||
#ifndef FURC_MIDDLE_REG_GEN_HPP
|
||||
#define FURC_MIDDLE_REG_GEN_HPP
|
||||
|
||||
#include "furc/middle/ir.hpp"
|
||||
#include "furc/middle/ssa.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
|
||||
class reg_gen {
|
||||
public:
|
||||
class disjoint_set {
|
||||
public:
|
||||
std::uint64_t find(std::uint64_t var) {
|
||||
if (m_parents.find(var) == m_parents.end()) m_parents.emplace(var, var);
|
||||
if (m_parents[var] == var) return var;
|
||||
return find(m_parents[var]);
|
||||
}
|
||||
|
||||
void unite(std::uint64_t var1, std::uint64_t var2) {
|
||||
std::uint64_t rep1 = find(var1);
|
||||
std::uint64_t rep2 = find(var2);
|
||||
if (rep1 != rep2) m_parents[rep1] = rep2;
|
||||
}
|
||||
private:
|
||||
std::unordered_map<std::uint64_t, std::uint64_t> m_parents;
|
||||
};
|
||||
public:
|
||||
struct block_info {
|
||||
std::unordered_set<std::uint64_t> defs;
|
||||
std::unordered_set<std::uint64_t> uses;
|
||||
std::unordered_set<std::uint64_t> liveIn;
|
||||
std::unordered_set<std::uint64_t> liveOut;
|
||||
};
|
||||
public:
|
||||
reg_gen(ir_function& func, ssa& ssa) {
|
||||
std::vector<block_info> lifeBlocks;
|
||||
live_analysis(lifeBlocks, func.blocks, ssa.cfgBlocks);
|
||||
remove_interference(func.blocks, lifeBlocks);
|
||||
merge(func.blocks);
|
||||
}
|
||||
public:
|
||||
static void live_analysis(std::vector<block_info>& lifeBlocks,
|
||||
const std::vector<ir_basic_block>& irBlocks,
|
||||
const std::vector<ssa::cfg_block>& cfgBlocks) {
|
||||
lifeBlocks.resize(irBlocks.size());
|
||||
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||
const auto& irBlock = irBlocks[i];
|
||||
auto& block = lifeBlocks[i];
|
||||
|
||||
for (const auto& instr : irBlock.instructions) {
|
||||
for (const auto& op : instr.sources) {
|
||||
if (op.type != ir_operand::Register) continue;
|
||||
if (block.defs.find(op.value.reg) != block.defs.end()) continue;
|
||||
block.uses.insert(op.value.reg);
|
||||
}
|
||||
|
||||
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||
block.defs.insert(instr.destination->value.reg);
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
|
||||
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||
const auto& irBlock = irBlocks[i];
|
||||
auto& block = lifeBlocks[i];
|
||||
|
||||
std::unordered_set<std::uint64_t> newSet;
|
||||
for (auto succ : cfgBlocks[i].sucs) {
|
||||
newSet.insert(lifeBlocks[succ].liveIn.begin(), lifeBlocks[succ].liveIn.end());
|
||||
}
|
||||
|
||||
if (newSet != block.liveOut) {
|
||||
block.liveOut = newSet;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
newSet.clear();
|
||||
newSet.insert(block.uses.begin(), block.uses.end());
|
||||
for (const auto& var : block.liveOut) {
|
||||
if (block.defs.find(var) != block.defs.end()) continue;
|
||||
newSet.insert(var);
|
||||
}
|
||||
|
||||
if (newSet != block.liveIn) {
|
||||
block.liveIn = newSet;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_interference(std::vector<ir_basic_block>& irBlocks, const std::vector<block_info>& lifeBlocks) {
|
||||
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
|
||||
auto& irBlock = irBlocks[i];
|
||||
|
||||
for (auto it = irBlock.instructions.begin(), end = irBlock.instructions.end();
|
||||
it != end && it->type == ir_instruction::Phi;
|
||||
++it) {
|
||||
assert(it->destination.has_value() && it->destination->type == ir_operand::Register);
|
||||
const auto& phiDst = it->destination->value.reg;
|
||||
|
||||
for (auto& op : it->sources) {
|
||||
assert(op.type == ir_operand::PhiPair);
|
||||
const auto& predBlock = lifeBlocks[op.value.phiPair.block];
|
||||
auto& phiArg = op.value.phiPair.reg;
|
||||
if (predBlock.liveOut.count(phiArg) == 0 || phiArg == phiDst) continue;
|
||||
|
||||
auto oldArg = phiArg;
|
||||
phiArg.ver = 0; // TODO: Allocate temporary registers
|
||||
|
||||
auto& irBlock = irBlocks[op.value.phiPair.block];
|
||||
assert(!irBlock.instructions.empty());
|
||||
auto it = irBlock.instructions.end() - 1;
|
||||
if (ir_instruction::is_terminating(it->type)) --it;
|
||||
irBlock.instructions.emplace(it,
|
||||
ir_instruction{ ir_instruction::Move,
|
||||
ir_operand::reg(phiArg.name, phiArg.ver),
|
||||
{ ir_operand::reg(oldArg.name, oldArg.ver) } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void merge(std::vector<ir_basic_block>& irBlocks) {
|
||||
disjoint_set dj;
|
||||
|
||||
for (const auto& block : irBlocks) {
|
||||
for (const auto& instr : block.instructions) {
|
||||
if (instr.type != ir_instruction::Phi) break;
|
||||
|
||||
assert(instr.destination.has_value() && instr.destination->type == ir_operand::Register);
|
||||
const auto& phiDst = instr.destination->value.reg;
|
||||
dj.find(phiDst);
|
||||
|
||||
for (const auto& op : instr.sources) {
|
||||
assert(op.type == ir_operand::PhiPair);
|
||||
const auto& predBlock = op.value.phiPair.block;
|
||||
const auto& phiArg = op.value.phiPair.reg;
|
||||
dj.unite(phiDst, phiArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& block : irBlocks) {
|
||||
auto it = block.instructions.begin();
|
||||
while (it != block.instructions.end() && it->type == ir_instruction::Phi) {
|
||||
it = block.instructions.erase(it);
|
||||
}
|
||||
for (; it != block.instructions.end(); ++it) {
|
||||
for (auto& op : it->sources) {
|
||||
if (op.type != ir_operand::Register) continue;
|
||||
op.value.reg = dj.find(op.value.reg);
|
||||
}
|
||||
|
||||
if (!it->destination.has_value() || it->destination->type != ir_operand::Register) continue;
|
||||
it->destination->value.reg = dj.find(it->destination->value.reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_MIDDLE_REG_GEN_HPP
|
||||
@@ -0,0 +1,101 @@
|
||||
#ifndef FURC_MIDDLE_SSA_HPP
|
||||
#define FURC_MIDDLE_SSA_HPP
|
||||
|
||||
#include "furc/middle/ir.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
|
||||
class ssa {
|
||||
public:
|
||||
struct cfg_block {
|
||||
std::unordered_set<std::uint64_t> preds;
|
||||
std::unordered_set<std::uint64_t> sucs;
|
||||
};
|
||||
|
||||
struct ssa_block {
|
||||
std::size_t order = 0;
|
||||
|
||||
std::uint64_t idom = -1;
|
||||
|
||||
std::unordered_set<std::uint64_t> children; // Children of the block in dominator tree
|
||||
|
||||
// Dominance Frontiers
|
||||
std::unordered_set<std::uint64_t> df;
|
||||
};
|
||||
|
||||
struct register_info {
|
||||
std::unordered_set<std::uint64_t> sites; // Definition Sites
|
||||
};
|
||||
public:
|
||||
ssa(ir_function& func) {
|
||||
registers.resize(func.regCount);
|
||||
compute_cfg(func.blocks, cfgBlocks);
|
||||
collect_registers(func.blocks, registers, globals);
|
||||
|
||||
std::vector<std::uint64_t> order;
|
||||
compute_rpo(cfgBlocks, ssaBlocks, order);
|
||||
|
||||
build_dtree(cfgBlocks, ssaBlocks, order);
|
||||
compute_dfrontiers(cfgBlocks, ssaBlocks);
|
||||
|
||||
ssaification(func.blocks, cfgBlocks, ssaBlocks, registers, globals);
|
||||
rename(func.blocks, func.regCount, cfgBlocks, ssaBlocks, order);
|
||||
}
|
||||
public:
|
||||
static void compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks);
|
||||
|
||||
static void collect_registers(const std::vector<ir_basic_block>& irBlocks,
|
||||
std::vector<register_info>& registers,
|
||||
std::unordered_set<std::uint64_t>& globals);
|
||||
|
||||
static void build_dtree(const std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<std::size_t>& order);
|
||||
|
||||
static void compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks);
|
||||
|
||||
static void compute_rpo(std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
std::vector<std::size_t>& order);
|
||||
|
||||
static void ssaification(std::vector<ir_basic_block>& irBlocks,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
const std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<register_info>& registers,
|
||||
const std::unordered_set<std::uint64_t>& globals);
|
||||
|
||||
static void rename(std::vector<ir_basic_block>& irBlocks,
|
||||
std::size_t regCount,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<std::uint64_t>& order);
|
||||
private:
|
||||
static void rename_rec(std::vector<std::uint64_t>& counters,
|
||||
std::vector<std::stack<std::uint64_t>>& stacks,
|
||||
std::vector<ir_basic_block>& irBlocks,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
const std::vector<ssa_block>& ssaBlocks,
|
||||
std::size_t blockIdx);
|
||||
private:
|
||||
static void rpo_dfs(std::unordered_set<std::size_t>& visited,
|
||||
std::vector<std::size_t>& order,
|
||||
std::size_t block,
|
||||
const std::vector<cfg_block>& blocks);
|
||||
|
||||
static std::size_t intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2);
|
||||
public:
|
||||
std::vector<cfg_block> cfgBlocks;
|
||||
std::vector<ssa_block> ssaBlocks;
|
||||
std::vector<register_info> registers;
|
||||
std::unordered_set<std::uint64_t> globals;
|
||||
};
|
||||
|
||||
} // namespace furc
|
||||
|
||||
#endif // FURC_MIDDLE_SSA_HPP
|
||||
@@ -2,7 +2,9 @@
|
||||
#define FURLANG_RESULT_HPP
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef FURLANG_SERIALIZATION_CODEC_HPP
|
||||
#define FURLANG_SERIALIZATION_CODEC_HPP
|
||||
|
||||
#include "furlang/result.hpp"
|
||||
#include "furlang/serialization/io.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace furlang {
|
||||
namespace serialization {
|
||||
|
||||
template <typename Codec, typename T>
|
||||
using codec_encode_result_t = decltype(std::declval<Codec>().encode(std::declval<writer&>(), std::declval<const T&>()));
|
||||
|
||||
template <typename Codec, typename T>
|
||||
using codec_decode_result_t = decltype(std::declval<Codec>().decode(std::declval<reader&>()));
|
||||
|
||||
template <typename Codec, typename T, typename = void>
|
||||
struct is_codec : std::false_type {};
|
||||
|
||||
template <typename Codec, typename T>
|
||||
struct is_codec<Codec, T, std::void_t<codec_encode_result_t<Codec, T>, codec_decode_result_t<Codec, T>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Codec, typename T>
|
||||
constexpr bool is_codec_v = is_codec<Codec, T>::value;
|
||||
|
||||
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||
result<error> encode(Codec& codec, writer& writer, const T& value) {
|
||||
return codec.encode(writer, value);
|
||||
}
|
||||
|
||||
template <typename Codec, typename T, typename = std::enable_if_t<is_codec_v<Codec, T>>>
|
||||
result<error, T> decode(Codec& codec, reader& reader) {
|
||||
return codec.decode(reader);
|
||||
}
|
||||
|
||||
template <typename T, typename = void>
|
||||
class codec;
|
||||
|
||||
template <typename T>
|
||||
struct codec<T, std::enable_if_t<std::is_integral_v<T>>> {
|
||||
result<error> encode(writer& writer, const T& value) { return writer.write_int(value); }
|
||||
result<error, T> decode(reader& reader) { return reader.read_int(T{}); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct codec<std::string> {
|
||||
result<error> encode(writer& writer, const std::string& value) { return writer.write_string(value); }
|
||||
|
||||
result<error, std::string> decode(reader& reader) { return reader.read_string(); }
|
||||
};
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_SERIALIZATION_CODEC_HPP
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef FURLANG_SERIALIZATION_ERROR_HPP
|
||||
#define FURLANG_SERIALIZATION_ERROR_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace furlang {
|
||||
namespace serialization {
|
||||
|
||||
enum class error_code {
|
||||
EndOfFile,
|
||||
InvalidData,
|
||||
InvalidTag,
|
||||
InvalidVersion,
|
||||
IntegerOverflow,
|
||||
SizeLimit,
|
||||
DuplicateId,
|
||||
UnknownId,
|
||||
TypeMismatch,
|
||||
Unsupported,
|
||||
};
|
||||
|
||||
struct error {
|
||||
error_code code;
|
||||
std::string message;
|
||||
|
||||
std::size_t offset = 0;
|
||||
};
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_SERIALIZATION_ERROR_HPP
|
||||
@@ -0,0 +1,190 @@
|
||||
#ifndef FURLANG_SERIALIZATION_IO_HPP
|
||||
#define FURLANG_SERIALIZATION_IO_HPP
|
||||
|
||||
#include "furlang/result.hpp"
|
||||
#include "furlang/serialization/error.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furlang {
|
||||
namespace serialization {
|
||||
|
||||
class writer {
|
||||
public:
|
||||
writer() = default;
|
||||
virtual ~writer() = default;
|
||||
|
||||
writer(writer&&) noexcept = default;
|
||||
writer& operator=(writer&&) noexcept = default;
|
||||
writer(const writer&) = default;
|
||||
writer& operator=(const writer&) = default;
|
||||
public:
|
||||
virtual result<error> write_s8(std::int8_t value) = 0;
|
||||
virtual result<error> write_u8(std::uint8_t value) = 0;
|
||||
virtual result<error> write_s16(std::int16_t value) = 0;
|
||||
virtual result<error> write_u16(std::uint16_t value) = 0;
|
||||
virtual result<error> write_s32(std::int32_t value) = 0;
|
||||
virtual result<error> write_u32(std::uint32_t value) = 0;
|
||||
virtual result<error> write_s64(std::int64_t value) = 0;
|
||||
virtual result<error> write_u64(std::uint64_t value) = 0;
|
||||
|
||||
result<error> write_int(std::int8_t value) { return write_s8(value); }
|
||||
result<error> write_int(std::uint8_t value) { return write_u8(value); }
|
||||
result<error> write_int(std::int16_t value) { return write_s16(value); }
|
||||
result<error> write_int(std::uint16_t value) { return write_u16(value); }
|
||||
result<error> write_int(std::int32_t value) { return write_s32(value); }
|
||||
result<error> write_int(std::uint32_t value) { return write_u32(value); }
|
||||
result<error> write_int(std::int64_t value) { return write_s64(value); }
|
||||
result<error> write_int(std::uint64_t value) { return write_u64(value); }
|
||||
|
||||
virtual result<error> write_string(const char* string) = 0;
|
||||
virtual result<error> write_string(std::string_view string) = 0;
|
||||
virtual result<error> write_string(const std::string& string) = 0;
|
||||
};
|
||||
|
||||
class reader {
|
||||
public:
|
||||
reader() = default;
|
||||
virtual ~reader() = default;
|
||||
|
||||
reader(reader&&) noexcept = default;
|
||||
reader& operator=(reader&&) noexcept = default;
|
||||
reader(const reader&) = default;
|
||||
reader& operator=(const reader&) = default;
|
||||
public:
|
||||
virtual result<error, std::int8_t> read_s8() = 0;
|
||||
virtual result<error, std::uint8_t> read_u8() = 0;
|
||||
virtual result<error, std::int16_t> read_s16() = 0;
|
||||
virtual result<error, std::uint16_t> read_u16() = 0;
|
||||
virtual result<error, std::int32_t> read_s32() = 0;
|
||||
virtual result<error, std::uint32_t> read_u32() = 0;
|
||||
virtual result<error, std::int64_t> read_s64() = 0;
|
||||
virtual result<error, std::uint64_t> read_u64() = 0;
|
||||
|
||||
result<error, std::int8_t> read_int(std::int8_t) { return read_s8(); }
|
||||
result<error, std::uint8_t> read_int(std::uint8_t) { return read_u8(); }
|
||||
result<error, std::int16_t> read_int(std::int16_t) { return read_s16(); }
|
||||
result<error, std::uint16_t> read_int(std::uint16_t) { return read_u16(); }
|
||||
result<error, std::int32_t> read_int(std::int32_t) { return read_s32(); }
|
||||
result<error, std::uint32_t> read_int(std::uint32_t) { return read_u32(); }
|
||||
result<error, std::int64_t> read_int(std::int64_t) { return read_s64(); }
|
||||
result<error, std::uint64_t> read_int(std::uint64_t) { return read_u64(); }
|
||||
|
||||
virtual result<error, std::string> read_string() = 0;
|
||||
|
||||
virtual std::size_t offset() const = 0;
|
||||
};
|
||||
|
||||
enum class endianness {
|
||||
Little = 0,
|
||||
Big = 1,
|
||||
};
|
||||
|
||||
class byte_writer : public writer {
|
||||
public:
|
||||
byte_writer(endianness endianness = endianness::Big)
|
||||
: m_endianness(endianness) {}
|
||||
public:
|
||||
result<error> write_s8(std::int8_t value) override;
|
||||
result<error> write_u8(std::uint8_t value) override;
|
||||
result<error> write_s16(std::int16_t value) override;
|
||||
result<error> write_u16(std::uint16_t value) override;
|
||||
result<error> write_s32(std::int32_t value) override;
|
||||
result<error> write_u32(std::uint32_t value) override;
|
||||
result<error> write_s64(std::int64_t value) override;
|
||||
result<error> write_u64(std::uint64_t value) override;
|
||||
|
||||
result<error> write_string(const char* string) override;
|
||||
result<error> write_string(std::string_view string) override;
|
||||
result<error> write_string(const std::string& string) override;
|
||||
private:
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
result<error> write_integral_le(T value) {
|
||||
return write_integral_le(value, std::make_index_sequence<sizeof(T)>{});
|
||||
}
|
||||
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||
result<error> write_integral_le(T value, std::index_sequence<I...>) {
|
||||
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||
(m_bytes.push_back(usig >> (I * 8)), ...);
|
||||
return {};
|
||||
}
|
||||
private:
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
void write_integral_be(T value) {
|
||||
write_integral_be(value, std::make_index_sequence<sizeof(T)>{});
|
||||
}
|
||||
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>, std::size_t... I>
|
||||
void write_integral_be(T value, std::index_sequence<I...>) {
|
||||
auto usig = static_cast<std::make_unsigned_t<T>>(value);
|
||||
(m_bytes.push_back(usig >> ((sizeof(T) - 1 - I) * 8)), ...);
|
||||
}
|
||||
private:
|
||||
endianness m_endianness;
|
||||
std::vector<std::uint8_t> m_bytes;
|
||||
};
|
||||
|
||||
class byte_reader : public reader {
|
||||
public:
|
||||
byte_reader(const std::uint8_t* bytes, std::size_t length, endianness endianness = endianness::Big)
|
||||
: m_endianness(endianness), m_bytes(bytes), m_length(length) {}
|
||||
public:
|
||||
result<error, std::int8_t> read_s8() override;
|
||||
result<error, std::uint8_t> read_u8() override;
|
||||
result<error, std::int16_t> read_s16() override;
|
||||
result<error, std::uint16_t> read_u16() override;
|
||||
result<error, std::int32_t> read_s32() override;
|
||||
result<error, std::uint32_t> read_u32() override;
|
||||
result<error, std::int64_t> read_s64() override;
|
||||
result<error, std::uint64_t> read_u64() override;
|
||||
|
||||
result<error, std::string> read_string() override;
|
||||
|
||||
std::size_t offset() const override;
|
||||
private:
|
||||
result<error, std::uint8_t> read_byte() {
|
||||
if (m_offset >= m_length)
|
||||
return result<error, std::uint8_t>::error(error{ error_code::EndOfFile, "", m_offset });
|
||||
return { m_bytes[m_offset++] };
|
||||
}
|
||||
private:
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
result<error, T> read_integral_le() {
|
||||
using U = std::make_unsigned_t<T>;
|
||||
U usig = 0;
|
||||
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||
auto res = read_byte();
|
||||
if (res.has_error()) return res;
|
||||
usig |= static_cast<U>(res.value()) << (i * 8);
|
||||
}
|
||||
return { static_cast<T>(usig) };
|
||||
}
|
||||
private:
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
result<error, T> read_integral_be() {
|
||||
using U = std::make_unsigned_t<T>;
|
||||
U usig = 0;
|
||||
for (std::size_t i = 0; i < sizeof(T); ++i) {
|
||||
auto res = read_byte();
|
||||
if (res.has_error()) return res;
|
||||
usig |= static_cast<U>(res.value()) << ((sizeof(T) - 1 - i) * 8);
|
||||
}
|
||||
return { static_cast<T>(usig) };
|
||||
}
|
||||
private:
|
||||
endianness m_endianness;
|
||||
const std::uint8_t* m_bytes;
|
||||
std::size_t m_length;
|
||||
std::size_t m_offset = 0;
|
||||
};
|
||||
|
||||
} // namespace serialization
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_SERIALIZATION_IO_HPP
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef FURLANG_VIEW_HPP
|
||||
#define FURLANG_VIEW_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furlang {
|
||||
|
||||
template <typename T>
|
||||
class view {
|
||||
public:
|
||||
constexpr view() noexcept = default;
|
||||
|
||||
constexpr view(const T* data, std::size_t size) noexcept
|
||||
: m_data(data), m_size(size) {}
|
||||
public:
|
||||
constexpr view subview(std::size_t offset, std::size_t count = std::numeric_limits<std::size_t>::max()) {
|
||||
if (count > 0 && offset >= m_size) throw std::runtime_error("offset too large");
|
||||
return { m_data + offset, std::min(m_size - offset, count) };
|
||||
}
|
||||
|
||||
const T& operator[](std::size_t offset) const {
|
||||
if (offset >= m_size) throw std::runtime_error("out of bounds");
|
||||
return m_data[offset];
|
||||
}
|
||||
|
||||
constexpr const T* data() const { return m_data; }
|
||||
constexpr std::size_t size() const { return m_size; }
|
||||
private:
|
||||
const T* m_data = nullptr;
|
||||
std::size_t m_size = 0;
|
||||
};
|
||||
|
||||
} // namespace furlang
|
||||
|
||||
#endif // FURLANG_VIEW_HPP
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef FURVM_CONSTANT_HPP
|
||||
#define FURVM_CONSTANT_HPP
|
||||
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
// TODO: Array constants
|
||||
struct constant {
|
||||
enum type_e {
|
||||
S32 = 0,
|
||||
U32,
|
||||
S64,
|
||||
U64,
|
||||
String,
|
||||
} type = S32;
|
||||
union {
|
||||
std::int32_t s32;
|
||||
std::uint32_t u32;
|
||||
std::int64_t s64;
|
||||
std::uint64_t u64;
|
||||
std::string_view string;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_CONSTANT_HPP
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef FURVM_CONTEXT_HPP
|
||||
#define FURVM_CONTEXT_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 <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
class context : public handle_container<mod_h> {
|
||||
public:
|
||||
friend class executor;
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a context.
|
||||
*/
|
||||
context() {}
|
||||
|
||||
~context() = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
context(context&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*/
|
||||
context& operator=(context&&) noexcept = default;
|
||||
|
||||
context(const context&) = delete;
|
||||
context& operator=(const context&) = delete;
|
||||
public:
|
||||
template <typename... Args>
|
||||
auto& allocate_executor() {
|
||||
executor executor(this);
|
||||
return m_executors.emplace_back(std::move(executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
auto& executor_at(Args&&... args) {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an executor from the context.
|
||||
*
|
||||
* @param args Id of the executor.
|
||||
* @return A handle to the executor.
|
||||
*/
|
||||
template <typename... Args>
|
||||
const auto& executor_at(Args&&... args) const {
|
||||
return m_executors.at(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
const std::vector<executor>& executors() const { return m_executors; }
|
||||
public:
|
||||
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;
|
||||
|
||||
class thing_type_store m_thingTypeStore;
|
||||
|
||||
// A list of things on the heap
|
||||
std::vector<std::byte*> m_heap;
|
||||
};
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_CONTEXT_HPP
|
||||
@@ -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 <optional>
|
||||
#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) {
|
||||
@@ -30,6 +32,16 @@ static inline executor_flags operator~(executor_flags flags) {
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -41,18 +53,10 @@ public:
|
||||
std::size_t position; /**< Cursor to a current instruction in the bytecode. */
|
||||
std::size_t stackBase; /**< Snapshot of the stack size before this frame. */
|
||||
|
||||
thing_type* returnType; /**< Return type. */
|
||||
std::vector<thing_h> variables; /**< Frame variables. */
|
||||
thing_type* returnType; /**< Return type. */
|
||||
std::vector<stack_thing> variables; /**< Frame variables. */
|
||||
};
|
||||
public:
|
||||
/**
|
||||
* @brief Returns a new executor.
|
||||
*
|
||||
* @param context Context.
|
||||
*/
|
||||
executor(const context_p& context)
|
||||
: m_context(context) {}
|
||||
|
||||
~executor() = default;
|
||||
|
||||
/**
|
||||
@@ -74,6 +78,11 @@ public:
|
||||
* @brief Copy constructor.
|
||||
*/
|
||||
executor& operator=(const executor&) = default;
|
||||
public:
|
||||
template <typename CallbackFwd>
|
||||
void set_new_frame_callback(CallbackFwd&& callback) {
|
||||
m_newFrameCb = std::forward<CallbackFwd>(callback);
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Returns flags of this executor.
|
||||
@@ -81,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.
|
||||
@@ -102,18 +122,10 @@ public:
|
||||
*
|
||||
* @return The frame.
|
||||
*/
|
||||
frame frame() const;
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes a thing handle onto the stack.
|
||||
*
|
||||
* @param handle Thing handle.
|
||||
*/
|
||||
template <typename HandleFwd>
|
||||
void push_thing(HandleFwd&& handle) {
|
||||
m_stack.emplace(std::forward<HandleFwd>(handle));
|
||||
}
|
||||
frame top_frame() const;
|
||||
|
||||
const std::stack<frame>& frames() const { return m_frames; }
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes a thing onto the stack.
|
||||
*
|
||||
@@ -122,21 +134,27 @@ public:
|
||||
* @param thing Thing.
|
||||
* @return The pushed handle.
|
||||
*/
|
||||
thing_h push_thing(class thing<>&& thing);
|
||||
stack_thing& push_thing(stack_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_h pop_thing();
|
||||
stack_thing pop_thing();
|
||||
|
||||
/**
|
||||
* @brief Returns the top thing on the stack.
|
||||
*
|
||||
* @return A handle to the top thing.
|
||||
*/
|
||||
thing_h thing() const;
|
||||
stack_thing& top_thing();
|
||||
|
||||
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.
|
||||
@@ -144,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_h& thing);
|
||||
void store_thing(variable_t variable, const stack_thing& thing);
|
||||
|
||||
/**
|
||||
* @brief Stores a thing in a frame variable.
|
||||
@@ -152,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_h&& thing);
|
||||
void store_thing(variable_t variable, stack_thing&& thing);
|
||||
|
||||
/**
|
||||
* @brief Returns a thing stored in a variable.
|
||||
@@ -160,7 +178,9 @@ public:
|
||||
* @param variable Id of the variable from which the handle will be fetched.
|
||||
* @return A handle stored in the variable.
|
||||
*/
|
||||
thing_h load_thing(variable_t variable) const;
|
||||
stack_thing& load_thing(variable_t variable);
|
||||
|
||||
const stack_thing& load_thing(variable_t variable) const;
|
||||
public:
|
||||
/**
|
||||
* @brief Executes next instruction.
|
||||
@@ -171,11 +191,16 @@ private:
|
||||
|
||||
thing_type* mod_to_thing_type(const mod_h& mod, const mod_type& type) const;
|
||||
private:
|
||||
executor_flags m_flags{}; // NOLINT(bugprone-invalid-enum-default-initialization)
|
||||
context_p m_context;
|
||||
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<struct frame> m_frames;
|
||||
std::stack<thing_h> m_stack;
|
||||
std::stack<frame> m_frames;
|
||||
std::vector<stack_thing> m_stack;
|
||||
|
||||
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
|
||||
|
||||
@@ -75,11 +75,7 @@ class constant;
|
||||
|
||||
// instruction.hpp
|
||||
|
||||
/**
|
||||
* @enum instruction_t
|
||||
* @brief Furvm's instruction type.
|
||||
*/
|
||||
enum class instruction_t : byte;
|
||||
struct instruction_argument;
|
||||
|
||||
/**
|
||||
* @struct instruction
|
||||
@@ -144,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
|
||||
|
||||
/**
|
||||
@@ -165,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;
|
||||
|
||||
/**
|
||||
@@ -173,11 +164,6 @@ class thing;
|
||||
*/
|
||||
using thing_id = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief A handle to a thing.
|
||||
*/
|
||||
using thing_h = handle<thing<thing_allocator>, refcount_header<thing_id>>;
|
||||
|
||||
// executor.hpp
|
||||
|
||||
/**
|
||||
@@ -199,21 +185,11 @@ enum class executor_flags : std::uint32_t;
|
||||
*/
|
||||
class executor;
|
||||
|
||||
/**
|
||||
* @brief An alias to a executor shared pointer.
|
||||
*/
|
||||
using executor_p = std::shared_ptr<executor>;
|
||||
|
||||
/**
|
||||
* @brief Furvm executor's index.
|
||||
*/
|
||||
using executor_id = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @brief A handle to an executor.
|
||||
*/
|
||||
using executor_h = handle<executor, refcount_header<executor_id>>;
|
||||
|
||||
// context.hpp
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,104 @@
|
||||
#ifndef FURVM_INSTRUCTION_HPP
|
||||
#define FURVM_INSTRUCTION_HPP
|
||||
|
||||
#include "furlang/view.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
struct instruction_argument {
|
||||
enum type_e {
|
||||
None = 0,
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
S32,
|
||||
U32,
|
||||
Constant,
|
||||
Type,
|
||||
Variable,
|
||||
GlobalVariable,
|
||||
Function,
|
||||
Offset,
|
||||
|
||||
Count,
|
||||
} type;
|
||||
union {
|
||||
std::int8_t s8;
|
||||
std::uint8_t u8;
|
||||
std::int16_t s16;
|
||||
std::uint16_t u16;
|
||||
std::int32_t s32;
|
||||
std::uint32_t u32;
|
||||
};
|
||||
|
||||
static const std::size_t s_sizes[Count];
|
||||
static const bool s_signedness[Count];
|
||||
|
||||
std::size_t size() const { return s_sizes[type]; }
|
||||
bool is_signed() const { return s_signedness[type]; }
|
||||
};
|
||||
|
||||
using instruction_argument_t = instruction_argument::type_e;
|
||||
|
||||
struct instruction {
|
||||
enum type_e : byte {
|
||||
NoOperation = 0,
|
||||
PushS8,
|
||||
PushU8,
|
||||
PushS16,
|
||||
PushU16,
|
||||
PushS32,
|
||||
PushU32,
|
||||
PushConstant,
|
||||
Array,
|
||||
Slice,
|
||||
Get,
|
||||
Set,
|
||||
Drop,
|
||||
Duplicate,
|
||||
Swap,
|
||||
Clone,
|
||||
Reference,
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
Equals,
|
||||
NotEquals,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
LessEqual,
|
||||
GreaterEqual,
|
||||
Pointerof,
|
||||
Sizeof,
|
||||
Lengthof,
|
||||
Load,
|
||||
Store,
|
||||
LoadGlobal,
|
||||
StoreGlobal,
|
||||
Call,
|
||||
Jump,
|
||||
JumpNotZero,
|
||||
Return,
|
||||
|
||||
Count,
|
||||
} type;
|
||||
instruction_argument arg;
|
||||
|
||||
static const instruction_argument_t s_arguments[Count];
|
||||
|
||||
std::size_t read(furlang::view<std::uint8_t> in);
|
||||
std::size_t write(std::vector<std::uint8_t>& out) const;
|
||||
};
|
||||
|
||||
using instruction_t = instruction::type_e;
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_INSTRUCTION_HPP
|
||||
@@ -2,13 +2,17 @@
|
||||
#define FURVM_MODULE_HPP
|
||||
|
||||
#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"
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@@ -23,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;
|
||||
@@ -40,6 +48,7 @@ struct mod_type {
|
||||
Ptr,
|
||||
Ref,
|
||||
Array,
|
||||
Slice,
|
||||
|
||||
Import,
|
||||
Count,
|
||||
@@ -48,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;
|
||||
@@ -92,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;
|
||||
}
|
||||
@@ -101,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;
|
||||
}
|
||||
@@ -112,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;
|
||||
}
|
||||
@@ -123,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;
|
||||
}
|
||||
@@ -133,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;
|
||||
}
|
||||
@@ -140,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;
|
||||
@@ -195,7 +215,7 @@ public:
|
||||
*
|
||||
* @return A constant reference to the bytecode.
|
||||
*/
|
||||
constexpr const bytecode_t& bytecode() const { return m_bytecode; }
|
||||
furlang::view<std::uint8_t> bytecode_view() const { return { m_bytecode.data(), m_bytecode.size() }; }
|
||||
public:
|
||||
/**
|
||||
* @brief Emplaces a function in the module's function container.
|
||||
@@ -284,6 +304,10 @@ public:
|
||||
m_functionMap.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
const handle_container<function_h>& functions() const { return m_functions; }
|
||||
|
||||
const auto& function_map() const { return m_functionMap; }
|
||||
public:
|
||||
template <typename NameFwd, typename Func>
|
||||
void set_native_function(NameFwd&& name, Func&& func) {
|
||||
@@ -341,6 +365,51 @@ public:
|
||||
void erase_type(Args&&... args) {
|
||||
m_types.erase(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
const handle_container<mod_type_h>& types() const { return m_types; }
|
||||
public:
|
||||
void set_global_variable_count(std::uint16_t count) {
|
||||
m_globalVariables.resize(count);
|
||||
m_globalVariables.shrink_to_fit();
|
||||
}
|
||||
|
||||
std::uint16_t get_global_variable_count() const { return static_cast<std::uint16_t>(m_globalVariables.size()); }
|
||||
|
||||
void store_global_variable(std::uint16_t var, thing<>&& thing) {
|
||||
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||
m_globalVariables.emplace(m_globalVariables.cbegin() + var, std::move(thing));
|
||||
}
|
||||
|
||||
void store_global_variable(std::uint16_t var, const thing<>& thing) {
|
||||
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||
m_globalVariables.emplace(m_globalVariables.cbegin() + var, thing);
|
||||
}
|
||||
|
||||
thing<>& load_global_variable(std::uint16_t var) {
|
||||
if (var >= m_globalVariables.size()) throw std::runtime_error("invalid slot");
|
||||
return m_globalVariables[var];
|
||||
}
|
||||
|
||||
const thing<>& load_global_variable(std::uint16_t var) const {
|
||||
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.
|
||||
@@ -369,7 +438,13 @@ private:
|
||||
|
||||
handle_container<mod_type_h> m_types;
|
||||
|
||||
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
|
||||
@@ -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
@@ -0,0 +1,226 @@
|
||||
#ifndef FURVM_TYPES_HPP
|
||||
#define FURVM_TYPES_HPP
|
||||
|
||||
#include "furvm/fwd.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
using s8 = std::int8_t;
|
||||
using s16 = std::int16_t;
|
||||
using s32 = std::int32_t;
|
||||
using s64 = std::int64_t;
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
struct thing_type {
|
||||
struct array_value {
|
||||
thing_type* type;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
struct slice_value {
|
||||
thing_type* type;
|
||||
};
|
||||
|
||||
enum type { // NOLINT
|
||||
S8 = 0,
|
||||
S16,
|
||||
S32,
|
||||
S64,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
String,
|
||||
Ptr,
|
||||
Ref,
|
||||
Array,
|
||||
Slice,
|
||||
|
||||
Count,
|
||||
} type = Count;
|
||||
union value {
|
||||
std::nullptr_t null = nullptr;
|
||||
thing_type* typeRef;
|
||||
array_value array;
|
||||
slice_value slice;
|
||||
|
||||
value() = default;
|
||||
|
||||
value(thing_type* type)
|
||||
: typeRef(type) {}
|
||||
|
||||
value(thing_type* type, std::size_t size)
|
||||
: array({}) {
|
||||
array.type = type;
|
||||
array.size = size;
|
||||
}
|
||||
} value;
|
||||
|
||||
static constexpr thing_type_id INVALID_ID = std::numeric_limits<thing_type_id>::max();
|
||||
|
||||
thing_type_id id = INVALID_ID;
|
||||
|
||||
bool operator==(const thing_type& other) const {
|
||||
if (type != other.type) return false;
|
||||
switch (type) {
|
||||
case S8:
|
||||
case S16:
|
||||
case S32:
|
||||
case S64:
|
||||
case U8:
|
||||
case U16:
|
||||
case U32:
|
||||
case U64:
|
||||
case String: return true;
|
||||
case Ptr:
|
||||
case Ref: return *value.typeRef == *other.value.typeRef;
|
||||
case Array: return *value.array.type == *other.value.array.type && value.array.size == other.value.array.size;
|
||||
case Slice: return *value.slice.type == *other.value.slice.type;
|
||||
case Count: break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator!=(const thing_type& other) const { return !this->operator==(other); }
|
||||
|
||||
static bool is_primitive(enum type type) {
|
||||
switch (type) {
|
||||
case S8:
|
||||
case S16:
|
||||
case S32:
|
||||
case S64:
|
||||
case U8:
|
||||
case U16:
|
||||
case U32:
|
||||
case U64: return true;
|
||||
case String:
|
||||
case Ptr:
|
||||
case Ref:
|
||||
case Array:
|
||||
case Slice: return false;
|
||||
case Count: break;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
static std::size_t primitive_size(enum type type) {
|
||||
switch (type) {
|
||||
case thing_type::S8: return sizeof(s8);
|
||||
case thing_type::S16: return sizeof(s16);
|
||||
case thing_type::S32: return sizeof(s32);
|
||||
case thing_type::S64: return sizeof(s64);
|
||||
case thing_type::U8: return sizeof(u8);
|
||||
case thing_type::U16: return sizeof(u16);
|
||||
case thing_type::U32: return sizeof(u32);
|
||||
case thing_type::U64: return sizeof(u64);
|
||||
case thing_type::String:
|
||||
case Ptr:
|
||||
case Ref:
|
||||
case Array:
|
||||
case Slice: return 0;
|
||||
case Count: break;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct overrides_thing_type_matching : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct overrides_thing_type_matching<T, std::void_t<decltype(T::matches(std::declval<const thing_type&>()))>>
|
||||
: std::is_same<decltype(T::matches(std::declval<const thing_type&>())), bool> {};
|
||||
|
||||
template <typename T>
|
||||
struct thing_traits {
|
||||
bool operator()(const thing_type& type) const {
|
||||
if constexpr (overrides_thing_type_matching<T>::value) {
|
||||
return T::matches(type);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<s8> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::S8; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<u8> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::U8; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<s16> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::S16; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<u16> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::U16; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<s32> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::S32; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<u32> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::U32; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<s64> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::S64; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct thing_traits<u64> {
|
||||
bool operator()(const thing_type& type) const { return type.type == thing_type::U64; }
|
||||
};
|
||||
|
||||
template <typename Inner>
|
||||
struct thing_traits<Inner*> {
|
||||
bool operator()(const thing_type& type) const {
|
||||
return (type.type == thing_type::Ptr || type.type == thing_type::Ref) &&
|
||||
thing_traits<Inner>{}(*type.value.typeRef);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Thing, typename = void>
|
||||
struct cassignable_to_thing : std::false_type {};
|
||||
|
||||
template <typename T, typename Thing>
|
||||
struct cassignable_to_thing<T,
|
||||
Thing,
|
||||
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<const T&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename Thing, typename = void>
|
||||
struct massignable_to_thing : std::false_type {};
|
||||
|
||||
template <typename T, typename Thing>
|
||||
struct massignable_to_thing<T,
|
||||
Thing,
|
||||
std::void_t<decltype(std::declval<thing_traits<T>>().assign_to(std::declval<Thing&>(), std::declval<T&&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace furvm
|
||||
|
||||
#endif // FURVM_TYPES_HPP
|
||||
@@ -0,0 +1,4 @@
|
||||
file(GLOB_RECURSE DISFURAS_SRCS "${CMAKE_SOURCE_DIR}/src/disfuras/**.cpp")
|
||||
add_executable(disfuras ${DISFURAS_SRCS})
|
||||
target_include_directories(disfuras PRIVATE ${CMAKE_SOURCE_DIR}/include/)
|
||||
target_link_libraries(disfuras PUBLIC furlang libfurc libfurvm)
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/module.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
void print_type(const furvm::mod_type& type, const furvm::mod& mod) {
|
||||
switch (type.type) {
|
||||
case furvm::mod_type::S8: std::cout << "$s8"; return;
|
||||
case furvm::mod_type::S16: std::cout << "$s16"; return;
|
||||
case furvm::mod_type::S32: std::cout << "$s32"; return;
|
||||
case furvm::mod_type::S64: std::cout << "$s64"; return;
|
||||
case furvm::mod_type::U8: std::cout << "$u8"; return;
|
||||
case furvm::mod_type::U16: std::cout << "$u16"; return;
|
||||
case furvm::mod_type::U32: std::cout << "$u32"; return;
|
||||
case furvm::mod_type::U64: std::cout << "$u64"; return;
|
||||
case furvm::mod_type::Ptr:
|
||||
std::cout << "ptr ";
|
||||
print_type(*mod.type_at(type.value.typeRef), mod);
|
||||
return;
|
||||
case furvm::mod_type::Ref:
|
||||
std::cout << "ref ";
|
||||
print_type(*mod.type_at(type.value.typeRef), mod);
|
||||
return;
|
||||
case furvm::mod_type::Array:
|
||||
std::cout << "array ";
|
||||
print_type(*mod.type_at(type.value.array.typeId), mod);
|
||||
if (type.value.array.size == 0) {
|
||||
std::cout << " dynamic";
|
||||
} else {
|
||||
std::cout << ' ' << type.value.array.size;
|
||||
}
|
||||
return;
|
||||
case furvm::mod_type::Import: std::cout << "import " << type.value.imprt.modId << "::t" << type.value.imprt.typeId;
|
||||
case furvm::mod_type::Count: break;
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
static const char* typeNames[furvm::instruction::Count] = {
|
||||
// NoOperation:
|
||||
"nop",
|
||||
// PushS8:
|
||||
"push $__t0",
|
||||
// PushU8:
|
||||
"push $__t1",
|
||||
// PushS16:
|
||||
"push $__t2",
|
||||
// PushU16:
|
||||
"push $__t3",
|
||||
// PushS32:
|
||||
"push $__t4",
|
||||
// PushU32:
|
||||
"push $__t5",
|
||||
// PushConstant:
|
||||
"push",
|
||||
// Array:
|
||||
"array",
|
||||
// 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:
|
||||
"lengthof",
|
||||
// Load:
|
||||
"load",
|
||||
// Store:
|
||||
"store",
|
||||
// LoadGlobal:
|
||||
"loadg",
|
||||
// StoreGlobal:
|
||||
"storeg",
|
||||
// Call:
|
||||
"call",
|
||||
// Jump:
|
||||
"jmp",
|
||||
// JumpNotZero:
|
||||
"jnz",
|
||||
// Return:
|
||||
"ret",
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <module.fmod>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
std::ifstream file(argv[1], std::ios::binary | std::ios::in);
|
||||
|
||||
furvm::mod mod;
|
||||
try {
|
||||
mod = furvm::mod::load(file);
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "Failed to load module " << argv[1] << ": " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "; Generated with disfuras\n";
|
||||
|
||||
std::cout << "; Types:\n";
|
||||
for (const auto& ptr : mod.types()) {
|
||||
const auto& [header, type] = *ptr;
|
||||
std::cout << "type __t" << header.id() << " = ";
|
||||
print_type(type, mod);
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
std::unordered_map<furvm::function_id, std::string> funcNames;
|
||||
std::size_t unnamedCounter = 0;
|
||||
|
||||
std::unordered_map<std::size_t, std::string> labels;
|
||||
std::unordered_set<std::size_t> deadLabels;
|
||||
std::size_t labelCounter = 0;
|
||||
|
||||
std::cout << "\n; Functions:\n";
|
||||
for (const auto& ptr : mod.functions()) {
|
||||
const auto& [header, func] = *ptr;
|
||||
auto it = mod.function_map().find(header.id());
|
||||
if (it != mod.function_map().end()) {
|
||||
std::cout << "public func " << (funcNames[header.id()] = it->second.first);
|
||||
} else {
|
||||
std::cout << "func " << (funcNames[header.id()] = "__f"s + std::to_string(unnamedCounter++));
|
||||
}
|
||||
for (const auto& param : func.signature().params) {
|
||||
std::cout << " $__t" << param.id();
|
||||
}
|
||||
|
||||
std::cout << " = ";
|
||||
if (func.signature().returnType.has_value())
|
||||
std::cout << "$__t" << func.signature().returnType->id() << ' '; // NOLINT
|
||||
switch (func.type()) {
|
||||
case furvm::function_t::Normal: {
|
||||
auto it = labels.find(func.position());
|
||||
if (it == labels.end()) it = labels.emplace(func.position(), funcNames[header.id()]).first;
|
||||
deadLabels.insert(func.position());
|
||||
std::cout << '#' << it->second;
|
||||
} break;
|
||||
case furvm::function_t::Native: {
|
||||
std::cout << "native " << func.native();
|
||||
} break;
|
||||
case furvm::function_t::Import: {
|
||||
std::cout << "import " << func.imp().mod << "::" << func.imp().function;
|
||||
} break;
|
||||
}
|
||||
std::cout << '\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{};
|
||||
off += instr.read(mod.bytecode_view().subview(off));
|
||||
if (instr.type != furvm::instruction::Jump && instr.type != furvm::instruction::JumpNotZero) continue;
|
||||
if (labels.find(off + instr.arg.s8) == labels.end())
|
||||
labels[off + instr.arg.s8] = "__l"s + std::to_string(labelCounter++);
|
||||
deadLabels.insert(off + instr.arg.s8);
|
||||
}
|
||||
// Actual printing pass:
|
||||
for (std::size_t off = 0; off < mod.bytecode().size();) {
|
||||
if (auto it = labels.find(off); it != labels.end()) {
|
||||
std::cout << it->second << ":\n";
|
||||
deadLabels.erase(off);
|
||||
}
|
||||
furvm::instruction instr{};
|
||||
off += instr.read(mod.bytecode_view().subview(off));
|
||||
std::cout << " " << typeNames[instr.type];
|
||||
switch (instr.arg.type) {
|
||||
case furvm::instruction_argument::None: break;
|
||||
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 << ' ' << std::to_string(instr.arg.s8); break;
|
||||
case furvm::instruction_argument::U32: std::cout << ' ' << std::to_string(instr.arg.u8); break;
|
||||
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;
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
if (!deadLabels.empty()) {
|
||||
std::cerr << "Malformed module: invalid jumps\n";
|
||||
return 1;
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "Exception uncaught: " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Disassemble into FIR (furlang's IR)
|
||||
@@ -0,0 +1,5 @@
|
||||
file(GLOB_RECURSE FURAS_SRCS "${CMAKE_SOURCE_DIR}/src/furas/**.cpp")
|
||||
file(GLOB_RECURSE FURAS_HDRS "${CMAKE_SOURCE_DIR}/include/furas/**.hpp")
|
||||
add_executable(furas ${FURAS_SRCS} ${FURAS_HDRS})
|
||||
target_include_directories(furas PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||
target_link_libraries(furas PRIVATE furlang libfurvm)
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "furvm/function.hpp"
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/module.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
@@ -21,48 +22,39 @@ using namespace std::string_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
struct instruction {
|
||||
furvm::instruction_t fur{};
|
||||
enum arg_type {
|
||||
None = 0,
|
||||
Type,
|
||||
Constant,
|
||||
Variable,
|
||||
Function,
|
||||
Label,
|
||||
} arg = None;
|
||||
};
|
||||
|
||||
// NOLINTBEGIN
|
||||
std::unordered_map<enum token::type, instruction> instructions = {
|
||||
{ token::Array, { furvm::instruction_t::Array, instruction::Type } },
|
||||
{ token::Get, { furvm::instruction_t::Get } },
|
||||
{ token::Set, { furvm::instruction_t::Set } },
|
||||
{ token::Drop, { furvm::instruction_t::Drop } },
|
||||
{ token::Dup, { furvm::instruction_t::Duplicate } },
|
||||
{ token::Swap, { furvm::instruction_t::Swap } },
|
||||
{ token::Clone, { furvm::instruction_t::Clone } },
|
||||
{ token::Ref, { furvm::instruction_t::Reference } },
|
||||
{ token::Add, { furvm::instruction_t::Add } },
|
||||
{ token::Sub, { furvm::instruction_t::Sub } },
|
||||
{ token::Mul, { furvm::instruction_t::Mul } },
|
||||
{ token::Div, { furvm::instruction_t::Div } },
|
||||
{ token::Mod, { furvm::instruction_t::Mod } },
|
||||
{ token::Eq, { furvm::instruction_t::Equals } },
|
||||
{ token::Neq, { furvm::instruction_t::NotEquals } },
|
||||
{ token::Lt, { furvm::instruction_t::LessThan } },
|
||||
{ token::Gt, { furvm::instruction_t::GreaterThan } },
|
||||
{ token::Le, { furvm::instruction_t::LessEqual } },
|
||||
{ token::Ge, { furvm::instruction_t::GreaterEqual } },
|
||||
{ token::Ptrof, { furvm::instruction_t::Pointerof } },
|
||||
{ token::Sizeof, { furvm::instruction_t::Sizeof } },
|
||||
{ token::Lenof, { furvm::instruction_t::Lengthof } },
|
||||
{ token::Load, { furvm::instruction_t::Load, instruction::Variable } },
|
||||
{ token::Store, { furvm::instruction_t::Store, instruction::Variable } },
|
||||
{ token::Call, { furvm::instruction_t::Call, instruction::Function } },
|
||||
{ token::Jmp, { furvm::instruction_t::Jump, instruction::Label } },
|
||||
{ token::Jnz, { furvm::instruction_t::JumpNotZero, instruction::Label } },
|
||||
{ token::Ret, { furvm::instruction_t::Return } },
|
||||
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 },
|
||||
{ token::Dup, furvm::instruction_t::Duplicate },
|
||||
{ token::Swap, furvm::instruction_t::Swap },
|
||||
{ token::Clone, furvm::instruction_t::Clone },
|
||||
{ token::Ref, furvm::instruction_t::Reference },
|
||||
{ token::Add, furvm::instruction_t::Add },
|
||||
{ token::Sub, furvm::instruction_t::Sub },
|
||||
{ token::Mul, furvm::instruction_t::Mul },
|
||||
{ token::Div, furvm::instruction_t::Div },
|
||||
{ token::Mod, furvm::instruction_t::Mod },
|
||||
{ token::Eq, furvm::instruction_t::Equals },
|
||||
{ token::Neq, furvm::instruction_t::NotEquals },
|
||||
{ token::Lt, furvm::instruction_t::LessThan },
|
||||
{ token::Gt, furvm::instruction_t::GreaterThan },
|
||||
{ token::Le, furvm::instruction_t::LessEqual },
|
||||
{ token::Ge, furvm::instruction_t::GreaterEqual },
|
||||
{ token::Ptrof, furvm::instruction_t::Pointerof },
|
||||
{ token::Sizeof, furvm::instruction_t::Sizeof },
|
||||
{ token::Lenof, furvm::instruction_t::Lengthof },
|
||||
{ token::Load, furvm::instruction_t::Load },
|
||||
{ token::Store, furvm::instruction_t::Store },
|
||||
{ token::LoadGlobal, furvm::instruction_t::LoadGlobal },
|
||||
{ token::StoreGlobal, furvm::instruction_t::StoreGlobal },
|
||||
{ token::Call, furvm::instruction_t::Call },
|
||||
{ token::Jmp, furvm::instruction_t::Jump },
|
||||
{ token::Jnz, furvm::instruction_t::JumpNotZero },
|
||||
{ token::Ret, furvm::instruction_t::Return },
|
||||
};
|
||||
// NOLINTEND
|
||||
|
||||
@@ -84,8 +76,10 @@ const char* token_type(enum token::type type) {
|
||||
case token::Import: return "import";
|
||||
case token::Public: return "public";
|
||||
case token::Private: return "private";
|
||||
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";
|
||||
@@ -109,6 +103,8 @@ const char* token_type(enum token::type type) {
|
||||
case token::Lenof: return "lenof";
|
||||
case token::Load: return "load";
|
||||
case token::Store: return "store";
|
||||
case token::LoadGlobal: return "loadg";
|
||||
case token::StoreGlobal: return "storeg";
|
||||
case token::Call: return "call";
|
||||
case token::Jmp: return "jmp";
|
||||
case token::Jnz: return "jnz";
|
||||
@@ -133,13 +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;
|
||||
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;
|
||||
|
||||
@@ -153,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));
|
||||
@@ -188,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 };
|
||||
@@ -209,23 +291,31 @@ 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) {
|
||||
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(label.offset - unknown);
|
||||
const auto jmpOff = static_cast<std::ptrdiff_t>(label.offset) - static_cast<std::ptrdiff_t>(unknown);
|
||||
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
|
||||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
|
||||
assert(false); // TODO: Further jumps are not implemented
|
||||
}
|
||||
mod.bytecode()[unknown - 1] = jmpOff;
|
||||
mod.bytecode()[unknown - 1] = static_cast<std::int8_t>(jmpOff);
|
||||
}
|
||||
label.functions = {};
|
||||
label.unknowns = {};
|
||||
return { generator_error::Success };
|
||||
}
|
||||
|
||||
case token::Allocate: {
|
||||
result = eat_token(lexer, token::Identifier);
|
||||
if (!result) return result.error;
|
||||
std::string name = std::string(result->value.string);
|
||||
variables.emplace(name, variables.size());
|
||||
mod.set_global_variable_count(variables.size());
|
||||
return { generator_error::Success };
|
||||
}
|
||||
|
||||
case token::Func:
|
||||
case token::Type:
|
||||
case token::Public:
|
||||
@@ -248,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;
|
||||
@@ -277,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()) {
|
||||
@@ -296,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: {
|
||||
@@ -312,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: {
|
||||
@@ -337,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,
|
||||
@@ -424,6 +477,7 @@ struct mod_context {
|
||||
}
|
||||
|
||||
case token::Array:
|
||||
case token::Slice:
|
||||
case token::Get:
|
||||
case token::Set:
|
||||
case token::Drop:
|
||||
@@ -447,16 +501,19 @@ 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:
|
||||
case token::Ret: {
|
||||
auto it = instructions.find(result->type);
|
||||
assert(it != instructions.end());
|
||||
mod.bytecode().push_back(static_cast<furvm::byte>(it->second.fur));
|
||||
switch (it->second.arg) {
|
||||
case instruction::None: break;
|
||||
case instruction::Type: {
|
||||
furvm::instruction instr{ it->second };
|
||||
instr.arg.type = furvm::instruction::s_arguments[instr.type];
|
||||
switch (instr.arg.type) {
|
||||
case furvm::instruction_argument::None: break;
|
||||
case furvm::instruction_argument::Type: {
|
||||
result = eat_token(lexer, token::Dolar);
|
||||
if (!result) return result.error;
|
||||
result = eat_token(lexer, token::Identifier);
|
||||
@@ -464,46 +521,43 @@ struct mod_context {
|
||||
auto type = types.find(std::string(result->value.string));
|
||||
if (type == types.end())
|
||||
return { generator_error::UnknownType, "Unknown type "s + std::string(result->value.string) };
|
||||
auto id = type->second.id();
|
||||
mod.bytecode().push_back((id >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 8) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 16) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 24) & 0xFF);
|
||||
instr.arg.u32 = type->second.id();
|
||||
} break;
|
||||
case instruction::Constant: {
|
||||
case furvm::instruction_argument::Constant: {
|
||||
assert(false); // TODO: Unimplemented
|
||||
} break;
|
||||
case instruction::Variable: {
|
||||
case furvm::instruction_argument::Variable: {
|
||||
result = eat_token(lexer, token::Percent);
|
||||
if (!result) return result.error;
|
||||
result = eat_token(lexer, token::Unsigned);
|
||||
if (!result) return result.error;
|
||||
std::uint16_t var = result->value.uint;
|
||||
mod.bytecode().push_back((var >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((var >> 8) & 0xFF);
|
||||
instr.arg.u16 = result->value.uint;
|
||||
} break;
|
||||
case instruction::Function: {
|
||||
case furvm::instruction_argument::GlobalVariable: {
|
||||
result = eat_token(lexer, token::Percent);
|
||||
if (!result) return result.error;
|
||||
result = eat_token(lexer, token::Identifier);
|
||||
if (!result) return result.error;
|
||||
if (auto it = variables.find(std::string(result->value.string)); it != variables.end())
|
||||
instr.arg.u16 = it->second;
|
||||
else
|
||||
throw std::runtime_error("Unknown global variable");
|
||||
} break;
|
||||
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();
|
||||
mod.bytecode().push_back((id >> 0) & 0xFF);
|
||||
mod.bytecode().push_back((id >> 8) & 0xFF);
|
||||
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 instruction::Label: {
|
||||
case furvm::instruction_argument::Offset: {
|
||||
result = eat_token(lexer, token::Sha256);
|
||||
if (!result) return result.error;
|
||||
result = eat_token(lexer, token::Identifier);
|
||||
@@ -511,18 +565,28 @@ struct mod_context {
|
||||
auto& label = labels[std::string(result->value.string)];
|
||||
auto offset = label.offset;
|
||||
if (offset == label_context::INVALID) {
|
||||
label.unknowns.push_back(mod.bytecode().size() + 1);
|
||||
mod.bytecode().push_back(0);
|
||||
return { generator_error::Success };
|
||||
label.unknowns.push_back(mod.bytecode().size() + 2);
|
||||
instr.arg.s8 = 0;
|
||||
break;
|
||||
}
|
||||
std::ptrdiff_t jmpOff = static_cast<std::ptrdiff_t>(offset - mod.bytecode().size() - 1);
|
||||
const auto jmpOff =
|
||||
static_cast<std::ptrdiff_t>(offset) - static_cast<std::ptrdiff_t>(mod.bytecode().size()) - 2;
|
||||
if (jmpOff < std::numeric_limits<std::int8_t>::min() ||
|
||||
jmpOff > std::numeric_limits<std::int8_t>::max()) {
|
||||
assert(false); // TODO: Further jumps are not implemented
|
||||
}
|
||||
mod.bytecode().push_back(jmpOff);
|
||||
instr.arg.s8 = static_cast<std::int8_t>(jmpOff);
|
||||
} break;
|
||||
case furvm::instruction_argument::S8:
|
||||
case furvm::instruction_argument::U8:
|
||||
case furvm::instruction_argument::S16:
|
||||
case furvm::instruction_argument::U16:
|
||||
case furvm::instruction_argument::S32:
|
||||
case furvm::instruction_argument::U32:
|
||||
case furvm::instruction_argument::Count:
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
instr.write(mod.bytecode());
|
||||
return { generator_error::Success };
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -39,9 +39,11 @@ token_r lexer::next_token() {
|
||||
{ "import", token::Import },
|
||||
{ "public", token::Public },
|
||||
{ "private", token::Private },
|
||||
{ "allocate", token::Allocate },
|
||||
|
||||
{ "push", token::Push },
|
||||
{ "array", token::Array },
|
||||
{ "slice", token::Slice },
|
||||
{ "get", token::Get },
|
||||
{ "set", token::Set },
|
||||
{ "drop", token::Drop },
|
||||
@@ -65,6 +67,8 @@ token_r lexer::next_token() {
|
||||
{ "lenof", token::Lenof },
|
||||
{ "load", token::Load },
|
||||
{ "store", token::Store },
|
||||
{ "loadg", token::LoadGlobal },
|
||||
{ "storeg", token::StoreGlobal },
|
||||
{ "call", token::Call },
|
||||
{ "jmp", token::Jmp },
|
||||
{ "jnz", token::Jnz },
|
||||
@@ -89,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] + "'" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
file(GLOB_RECURSE FURC_SRCS "src/**.cpp")
|
||||
file(GLOB_RECURSE FURC_HDRS "include/**.hpp")
|
||||
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 include/)
|
||||
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 src/main.cpp)
|
||||
add_executable(furc "${CMAKE_SOURCE_DIR}/src/furc/main.cpp")
|
||||
target_link_libraries(furc PRIVATE libfurc)
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
file(GLOB_RECURSE FURC_TESTS "test/**.cpp")
|
||||
add_executable(furc_tests ${FURC_TESTS})
|
||||
add_executable(furc_tests "${CMAKE_SOURCE_DIR}/test/furc/ssa.cpp")
|
||||
target_link_libraries(furc_tests PRIVATE libfurc GTest::gtest_main)
|
||||
gtest_discover_tests(furc_tests)
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "furc/back/furvm/target.hpp"
|
||||
|
||||
#include "furvm/function.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furc {
|
||||
|
||||
furvm::mod furvm_generator::generate(const ir_module& mod) {
|
||||
if (!mod.variables.empty()) throw std::runtime_error("global variables are not supported for furvm yet");
|
||||
|
||||
furvm::mod furvmMod;
|
||||
for (const auto& func : mod.functions) {
|
||||
furvmMod.emplace_function(furvm::function_sig{ {}, {} }, furvmMod.bytecode().size());
|
||||
}
|
||||
|
||||
return furvmMod;
|
||||
}
|
||||
|
||||
} // namespace furc
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "furc/front/lexer.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace furc {
|
||||
|
||||
token lexer::next_token() {
|
||||
if (m_peekToken.empty()) return get_token();
|
||||
auto tok = m_peekToken.front();
|
||||
m_peekToken.pop_front();
|
||||
return tok;
|
||||
}
|
||||
|
||||
token lexer::peek_token(std::size_t offset) {
|
||||
if (m_peekToken.size() <= offset) {
|
||||
while (m_peekToken.size() <= offset) {
|
||||
auto tok = get_token();
|
||||
m_peekToken.push_back(tok);
|
||||
}
|
||||
}
|
||||
return m_peekToken[offset];
|
||||
}
|
||||
|
||||
token lexer::get_token() {
|
||||
skip_spaces();
|
||||
|
||||
if (m_cursor >= m_content.size()) return { location(), token::EndOfFile };
|
||||
|
||||
auto loc = location();
|
||||
|
||||
if (std::isdigit(get()) != 0) {
|
||||
std::uint64_t value = get() - '0';
|
||||
next();
|
||||
while (m_cursor < m_content.size() && std::isdigit(get()) != 0) {
|
||||
static constexpr std::uint64_t MAX = std::numeric_limits<std::uint64_t>::max();
|
||||
static constexpr std::uint64_t MAX_MULTS = MAX / 10;
|
||||
static constexpr std::uint64_t LAST_DIGIT = MAX % 10;
|
||||
|
||||
if (value > MAX_MULTS) {
|
||||
return { location(), token::InvalidInteger };
|
||||
}
|
||||
std::uint64_t digit = get() - '0';
|
||||
if (value == MAX_MULTS && digit > LAST_DIGIT) {
|
||||
return { location(), token::InvalidInteger };
|
||||
}
|
||||
|
||||
value *= 10;
|
||||
value += digit;
|
||||
next();
|
||||
}
|
||||
return { loc, value };
|
||||
}
|
||||
|
||||
if (std::isalnum(get()) != 0 || get() == '_') {
|
||||
static std::unordered_map<std::string_view, token_t> s_keywords = {
|
||||
{ "func", token::Func },
|
||||
{ "return", token::Return },
|
||||
{ "if", token::If },
|
||||
{ "else", token::Else },
|
||||
{ "while", token::While },
|
||||
{ "public", token::Public },
|
||||
{ "private", token::Private },
|
||||
{ "pre", token::Pre },
|
||||
{ "post", token::Post },
|
||||
{ "pointerof", token::Pointerof },
|
||||
{ "sizeof", token::Sizeof },
|
||||
{ "lengthof", token::Lengthof },
|
||||
{ "s8", token::S8 },
|
||||
{ "u8", token::U8 },
|
||||
{ "s16", token::S16 },
|
||||
{ "u16", token::U16 },
|
||||
{ "s32", token::S32 },
|
||||
{ "u32", token::U32 },
|
||||
{ "s64", token::S64 },
|
||||
{ "u64", token::U64 },
|
||||
};
|
||||
|
||||
std::size_t begin = m_cursor;
|
||||
next();
|
||||
while (m_cursor < m_content.size() && (std::isalnum(get()) != 0 || get() == '_'))
|
||||
next();
|
||||
std::string_view name = m_content.substr(begin, m_cursor - begin);
|
||||
if (auto it = s_keywords.find(name); it != s_keywords.end()) {
|
||||
return { loc, it->second };
|
||||
}
|
||||
return { loc, token::Identifier, name };
|
||||
}
|
||||
|
||||
if (get() == '"') {
|
||||
next();
|
||||
|
||||
std::size_t begin = m_cursor;
|
||||
while (m_cursor < m_content.size() && get() != '"')
|
||||
next();
|
||||
if (m_cursor >= m_content.size()) return { location(), token::UnexpectedEOF };
|
||||
|
||||
next();
|
||||
return { loc, token::String, m_content.substr(begin, m_cursor - begin - 1) };
|
||||
}
|
||||
|
||||
if (get() == '\'') {
|
||||
next();
|
||||
bool slash = get() == '\\';
|
||||
if (slash) next();
|
||||
auto loc2 = location();
|
||||
char character = get();
|
||||
next();
|
||||
if (get() != '\'') return { location(), token::UnexpectedCharacter, get() };
|
||||
next();
|
||||
if (slash) {
|
||||
switch (character) {
|
||||
case '\\': character = '\\'; break;
|
||||
case 'n': character = '\n'; break;
|
||||
case 'r': character = '\r'; break;
|
||||
case 't': character = '\t'; break;
|
||||
default: return { loc2, token::UnexpectedCharacter, character };
|
||||
}
|
||||
}
|
||||
return { loc, token::Char, character };
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string_view, token_t> s_tokens = {
|
||||
{ "(", token::LParen },
|
||||
{ ")", token::RParen },
|
||||
{ "{", token::LBrace },
|
||||
{ "}", token::RBrace },
|
||||
{ "[", token::LBracket },
|
||||
{ "]", token::RBracket },
|
||||
{ ";", token::Semicolon },
|
||||
{ ":", token::Colon },
|
||||
{ ",", token::Comma },
|
||||
{ ".", token::Dot },
|
||||
{ "+", token::Plus },
|
||||
{ "-", token::Minus },
|
||||
{ "*", token::Star },
|
||||
{ "/", token::Slash },
|
||||
{ "%", token::Percent },
|
||||
{ "&", token::Ampersand },
|
||||
{ "|", token::Pipe },
|
||||
{ "^", token::Hat },
|
||||
{ "&&", token::DblAmpersand },
|
||||
{ "||", token::DblPipe },
|
||||
{ "++", token::DblPlus },
|
||||
{ "--", token::DblMinus },
|
||||
{ "!", token::ExMark },
|
||||
{ "^^", token::CatEars },
|
||||
{ "=", token::Equals },
|
||||
{ "+=", token::PlusEquals },
|
||||
{ "-=", token::MinusEquals },
|
||||
{ "*=", token::StarEquals },
|
||||
{ "/=", token::SlashEquals },
|
||||
{ "%=", token::PercentEquals },
|
||||
{ "&=", token::AmpersandEquals },
|
||||
{ "|=", token::PipeEquals },
|
||||
{ "^=", token::HatEquals },
|
||||
{ "==", token::DblEquals },
|
||||
{ "!=", token::ExEquals },
|
||||
{ "<", token::LessThan },
|
||||
{ "<=", token::LessEquals },
|
||||
{ ">", token::GreaterThan },
|
||||
{ ">=", token::GreaterEquals },
|
||||
{ "->", token::SlimArrow },
|
||||
{ "=>", token::FatArrow },
|
||||
{ "@", token::Monkey },
|
||||
{ "#", token::Sha256 },
|
||||
};
|
||||
|
||||
std::size_t begin = m_cursor;
|
||||
std::size_t len = 1;
|
||||
while (begin + len - 1 < m_content.size() && s_tokens.find(m_content.substr(begin, len)) != s_tokens.end())
|
||||
++len;
|
||||
|
||||
if (len > 1) {
|
||||
auto type = s_tokens[m_content.substr(begin, len - 1)];
|
||||
m_cursor += len - 1;
|
||||
return { loc, type };
|
||||
}
|
||||
|
||||
return { loc, token::UnexpectedCharacter, get() };
|
||||
}
|
||||
|
||||
void lexer::next() {
|
||||
if (m_cursor < m_content.size()) ++m_cursor;
|
||||
}
|
||||
|
||||
constexpr char lexer::get(std::size_t offset) const {
|
||||
return m_content[m_cursor + offset];
|
||||
}
|
||||
|
||||
void lexer::skip_spaces() {
|
||||
while (m_cursor < m_content.size() && std::isspace(get()) != 0) {
|
||||
if (m_content[m_cursor++] == '\n') {
|
||||
++m_row;
|
||||
m_lineStart = m_cursor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr token::location lexer::location() const {
|
||||
return { m_filepath, m_row, m_cursor - m_lineStart };
|
||||
}
|
||||
|
||||
} // namespace furc
|
||||
@@ -0,0 +1,385 @@
|
||||
#include "furc/front/parser.hpp"
|
||||
|
||||
#include "furc/front/ast.hpp"
|
||||
#include "furc/front/token.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace furc {
|
||||
|
||||
ast parser::parse() {
|
||||
ast tree;
|
||||
while (m_lexer.peek_token().type != token::EndOfFile) {
|
||||
auto* decl = parse_decl();
|
||||
assert(decl);
|
||||
tree.decls.push_back(decl);
|
||||
}
|
||||
|
||||
return std::move(tree);
|
||||
}
|
||||
|
||||
stmt_node* parser::parse_stmt() {
|
||||
switch (m_lexer.peek_token().type) {
|
||||
case token::LBrace: return m_arena->allocate<comp_stmt_node>(parse_comp());
|
||||
case token::Return: {
|
||||
m_lexer.next_token();
|
||||
return_stmt_node node;
|
||||
if (m_lexer.peek_token().type != token::Semicolon) {
|
||||
node.value = parse_expr();
|
||||
}
|
||||
eat_token(token::Semicolon);
|
||||
return m_arena->allocate<return_stmt_node>(std::move(node));
|
||||
}
|
||||
case token::If: {
|
||||
m_lexer.next_token();
|
||||
eat_token(token::LParen);
|
||||
if_stmt_node node;
|
||||
node.cond = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
node.thenBranch = parse_stmt();
|
||||
if (m_lexer.peek_token().type == token::Else) {
|
||||
m_lexer.next_token();
|
||||
node.elseBranch = parse_stmt();
|
||||
}
|
||||
return m_arena->allocate<if_stmt_node>(std::move(node));
|
||||
}
|
||||
case token::While: {
|
||||
m_lexer.next_token();
|
||||
eat_token(token::LParen);
|
||||
while_stmt_node node;
|
||||
node.cond = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
node.body = parse_stmt();
|
||||
return m_arena->allocate<while_stmt_node>(std::move(node));
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::Func) {
|
||||
eat_token(token::Func);
|
||||
func_decl_node func;
|
||||
|
||||
func.name = std::string(eat_token(token::Identifier).value.string);
|
||||
eat_token(token::LParen);
|
||||
if (m_lexer.peek_token().type != token::RParen) {
|
||||
do {
|
||||
var_decl_node param;
|
||||
|
||||
param.name = std::string(eat_token(token::Identifier).value.string);
|
||||
eat_token(token::Colon);
|
||||
param.type = parse_type();
|
||||
if (m_lexer.peek_token().type == token::Equals) {
|
||||
m_lexer.next_token();
|
||||
param.init = parse_expr();
|
||||
}
|
||||
func.params.emplace_back(std::move(param));
|
||||
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||
} else {
|
||||
eat_token(token::RParen);
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::SlimArrow) {
|
||||
m_lexer.next_token();
|
||||
func.type = parse_type();
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::Semicolon) {
|
||||
m_lexer.next_token();
|
||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||
}
|
||||
|
||||
func_decl_node::def_s def;
|
||||
|
||||
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||
auto token = m_lexer.next_token();
|
||||
eat_token(token::LParen);
|
||||
auto* expr = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
|
||||
switch (token.type) {
|
||||
case token::Pre: {
|
||||
def.preConds.push_back(expr);
|
||||
} break;
|
||||
case token::Post: {
|
||||
def.postConds.push_back(expr);
|
||||
} break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
def.body = parse_comp();
|
||||
func.def = std::move(def);
|
||||
|
||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::Identifier &&
|
||||
(m_lexer.peek_token(1).type == token::Colon || m_lexer.peek_token(1).type == token::Equals)) {
|
||||
var_decl_node var;
|
||||
var.name = std::string(eat_token(token::Identifier).value.string);
|
||||
eat_token(token::Colon); // TODO: Auto-deduce the type
|
||||
var.type = parse_type();
|
||||
if (eat_token(token::Equals, token::Semicolon).type == token::Equals) {
|
||||
var.init = parse_expr();
|
||||
eat_token(token::Semicolon);
|
||||
}
|
||||
|
||||
return m_arena->allocate<var_decl_node>(std::move(var));
|
||||
}
|
||||
|
||||
auto* expr = parse_expr();
|
||||
eat_token(token::Semicolon);
|
||||
return expr;
|
||||
}
|
||||
|
||||
decl_node* parser::parse_decl() {
|
||||
auto first = eat_token(token::Identifier, token::Func);
|
||||
if (first.type == token::Func) {
|
||||
func_decl_node func;
|
||||
|
||||
func.name = std::string(eat_token(token::Identifier).value.string);
|
||||
eat_token(token::LParen);
|
||||
if (m_lexer.peek_token().type != token::RParen) {
|
||||
do {
|
||||
var_decl_node param;
|
||||
|
||||
param.name = std::string(eat_token(token::Identifier).value.string);
|
||||
eat_token(token::Colon);
|
||||
param.type = parse_type();
|
||||
if (m_lexer.peek_token().type == token::Equals) {
|
||||
m_lexer.next_token();
|
||||
param.init = parse_expr();
|
||||
}
|
||||
func.params.emplace_back(std::move(param));
|
||||
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||
} else {
|
||||
eat_token(token::RParen);
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::SlimArrow) {
|
||||
m_lexer.next_token();
|
||||
func.type = parse_type();
|
||||
}
|
||||
|
||||
if (m_lexer.peek_token().type == token::Semicolon) {
|
||||
m_lexer.next_token();
|
||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||
}
|
||||
|
||||
func_decl_node::def_s def;
|
||||
|
||||
while (m_lexer.peek_token().type == token::Pre || m_lexer.peek_token().type == token::Post) {
|
||||
auto token = m_lexer.next_token();
|
||||
eat_token(token::LParen);
|
||||
auto* expr = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
|
||||
switch (token.type) {
|
||||
case token::Pre: {
|
||||
def.preConds.push_back(expr);
|
||||
} break;
|
||||
case token::Post: {
|
||||
def.postConds.push_back(expr);
|
||||
} break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
def.body = parse_comp();
|
||||
func.def = std::move(def);
|
||||
|
||||
return m_arena->allocate<func_decl_node>(std::move(func));
|
||||
}
|
||||
|
||||
var_decl_node var;
|
||||
var.name = std::string(first.value.string);
|
||||
eat_token(token::Colon); // TODO: Auto-deduce the type
|
||||
var.type = parse_type();
|
||||
if (eat_token(token::Equals, token::Semicolon).type == token::Equals) {
|
||||
var.init = parse_expr();
|
||||
eat_token(token::Semicolon);
|
||||
}
|
||||
|
||||
return m_arena->allocate<var_decl_node>(std::move(var));
|
||||
}
|
||||
|
||||
expr_node* parser::parse_expr() {
|
||||
return parse_expr_right(parse_expr_unary());
|
||||
}
|
||||
|
||||
ast_type parser::parse_type() {
|
||||
auto token =
|
||||
eat_token(token::S8, token::U8, token::S16, token::U16, token::S32, token::U32, token::S64, token::U64);
|
||||
switch (token.type) {
|
||||
case token::S8: return { ast_type::S8 };
|
||||
case token::U8: return { ast_type::U8 };
|
||||
case token::S16: return { ast_type::S16 };
|
||||
case token::U16: return { ast_type::U16 };
|
||||
case token::S32: return { ast_type::S32 };
|
||||
case token::U32: return { ast_type::U32 };
|
||||
case token::S64: return { ast_type::S64 };
|
||||
case token::U64: return { ast_type::U64 };
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
comp_stmt_node parser::parse_comp() {
|
||||
comp_stmt_node comp;
|
||||
|
||||
eat_token(token::LBrace);
|
||||
while (m_lexer.peek_token().type != token::EndOfFile && m_lexer.peek_token().type != token::RBrace) {
|
||||
comp.stmts.push_back(parse_stmt());
|
||||
}
|
||||
eat_token(token::RBrace);
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
expr_node* parser::parse_expr_primary() {
|
||||
auto token = eat_token(token::Identifier, token::LParen, token::If, token::Integer, token::Char);
|
||||
switch (token.type) {
|
||||
case token::Identifier: {
|
||||
return m_arena->allocate<var_read_expr_node>(std::string(token.value.string));
|
||||
}
|
||||
case token::LParen: {
|
||||
group_expr_node group;
|
||||
group.inner = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
return m_arena->allocate<group_expr_node>(std::move(group));
|
||||
}
|
||||
case token::If: {
|
||||
if_expr_node ifExpr;
|
||||
eat_token(token::LParen);
|
||||
ifExpr.cond = parse_expr();
|
||||
eat_token(token::RParen);
|
||||
ifExpr.thenExpr = parse_expr();
|
||||
eat_token(token::Else);
|
||||
ifExpr.elseExpr = parse_expr();
|
||||
return m_arena->allocate<if_expr_node>(std::move(ifExpr));
|
||||
}
|
||||
case token::Integer: {
|
||||
return m_arena->allocate<int_lit_node>(int_lit_node(token.value.integer));
|
||||
}
|
||||
case token::Char: {
|
||||
return m_arena->allocate<char_lit_node>(char_lit_node(token.value.character));
|
||||
}
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
expr_node* parser::parse_expr_unary() {
|
||||
static std::unordered_map<token_t, unary_op_expr_node::unary_op_type> s_prefixOps = {
|
||||
{ token::Plus, unary_op_expr_node::Positive },
|
||||
{ token::Minus, unary_op_expr_node::Negative },
|
||||
{ token::DblPlus, unary_op_expr_node::PreInc },
|
||||
{ token::DblMinus, unary_op_expr_node::PreDec },
|
||||
{ token::Tilde, unary_op_expr_node::BinNot },
|
||||
{ token::ExMark, unary_op_expr_node::Not },
|
||||
|
||||
{ token::Sizeof, unary_op_expr_node::Sizeof },
|
||||
{ token::Pointerof, unary_op_expr_node::Pointerof },
|
||||
{ token::Lengthof, unary_op_expr_node::Lengthof },
|
||||
};
|
||||
|
||||
static std::unordered_map<token_t, unary_op_expr_node::unary_op_type> s_postfixOps = {
|
||||
{ token::DblPlus, unary_op_expr_node::PostInc },
|
||||
{ token::DblMinus, unary_op_expr_node::PostDec },
|
||||
};
|
||||
|
||||
auto it = s_prefixOps.find(m_lexer.peek_token().type);
|
||||
if (it == s_prefixOps.end()) {
|
||||
auto* expr = parse_expr_primary();
|
||||
while (true) {
|
||||
auto postIt = s_postfixOps.find(m_lexer.peek_token().type);
|
||||
if (postIt == s_postfixOps.end()) return expr;
|
||||
m_lexer.next_token();
|
||||
|
||||
unary_op_expr_node unary;
|
||||
unary.lhs = expr;
|
||||
unary.type = postIt->second;
|
||||
expr = m_arena->allocate<unary_op_expr_node>(std::move(unary));
|
||||
}
|
||||
}
|
||||
|
||||
auto token = m_lexer.next_token();
|
||||
|
||||
unary_op_expr_node unary;
|
||||
unary.lhs = parse_expr_unary();
|
||||
unary.type = it->second;
|
||||
return m_arena->allocate<unary_op_expr_node>(std::move(unary));
|
||||
}
|
||||
|
||||
expr_node* parser::parse_expr_right(expr_node* lhs, std::uint32_t precedence) {
|
||||
struct op_info {
|
||||
enum type_e {
|
||||
Binary = 0,
|
||||
FunctionCall,
|
||||
} type;
|
||||
std::uint32_t precedence;
|
||||
union {
|
||||
binary_op_expr_node::binary_op_type binary;
|
||||
};
|
||||
bool right = false;
|
||||
};
|
||||
|
||||
static std::unordered_map<token_t, op_info> s_ops = {
|
||||
{ token::Plus, { op_info::Binary, 4, binary_op_expr_node::Add } },
|
||||
{ token::Minus, { op_info::Binary, 4, binary_op_expr_node::Sub } },
|
||||
{ token::Star, { op_info::Binary, 3, binary_op_expr_node::Mul } },
|
||||
{ token::Slash, { op_info::Binary, 3, binary_op_expr_node::Div } },
|
||||
{ token::Percent, { op_info::Binary, 3, binary_op_expr_node::Mod } },
|
||||
{ token::DblLT, { op_info::Binary, 5, binary_op_expr_node::Shl } },
|
||||
{ token::DblGT, { op_info::Binary, 5, binary_op_expr_node::Shr } },
|
||||
{ token::Ampersand, { op_info::Binary, 8, binary_op_expr_node::BinAnd } },
|
||||
{ token::Pipe, { op_info::Binary, 10, binary_op_expr_node::BinOr } },
|
||||
{ token::Hat, { op_info::Binary, 9, binary_op_expr_node::BinXor } },
|
||||
{ token::DblAmpersand, { op_info::Binary, 11, binary_op_expr_node::And } },
|
||||
{ token::DblPipe, { op_info::Binary, 12, binary_op_expr_node::Or } },
|
||||
{ token::DblEquals, { op_info::Binary, 7, binary_op_expr_node::Equals } },
|
||||
{ token::ExEquals, { op_info::Binary, 7, binary_op_expr_node::NotEquals } },
|
||||
{ token::LessThan, { op_info::Binary, 6, binary_op_expr_node::LessThan } },
|
||||
{ token::LessEquals, { op_info::Binary, 6, binary_op_expr_node::LessEquals } },
|
||||
{ token::GreaterThan, { op_info::Binary, 6, binary_op_expr_node::GreaterThan } },
|
||||
{ token::GreaterEquals, { op_info::Binary, 6, binary_op_expr_node::GreaterEquals } },
|
||||
{ token::LParen, { op_info::FunctionCall, 1 } },
|
||||
};
|
||||
|
||||
while (true) {
|
||||
auto it = s_ops.find(m_lexer.peek_token().type);
|
||||
if (it == s_ops.end() || it->second.precedence >= precedence) return lhs;
|
||||
auto op = it->second;
|
||||
m_lexer.next_token();
|
||||
|
||||
if (op.type == op_info::FunctionCall) {
|
||||
func_call_expr_node funcCall;
|
||||
funcCall.lhs = lhs;
|
||||
|
||||
if (m_lexer.peek_token().type != token::RParen) {
|
||||
do {
|
||||
funcCall.args.push_back(parse_expr());
|
||||
} while (eat_token(token::Comma, token::RParen).type == token::Comma);
|
||||
} else {
|
||||
m_lexer.next_token();
|
||||
}
|
||||
|
||||
lhs = m_arena->allocate<func_call_expr_node>(std::move(funcCall));
|
||||
} else {
|
||||
binary_op_expr_node binary;
|
||||
binary.lhs = lhs;
|
||||
binary.rhs = parse_expr_unary();
|
||||
binary.type = op.binary;
|
||||
|
||||
auto nextIt = s_ops.find(m_lexer.peek_token().type);
|
||||
if (nextIt != s_ops.end()) {
|
||||
binary.rhs = parse_expr_right(binary.rhs, op.precedence + (op.right ? 1 : 0));
|
||||
}
|
||||
|
||||
lhs = m_arena->allocate<binary_op_expr_node>(std::move(binary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furc
|
||||
@@ -0,0 +1,31 @@
|
||||
#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) {
|
||||
furlang::arena arena;
|
||||
|
||||
std::string_view content = R"(
|
||||
func main(argc: u64) -> s32 {
|
||||
x: s32 = 1 + 2 * 3;
|
||||
return if (x == 9) 1 else 0;
|
||||
}
|
||||
)";
|
||||
|
||||
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
|
||||
@@ -0,0 +1,254 @@
|
||||
#include "furc/middle/ir.hpp"
|
||||
|
||||
#include "furc/front/ast.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace furc {
|
||||
|
||||
static ir_type ast_type_to_ir(const ast_type& type) {
|
||||
switch (type.type) {
|
||||
case ast_type::Void: return { ir_type::Void };
|
||||
case ast_type::S8: return { ir_type::S8 };
|
||||
case ast_type::U8: return { ir_type::U8 };
|
||||
case ast_type::S16: return { ir_type::S16 };
|
||||
case ast_type::U16: return { ir_type::U16 };
|
||||
case ast_type::S32: return { ir_type::S32 };
|
||||
case ast_type::U32: return { ir_type::U32 };
|
||||
case ast_type::S64: return { ir_type::S64 };
|
||||
case ast_type::U64: return { ir_type::U64 };
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
void ir_generator::visit_comp_stmt_node(const comp_stmt_node& node) {
|
||||
// TODO: Introduce scopes for statements to naturally allow variable shadowing.
|
||||
for (const auto& stmt : node.stmts) {
|
||||
stmt->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit_if_stmt_node(const if_stmt_node& node) {
|
||||
node.cond->accept(*this);
|
||||
auto* branch = context().terminate(context().last_register(), context().blockIdx + 1, 0);
|
||||
|
||||
context().new_next();
|
||||
node.thenBranch->accept(*this);
|
||||
context().new_next();
|
||||
branch->destination->value.blockPair.second = context().blockIdx; // NOLINT
|
||||
if (node.elseBranch != nullptr) {
|
||||
node.elseBranch->accept(*this);
|
||||
context().new_next();
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit_while_stmt_node(const while_stmt_node& node) {
|
||||
context().new_next();
|
||||
auto header = context().blockIdx;
|
||||
node.cond->accept(*this);
|
||||
auto* branch = context().terminate(context().last_register(), context().blockIdx + 1, 0);
|
||||
|
||||
context().new_next();
|
||||
auto body = context().blockIdx;
|
||||
node.body->accept(*this);
|
||||
if (!context().blockPtr->is_terminated()) context().terminate(header);
|
||||
|
||||
context().new_next();
|
||||
branch->destination->value.blockPair.second = context().blockIdx; // NOLINT
|
||||
}
|
||||
|
||||
void ir_generator::visit_return_stmt_node(const return_stmt_node& node) {
|
||||
if (node.value == nullptr) {
|
||||
context().terminate();
|
||||
} else {
|
||||
node.value->accept(*this);
|
||||
context().terminate(context().last_register());
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit_var_decl_node(const var_decl_node& node) {
|
||||
const auto* var = m_scope->allocate(m_module.arena, node.name, ast_type_to_ir(node.type));
|
||||
|
||||
if (node.init != nullptr) {
|
||||
if (m_context.empty()) {
|
||||
m_initContext.new_next();
|
||||
m_context.push(std::move(m_initContext));
|
||||
node.init->accept(*this);
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move, var->operand(), { context().last_register() } });
|
||||
m_initContext = std::move(m_context.top());
|
||||
m_context.pop();
|
||||
} else {
|
||||
node.init->accept(*this);
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move, var->operand(), { context().last_register() } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit_func_decl_node(const func_decl_node& node) {
|
||||
ir_function function;
|
||||
function.previous = m_scope;
|
||||
function.name = node.name;
|
||||
for (const auto& param : node.params) {
|
||||
function.params.push_back(ast_type_to_ir(param.type));
|
||||
function.allocate(m_module.arena, param.name, ast_type_to_ir(param.type));
|
||||
}
|
||||
function.retType = ast_type_to_ir(node.type);
|
||||
|
||||
if (node.def.has_value()) {
|
||||
auto* func = m_module.functions.emplace_back(m_module.arena.allocate<ir_function>(std::move(function)));
|
||||
m_context.emplace(func);
|
||||
m_scope = func;
|
||||
node.def->body.accept(*this);
|
||||
m_scope = m_scope->previous;
|
||||
m_context.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void ir_generator::visit_var_read_expr_node(const var_read_expr_node& node) {
|
||||
const auto* var = m_scope->variable(node.name);
|
||||
if (var == nullptr) throw std::runtime_error("unknown variable");
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move, context().next_register(), { var->operand() } });
|
||||
}
|
||||
|
||||
void ir_generator::visit_func_call_expr_node(const func_call_expr_node& node) {
|
||||
throw std::runtime_error("not implemented");
|
||||
}
|
||||
|
||||
void ir_generator::visit_group_expr_node(const group_expr_node& node) {
|
||||
node.inner->accept(*this);
|
||||
}
|
||||
|
||||
void ir_generator::visit_binary_op_expr_node(const binary_op_expr_node& node) {
|
||||
node.lhs->accept(*this);
|
||||
auto lhs = context().last_register();
|
||||
node.rhs->accept(*this);
|
||||
auto rhs = context().last_register();
|
||||
|
||||
switch (node.type) {
|
||||
case binary_op_expr_node::Add:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Add, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Sub:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Sub, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Mul:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Mul, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Div:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Div, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Mod:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Mod, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Shl:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Shl, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Shr:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Shr, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::BinAnd:
|
||||
context().add_instr(ir_instruction{ ir_instruction::BinAnd, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::BinOr:
|
||||
context().add_instr(ir_instruction{ ir_instruction::BinOr, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::BinXor:
|
||||
context().add_instr(ir_instruction{ ir_instruction::BinXor, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::And:
|
||||
context().add_instr(ir_instruction{ ir_instruction::And, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Or:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Or, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::Equals:
|
||||
context().add_instr(ir_instruction{ ir_instruction::Eq, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::NotEquals:
|
||||
context().add_instr(ir_instruction{ ir_instruction::NotEq, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::LessThan:
|
||||
context().add_instr(ir_instruction{ ir_instruction::LessThan, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::LessEquals:
|
||||
context().add_instr(ir_instruction{ ir_instruction::LessEq, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::GreaterThan:
|
||||
context().add_instr(ir_instruction{ ir_instruction::GreaterThan, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
case binary_op_expr_node::GreaterEquals:
|
||||
context().add_instr(ir_instruction{ ir_instruction::GreaterEq, context().next_register(), { lhs, rhs } });
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
void ir_generator::visit_unary_op_expr_node(const unary_op_expr_node& node) {
|
||||
if (node.type == unary_op_expr_node::PostInc || node.type == unary_op_expr_node::PostDec) {
|
||||
node.lhs->accept(*this);
|
||||
auto lhs = context().last_register();
|
||||
node.lhs->accept(*this);
|
||||
context().add_instr(node.type == unary_op_expr_node::PostInc ? ir_instruction::Increment
|
||||
: ir_instruction::Decrement,
|
||||
context().last_register());
|
||||
return;
|
||||
}
|
||||
|
||||
node.lhs->accept(*this);
|
||||
auto lhs = context().last_register();
|
||||
|
||||
switch (node.type) {
|
||||
case unary_op_expr_node::Positive: context().add_instr(ir_instruction::Positive, lhs);
|
||||
case unary_op_expr_node::Negative: context().add_instr(ir_instruction::Negative, lhs);
|
||||
case unary_op_expr_node::PreInc: context().add_instr(ir_instruction::Increment, lhs);
|
||||
case unary_op_expr_node::PreDec: context().add_instr(ir_instruction::Decrement, lhs);
|
||||
case unary_op_expr_node::BinNot: context().add_instr(ir_instruction::BinNot, lhs);
|
||||
case unary_op_expr_node::Not: context().add_instr(ir_instruction::Not, lhs);
|
||||
case unary_op_expr_node::Sizeof: context().add_instr(ir_instruction::Sizeof, lhs);
|
||||
case unary_op_expr_node::Pointerof: context().add_instr(ir_instruction::Pointerof, lhs);
|
||||
case unary_op_expr_node::Lengthof: context().add_instr(ir_instruction::Lenof, lhs);
|
||||
case unary_op_expr_node::PostInc:
|
||||
case unary_op_expr_node::PostDec: return;
|
||||
}
|
||||
throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
void ir_generator::visit_if_expr_node(const if_expr_node& node) {
|
||||
node.cond->accept(*this);
|
||||
auto* branch = context().terminate(context().last_register(), context().blockIdx + 1, 0);
|
||||
|
||||
context().new_next();
|
||||
auto thenBranch = context().blockIdx;
|
||||
node.thenExpr->accept(*this);
|
||||
auto thenReg = context().last_register();
|
||||
|
||||
context().new_next();
|
||||
branch->destination->value.blockPair.second = context().blockIdx; // NOLINT
|
||||
node.elseExpr->accept(*this);
|
||||
auto elseReg = context().last_register();
|
||||
auto resReg = context().next_register();
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move, resReg, { elseReg } });
|
||||
|
||||
context().new_next();
|
||||
auto epilogue = context().blockIdx;
|
||||
|
||||
context().go(thenBranch);
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move, resReg, { thenReg } });
|
||||
context().terminate(epilogue);
|
||||
context().go(epilogue);
|
||||
}
|
||||
|
||||
void ir_generator::visit_int_lit_node(const int_lit_node& node) {
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move,
|
||||
context().next_register(),
|
||||
{ ir_operand{ ir_operand::Integer, node.value } } });
|
||||
}
|
||||
|
||||
void ir_generator::visit_char_lit_node(const char_lit_node& node) {
|
||||
context().add_instr(ir_instruction{ ir_instruction::Move,
|
||||
context().next_register(),
|
||||
{ ir_operand{ ir_operand::Integer, static_cast<std::uint64_t>(node.value) } } });
|
||||
}
|
||||
|
||||
} // namespace furc
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Sources:
|
||||
* - Practical Improvements to the Construction and Deconstruction of Static Single Assignment Form:
|
||||
* https://web.archive.org/web/20100607003509/http://www.cs.rice.edu/~harv/my_papers/ssa.pdf
|
||||
* - A Simple, Fast Dominance Algorithm:
|
||||
* https://www.researchgate.net/publication/2569680_A_Simple_Fast_Dominance_Algorithm
|
||||
*/
|
||||
|
||||
#include "furc/middle/ssa.hpp"
|
||||
|
||||
#include "furc/middle/ir.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <stack>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace furc {
|
||||
|
||||
void ssa::compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks) {
|
||||
cfgBlocks.resize(irBlocks.size());
|
||||
|
||||
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
|
||||
const auto& block = irBlocks[i];
|
||||
if (block.instructions.empty()) continue;
|
||||
|
||||
const auto& termInstr = block.instructions.back();
|
||||
switch (termInstr.type) {
|
||||
case ir_instruction::Branch: {
|
||||
const auto& dst = termInstr.destination.value();
|
||||
assert(dst.type == ir_operand::Block);
|
||||
cfgBlocks[dst.value.block].preds.insert(i);
|
||||
cfgBlocks[i].sucs.insert(dst.value.block);
|
||||
} break;
|
||||
case ir_instruction::BranchCond: {
|
||||
const auto& dst = termInstr.destination.value();
|
||||
assert(dst.type == ir_operand::BlockPair);
|
||||
cfgBlocks[dst.value.blockPair.first].preds.insert(i);
|
||||
cfgBlocks[dst.value.blockPair.second].preds.insert(i);
|
||||
cfgBlocks[i].sucs.insert(dst.value.blockPair.first);
|
||||
cfgBlocks[i].sucs.insert(dst.value.blockPair.second);
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::collect_registers(const std::vector<ir_basic_block>& irBlocks,
|
||||
std::vector<register_info>& registers,
|
||||
std::unordered_set<std::uint64_t>& globals) {
|
||||
for (std::size_t i = 0; i < irBlocks.size(); ++i) {
|
||||
const auto& block = irBlocks[i];
|
||||
for (const auto& instr : block.instructions) {
|
||||
for (const auto& src : instr.sources) {
|
||||
if (src.type != ir_operand::Register) continue;
|
||||
const auto& reg = registers.at(src.value.reg.name);
|
||||
if (reg.sites.find(i) != reg.sites.end()) continue;
|
||||
globals.insert(src.value.reg.name);
|
||||
}
|
||||
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||
registers[instr.destination->value.reg.name].sites.insert(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::build_dtree(const std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<std::size_t>& order) {
|
||||
ssaBlocks[order.front()].idom = order.front();
|
||||
|
||||
bool changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (auto it = order.begin() + 1; it != order.end(); ++it) {
|
||||
static constexpr std::uint64_t INVALID = std::numeric_limits<std::uint64_t>::max();
|
||||
|
||||
std::uint64_t newIdom = -1;
|
||||
bool found = false;
|
||||
|
||||
for (std::uint64_t pred : cfgBlocks[*it].preds) {
|
||||
if (ssaBlocks[pred].idom == INVALID) continue;
|
||||
newIdom = found ? intersect(ssaBlocks, pred, newIdom) : pred;
|
||||
found = true;
|
||||
}
|
||||
if (ssaBlocks[*it].idom != newIdom) {
|
||||
ssaBlocks[*it].idom = newIdom;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks) {
|
||||
for (std::uint64_t i = 0; i < ssaBlocks.size(); ++i) {
|
||||
if (cfgBlocks[i].preds.size() < 2) continue;
|
||||
const auto& cfgBlock = cfgBlocks[i];
|
||||
auto& ssaBlock = ssaBlocks[i];
|
||||
|
||||
for (std::uint64_t worker : cfgBlock.preds) {
|
||||
while (worker != ssaBlock.idom) {
|
||||
ssaBlocks[worker].df.insert(i);
|
||||
worker = ssaBlocks[worker].idom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::compute_rpo(std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
std::vector<std::size_t>& order) {
|
||||
std::unordered_set<std::size_t> visited;
|
||||
if (!cfgBlocks.empty()) rpo_dfs(visited, order, 0, cfgBlocks);
|
||||
std::reverse(order.begin(), order.end());
|
||||
ssaBlocks.resize(cfgBlocks.size());
|
||||
for (std::size_t i = 0; i < order.size(); ++i) {
|
||||
ssaBlocks[order[i]].order = i;
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::ssaification(std::vector<ir_basic_block>& irBlocks,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
const std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<register_info>& registers,
|
||||
const std::unordered_set<std::uint64_t>& globals) {
|
||||
std::vector<std::uint64_t> worklist;
|
||||
|
||||
for (std::uint64_t i = 0; i < registers.size(); ++i) {
|
||||
const auto& reg = registers[i];
|
||||
if (reg.sites.size() < 2 || globals.find(i) == globals.end()) continue;
|
||||
worklist.insert(worklist.end(), reg.sites.begin(), reg.sites.end());
|
||||
|
||||
std::unordered_set<std::uint64_t> done;
|
||||
while (!worklist.empty()) {
|
||||
const auto blockIdx = worklist.back();
|
||||
worklist.pop_back();
|
||||
|
||||
for (auto frontier : ssaBlocks[blockIdx].df) {
|
||||
if (done.find(frontier) != done.end()) continue;
|
||||
done.insert(frontier);
|
||||
|
||||
auto& target = irBlocks[frontier];
|
||||
ir_instruction instr = { ir_instruction::Phi, ir_operand{ ir_operand::Register, i } };
|
||||
for (const auto& pred : cfgBlocks[frontier].preds)
|
||||
instr.sources.emplace_back(ir_operand::PhiPair, i, pred);
|
||||
target.instructions.emplace(target.instructions.begin(), std::move(instr));
|
||||
|
||||
if (reg.sites.find(frontier) == reg.sites.end()) worklist.push_back(frontier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ssa::rename(std::vector<ir_basic_block>& irBlocks,
|
||||
std::size_t regCount,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
std::vector<ssa_block>& ssaBlocks,
|
||||
const std::vector<std::uint64_t>& order) {
|
||||
std::vector<std::uint64_t> counters;
|
||||
std::vector<std::stack<std::uint64_t>> stacks;
|
||||
|
||||
counters.resize(regCount);
|
||||
stacks.resize(regCount);
|
||||
|
||||
for (auto it = order.begin() + 1; it != order.end(); ++it) {
|
||||
std::uint64_t parent = ssaBlocks[*it].idom;
|
||||
if (parent != std::numeric_limits<std::uint64_t>::max()) ssaBlocks[parent].children.emplace(*it);
|
||||
}
|
||||
|
||||
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, order.front());
|
||||
}
|
||||
|
||||
void ssa::rename_rec(std::vector<std::uint64_t>& counters,
|
||||
std::vector<std::stack<std::uint64_t>>& stacks,
|
||||
std::vector<ir_basic_block>& irBlocks,
|
||||
const std::vector<cfg_block>& cfgBlocks,
|
||||
const std::vector<ssa_block>& ssaBlocks,
|
||||
std::size_t blockIdx) {
|
||||
std::unordered_map<std::uint64_t, std::size_t> pushed;
|
||||
|
||||
auto& block = irBlocks[blockIdx];
|
||||
for (auto& instr : block.instructions) {
|
||||
if (instr.type == ir_instruction::Phi) {
|
||||
auto reg = instr.destination->value.reg.name;
|
||||
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
|
||||
++pushed[reg];
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& op : instr.sources) {
|
||||
if (op.type != ir_operand::Register) continue;
|
||||
auto reg = op.value.reg.name;
|
||||
op.value.reg.ver = stacks[reg].top();
|
||||
}
|
||||
|
||||
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||
auto reg = instr.destination->value.reg.name;
|
||||
stacks[reg].push(instr.destination->value.reg.ver = counters[reg]++);
|
||||
++pushed[reg];
|
||||
}
|
||||
|
||||
for (auto succIdx : cfgBlocks[blockIdx].sucs) {
|
||||
auto& succ = irBlocks[succIdx];
|
||||
for (auto& instr : succ.instructions) {
|
||||
if (instr.type != ir_instruction::Phi) break;
|
||||
for (auto& op : instr.sources) {
|
||||
if (op.value.phiPair.block != blockIdx) continue;
|
||||
op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (std::uint64_t child : ssaBlocks[blockIdx].children)
|
||||
rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, child);
|
||||
|
||||
for (auto [reg, count] : pushed)
|
||||
while ((count--) > 0)
|
||||
stacks[reg].pop();
|
||||
}
|
||||
|
||||
void ssa::rpo_dfs(std::unordered_set<std::size_t>& visited,
|
||||
std::vector<std::size_t>& order,
|
||||
std::size_t block,
|
||||
const std::vector<cfg_block>& blocks) {
|
||||
visited.insert(block);
|
||||
for (auto succ : blocks[block].sucs) {
|
||||
if (visited.find(succ) != visited.end()) continue;
|
||||
rpo_dfs(visited, order, succ, blocks);
|
||||
}
|
||||
order.push_back(block);
|
||||
}
|
||||
|
||||
std::size_t ssa::intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2) {
|
||||
while (b1 != b2) {
|
||||
while (m_blocks[b1].order > m_blocks[b2].order)
|
||||
b1 = m_blocks[b1].idom;
|
||||
while (m_blocks[b2].order > m_blocks[b1].order)
|
||||
b2 = m_blocks[b2].idom;
|
||||
}
|
||||
return b1;
|
||||
}
|
||||
|
||||
// // 5. Renaming
|
||||
// std::vector<std::uint64_t> counters;
|
||||
// std::vector<std::stack<std::uint64_t>> stacks;
|
||||
//
|
||||
// counters.resize(func.regCount);
|
||||
// stacks.resize(func.regCount);
|
||||
//
|
||||
// for (std::size_t i = 1; i < order.size(); ++i) {
|
||||
// std::size_t parent = blocks[order[i]].idom;
|
||||
// if (parent != std::numeric_limits<std::size_t>::max()) blocks[parent].children.emplace(order[i]);
|
||||
// }
|
||||
//
|
||||
// auto rename = [&counters, &stacks, &blocks, &func](auto& self, std::size_t blockIdx) -> void {
|
||||
// std::unordered_map<std::size_t, std::size_t> pushed;
|
||||
//
|
||||
// auto& block = func.blocks[blockIdx];
|
||||
// for (auto& instr : block.instructions) {
|
||||
// if (instr.type == ir_instruction::Phi) {
|
||||
// auto reg = instr.destination->value.reg.name;
|
||||
// auto idx = counters[reg]++;
|
||||
// instr.destination->value.reg.ver = idx;
|
||||
// stacks[reg].push(idx);
|
||||
// ++pushed[reg];
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// for (auto& op : instr.sources) {
|
||||
// if (op.type != ir_operand::Register) continue;
|
||||
// auto reg = op.value.reg.name;
|
||||
// op.value.reg.ver = stacks[reg].top();
|
||||
// }
|
||||
//
|
||||
// if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
|
||||
// auto reg = instr.destination->value.reg.name;
|
||||
// auto idx = counters[reg]++;
|
||||
// instr.destination->value.reg.ver = idx;
|
||||
// stacks[reg].push(idx);
|
||||
// ++pushed[reg];
|
||||
// }
|
||||
//
|
||||
// for (auto succIdx : blocks[blockIdx].sucs) {
|
||||
// auto& succ = func.blocks[succIdx];
|
||||
// for (auto& instr : succ.instructions) {
|
||||
// if (instr.type != ir_instruction::Phi) break;
|
||||
// for (auto& op : instr.sources) {
|
||||
// if (op.value.phiPair.block != blockIdx) continue;
|
||||
// op.value.phiPair.reg.ver = stacks[op.value.phiPair.reg.name].top();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for (std::size_t child : blocks[blockIdx].children)
|
||||
// self(self, child);
|
||||
//
|
||||
// for (auto [reg, count] : pushed)
|
||||
// while (count--)
|
||||
// stacks[reg].pop();
|
||||
// };
|
||||
// rename(rename, order.front());
|
||||
// }
|
||||
|
||||
} // namespace furc
|
||||
@@ -0,0 +1,5 @@
|
||||
file(GLOB_RECURSE FURDB_SRCS "${CMAKE_SOURCE_DIR}/src/furdb/**.cpp")
|
||||
file(GLOB_RECURSE FURDB_HDRS "${CMAKE_SOURCE_DIR}/include/furdb/**.hpp")
|
||||
add_executable(furdb ${FURDB_SRCS} ${FURDB_HDRS})
|
||||
target_include_directories(furdb PRIVATE ${CMAKE_SOURCE_DIR}/include/)
|
||||
target_link_libraries(furdb PUBLIC furlang libfurc libfurvm isocline)
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef COMMAND_HPP
|
||||
#define COMMAND_HPP
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
struct command_info {
|
||||
std::string_view commandName;
|
||||
std::vector<std::string_view> args;
|
||||
};
|
||||
|
||||
class command {
|
||||
public:
|
||||
command() = default;
|
||||
virtual ~command() = default;
|
||||
|
||||
command(command&&) noexcept = default;
|
||||
command& operator=(command&&) noexcept = default;
|
||||
command(const command&) = delete;
|
||||
command& operator=(const command&) = delete;
|
||||
public:
|
||||
virtual void execute(context& ctx, const command_info& info) = 0;
|
||||
public:
|
||||
static command_info parse(std::string_view line);
|
||||
};
|
||||
|
||||
class quit_command final : public command {
|
||||
public:
|
||||
void execute(context& ctx, const command_info& info) override;
|
||||
};
|
||||
|
||||
class run_command final : public command {
|
||||
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
|
||||
@@ -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';
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef CONTEXT_HPP
|
||||
#define CONTEXT_HPP
|
||||
|
||||
#include <furvm/context.hpp>
|
||||
#include <furvm/executor.hpp>
|
||||
#include <furvm/fwd.hpp>
|
||||
#include <furvm/module.hpp>
|
||||
|
||||
struct context {
|
||||
furvm::context_p context = std::make_shared<furvm::context>();
|
||||
furvm::mod_h mod;
|
||||
furvm::executor* executor = nullptr;
|
||||
furvm::function_h mainFunction;
|
||||
|
||||
bool running = true;
|
||||
bool halt = true;
|
||||
|
||||
void kill() const;
|
||||
void init();
|
||||
|
||||
void run();
|
||||
|
||||
void print_instruction() const;
|
||||
};
|
||||
|
||||
#endif // CONTEXT_HPP
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "command.hpp"
|
||||
#include "context.hpp"
|
||||
#include "furvm/function.hpp"
|
||||
#include "isocline.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <furvm/fwd.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
// taken from furvm uwu :3 ^^ nya~ ngh~
|
||||
static void print_thing(const furvm::thing<>& thing) {
|
||||
using namespace furvm;
|
||||
|
||||
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 (u64 i = 0; i < thing.length(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
print_thing(thing.at(i));
|
||||
}
|
||||
std::cout << " }";
|
||||
break;
|
||||
default: std::cerr << "{Type not recognized}";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Argument parsing
|
||||
// TODO: Multiple modules
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <module.fmod>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string_view, command*> s_commands;
|
||||
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);
|
||||
|
||||
context ctx;
|
||||
try {
|
||||
ctx.mod = ctx.context->emplace(argv[1], furvm::mod::load(file));
|
||||
} catch (std::exception ex) {
|
||||
std::cerr << "Failed to load module " << argv[1] << ": " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
ctx.executor = &ctx.context->allocate_executor();
|
||||
ctx.mainFunction = ctx.mod->function_at("main", furvm::function_sig{});
|
||||
|
||||
ctx.mod->set_native_function("println", [](furvm::executor& executor) {
|
||||
print_thing(executor.load_thing(0));
|
||||
std::cout << '\n';
|
||||
});
|
||||
|
||||
std::string inputLine;
|
||||
std::string prevInput;
|
||||
while (ctx.running) {
|
||||
std::swap(inputLine, prevInput);
|
||||
|
||||
char* inputRaw = ic_readline("furdb");
|
||||
if (inputRaw == nullptr) break;
|
||||
inputLine = inputRaw;
|
||||
free(inputRaw); // NOLINT
|
||||
|
||||
if (inputLine.empty()) {
|
||||
inputLine = prevInput;
|
||||
}
|
||||
|
||||
auto info = command::parse(inputLine);
|
||||
auto it = s_commands.find(info.commandName);
|
||||
if (it == s_commands.end()) {
|
||||
std::cerr << "Unknown command \"" << info.commandName << "\"\n";
|
||||
continue;
|
||||
}
|
||||
it->second->execute(ctx, info);
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "Uncaught exception: " << ex.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
file(GLOB_RECURSE FURLANG_SRCS "${CMAKE_SOURCE_DIR}/src/furlang/**.cpp")
|
||||
file(GLOB_RECURSE FURLANG_HDRS "${CMAKE_SOURCE_DIR}/include/furlang/**.hpp")
|
||||
add_library(furlang STATIC ${FURLANG_SRCS} ${FURLANG_HDRS})
|
||||
target_include_directories(furlang PUBLIC ${CMAKE_SOURCE_DIR}/include/)
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "furlang/serialization/io.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace furlang::serialization {
|
||||
|
||||
result<error> byte_writer::write_s8(std::int8_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_u8(std::uint8_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_s16(std::int16_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_u16(std::uint16_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_s32(std::int32_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_u32(std::uint32_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_s64(std::int64_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_u64(std::uint64_t value) {
|
||||
if (m_endianness == endianness::Little)
|
||||
write_integral_le(value);
|
||||
else
|
||||
write_integral_be(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_string(const char* string) {
|
||||
write_u16(std::strlen(string));
|
||||
m_bytes.insert(m_bytes.end(), string, string + std::strlen(string));
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_string(std::string_view string) {
|
||||
write_u16(string.length());
|
||||
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error> byte_writer::write_string(const std::string& string) {
|
||||
write_u16(string.length());
|
||||
m_bytes.insert(m_bytes.end(), string.begin(), string.end());
|
||||
return {};
|
||||
}
|
||||
|
||||
result<error, std::int8_t> byte_reader::read_s8() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::int8_t>() : read_integral_be<std::int8_t>();
|
||||
}
|
||||
|
||||
result<error, std::uint8_t> byte_reader::read_u8() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::uint8_t>() : read_integral_be<std::uint8_t>();
|
||||
}
|
||||
|
||||
result<error, std::int16_t> byte_reader::read_s16() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::int16_t>() : read_integral_be<std::int16_t>();
|
||||
}
|
||||
|
||||
result<error, std::uint16_t> byte_reader::read_u16() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::uint16_t>() : read_integral_be<std::uint16_t>();
|
||||
}
|
||||
|
||||
result<error, std::int32_t> byte_reader::read_s32() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::int32_t>() : read_integral_be<std::int32_t>();
|
||||
}
|
||||
|
||||
result<error, std::uint32_t> byte_reader::read_u32() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::uint32_t>() : read_integral_be<std::uint32_t>();
|
||||
}
|
||||
|
||||
result<error, std::int64_t> byte_reader::read_s64() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::int64_t>() : read_integral_be<std::int64_t>();
|
||||
}
|
||||
|
||||
result<error, std::uint64_t> byte_reader::read_u64() {
|
||||
return m_endianness == endianness::Little ? read_integral_le<std::uint64_t>() : read_integral_be<std::uint64_t>();
|
||||
}
|
||||
|
||||
result<error, std::string> byte_reader::read_string() {
|
||||
std::string str;
|
||||
auto length = read_u16();
|
||||
if (length.has_error()) return length;
|
||||
str.resize(length.value());
|
||||
if (m_offset + str.length() >= m_length)
|
||||
return result<error, std::string>::error(error{ error_code::EndOfFile, "", m_length });
|
||||
str.assign(reinterpret_cast<const char*>(m_bytes) + m_offset, str.length());
|
||||
m_offset += str.length();
|
||||
return str;
|
||||
}
|
||||
|
||||
std::size_t byte_reader::offset() const {
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
} // namespace furlang::serialization
|
||||
@@ -1,16 +1,16 @@
|
||||
file(GLOB_RECURSE FURVM_SRCS "src/**.cpp")
|
||||
file(GLOB_RECURSE FURVM_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)
|
||||
@@ -0,0 +1,421 @@
|
||||
#include "furvm/executor.hpp"
|
||||
|
||||
#include "furlang/view.hpp"
|
||||
#include "furvm/context.hpp" // IWYU pragma: keep
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/function.hpp" // IWYU pragma: keep
|
||||
#include "furvm/fwd.hpp"
|
||||
#include "furvm/instruction.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
thing_type executor::thing_type_impl(mod_h mod, mod_type type) const {
|
||||
while (type.type == mod_type::Import) {
|
||||
auto imprt = std::move(type.value.imprt);
|
||||
mod = m_context->at(imprt.modId);
|
||||
type = *mod->type_at(imprt.typeId);
|
||||
}
|
||||
switch (static_cast<enum thing_type::type>(type.type)) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64:
|
||||
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 } };
|
||||
}
|
||||
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 {
|
||||
struct thing_type thingType = thing_type_impl(mod, type);
|
||||
return m_context->tt_store().insert(thingType);
|
||||
}
|
||||
|
||||
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<stack_thing> args;
|
||||
args.reserve(signature.params.size());
|
||||
for (const auto& param : signature.params) {
|
||||
auto arg = pop_thing();
|
||||
if (!compare_thing_types(arg.type(), *mod_to_thing_type(mod, *param)))
|
||||
throw std::runtime_error("function argument type mismatch");
|
||||
args.emplace_back(std::move(arg));
|
||||
}
|
||||
|
||||
struct thing_type* returnType = nullptr;
|
||||
if (function.signature().returnType.has_value())
|
||||
returnType = mod_to_thing_type(mod, *function.signature().returnType.value()); // NOLINT
|
||||
|
||||
switch (function.type()) {
|
||||
case function_t::Normal: {
|
||||
m_frames.emplace(
|
||||
(struct executor::frame){ mod, function.position(), m_stack.size(), returnType, std::move(args) });
|
||||
} break;
|
||||
case function_t::Native: {
|
||||
m_frames.emplace((struct executor::frame){ mod, 0, m_stack.size(), returnType, std::move(args) });
|
||||
modInst->get_native_function(function.native())(*this);
|
||||
pop_frame();
|
||||
return;
|
||||
}
|
||||
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<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;
|
||||
}
|
||||
|
||||
struct executor::frame executor::top_frame() const {
|
||||
return m_frames.top();
|
||||
}
|
||||
|
||||
executor::stack_thing& executor::push_thing(stack_thing&& thing) {
|
||||
return m_stack.emplace_back(std::move(thing));
|
||||
}
|
||||
|
||||
executor::stack_thing& executor::push_thing(const stack_thing& thing) {
|
||||
return m_stack.emplace_back(thing);
|
||||
}
|
||||
|
||||
executor::stack_thing executor::pop_thing() {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
auto top = std::move(m_stack.back());
|
||||
m_stack.pop_back();
|
||||
return std::move(top);
|
||||
}
|
||||
|
||||
executor::stack_thing& executor::top_thing() {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
return m_stack.back();
|
||||
}
|
||||
|
||||
const executor::stack_thing& executor::top_thing() const {
|
||||
if (m_frames.top().stackBase >= m_stack.size()) throw stack_underflow();
|
||||
return m_stack.back();
|
||||
}
|
||||
|
||||
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, stack_thing&& thing) {
|
||||
auto& frame = m_frames.top();
|
||||
if (frame.variables.size() <= variable) frame.variables.resize(variable + 1);
|
||||
frame.variables[variable] = std::move(thing);
|
||||
}
|
||||
|
||||
executor::stack_thing& executor::load_thing(variable_t variable) {
|
||||
auto& frame = m_frames.top();
|
||||
return frame.variables[variable];
|
||||
}
|
||||
|
||||
const executor::stack_thing& executor::load_thing(variable_t variable) const {
|
||||
const auto& frame = m_frames.top();
|
||||
return frame.variables[variable];
|
||||
}
|
||||
|
||||
void executor::step() {
|
||||
if ((m_flags & executor_flags::Suspended) == executor_flags::Suspended) return;
|
||||
|
||||
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_stackStorage } }).get<s8>() = instr.arg.s8;
|
||||
} break;
|
||||
case instruction_t::PushU8: {
|
||||
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_stackStorage } }).get<s16>() = instr.arg.s16;
|
||||
} break;
|
||||
case instruction_t::PushU16: {
|
||||
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_stackStorage } }).get<s32>() = instr.arg.s8; // NOLINT
|
||||
} break;
|
||||
case instruction_t::PushU32: {
|
||||
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_stackStorage } });
|
||||
|
||||
if (type.value.array.size == 0) {
|
||||
auto sizeThing = pop_thing();
|
||||
std::int64_t size = sizeThing.integer();
|
||||
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();
|
||||
push_thing(array.at(index.integer()));
|
||||
} break;
|
||||
case instruction_t::Set: {
|
||||
auto element = pop_thing();
|
||||
auto index = pop_thing();
|
||||
auto array = pop_thing();
|
||||
array.at(index.integer()).assign(std::move(element));
|
||||
} break;
|
||||
case instruction_t::Drop: {
|
||||
pop_thing();
|
||||
} break;
|
||||
case instruction_t::Duplicate: {
|
||||
push_thing(top_thing());
|
||||
} break;
|
||||
case instruction_t::Swap: {
|
||||
auto thing1 = pop_thing();
|
||||
auto thing2 = pop_thing();
|
||||
push_thing(std::move(thing1));
|
||||
push_thing(std::move(thing2));
|
||||
} break;
|
||||
case instruction_t::Clone: {
|
||||
push_thing(top_thing());
|
||||
} break;
|
||||
case instruction_t::Reference: {
|
||||
push_thing(stack_thing::make_reference(pop_thing(), { m_stackStorage }));
|
||||
} break;
|
||||
case instruction_t::Add: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.add(rhs));
|
||||
} break;
|
||||
case instruction_t::Sub: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.sub(rhs));
|
||||
} break;
|
||||
case instruction_t::Mul: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.mul(rhs));
|
||||
} break;
|
||||
case instruction_t::Div: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.div(rhs));
|
||||
} break;
|
||||
case instruction_t::Mod: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.mod(rhs));
|
||||
} break;
|
||||
case instruction_t::Equals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.equals(rhs));
|
||||
} break;
|
||||
case instruction_t::NotEquals: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.not_equals(rhs));
|
||||
} break;
|
||||
case instruction_t::LessThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.less_than(rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterThan: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.greater_than(rhs));
|
||||
} break;
|
||||
case instruction_t::LessEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.less_equals(rhs));
|
||||
} break;
|
||||
case instruction_t::GreaterEqual: {
|
||||
auto rhs = pop_thing();
|
||||
auto lhs = pop_thing();
|
||||
push_thing(lhs.greater_equals(rhs));
|
||||
} break;
|
||||
case instruction_t::Pointerof: {
|
||||
auto thing = pop_thing();
|
||||
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_stackStorage } });
|
||||
switch (thing.type().type) {
|
||||
case thing_type::S8:
|
||||
case thing_type::S16:
|
||||
case thing_type::S32:
|
||||
case thing_type::S64:
|
||||
case thing_type::U8:
|
||||
case thing_type::U16:
|
||||
case thing_type::U32:
|
||||
case thing_type::U64: size.get<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<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_stackStorage } }).get<u64>() = thing.length();
|
||||
} break;
|
||||
case instruction_t::Load: {
|
||||
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(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, pop_thing());
|
||||
} break;
|
||||
case instruction_t::Call: {
|
||||
push_frame(frame.mod, *frame.mod->function_at(instr.arg.u16));
|
||||
} break;
|
||||
case instruction_t::Jump: {
|
||||
frame.position += instr.arg.s8;
|
||||
} break;
|
||||
case instruction_t::JumpNotZero: {
|
||||
if (pop_thing().integer() != 0) frame.position += instr.arg.s8;
|
||||
} break;
|
||||
case instruction_t::Return: {
|
||||
pop_frame();
|
||||
if (m_frames.empty()) m_flags = m_flags | executor_flags::Done;
|
||||
} break;
|
||||
default: throw std::runtime_error("unknown instruction");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "furvm/function.hpp"
|
||||
|
||||
#include "furlang/utility/hash.hpp"
|
||||
#include "furvm/furvm.hpp"
|
||||
|
||||
#include <utility>
|
||||
@@ -100,8 +101,35 @@ function& function::operator=(const function& other) {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct mod_type_hash {
|
||||
std::size_t operator()(const mod_type_h& type) const {
|
||||
std::size_t hash = std::hash<decltype(type->type)>{}(type->type);
|
||||
switch (type->type) {
|
||||
case mod_type::S8:
|
||||
case mod_type::S16:
|
||||
case mod_type::S32:
|
||||
case mod_type::S64:
|
||||
case mod_type::U8:
|
||||
case mod_type::U16:
|
||||
case mod_type::U32:
|
||||
case mod_type::U64: return hash;
|
||||
case mod_type::Ptr:
|
||||
case mod_type::Ref: return furlang::utility::hash_combine(hash, type->value.typeRef);
|
||||
case mod_type::Array:
|
||||
hash = furlang::utility::hash_combine(hash, type->value.array.size);
|
||||
return furlang::utility::hash_combine(hash, type->value.array.typeId);
|
||||
case mod_type::Import:
|
||||
hash = furlang::utility::hash_combine(hash,
|
||||
std::hash<decltype(type->value.imprt.modId)>{}(type->value.imprt.modId));
|
||||
return furlang::utility::hash_combine(hash, type->value.imprt.typeId);
|
||||
case mod_type::Count: break;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t function_sig_hash::operator()(const function_sig& signature) const {
|
||||
return furlang::utility::vector_hash<mod_type_h, detail::handle_hash<mod_type_h>>{}(signature.params);
|
||||
return furlang::utility::vector_hash<mod_type_h, mod_type_hash>{}(signature.params);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
@@ -0,0 +1,188 @@
|
||||
#include "furvm/instruction.hpp"
|
||||
|
||||
#include "furlang/view.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace furvm {
|
||||
|
||||
const std::size_t instruction_argument::s_sizes[instruction_argument::Count] = {
|
||||
// None:
|
||||
0,
|
||||
// S8:
|
||||
1,
|
||||
// U8:
|
||||
1,
|
||||
// S16:
|
||||
2,
|
||||
// U16:
|
||||
2,
|
||||
// S32
|
||||
1,
|
||||
// U32
|
||||
1,
|
||||
// Constant:
|
||||
2,
|
||||
// Type:
|
||||
4,
|
||||
// Variable:
|
||||
2,
|
||||
// GlobalVariable:
|
||||
2,
|
||||
// Function:
|
||||
4,
|
||||
// Offset:
|
||||
1,
|
||||
};
|
||||
|
||||
const bool instruction_argument::s_signedness[instruction_argument::Count] = {
|
||||
// None:
|
||||
false,
|
||||
// S8:
|
||||
true,
|
||||
// U8:
|
||||
false,
|
||||
// S16:
|
||||
true,
|
||||
// U16:
|
||||
false,
|
||||
// S32
|
||||
true,
|
||||
// U32:
|
||||
false,
|
||||
// Constant:
|
||||
false,
|
||||
// Type:
|
||||
false,
|
||||
// Variable:
|
||||
false,
|
||||
// GlobalVariable:
|
||||
false,
|
||||
// Function:
|
||||
false,
|
||||
// Offset:
|
||||
true,
|
||||
};
|
||||
|
||||
const instruction_argument_t instruction::s_arguments[instruction::Count] = {
|
||||
// NoOperation:
|
||||
instruction_argument::None,
|
||||
// PushS8:
|
||||
instruction_argument::S8,
|
||||
// PushU8:
|
||||
instruction_argument::U8,
|
||||
// PushS16:
|
||||
instruction_argument::S16,
|
||||
// PushU16:
|
||||
instruction_argument::U16,
|
||||
// PushS32:
|
||||
instruction_argument::S32,
|
||||
// PushU32:
|
||||
instruction_argument::U32,
|
||||
// PushConstant:
|
||||
instruction_argument::Constant,
|
||||
// Array:
|
||||
instruction_argument::Type,
|
||||
// Slice:
|
||||
instruction_argument::None,
|
||||
// Get:
|
||||
instruction_argument::None,
|
||||
// Set:
|
||||
instruction_argument::None,
|
||||
// Drop:
|
||||
instruction_argument::None,
|
||||
// Duplicate:
|
||||
instruction_argument::None,
|
||||
// Swap:
|
||||
instruction_argument::None,
|
||||
// Clone:
|
||||
instruction_argument::None,
|
||||
// Reference:
|
||||
instruction_argument::None,
|
||||
// Add:
|
||||
instruction_argument::None,
|
||||
// Sub:
|
||||
instruction_argument::None,
|
||||
// Mul:
|
||||
instruction_argument::None,
|
||||
// Div:
|
||||
instruction_argument::None,
|
||||
// Mod:
|
||||
instruction_argument::None,
|
||||
// Equals:
|
||||
instruction_argument::None,
|
||||
// NotEquals:
|
||||
instruction_argument::None,
|
||||
// LessThan:
|
||||
instruction_argument::None,
|
||||
// GreaterThan:
|
||||
instruction_argument::None,
|
||||
// LessEqual:
|
||||
instruction_argument::None,
|
||||
// GreaterEqual:
|
||||
instruction_argument::None,
|
||||
// Pointerof:
|
||||
instruction_argument::None,
|
||||
// Sizeof:
|
||||
instruction_argument::None,
|
||||
// Lengthof:
|
||||
instruction_argument::None,
|
||||
// Load:
|
||||
instruction_argument::Variable,
|
||||
// Store:
|
||||
instruction_argument::Variable,
|
||||
// LoadGlobal:
|
||||
instruction_argument::GlobalVariable,
|
||||
// StoreGlobal:
|
||||
instruction_argument::GlobalVariable,
|
||||
// Call:
|
||||
instruction_argument::Function,
|
||||
// Jump:
|
||||
instruction_argument::Offset,
|
||||
// JumpNotZero:
|
||||
instruction_argument::Offset,
|
||||
// Return:
|
||||
instruction_argument::None,
|
||||
};
|
||||
|
||||
std::size_t instruction::read(furlang::view<std::uint8_t> in) {
|
||||
type = static_cast<type_e>(in[0]);
|
||||
if (type >= Count) throw std::runtime_error("invalid instruction");
|
||||
arg.type = s_arguments[type];
|
||||
|
||||
furlang::view argView = in.subview(1, arg.size());
|
||||
if (argView.size() != arg.size()) throw std::runtime_error("invalid bytecode");
|
||||
switch (argView.size()) {
|
||||
case 0: break;
|
||||
case 1: arg.u8 = argView[0]; break;
|
||||
case 2: arg.u16 = argView[0] | (argView[1] << 8); break;
|
||||
case 4: arg.u32 = argView[0] | (argView[1] << 8) | (argView[2] << 16) | (argView[3] << 24); break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
|
||||
return 1 + argView.size();
|
||||
}
|
||||
|
||||
std::size_t instruction::write(std::vector<std::uint8_t>& out) const {
|
||||
if (type >= Count) throw std::runtime_error("invalid instruction");
|
||||
if (s_arguments[type] != arg.type) throw std::runtime_error("malformed instruction");
|
||||
out.push_back(static_cast<char>(type));
|
||||
switch (arg.size()) {
|
||||
case 0: break;
|
||||
case 1: out.push_back(arg.u8); break;
|
||||
case 2:
|
||||
out.push_back(arg.u16 & 0xFF);
|
||||
out.push_back((arg.u16 >> 8) & 0xFF);
|
||||
break;
|
||||
case 4:
|
||||
out.push_back(arg.u32 & 0xFF);
|
||||
out.push_back((arg.u32 >> 8) & 0xFF);
|
||||
out.push_back((arg.u32 >> 16) & 0xFF);
|
||||
out.push_back((arg.u32 >> 24) & 0xFF);
|
||||
break;
|
||||
default: throw std::runtime_error("unreachable");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace furvm
|
||||
@@ -10,21 +10,29 @@
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
@@ -75,16 +83,15 @@ int main(int argc, char** argv) {
|
||||
mod->emplace_function(furvm::function_sig{ { u64Type }, u64Type }, "print").dispatch();
|
||||
#endif
|
||||
mod->set_native_function("println", [](furvm::executor& executor) {
|
||||
auto arg = executor.load_thing(0);
|
||||
print_thing(*arg);
|
||||
print_thing(executor.load_thing(0));
|
||||
std::cout << '\n';
|
||||
});
|
||||
|
||||
furvm::executor_h executor = context->emplace_executor(context);
|
||||
executor->push_frame(mod, *mainFunc);
|
||||
auto& executor = context->allocate_executor();
|
||||
executor.push_frame(mod, *mainFunc);
|
||||
|
||||
while ((executor->flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
|
||||
executor->step();
|
||||
while ((executor.flags() & furvm::executor_flags::Done) != furvm::executor_flags::Done) {
|
||||
executor.step();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -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);
|
||||
@@ -88,6 +104,8 @@ std::ostream& mod::serialize(std::ostream& os) const {
|
||||
}
|
||||
}
|
||||
|
||||
detail::serialize(os, static_cast<std::uint16_t>(m_globalVariables.size()));
|
||||
|
||||
detail::serialize(os, std::uint64_t(m_bytecode.size()));
|
||||
return os.write(reinterpret_cast<const char*>(m_bytecode.data()), static_cast<std::streamsize>(m_bytecode.size()));
|
||||
}
|
||||
@@ -102,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) {
|
||||
@@ -124,7 +158,12 @@ mod mod::load(std::istream& is) {
|
||||
case mod_type::Ptr: {
|
||||
mod_type_id typeId = 0;
|
||||
detail::load(is, typeId);
|
||||
mod.emplace_type(id, typeId).dispatch();
|
||||
mod.emplace_type(id, mod_type::Ptr, typeId).dispatch();
|
||||
} break;
|
||||
case mod_type::Ref: {
|
||||
mod_type_id typeId = 0;
|
||||
detail::load(is, typeId);
|
||||
mod.emplace_type(id, mod_type::Ref, typeId).dispatch();
|
||||
} break;
|
||||
case mod_type::Array: {
|
||||
mod_type_id typeId = 0;
|
||||
@@ -133,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);
|
||||
@@ -192,6 +236,10 @@ mod mod::load(std::istream& is) {
|
||||
}
|
||||
}
|
||||
|
||||
std::uint16_t globalVariables = 0;
|
||||
detail::load(is, globalVariables);
|
||||
mod.set_global_variable_count(globalVariables);
|
||||
|
||||
std::uint64_t bytecodeLength = 0;
|
||||
detail::load(is, bytecodeLength);
|
||||
mod.bytecode().resize(bytecodeLength);
|
||||
@@ -0,0 +1,141 @@
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <furc/middle/ir.hpp>
|
||||
#include <furc/middle/ssa.hpp>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace furc::test {
|
||||
|
||||
class SsaCfg : public testing::Test {
|
||||
public:
|
||||
SsaCfg()
|
||||
: p_context(&p_function) {}
|
||||
protected:
|
||||
void compute() { ssa::compute_cfg(p_function.blocks, p_cfgBlocks); }
|
||||
protected:
|
||||
std::vector<ssa::cfg_block> p_cfgBlocks;
|
||||
|
||||
ir_function p_function;
|
||||
ir_context p_context;
|
||||
};
|
||||
|
||||
TEST_F(SsaCfg, Diamond) {
|
||||
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
|
||||
|
||||
// Entry
|
||||
p_context.terminate(null, 1, 2);
|
||||
p_context.new_next();
|
||||
// A
|
||||
p_context.terminate(3);
|
||||
p_context.new_next();
|
||||
// B
|
||||
p_context.terminate(3);
|
||||
p_context.new_next();
|
||||
// C
|
||||
p_context.terminate(4);
|
||||
p_context.new_next();
|
||||
// Exit
|
||||
p_context.terminate();
|
||||
|
||||
compute();
|
||||
|
||||
EXPECT_EQ(p_cfgBlocks[0].sucs.size(), 2);
|
||||
EXPECT_NE(p_cfgBlocks[0].sucs.find(1), p_cfgBlocks[0].sucs.end());
|
||||
EXPECT_NE(p_cfgBlocks[0].sucs.find(2), p_cfgBlocks[0].sucs.end());
|
||||
EXPECT_EQ(p_cfgBlocks[3].preds, p_cfgBlocks[0].sucs);
|
||||
EXPECT_EQ(p_cfgBlocks[3].sucs.size(), 1);
|
||||
EXPECT_NE(p_cfgBlocks[3].sucs.find(4), p_cfgBlocks[3].sucs.end());
|
||||
}
|
||||
|
||||
class SsaDom : public testing::Test {
|
||||
public:
|
||||
SsaDom()
|
||||
: p_context(&p_function) {}
|
||||
protected:
|
||||
void compute() {
|
||||
ssa::compute_cfg(p_function.blocks, p_cfgBlocks);
|
||||
p_ssaRegisters.resize(p_function.regCount);
|
||||
ssa::collect_registers(p_function.blocks, p_ssaRegisters, p_ssaGlobals);
|
||||
|
||||
std::vector<std::uint64_t> order;
|
||||
ssa::compute_rpo(p_cfgBlocks, p_ssaBlocks, order);
|
||||
|
||||
ssa::build_dtree(p_cfgBlocks, p_ssaBlocks, order);
|
||||
ssa::compute_dfrontiers(p_cfgBlocks, p_ssaBlocks);
|
||||
|
||||
ssa::ssaification(p_function.blocks, p_cfgBlocks, p_ssaBlocks, p_ssaRegisters, p_ssaGlobals);
|
||||
ssa::rename(p_function.blocks, p_function.regCount, p_cfgBlocks, p_ssaBlocks, order);
|
||||
}
|
||||
protected:
|
||||
std::vector<ssa::cfg_block> p_cfgBlocks;
|
||||
std::vector<ssa::ssa_block> p_ssaBlocks;
|
||||
std::vector<ssa::register_info> p_ssaRegisters;
|
||||
std::unordered_set<std::uint64_t> p_ssaGlobals;
|
||||
|
||||
ir_function p_function;
|
||||
ir_context p_context;
|
||||
};
|
||||
|
||||
TEST_F(SsaDom, Diamond) {
|
||||
ir_operand null = { ir_operand::Integer, static_cast<std::uint64_t>(0) };
|
||||
|
||||
// Entry
|
||||
p_context.terminate(null, 1, 2);
|
||||
p_context.new_next();
|
||||
// A
|
||||
p_context.add_instr(ir_instruction{ ir_instruction::Move,
|
||||
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||
{ null } });
|
||||
p_context.terminate(3);
|
||||
p_context.new_next();
|
||||
// B
|
||||
p_context.add_instr(ir_instruction{ ir_instruction::Move,
|
||||
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||
{ null } });
|
||||
p_context.terminate(3);
|
||||
p_context.new_next();
|
||||
// C
|
||||
p_context.add_instr(ir_instruction{ ir_instruction::Add,
|
||||
ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) },
|
||||
{ ir_operand{ ir_operand::Register, static_cast<std::uint64_t>(0) } } });
|
||||
p_context.new_next();
|
||||
// Exit
|
||||
p_context.terminate();
|
||||
|
||||
p_function.regCount = 1;
|
||||
|
||||
compute();
|
||||
|
||||
EXPECT_TRUE(p_ssaBlocks[0].df.empty());
|
||||
|
||||
EXPECT_EQ(p_ssaBlocks[1].idom, 0);
|
||||
EXPECT_EQ(p_ssaBlocks[1].df.size(), 1);
|
||||
EXPECT_NE(p_ssaBlocks[1].df.find(3), p_ssaBlocks[1].df.end());
|
||||
|
||||
EXPECT_EQ(p_ssaBlocks[2].idom, 0);
|
||||
EXPECT_EQ(p_ssaBlocks[2].df.size(), 1);
|
||||
EXPECT_NE(p_ssaBlocks[2].df.find(3), p_ssaBlocks[2].df.end());
|
||||
|
||||
EXPECT_EQ(p_ssaBlocks[3].idom, 0);
|
||||
EXPECT_TRUE(p_ssaBlocks[3].df.empty());
|
||||
|
||||
EXPECT_EQ(p_ssaBlocks[4].idom, 3);
|
||||
EXPECT_TRUE(p_ssaBlocks[4].df.empty());
|
||||
|
||||
// Renaming
|
||||
EXPECT_EQ(p_function.blocks[1].instructions.front(),
|
||||
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 3), { null } }));
|
||||
EXPECT_EQ(p_function.blocks[2].instructions.front(),
|
||||
(ir_instruction{ ir_instruction::Move, ir_operand::reg(0, 2), { null } }));
|
||||
EXPECT_EQ(p_function.blocks[3].instructions.front(),
|
||||
(ir_instruction{ ir_instruction::Phi,
|
||||
ir_operand::reg(0, 0),
|
||||
{ ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 2 }, 2 },
|
||||
ir_operand{ ir_operand::PhiPair, ir_operand::value_u::register_s{ 0, 3 }, 1 } } }));
|
||||
}
|
||||
|
||||
} // namespace furc::test
|
||||
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "furvm/exceptions.hpp"
|
||||
#include "furvm/furvm.hpp"
|
||||
#include "furvm/thing.hpp"
|
||||
|
||||
#include "gtest/gtest.h" // IWYU pragma: keep
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: Basic program tests (e.g. for loops)
|
||||
|
||||
TEST(ThingOps, Add) {
|
||||
furvm::thing lhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
||||
lhs.get<furvm::u32>() = 6;
|
||||
furvm::thing rhs{ furvm::thing_type{ furvm::thing_type::U32 } };
|
||||
rhs.get<furvm::u32>() = 7;
|
||||
|
||||
auto res = lhs.add(rhs);
|
||||
ASSERT_EQ(res.type().type, furvm::thing_type::U32);
|
||||
EXPECT_EQ(lhs.get<furvm::u32>(), 6);
|
||||
EXPECT_EQ(rhs.get<furvm::u32>(), 7);
|
||||
EXPECT_EQ(res.get<furvm::u32>(), 6 + 7);
|
||||
}
|
||||
|
||||
TEST(ThingOps, Array) {
|
||||
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||
|
||||
static constexpr std::size_t LENGTH = 10;
|
||||
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||
|
||||
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||
EXPECT_EQ(array.length(), LENGTH);
|
||||
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||
auto el = array.at(i);
|
||||
el.get<furvm::u32>() = values[i];
|
||||
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThingOps, Slice) {
|
||||
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||
|
||||
static constexpr std::size_t LENGTH = 10;
|
||||
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||
|
||||
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||
EXPECT_EQ(array.length(), LENGTH);
|
||||
furvm::thing slice = array.slice(0, LENGTH);
|
||||
EXPECT_EQ(slice.length(), LENGTH);
|
||||
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||
auto el = slice.at(i);
|
||||
el.get<furvm::u32>() = values[i];
|
||||
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||
EXPECT_EQ(slice.at(i).integer(), array.at(i).integer());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThingOps, String) {
|
||||
static constexpr std::string_view STRING = "femboje na topie";
|
||||
static constexpr auto LENGTH = STRING.length();
|
||||
|
||||
furvm::thing string{ furvm::thing_type{ furvm::thing_type::String } };
|
||||
string.assign(STRING);
|
||||
EXPECT_EQ(string.length(), LENGTH);
|
||||
furvm::thing slice = string.slice(0, LENGTH);
|
||||
EXPECT_EQ(slice.length(), LENGTH);
|
||||
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||
auto el = slice.at(i);
|
||||
EXPECT_EQ(slice.at(i).get<furvm::u8>(), STRING[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThingOps, Iterators) {
|
||||
furvm::thing_type innerType = { furvm::thing_type::U32 };
|
||||
|
||||
static constexpr std::size_t LENGTH = 10;
|
||||
static constexpr std::array<furvm::u32, LENGTH> values = { 0, 1, 2, 3, 4, 5, 6, 7, 6, 7 };
|
||||
|
||||
furvm::thing array{ furvm::thing_type{ furvm::thing_type::Array, { &innerType, LENGTH } } };
|
||||
EXPECT_EQ(array.length(), LENGTH);
|
||||
for (std::size_t i = 0; i < LENGTH; ++i) {
|
||||
auto el = array.at(i);
|
||||
el.get<furvm::u32>() = values[i];
|
||||
EXPECT_EQ(array.at(i).integer(), values[i]);
|
||||
}
|
||||
|
||||
std::size_t itCount = 0;
|
||||
for (auto it = array.begin(); it != array.end(); ++it, ++itCount) {
|
||||
auto idx = it - array.begin();
|
||||
ASSERT_EQ(idx, itCount);
|
||||
ASSERT_LT(idx, LENGTH);
|
||||
EXPECT_EQ(it->integer(), values[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThingOps, Access) {
|
||||
furvm::thing thing{ furvm::thing_type{ furvm::thing_type::U8 } };
|
||||
EXPECT_NO_THROW(thing.get<furvm::u8>());
|
||||
EXPECT_THROW(thing.get<furvm::s8>(), furvm::bad_thing_access);
|
||||
EXPECT_THROW(thing.get<furvm::s32>(), furvm::bad_thing_access);
|
||||
EXPECT_THROW(thing.get<void*>(), furvm::bad_thing_access);
|
||||
|
||||
EXPECT_NO_THROW(thing.assign<furvm::u8>(67));
|
||||
EXPECT_THROW(thing.assign<furvm::s32>(1337), furvm::bad_thing_access);
|
||||
EXPECT_EQ(thing.get<furvm::u8>(), 67);
|
||||
EXPECT_NO_THROW(thing.set<furvm::u8>(67)); // He talkin' 'bout sum 6-7 while I want sixty ni-
|
||||
EXPECT_THROW(thing.set<furvm::s32>(1337), furvm::bad_thing_access);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user