From 46d0de759aeadb202368830c9f7fbdebeee6f655 Mon Sep 17 00:00:00 2001 From: CHatingPython Date: Thu, 10 Sep 2026 21:48:45 +0200 Subject: [PATCH] feat(furc/ssa): implement SSA stage --- furc/include/furc/middle/ssa.hpp | 90 +++++++- furc/src/main.cpp | 8 +- furc/src/middle/ssa.cpp | 351 ++++++++++++++++++++----------- 3 files changed, 326 insertions(+), 123 deletions(-) diff --git a/furc/include/furc/middle/ssa.hpp b/furc/include/furc/middle/ssa.hpp index c595f24..04037c0 100644 --- a/furc/include/furc/middle/ssa.hpp +++ b/furc/include/furc/middle/ssa.hpp @@ -3,13 +3,97 @@ #include "furc/middle/ir.hpp" +#include +#include +#include +#include +#include + namespace furc { class ssa { - ssa() = delete; public: - static void process(ir_module& mod); - static void destruct(ir_module& mod); + struct cfg_block { + std::unordered_set preds; + std::unordered_set sucs; + }; + + struct ssa_block { + std::size_t order = 0; + + std::uint64_t idom = -1; + + std::unordered_set children; // Children of the block in dominator tree + + // Dominance Frontiers + std::unordered_set df; + }; + + struct register_info { + std::unordered_set sites; // Definition Sites + }; +public: + ssa(ir_function& func) { + m_registers.resize(func.regCount); + compute_cfg(func.blocks, m_cfgBlocks); + collect_registers(func.blocks, m_registers, m_globals); + + std::vector order; + compute_rpo(m_cfgBlocks, m_ssaBlocks, order); + + build_dtree(m_cfgBlocks, m_ssaBlocks, order); + compute_dfrontiers(m_cfgBlocks, m_ssaBlocks); + + ssaification(func.blocks, m_cfgBlocks, m_ssaBlocks, m_registers, m_globals); + rename(func.blocks, func.regCount, m_cfgBlocks, m_ssaBlocks, order); + } +public: + static void compute_cfg(const std::vector& irBlocks, std::vector& cfgBlocks); + + static void collect_registers(const std::vector& irBlocks, + std::vector& registers, + std::unordered_set& globals); + + static void build_dtree(const std::vector& cfgBlocks, + std::vector& ssaBlocks, + const std::vector& order); + + static void compute_dfrontiers(const std::vector& cfgBlocks, std::vector& ssaBlocks); + + static void compute_rpo(std::vector& cfgBlocks, + std::vector& ssaBlocks, + std::vector& order); + + static void ssaification(std::vector& irBlocks, + const std::vector& cfgBlocks, + const std::vector& ssaBlocks, + const std::vector& registers, + const std::unordered_set& globals); + + static void rename(std::vector& irBlocks, + std::size_t regCount, + const std::vector& cfgBlocks, + std::vector& ssaBlocks, + const std::vector& order); +private: + static void rename_rec(std::vector& counters, + std::vector>& stacks, + std::vector& irBlocks, + const std::vector& cfgBlocks, + const std::vector& ssaBlocks, + std::size_t blockIdx); +private: + static void rpo_dfs(std::unordered_set& visited, + std::vector& order, + std::size_t block, + 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; }; } // namespace furc diff --git a/furc/src/main.cpp b/furc/src/main.cpp index 06df215..f282eef 100644 --- a/furc/src/main.cpp +++ b/furc/src/main.cpp @@ -1,3 +1,5 @@ +#ifndef LIBFURC + #include "furc/front/lexer.hpp" #include "furc/front/parser.hpp" #include "furc/middle/ir.hpp" @@ -17,7 +19,11 @@ int main(void) { furc::lexer lexer = { "", content }; furc::parser parser = { std::move(lexer), arena }; furc::ir_module irModule = furc::ir_generator::generate(parser.parse()); - furc::ssa::process(irModule); + for (auto& func : irModule.functions) { + furc::ssa ssa(*func); + } return 0; } + +#endif // LIBFURC diff --git a/furc/src/middle/ssa.cpp b/furc/src/middle/ssa.cpp index bd5999d..08cf6ec 100644 --- a/furc/src/middle/ssa.cpp +++ b/furc/src/middle/ssa.cpp @@ -12,168 +12,140 @@ #include #include +#include +#include #include +#include #include #include namespace furc { -namespace { +void ssa::compute_cfg(const std::vector& irBlocks, std::vector& cfgBlocks) { + cfgBlocks.resize(irBlocks.size()); -struct block_info { - std::size_t order = 0; - - std::unordered_set preds; - std::unordered_set sucs; - std::size_t idom = 0; - - // Dominance Frontiers - std::unordered_set df; -}; - -struct register_info { - std::unordered_set sites; // Definition Sites -}; - -void rpo_dfs(std::unordered_set& visited, - std::vector& order, - std::size_t block, - std::vector& 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); -} - -void compute_rpo(std::vector& blocks, std::vector& order) { - std::unordered_set visited; - if (!blocks.empty()) rpo_dfs(visited, order, 0, blocks); - std::reverse(order.begin(), order.begin()); - for (std::size_t i = 0; i < order.size(); ++i) { - blocks[order[i]].order = i; - } -} - -std::size_t intersect(std::vector& blocks, std::size_t b1, std::size_t b2) { - std::size_t finger1 = b1; - std::size_t finger2 = b2; - while (finger1 != finger2) { - while (finger1 < finger2) - finger1 = blocks[finger1].idom; - while (finger2 < finger1) - finger2 = blocks[finger2].idom; - } - return finger1; -} - -void process_function(ir_function& func) { - std::vector blocks(func.blocks.size()); - std::vector registers(func.regCount); - - std::unordered_set nonLocals; - - // 1. Compute CFG - for (std::size_t i = 0; i < func.blocks.size(); ++i) { - const auto& block = func.blocks[i]; + for (std::size_t i = 0; i < irBlocks.size(); ++i) { + const auto& block = irBlocks[i]; if (block.instructions.empty()) continue; - for (const auto& instr : block.instructions) { - for (const auto& op : instr.sources) { - if (op.type != ir_operand::Register) continue; - const auto& reg = registers[op.value.reg.name]; - if (reg.sites.find(i) != reg.sites.end()) continue; - nonLocals.insert(op.value.reg.name); - } - - if (!instr.destination.has_value() || instr.destination->type != ir_operand::Register) continue; - registers[instr.destination->value.reg.name].sites.insert(i); - } - const auto& termInstr = block.instructions.back(); switch (termInstr.type) { case ir_instruction::Branch: { const auto& dst = termInstr.destination.value(); - if (dst.type != ir_operand::Block) throw std::runtime_error("invalid operand"); - blocks[dst.value.block].preds.insert(i); - blocks[i].sucs.insert(dst.value.block); + 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(); - if (dst.type != ir_operand::BlockPair) throw std::runtime_error("invalid operand"); - blocks[dst.value.blockPair.first].preds.insert(i); - blocks[dst.value.blockPair.second].preds.insert(i); - blocks[i].preds.insert(dst.value.blockPair.first); - blocks[i].preds.insert(dst.value.blockPair.second); + 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; } } +} - // 2. Computing dominance tree - std::vector order; - order.reserve(blocks.size()); - compute_rpo(blocks, order); +void ssa::collect_registers(const std::vector& irBlocks, + std::vector& registers, + std::unordered_set& 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); + } + } +} - blocks[order.front()].idom = order.front(); +void ssa::build_dtree(const std::vector& cfgBlocks, + std::vector& ssaBlocks, + const std::vector& 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::max(); - for (std::size_t i = 1; i < order.size(); ++i) { - auto& block = blocks[order[i]]; - std::size_t newIdom = -1; - bool found = false; - for (auto pred : block.preds) { - if (blocks[pred].idom == -1) continue; - newIdom = found ? intersect(blocks, pred, newIdom) : pred; + 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 (block.idom != newIdom) { - block.idom = newIdom; - changed = true; + if (ssaBlocks[*it].idom != newIdom) { + ssaBlocks[*it].idom = newIdom; + changed = true; } } } +} - // 3. Computing Dominance Frontiers - for (std::size_t j = 0; j < blocks.size(); ++j) { - const auto& join = blocks[j]; - if (join.preds.size() < 2) continue; - for (std::size_t runner : join.preds) { - while (runner != join.idom) { - blocks[runner].df.insert(j); - runner = blocks[runner].idom; +void ssa::compute_dfrontiers(const std::vector& cfgBlocks, std::vector& 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; } } } +} - // 4. Inserting Phi-nodes (Semi-Pruned SSA form) - std::vector worklist; +void ssa::compute_rpo(std::vector& cfgBlocks, + std::vector& ssaBlocks, + std::vector& order) { + std::unordered_set 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; + } +} - for (std::size_t i = 0; i < registers.size(); ++i) { +void ssa::ssaification(std::vector& irBlocks, + const std::vector& cfgBlocks, + const std::vector& ssaBlocks, + const std::vector& registers, + const std::unordered_set& globals) { + std::vector worklist; + + for (std::uint64_t i = 0; i < registers.size(); ++i) { const auto& reg = registers[i]; - if (reg.sites.size() < 2 || nonLocals.find(i) == nonLocals.end()) continue; - + if (reg.sites.size() < 2 || globals.find(i) == globals.end()) continue; worklist.insert(worklist.end(), reg.sites.begin(), reg.sites.end()); - std::unordered_set done; + std::unordered_set done; while (!worklist.empty()) { const auto blockIdx = worklist.back(); worklist.pop_back(); - for (auto frontier : blocks[blockIdx].df) { + + for (auto frontier : ssaBlocks[blockIdx].df) { if (done.find(frontier) != done.end()) continue; done.insert(frontier); - auto& target = func.blocks[frontier]; - - ir_instruction instr = { ir_instruction::Phi }; - for (const auto& pred : blocks[frontier].preds) + 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); @@ -182,13 +154,154 @@ void process_function(ir_function& func) { } } -} // namespace +void ssa::rename(std::vector& irBlocks, + std::size_t regCount, + const std::vector& cfgBlocks, + std::vector& ssaBlocks, + const std::vector& order) { + std::vector counters; + std::vector> stacks; -void ssa::process(ir_module& mod) { - for (auto* func : mod.functions) - process_function(*func); + 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::max()) ssaBlocks[parent].children.emplace(*it); + } + + rename_rec(counters, stacks, irBlocks, cfgBlocks, ssaBlocks, order.front()); } -void ssa::destruct(ir_module& mod) {} +void ssa::rename_rec(std::vector& counters, + std::vector>& stacks, + std::vector& irBlocks, + const std::vector& cfgBlocks, + const std::vector& ssaBlocks, + std::size_t blockIdx) { + std::unordered_map 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& visited, + std::vector& order, + std::size_t block, + const std::vector& 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& 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 counters; +// std::vector> 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::max()) blocks[parent].children.emplace(order[i]); +// } +// +// auto rename = [&counters, &stacks, &blocks, &func](auto& self, std::size_t blockIdx) -> void { +// std::unordered_map 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