Files
mysysy/src/midend/Pass/Optimize/Reg2Mem.cpp
2025-08-03 22:18:00 +08:00

288 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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