diff --git a/furc/include/furc/middle/ir.hpp b/furc/include/furc/middle/ir.hpp index 6326c88..37b61d4 100644 --- a/furc/include/furc/middle/ir.hpp +++ b/furc/include/furc/middle/ir.hpp @@ -34,6 +34,16 @@ struct ir_operand { struct register_s { std::uint64_t name : 54; std::uint64_t ver : 10; + + register_s() = default; + + register_s(std::uint64_t id) + : name((id >> 10) & ((1ULL << 54) - 1)), ver((id >> 0) & ((1 << 10) - 1)) {} + + register_s(std::uint64_t name, std::uint64_t ver) + : name(name), ver(ver) {} + + operator std::uint64_t() const { return name << 10 | ver; } } reg; std::uint16_t variable; std::uint16_t global; diff --git a/furc/include/furc/middle/reg_gen.hpp b/furc/include/furc/middle/reg_gen.hpp new file mode 100644 index 0000000..59f162c --- /dev/null +++ b/furc/include/furc/middle/reg_gen.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 +#include +#include +#include + +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 m_parents; + }; +public: + struct block_info { + std::unordered_set defs; + std::unordered_set uses; + std::unordered_set liveIn; + std::unordered_set liveOut; + }; +public: + reg_gen(ir_function& func, ssa& ssa) { + std::vector lifeBlocks; + live_analysis(lifeBlocks, func.blocks, ssa.cfgBlocks); + remove_interference(func.blocks, lifeBlocks); + merge(func.blocks); + } +public: + static void live_analysis(std::vector& lifeBlocks, + const std::vector& irBlocks, + const std::vector& 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 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& irBlocks, const std::vector& 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& 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 diff --git a/furc/include/furc/middle/ssa.hpp b/furc/include/furc/middle/ssa.hpp index 04037c0..028797a 100644 --- a/furc/include/furc/middle/ssa.hpp +++ b/furc/include/furc/middle/ssa.hpp @@ -34,18 +34,18 @@ public: }; public: ssa(ir_function& func) { - m_registers.resize(func.regCount); - compute_cfg(func.blocks, m_cfgBlocks); - collect_registers(func.blocks, m_registers, m_globals); + registers.resize(func.regCount); + compute_cfg(func.blocks, cfgBlocks); + collect_registers(func.blocks, registers, globals); std::vector order; - compute_rpo(m_cfgBlocks, m_ssaBlocks, order); + compute_rpo(cfgBlocks, ssaBlocks, order); - build_dtree(m_cfgBlocks, m_ssaBlocks, order); - compute_dfrontiers(m_cfgBlocks, m_ssaBlocks); + build_dtree(cfgBlocks, ssaBlocks, order); + compute_dfrontiers(cfgBlocks, ssaBlocks); - ssaification(func.blocks, m_cfgBlocks, m_ssaBlocks, m_registers, m_globals); - rename(func.blocks, func.regCount, m_cfgBlocks, m_ssaBlocks, order); + ssaification(func.blocks, cfgBlocks, ssaBlocks, registers, globals); + rename(func.blocks, func.regCount, cfgBlocks, ssaBlocks, order); } public: static void compute_cfg(const std::vector& irBlocks, std::vector& cfgBlocks); @@ -89,11 +89,11 @@ private: const std::vector& blocks); static std::size_t intersect(std::vector& m_blocks, std::size_t b1, std::size_t b2); -private: - std::vector m_cfgBlocks; - std::vector m_ssaBlocks; - std::vector m_registers; - std::unordered_set m_globals; +public: + std::vector cfgBlocks; + std::vector ssaBlocks; + std::vector registers; + std::unordered_set globals; }; } // namespace furc diff --git a/furc/src/main.cpp b/furc/src/main.cpp index f282eef..ce17ccc 100644 --- a/furc/src/main.cpp +++ b/furc/src/main.cpp @@ -3,6 +3,7 @@ #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" @@ -20,7 +21,8 @@ int main(void) { 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::ssa ssa(*func); + furc::reg_gen gen(*func, ssa); } return 0;