diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b0fb80..333edea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,7 +28,7 @@ add_executable(sysyc DCE.cpp AddressCalculationExpansion.cpp Mem2Reg.cpp - # Reg2Mem.cpp + Reg2Mem.cpp RISCv64Backend.cpp RISCv64ISel.cpp RISCv64RegAlloc.cpp diff --git a/src/Mem2Reg.cpp b/src/Mem2Reg.cpp index a5a3d72..251b8ef 100644 --- a/src/Mem2Reg.cpp +++ b/src/Mem2Reg.cpp @@ -55,7 +55,8 @@ void Mem2RegContext::run(Function *func, AnalysisManager *AM) { // 为每个可提升的 alloca 初始化其值栈 for (auto alloca : promotableAllocas) { // 初始值通常是 undef 或 null,取决于 IR 类型系统 - allocaToValueStackMap[alloca].push(nullptr); // 压入一个初始的“未定义”值 + UndefinedValue *undefValue = UndefinedValue::get(alloca->getType()->as()->getBaseType()); + allocaToValueStackMap[alloca].push(undefValue); // 压入一个初始的“未定义”值 } // 从入口基本块开始,对支配树进行 DFS 遍历,进行变量重命名 diff --git a/src/Pass.cpp b/src/Pass.cpp index 6be44c5..4491b8d 100644 --- a/src/Pass.cpp +++ b/src/Pass.cpp @@ -4,6 +4,7 @@ #include "SysYIRPrinter.h" #include "DCE.h" #include "Mem2Reg.h" +#include "Reg2Mem.h" #include "Pass.h" #include #include @@ -67,6 +68,10 @@ void PassManager::runOptimizationPipeline(Module* moduleIR, IRBuilder* builderIR this->addPass(&Mem2Reg::ID); this->run(); + this->clearPasses(); + this->addPass(&Reg2Mem::ID); + this->run(); + if (DEBUG) std::cout << "--- Custom optimization sequence finished ---\n"; } diff --git a/src/Reg2Mem.cpp b/src/Reg2Mem.cpp new file mode 100644 index 0000000..8b4a291 --- /dev/null +++ b/src/Reg2Mem.cpp @@ -0,0 +1,266 @@ +#include "Reg2Mem.h" +#include "SysYIROptUtils.h" // 如果有的话 + +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 (dynamic_cast(val) || dynamic_cast(val)) { + // 如果值已经是指针类型,则通常不为其分配额外的内存,因为它已经是一个地址。 + // (除非我们想将其值也存储起来,这通常不用于 Reg2Mem) + // Reg2Mem 关注的是将非指针值从寄存器语义转换为内存语义。 + if (val->getType()->isPointer()) { + return false; + } + 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(arg->getType(), {}, arg->getName() + ".reg2mem"); + // 将参数值 store 到 alloca 中 (这是 Mem2Reg 逆转的关键一步) + builder->createStoreInst(arg, alloca); + 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()) { + // 只有有结果的指令才可能需要分配内存 + // (例如 BinaryInst, CallInst, LoadInst, PhiInst 等) + // StoreInst, BranchInst, ReturnInst 等没有结果的指令不需要 + if (inst.get()->getType()->isVoid()) { // 没有返回值的指令 + continue; + } + + if (isPromotableToMemory(inst.get())) { + // 为指令的结果分配内存 + // AllocaInst 应该在入口块,而不是当前指令所在块 + // 这里我们只是创建,并稍后调整其位置 + // 通常的做法是在循环结束后统一将 alloca 放到 entryBlock 的顶部 + AllocaInst *alloca = builder->createAllocaInst(inst.get()->getType(), {}, inst->getName() + ".reg2mem"); + valueToAllocaMap[inst.get()] = alloca; + } + } + } + + // 统一将所有新创建的 AllocaInst 移到入口块的最前面, + // 在参数的 AllocaInst 和 Store 指令之后。 + // 这部分可能需要根据你的 IR 结构来调整。 + // 假设 builder.createAllocaInst 默认在当前 builder position 插入。 + // 如果 builder 只能在当前位置插入,可能需要收集这些 alloca,然后在最后手动移动。 + // 更简单的方式是:在 builder 创建 alloca 时,将 position 设置到 entryBlock 的开头。 + builder->setPosition(entryBlock, entryBlock->begin()); + // 再次循环 valueToAllocaMap,如果 alloca 是新创建的,确保它在正确位置 + // 对于已经创建的 alloca,这里不需要重新创建 + for (auto const &[val, alloca_inst] : valueToAllocaMap) { + // 假设 alloca_inst 还没有被插入或者需要移动到开头 + // 你的 builder 可能需要一个方法来将指令移动到特定位置 + // 如果 builder.createAllocaInst 总是插入到当前位置,那么需要在循环之前设置好 builder 的位置 + } + // 再次设置 builder 位置到入口块的末尾,以便后续插入 Store 等指令 + // 由于消除了fallthrough所有入口块的末尾一定是一个 terminator + // 所以可以安全地将位置设置到末尾的terminator。 + // 这将确保后续的 Store 指令插入到入口块的末尾。 + 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); // 清理 use-def 链 + phi->getParent()->removeInst(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 \ No newline at end of file diff --git a/src/include/IR.h b/src/include/IR.h index 0b95e41..6d21f04 100644 --- a/src/include/IR.h +++ b/src/include/IR.h @@ -525,6 +525,10 @@ public: iterator begin() { return instructions.begin(); } iterator end() { return instructions.end(); } iterator terminator() { return std::prev(end()); } + iterator findInstIterator(Instruction *inst) { + return std::find_if(instructions.begin(), instructions.end(), + [inst](const std::unique_ptr &i) { return i.get() == inst; }); + } ///< 查找指定指令的迭代器 bool hasSuccessor(BasicBlock *block) const { return std::find(successors.begin(), successors.end(), block) != successors.end(); } ///< 判断是否有后继块 diff --git a/src/include/Reg2Mem.h b/src/include/Reg2Mem.h new file mode 100644 index 0000000..5bd3b26 --- /dev/null +++ b/src/include/Reg2Mem.h @@ -0,0 +1,59 @@ +#pragma once + +#include "IR.h" +#include "IRBuilder.h" // 你的 IR Builder +#include "Liveness.h" +#include "Dom.h" +#include "Pass.h" // 你的 Pass 框架基类 +#include // 调试用 +#include // 用于 Value 到 AllocaInst 的映射 +#include // 可能用于其他辅助集合 +#include +#include + +namespace sysy { + +class Reg2MemContext { +public: + Reg2MemContext(IRBuilder *b) : builder(b) {} + + // 运行 Reg2Mem 优化 + void run(Function *func); + +private: + IRBuilder *builder; // IR 构建器 + + // 存储 SSA Value 到对应的 AllocaInst 的映射 + // 只有那些需要被"溢出"到内存的 SSA 值才会被记录在这里 + std::map valueToAllocaMap; + + // 辅助函数: + // 1. 识别并为 SSA Value 分配 AllocaInst + void allocateMemoryForSSAValues(Function *func); + + // 2. 将 SSA 值的使用替换为 Load/Store + void insertLoadsAndStores(Function *func); + + // 3. 处理 Phi 指令,将其转换为 Load/Store + void rewritePhis(Function *func); + + // 4. 清理 (例如,可能删除不再需要的 Phi 指令) + void cleanup(Function *func); + + // 判断一个 Value 是否是 AllocaInst 可以为其分配内存的目标 + // 通常指非指针类型的Instruction结果和Argument + bool isPromotableToMemory(Value *val); +}; + +class Reg2Mem : public OptimizationPass { +private: + IRBuilder *builder; ///< IR构建器,用于插入指令 +public: + static void *ID; ///< Pass的唯一标识符 + Reg2Mem() : OptimizationPass("Reg2Mem", Pass::Granularity::Function), builder(builder) {} + bool runOnFunction(Function *F, AnalysisManager &AM) override; + void getAnalysisUsage(std::set &analysisDependencies, std::set &analysisInvalidations) const override; + void *getPassID() const override { return &ID; } ///< 获取 Pass ID +}; + +} // namespace sysy \ No newline at end of file