From d0f3637539786f2adad27e6a863b28b85c0fe172 Mon Sep 17 00:00:00 2001 From: CHatingPython Date: Sat, 13 Jun 2026 14:46:16 +0200 Subject: [PATCH] feat(furc/ssa): implement basic constant propagation Refs: #2 --- furc/include/furc/front/ssa.hpp | 2 + furc/src/front/ssa.cpp | 221 +++++++++++++++++++++++++ furlang/include/furlang/ir/operand.hpp | 15 ++ 3 files changed, 238 insertions(+) diff --git a/furc/include/furc/front/ssa.hpp b/furc/include/furc/front/ssa.hpp index 23c569c..088c602 100644 --- a/furc/include/furc/front/ssa.hpp +++ b/furc/include/furc/front/ssa.hpp @@ -20,6 +20,8 @@ public: private: static void optimize(const std::unique_ptr& func); + static void constant_propagation(const std::unique_ptr& func); + static void dfs_rpo(furlang::ir::block_index block, const block_map_t& successors, std::unordered_set& visited, diff --git a/furc/src/front/ssa.cpp b/furc/src/front/ssa.cpp index 155f1fd..544e8fd 100644 --- a/furc/src/front/ssa.cpp +++ b/furc/src/front/ssa.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -17,6 +19,7 @@ namespace furc::front { void ssa::optimize(furlang::ir::module& mod) { for (const auto& func : mod.functions()) { ssa::optimize(func); + ssa::constant_propagation(func); } } @@ -260,6 +263,224 @@ void ssa::optimize(const std::unique_ptr& func) { renameBlock(entry); } +enum class lattice_t { + Top, + Constant, + Bottom, +}; + +struct lattice { + lattice_t type = lattice_t::Top; + std::uint64_t value = 0; + + bool operator==(const lattice& rhs) const { + if (type != rhs.type) return false; + return type != lattice_t::Constant || value == rhs.value; + } +}; + +void ssa::constant_propagation(const std::unique_ptr& func) { + using block_idx = furlang::ir::block_index; + using reg_t = furlang::ir::register_operand; + using reg2_t = furlang::ir::register_t; + + std::unordered_map latVals; + std::unordered_map> edges; + std::unordered_set executableBlocks; + std::set> executedEdges; + + std::queue> cfgWorklist; + std::queue ssaWorklist; + + std::unordered_map blockMap; + + auto getOperandLattice = [&](const furlang::ir::operand& op) -> lattice { + if (op.type() == furlang::ir::operand_t::Integer) { + lattice lat; + lat.type = lattice_t::Constant; + lat.value = op.integer(); + return lat; + } + if (op.type() == furlang::ir::operand_t::Register) { + auto reg = op.reg(); + if (latVals.find(reg) == latVals.end()) return { lattice_t::Top }; + return latVals[reg]; + } + return { lattice_t::Bottom }; + }; + + for (block_idx blockIdx = 0; blockIdx < func->blocks().size(); ++blockIdx) { + const auto& block = func->blocks()[blockIdx]; + auto& instrs = block->instructions(); + for (auto it = instrs.begin(); it != instrs.end(); ++it) { + const auto& instr = *it; + blockMap[instr.get()] = blockIdx; + for (const auto& op : instr->sources()) { + if (op->type() != furlang::ir::operand_t::Register) continue; + edges[op->reg()].push_back(instr.get()); + } + } + + blockMap[block->exit().get()] = blockIdx; + for (const auto& op : block->exit()->sources()) { + if (op->type() != furlang::ir::operand_t::Register) continue; + edges[op->reg()].push_back(block->exit().get()); + } + } + + cfgWorklist.push({ 0, 0 }); + while (!cfgWorklist.empty() || !ssaWorklist.empty()) { + if (!cfgWorklist.empty()) { + auto edge = cfgWorklist.front(); + cfgWorklist.pop(); + block_idx from = edge.first; + block_idx to = edge.second; + + if (executedEdges.count(edge) != 0) continue; + executedEdges.insert(edge); + + bool firstVisit = (executableBlocks.find(to) == executableBlocks.end()); + executableBlocks.insert(to); + + const auto& block = func->blocks()[to]; + if (firstVisit) { + for (auto& instr : block->instructions()) { + ssaWorklist.push(instr.get()); + } + ssaWorklist.push(block->exit().get()); + } else { + for (auto& instr : block->instructions()) { + if (instr->type() != furlang::ir::instruction_t::Phi) break; + ssaWorklist.push(instr.get()); + } + } + } + + if (!ssaWorklist.empty()) { + auto* instr = ssaWorklist.front(); + ssaWorklist.pop(); + + block_idx blockIdx = blockMap[instr]; + if (executableBlocks.find(blockIdx) == executableBlocks.end()) continue; + + lattice newLat = { lattice_t::Top }; + + switch (instr->type()) { + case furlang::ir::instruction_t::Phi: { + auto& phi = dynamic_cast(*instr); + for (const auto& [op, label] : phi.labels()) { + if (executedEdges.count({ label, blockIdx }) == 0) continue; + lattice opLat = getOperandLattice(op); + if (opLat.type == lattice_t::Bottom) newLat.type = lattice_t::Bottom; + if (opLat.type == lattice_t::Constant) { + if (newLat.type == lattice_t::Top) { + newLat = opLat; + } else if (newLat.type == lattice_t::Constant && newLat.value != opLat.value) { + newLat.type = lattice_t::Bottom; + } + } + } + } break; + case furlang::ir::instruction_t::Assign: { + newLat = getOperandLattice(*instr->sources().front()); + } break; + case furlang::ir::instruction_t::BinaryOp: { + lattice lhs = getOperandLattice(*instr->sources()[0]); + lattice rhs = getOperandLattice(*instr->sources()[1]); + + if (lhs.type == lattice_t::Bottom || rhs.type == lattice_t::Bottom) { + newLat.type = lattice_t::Bottom; + } else if (lhs.type == lattice_t::Constant && rhs.type == lattice_t::Constant) { + newLat.type = lattice_t::Constant; + switch (dynamic_cast(*instr).op_type()) { + case furlang::ir::binary_op_instruction_t::Add: newLat.value = lhs.value + rhs.value; break; + case furlang::ir::binary_op_instruction_t::Sub: newLat.value = lhs.value - rhs.value; break; + case furlang::ir::binary_op_instruction_t::Mul: newLat.value = lhs.value * rhs.value; break; + case furlang::ir::binary_op_instruction_t::Div: newLat.value = lhs.value / rhs.value; break; + case furlang::ir::binary_op_instruction_t::Mod: newLat.value = lhs.value % rhs.value; break; + case furlang::ir::binary_op_instruction_t::Eq: + newLat.value = (lhs.value == rhs.value) ? 1 : 0; + break; + case furlang::ir::binary_op_instruction_t::NotEq: + newLat.value = (lhs.value != rhs.value) ? 1 : 0; + break; + case furlang::ir::binary_op_instruction_t::LessThan: + newLat.value = (lhs.value < rhs.value) ? 1 : 0; + break; + case furlang::ir::binary_op_instruction_t::GreaterThan: + newLat.value = (lhs.value > rhs.value) ? 1 : 0; + break; + case furlang::ir::binary_op_instruction_t::LessEq: + newLat.value = (lhs.value <= rhs.value) ? 1 : 0; + break; + case furlang::ir::binary_op_instruction_t::GreaterEq: + newLat.value = (lhs.value >= rhs.value) ? 1 : 0; + break; + } + } + } break; + default: break; + } + + if (instr->has_destination() && instr->destination().type() == furlang::ir::operand_t::Register) { + auto dst = instr->destination().reg(); + if (!(latVals[dst] == newLat)) { + latVals[dst] = newLat; + for (auto* uInstr : edges[dst]) + ssaWorklist.push(uInstr); + } + } + + if (instr == func->blocks()[blockIdx]->exit().get()) { + auto* exit = func->blocks()[blockIdx]->exit().get(); + if (exit->type() == furlang::ir::instruction_t::Branch) { + auto& br = dynamic_cast(*exit); + cfgWorklist.push({ blockIdx, br.block() }); + } else if (exit->type() == furlang::ir::instruction_t::BranchCond) { + auto& br = dynamic_cast(*exit); + lattice cond = getOperandLattice(*exit->sources()[0]); + + if (cond.type == lattice_t::Constant) { + if (cond.value != 0) + cfgWorklist.push({ blockIdx, br.if_block() }); + else + cfgWorklist.push({ blockIdx, br.else_block() }); + } else { + cfgWorklist.push({ blockIdx, br.if_block() }); + cfgWorklist.push({ blockIdx, br.else_block() }); + } + } + } + } + } + + for (block_idx i = 0; i < func->blocks().size(); ++i) { + if (executableBlocks.find(i) == executableBlocks.end()) { + func->blocks()[i]->instructions().clear(); + continue; + } + + const auto& block = func->blocks()[i]; + + for (auto& instr : block->instructions()) { + for (auto& op : instr->sources()) { + if (op->type() != furlang::ir::operand_t::Register) continue; + auto reg = op->reg(); + if (latVals[reg].type != lattice_t::Constant) continue; + *op = furlang::ir::operand::new_integer(latVals[reg].value); + } + } + + auto* exit = block->exit().get(); + if (exit->type() != furlang::ir::instruction_t::BranchCond) continue; + auto& br = dynamic_cast(*exit); + lattice cond = getOperandLattice(*exit->sources()[0]); + if (cond.type != lattice_t::Constant) continue; + block_idx target = (cond.value != 0) ? br.if_block() : br.else_block(); + block->exit() = std::make_unique(target); + } +} + void ssa::dfs_rpo(furlang::ir::block_index block, const block_map_t& successors, std::unordered_set& visited, diff --git a/furlang/include/furlang/ir/operand.hpp b/furlang/include/furlang/ir/operand.hpp index 81406d9..ed29c6b 100644 --- a/furlang/include/furlang/ir/operand.hpp +++ b/furlang/include/furlang/ir/operand.hpp @@ -44,6 +44,8 @@ struct register_operand { 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; } }; /** @@ -270,4 +272,17 @@ private: } // namespace ir } // namespace furlang +namespace std { + +template <> +struct hash { + std::size_t operator()(const furlang::ir::register_operand& op) const noexcept { + std::size_t h1 = std::hash{}(op.reg); + std::size_t h2 = std::hash{}(op.ver); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + } +}; + +} // namespace std + #endif // FURLANG_IR_OPERAND_HPP