chore: flat out the file structure

This commit is contained in:
2026-09-11 18:48:16 +02:00
parent 959d0a7773
commit 3b98f77c08
78 changed files with 76 additions and 40 deletions
+455
View File
@@ -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
+177
View File
@@ -0,0 +1,177 @@
/**
* SSA destruction (out-of-SSA phase) for register-based targets based on "Mechanizing Conventional SSA for a
* Verified Destruction with Coalescing" by Delphine Demange and Yon Fernandez de Retana
* (https://dl.acm.org/doi/pdf/10.1145/2892208.2892222 09/11/2026).
*/
#ifndef FURC_MIDDLE_REG_GEN_HPP
#define FURC_MIDDLE_REG_GEN_HPP
#include "furc/middle/ir.hpp"
#include "furc/middle/ssa.hpp"
#include <cassert>
#include <cstdint>
#include <unordered_set>
#include <vector>
namespace furc {
class reg_gen {
public:
class disjoint_set {
public:
std::uint64_t find(std::uint64_t var) {
if (m_parents.find(var) == m_parents.end()) m_parents.emplace(var, var);
if (m_parents[var] == var) return var;
return find(m_parents[var]);
}
void unite(std::uint64_t var1, std::uint64_t var2) {
std::uint64_t rep1 = find(var1);
std::uint64_t rep2 = find(var2);
if (rep1 != rep2) m_parents[rep1] = rep2;
}
private:
std::unordered_map<std::uint64_t, std::uint64_t> m_parents;
};
public:
struct block_info {
std::unordered_set<std::uint64_t> defs;
std::unordered_set<std::uint64_t> uses;
std::unordered_set<std::uint64_t> liveIn;
std::unordered_set<std::uint64_t> liveOut;
};
public:
reg_gen(ir_function& func, ssa& ssa) {
std::vector<block_info> lifeBlocks;
live_analysis(lifeBlocks, func.blocks, ssa.cfgBlocks);
remove_interference(func.blocks, lifeBlocks);
merge(func.blocks);
}
public:
static void live_analysis(std::vector<block_info>& lifeBlocks,
const std::vector<ir_basic_block>& irBlocks,
const std::vector<ssa::cfg_block>& cfgBlocks) {
lifeBlocks.resize(irBlocks.size());
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
const auto& irBlock = irBlocks[i];
auto& block = lifeBlocks[i];
for (const auto& instr : irBlock.instructions) {
for (const auto& op : instr.sources) {
if (op.type != ir_operand::Register) continue;
if (block.defs.find(op.value.reg) != block.defs.end()) continue;
block.uses.insert(op.value.reg);
}
if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue;
block.defs.insert(instr.destination->value.reg);
}
}
bool changed = true;
while (changed) {
changed = false;
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
const auto& irBlock = irBlocks[i];
auto& block = lifeBlocks[i];
std::unordered_set<std::uint64_t> newSet;
for (auto succ : cfgBlocks[i].sucs) {
newSet.insert(lifeBlocks[succ].liveIn.begin(), lifeBlocks[succ].liveIn.end());
}
if (newSet != block.liveOut) {
block.liveOut = newSet;
changed = true;
}
newSet.clear();
newSet.insert(block.uses.begin(), block.uses.end());
for (const auto& var : block.liveOut) {
if (block.defs.find(var) != block.defs.end()) continue;
newSet.insert(var);
}
if (newSet != block.liveIn) {
block.liveIn = newSet;
changed = true;
}
}
}
}
static void remove_interference(std::vector<ir_basic_block>& irBlocks, const std::vector<block_info>& lifeBlocks) {
for (std::uint64_t i = 0; i < irBlocks.size(); ++i) {
auto& irBlock = irBlocks[i];
for (auto it = irBlock.instructions.begin(), end = irBlock.instructions.end();
it != end && it->type == ir_instruction::Phi;
++it) {
assert(it->destination.has_value() && it->destination->type == ir_operand::Register);
const auto& phiDst = it->destination->value.reg;
for (auto& op : it->sources) {
assert(op.type == ir_operand::PhiPair);
const auto& predBlock = lifeBlocks[op.value.phiPair.block];
auto& phiArg = op.value.phiPair.reg;
if (predBlock.liveOut.count(phiArg) == 0 || phiArg == phiDst) continue;
auto oldArg = phiArg;
phiArg.ver = 0; // TODO: Allocate temporary registers
auto& irBlock = irBlocks[op.value.phiPair.block];
assert(!irBlock.instructions.empty());
auto it = irBlock.instructions.end() - 1;
if (ir_instruction::is_terminating(it->type)) --it;
irBlock.instructions.emplace(it,
ir_instruction{ ir_instruction::Move,
ir_operand::reg(phiArg.name, phiArg.ver),
{ ir_operand::reg(oldArg.name, oldArg.ver) } });
}
}
}
}
static void merge(std::vector<ir_basic_block>& irBlocks) {
disjoint_set dj;
for (const auto& block : irBlocks) {
for (const auto& instr : block.instructions) {
if (instr.type != ir_instruction::Phi) break;
assert(instr.destination.has_value() && instr.destination->type == ir_operand::Register);
const auto& phiDst = instr.destination->value.reg;
dj.find(phiDst);
for (const auto& op : instr.sources) {
assert(op.type == ir_operand::PhiPair);
const auto& predBlock = op.value.phiPair.block;
const auto& phiArg = op.value.phiPair.reg;
dj.unite(phiDst, phiArg);
}
}
}
for (auto& block : irBlocks) {
auto it = block.instructions.begin();
while (it != block.instructions.end() && it->type == ir_instruction::Phi) {
it = block.instructions.erase(it);
}
for (; it != block.instructions.end(); ++it) {
for (auto& op : it->sources) {
if (op.type != ir_operand::Register) continue;
op.value.reg = dj.find(op.value.reg);
}
if (!it->destination.has_value() || it->destination->type != ir_operand::Register) continue;
it->destination->value.reg = dj.find(it->destination->value.reg);
}
}
}
};
} // namespace furc
#endif // FURC_MIDDLE_REG_GEN_HPP
+101
View File
@@ -0,0 +1,101 @@
#ifndef FURC_MIDDLE_SSA_HPP
#define FURC_MIDDLE_SSA_HPP
#include "furc/middle/ir.hpp"
#include <cassert>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc {
class ssa {
public:
struct cfg_block {
std::unordered_set<std::uint64_t> preds;
std::unordered_set<std::uint64_t> sucs;
};
struct ssa_block {
std::size_t order = 0;
std::uint64_t idom = -1;
std::unordered_set<std::uint64_t> children; // Children of the block in dominator tree
// Dominance Frontiers
std::unordered_set<std::uint64_t> df;
};
struct register_info {
std::unordered_set<std::uint64_t> sites; // Definition Sites
};
public:
ssa(ir_function& func) {
registers.resize(func.regCount);
compute_cfg(func.blocks, cfgBlocks);
collect_registers(func.blocks, registers, globals);
std::vector<std::uint64_t> order;
compute_rpo(cfgBlocks, ssaBlocks, order);
build_dtree(cfgBlocks, ssaBlocks, order);
compute_dfrontiers(cfgBlocks, ssaBlocks);
ssaification(func.blocks, cfgBlocks, ssaBlocks, registers, globals);
rename(func.blocks, func.regCount, cfgBlocks, ssaBlocks, order);
}
public:
static void compute_cfg(const std::vector<ir_basic_block>& irBlocks, std::vector<cfg_block>& cfgBlocks);
static void collect_registers(const std::vector<ir_basic_block>& irBlocks,
std::vector<register_info>& registers,
std::unordered_set<std::uint64_t>& globals);
static void build_dtree(const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::size_t>& order);
static void compute_dfrontiers(const std::vector<cfg_block>& cfgBlocks, std::vector<ssa_block>& ssaBlocks);
static void compute_rpo(std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
std::vector<std::size_t>& order);
static void ssaification(std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
const std::vector<register_info>& registers,
const std::unordered_set<std::uint64_t>& globals);
static void rename(std::vector<ir_basic_block>& irBlocks,
std::size_t regCount,
const std::vector<cfg_block>& cfgBlocks,
std::vector<ssa_block>& ssaBlocks,
const std::vector<std::uint64_t>& order);
private:
static void rename_rec(std::vector<std::uint64_t>& counters,
std::vector<std::stack<std::uint64_t>>& stacks,
std::vector<ir_basic_block>& irBlocks,
const std::vector<cfg_block>& cfgBlocks,
const std::vector<ssa_block>& ssaBlocks,
std::size_t blockIdx);
private:
static void rpo_dfs(std::unordered_set<std::size_t>& visited,
std::vector<std::size_t>& order,
std::size_t block,
const std::vector<cfg_block>& blocks);
static std::size_t intersect(std::vector<ssa_block>& m_blocks, std::size_t b1, std::size_t b2);
public:
std::vector<cfg_block> cfgBlocks;
std::vector<ssa_block> ssaBlocks;
std::vector<register_info> registers;
std::unordered_set<std::uint64_t> globals;
};
} // namespace furc
#endif // FURC_MIDDLE_SSA_HPP