288 lines
12 KiB
C++
288 lines
12 KiB
C++
#include "Reg2Mem.h"
|
||
#include "SysYIROptUtils.h"
|
||
#include "SysYIRPrinter.h"
|
||
|
||
extern int DEBUG; // 全局调试标志
|
||
|
||
namespace sysy {
|
||
|
||
void *Reg2Mem::ID = (void *)&Reg2Mem::ID;
|
||
|
||
void Reg2MemContext::run(Function *func) {
|
||
if (func->getBasicBlocks().empty()) {
|
||
return;
|
||
}
|
||
|
||
// 清空状态,确保每次运行都是新的
|
||
valueToAllocaMap.clear();
|
||
|
||
// 阶段1: 识别并为 SSA Value 分配 AllocaInst
|
||
allocateMemoryForSSAValues(func);
|
||
|
||
// 阶段2: 将 Phi 指令转换为 Load/Store 逻辑 (此阶段需要先于通用 Load/Store 插入)
|
||
// 这样做是因为 Phi 指令的特殊性,它需要在前驱块的末尾插入 Store
|
||
// 如果先处理通用 Load/Store,可能无法正确处理 Phi 的复杂性
|
||
rewritePhis(func); // Phi 指令可能在 rewritePhis 中被删除或标记删除
|
||
|
||
// 阶段3: 将其他 SSA Value 的使用替换为 Load/Store
|
||
insertLoadsAndStores(func);
|
||
|
||
// 阶段4: 清理(删除不再需要的 Phi 指令)
|
||
cleanup(func);
|
||
}
|
||
|
||
bool Reg2MemContext::isPromotableToMemory(Value *val) {
|
||
// 参数和指令结果是 SSA 值
|
||
if(DEBUG){
|
||
// if(val->getName() == ""){
|
||
// assert(false && "Value name should not be empty in Reg2MemContext::isPromotableToMemory");
|
||
// }
|
||
// std::cout << "Checking if value is promotable to memory: " << val->getName() << std::endl;
|
||
}
|
||
// if (dynamic_cast<Argument *>(val) || dynamic_cast<Instruction *>(val)) {
|
||
// // 如果值已经是指针类型,则通常不为其分配额外的内存,因为它已经是一个地址。
|
||
// // (除非我们想将其值也存储起来,这通常不用于 Reg2Mem)
|
||
// // // Reg2Mem 关注的是将非指针值从寄存器语义转换为内存语义。
|
||
// if (val->getType()->isPointer()) {
|
||
// return false;
|
||
// }
|
||
// return true;
|
||
// }
|
||
// 1. 如果是 Argument,则可以提升到内存
|
||
if (dynamic_cast<Argument *>(val)) {
|
||
// 参数类型(i32, i32* 等)都可以为其分配内存
|
||
// 因为它们在 Mem2Reg 逆操作中,被认为是从寄存器分配到内存
|
||
return true;
|
||
}
|
||
if (dynamic_cast<PhiInst *>(val)) {
|
||
// Phi 指令的结果也是一个 SSA 值,需要将其转换为 Load/Store
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void Reg2MemContext::allocateMemoryForSSAValues(Function *func) {
|
||
// AllocaInst 必须在函数的入口基本块中
|
||
BasicBlock *entryBlock = func->getEntryBlock();
|
||
if (!entryBlock) {
|
||
return; // 函数可能没有入口块 (例如声明)
|
||
}
|
||
|
||
// 1. 为函数参数分配内存
|
||
builder->setPosition(entryBlock, entryBlock->begin()); // 确保在入口块的开始位置插入
|
||
for (auto arg : func->getArguments()) {
|
||
// 默认情况下,将所有参数是提升到内存
|
||
if (isPromotableToMemory(arg)) {
|
||
// 参数的类型就是 AllocaInst 需要分配的类型
|
||
AllocaInst *alloca = builder->createAllocaInst(Type::getPointerType(arg->getType()), arg->getName() + ".reg2mem");
|
||
// 将参数值 store 到 alloca 中 (这是 Mem2Reg 逆转的关键一步)
|
||
valueToAllocaMap[arg] = alloca;
|
||
|
||
// 确保 alloca 位于入口块的顶部,但在所有参数的 store 指令之前
|
||
// 通常 alloca 都在 entry block 的最开始
|
||
// 这里我们只是创建,并让 builder 决定插入位置 (通常在当前插入点)
|
||
// 如果需要严格控制顺序,可能需要手动 insert 到 instruction list
|
||
}
|
||
}
|
||
|
||
// 2. 为指令结果分配内存
|
||
// 遍历所有基本块和指令,找出所有需要分配 Alloca 的指令结果
|
||
for (auto &bb : func->getBasicBlocks()) {
|
||
for (auto &inst : bb->getInstructions_Range()) {
|
||
// SysYPrinter::printInst(inst.get());
|
||
// 只有有结果的指令才可能需要分配内存
|
||
// (例如 BinaryInst, CallInst, LoadInst, PhiInst 等)
|
||
// StoreInst, BranchInst, ReturnInst 等没有结果的指令不需要
|
||
|
||
if (dynamic_cast<AllocaInst*>(inst.get()) || inst.get()->getType()->isVoid()) {
|
||
continue;
|
||
}
|
||
|
||
if (isPromotableToMemory(inst.get())) {
|
||
// 为指令的结果分配内存
|
||
// AllocaInst 应该在入口块,而不是当前指令所在块
|
||
// 这里我们只是创建,并稍后调整其位置
|
||
// 通常的做法是在循环结束后统一将 alloca 放到 entryBlock 的顶部
|
||
AllocaInst *alloca = builder->createAllocaInst(Type::getPointerType(inst.get()->getType()), inst.get()->getName() + ".reg2mem");
|
||
valueToAllocaMap[inst.get()] = alloca;
|
||
}
|
||
}
|
||
}
|
||
Instruction *firstNonAlloca = nullptr;
|
||
for (auto instIter = entryBlock->getInstructions().begin(); instIter != entryBlock->getInstructions().end(); instIter++) {
|
||
if (!dynamic_cast<AllocaInst*>(instIter->get())) {
|
||
firstNonAlloca = instIter->get();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (firstNonAlloca) {
|
||
builder->setPosition(entryBlock, entryBlock->findInstIterator(firstNonAlloca));
|
||
} else { // 如果 entryBlock 只有 AllocaInst 或为空,则设置到 terminator 前
|
||
builder->setPosition(entryBlock, entryBlock->terminator());
|
||
}
|
||
|
||
// 插入所有参数的初始 Store 指令
|
||
for (auto arg : func->getArguments()) {
|
||
if (valueToAllocaMap.count(arg)) { // 检查是否为其分配了 alloca
|
||
builder->createStoreInst(arg, valueToAllocaMap[arg]);
|
||
}
|
||
}
|
||
|
||
builder->setPosition(entryBlock, entryBlock->terminator());
|
||
}
|
||
|
||
void Reg2MemContext::rewritePhis(Function *func) {
|
||
std::vector<PhiInst *> phisToErase; // 收集要删除的 Phi
|
||
|
||
// 遍历所有基本块和其中的指令,查找 Phi 指令
|
||
for (auto &bb : func->getBasicBlocks()) {
|
||
// auto insts = bb->getInstructions(); // 复制一份,因为要修改
|
||
for (auto instIter = bb->getInstructions().begin(); instIter != bb->getInstructions().end(); instIter++) {
|
||
Instruction *inst = instIter->get();
|
||
if (auto phiInst = dynamic_cast<PhiInst *>(inst)) {
|
||
// 检查 Phi 指令是否是需要处理的 SSA 值
|
||
if (valueToAllocaMap.count(phiInst)) {
|
||
AllocaInst *alloca = valueToAllocaMap[phiInst];
|
||
|
||
// 1. 为 Phi 指令的每个入边,在前驱块的末尾插入 Store 指令
|
||
// PhiInst 假设有 getIncomingValues() 和 getIncomingBlocks()
|
||
for (unsigned i = 0; i < phiInst->getNumIncomingValues(); ++i) { // 假设 PhiInst 是通过操作数来管理入边的
|
||
Value *incomingValue = phiInst->getIncomingValue(i); // 获取入值
|
||
BasicBlock *incomingBlock = phiInst->getIncomingBlock(i); // 获取对应的入块
|
||
|
||
// 在入块的跳转指令之前插入 StoreInst
|
||
// 需要找到 incomingBlock 的终结指令 (Terminator Instruction)
|
||
// 并将 StoreInst 插入到它前面
|
||
if (incomingBlock->terminator()->get()->isTerminator()) {
|
||
builder->setPosition(incomingBlock, incomingBlock->terminator());
|
||
} else {
|
||
// 如果没有终结指令,插入到末尾
|
||
builder->setPosition(incomingBlock, incomingBlock->end());
|
||
}
|
||
builder->createStoreInst(incomingValue, alloca);
|
||
}
|
||
|
||
// 2. 在当前 Phi 所在基本块的开头,插入 Load 指令
|
||
// 将 Load 指令插入到 Phi 指令之后,因为 Phi 指令即将被删除
|
||
builder->setPosition(bb.get(), bb.get()->findInstIterator(phiInst));
|
||
LoadInst *newLoad = builder->createLoadInst(alloca);
|
||
|
||
// 3. 将 Phi 指令的所有用途替换为新的 Load 指令
|
||
phiInst->replaceAllUsesWith(newLoad);
|
||
|
||
// 标记 Phi 指令待删除
|
||
phisToErase.push_back(phiInst);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 实际删除 Phi 指令
|
||
for (auto phi : phisToErase) {
|
||
if (phi && phi->getParent()) {
|
||
SysYIROptUtils::usedelete(phi);
|
||
}
|
||
}
|
||
}
|
||
|
||
void Reg2MemContext::insertLoadsAndStores(Function *func) {
|
||
// 收集所有需要替换的 uses,避免在迭代时修改 use 链表
|
||
std::vector<std::pair<Use *, LoadInst *>> usesToReplace;
|
||
std::vector<Instruction *> instsToStore; // 收集需要插入 Store 的指令
|
||
|
||
// 遍历所有基本块和指令
|
||
for (auto &bb : func->getBasicBlocks()) {
|
||
for (auto instIter = bb->getInstructions().begin(); instIter != bb->getInstructions().end(); instIter++) {
|
||
Instruction *inst = instIter->get();
|
||
|
||
// 如果指令有结果且我们为其分配了 alloca (Phi 已在 rewritePhis 处理)
|
||
// 并且其类型不是 void
|
||
if (!inst->getType()->isVoid() && valueToAllocaMap.count(inst)) {
|
||
// 在指令之后插入 Store 指令
|
||
// StoreInst 应该插入到当前指令之后
|
||
builder->setPosition(bb.get(), bb.get()->findInstIterator(inst));
|
||
builder->createStoreInst(inst, valueToAllocaMap[inst]);
|
||
}
|
||
|
||
// 处理指令的操作数:如果操作数是一个 SSA 值,且为其分配了 alloca
|
||
// (并且这个操作数不是 Phi Inst 的 incoming value,因为 Phi 的 incoming value 已经在 rewritePhis 中处理了)
|
||
// 注意:Phi Inst 的操作数是特殊的,它们表示来自不同前驱块的值。
|
||
// 这里的处理主要是针对非 Phi 指令的操作数。
|
||
for (auto use = inst->getUses().begin(); use != inst->getUses().end(); ++use) {
|
||
// 如果当前 use 的 Value 是一个 Instruction 或 Argument
|
||
Value *operand = use->get()->getValue();
|
||
if (isPromotableToMemory(operand) && valueToAllocaMap.count(operand)) {
|
||
// 确保这个 operand 不是一个即将被删除的 Phi 指令
|
||
// (在 rewritePhis 阶段,Phi 已经被处理并可能被标记删除)
|
||
// 或者检查 use 的 user 不是 PhiInst
|
||
if (dynamic_cast<PhiInst *>(inst)) {
|
||
continue; // Phi 的操作数已在 rewritePhis 中处理
|
||
}
|
||
|
||
AllocaInst *alloca = valueToAllocaMap[operand];
|
||
|
||
// 在使用点之前插入 Load 指令
|
||
// LoadInst 应该插入到使用它的指令之前
|
||
builder->setPosition(bb.get(), bb.get()->findInstIterator(inst));
|
||
LoadInst *newLoad = builder->createLoadInst(alloca);
|
||
|
||
// 记录要替换的 use
|
||
usesToReplace.push_back({use->get(), newLoad});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 执行所有替换操作
|
||
for (auto &pair : usesToReplace) {
|
||
pair.first->setValue(pair.second); // 替换 use 的 Value
|
||
}
|
||
}
|
||
|
||
void Reg2MemContext::cleanup(Function *func) {
|
||
// 此时,所有原始的 Phi 指令应该已经被删除。
|
||
// 如果有其他需要删除的临时指令,可以在这里处理。
|
||
// 通常,Reg2Mem 的清理比 Mem2Reg 简单,因为主要是在插入指令。
|
||
// 这里可以作为一个占位符,以防未来有其他清理需求。
|
||
}
|
||
|
||
bool Reg2Mem::runOnFunction(Function *F, AnalysisManager &AM) {
|
||
// 记录初始指令数量
|
||
size_t initial_inst_count = 0;
|
||
for (auto &bb : F->getBasicBlocks()) {
|
||
initial_inst_count += bb->getInstructions().size();
|
||
}
|
||
|
||
Reg2MemContext ctx(builder); // 假设 builder 是一个全局或可访问的 IRBuilder 实例
|
||
ctx.run(F);
|
||
|
||
// 记录最终指令数量
|
||
size_t final_inst_count = 0;
|
||
for (auto &bb : F->getBasicBlocks()) {
|
||
final_inst_count += bb->getInstructions().size();
|
||
}
|
||
// TODO: 添加更精确的变化检测逻辑,例如在run函数中维护changed状态
|
||
bool changed = (initial_inst_count != final_inst_count); // 粗略判断是否改变
|
||
|
||
if (changed) {
|
||
// Reg2Mem 会显著改变 IR 结构,特别是数据流。
|
||
// 它会插入大量的 Load/Store 指令,改变 Value 的来源。
|
||
// 这会使几乎所有数据流分析失效。
|
||
// 例如:
|
||
// AM.invalidateAnalysis(&DominatorTreeAnalysisPass::ID, F); // 如果基本块结构改变,可能失效
|
||
// AM.invalidateAnalysis(&LivenessAnalysisPass::ID, F); // 活跃性分析肯定失效
|
||
// AM.invalidateAnalysis(&DCEPass::ID, F); // 可能产生新的死代码
|
||
// ... 其他所有数据流分析
|
||
}
|
||
return changed;
|
||
}
|
||
|
||
void Reg2Mem::getAnalysisUsage(std::set<void *> &analysisDependencies, std::set<void *> &analysisInvalidations) const {
|
||
// Reg2Mem 通常不需要特定的分析作为依赖,因为它主要是一个转换。
|
||
// 但它会使许多分析失效。
|
||
analysisInvalidations.insert(&LivenessAnalysisPass::ID); // 例如
|
||
analysisInvalidations.insert(&DominatorTreeAnalysisPass::ID);
|
||
}
|
||
|
||
} // namespace sysy
|