[peephole]Pass架构重构优化

This commit is contained in:
2025-07-27 11:03:26 +08:00
parent 78dee0d72a
commit 634a84f29c
10 changed files with 319 additions and 192 deletions

View File

@ -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 运行时库和项目头文件

109
src/CalleeSavedHandler.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "CalleeSavedHandler.h"
#include <set>
#include <algorithm>
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<PhysicalReg> 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<RegOperand*>(op.get()));
} else if (op->getKind() == MachineOperand::KIND_MEM) {
check_and_insert_reg(static_cast<MemOperand*>(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<RegOperand*>((*it)->getOperands()[0].get())->getPReg() == PhysicalReg::S0)
{
prologue_end = std::next(it);
break;
}
}
// 为了栈帧布局确定性,对寄存器进行排序
std::vector<PhysicalReg> 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<MachineInstr>(RVOpcodes::SD);
sd->addOperand(std::make_unique<RegOperand>(reg));
sd->addOperand(std::make_unique<MemOperand>(
std::make_unique<RegOperand>(PhysicalReg::S0), // 假设s0是帧指针
std::make_unique<ImmOperand>(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<MachineInstr>(RVOpcodes::LD);
ld->addOperand(std::make_unique<RegOperand>(reg));
ld->addOperand(std::make_unique<MemOperand>(
std::make_unique<RegOperand>(PhysicalReg::S0),
std::make_unique<ImmOperand>(current_offset)
));
mbb->getInstructions().insert(it, std::move(ld));
}
break; // 处理完一个基本块的ret即可
}
}
}
}
} // namespace sysy

36
src/PostRA_Scheduler.cpp Normal file
View File

@ -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

36
src/PreRA_Scheduler.cpp Normal file
View File

@ -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

View File

@ -1,27 +1,15 @@
#include "RISCv64Passes.h"
// #include <iostream>
#include "RISCv64Peephole.h"
#include <functional>
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<PhysicalReg> 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<RegOperand*>(op.get()));
} else if (op->getKind() == MachineOperand::KIND_MEM) {
check_and_insert_reg(static_cast<MemOperand*>(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<RegOperand*>((*it)->getOperands()[0].get())->getPReg() == PhysicalReg::S0)
{
prologue_end = std::next(it);
break;
}
}
// 为了栈帧布局确定性,对寄存器进行排序
std::vector<PhysicalReg> 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<MachineInstr>(RVOpcodes::SD);
sd->addOperand(std::make_unique<RegOperand>(reg));
sd->addOperand(std::make_unique<MemOperand>(
std::make_unique<RegOperand>(PhysicalReg::S0), // 假设s0是帧指针
std::make_unique<ImmOperand>(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<MachineInstr>(RVOpcodes::LD);
ld->addOperand(std::make_unique<RegOperand>(reg));
ld->addOperand(std::make_unique<MemOperand>(
std::make_unique<RegOperand>(PhysicalReg::S0),
std::make_unique<ImmOperand>(current_offset)
));
mbb->getInstructions().insert(it, std::move(ld));
}
break; // 处理完一个基本块的ret即可
}
}
}
}
} // namespace sysy

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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