feat(mir): 添加指令级活变量分析——精确 [start,end] 区间

- 三阶段算法:块级 backward dataflow fixpoint + 指令级反向扫描 + 区间构建
- 支持 phi-copy 插入(非严格 SSA):union 区间
- 31 种 MIR opcode 的 def 判定(HasVRegDef)
- 用于线性扫描寄存器分配的精确活跃区间输入
lzk
lzkk 4 days ago
parent 6c5441ff43
commit a9ebfdc0e0

@ -0,0 +1,426 @@
#include "mir/MIR.h"
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "utils/Log.h"
namespace mir
{
namespace
{
// ---- Phase 1 helpers -------------------------------------------------
/// Return true if opcode has a VReg def (always operands[0]).
static bool HasVRegDef(Opcode opcode)
{
switch (opcode)
{
case Opcode::MovImm:
case Opcode::LoadStack:
case Opcode::LoadGlobal:
case Opcode::LoadGlobalAddr:
case Opcode::LoadStackAddr:
case Opcode::LoadMem:
case Opcode::AddRR:
case Opcode::SubRR:
case Opcode::AddImm:
case Opcode::SubImm:
case Opcode::MulRR:
case Opcode::DivRR:
case Opcode::ModRR:
case Opcode::AndRR:
case Opcode::OrRR:
case Opcode::XorRR:
case Opcode::ShlRR:
case Opcode::ShrRR:
case Opcode::AsrRR:
case Opcode::Asr64RR:
case Opcode::Uxtw:
case Opcode::Sxtw:
case Opcode::CSet:
case Opcode::Csel:
case Opcode::Smull:
case Opcode::Msub:
case Opcode::NegRR:
case Opcode::FAddRR:
case Opcode::FSubRR:
case Opcode::FMulRR:
case Opcode::FDivRR:
case Opcode::Scvtf:
case Opcode::FCvtzs:
case Opcode::FMovWS:
case Opcode::MovReg:
return true;
default:
return false;
}
}
/// Extract def VReg (operands[0] if VReg) and use VRegs from one instruction.
static void ExtractDefUse(const MachineInstr &inst, int &def_vreg,
std::vector<int> &use_vregs)
{
def_vreg = -1;
use_vregs.clear();
const auto &ops = inst.GetOperands();
const auto opcode = inst.GetOpcode();
if (HasVRegDef(opcode) && !ops.empty() &&
ops[0].GetKind() == Operand::Kind::VReg)
{
def_vreg = ops[0].GetVRegId();
}
// All other VReg operands are uses
for (size_t i = 0; i < ops.size(); ++i)
{
// For def-producing instructions, operands[0] is the def (already handled)
if (HasVRegDef(opcode) && i == 0)
continue;
if (ops[i].GetKind() == Operand::Kind::VReg)
use_vregs.push_back(ops[i].GetVRegId());
}
}
} // anonymous namespace
} // namespace mir
namespace mir
{
// ---- Block-level dataflow structures --------------------------------
struct BlockLiveInfo
{
std::unordered_set<int> def;
std::unordered_set<int> use;
std::unordered_set<int> live_in;
std::unordered_set<int> live_out;
std::vector<int> successors; // block indices
std::vector<int> predecessors; // block indices
};
std::vector<LiveInterval> ComputeInstLiveness(MachineFunction &func)
{
auto &blocks = func.GetBlocks();
const int num_blocks = static_cast<int>(blocks.size());
// ================================================================
// Phase 1: Block-level backward liveness (fixpoint iteration)
// ================================================================
// 1a. Build label → block-index mapping
std::unordered_map<int, int> label_to_idx;
for (int i = 0; i < num_blocks; ++i)
{
if (!blocks[i])
continue;
label_to_idx[blocks[i]->GetLabelId()] = i;
}
// 1b. Compute per-block def/use + successors
std::vector<BlockLiveInfo> blk_info(num_blocks);
for (int i = 0; i < num_blocks; ++i)
{
if (!blocks[i])
continue;
auto &info = blk_info[i];
auto &insts = blocks[i]->GetInstructions();
for (const auto &inst : insts)
{
int def_vreg;
std::vector<int> use_vregs;
ExtractDefUse(inst, def_vreg, use_vregs);
// All uses are added first, then def is added. This avoids
// counting "def first, then use" in the same block incorrectly.
for (int u : use_vregs)
{
if (info.def.count(u) == 0)
info.use.insert(u);
}
if (def_vreg >= 0)
{
// A vreg used before being defined in this block stays in use set
if (info.use.count(def_vreg) == 0)
info.def.insert(def_vreg);
}
}
// ---- Determine successors ----
bool has_br = false;
bool has_condbr = false;
int br_target_label = -1;
int condbr_target_label = -1;
bool has_ret = false;
for (const auto &inst : insts)
{
const auto opcode = inst.GetOpcode();
const auto &ops = inst.GetOperands();
if (opcode == Opcode::Br && !ops.empty() &&
ops[0].GetKind() == Operand::Kind::Label)
{
has_br = true;
br_target_label = ops[0].GetLabel();
}
else if (opcode == Opcode::CondBr && ops.size() >= 2 &&
ops[1].GetKind() == Operand::Kind::Label)
{
has_condbr = true;
condbr_target_label = ops[1].GetLabel();
}
else if (opcode == Opcode::Ret)
{
has_ret = true;
}
}
auto add_succ = [&](int label)
{
auto it = label_to_idx.find(label);
if (it != label_to_idx.end())
info.successors.push_back(it->second);
};
if (has_ret)
{
// No successors — function exit
}
else if (has_br)
{
// Unconditional branch: target covers the only outgoing path.
add_succ(br_target_label);
// If there's also a CondBr, its target is taken when condition is
// true — the Br covers the false path.
if (has_condbr)
add_succ(condbr_target_label);
}
else if (has_condbr)
{
// Conditional branch without Br: true path = target, false path =
// falls through to next block in insertion order.
add_succ(condbr_target_label);
if (i + 1 < num_blocks)
info.successors.push_back(i + 1);
}
else
{
// Ordinary block — falls through to next block.
if (i + 1 < num_blocks)
info.successors.push_back(i + 1);
}
}
// 1c. Build predecessor lists
for (int i = 0; i < num_blocks; ++i)
{
for (int s : blk_info[i].successors)
{
if (s >= 0 && s < num_blocks)
blk_info[s].predecessors.push_back(i);
}
}
// 1d. Worklist fixpoint
// Initialise live_in with use sets
for (int i = 0; i < num_blocks; ++i)
{
blk_info[i].live_in = blk_info[i].use;
}
std::queue<int> worklist;
std::vector<bool> in_queue(num_blocks, false);
for (int i = 0; i < num_blocks; ++i)
{
if (blocks[i])
{
worklist.push(i);
in_queue[i] = true;
}
}
while (!worklist.empty())
{
int b = worklist.front();
worklist.pop();
in_queue[b] = false;
// Compute new live_out = union of successors' live_in
std::unordered_set<int> new_live_out;
for (int s : blk_info[b].successors)
{
if (s < 0 || s >= num_blocks)
continue;
for (int v : blk_info[s].live_in)
new_live_out.insert(v);
}
// Compute new live_in = use (live_out - def)
std::unordered_set<int> new_live_in = blk_info[b].use;
for (int v : new_live_out)
{
if (blk_info[b].def.count(v) == 0)
new_live_in.insert(v);
}
if (new_live_in != blk_info[b].live_in)
{
blk_info[b].live_out = std::move(new_live_out);
blk_info[b].live_in = std::move(new_live_in);
// Enqueue all predecessors (their live_out depends on us)
for (int p : blk_info[b].predecessors)
{
if (!in_queue[p])
{
in_queue[p] = true;
worklist.push(p);
}
}
}
}
// ================================================================
// Phase 2: Instruction-level interval computation (reverse scan)
// ================================================================
std::unordered_map<int, int> vreg_start;
std::unordered_map<int, int> vreg_end;
// Assign global instruction positions
int global_pos = 0;
// Map global_pos → (block_idx, local_instr_idx) for the reverse scan
struct PosInfo
{
int block_idx;
int instr_count; // number of instructions in this block
};
std::vector<PosInfo> pos_to_block;
for (int i = 0; i < num_blocks; ++i)
{
if (!blocks[i])
continue;
int count = static_cast<int>(blocks[i]->GetInstructions().size());
for (int j = 0; j < count; ++j)
pos_to_block.push_back({i, count});
global_pos += count;
}
const int total_instrs = global_pos;
// Reverse scan: process blocks in reverse order
for (int bi = num_blocks - 1; bi >= 0; --bi)
{
if (!blocks[bi])
continue;
auto &insts = blocks[bi]->GetInstructions();
const int num_instrs = static_cast<int>(insts.size());
if (num_instrs == 0)
continue;
// Compute the starting global position of the first instruction in
// this block
int block_start_pos = 0;
for (int pi = 0; pi < bi; ++pi)
{
if (blocks[pi])
block_start_pos += static_cast<int>(blocks[pi]->GetInstructions().size());
}
// Start with live_out of this block
std::unordered_set<int> live = blk_info[bi].live_out;
// Process instructions from last to first.
// Correct backward order: uses first (add to live), then record
// (interval extends to this position), then defs (remove from live).
// This ensures that a vreg used at this position IS recorded as
// live here, even if it was not previously in the live set.
for (int j = num_instrs - 1; j >= 0; --j)
{
int pos = block_start_pos + j;
const auto &inst = insts[j];
int def_vreg;
std::vector<int> use_vregs;
ExtractDefUse(inst, def_vreg, use_vregs);
// Uses: going backward, uses make the vreg live before this
// instruction.
for (int u : use_vregs)
live.insert(u);
// Record: all vregs currently live extend their interval to this
// position.
for (int v : live)
{
auto sit = vreg_start.find(v);
if (sit == vreg_start.end() || pos < sit->second)
vreg_start[v] = pos;
auto eit = vreg_end.find(v);
if (eit == vreg_end.end() || pos > eit->second)
vreg_end[v] = pos;
}
// Def: going backward, the def is the beginning of the live range
// — remove from live so that earlier positions don't see it
// (unless a later use re-adds it for the prior value).
if (def_vreg >= 0)
live.erase(def_vreg);
}
// After processing all instructions, live should equal live_in.
// Any vreg still in live for the entry block (block 0) is live-in
// at function entry → set start = 0.
if (bi == 0)
{
for (int v : live)
{
vreg_start[v] = 0;
// Also ensure end is at least 0
auto eit = vreg_end.find(v);
if (eit == vreg_end.end() || 0 > eit->second)
vreg_end[v] = 0;
}
}
}
// ================================================================
// Phase 3: Build LiveInterval objects
// ================================================================
const int num_vregs = func.GetNumVRegs();
std::vector<LiveInterval> intervals;
for (int v = 0; v < num_vregs; ++v)
{
auto sit = vreg_start.find(v);
auto eit = vreg_end.find(v);
int start = (sit != vreg_start.end()) ? sit->second : 0;
int end = (eit != vreg_end.end()) ? eit->second : 0;
// Filter out unused vregs
if (start > end)
continue;
LiveInterval li;
li.vreg = v;
li.start = start;
li.end = end;
li.vreg_class = func.GetVRegClass(v);
intervals.push_back(li);
}
return intervals;
}
} // namespace mir
Loading…
Cancel
Save