Compare commits

..

6 Commits

Author SHA1 Message Date
CHatingPython be386cf230 feat(furc/ssa): implement renaming
Refs: #2
2026-06-12 22:07:26 +02:00
CHatingPython a14252bd1d refactor(furlang/ir): improve instruction interface
Refs: #2
2026-06-12 14:45:26 +02:00
CHatingPython 6b92e19b23 refactor(furlang/ir): add version to register_operand
Refs: #2
2026-06-12 14:07:09 +02:00
CHatingPython 865691df26 feat(furc, ssa): implement phi instruction placement
Refs: #2
2026-06-12 13:45:35 +02:00
CHatingPython b4fda8d7d0 feat(furc): start working on ssa optimizations
Compute dominance frontiers.

Refs: #2
2026-06-12 12:21:52 +02:00
CHatingPython 3f6fcc56ff feat(furlang): introduce phi instruction
Refs: #2
2026-06-12 09:43:42 +02:00
5 changed files with 493 additions and 7 deletions
+32
View File
@@ -0,0 +1,32 @@
#ifndef FURC_FRONT_SSA_HPP
#define FURC_FRONT_SSA_HPP
#include "furlang/ir/function.hpp"
#include "furlang/ir/instruction.hpp"
#include "furlang/ir/module.hpp"
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc {
namespace front {
class ssa {
using block_map_t = std::unordered_map<furlang::ir::block_index, std::vector<furlang::ir::block_index>>;
public:
static void optimize(furlang::ir::module& mod);
private:
static void optimize(const std::unique_ptr<furlang::ir::function>& func);
static void dfs_rpo(furlang::ir::block_index block,
const block_map_t& successors,
std::unordered_set<furlang::ir::block_index>& visited,
std::vector<furlang::ir::block_index>& rpo);
};
} // namespace front
} // namespace furc
#endif // FURC_FRONT_SSA_HPP
+253
View File
@@ -0,0 +1,253 @@
#include "furc/front/ssa.hpp"
#include "furlang/ir/instruction.hpp"
#include "furlang/ir/operand.hpp"
#include <algorithm>
#include <cstddef>
#include <functional>
#include <memory>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace furc::front {
void ssa::optimize(furlang::ir::module& mod) {
for (const auto& func : mod.functions()) {
ssa::optimize(func);
}
}
void ssa::optimize(const std::unique_ptr<furlang::ir::function>& func) {
block_map_t predecessors;
block_map_t successors;
std::unordered_map<furlang::ir::register_t, std::unordered_set<furlang::ir::block_index>> regSites;
std::unordered_map<furlang::ir::block_index, std::unordered_set<furlang::ir::register_t>> regUses;
std::unordered_map<furlang::ir::block_index, furlang::ir::block_index> idoms;
std::unordered_map<furlang::ir::register_t, std::uint32_t> regVers;
std::unordered_map<furlang::ir::register_t, std::stack<std::uint32_t>> regVerStacks;
std::unordered_map<furlang::ir::block_index, std::vector<furlang::ir::block_index>> domTree;
for (furlang::ir::block_index i = 0; i < func->blocks().size(); ++i) {
const auto& block = func->blocks()[i];
for (const auto& instr : block->instructions()) {
if (instr->has_destination()) {
if (instr->destination().type() == furlang::ir::operand_t::Register) {
regSites[instr->destination().reg()].insert(i);
}
}
for (const furlang::ir::operand* src : instr->sources()) {
if (src->type() != furlang::ir::operand_t::Register) continue;
regUses[i].insert(src->reg());
}
}
const auto& exit = block->exit();
switch (exit->type()) {
case furlang::ir::instruction_t::Branch: {
const auto& br = dynamic_cast<const furlang::ir::branch_instruction&>(*exit);
predecessors[br.block()].push_back(i);
successors[i].push_back(br.block());
} break;
case furlang::ir::instruction_t::BranchCond: {
const auto& br = dynamic_cast<const furlang::ir::branch_cond_instruction&>(*exit);
predecessors[br.if_block()].push_back(i);
predecessors[br.else_block()].push_back(i);
successors[i].push_back(br.if_block());
successors[i].push_back(br.else_block());
} break;
default: break;
}
}
std::unordered_set<furlang::ir::block_index> visited;
std::vector<furlang::ir::block_index> rpoOrder;
dfs_rpo(0, successors, visited, rpoOrder);
std::reverse(rpoOrder.begin(), rpoOrder.end());
std::unordered_map<furlang::ir::block_index, std::size_t> rpoIndex;
for (std::size_t i = 0; i < rpoOrder.size(); ++i) {
rpoIndex[rpoOrder[i]] = i;
}
auto intersect = [&](furlang::ir::block_index block1, furlang::ir::block_index block2) {
while (block1 != block2) {
while (rpoIndex[block1] > rpoIndex[block2]) {
block1 = idoms[block1];
}
while (rpoIndex[block2] > rpoIndex[block1]) {
block2 = idoms[block2];
}
}
return block1;
};
auto entry = rpoOrder.front();
idoms[entry] = entry;
bool changed = true;
while (changed) {
changed = false;
for (std::size_t i = 1; i < rpoOrder.size(); ++i) {
auto block = rpoOrder[i];
furlang::ir::block_index newIdom = 0;
bool found = false;
for (auto pred : predecessors[block]) {
if (idoms.find(pred) == idoms.end()) continue;
if (!found) {
newIdom = pred;
found = true;
} else {
newIdom = intersect(pred, newIdom);
}
}
if (idoms.find(block) == idoms.end() || idoms[block] != newIdom) {
idoms[block] = newIdom;
changed = true;
}
}
}
std::unordered_map<furlang::ir::block_index, std::unordered_set<furlang::ir::block_index>> df;
for (auto block : rpoOrder) {
df[block] = std::unordered_set<furlang::ir::block_index>{};
}
for (auto block : rpoOrder) {
if (predecessors[block].size() < 2) continue;
for (auto pred : predecessors[block]) {
auto runner = pred;
while (runner != idoms[block]) {
df[runner].insert(block);
runner = idoms[runner];
}
}
}
std::unordered_map<furlang::ir::block_index, std::unordered_set<furlang::ir::register_t>> phis;
for (const auto& [reg, blocks] : regSites) {
std::vector<furlang::ir::block_index> worklist(blocks.begin(), blocks.end());
std::unordered_set<furlang::ir::block_index> added;
while (!worklist.empty()) {
auto block = worklist.back();
worklist.pop_back();
for (auto frontier : df[block]) {
if (added.find(frontier) != added.end()) continue;
phis[frontier].insert(reg);
added.insert(frontier);
if (blocks.find(frontier) == blocks.end()) {
worklist.push_back(frontier);
}
}
}
}
for (furlang::ir::block_index i = 0; i < func->blocks().size(); ++i) {
if (phis.find(i) == phis.end()) continue;
const auto& block = func->blocks()[i];
const auto& preds = predecessors[i];
for (auto reg : phis[i]) {
if (regUses[i].find(reg) == regUses[i].end()) continue;
auto phiInstr = std::make_unique<furlang::ir::phi_instruction>(reg);
for (auto pred : preds) {
phiInstr->labels().emplace_back(furlang::ir::operand::new_reg(reg), pred);
}
block->instructions().emplace(block->instructions().begin(), std::move(phiInstr));
}
}
for (const auto& [block, idom] : idoms) {
if (block != idom) domTree[idom].push_back(block);
}
std::function<void(furlang::ir::block_index)> renameBlock = [&](furlang::ir::block_index blockIndex) -> void {
std::unordered_map<furlang::ir::register_t, std::uint32_t> pushed;
const auto& block = func->blocks()[blockIndex];
for (auto& instr : block->instructions()) {
if (instr->type() != furlang::ir::instruction_t::Phi) continue;
auto orig = instr->destination().reg();
std::uint32_t newVer = regVers[orig]++;
instr->destination().reg().ver = newVer;
regVerStacks[orig].push(newVer);
++pushed[orig];
}
for (auto& instr : block->instructions()) {
if (instr->type() == furlang::ir::instruction_t::Phi) continue;
for (auto& operand : instr->sources()) {
if (operand->type() != furlang::ir::operand_t::Register) continue;
auto orig = operand->reg();
if (!regVerStacks[orig].empty()) {
operand->reg().ver = regVerStacks[orig].top();
}
}
if (instr->has_destination() || instr->destination().type() == furlang::ir::operand_t::Register) {
auto orig = instr->destination().reg();
std::uint32_t newVer = regVers[orig]++;
instr->destination().reg().ver = newVer;
regVerStacks[orig].push(newVer);
++pushed[orig];
}
}
for (auto succIndex : successors[blockIndex]) {
const auto& succ = func->blocks()[succIndex];
for (auto& instr : succ->instructions()) {
if (instr->type() != furlang::ir::instruction_t::Phi) break;
auto& phi = dynamic_cast<furlang::ir::phi_instruction&>(*instr);
for (auto& [op, bl] : phi.labels()) {
if (bl != blockIndex) continue;
if (regVerStacks[op.reg()].empty()) continue;
op.reg().ver = regVerStacks[op.reg()].top();
}
}
}
for (const auto& child : domTree[blockIndex]) {
renameBlock(child);
}
for (const auto& [reg, count] : pushed) {
for (std::uint32_t i = 0; i < count; ++i) {
regVerStacks[reg].pop();
}
}
};
renameBlock(entry);
}
void ssa::dfs_rpo(furlang::ir::block_index block,
const block_map_t& successors,
std::unordered_set<furlang::ir::block_index>& visited,
std::vector<furlang::ir::block_index>& rpo) {
visited.insert(block);
if (auto it = successors.find(block); it != successors.end()) {
for (auto successor : it->second) {
if (visited.find(successor) == visited.end()) {
dfs_rpo(successor, successors, visited, rpo);
}
}
}
rpo.push_back(block);
}
} // namespace furc::front
+3
View File
@@ -3,6 +3,7 @@
#include "furc/ast/program.hpp" #include "furc/ast/program.hpp"
#include "furc/front/ir_generator.hpp" #include "furc/front/ir_generator.hpp"
#include "furc/front/parser.hpp" #include "furc/front/parser.hpp"
#include "furc/front/ssa.hpp"
#include <iostream> #include <iostream>
@@ -34,6 +35,8 @@ int main(void) {
program->accept(generator); program->accept(generator);
auto module = std::move(generator.move_module()); auto module = std::move(generator.move_module());
furc::front::ssa::optimize(module);
std::cout << "Generated IR:\n"; std::cout << "Generated IR:\n";
for (const auto& function : module.functions()) { for (const auto& function : module.functions()) {
std::cout << function->name() << ":\n"; std::cout << function->name() << ":\n";
+161 -5
View File
@@ -6,6 +6,9 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <ostream> #include <ostream>
#include <stdexcept>
#include <utility>
#include <vector>
namespace furlang { namespace furlang {
namespace ir { namespace ir {
@@ -21,6 +24,7 @@ enum class instruction_t {
Branch, /**< Branch */ Branch, /**< Branch */
BranchCond, /**< Conditional branch */ BranchCond, /**< Conditional branch */
Return, /**< Return */ Return, /**< Return */
Phi, /**< Phi function */
}; };
/** /**
@@ -66,6 +70,41 @@ public:
* @return The type. * @return The type.
*/ */
virtual instruction_t type() const = 0; virtual instruction_t type() const = 0;
/**
* @brief Returns whether this instruction has a destination operand.
*
* @return true if this instruction has the destination operand.
*/
virtual bool has_destination() const { return false; }
/**
* @brief Returns destination operand of this instruction.
*
* @return The destination operand.
*/
virtual operand& destination() { throw std::runtime_error("instruction type mismatch"); }
/**
* @brief Returns destination operand of this instruction.
*
* @return The destination operand.
*/
virtual const operand& destination() const { throw std::runtime_error("instruction type mismatch"); }
/**
* @brief Returns a list of source operands of this instruction.
*
* @return The list of source operands.
*/
virtual std::vector<operand*> sources() { return {}; }
/**
* @brief Returns a list of source operands of this instruction.
*
* @return The list of source operands.
*/
virtual std::vector<const operand*> sources() const { return {}; }
public: public:
/** /**
* @brief Prints an instruction to an output stream. * @brief Prints an instruction to an output stream.
@@ -158,18 +197,39 @@ public:
instruction_t type() const override { return instruction_t::Assign; } instruction_t type() const override { return instruction_t::Assign; }
/** /**
* @brief Returns this instruction's source. * @brief Returns whether this instruction has a destination operand.
* *
* @return The source. * @return true
*/ */
const operand& source() const { return m_source; } bool has_destination() const override { return true; }
/** /**
* @brief Returns this instruction's destination. * @brief Returns this instruction's destination.
* *
* @return The destination. * @return The destination.
*/ */
const operand& destination() const { return m_destination; } operand& destination() override { return m_destination; }
/**
* @brief Returns this instruction's destination.
*
* @return The destination.
*/
const operand& destination() const override { return m_destination; }
/**
* @brief Returns a list of this instruction's source operands.
*
* @return The list of source operands.
*/
std::vector<operand*> sources() override { return { &m_source }; }
/**
* @brief Returns a list of this instruction's source operands.
*
* @return The list of source operands.
*/
std::vector<const operand*> sources() const override { return { &m_source }; }
private: private:
operand m_source; operand m_source;
operand m_destination; operand m_destination;
@@ -253,6 +313,16 @@ public:
*/ */
instruction_t type() const override { return instruction_t::BinaryOp; } instruction_t type() const override { return instruction_t::BinaryOp; }
bool has_destination() const override { return true; }
operand& destination() override { return m_dst; }
const operand& destination() const override { return m_dst; }
std::vector<operand*> sources() override { return { &m_lhs, &m_rhs }; }
std::vector<const operand*> sources() const override { return { &m_lhs, &m_rhs }; }
/** /**
* @brief Returns this instruction's operation type. * @brief Returns this instruction's operation type.
* *
@@ -442,6 +512,16 @@ public:
*/ */
instruction_t type() const override { return instruction_t::Return; } instruction_t type() const override { return instruction_t::Return; }
std::vector<operand*> sources() override {
if (m_value.has_value()) return { &*m_value };
return {};
}
std::vector<const operand*> sources() const override {
if (m_value.has_value()) return { &*m_value };
return {};
}
/** /**
* @brief Returns this instruction's return value operand. * @brief Returns this instruction's return value operand.
* *
@@ -458,7 +538,83 @@ protected:
} }
}; };
/**
* @brief Phi instruction
*
* Used to implement the phi node in the SSA graph.
*/
class phi_instruction final : public instruction {
public:
phi_instruction(register_operand dst)
: m_dst(operand::new_reg(dst)) {}
~phi_instruction() override = default;
/**
* @brief Move constructor.
*/
phi_instruction(phi_instruction&&) noexcept = default;
/**
* @brief Move constructor.
*/
phi_instruction& operator=(phi_instruction&&) noexcept = default;
phi_instruction(const phi_instruction&) = delete;
phi_instruction& operator=(const phi_instruction&) = delete;
public:
/**
* @brief Returns this instruction's type.
*
* @return instruction_t::Phi.
*/
instruction_t type() const override { return instruction_t::Phi; }
/**
* @brief Returns this instruction's destination register.
*
* @return The register.
*/
operand& destination() override { return m_dst; }
/**
* @brief Returns this instruction's destination register.
*
* @return The register.
*/
const operand& destination() const override { return m_dst; }
/**
* @brief Returns this instruction's labels.
*
* @return The labels.
*/
std::vector<std::pair<operand, block_index>>& labels() { return m_labels; }
/**
* @brief Returns this instruction's labels.
*
* @return The labels.
*/
const std::vector<std::pair<operand, block_index>>& labels() const { return m_labels; }
private:
operand m_dst;
std::vector<std::pair<operand, block_index>> m_labels;
protected:
std::ostream& print(std::ostream& os) const override {
os << "phi " << m_dst << " =";
bool first = true;
for (const auto& pair : m_labels) {
if (!first) os << ',';
first = false;
os << ' ' << pair.second << ": " << pair.first;
}
return os;
}
};
} // namespace ir } // namespace ir
} // namespace furlang } // namespace furlang
#endif // FURLANG_IR_INSTRUCTION_HPP #endif // FURLANG_IR_INSTRUCTION_HPP
+44 -2
View File
@@ -19,11 +19,33 @@ enum class operand_t {
String, /**< String */ String, /**< String */
}; };
/**
* @brief Alias to a register type.
*/
using register_t = std::uint32_t;
/** /**
* @brief Register operand alias. * @brief Register operand alias.
* @see operand_t::Register * @see operand_t::Register
*/ */
using register_operand = std::uint32_t; struct register_operand {
register_t reg;
std::uint32_t ver = 0;
register_operand(register_t reg)
: reg(reg) {}
register_operand(register_t reg, std::uint32_t ver)
: reg(reg), ver(ver) {}
operator std::uint32_t&() { return reg; }
operator const std::uint32_t&() const { return reg; }
friend std::ostream& operator<<(std::ostream& os, const register_operand& op) {
return os << op.reg << '_' << op.ver;
}
};
/** /**
* @brief Variable operand alias. * @brief Variable operand alias.
* @see operand_t::Variable * @see operand_t::Variable
@@ -102,6 +124,19 @@ public:
operand(const operand&) = delete; operand(const operand&) = delete;
operand& operator=(const operand&) = delete; operand& operator=(const operand&) = delete;
public: public:
/**
* @brief Construct a new register operand.
*
* @param value Value of the new register operand.
* @return The register operand.
*/
static operand new_reg(register_t value) {
operand operand;
operand.m_type = operand_t::Register;
operand.m_value.reg = { value, 0 };
return operand;
}
/** /**
* @brief Construct a new register operand. * @brief Construct a new register operand.
* *
@@ -163,6 +198,13 @@ public:
*/ */
operand_t type() const { return m_type; } operand_t type() const { return m_type; }
/**
* @brief Returns this operand's register value.
*
* @return The register value.
*/
register_operand& reg() { return m_value.reg; }
/** /**
* @brief Returns this operand's register value. * @brief Returns this operand's register value.
* *
@@ -228,4 +270,4 @@ private:
} // namespace ir } // namespace ir
} // namespace furlang } // namespace furlang
#endif // FURLANG_IR_OPERAND_HPP #endif // FURLANG_IR_OPERAND_HPP