/** * 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