From 634a84f29c091a01eceb548aa73e765eb527eca8 Mon Sep 17 00:00:00 2001 From: CGH0S7 <776459475@qq.com> Date: Sun, 27 Jul 2025 11:03:26 +0800 Subject: [PATCH] =?UTF-8?q?[peephole]Pass=E6=9E=B6=E6=9E=84=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 6 +- src/CalleeSavedHandler.cpp | 109 +++++++++++++++ src/PostRA_Scheduler.cpp | 36 +++++ src/PreRA_Scheduler.cpp | 36 +++++ ...{RISCv64Passes.cpp => RISCv64Peephole.cpp} | 131 +----------------- src/include/CalleeSavedHandler.h | 33 +++++ src/include/PostRA_Scheduler.h | 30 ++++ src/include/PreRA_Scheduler.h | 30 ++++ src/include/RISCv64Passes.h | 70 +--------- src/include/RISCv64Peephole.h | 30 ++++ 10 files changed, 319 insertions(+), 192 deletions(-) create mode 100644 src/CalleeSavedHandler.cpp create mode 100644 src/PostRA_Scheduler.cpp create mode 100644 src/PreRA_Scheduler.cpp rename src/{RISCv64Passes.cpp => RISCv64Peephole.cpp} (81%) create mode 100644 src/include/CalleeSavedHandler.h create mode 100644 src/include/PostRA_Scheduler.h create mode 100644 src/include/PreRA_Scheduler.h create mode 100644 src/include/RISCv64Peephole.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 01f54d8..0a905f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,7 +33,11 @@ add_executable(sysyc RISCv64ISel.cpp RISCv64RegAlloc.cpp RISCv64AsmPrinter.cpp - RISCv64Passes.cpp + # RISCv64Passes.cpp + RISCv64Peephole.cpp + PreRA_Scheduler.cpp + PostRA_Scheduler.cpp + CalleeSavedHandler.cpp ) # 设置 include 路径,包含 ANTLR 运行时库和项目头文件 diff --git a/src/CalleeSavedHandler.cpp b/src/CalleeSavedHandler.cpp new file mode 100644 index 0000000..d33ae53 --- /dev/null +++ b/src/CalleeSavedHandler.cpp @@ -0,0 +1,109 @@ +#include "CalleeSavedHandler.h" +#include +#include + +namespace sysy { + +char CalleeSavedHandler::ID = 0; + +bool CalleeSavedHandler::runOnFunction(Function *F, AnalysisManager& AM) { + // This pass works on MachineFunction level, not IR level + return false; +} + +void CalleeSavedHandler::runOnMachineFunction(MachineFunction* mfunc) { + StackFrameInfo& frame_info = mfunc->getFrameInfo(); + std::set used_callee_saved; + + // 1. 扫描所有指令,找出被使用的s寄存器 + for (auto& mbb : mfunc->getBlocks()) { + for (auto& instr : mbb->getInstructions()) { + for (auto& op : instr->getOperands()) { + + // 辅助Lambda,用于检查和插入寄存器 + auto check_and_insert_reg = [&](RegOperand* reg_op) { + if (!reg_op->isVirtual()) { + PhysicalReg preg = reg_op->getPReg(); + // --- 关键检查点 --- + // 必须严格判断是否在 s0-s11 的范围内。 + // a0, t0 等寄存器绝对不应被视为被调用者保存寄存器。 + if (preg >= PhysicalReg::S0 && preg <= PhysicalReg::S11) { + used_callee_saved.insert(preg); + } + } + }; + + if (op->getKind() == MachineOperand::KIND_REG) { + check_and_insert_reg(static_cast(op.get())); + } else if (op->getKind() == MachineOperand::KIND_MEM) { + check_and_insert_reg(static_cast(op.get())->getBase()); + } + } + } + } + + // 如果没有使用s寄存器(除了可能作为帧指针的s0),则无需操作 + if (used_callee_saved.empty() || (used_callee_saved.size() == 1 && used_callee_saved.count(PhysicalReg::S0))) { + return; + } + + // 将结果存入StackFrameInfo,供后续使用 + frame_info.used_callee_saved_regs = used_callee_saved; + + // 2. 在函数序言插入保存指令 + MachineBasicBlock* entry_block = mfunc->getBlocks().front().get(); + auto& entry_instrs = entry_block->getInstructions(); + auto prologue_end = entry_instrs.begin(); + + // 找到序言结束的位置(通常是addi s0, sp, size之后) + for (auto it = entry_instrs.begin(); it != entry_instrs.end(); ++it) { + if ((*it)->getOpcode() == RVOpcodes::ADDI && + (*it)->getOperands()[0]->getKind() == MachineOperand::KIND_REG && + static_cast((*it)->getOperands()[0].get())->getPReg() == PhysicalReg::S0) + { + prologue_end = std::next(it); + break; + } + } + + // 为了栈帧布局确定性,对寄存器进行排序 + std::vector sorted_regs(used_callee_saved.begin(), used_callee_saved.end()); + std::sort(sorted_regs.begin(), sorted_regs.end()); + + int current_offset = -16; // ra和s0已经占用了-8和-16的位置 + for (PhysicalReg reg : sorted_regs) { + if (reg == PhysicalReg::S0) continue; // s0已经在序言中处理 + current_offset -= 8; + auto sd = std::make_unique(RVOpcodes::SD); + sd->addOperand(std::make_unique(reg)); + sd->addOperand(std::make_unique( + std::make_unique(PhysicalReg::S0), // 假设s0是帧指针 + std::make_unique(current_offset) + )); + entry_instrs.insert(prologue_end, std::move(sd)); + } + + // 3. 在函数结尾(ret之前)插入恢复指令 + for (auto& mbb : mfunc->getBlocks()) { + for (auto it = mbb->getInstructions().begin(); it != mbb->getInstructions().end(); ++it) { + if ((*it)->getOpcode() == RVOpcodes::RET) { + // 以相反的顺序恢复 + current_offset = -16; + for (PhysicalReg reg : sorted_regs) { + if (reg == PhysicalReg::S0) continue; + current_offset -= 8; + auto ld = std::make_unique(RVOpcodes::LD); + ld->addOperand(std::make_unique(reg)); + ld->addOperand(std::make_unique( + std::make_unique(PhysicalReg::S0), + std::make_unique(current_offset) + )); + mbb->getInstructions().insert(it, std::move(ld)); + } + break; // 处理完一个基本块的ret即可 + } + } + } +} + +} // namespace sysy \ No newline at end of file diff --git a/src/PostRA_Scheduler.cpp b/src/PostRA_Scheduler.cpp new file mode 100644 index 0000000..028107d --- /dev/null +++ b/src/PostRA_Scheduler.cpp @@ -0,0 +1,36 @@ +#include "PostRA_Scheduler.h" + +namespace sysy { + +char PostRA_Scheduler::ID = 0; + +bool PostRA_Scheduler::runOnFunction(Function *F, AnalysisManager& AM) { + // TODO: 在此实现寄存器分配后的局部指令调度。 + // 遍历mfunc中的每一个MachineBasicBlock。 + // 重点关注由寄存器分配器插入的spill/fill代码。 + // + // 实现思路: + // 1. 识别出用于spill/fill的lw/sw指令。 + // 2. 在不违反数据依赖(包括物理寄存器引入的伪依赖)的前提下, + // 尝试将lw指令向上移动,使其与使用它的指令之间有足够的距离,以隐藏访存延迟。 + // 3. 同样,可以尝试将sw指令向下移动。 + // + // std::cout << "Running Post-RA Local Scheduler... " << std::endl; + return false; +} + +void PostRA_Scheduler::runOnMachineFunction(MachineFunction *mfunc) { + // TODO: 在此实现寄存器分配后的局部指令调度。 + // 遍历mfunc中的每一个MachineBasicBlock。 + // 重点关注由寄存器分配器插入的spill/fill代码。 + // + // 实现思路: + // 1. 识别出用于spill/fill的lw/sw指令。 + // 2. 在不违反数据依赖(包括物理寄存器引入的伪依赖)的前提下, + // 尝试将lw指令向上移动,使其与使用它的指令之间有足够的距离,以隐藏访存延迟。 + // 3. 同样,可以尝试将sw指令向下移动。 + // + // std::cout << "Running Post-RA Local Scheduler... " << std::endl; +} + +} // namespace sysy \ No newline at end of file diff --git a/src/PreRA_Scheduler.cpp b/src/PreRA_Scheduler.cpp new file mode 100644 index 0000000..137edca --- /dev/null +++ b/src/PreRA_Scheduler.cpp @@ -0,0 +1,36 @@ +#include "PreRA_Scheduler.h" + +namespace sysy { + +char PreRA_Scheduler::ID = 0; + +bool PreRA_Scheduler::runOnFunction(Function *F, AnalysisManager& AM) { + // TODO: 在此实现寄存器分配前的指令调度。 + // 遍历mfunc中的每一个MachineBasicBlock。 + // 对每个基本块内的MachineInstr列表进行重排。 + // + // 实现思路: + // 1. 分析每个基本块内指令的数据依赖关系,构建依赖图(DAG)。 + // 2. + // 根据目标处理器的流水线特性(指令延迟等),使用列表调度等算法对指令进行重排。 + // 3. 此时操作的是虚拟寄存器,只存在真依赖,调度自由度最大。 + // + // std::cout << "Running Pre-RA Instruction Scheduler..." << std::endl; + return false; +} + +void PreRA_Scheduler::runOnMachineFunction(MachineFunction *mfunc) { + // TODO: 在此实现寄存器分配前的指令调度。 + // 遍历mfunc中的每一个MachineBasicBlock。 + // 对每个基本块内的MachineInstr列表进行重排。 + // + // 实现思路: + // 1. 分析每个基本块内指令的数据依赖关系,构建依赖图(DAG)。 + // 2. + // 根据目标处理器的流水线特性(指令延迟等),使用列表调度等算法对指令进行重排。 + // 3. 此时操作的是虚拟寄存器,只存在真依赖,调度自由度最大。 + // + // std::cout << "Running Pre-RA Instruction Scheduler..." << std::endl; +} + +} // namespace sysy \ No newline at end of file diff --git a/src/RISCv64Passes.cpp b/src/RISCv64Peephole.cpp similarity index 81% rename from src/RISCv64Passes.cpp rename to src/RISCv64Peephole.cpp index 15d397c..16e3a12 100644 --- a/src/RISCv64Passes.cpp +++ b/src/RISCv64Peephole.cpp @@ -1,27 +1,15 @@ -#include "RISCv64Passes.h" -// #include +#include "RISCv64Peephole.h" #include namespace sysy { -// --- 寄存器分配前优化 --- +char PeepholeOptimizer::ID = 0; -void PreRA_Scheduler::runOnMachineFunction(MachineFunction *mfunc) { - // TODO: 在此实现寄存器分配前的指令调度。 - // 遍历mfunc中的每一个MachineBasicBlock。 - // 对每个基本块内的MachineInstr列表进行重排。 - // - // 实现思路: - // 1. 分析每个基本块内指令的数据依赖关系,构建依赖图(DAG)。 - // 2. - // 根据目标处理器的流水线特性(指令延迟等),使用列表调度等算法对指令进行重排。 - // 3. 此时操作的是虚拟寄存器,只存在真依赖,调度自由度最大。 - // - // std::cout << "Running Pre-RA Instruction Scheduler..." << std::endl; +bool PeepholeOptimizer::runOnFunction(Function *F, AnalysisManager& AM) { + // This pass works on MachineFunction level, not IR level + return false; } -// --- 寄存器分配后优化 --- - void PeepholeOptimizer::runOnMachineFunction(MachineFunction *mfunc) { if (!mfunc) return; @@ -661,113 +649,4 @@ void PeepholeOptimizer::runOnMachineFunction(MachineFunction *mfunc) { } } -void PostRA_Scheduler::runOnMachineFunction(MachineFunction *mfunc) { - // TODO: 在此实现寄存器分配后的局部指令调度。 - // 遍历mfunc中的每一个MachineBasicBlock。 - // 重点关注由寄存器分配器插入的spill/fill代码。 - // - // 实现思路: - // 1. 识别出用于spill/fill的lw/sw指令。 - // 2. 在不违反数据依赖(包括物理寄存器引入的伪依赖)的前提下, - // 尝试将lw指令向上移动,使其与使用它的指令之间有足够的距离,以隐藏访存延迟。 - // 3. 同样,可以尝试将sw指令向下移动。 - // - // std::cout << "Running Post-RA Local Scheduler..." << std::endl; -} - -void CalleeSavedHandler::runOnMachineFunction(MachineFunction* mfunc) { - StackFrameInfo& frame_info = mfunc->getFrameInfo(); - std::set used_callee_saved; - - // 1. 扫描所有指令,找出被使用的s寄存器 - for (auto& mbb : mfunc->getBlocks()) { - for (auto& instr : mbb->getInstructions()) { - for (auto& op : instr->getOperands()) { - - // 辅助Lambda,用于检查和插入寄存器 - auto check_and_insert_reg = [&](RegOperand* reg_op) { - if (!reg_op->isVirtual()) { - PhysicalReg preg = reg_op->getPReg(); - // --- 关键检查点 --- - // 必须严格判断是否在 s0-s11 的范围内。 - // a0, t0 等寄存器绝对不应被视为被调用者保存寄存器。 - if (preg >= PhysicalReg::S0 && preg <= PhysicalReg::S11) { - used_callee_saved.insert(preg); - } - } - }; - - if (op->getKind() == MachineOperand::KIND_REG) { - check_and_insert_reg(static_cast(op.get())); - } else if (op->getKind() == MachineOperand::KIND_MEM) { - check_and_insert_reg(static_cast(op.get())->getBase()); - } - } - } - } - - // 如果没有使用s寄存器(除了可能作为帧指针的s0),则无需操作 - if (used_callee_saved.empty() || (used_callee_saved.size() == 1 && used_callee_saved.count(PhysicalReg::S0))) { - return; - } - - // 将结果存入StackFrameInfo,供后续使用 - frame_info.used_callee_saved_regs = used_callee_saved; - - // 2. 在函数序言插入保存指令 - MachineBasicBlock* entry_block = mfunc->getBlocks().front().get(); - auto& entry_instrs = entry_block->getInstructions(); - auto prologue_end = entry_instrs.begin(); - - // 找到序言结束的位置(通常是addi s0, sp, size之后) - for (auto it = entry_instrs.begin(); it != entry_instrs.end(); ++it) { - if ((*it)->getOpcode() == RVOpcodes::ADDI && - (*it)->getOperands()[0]->getKind() == MachineOperand::KIND_REG && - static_cast((*it)->getOperands()[0].get())->getPReg() == PhysicalReg::S0) - { - prologue_end = std::next(it); - break; - } - } - - // 为了栈帧布局确定性,对寄存器进行排序 - std::vector sorted_regs(used_callee_saved.begin(), used_callee_saved.end()); - std::sort(sorted_regs.begin(), sorted_regs.end()); - - int current_offset = -16; // ra和s0已经占用了-8和-16的位置 - for (PhysicalReg reg : sorted_regs) { - if (reg == PhysicalReg::S0) continue; // s0已经在序言中处理 - current_offset -= 8; - auto sd = std::make_unique(RVOpcodes::SD); - sd->addOperand(std::make_unique(reg)); - sd->addOperand(std::make_unique( - std::make_unique(PhysicalReg::S0), // 假设s0是帧指针 - std::make_unique(current_offset) - )); - entry_instrs.insert(prologue_end, std::move(sd)); - } - - // 3. 在函数结尾(ret之前)插入恢复指令 - for (auto& mbb : mfunc->getBlocks()) { - for (auto it = mbb->getInstructions().begin(); it != mbb->getInstructions().end(); ++it) { - if ((*it)->getOpcode() == RVOpcodes::RET) { - // 以相反的顺序恢复 - current_offset = -16; - for (PhysicalReg reg : sorted_regs) { - if (reg == PhysicalReg::S0) continue; - current_offset -= 8; - auto ld = std::make_unique(RVOpcodes::LD); - ld->addOperand(std::make_unique(reg)); - ld->addOperand(std::make_unique( - std::make_unique(PhysicalReg::S0), - std::make_unique(current_offset) - )); - mbb->getInstructions().insert(it, std::move(ld)); - } - break; // 处理完一个基本块的ret即可 - } - } - } -} - } // namespace sysy \ No newline at end of file diff --git a/src/include/CalleeSavedHandler.h b/src/include/CalleeSavedHandler.h new file mode 100644 index 0000000..73a9fb3 --- /dev/null +++ b/src/include/CalleeSavedHandler.h @@ -0,0 +1,33 @@ +#ifndef CALLEE_SAVED_HANDLER_H +#define CALLEE_SAVED_HANDLER_H + +#include "RISCv64LLIR.h" +#include "Pass.h" + +namespace sysy { + +/** + * @class CalleeSavedHandler + * @brief 处理被调用者保存寄存器(Callee-Saved Registers)的Pass。 + * * 这个Pass在寄存器分配之后运行。它的主要职责是: + * 1. 扫描整个函数,找出所有被使用的 `s` 系列寄存器。 + * 2. 在函数序言中插入 `sd` 指令来保存这些寄存器。 + * 3. 在函数结尾(ret指令前)插入 `ld` 指令来恢复这些寄存器。 + * 4. 正确计算因保存这些寄存器而需要的额外栈空间,并更新StackFrameInfo。 + */ +class CalleeSavedHandler : public Pass { +public: + static char ID; + + CalleeSavedHandler() : Pass("callee-saved-handler", Granularity::Function, PassKind::Optimization) {} + + void *getPassID() const override { return &ID; } + + bool runOnFunction(Function *F, AnalysisManager& AM) override; + + void runOnMachineFunction(MachineFunction* mfunc); +}; + +} // namespace sysy + +#endif // CALLEE_SAVED_HANDLER_H \ No newline at end of file diff --git a/src/include/PostRA_Scheduler.h b/src/include/PostRA_Scheduler.h new file mode 100644 index 0000000..c0f656c --- /dev/null +++ b/src/include/PostRA_Scheduler.h @@ -0,0 +1,30 @@ +#ifndef POST_RA_SCHEDULER_H +#define POST_RA_SCHEDULER_H + +#include "RISCv64LLIR.h" +#include "Pass.h" + +namespace sysy { + +/** + * @class PostRA_Scheduler + * @brief 寄存器分配后的局部指令调度器 + * * 主要目标是优化寄存器分配器插入的spill/fill代码(lw/sw), + * 尝试将加载指令提前,以隐藏其访存延迟。 + */ +class PostRA_Scheduler : public Pass { +public: + static char ID; + + PostRA_Scheduler() : Pass("post-ra-scheduler", Granularity::Function, PassKind::Optimization) {} + + void *getPassID() const override { return &ID; } + + bool runOnFunction(Function *F, AnalysisManager& AM) override; + + void runOnMachineFunction(MachineFunction* mfunc); +}; + +} // namespace sysy + +#endif // POST_RA_SCHEDULER_H \ No newline at end of file diff --git a/src/include/PreRA_Scheduler.h b/src/include/PreRA_Scheduler.h new file mode 100644 index 0000000..16e2d8c --- /dev/null +++ b/src/include/PreRA_Scheduler.h @@ -0,0 +1,30 @@ +#ifndef PRE_RA_SCHEDULER_H +#define PRE_RA_SCHEDULER_H + +#include "RISCv64LLIR.h" +#include "Pass.h" + +namespace sysy { + +/** + * @class PreRA_Scheduler + * @brief 寄存器分配前的指令调度器 + * * 在虚拟寄存器上进行操作,此时调度自由度最大, + * 主要目标是隐藏指令延迟,提高流水线效率。 + */ +class PreRA_Scheduler : public Pass { +public: + static char ID; + + PreRA_Scheduler() : Pass("pre-ra-scheduler", Granularity::Function, PassKind::Optimization) {} + + void *getPassID() const override { return &ID; } + + bool runOnFunction(Function *F, AnalysisManager& AM) override; + + void runOnMachineFunction(MachineFunction* mfunc); +}; + +} // namespace sysy + +#endif // PRE_RA_SCHEDULER_H \ No newline at end of file diff --git a/src/include/RISCv64Passes.h b/src/include/RISCv64Passes.h index 56013ae..565cf81 100644 --- a/src/include/RISCv64Passes.h +++ b/src/include/RISCv64Passes.h @@ -2,74 +2,14 @@ #define RISCV64_PASSES_H #include "RISCv64LLIR.h" +#include "RISCv64Peephole.h" +#include "PreRA_Scheduler.h" +#include "PostRA_Scheduler.h" +#include "CalleeSavedHandler.h" +#include "Pass.h" namespace sysy { -/** - * @class BackendPass - * @brief 所有优化Pass的抽象基类 (可选,但推荐) - * * 定义一个通用的接口,所有优化都应该实现它。 - */ -class BackendPass { -public: - virtual ~BackendPass() = default; - virtual void runOnMachineFunction(MachineFunction* mfunc) = 0; -}; - - -// --- 寄存器分配前优化 --- - -/** - * @class PreRA_Scheduler - * @brief 寄存器分配前的指令调度器 - * * 在虚拟寄存器上进行操作,此时调度自由度最大, - * 主要目标是隐藏指令延迟,提高流水线效率。 - */ -class PreRA_Scheduler : public BackendPass { -public: - void runOnMachineFunction(MachineFunction* mfunc) override; -}; - - -/** - * @class CalleeSavedHandler - * @brief 处理被调用者保存寄存器(Callee-Saved Registers)的Pass。 - * * 这个Pass在寄存器分配之后运行。它的主要职责是: - * 1. 扫描整个函数,找出所有被使用的 `s` 系列寄存器。 - * 2. 在函数序言中插入 `sd` 指令来保存这些寄存器。 - * 3. 在函数结尾(ret指令前)插入 `ld` 指令来恢复这些寄存器。 - * 4. 正确计算因保存这些寄存器而需要的额外栈空间,并更新StackFrameInfo。 - */ -class CalleeSavedHandler : public BackendPass { -public: - void runOnMachineFunction(MachineFunction* mfunc) override; -}; - -// --- 寄存器分配后优化 --- - -/** - * @class PeepholeOptimizer - * @brief 窥孔优化器 - * * 在已分配物理寄存器的指令流上,通过一个小的滑动窗口来查找 - * 并替换掉一些冗余或低效的指令模式。 - */ -class PeepholeOptimizer : public BackendPass { -public: - void runOnMachineFunction(MachineFunction* mfunc) override; -}; - -/** - * @class PostRA_Scheduler - * @brief 寄存器分配后的局部指令调度器 - * * 主要目标是优化寄存器分配器插入的spill/fill代码(lw/sw), - * 尝试将加载指令提前,以隐藏其访存延迟。 - */ -class PostRA_Scheduler : public BackendPass { -public: - void runOnMachineFunction(MachineFunction* mfunc) override; -}; - - } // namespace sysy #endif // RISCV64_PASSES_H \ No newline at end of file diff --git a/src/include/RISCv64Peephole.h b/src/include/RISCv64Peephole.h new file mode 100644 index 0000000..8b98fd5 --- /dev/null +++ b/src/include/RISCv64Peephole.h @@ -0,0 +1,30 @@ +#ifndef RISCV64_PEEPHOLE_H +#define RISCV64_PEEPHOLE_H + +#include "RISCv64LLIR.h" +#include "Pass.h" + +namespace sysy { + +/** + * @class PeepholeOptimizer + * @brief 窥孔优化器 + * * 在已分配物理寄存器的指令流上,通过一个小的滑动窗口来查找 + * 并替换掉一些冗余或低效的指令模式。 + */ +class PeepholeOptimizer : public Pass { +public: + static char ID; + + PeepholeOptimizer() : Pass("peephole-optimizer", Granularity::Function, PassKind::Optimization) {} + + void *getPassID() const override { return &ID; } + + bool runOnFunction(Function *F, AnalysisManager& AM) override; + + void runOnMachineFunction(MachineFunction* mfunc); +}; + +} // namespace sysy + +#endif // RISCV64_PEEPHOLE_H \ No newline at end of file