#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(val) || dynamic_cast(val)) { // // 如果值已经是指针类型,则通常不为其分配额外的内存,因为它已经是一个地址。 // // (除非我们想将其值也存储起来,这通常不用于 Reg2Mem) // // // Reg2Mem 关注的是将非指针值从寄存器语义转换为内存语义。 // if (val->getType()->isPointer()) { // return false; // } // return true; // } // 1. 如果是 Argument,则可以提升到内存 if (dynamic_cast(val)) { // 参数类型(i32, i32* 等)都可以为其分配内存 // 因为它们在 Mem2Reg 逆操作中,被认为是从寄存器分配到内存 return true; } if (dynamic_cast(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(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(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 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(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->getValue(i); // 获取入值 BasicBlock *incomingBlock = phiInst->getBlock(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> usesToReplace; std::vector 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(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 &analysisDependencies, std::set &analysisInvalidations) const { // Reg2Mem 通常不需要特定的分析作为依赖,因为它主要是一个转换。 // 但它会使许多分析失效。 analysisInvalidations.insert(&LivenessAnalysisPass::ID); // 例如 analysisInvalidations.insert(&DominatorTreeAnalysisPass::ID); } } // namespace sysy