Compare commits
14 Commits
SCCP
...
deploy-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef4bdfc8eb | ||
|
|
0e492cd6d7 | ||
|
|
2040670f8c | ||
|
|
b20bda2f52 | ||
|
|
20a5c5cbfb | ||
|
|
cf88ca77cb | ||
|
|
fd6fe22020 | ||
|
|
e8fe710c26 | ||
|
|
9c87cb397b | ||
|
|
c45938d41d | ||
|
|
3baccbc03a | ||
|
|
24d8e730f1 | ||
|
|
bbfbf96b5e | ||
|
|
f7e811b756 |
@@ -57,11 +57,22 @@ bool AddressCalculationExpansion::run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (GlobalValue* globalValue = dynamic_cast<GlobalValue*>(basePointer)) {
|
} else if (GlobalValue* globalValue = dynamic_cast<GlobalValue*>(basePointer)) {
|
||||||
std::cerr << "Warning: GlobalValue dimension handling needs explicit implementation for GEP expansion. Skipping GEP for: ";
|
// 遍历 GlobalValue 的所有维度操作数
|
||||||
SysYPrinter::printValue(globalValue);
|
for (const auto& use_ptr : globalValue->getDims()) {
|
||||||
std::cerr << "\n";
|
Value* dimValue = use_ptr->getValue();
|
||||||
++it;
|
// 将维度值转换为常量整数
|
||||||
continue;
|
if (ConstantInteger* constVal = dynamic_cast<ConstantInteger*>(dimValue)) {
|
||||||
|
dims.push_back(constVal->getInt());
|
||||||
|
} else {
|
||||||
|
// 如果维度不是常量整数,则无法处理。
|
||||||
|
// 根据 IR.h 中 GlobalValue 的构造函数,这种情况不应发生,但作为安全检查是好的。
|
||||||
|
std::cerr << "Warning: GlobalValue dimension is not a constant integer. Skipping GEP expansion for: ";
|
||||||
|
SysYPrinter::printValue(globalValue);
|
||||||
|
std::cerr << "\n";
|
||||||
|
dims.clear(); // 清空已收集的部分维度信息
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Warning: Base pointer is not AllocaInst/GlobalValue or its array dimensions cannot be determined for GEP expansion. Skipping GEP for: ";
|
std::cerr << "Warning: Base pointer is not AllocaInst/GlobalValue or its array dimensions cannot be determined for GEP expansion. Skipping GEP for: ";
|
||||||
SysYPrinter::printValue(basePointer);
|
SysYPrinter::printValue(basePointer);
|
||||||
|
|||||||
@@ -1,276 +0,0 @@
|
|||||||
#include "DeadCodeElimination.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
extern int DEBUG;
|
|
||||||
namespace sysy {
|
|
||||||
|
|
||||||
void DeadCodeElimination::runDCEPipeline() {
|
|
||||||
const auto& functions = pModule->getFunctions();
|
|
||||||
for (const auto& function : functions) {
|
|
||||||
const auto& func = function.second;
|
|
||||||
bool changed = true;
|
|
||||||
while (changed) {
|
|
||||||
changed = false;
|
|
||||||
eliminateDeadStores(func.get(), changed);
|
|
||||||
eliminateDeadLoads(func.get(), changed);
|
|
||||||
eliminateDeadAllocas(func.get(), changed);
|
|
||||||
eliminateDeadRedundantLoadStore(func.get(), changed);
|
|
||||||
eliminateDeadGlobals(changed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消除无用存储 消除条件:
|
|
||||||
// 存储的目标指针(pointer)不是全局变量(!isGlobal(pointer))。
|
|
||||||
// 存储的目标指针不是数组参数(!isArr(pointer) 或不在函数参数列表里)。
|
|
||||||
// 该指针的所有使用者(uses)仅限 alloca 或 store(即没有 load 或其他指令使用它)。
|
|
||||||
void DeadCodeElimination::eliminateDeadStores(Function* func, bool& changed) {
|
|
||||||
for (const auto& block : func->getBasicBlocks()) {
|
|
||||||
auto& instrs = block->getInstructions();
|
|
||||||
for (auto iter = instrs.begin(); iter != instrs.end();) {
|
|
||||||
auto inst = iter->get();
|
|
||||||
if (!inst->isStore()) {
|
|
||||||
++iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto storeInst = dynamic_cast<StoreInst*>(inst);
|
|
||||||
auto pointer = storeInst->getPointer();
|
|
||||||
// 如果是全局变量或者是函数的数组参数
|
|
||||||
if (isGlobal(pointer) || (isArr(pointer) &&
|
|
||||||
std::find(func->getEntryBlock()->getArguments().begin(),
|
|
||||||
func->getEntryBlock()->getArguments().end(),
|
|
||||||
pointer) != func->getEntryBlock()->getArguments().end())) {
|
|
||||||
++iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool changetag = true;
|
|
||||||
for (auto& use : pointer->getUses()) {
|
|
||||||
// 依次判断store的指针是否被其他指令使用
|
|
||||||
auto user = use->getUser();
|
|
||||||
auto userInst = dynamic_cast<Instruction*>(user);
|
|
||||||
// 如果使用store的指针的指令不是Alloca或Store,则不删除
|
|
||||||
if (userInst != nullptr && !userInst->isAlloca() && !userInst->isStore()) {
|
|
||||||
changetag = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changetag) {
|
|
||||||
changed = true;
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Store Found ===\n";
|
|
||||||
SysYPrinter::printInst(storeInst);
|
|
||||||
}
|
|
||||||
usedelete(storeInst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
} else {
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 消除无用加载 消除条件:
|
|
||||||
// 该指令的结果未被使用(inst->getUses().empty())。
|
|
||||||
void DeadCodeElimination::eliminateDeadLoads(Function* func, bool& changed) {
|
|
||||||
for (const auto& block : func->getBasicBlocks()) {
|
|
||||||
auto& instrs = block->getInstructions();
|
|
||||||
for (auto iter = instrs.begin(); iter != instrs.end();) {
|
|
||||||
auto inst = iter->get();
|
|
||||||
if (inst->isBinary() || inst->isUnary() || inst->isLoad()) {
|
|
||||||
if (inst->getUses().empty()) {
|
|
||||||
changed = true;
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Load Binary Unary Found ===\n";
|
|
||||||
SysYPrinter::printInst(inst);
|
|
||||||
}
|
|
||||||
usedelete(inst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消除无用加载 消除条件:
|
|
||||||
// 该 alloca 未被任何指令使用(allocaInst->getUses().empty())。
|
|
||||||
// 该 alloca 不是函数的参数(不在 entry 块的参数列表里)。
|
|
||||||
void DeadCodeElimination::eliminateDeadAllocas(Function* func, bool& changed) {
|
|
||||||
for (const auto& block : func->getBasicBlocks()) {
|
|
||||||
auto& instrs = block->getInstructions();
|
|
||||||
for (auto iter = instrs.begin(); iter != instrs.end();) {
|
|
||||||
auto inst = iter->get();
|
|
||||||
if (inst->isAlloca()) {
|
|
||||||
auto allocaInst = dynamic_cast<AllocaInst*>(inst);
|
|
||||||
if (allocaInst->getUses().empty() &&
|
|
||||||
std::find(func->getEntryBlock()->getArguments().begin(),
|
|
||||||
func->getEntryBlock()->getArguments().end(),
|
|
||||||
allocaInst) == func->getEntryBlock()->getArguments().end()) {
|
|
||||||
changed = true;
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Alloca Found ===\n";
|
|
||||||
SysYPrinter::printInst(inst);
|
|
||||||
}
|
|
||||||
usedelete(inst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeadCodeElimination::eliminateDeadIndirectiveAllocas(Function* func, bool& changed) {
|
|
||||||
// 删除mem2reg时引入的且现在已经没有value使用了的隐式alloca
|
|
||||||
FunctionAnalysisInfo* funcInfo = pCFA->getFunctionAnalysisInfo(func);
|
|
||||||
for (auto it = funcInfo->getIndirectAllocas().begin(); it != funcInfo->getIndirectAllocas().end();) {
|
|
||||||
auto &allocaInst = *it;
|
|
||||||
if (allocaInst->getUses().empty()) {
|
|
||||||
changed = true;
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Indirect Alloca Found ===\n";
|
|
||||||
SysYPrinter::printInst(allocaInst.get());
|
|
||||||
}
|
|
||||||
it = funcInfo->getIndirectAllocas().erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 该全局变量未被任何指令使用(global->getUses().empty())。
|
|
||||||
void DeadCodeElimination::eliminateDeadGlobals(bool& changed) {
|
|
||||||
auto& globals = pModule->getGlobals();
|
|
||||||
for (auto it = globals.begin(); it != globals.end();) {
|
|
||||||
auto& global = *it;
|
|
||||||
if (global->getUses().empty()) {
|
|
||||||
changed = true;
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Global Found ===\n";
|
|
||||||
SysYPrinter::printValue(global.get());
|
|
||||||
}
|
|
||||||
it = globals.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消除冗余加载和存储 消除条件:
|
|
||||||
// phi 指令的目标指针仅被该 phi 使用(无其他 store/load 使用)。
|
|
||||||
// memset 指令的目标指针未被使用(pointer->getUses().empty())
|
|
||||||
// store -> load -> store 模式
|
|
||||||
void DeadCodeElimination::eliminateDeadRedundantLoadStore(Function* func, bool& changed) {
|
|
||||||
for (const auto& block : func->getBasicBlocks()) {
|
|
||||||
auto& instrs = block->getInstructions();
|
|
||||||
for (auto iter = instrs.begin(); iter != instrs.end();) {
|
|
||||||
auto inst = iter->get();
|
|
||||||
if (inst->isPhi()) {
|
|
||||||
auto phiInst = dynamic_cast<PhiInst*>(inst);
|
|
||||||
auto pointer = phiInst->getPointer();
|
|
||||||
bool tag = true;
|
|
||||||
for (const auto& use : pointer->getUses()) {
|
|
||||||
auto user = use->getUser();
|
|
||||||
if (user != inst) {
|
|
||||||
tag = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// 如果 pointer 仅被该 phi 使用,可以删除 ph
|
|
||||||
if (tag) {
|
|
||||||
changed = true;
|
|
||||||
usedelete(inst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 数组指令还不完善,不保证memset优化效果
|
|
||||||
} else if (inst->isMemset()) {
|
|
||||||
auto memsetInst = dynamic_cast<MemsetInst*>(inst);
|
|
||||||
auto pointer = memsetInst->getPointer();
|
|
||||||
if (pointer->getUses().empty()) {
|
|
||||||
changed = true;
|
|
||||||
usedelete(inst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}else if(inst->isLoad()) {
|
|
||||||
if (iter != instrs.begin()) {
|
|
||||||
auto loadInst = dynamic_cast<LoadInst*>(inst);
|
|
||||||
auto loadPointer = loadInst->getPointer();
|
|
||||||
// TODO:store -> load -> store 模式
|
|
||||||
auto prevIter = std::prev(iter);
|
|
||||||
auto prevInst = prevIter->get();
|
|
||||||
if (prevInst->isStore()) {
|
|
||||||
auto prevStore = dynamic_cast<StoreInst*>(prevInst);
|
|
||||||
auto prevStorePointer = prevStore->getPointer();
|
|
||||||
auto prevStoreValue = prevStore->getOperand(0);
|
|
||||||
// 确保前一个 store 不是数组操作
|
|
||||||
if (prevStore->getIndices().empty()) {
|
|
||||||
// 检查后一条指令是否是 store 同一个值
|
|
||||||
auto nextIter = std::next(iter);
|
|
||||||
if (nextIter != instrs.end()) {
|
|
||||||
auto nextInst = nextIter->get();
|
|
||||||
if (nextInst->isStore()) {
|
|
||||||
auto nextStore = dynamic_cast<StoreInst*>(nextInst);
|
|
||||||
auto nextStorePointer = nextStore->getPointer();
|
|
||||||
auto nextStoreValue = nextStore->getOperand(0);
|
|
||||||
// 确保后一个 store 不是数组操作
|
|
||||||
if (nextStore->getIndices().empty()) {
|
|
||||||
// 判断优化条件:
|
|
||||||
// 1. prevStore 的指针操作数 == load 的指针操作数
|
|
||||||
// 2. nextStore 的值操作数 == load 指令本身
|
|
||||||
if (prevStorePointer == loadPointer &&
|
|
||||||
nextStoreValue == loadInst) {
|
|
||||||
// 可以优化直接把prevStorePointer的值存到nextStorePointer
|
|
||||||
changed = true;
|
|
||||||
nextStore->setOperand(0, prevStoreValue);
|
|
||||||
if(DEBUG){
|
|
||||||
std::cout << "=== Dead Store Load Store Found(now only del Load) ===\n";
|
|
||||||
SysYPrinter::printInst(prevStore);
|
|
||||||
SysYPrinter::printInst(loadInst);
|
|
||||||
SysYPrinter::printInst(nextStore);
|
|
||||||
}
|
|
||||||
usedelete(loadInst);
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
// 删除 prevStore 这里是不是可以留给删除无用store处理?
|
|
||||||
// if (prevStore->getUses().empty()) {
|
|
||||||
// usedelete(prevStore);
|
|
||||||
// instrs.erase(prevIter); // 删除 prevStore
|
|
||||||
// }
|
|
||||||
continue; // 跳过 ++iter,因为已经移动迭代器
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool DeadCodeElimination::isGlobal(Value *val){
|
|
||||||
auto gval = dynamic_cast<GlobalValue *>(val);
|
|
||||||
return gval != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeadCodeElimination::isArr(Value *val){
|
|
||||||
auto aval = dynamic_cast<AllocaInst *>(val);
|
|
||||||
return aval != nullptr && aval->getNumDims() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeadCodeElimination::usedelete(Instruction *instr){
|
|
||||||
for (auto &use1 : instr->getOperands()) {
|
|
||||||
auto val1 = use1->getValue();
|
|
||||||
val1->removeUse(use1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sysy
|
|
||||||
801
src/Mem2Reg.cpp
801
src/Mem2Reg.cpp
@@ -1,801 +0,0 @@
|
|||||||
#include "Mem2Reg.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <iterator>
|
|
||||||
#include <memory>
|
|
||||||
#include <queue>
|
|
||||||
#include <stack>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include "IR.h"
|
|
||||||
#include "SysYIRAnalyser.h"
|
|
||||||
#include "SysYIRPrinter.h"
|
|
||||||
|
|
||||||
namespace sysy {
|
|
||||||
|
|
||||||
// 计算给定变量的定义块集合的迭代支配边界
|
|
||||||
// TODO:优化Semi-Naive IDF
|
|
||||||
std::unordered_set<BasicBlock *> Mem2Reg::computeIterDf(const std::unordered_set<BasicBlock *> &blocks) {
|
|
||||||
std::unordered_set<BasicBlock *> workList;
|
|
||||||
std::unordered_set<BasicBlock *> ret_list;
|
|
||||||
workList.insert(blocks.begin(), blocks.end());
|
|
||||||
|
|
||||||
while (!workList.empty()) {
|
|
||||||
auto n = workList.begin();
|
|
||||||
BlockAnalysisInfo* blockInfo = controlFlowAnalysis->getBlockAnalysisInfo(*n);
|
|
||||||
auto DFs = blockInfo->getDomFrontiers();
|
|
||||||
for (auto c : DFs) {
|
|
||||||
// 如果c不在ret_list中,则将其加入ret_list和workList
|
|
||||||
// 这里的c是n的支配边界
|
|
||||||
// 也就是n的支配边界中的块
|
|
||||||
// 需要注意的是,支配边界是一个集合,所以可能会有重复
|
|
||||||
if (ret_list.count(c) == 0U) {
|
|
||||||
ret_list.emplace(c);
|
|
||||||
workList.emplace(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workList.erase(n);
|
|
||||||
}
|
|
||||||
return ret_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算value2Blocks的映射,包括value2AllocBlocks、value2DefBlocks以及value2UseBlocks
|
|
||||||
* 其中value2DefBlocks可用于计算迭代支配边界来插入相应变量的phi结点
|
|
||||||
* 这里的value2AllocBlocks、value2DefBlocks和value2UseBlocks改变了函数级别的分析信息
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::computeValue2Blocks() -> void {
|
|
||||||
SysYPrinter printer(pModule); // 初始化打印机
|
|
||||||
// std::cout << "===== Start computeValue2Blocks =====" << std::endl;
|
|
||||||
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
// std::cout << "\nProcessing function: " << func->getName() << std::endl;
|
|
||||||
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
if (!funcInfo) {
|
|
||||||
std::cerr << "ERROR: No analysis info for function " << func->getName() << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto basicBlocks = func->getBasicBlocks();
|
|
||||||
// std::cout << "BasicBlocks count: " << basicBlocks.size() << std::endl;
|
|
||||||
|
|
||||||
for (auto &it : basicBlocks) {
|
|
||||||
auto basicBlock = it.get();
|
|
||||||
// std::cout << "\nProcessing BB: " << basicBlock->getName() << std::endl;
|
|
||||||
// printer.printBlock(basicBlock); // 打印基本块内容
|
|
||||||
|
|
||||||
auto &instrs = basicBlock->getInstructions();
|
|
||||||
for (auto &instr : instrs) {
|
|
||||||
// std::cout << " Analyzing instruction: ";
|
|
||||||
// printer.printInst(instr.get());
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
if (instr->isAlloca()) {
|
|
||||||
if (!(isArr(instr.get()) || isGlobal(instr.get()))) {
|
|
||||||
// std::cout << " Found alloca: ";
|
|
||||||
// printer.printInst(instr.get());
|
|
||||||
// std::cout << " -> Adding to allocBlocks" << std::endl;
|
|
||||||
|
|
||||||
funcInfo->addValue2AllocBlocks(instr.get(), basicBlock);
|
|
||||||
} else {
|
|
||||||
// std::cout << " Skip array/global alloca: ";
|
|
||||||
// printer.printInst(instr.get());
|
|
||||||
// std::cout << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (instr->isStore()) {
|
|
||||||
auto val = instr->getOperand(1);
|
|
||||||
// std::cout << " Store target: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
// std::cout << " Adding store to defBlocks for value: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(instr.get()));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
// 将store的目标值添加到defBlocks中
|
|
||||||
funcInfo->addValue2DefBlocks(val, basicBlock);
|
|
||||||
} else {
|
|
||||||
// std::cout << " Skip array/global store" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (instr->isLoad()) {
|
|
||||||
auto val = instr->getOperand(0);
|
|
||||||
// std::cout << " Load source: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
// std::cout << " Adding load to useBlocks for value: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
funcInfo->addValue2UseBlocks(val, basicBlock);
|
|
||||||
} else {
|
|
||||||
// std::cout << " Skip array/global load" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印分析结果
|
|
||||||
// std::cout << "\nAnalysis results for function " << func->getName() << ":" << std::endl;
|
|
||||||
|
|
||||||
// auto &allocMap = funcInfo->getValue2AllocBlocks();
|
|
||||||
// std::cout << "AllocBlocks (" << allocMap.size() << "):" << std::endl;
|
|
||||||
// for (auto &[val, bb] : allocMap) {
|
|
||||||
// std::cout << " ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << " in BB: " << bb->getName() << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// auto &defMap = funcInfo->getValue2DefBlocks();
|
|
||||||
// std::cout << "DefBlocks (" << defMap.size() << "):" << std::endl;
|
|
||||||
// for (auto &[val, bbs] : defMap) {
|
|
||||||
// std::cout << " ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// for (const auto &[bb, count] : bbs) {
|
|
||||||
// std::cout << " in BB: " << bb->getName() << " (count: " << count << ")";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// auto &useMap = funcInfo->getValue2UseBlocks();
|
|
||||||
// std::cout << "UseBlocks (" << useMap.size() << "):" << std::endl;
|
|
||||||
// for (auto &[val, bbs] : useMap) {
|
|
||||||
// std::cout << " ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// for (const auto &[bb, count] : bbs) {
|
|
||||||
// std::cout << " in BB: " << bb->getName() << " (count: " << count << ")";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// std::cout << "===== End computeValue2Blocks =====" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 级联关系的顺带消除,用于llvm mem2reg类预优化1
|
|
||||||
*
|
|
||||||
* 采用队列进行模拟,从某种程度上来看其实可以看作是UD链的反向操作;
|
|
||||||
*
|
|
||||||
* @param [in] instr store指令使用的指令
|
|
||||||
* @param [in] changed 不动点法的判断标准,地址传递
|
|
||||||
* @param [in] func 指令所在函数
|
|
||||||
* @param [in] block 指令所在基本块
|
|
||||||
* @param [in] instrs 基本块所在指令集合,地址传递
|
|
||||||
* @return 无返回值,但满足条件的情况下会对指令进行删除
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::cascade(Instruction *instr, bool &changed, Function *func, BasicBlock *block,
|
|
||||||
std::list<std::unique_ptr<Instruction>> &instrs) -> void {
|
|
||||||
if (instr != nullptr) {
|
|
||||||
if (instr->isUnary() || instr->isBinary() || instr->isLoad()) {
|
|
||||||
std::queue<Instruction *> toRemove;
|
|
||||||
toRemove.push(instr);
|
|
||||||
while (!toRemove.empty()) {
|
|
||||||
auto top = toRemove.front();
|
|
||||||
toRemove.pop();
|
|
||||||
auto operands = top->getOperands();
|
|
||||||
for (const auto &operand : operands) {
|
|
||||||
auto elem = dynamic_cast<Instruction *>(operand->getValue());
|
|
||||||
if (elem != nullptr) {
|
|
||||||
if ((elem->isUnary() || elem->isBinary() || elem->isLoad()) && elem->getUses().size() == 1 &&
|
|
||||||
elem->getUses().front()->getUser() == top) {
|
|
||||||
toRemove.push(elem);
|
|
||||||
} else if (elem->isAlloca()) {
|
|
||||||
// value2UseBlock中该block对应次数-1,如果该变量的该useblock中count减为0了,则意味着
|
|
||||||
// 该block其他地方也没用到该alloc了,故从value2UseBlock中删除
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
auto res = funcInfo->removeValue2UseBlock(elem, block);
|
|
||||||
// 只要有一次返回了true,就说明有变化
|
|
||||||
if (res) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto tofind =
|
|
||||||
std::find_if(instrs.begin(), instrs.end(), [&top](const auto &instr) { return instr.get() == top; });
|
|
||||||
assert(tofind != instrs.end());
|
|
||||||
usedelete(tofind->get());
|
|
||||||
instrs.erase(tofind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* llvm mem2reg预优化1: 删除不含load的alloc和store
|
|
||||||
*
|
|
||||||
* 1. 删除不含load的alloc和store;
|
|
||||||
* 2. 删除store指令,之前的用于作store指令第0个操作数的那些级联指令就冗余了,也要删除;
|
|
||||||
* 3. 删除之后,可能有些变量的load使用恰好又没有了,因此再次从第一步开始循环,这里使用不动点法
|
|
||||||
*
|
|
||||||
* 由于删除了级联关系,所以这里的方法有点儿激进;
|
|
||||||
* 同时也考虑了级联关系时如果调用了函数,可能会有side effect,所以没有删除调用函数的级联关系;
|
|
||||||
* 而且关于函数参数的alloca不会在指令中删除,也不会在value2Alloca中删除;
|
|
||||||
* 同样地,我们不考虑数组和global,不过这里的代码是基于value2blocks的,在value2blocks中已经考虑了,所以不用显式指明
|
|
||||||
*=
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::preOptimize1() -> void {
|
|
||||||
SysYPrinter printer(pModule); // 初始化打印机
|
|
||||||
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
// std::cout << "===== Start preOptimize1 =====" << std::endl;
|
|
||||||
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
// std::cout << "\nProcessing function: " << func->getName() << std::endl;
|
|
||||||
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
if (!funcInfo) {
|
|
||||||
// std::cerr << "ERROR: No analysis info for function " << func->getName() << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &vToDefB = funcInfo->getValue2DefBlocks();
|
|
||||||
auto &vToUseB = funcInfo->getValue2UseBlocks();
|
|
||||||
auto &vToAllocB = funcInfo->getValue2AllocBlocks();
|
|
||||||
|
|
||||||
// 打印初始状态
|
|
||||||
// std::cout << "Initial allocas: " << vToAllocB.size() << std::endl;
|
|
||||||
// for (auto &[val, bb] : vToAllocB) {
|
|
||||||
// std::cout << " Alloca: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << " in BB: " << bb->getName() << std::endl;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 阶段1:删除无store的alloca
|
|
||||||
// std::cout << "\nPhase 1: Remove unused allocas" << std::endl;
|
|
||||||
for (auto iter = vToAllocB.begin(); iter != vToAllocB.end();) {
|
|
||||||
auto val = iter->first;
|
|
||||||
auto bb = iter->second;
|
|
||||||
|
|
||||||
// std::cout << "Checking alloca: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << " in BB: " << bb->getName() << std::endl;
|
|
||||||
|
|
||||||
// 如果该alloca没有对应的store指令,且不在函数参数中
|
|
||||||
// 这里的vToDefB是value2DefBlocks,vToUseB是value2UseBlocks
|
|
||||||
|
|
||||||
// 打印vToDefB
|
|
||||||
// std::cout << "DefBlocks (" << vToDefB.size() << "):" << std::endl;
|
|
||||||
// for (auto &[val, bbs] : vToDefB) {
|
|
||||||
// std::cout << " ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// for (const auto &[bb, count] : bbs) {
|
|
||||||
// std::cout << " in BB: " << bb->getName() << " (count: " << count << ")" << std::endl;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// std::cout << vToDefB.count(val) << std::endl;
|
|
||||||
|
|
||||||
if (vToDefB.count(val) == 0U &&
|
|
||||||
std::find(func->getEntryBlock()->getArguments().begin(),
|
|
||||||
func->getEntryBlock()->getArguments().end(),
|
|
||||||
val) == func->getEntryBlock()->getArguments().end()) {
|
|
||||||
|
|
||||||
// std::cout << " Removing unused alloca: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
auto tofind = std::find_if(bb->getInstructions().begin(),
|
|
||||||
bb->getInstructions().end(),
|
|
||||||
[val](const auto &instr) {
|
|
||||||
return instr.get() == val;
|
|
||||||
});
|
|
||||||
if (tofind == bb->getInstructions().end()) {
|
|
||||||
// std::cerr << "ERROR: Alloca not found in BB!" << std::endl;
|
|
||||||
++iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
usedelete(tofind->get());
|
|
||||||
bb->getInstructions().erase(tofind);
|
|
||||||
iter = vToAllocB.erase(iter);
|
|
||||||
} else {
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 阶段2:删除无load的store
|
|
||||||
// std::cout << "\nPhase 2: Remove dead stores" << std::endl;
|
|
||||||
bool changed = true;
|
|
||||||
int iteration = 0;
|
|
||||||
|
|
||||||
while (changed) {
|
|
||||||
changed = false;
|
|
||||||
iteration++;
|
|
||||||
// std::cout << "\nIteration " << iteration << std::endl;
|
|
||||||
|
|
||||||
for (auto iter = vToDefB.begin(); iter != vToDefB.end();) {
|
|
||||||
auto val = iter->first;
|
|
||||||
|
|
||||||
// std::cout << "Checking value: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
if (vToUseB.count(val) == 0U) {
|
|
||||||
// std::cout << " Found dead store for value: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
auto blocks = funcInfo->getDefBlocksByValue(val);
|
|
||||||
for (auto block : blocks) {
|
|
||||||
// std::cout << " Processing BB: " << block->getName() << std::endl;
|
|
||||||
// printer.printBlock(block); // 打印基本块内容
|
|
||||||
|
|
||||||
auto &instrs = block->getInstructions();
|
|
||||||
for (auto it = instrs.begin(); it != instrs.end();) {
|
|
||||||
if ((*it)->isStore() && (*it)->getOperand(1) == val) {
|
|
||||||
// std::cout << " Removing store: ";
|
|
||||||
// printer.printInst(it->get());
|
|
||||||
std::cout << std::endl;
|
|
||||||
|
|
||||||
auto valUsedByStore = dynamic_cast<Instruction *>((*it)->getOperand(0));
|
|
||||||
usedelete(it->get());
|
|
||||||
|
|
||||||
if (valUsedByStore != nullptr &&
|
|
||||||
valUsedByStore->getUses().size() == 1 &&
|
|
||||||
valUsedByStore->getUses().front()->getUser() == (*it).get()) {
|
|
||||||
// std::cout << " Cascade deleting: ";
|
|
||||||
// printer.printInst(valUsedByStore);
|
|
||||||
// std::cout << std::endl;
|
|
||||||
|
|
||||||
cascade(valUsedByStore, changed, func, block, instrs);
|
|
||||||
}
|
|
||||||
it = instrs.erase(it);
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除对应的alloca
|
|
||||||
if (std::find(func->getEntryBlock()->getArguments().begin(),
|
|
||||||
func->getEntryBlock()->getArguments().end(),
|
|
||||||
val) == func->getEntryBlock()->getArguments().end()) {
|
|
||||||
auto bb = funcInfo->getAllocBlockByValue(val);
|
|
||||||
if (bb != nullptr) {
|
|
||||||
// std::cout << " Removing alloca: ";
|
|
||||||
// printer.printInst(dynamic_cast<Instruction *>(val));
|
|
||||||
// std::cout << " in BB: " << bb->getName() << std::endl;
|
|
||||||
|
|
||||||
funcInfo->removeValue2AllocBlock(val);
|
|
||||||
auto tofind = std::find_if(bb->getInstructions().begin(),
|
|
||||||
bb->getInstructions().end(),
|
|
||||||
[val](const auto &instr) {
|
|
||||||
return instr.get() == val;
|
|
||||||
});
|
|
||||||
if (tofind != bb->getInstructions().end()) {
|
|
||||||
usedelete(tofind->get());
|
|
||||||
bb->getInstructions().erase(tofind);
|
|
||||||
} else {
|
|
||||||
std::cerr << "ERROR: Alloca not found in BB!" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iter = vToDefB.erase(iter);
|
|
||||||
} else {
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// std::cout << "===== End preOptimize1 =====" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* llvm mem2reg预优化2: 针对某个变量的Defblocks只有一个块的情况
|
|
||||||
*
|
|
||||||
* 1. 该基本块最后一次对该变量的store指令后的所有对该变量的load指令都可以替换为该基本块最后一次store指令的第0个操作数;
|
|
||||||
* 2. 以该基本块为必经结点的结点集合中的对该变量的load指令都可以替换为该基本块最后一次对该变量的store指令的第0个操作数;
|
|
||||||
* 3.
|
|
||||||
* 如果对该变量的所有load均替换掉了,删除该基本块中最后一次store指令,如果这个store指令是唯一的define,那么再删除alloca指令(不删除参数的alloca);
|
|
||||||
* 4.
|
|
||||||
* 如果对该value的所有load都替换掉了,对于该变量剩下还有store的话,就转换成了preOptimize1的情况,再调用preOptimize1进行删除;
|
|
||||||
*
|
|
||||||
* 同样不考虑数组和全局变量,因为这些变量不会被mem2reg优化,在value2blocks中已经考虑了,所以不用显式指明;
|
|
||||||
* 替换的操作采用了UD链进行简化和效率的提升
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::preOptimize2() -> void {
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
auto values = funcInfo->getValuesOfDefBlock();
|
|
||||||
for (auto val : values) {
|
|
||||||
auto blocks = funcInfo->getDefBlocksByValue(val);
|
|
||||||
// 该val只有一个defining block
|
|
||||||
if (blocks.size() == 1) {
|
|
||||||
auto block = *blocks.begin();
|
|
||||||
auto &instrs = block->getInstructions();
|
|
||||||
auto rit = std::find_if(instrs.rbegin(), instrs.rend(),
|
|
||||||
[val](const auto &instr) { return instr->isStore() && instr->getOperand(1) == val; });
|
|
||||||
// 注意reverse_iterator求base后是指向下一个指令,因此要减一才是原来的指令
|
|
||||||
assert(rit != instrs.rend());
|
|
||||||
auto it = --rit.base();
|
|
||||||
auto propogationVal = (*it)->getOperand(0);
|
|
||||||
// 其实该块中it后对该val的load指令也可以替换掉了
|
|
||||||
for (auto curit = std::next(it); curit != instrs.end();) {
|
|
||||||
if ((*curit)->isLoad() && (*curit)->getOperand(0) == val) {
|
|
||||||
curit->get()->replaceAllUsesWith(propogationVal);
|
|
||||||
usedelete(curit->get());
|
|
||||||
curit = instrs.erase(curit);
|
|
||||||
funcInfo->removeValue2UseBlock(val, block);
|
|
||||||
} else {
|
|
||||||
++curit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 在支配树后继结点中替换load指令的操作数
|
|
||||||
BlockAnalysisInfo* blockInfo = controlFlowAnalysis->getBlockAnalysisInfo(block);
|
|
||||||
std::vector<BasicBlock *> blkchildren;
|
|
||||||
// 获取该块的支配树后继结点
|
|
||||||
std::queue<BasicBlock *> q;
|
|
||||||
auto sdoms = blockInfo->getSdoms();
|
|
||||||
for (auto sdom : sdoms) {
|
|
||||||
q.push(sdom);
|
|
||||||
blkchildren.push_back(sdom);
|
|
||||||
}
|
|
||||||
while (!q.empty()) {
|
|
||||||
auto blk = q.front();
|
|
||||||
q.pop();
|
|
||||||
BlockAnalysisInfo* blkInfo = controlFlowAnalysis->getBlockAnalysisInfo(blk);
|
|
||||||
for (auto sdom : blkInfo->getSdoms()) {
|
|
||||||
q.push(sdom);
|
|
||||||
blkchildren.push_back(sdom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (auto child : blkchildren) {
|
|
||||||
auto &childInstrs = child->getInstructions();
|
|
||||||
for (auto childIter = childInstrs.begin(); childIter != childInstrs.end();) {
|
|
||||||
if ((*childIter)->isLoad() && (*childIter)->getOperand(0) == val) {
|
|
||||||
childIter->get()->replaceAllUsesWith(propogationVal);
|
|
||||||
usedelete(childIter->get());
|
|
||||||
childIter = childInstrs.erase(childIter);
|
|
||||||
funcInfo->removeValue2UseBlock(val, child);
|
|
||||||
} else {
|
|
||||||
++childIter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果对该val的所有load均替换掉了,那么对于该val的defining block中的最后一个define也可以删除了
|
|
||||||
// 同时该块中前面对于该val的define也变成死代码了,可调用preOptimize1进行删除
|
|
||||||
if (funcInfo->getUseBlocksByValue(val).empty()) {
|
|
||||||
usedelete(it->get());
|
|
||||||
instrs.erase(it);
|
|
||||||
auto change = funcInfo->removeValue2DefBlock(val, block);
|
|
||||||
if (change) {
|
|
||||||
// 如果define是唯一的,且不是函数参数的alloca,直接删alloca
|
|
||||||
if (std::find(func->getEntryBlock()->getArguments().begin(), func->getEntryBlock()->getArguments().end(),
|
|
||||||
val) == func->getEntryBlock()->getArguments().end()) {
|
|
||||||
auto bb = funcInfo->getAllocBlockByValue(val);
|
|
||||||
assert(bb != nullptr);
|
|
||||||
auto tofind = std::find_if(bb->getInstructions().begin(), bb->getInstructions().end(),
|
|
||||||
[val](const auto &instr) { return instr.get() == val; });
|
|
||||||
usedelete(tofind->get());
|
|
||||||
bb->getInstructions().erase(tofind);
|
|
||||||
funcInfo->removeValue2AllocBlock(val);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果该变量还有其他的define,那么前面的define也变成死代码了
|
|
||||||
assert(!funcInfo->getDefBlocksByValue(val).empty());
|
|
||||||
assert(funcInfo->getUseBlocksByValue(val).empty());
|
|
||||||
preOptimize1();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief llvm mem2reg类预优化3:针对某个变量的所有读写都在同一个块中的情况
|
|
||||||
*
|
|
||||||
* 1. 将每一个load替换成前一个store的值,并删除该load;
|
|
||||||
* 2. 如果在load前没有对该变量的store,则不删除该load;
|
|
||||||
* 3. 如果一个store后没有任何对改变量的load,则删除该store;
|
|
||||||
*
|
|
||||||
* @note 额外说明:第二点不用显式处理,因为我们的方法是从找到第一个store开始;
|
|
||||||
* 第三点其实可以更激进一步地理解,即每次替换了load之后,它对应地那个store也可以删除了,同时注意这里不要使用preoptimize1进行处理,因为他们的级联关系是有用的:即用来求load的替换值;
|
|
||||||
* 同样地,我们这里不考虑数组和全局变量,因为这些变量不会被mem2reg优化,不过这里在计算value2DefBlocks时已经跳过了,所以不需要再显式处理了;
|
|
||||||
* 替换的操作采用了UD链进行简化和效率的提升
|
|
||||||
*
|
|
||||||
* @param [in] void
|
|
||||||
* @return 无返回值,但满足条件的情况下会对指令的操作数进行替换以及对指令进行删除
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::preOptimize3() -> void {
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
auto values = funcInfo->getValuesOfDefBlock();
|
|
||||||
for (auto val : values) {
|
|
||||||
auto sblocks = funcInfo->getDefBlocksByValue(val);
|
|
||||||
auto lblocks = funcInfo->getUseBlocksByValue(val);
|
|
||||||
if (sblocks.size() == 1 && lblocks.size() == 1 && *sblocks.begin() == *lblocks.begin()) {
|
|
||||||
auto block = *sblocks.begin();
|
|
||||||
auto &instrs = block->getInstructions();
|
|
||||||
auto it = std::find_if(instrs.begin(), instrs.end(),
|
|
||||||
[val](const auto &instr) { return instr->isStore() && instr->getOperand(1) == val; });
|
|
||||||
while (it != instrs.end()) {
|
|
||||||
auto propogationVal = (*it)->getOperand(0);
|
|
||||||
auto last = std::find_if(std::next(it), instrs.end(), [val](const auto &instr) {
|
|
||||||
return instr->isStore() && instr->getOperand(1) == val;
|
|
||||||
});
|
|
||||||
for (auto curit = std::next(it); curit != last;) {
|
|
||||||
if ((*curit)->isLoad() && (*curit)->getOperand(0) == val) {
|
|
||||||
curit->get()->replaceAllUsesWith(propogationVal);
|
|
||||||
usedelete(curit->get());
|
|
||||||
curit = instrs.erase(curit);
|
|
||||||
funcInfo->removeValue2UseBlock(val, block);
|
|
||||||
} else {
|
|
||||||
++curit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 替换了load之后,它对应地那个store也可以删除了
|
|
||||||
if (!(std::find_if(func->getEntryBlock()->getArguments().begin(), func->getEntryBlock()->getArguments().end(),
|
|
||||||
[val](const auto &instr) { return instr == val; }) !=
|
|
||||||
func->getEntryBlock()->getArguments().end()) &&
|
|
||||||
last == instrs.end()) {
|
|
||||||
usedelete(it->get());
|
|
||||||
it = instrs.erase(it);
|
|
||||||
if (funcInfo->removeValue2DefBlock(val, block)) {
|
|
||||||
auto bb = funcInfo->getAllocBlockByValue(val);
|
|
||||||
if (bb != nullptr) {
|
|
||||||
auto tofind = std::find_if(bb->getInstructions().begin(), bb->getInstructions().end(),
|
|
||||||
[val](const auto &instr) { return instr.get() == val; });
|
|
||||||
usedelete(tofind->get());
|
|
||||||
bb->getInstructions().erase(tofind);
|
|
||||||
funcInfo->removeValue2AllocBlock(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it = last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为所有变量的定义块集合的迭代支配边界插入phi结点
|
|
||||||
*
|
|
||||||
* insertPhi是mem2reg的核心之一,这里是对所有变量的迭代支配边界的phi结点插入,无参数也无返回值;
|
|
||||||
* 同样跳过对数组和全局变量的处理,因为这些变量不会被mem2reg优化,刚好这里在计算value2DefBlocks时已经跳过了,所以不需要再显式处理了;
|
|
||||||
* 同时我们进行了剪枝处理,只有在基本块入口活跃的变量,才插入phi函数
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::insertPhi() -> void {
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
const auto &vToDefB = funcInfo->getValue2DefBlocks();
|
|
||||||
for (const auto &map_pair : vToDefB) {
|
|
||||||
// 首先为每个变量找到迭代支配边界
|
|
||||||
auto val = map_pair.first;
|
|
||||||
auto blocks = funcInfo->getDefBlocksByValue(val);
|
|
||||||
auto itDFs = computeIterDf(blocks);
|
|
||||||
// 然后在每个变量相应的迭代支配边界上插入phi结点
|
|
||||||
for (auto basicBlock : itDFs) {
|
|
||||||
const auto &actiTable = activeVarAnalysis->getActiveTable();
|
|
||||||
auto dval = dynamic_cast<User *>(val);
|
|
||||||
// 只有在基本块入口活跃的变量,才插入phi函数
|
|
||||||
if (actiTable.at(basicBlock).front().count(dval) != 0U) {
|
|
||||||
pBuilder->createPhiInst(val->getType(), val, basicBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重命名
|
|
||||||
*
|
|
||||||
* 重命名是mem2reg的核心之二,这里是对单个块的重命名,递归实现
|
|
||||||
* 同样跳过对数组和全局变量的处理,因为这些变量不会被mem2reg优化
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::rename(BasicBlock *block, std::unordered_map<Value *, int> &count,
|
|
||||||
std::unordered_map<Value *, std::stack<Instruction *>> &stacks) -> void {
|
|
||||||
auto &instrs = block->getInstructions();
|
|
||||||
std::unordered_map<Value *, int> valPop;
|
|
||||||
// 第一大步:对块中的所有指令遍历处理
|
|
||||||
for (auto iter = instrs.begin(); iter != instrs.end();) {
|
|
||||||
auto instr = iter->get();
|
|
||||||
// 对于load指令,变量用最新的那个
|
|
||||||
if (instr->isLoad()) {
|
|
||||||
auto val = instr->getOperand(0);
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
if (!stacks[val].empty()) {
|
|
||||||
instr->replaceOperand(0, stacks[val].top());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 然后对于define的情况,看alloca、store和phi指令
|
|
||||||
if (instr->isDefine()) {
|
|
||||||
if (instr->isAlloca()) {
|
|
||||||
// alloca指令名字不改了,命名就按x,x_1,x_2...来就行
|
|
||||||
auto val = instr;
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
++valPop[val];
|
|
||||||
stacks[val].push(val);
|
|
||||||
++count[val];
|
|
||||||
}
|
|
||||||
} else if (instr->isPhi()) {
|
|
||||||
// Phi指令也是一条特殊的define指令
|
|
||||||
auto val = dynamic_cast<PhiInst *>(instr)->getMapVal();
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
auto i = count[val];
|
|
||||||
if (i == 0) {
|
|
||||||
// 对还未alloca就有phi的指令的处理,直接删除
|
|
||||||
usedelete(iter->get());
|
|
||||||
iter = instrs.erase(iter);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto newname = dynamic_cast<Instruction *>(val)->getName() + "_" + std::to_string(i);
|
|
||||||
auto newalloca = pBuilder->createAllocaInstWithoutInsert(val->getType(), {}, block, newname);
|
|
||||||
FunctionAnalysisInfo* ParentfuncInfo = controlFlowAnalysis->getFunctionAnalysisInfo(block->getParent());
|
|
||||||
ParentfuncInfo->addIndirectAlloca(newalloca);
|
|
||||||
instr->replaceOperand(0, newalloca);
|
|
||||||
++valPop[val];
|
|
||||||
stacks[val].push(newalloca);
|
|
||||||
++count[val];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// store指令看operand的名字,我们的实现是规定变量在operand的第二位,用一个新的alloca x_i代替
|
|
||||||
auto val = instr->getOperand(1);
|
|
||||||
if (!(isArr(val) || isGlobal(val))) {
|
|
||||||
auto i = count[val];
|
|
||||||
auto newname = dynamic_cast<Instruction *>(val)->getName() + "_" + std::to_string(i);
|
|
||||||
auto newalloca = pBuilder->createAllocaInstWithoutInsert(val->getType(), {}, block, newname);
|
|
||||||
FunctionAnalysisInfo* ParentfuncInfo = controlFlowAnalysis->getFunctionAnalysisInfo(block->getParent());
|
|
||||||
ParentfuncInfo->addIndirectAlloca(newalloca);
|
|
||||||
// block->getParent()->addIndirectAlloca(newalloca);
|
|
||||||
instr->replaceOperand(1, newalloca);
|
|
||||||
++valPop[val];
|
|
||||||
stacks[val].push(newalloca);
|
|
||||||
++count[val];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
// 第二大步:把所有CFG中的该块的successor的phi指令的相应operand确定
|
|
||||||
for (auto succ : block->getSuccessors()) {
|
|
||||||
auto position = getPredIndex(block, succ);
|
|
||||||
for (auto &instr : succ->getInstructions()) {
|
|
||||||
if (instr->isPhi()) {
|
|
||||||
auto val = dynamic_cast<PhiInst *>(instr.get())->getMapVal();
|
|
||||||
if (!stacks[val].empty()) {
|
|
||||||
instr->replaceOperand(position + 1, stacks[val].top());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// phi指令是添加在块的最前面的,因此过了之后就不会有phi了,直接break
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 第三大步:递归支配树的后继,支配树才能表示define-use关系
|
|
||||||
BlockAnalysisInfo* blockInfo = controlFlowAnalysis->getBlockAnalysisInfo(block);
|
|
||||||
for (auto sdom : blockInfo->getSdoms()) {
|
|
||||||
rename(sdom, count, stacks);
|
|
||||||
}
|
|
||||||
// 第四大步:遍历块中的所有指令,如果涉及到define,就弹栈,这一步是必要的,可以从递归的整体性来思考原因
|
|
||||||
// 注意这里count没清理,因为平级之间计数仍然是一直增加的,但是stack要清理,因为define-use关系来自直接
|
|
||||||
// 支配结点而不是平级之间,不清理栈会被污染
|
|
||||||
// 提前优化:知道变量对应的要弹栈的次数就可以了,没必要遍历所有instr.
|
|
||||||
for (auto val_pair : valPop) {
|
|
||||||
auto val = val_pair.first;
|
|
||||||
for (int i = 0; i < val_pair.second; ++i) {
|
|
||||||
stacks[val].pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重命名所有块
|
|
||||||
*
|
|
||||||
* 调用rename,自上而下实现所有rename
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::renameAll() -> void {
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (const auto &function : functions) {
|
|
||||||
auto func = function.second.get();
|
|
||||||
// 对于每个function都要SSA化,所以count和stacks定义在这并初始化
|
|
||||||
std::unordered_map<Value *, int> count;
|
|
||||||
std::unordered_map<Value *, std::stack<Instruction *>> stacks;
|
|
||||||
FunctionAnalysisInfo* funcInfo = controlFlowAnalysis->getFunctionAnalysisInfo(func);
|
|
||||||
for (const auto &map_pair : funcInfo->getValue2DefBlocks()) {
|
|
||||||
auto val = map_pair.first;
|
|
||||||
count[val] = 0;
|
|
||||||
}
|
|
||||||
rename(func->getEntryBlock(), count, stacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mem2reg,对外的接口
|
|
||||||
*
|
|
||||||
* 静态单一赋值 + mem2reg等pass的逻辑组合
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::mem2regPipeline() -> void {
|
|
||||||
// 首先进行mem2reg的前置分析
|
|
||||||
controlFlowAnalysis->clear();
|
|
||||||
controlFlowAnalysis->runControlFlowAnalysis();
|
|
||||||
// 活跃变量分析
|
|
||||||
activeVarAnalysis->clear();
|
|
||||||
dataFlowAnalysisUtils.addBackwardAnalyzer(activeVarAnalysis);
|
|
||||||
dataFlowAnalysisUtils.backwardAnalyze(pModule);
|
|
||||||
|
|
||||||
// 计算所有valueToBlocks的定义映射
|
|
||||||
computeValue2Blocks();
|
|
||||||
// SysYPrinter printer(pModule);
|
|
||||||
// 参考llvm的mem2reg遍,在插入phi结点之前,先做些优化
|
|
||||||
preOptimize1();
|
|
||||||
// printer.printIR();
|
|
||||||
preOptimize2();
|
|
||||||
// printer.printIR();
|
|
||||||
// 优化三 可能会针对局部变量优化而删除整个块的alloca/store
|
|
||||||
preOptimize3();
|
|
||||||
//再进行活跃变量分析
|
|
||||||
// 报错?
|
|
||||||
|
|
||||||
// printer.printIR();
|
|
||||||
dataFlowAnalysisUtils.backwardAnalyze(pModule);
|
|
||||||
// 为所有变量插入phi结点
|
|
||||||
insertPhi();
|
|
||||||
// 重命名
|
|
||||||
renameAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算块n是块s的第几个前驱
|
|
||||||
*
|
|
||||||
* helperfunction,没有返回值,但是会将dom和other的交集赋值给dom
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::getPredIndex(BasicBlock *n, BasicBlock *s) -> int {
|
|
||||||
int index = 0;
|
|
||||||
for (auto elem : s->getPredecessors()) {
|
|
||||||
if (elem == n) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
++index;
|
|
||||||
}
|
|
||||||
assert(index < static_cast<int>(s->getPredecessors().size()) && "n is not a predecessor of s.");
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断一个value是不是全局变量
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::isGlobal(Value *val) -> bool {
|
|
||||||
auto gval = dynamic_cast<GlobalValue *>(val);
|
|
||||||
return gval != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断一个value是不是数组
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::isArr(Value *val) -> bool {
|
|
||||||
auto aval = dynamic_cast<AllocaInst *>(val);
|
|
||||||
return aval != nullptr && aval->getNumDims() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除一个指令的operand对应的value的该条use
|
|
||||||
*/
|
|
||||||
auto Mem2Reg::usedelete(Instruction *instr) -> void {
|
|
||||||
for (auto &use : instr->getOperands()) {
|
|
||||||
auto val = use->getValue();
|
|
||||||
val->removeUse(use);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace sysy
|
|
||||||
@@ -18,7 +18,7 @@ bool isMemoryOp(RVOpcodes opcode) {
|
|||||||
|
|
||||||
RISCv64AsmPrinter::RISCv64AsmPrinter(MachineFunction* mfunc) : MFunc(mfunc) {}
|
RISCv64AsmPrinter::RISCv64AsmPrinter(MachineFunction* mfunc) : MFunc(mfunc) {}
|
||||||
|
|
||||||
void RISCv64AsmPrinter::run(std::ostream& os) {
|
void RISCv64AsmPrinter::run(std::ostream& os, bool debug) {
|
||||||
OS = &os;
|
OS = &os;
|
||||||
|
|
||||||
*OS << ".globl " << MFunc->getName() << "\n";
|
*OS << ".globl " << MFunc->getName() << "\n";
|
||||||
@@ -27,7 +27,7 @@ void RISCv64AsmPrinter::run(std::ostream& os) {
|
|||||||
printPrologue();
|
printPrologue();
|
||||||
|
|
||||||
for (auto& mbb : MFunc->getBlocks()) {
|
for (auto& mbb : MFunc->getBlocks()) {
|
||||||
printBasicBlock(mbb.get());
|
printBasicBlock(mbb.get(), debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ void RISCv64AsmPrinter::printPrologue() {
|
|||||||
*OS << " addi sp, sp, -" << aligned_stack_size << "\n";
|
*OS << " addi sp, sp, -" << aligned_stack_size << "\n";
|
||||||
*OS << " sd ra, " << (aligned_stack_size - 8) << "(sp)\n";
|
*OS << " sd ra, " << (aligned_stack_size - 8) << "(sp)\n";
|
||||||
*OS << " sd s0, " << (aligned_stack_size - 16) << "(sp)\n";
|
*OS << " sd s0, " << (aligned_stack_size - 16) << "(sp)\n";
|
||||||
*OS << " mv s0, sp\n";
|
*OS << " addi s0, sp, " << aligned_stack_size << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 忠实还原保存函数入口参数的逻辑
|
// 忠实还原保存函数入口参数的逻辑
|
||||||
@@ -73,24 +73,31 @@ void RISCv64AsmPrinter::printEpilogue() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb) {
|
void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb, bool debug) {
|
||||||
if (!mbb->getName().empty()) {
|
if (!mbb->getName().empty()) {
|
||||||
*OS << mbb->getName() << ":\n";
|
*OS << mbb->getName() << ":\n";
|
||||||
}
|
}
|
||||||
for (auto& instr : mbb->getInstructions()) {
|
for (auto& instr : mbb->getInstructions()) {
|
||||||
printInstruction(instr.get());
|
printInstruction(instr.get(), debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) {
|
void RISCv64AsmPrinter::printInstruction(MachineInstr* instr, bool debug) {
|
||||||
auto opcode = instr->getOpcode();
|
auto opcode = instr->getOpcode();
|
||||||
if (opcode == RVOpcodes::RET) {
|
if (opcode == RVOpcodes::RET) {
|
||||||
printEpilogue();
|
printEpilogue();
|
||||||
}
|
}
|
||||||
if (opcode != RVOpcodes::LABEL) {
|
|
||||||
*OS << " ";
|
if (opcode == RVOpcodes::LABEL) {
|
||||||
|
// 标签直接打印,不加缩进
|
||||||
|
printOperand(instr->getOperands()[0].get());
|
||||||
|
*OS << ":\n";
|
||||||
|
return; // 处理完毕,直接返回
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于所有非标签指令,先打印缩进
|
||||||
|
*OS << " ";
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case RVOpcodes::ADD: *OS << "add "; break; case RVOpcodes::ADDI: *OS << "addi "; break;
|
case RVOpcodes::ADD: *OS << "add "; break; case RVOpcodes::ADDI: *OS << "addi "; break;
|
||||||
case RVOpcodes::ADDW: *OS << "addw "; break; case RVOpcodes::ADDIW: *OS << "addiw "; break;
|
case RVOpcodes::ADDW: *OS << "addw "; break; case RVOpcodes::ADDIW: *OS << "addiw "; break;
|
||||||
@@ -126,13 +133,21 @@ void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) {
|
|||||||
case RVOpcodes::SNEZ: *OS << "snez "; break;
|
case RVOpcodes::SNEZ: *OS << "snez "; break;
|
||||||
case RVOpcodes::CALL: *OS << "call "; break;
|
case RVOpcodes::CALL: *OS << "call "; break;
|
||||||
case RVOpcodes::LABEL:
|
case RVOpcodes::LABEL:
|
||||||
printOperand(instr->getOperands()[0].get());
|
// printOperand(instr->getOperands()[0].get());
|
||||||
*OS << ":";
|
// *OS << ":";
|
||||||
break;
|
break;
|
||||||
case RVOpcodes::FRAME_LOAD:
|
case RVOpcodes::FRAME_LOAD:
|
||||||
|
// It should have been eliminated by RegAlloc
|
||||||
|
if (!debug) throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter");
|
||||||
|
*OS << "frame_load "; break;
|
||||||
case RVOpcodes::FRAME_STORE:
|
case RVOpcodes::FRAME_STORE:
|
||||||
// These should have been eliminated by RegAlloc
|
// It should have been eliminated by RegAlloc
|
||||||
throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter");
|
if (!debug) throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter");
|
||||||
|
*OS << "frame_store "; break;
|
||||||
|
case RVOpcodes::FRAME_ADDR:
|
||||||
|
// It should have been eliminated by RegAlloc
|
||||||
|
if (!debug) throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter");
|
||||||
|
*OS << "frame_addr "; break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Unknown opcode in AsmPrinter");
|
throw std::runtime_error("Unknown opcode in AsmPrinter");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ std::string RISCv64CodeGen::function_gen(Function* func) {
|
|||||||
RISCv64ISel isel;
|
RISCv64ISel isel;
|
||||||
std::unique_ptr<MachineFunction> mfunc = isel.runOnFunction(func);
|
std::unique_ptr<MachineFunction> mfunc = isel.runOnFunction(func);
|
||||||
|
|
||||||
|
std::stringstream ss1;
|
||||||
|
RISCv64AsmPrinter printer1(mfunc.get());
|
||||||
|
printer1.run(ss1, true);
|
||||||
|
|
||||||
// 阶段 2: 指令调度 (Instruction Scheduling)
|
// 阶段 2: 指令调度 (Instruction Scheduling)
|
||||||
PreRA_Scheduler scheduler;
|
PreRA_Scheduler scheduler;
|
||||||
scheduler.runOnMachineFunction(mfunc.get());
|
scheduler.runOnMachineFunction(mfunc.get());
|
||||||
@@ -81,7 +85,7 @@ std::string RISCv64CodeGen::function_gen(Function* func) {
|
|||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
RISCv64AsmPrinter printer(mfunc.get());
|
RISCv64AsmPrinter printer(mfunc.get());
|
||||||
printer.run(ss);
|
printer.run(ss);
|
||||||
|
if (DEBUG) ss << ss1.str(); // 将指令选择阶段的结果也包含在最终输出中
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cmath> // For std::fabs
|
#include <cmath> // For std::fabs
|
||||||
#include <limits> // For std::numeric_limits
|
#include <limits> // For std::numeric_limits
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace sysy {
|
namespace sysy {
|
||||||
|
|
||||||
@@ -82,6 +83,10 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
|
|||||||
CurMBB = bb_map.at(bb);
|
CurMBB = bb_map.at(bb);
|
||||||
auto dag = build_dag(bb);
|
auto dag = build_dag(bb);
|
||||||
|
|
||||||
|
if (DEBUG) { // 使用 DEBUG 宏或变量来控制是否打印
|
||||||
|
print_dag(dag, bb->getName());
|
||||||
|
}
|
||||||
|
|
||||||
std::map<Value*, DAGNode*> value_to_node;
|
std::map<Value*, DAGNode*> value_to_node;
|
||||||
for(const auto& node : dag) {
|
for(const auto& node : dag) {
|
||||||
if (node->value) {
|
if (node->value) {
|
||||||
@@ -92,6 +97,10 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
|
|||||||
std::set<DAGNode*> selected_nodes;
|
std::set<DAGNode*> selected_nodes;
|
||||||
std::function<void(DAGNode*)> select_recursive =
|
std::function<void(DAGNode*)> select_recursive =
|
||||||
[&](DAGNode* node) {
|
[&](DAGNode* node) {
|
||||||
|
if (DEEPDEBUG) {
|
||||||
|
std::cout << "[DEEPDEBUG] select_recursive: Visiting node with kind: " << node->kind
|
||||||
|
<< " (Value: " << (node->value ? node->value->getName() : "null") << ")" << std::endl;
|
||||||
|
}
|
||||||
if (!node || selected_nodes.count(node)) return;
|
if (!node || selected_nodes.count(node)) return;
|
||||||
for (auto operand : node->operands) {
|
for (auto operand : node->operands) {
|
||||||
select_recursive(operand);
|
select_recursive(operand);
|
||||||
@@ -118,24 +127,37 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心函数:为DAG节点选择并生成MachineInstr (忠实移植版)
|
// 核心函数:为DAG节点选择并生成MachineInstr (已修复和增强的完整版本)
|
||||||
void RISCv64ISel::selectNode(DAGNode* node) {
|
void RISCv64ISel::selectNode(DAGNode* node) {
|
||||||
|
// 调用者(select_recursive)已经保证了操作数节点会先于当前节点被选择。
|
||||||
|
// 因此,这里我们只处理当前节点。
|
||||||
|
|
||||||
switch (node->kind) {
|
switch (node->kind) {
|
||||||
|
// [V2优点] 采纳“延迟物化”(Late Materialization)思想。
|
||||||
|
// 这两个节点仅作为标记,不直接生成指令。它们的目的是在DAG中保留类型信息。
|
||||||
|
// 加载其值的责任,被转移给了使用它们的父节点(如STORE, BINARY等)。
|
||||||
|
// 这修复了之前版本中“使用未初始化虚拟寄存器”的根本性bug。
|
||||||
case DAGNode::CONSTANT:
|
case DAGNode::CONSTANT:
|
||||||
case DAGNode::ALLOCA_ADDR:
|
case DAGNode::ALLOCA_ADDR:
|
||||||
if (node->value) getVReg(node->value);
|
if (node->value) {
|
||||||
|
// 确保它有一个关联的虚拟寄存器即可,不生成代码。
|
||||||
|
getVReg(node->value);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DAGNode::LOAD: {
|
case DAGNode::LOAD: {
|
||||||
auto dest_vreg = getVReg(node->value);
|
auto dest_vreg = getVReg(node->value);
|
||||||
Value* ptr_val = node->operands[0]->value;
|
Value* ptr_val = node->operands[0]->value;
|
||||||
|
|
||||||
|
// [V1设计保留] 对于从栈变量加载,继续使用伪指令 FRAME_LOAD。
|
||||||
|
// 这种设计将栈帧布局的具体计算推迟到后续的 `eliminateFrameIndices` 阶段,保持了模块化。
|
||||||
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
|
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_LOAD);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_LOAD);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
|
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
|
||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
} else if (auto global = dynamic_cast<GlobalValue*>(ptr_val)) {
|
} else if (auto global = dynamic_cast<GlobalValue*>(ptr_val)) {
|
||||||
|
// 对于全局变量,先用 la 加载其地址,再用 lw 加载其值。
|
||||||
auto addr_vreg = getNewVReg();
|
auto addr_vreg = getNewVReg();
|
||||||
auto la = std::make_unique<MachineInstr>(RVOpcodes::LA);
|
auto la = std::make_unique<MachineInstr>(RVOpcodes::LA);
|
||||||
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
|
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
|
||||||
@@ -150,6 +172,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
));
|
));
|
||||||
CurMBB->addInstruction(std::move(lw));
|
CurMBB->addInstruction(std::move(lw));
|
||||||
} else {
|
} else {
|
||||||
|
// 对于已经在虚拟寄存器中的指针地址,直接通过该地址加载。
|
||||||
auto ptr_vreg = getVReg(ptr_val);
|
auto ptr_vreg = getVReg(ptr_val);
|
||||||
auto lw = std::make_unique<MachineInstr>(RVOpcodes::LW);
|
auto lw = std::make_unique<MachineInstr>(RVOpcodes::LW);
|
||||||
lw->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
lw->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
@@ -166,7 +189,13 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
Value* val_to_store = node->operands[0]->value;
|
Value* val_to_store = node->operands[0]->value;
|
||||||
Value* ptr_val = node->operands[1]->value;
|
Value* ptr_val = node->operands[1]->value;
|
||||||
|
|
||||||
|
// [V2优点] 在STORE节点内部负责加载作为源的常量。
|
||||||
|
// 如果要存储的值是一个常量,就在这里生成 `li` 指令加载它。
|
||||||
if (auto val_const = dynamic_cast<ConstantValue*>(val_to_store)) {
|
if (auto val_const = dynamic_cast<ConstantValue*>(val_to_store)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-BINARY: Found constant operand with value " << val_const->getInt()
|
||||||
|
<< ". Generating LI instruction." << std::endl;
|
||||||
|
}
|
||||||
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
li->addOperand(std::make_unique<RegOperand>(getVReg(val_const)));
|
li->addOperand(std::make_unique<RegOperand>(getVReg(val_const)));
|
||||||
li->addOperand(std::make_unique<ImmOperand>(val_const->getInt()));
|
li->addOperand(std::make_unique<ImmOperand>(val_const->getInt()));
|
||||||
@@ -174,13 +203,15 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
}
|
}
|
||||||
auto val_vreg = getVReg(val_to_store);
|
auto val_vreg = getVReg(val_to_store);
|
||||||
|
|
||||||
|
// [V1设计保留] 同样,对于向栈变量的存储,使用 FRAME_STORE 伪指令。
|
||||||
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
|
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_STORE);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_STORE);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(val_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(val_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
|
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
|
||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
} else if (auto global = dynamic_cast<GlobalValue*>(ptr_val)) {
|
} else if (auto global = dynamic_cast<GlobalValue*>(ptr_val)) {
|
||||||
auto addr_vreg = getNewVReg();
|
// 向全局变量存储。
|
||||||
|
auto addr_vreg = getNewVReg();
|
||||||
auto la = std::make_unique<MachineInstr>(RVOpcodes::LA);
|
auto la = std::make_unique<MachineInstr>(RVOpcodes::LA);
|
||||||
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
|
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
|
||||||
la->addOperand(std::make_unique<LabelOperand>(global->getName()));
|
la->addOperand(std::make_unique<LabelOperand>(global->getName()));
|
||||||
@@ -194,6 +225,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
));
|
));
|
||||||
CurMBB->addInstruction(std::move(sw));
|
CurMBB->addInstruction(std::move(sw));
|
||||||
} else {
|
} else {
|
||||||
|
// 向一个指针(存储在虚拟寄存器中)指向的地址存储。
|
||||||
auto ptr_vreg = getVReg(ptr_val);
|
auto ptr_vreg = getVReg(ptr_val);
|
||||||
auto sw = std::make_unique<MachineInstr>(RVOpcodes::SW);
|
auto sw = std::make_unique<MachineInstr>(RVOpcodes::SW);
|
||||||
sw->addOperand(std::make_unique<RegOperand>(val_vreg));
|
sw->addOperand(std::make_unique<RegOperand>(val_vreg));
|
||||||
@@ -211,36 +243,107 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
Value* lhs = bin->getLhs();
|
Value* lhs = bin->getLhs();
|
||||||
Value* rhs = bin->getRhs();
|
Value* rhs = bin->getRhs();
|
||||||
|
|
||||||
|
if (bin->getKind() == BinaryInst::kAdd) {
|
||||||
|
Value* base = nullptr;
|
||||||
|
Value* offset = nullptr;
|
||||||
|
|
||||||
|
// [修改] 扩展基地址的判断,使其可以识别 AllocaInst 或 GlobalValue
|
||||||
|
if (dynamic_cast<AllocaInst*>(lhs) || dynamic_cast<GlobalValue*>(lhs)) {
|
||||||
|
base = lhs;
|
||||||
|
offset = rhs;
|
||||||
|
} else if (dynamic_cast<AllocaInst*>(rhs) || dynamic_cast<GlobalValue*>(rhs)) {
|
||||||
|
base = rhs;
|
||||||
|
offset = lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果成功匹配到地址计算模式
|
||||||
|
if (base) {
|
||||||
|
// 1. 先为偏移量加载常量(如果它是常量的话)
|
||||||
|
if (auto const_offset = dynamic_cast<ConstantValue*>(offset)) {
|
||||||
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
|
li->addOperand(std::make_unique<RegOperand>(getVReg(const_offset)));
|
||||||
|
li->addOperand(std::make_unique<ImmOperand>(const_offset->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(li));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. [修改] 根据基地址的类型,生成不同的指令来获取基地址
|
||||||
|
auto base_addr_vreg = getNewVReg(); // 创建一个新的临时vreg来存放基地址
|
||||||
|
|
||||||
|
// 情况一:基地址是局部栈变量
|
||||||
|
if (auto alloca_base = dynamic_cast<AllocaInst*>(base)) {
|
||||||
|
auto frame_addr_instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_ADDR);
|
||||||
|
frame_addr_instr->addOperand(std::make_unique<RegOperand>(base_addr_vreg));
|
||||||
|
frame_addr_instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca_base)));
|
||||||
|
CurMBB->addInstruction(std::move(frame_addr_instr));
|
||||||
|
}
|
||||||
|
// 情况二:基地址是全局变量
|
||||||
|
else if (auto global_base = dynamic_cast<GlobalValue*>(base)) {
|
||||||
|
auto la_instr = std::make_unique<MachineInstr>(RVOpcodes::LA);
|
||||||
|
la_instr->addOperand(std::make_unique<RegOperand>(base_addr_vreg));
|
||||||
|
la_instr->addOperand(std::make_unique<LabelOperand>(global_base->getName()));
|
||||||
|
CurMBB->addInstruction(std::move(la_instr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 生成真正的add指令,计算最终地址(这部分逻辑保持不变)
|
||||||
|
auto final_addr_vreg = getVReg(bin); // 这是整个二元运算的结果vreg
|
||||||
|
auto offset_vreg = getVReg(offset);
|
||||||
|
auto add_instr = std::make_unique<MachineInstr>(RVOpcodes::ADD); // 指针运算是64位
|
||||||
|
add_instr->addOperand(std::make_unique<RegOperand>(final_addr_vreg));
|
||||||
|
add_instr->addOperand(std::make_unique<RegOperand>(base_addr_vreg));
|
||||||
|
add_instr->addOperand(std::make_unique<RegOperand>(offset_vreg));
|
||||||
|
CurMBB->addInstruction(std::move(add_instr));
|
||||||
|
|
||||||
|
return; // 地址计算处理完毕,直接返回
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// [V2优点] 在BINARY节点内部按需加载常量操作数。
|
||||||
auto load_val_if_const = [&](Value* val) {
|
auto load_val_if_const = [&](Value* val) {
|
||||||
if (auto c = dynamic_cast<ConstantValue*>(val)) {
|
if (auto c = dynamic_cast<ConstantValue*>(val)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-BINARY: Found constant operand with value " << c->getInt()
|
||||||
|
<< ". Generating LI instruction." << std::endl;
|
||||||
|
}
|
||||||
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
li->addOperand(std::make_unique<RegOperand>(getVReg(c)));
|
li->addOperand(std::make_unique<RegOperand>(getVReg(c)));
|
||||||
li->addOperand(std::make_unique<ImmOperand>(c->getInt()));
|
li->addOperand(std::make_unique<ImmOperand>(c->getInt()));
|
||||||
CurMBB->addInstruction(std::move(li));
|
CurMBB->addInstruction(std::move(li));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
load_val_if_const(lhs);
|
|
||||||
load_val_if_const(rhs);
|
|
||||||
|
|
||||||
auto dest_vreg = getVReg(bin);
|
// 检查是否能应用立即数优化。
|
||||||
auto lhs_vreg = getVReg(lhs);
|
bool rhs_is_imm_opt = false;
|
||||||
auto rhs_vreg = getVReg(rhs);
|
if (auto rhs_const = dynamic_cast<ConstantValue*>(rhs)) {
|
||||||
|
if (bin->getKind() == BinaryInst::kAdd && rhs_const->getInt() >= -2048 && rhs_const->getInt() < 2048) {
|
||||||
if (bin->getKind() == BinaryInst::kAdd) {
|
rhs_is_imm_opt = true;
|
||||||
if (auto rhs_const = dynamic_cast<ConstantValue*>(rhs)) {
|
|
||||||
if (rhs_const->getInt() >= -2048 && rhs_const->getInt() < 2048) {
|
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::ADDIW);
|
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
|
||||||
instr->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
|
||||||
instr->addOperand(std::make_unique<ImmOperand>(rhs_const->getInt()));
|
|
||||||
CurMBB->addInstruction(std::move(instr));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 仅在不能作为立即数操作数时才需要提前加载。
|
||||||
|
load_val_if_const(lhs);
|
||||||
|
if (!rhs_is_imm_opt) {
|
||||||
|
load_val_if_const(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dest_vreg = getVReg(bin);
|
||||||
|
auto lhs_vreg = getVReg(lhs);
|
||||||
|
|
||||||
|
// [V2优点] 融合 ADDIW 优化。
|
||||||
|
if (rhs_is_imm_opt) {
|
||||||
|
auto rhs_const = dynamic_cast<ConstantValue*>(rhs);
|
||||||
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::ADDIW);
|
||||||
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
|
instr->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
||||||
|
instr->addOperand(std::make_unique<ImmOperand>(rhs_const->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(instr));
|
||||||
|
return; // 指令已生成,直接返回。
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rhs_vreg = getVReg(rhs);
|
||||||
|
|
||||||
switch (bin->getKind()) {
|
switch (bin->getKind()) {
|
||||||
case BinaryInst::kAdd: {
|
case BinaryInst::kAdd: {
|
||||||
|
// 区分指针运算(64位)和整数运算(32位)。
|
||||||
RVOpcodes opcode = (lhs->getType()->isPointer() || rhs->getType()->isPointer()) ? RVOpcodes::ADD : RVOpcodes::ADDW;
|
RVOpcodes opcode = (lhs->getType()->isPointer() || rhs->getType()->isPointer()) ? RVOpcodes::ADD : RVOpcodes::ADDW;
|
||||||
auto instr = std::make_unique<MachineInstr>(opcode);
|
auto instr = std::make_unique<MachineInstr>(opcode);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
@@ -281,7 +384,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpEQ: {
|
case BinaryInst::kICmpEQ: { // 等于 (a == b) -> (subw; seqz)
|
||||||
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
||||||
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
||||||
@@ -294,7 +397,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(seqz));
|
CurMBB->addInstruction(std::move(seqz));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpNE: {
|
case BinaryInst::kICmpNE: { // 不等于 (a != b) -> (subw; snez)
|
||||||
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
||||||
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
||||||
@@ -307,7 +410,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(snez));
|
CurMBB->addInstruction(std::move(snez));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpLT: {
|
case BinaryInst::kICmpLT: { // 小于 (a < b) -> slt
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
||||||
@@ -315,7 +418,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpGT: {
|
case BinaryInst::kICmpGT: { // 大于 (a > b) -> (b < a) -> slt
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(rhs_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(rhs_vreg));
|
||||||
@@ -323,7 +426,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpLE: {
|
case BinaryInst::kICmpLE: { // 小于等于 (a <= b) -> !(b < a) -> (slt; xori)
|
||||||
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
||||||
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
slt->addOperand(std::make_unique<RegOperand>(rhs_vreg));
|
slt->addOperand(std::make_unique<RegOperand>(rhs_vreg));
|
||||||
@@ -337,7 +440,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(xori));
|
CurMBB->addInstruction(std::move(xori));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BinaryInst::kICmpGE: {
|
case BinaryInst::kICmpGE: { // 大于等于 (a >= b) -> !(a < b) -> (slt; xori)
|
||||||
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
|
||||||
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
slt->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
slt->addOperand(std::make_unique<RegOperand>(lhs_vreg));
|
||||||
@@ -363,7 +466,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
auto src_vreg = getVReg(unary->getOperand());
|
auto src_vreg = getVReg(unary->getOperand());
|
||||||
|
|
||||||
switch (unary->getKind()) {
|
switch (unary->getKind()) {
|
||||||
case UnaryInst::kNeg: {
|
case UnaryInst::kNeg: { // 取负: 0 - src
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
|
instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
|
||||||
@@ -371,7 +474,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(instr));
|
CurMBB->addInstruction(std::move(instr));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UnaryInst::kNot: {
|
case UnaryInst::kNot: { // 逻辑非: src == 0 ? 1 : 0
|
||||||
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SEQZ);
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SEQZ);
|
||||||
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
instr->addOperand(std::make_unique<RegOperand>(src_vreg));
|
instr->addOperand(std::make_unique<RegOperand>(src_vreg));
|
||||||
@@ -386,7 +489,10 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
|
|
||||||
case DAGNode::CALL: {
|
case DAGNode::CALL: {
|
||||||
auto call = dynamic_cast<CallInst*>(node->value);
|
auto call = dynamic_cast<CallInst*>(node->value);
|
||||||
for (size_t i = 0; i < node->operands.size() && i < 8; ++i) {
|
// 处理函数参数,放入a0-a7物理寄存器
|
||||||
|
size_t num_operands = node->operands.size();
|
||||||
|
size_t reg_arg_count = std::min(num_operands, (size_t)8);
|
||||||
|
for (size_t i = 0; i < reg_arg_count; ++i) {
|
||||||
DAGNode* arg_node = node->operands[i];
|
DAGNode* arg_node = node->operands[i];
|
||||||
auto arg_preg = static_cast<PhysicalReg>(static_cast<int>(PhysicalReg::A0) + i);
|
auto arg_preg = static_cast<PhysicalReg>(static_cast<int>(PhysicalReg::A0) + i);
|
||||||
|
|
||||||
@@ -405,11 +511,64 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(mv));
|
CurMBB->addInstruction(std::move(mv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (num_operands > 8) {
|
||||||
|
size_t stack_arg_count = num_operands - 8;
|
||||||
|
int stack_space = stack_arg_count * 8; // RV64中每个参数槽位8字节
|
||||||
|
|
||||||
|
// 2a. 在栈上分配空间
|
||||||
|
auto alloc_instr = std::make_unique<MachineInstr>(RVOpcodes::ADDI);
|
||||||
|
alloc_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::SP));
|
||||||
|
alloc_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::SP));
|
||||||
|
alloc_instr->addOperand(std::make_unique<ImmOperand>(-stack_space));
|
||||||
|
CurMBB->addInstruction(std::move(alloc_instr));
|
||||||
|
|
||||||
|
// 2b. 存储每个栈参数
|
||||||
|
for (size_t i = 8; i < num_operands; ++i) {
|
||||||
|
DAGNode* arg_node = node->operands[i];
|
||||||
|
unsigned src_vreg;
|
||||||
|
|
||||||
|
// 准备源寄存器
|
||||||
|
if (arg_node->kind == DAGNode::CONSTANT) {
|
||||||
|
// 如果是常量,先加载到临时寄存器
|
||||||
|
src_vreg = getNewVReg();
|
||||||
|
auto const_val = dynamic_cast<ConstantValue*>(arg_node->value);
|
||||||
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
|
li->addOperand(std::make_unique<RegOperand>(src_vreg));
|
||||||
|
li->addOperand(std::make_unique<ImmOperand>(const_val->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(li));
|
||||||
|
} else {
|
||||||
|
src_vreg = getVReg(arg_node->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算在栈上的偏移量
|
||||||
|
int offset = (i - 8) * 8;
|
||||||
|
|
||||||
|
// 生成 sd 指令
|
||||||
|
auto sd_instr = std::make_unique<MachineInstr>(RVOpcodes::SD);
|
||||||
|
sd_instr->addOperand(std::make_unique<RegOperand>(src_vreg));
|
||||||
|
sd_instr->addOperand(std::make_unique<MemOperand>(
|
||||||
|
std::make_unique<RegOperand>(PhysicalReg::SP),
|
||||||
|
std::make_unique<ImmOperand>(offset)
|
||||||
|
));
|
||||||
|
CurMBB->addInstruction(std::move(sd_instr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto call_instr = std::make_unique<MachineInstr>(RVOpcodes::CALL);
|
auto call_instr = std::make_unique<MachineInstr>(RVOpcodes::CALL);
|
||||||
call_instr->addOperand(std::make_unique<LabelOperand>(call->getCallee()->getName()));
|
call_instr->addOperand(std::make_unique<LabelOperand>(call->getCallee()->getName()));
|
||||||
CurMBB->addInstruction(std::move(call_instr));
|
CurMBB->addInstruction(std::move(call_instr));
|
||||||
|
|
||||||
|
if (num_operands > 8) {
|
||||||
|
size_t stack_arg_count = num_operands - 8;
|
||||||
|
int stack_space = stack_arg_count * 8;
|
||||||
|
|
||||||
|
auto dealloc_instr = std::make_unique<MachineInstr>(RVOpcodes::ADDI);
|
||||||
|
dealloc_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::SP));
|
||||||
|
dealloc_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::SP));
|
||||||
|
dealloc_instr->addOperand(std::make_unique<ImmOperand>(stack_space));
|
||||||
|
CurMBB->addInstruction(std::move(dealloc_instr));
|
||||||
|
}
|
||||||
|
// 处理返回值,从a0移动到目标虚拟寄存器
|
||||||
if (!call->getType()->isVoid()) {
|
if (!call->getType()->isVoid()) {
|
||||||
auto mv_instr = std::make_unique<MachineInstr>(RVOpcodes::MV);
|
auto mv_instr = std::make_unique<MachineInstr>(RVOpcodes::MV);
|
||||||
mv_instr->addOperand(std::make_unique<RegOperand>(getVReg(call)));
|
mv_instr->addOperand(std::make_unique<RegOperand>(getVReg(call)));
|
||||||
@@ -423,6 +582,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
auto ret_inst_ir = dynamic_cast<ReturnInst*>(node->value);
|
auto ret_inst_ir = dynamic_cast<ReturnInst*>(node->value);
|
||||||
if (ret_inst_ir && ret_inst_ir->hasReturnValue()) {
|
if (ret_inst_ir && ret_inst_ir->hasReturnValue()) {
|
||||||
Value* ret_val = ret_inst_ir->getReturnValue();
|
Value* ret_val = ret_inst_ir->getReturnValue();
|
||||||
|
// [V2优点] 在RETURN节点内加载常量返回值
|
||||||
if (auto const_val = dynamic_cast<ConstantValue*>(ret_val)) {
|
if (auto const_val = dynamic_cast<ConstantValue*>(ret_val)) {
|
||||||
auto li_instr = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
auto li_instr = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
li_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::A0));
|
li_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::A0));
|
||||||
@@ -435,36 +595,121 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(mv_instr));
|
CurMBB->addInstruction(std::move(mv_instr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// [V1设计保留] 函数尾声(epilogue)不由RETURN节点生成,
|
||||||
|
// 而是由后续的AsmPrinter或其它Pass统一处理,这是一种常见且有效的模块化设计。
|
||||||
auto ret_mi = std::make_unique<MachineInstr>(RVOpcodes::RET);
|
auto ret_mi = std::make_unique<MachineInstr>(RVOpcodes::RET);
|
||||||
CurMBB->addInstruction(std::move(ret_mi));
|
CurMBB->addInstruction(std::move(ret_mi));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case DAGNode::BRANCH: {
|
case DAGNode::BRANCH: {
|
||||||
if (auto cond_br = dynamic_cast<CondBrInst*>(node->value)) {
|
// 处理条件分支
|
||||||
|
if (auto cond_br = dynamic_cast<CondBrInst*>(node->value)) {
|
||||||
|
Value* condition = cond_br->getCondition();
|
||||||
|
auto then_bb_name = cond_br->getThenBlock()->getName();
|
||||||
|
auto else_bb_name = cond_br->getElseBlock()->getName();
|
||||||
|
|
||||||
|
// [优化] 检查分支条件是否为编译期常量
|
||||||
|
if (auto const_cond = dynamic_cast<ConstantValue*>(condition)) {
|
||||||
|
// 如果条件是常量,直接生成一个无条件跳转J,而不是BNE
|
||||||
|
if (const_cond->getInt() != 0) { // 条件为 true
|
||||||
|
auto j_instr = std::make_unique<MachineInstr>(RVOpcodes::J);
|
||||||
|
j_instr->addOperand(std::make_unique<LabelOperand>(then_bb_name));
|
||||||
|
CurMBB->addInstruction(std::move(j_instr));
|
||||||
|
} else { // 条件为 false
|
||||||
|
auto j_instr = std::make_unique<MachineInstr>(RVOpcodes::J);
|
||||||
|
j_instr->addOperand(std::make_unique<LabelOperand>(else_bb_name));
|
||||||
|
CurMBB->addInstruction(std::move(j_instr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果条件不是常量,则执行标准流程
|
||||||
|
else {
|
||||||
|
// [修复] 为条件变量生成加载指令(如果它是常量的话,尽管上面已经处理了)
|
||||||
|
// 这一步是为了逻辑完整,以防有其他类型的常量没有被捕获
|
||||||
|
if (auto const_val = dynamic_cast<ConstantValue*>(condition)) {
|
||||||
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
|
li->addOperand(std::make_unique<RegOperand>(getVReg(const_val)));
|
||||||
|
li->addOperand(std::make_unique<ImmOperand>(const_val->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(li));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cond_vreg = getVReg(condition);
|
||||||
|
|
||||||
|
// 生成 bne cond, zero, then_label (如果cond不为0,则跳转到then)
|
||||||
auto br_instr = std::make_unique<MachineInstr>(RVOpcodes::BNE);
|
auto br_instr = std::make_unique<MachineInstr>(RVOpcodes::BNE);
|
||||||
br_instr->addOperand(std::make_unique<RegOperand>(getVReg(cond_br->getCondition())));
|
br_instr->addOperand(std::make_unique<RegOperand>(cond_vreg));
|
||||||
br_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
|
br_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
|
||||||
br_instr->addOperand(std::make_unique<LabelOperand>(cond_br->getThenBlock()->getName()));
|
br_instr->addOperand(std::make_unique<LabelOperand>(then_bb_name));
|
||||||
CurMBB->addInstruction(std::move(br_instr));
|
CurMBB->addInstruction(std::move(br_instr));
|
||||||
} else if (auto uncond_br = dynamic_cast<UncondBrInst*>(node->value)) {
|
|
||||||
|
// 为else分支生成无条件跳转 (后续Pass可以优化掉不必要的跳转)
|
||||||
auto j_instr = std::make_unique<MachineInstr>(RVOpcodes::J);
|
auto j_instr = std::make_unique<MachineInstr>(RVOpcodes::J);
|
||||||
j_instr->addOperand(std::make_unique<LabelOperand>(uncond_br->getBlock()->getName()));
|
j_instr->addOperand(std::make_unique<LabelOperand>(else_bb_name));
|
||||||
CurMBB->addInstruction(std::move(j_instr));
|
CurMBB->addInstruction(std::move(j_instr));
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
// 处理无条件分支
|
||||||
|
else if (auto uncond_br = dynamic_cast<UncondBrInst*>(node->value)) {
|
||||||
|
auto j_instr = std::make_unique<MachineInstr>(RVOpcodes::J);
|
||||||
|
j_instr->addOperand(std::make_unique<LabelOperand>(uncond_br->getBlock()->getName()));
|
||||||
|
CurMBB->addInstruction(std::move(j_instr));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case DAGNode::MEMSET: {
|
case DAGNode::MEMSET: {
|
||||||
|
// [V1设计保留] Memset的核心展开逻辑在虚拟寄存器层面是正确的,无需修改。
|
||||||
|
// 之前的bug是由于其输入(地址、值、大小)的虚拟寄存器未被正确初始化。
|
||||||
|
// 在修复了CONSTANT/ALLOCA_ADDR的加载问题后,此处的逻辑现在可以正常工作。
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-MEMSET: Processing MEMSET node." << std::endl;
|
||||||
|
}
|
||||||
auto memset = dynamic_cast<MemsetInst*>(node->value);
|
auto memset = dynamic_cast<MemsetInst*>(node->value);
|
||||||
|
Value* val_to_set = memset->getValue();
|
||||||
|
Value* size_to_set = memset->getSize();
|
||||||
|
Value* ptr_val = memset->getPointer();
|
||||||
|
auto dest_addr_vreg = getVReg(ptr_val);
|
||||||
|
|
||||||
|
if (auto const_val = dynamic_cast<ConstantValue*>(val_to_set)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-MEMSET: Found constant 'value' operand (" << const_val->getInt() << "). Generating LI." << std::endl;
|
||||||
|
}
|
||||||
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
|
li->addOperand(std::make_unique<RegOperand>(getVReg(const_val)));
|
||||||
|
li->addOperand(std::make_unique<ImmOperand>(const_val->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(li));
|
||||||
|
}
|
||||||
|
if (auto const_size = dynamic_cast<ConstantValue*>(size_to_set)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-MEMSET: Found constant 'size' operand (" << const_size->getInt() << "). Generating LI." << std::endl;
|
||||||
|
}
|
||||||
|
auto li = std::make_unique<MachineInstr>(RVOpcodes::LI);
|
||||||
|
li->addOperand(std::make_unique<RegOperand>(getVReg(const_size)));
|
||||||
|
li->addOperand(std::make_unique<ImmOperand>(const_size->getInt()));
|
||||||
|
CurMBB->addInstruction(std::move(li));
|
||||||
|
}
|
||||||
|
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] selectNode-MEMSET: Found 'pointer' operand is an AllocaInst. Generating FRAME_ADDR." << std::endl;
|
||||||
|
}
|
||||||
|
// 生成新的伪指令来获取栈地址
|
||||||
|
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_ADDR);
|
||||||
|
instr->addOperand(std::make_unique<RegOperand>(dest_addr_vreg)); // 目标虚拟寄存器
|
||||||
|
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca))); // 源AllocaInst
|
||||||
|
CurMBB->addInstruction(std::move(instr));
|
||||||
|
}
|
||||||
auto r_dest_addr = getVReg(memset->getPointer());
|
auto r_dest_addr = getVReg(memset->getPointer());
|
||||||
auto r_num_bytes = getVReg(memset->getSize());
|
auto r_num_bytes = getVReg(memset->getSize());
|
||||||
auto r_value_byte = getVReg(memset->getValue());
|
auto r_value_byte = getVReg(memset->getValue());
|
||||||
|
|
||||||
|
// 为memset内部逻辑创建新的临时虚拟寄存器
|
||||||
auto r_counter = getNewVReg();
|
auto r_counter = getNewVReg();
|
||||||
auto r_end_addr = getNewVReg();
|
auto r_end_addr = getNewVReg();
|
||||||
auto r_current_addr = getNewVReg();
|
auto r_current_addr = getNewVReg();
|
||||||
auto r_temp_val = getNewVReg();
|
auto r_temp_val = getNewVReg();
|
||||||
|
|
||||||
|
// 定义一系列lambda表达式来简化指令创建
|
||||||
auto add_instr = [&](RVOpcodes op, unsigned rd, unsigned rs1, unsigned rs2) {
|
auto add_instr = [&](RVOpcodes op, unsigned rd, unsigned rs1, unsigned rs2) {
|
||||||
auto i = std::make_unique<MachineInstr>(op);
|
auto i = std::make_unique<MachineInstr>(op);
|
||||||
i->addOperand(std::make_unique<RegOperand>(rd));
|
i->addOperand(std::make_unique<RegOperand>(rd));
|
||||||
@@ -503,12 +748,14 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(i));
|
CurMBB->addInstruction(std::move(i));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生成唯一的循环标签
|
||||||
int unique_id = this->local_label_counter++;
|
int unique_id = this->local_label_counter++;
|
||||||
std::string loop_start_label = MFunc->getName() + "_memset_loop_start_" + std::to_string(unique_id);
|
std::string loop_start_label = MFunc->getName() + "_memset_loop_start_" + std::to_string(unique_id);
|
||||||
std::string loop_end_label = MFunc->getName() + "_memset_loop_end_" + std::to_string(unique_id);
|
std::string loop_end_label = MFunc->getName() + "_memset_loop_end_" + std::to_string(unique_id);
|
||||||
std::string remainder_label = MFunc->getName() + "_memset_remainder_" + std::to_string(unique_id);
|
std::string remainder_label = MFunc->getName() + "_memset_remainder_" + std::to_string(unique_id);
|
||||||
std::string done_label = MFunc->getName() + "_memset_done_" + std::to_string(unique_id);
|
std::string done_label = MFunc->getName() + "_memset_done_" + std::to_string(unique_id);
|
||||||
|
|
||||||
|
// 构造64位的填充值
|
||||||
addi_instr(RVOpcodes::ANDI, r_temp_val, r_value_byte, 255);
|
addi_instr(RVOpcodes::ANDI, r_temp_val, r_value_byte, 255);
|
||||||
addi_instr(RVOpcodes::SLLI, r_value_byte, r_temp_val, 8);
|
addi_instr(RVOpcodes::SLLI, r_value_byte, r_temp_val, 8);
|
||||||
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
||||||
@@ -516,6 +763,8 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
||||||
addi_instr(RVOpcodes::SLLI, r_value_byte, r_temp_val, 32);
|
addi_instr(RVOpcodes::SLLI, r_value_byte, r_temp_val, 32);
|
||||||
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
|
||||||
|
|
||||||
|
// 计算循环边界
|
||||||
add_instr(RVOpcodes::ADD, r_end_addr, r_dest_addr, r_num_bytes);
|
add_instr(RVOpcodes::ADD, r_end_addr, r_dest_addr, r_num_bytes);
|
||||||
auto mv = std::make_unique<MachineInstr>(RVOpcodes::MV);
|
auto mv = std::make_unique<MachineInstr>(RVOpcodes::MV);
|
||||||
mv->addOperand(std::make_unique<RegOperand>(r_current_addr));
|
mv->addOperand(std::make_unique<RegOperand>(r_current_addr));
|
||||||
@@ -523,17 +772,22 @@ void RISCv64ISel::selectNode(DAGNode* node) {
|
|||||||
CurMBB->addInstruction(std::move(mv));
|
CurMBB->addInstruction(std::move(mv));
|
||||||
addi_instr(RVOpcodes::ANDI, r_counter, r_num_bytes, -8);
|
addi_instr(RVOpcodes::ANDI, r_counter, r_num_bytes, -8);
|
||||||
add_instr(RVOpcodes::ADD, r_counter, r_dest_addr, r_counter);
|
add_instr(RVOpcodes::ADD, r_counter, r_dest_addr, r_counter);
|
||||||
|
|
||||||
|
// 8字节主循环
|
||||||
label_instr(loop_start_label);
|
label_instr(loop_start_label);
|
||||||
branch_instr(RVOpcodes::BGEU, r_current_addr, r_counter, loop_end_label);
|
branch_instr(RVOpcodes::BGEU, r_current_addr, r_counter, loop_end_label);
|
||||||
store_instr(RVOpcodes::SD, r_temp_val, r_current_addr, 0);
|
store_instr(RVOpcodes::SD, r_temp_val, r_current_addr, 0);
|
||||||
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 8);
|
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 8);
|
||||||
jump_instr(loop_start_label);
|
jump_instr(loop_start_label);
|
||||||
|
|
||||||
|
// 1字节收尾循环
|
||||||
label_instr(loop_end_label);
|
label_instr(loop_end_label);
|
||||||
label_instr(remainder_label);
|
label_instr(remainder_label);
|
||||||
branch_instr(RVOpcodes::BGEU, r_current_addr, r_end_addr, done_label);
|
branch_instr(RVOpcodes::BGEU, r_current_addr, r_end_addr, done_label);
|
||||||
store_instr(RVOpcodes::SB, r_temp_val, r_current_addr, 0);
|
store_instr(RVOpcodes::SB, r_temp_val, r_current_addr, 0);
|
||||||
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 1);
|
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 1);
|
||||||
jump_instr(remainder_label);
|
jump_instr(remainder_label);
|
||||||
|
|
||||||
label_instr(done_label);
|
label_instr(done_label);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -590,6 +844,12 @@ std::vector<std::unique_ptr<RISCv64ISel::DAGNode>> RISCv64ISel::build_dag(BasicB
|
|||||||
memset_node->operands.push_back(get_operand_node(memset->getBegin(), value_to_node, nodes_storage));
|
memset_node->operands.push_back(get_operand_node(memset->getBegin(), value_to_node, nodes_storage));
|
||||||
memset_node->operands.push_back(get_operand_node(memset->getSize(), value_to_node, nodes_storage));
|
memset_node->operands.push_back(get_operand_node(memset->getSize(), value_to_node, nodes_storage));
|
||||||
memset_node->operands.push_back(get_operand_node(memset->getValue(), value_to_node, nodes_storage));
|
memset_node->operands.push_back(get_operand_node(memset->getValue(), value_to_node, nodes_storage));
|
||||||
|
if (DEBUG) {
|
||||||
|
std::cout << "[DEBUG] build_dag: Created MEMSET node for: " << memset->getName() << std::endl;
|
||||||
|
for (size_t i = 0; i < memset_node->operands.size(); ++i) {
|
||||||
|
std::cout << " -> Operand " << i << " has kind: " << memset_node->operands[i]->kind << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (auto load = dynamic_cast<LoadInst*>(inst)) {
|
} else if (auto load = dynamic_cast<LoadInst*>(inst)) {
|
||||||
auto load_node = create_node(DAGNode::LOAD, load, value_to_node, nodes_storage);
|
auto load_node = create_node(DAGNode::LOAD, load, value_to_node, nodes_storage);
|
||||||
load_node->operands.push_back(get_operand_node(load->getPointer(), value_to_node, nodes_storage));
|
load_node->operands.push_back(get_operand_node(load->getPointer(), value_to_node, nodes_storage));
|
||||||
@@ -632,4 +892,98 @@ std::vector<std::unique_ptr<RISCv64ISel::DAGNode>> RISCv64ISel::build_dag(BasicB
|
|||||||
return nodes_storage;
|
return nodes_storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [新] 打印DAG图以供调试的辅助函数
|
||||||
|
void RISCv64ISel::print_dag(const std::vector<std::unique_ptr<DAGNode>>& dag, const std::string& bb_name) {
|
||||||
|
// 检查是否有DEBUG宏或者全局变量,避免在非调试模式下打印
|
||||||
|
// if (!DEBUG) return;
|
||||||
|
|
||||||
|
std::cerr << "=== DAG for Basic Block: " << bb_name << " ===\n";
|
||||||
|
std::set<DAGNode*> visited;
|
||||||
|
|
||||||
|
// 为节点分配临时ID,方便阅读
|
||||||
|
std::map<DAGNode*, int> node_to_id;
|
||||||
|
int current_id = 0;
|
||||||
|
for (const auto& node_ptr : dag) {
|
||||||
|
node_to_id[node_ptr.get()] = current_id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将NodeKind枚举转换为字符串的辅助函数
|
||||||
|
auto get_kind_string = [](DAGNode::NodeKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case DAGNode::CONSTANT: return "CONSTANT";
|
||||||
|
case DAGNode::LOAD: return "LOAD";
|
||||||
|
case DAGNode::STORE: return "STORE";
|
||||||
|
case DAGNode::BINARY: return "BINARY";
|
||||||
|
case DAGNode::CALL: return "CALL";
|
||||||
|
case DAGNode::RETURN: return "RETURN";
|
||||||
|
case DAGNode::BRANCH: return "BRANCH";
|
||||||
|
case DAGNode::ALLOCA_ADDR: return "ALLOCA_ADDR";
|
||||||
|
case DAGNode::UNARY: return "UNARY";
|
||||||
|
case DAGNode::MEMSET: return "MEMSET";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归打印节点的lambda表达式
|
||||||
|
std::function<void(DAGNode*, int)> print_node =
|
||||||
|
[&](DAGNode* node, int indent) {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
std::string current_indent(indent, ' ');
|
||||||
|
int node_id = node_to_id.count(node) ? node_to_id[node] : -1;
|
||||||
|
|
||||||
|
std::cerr << current_indent << "Node#" << node_id << ": " << get_kind_string(node->kind);
|
||||||
|
|
||||||
|
// 尝试打印关联的虚拟寄存器
|
||||||
|
if (node->value && vreg_map.count(node->value)) {
|
||||||
|
std::cerr << " (vreg: %vreg" << vreg_map.at(node->value) << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印关联的IR Value信息
|
||||||
|
if (node->value) {
|
||||||
|
std::cerr << " [";
|
||||||
|
if (auto inst = dynamic_cast<Instruction*>(node->value)) {
|
||||||
|
std::cerr << inst->getKindString();
|
||||||
|
if (!inst->getName().empty()) {
|
||||||
|
std::cerr << "(" << inst->getName() << ")";
|
||||||
|
}
|
||||||
|
} else if (auto constant = dynamic_cast<ConstantValue*>(node->value)) {
|
||||||
|
std::cerr << "Const(" << constant->getInt() << ")";
|
||||||
|
} else if (auto global = dynamic_cast<GlobalValue*>(node->value)) {
|
||||||
|
std::cerr << "Global(" << global->getName() << ")";
|
||||||
|
} else if (auto alloca = dynamic_cast<AllocaInst*>(node->value)) {
|
||||||
|
std::cerr << "Alloca(" << alloca->getName() << ")";
|
||||||
|
}
|
||||||
|
std::cerr << "]";
|
||||||
|
}
|
||||||
|
std::cerr << "\n";
|
||||||
|
|
||||||
|
if (visited.count(node)) {
|
||||||
|
std::cerr << current_indent << " (已打印过子节点)\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.insert(node);
|
||||||
|
|
||||||
|
if (!node->operands.empty()) {
|
||||||
|
std::cerr << current_indent << " Operands:\n";
|
||||||
|
for (auto operand : node->operands) {
|
||||||
|
print_node(operand, indent + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从根节点(没有用户的节点,或有副作用的节点)开始打印
|
||||||
|
for (const auto& node_ptr : dag) {
|
||||||
|
if (node_ptr->users.empty() ||
|
||||||
|
node_ptr->kind == DAGNode::STORE ||
|
||||||
|
node_ptr->kind == DAGNode::RETURN ||
|
||||||
|
node_ptr->kind == DAGNode::BRANCH ||
|
||||||
|
node_ptr->kind == DAGNode::MEMSET)
|
||||||
|
{
|
||||||
|
print_node(node_ptr.get(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cerr << "======================================\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sysy
|
} // namespace sysy
|
||||||
@@ -27,7 +27,8 @@ void RISCv64RegAlloc::run() {
|
|||||||
|
|
||||||
void RISCv64RegAlloc::eliminateFrameIndices() {
|
void RISCv64RegAlloc::eliminateFrameIndices() {
|
||||||
StackFrameInfo& frame_info = MFunc->getFrameInfo();
|
StackFrameInfo& frame_info = MFunc->getFrameInfo();
|
||||||
int current_offset = 0;
|
int current_offset = 20; // 这里写20是为了在$s0和第一个变量之间留出20字节的安全区,
|
||||||
|
// 以防止一些函数调用方面的恶性bug。
|
||||||
Function* F = MFunc->getFunc();
|
Function* F = MFunc->getFunc();
|
||||||
RISCv64ISel* isel = MFunc->getISel();
|
RISCv64ISel* isel = MFunc->getISel();
|
||||||
|
|
||||||
@@ -94,6 +95,18 @@ void RISCv64RegAlloc::eliminateFrameIndices() {
|
|||||||
std::make_unique<RegOperand>(addr_vreg),
|
std::make_unique<RegOperand>(addr_vreg),
|
||||||
std::make_unique<ImmOperand>(0)));
|
std::make_unique<ImmOperand>(0)));
|
||||||
new_instructions.push_back(std::move(sw));
|
new_instructions.push_back(std::move(sw));
|
||||||
|
} else if (instr_ptr->getOpcode() == RVOpcodes::FRAME_ADDR) { // [新] 处理FRAME_ADDR
|
||||||
|
auto& operands = instr_ptr->getOperands();
|
||||||
|
unsigned dest_vreg = static_cast<RegOperand*>(operands[0].get())->getVRegNum();
|
||||||
|
unsigned alloca_vreg = static_cast<RegOperand*>(operands[1].get())->getVRegNum();
|
||||||
|
int offset = frame_info.alloca_offsets.at(alloca_vreg);
|
||||||
|
|
||||||
|
// 将 `frame_addr rd, rs` 展开为 `addi rd, s0, offset`
|
||||||
|
auto addi = std::make_unique<MachineInstr>(RVOpcodes::ADDI);
|
||||||
|
addi->addOperand(std::make_unique<RegOperand>(dest_vreg));
|
||||||
|
addi->addOperand(std::make_unique<RegOperand>(PhysicalReg::S0)); // 基地址是帧指针 s0
|
||||||
|
addi->addOperand(std::make_unique<ImmOperand>(offset));
|
||||||
|
new_instructions.push_back(std::move(addi));
|
||||||
} else {
|
} else {
|
||||||
new_instructions.push_back(std::move(instr_ptr));
|
new_instructions.push_back(std::move(instr_ptr));
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/Reg2Mem.cpp
129
src/Reg2Mem.cpp
@@ -1,129 +0,0 @@
|
|||||||
#include "Reg2Mem.h"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <iostream>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace sysy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除phi节点
|
|
||||||
* 删除phi节点后可能会生成冗余存储代码
|
|
||||||
*/
|
|
||||||
void Reg2Mem::DeletePhiInst(){
|
|
||||||
auto &functions = pModule->getFunctions();
|
|
||||||
for (auto &function : functions) {
|
|
||||||
auto basicBlocks = function.second->getBasicBlocks();
|
|
||||||
for (auto &basicBlock : basicBlocks) {
|
|
||||||
|
|
||||||
for (auto iter = basicBlock->begin(); iter != basicBlock->end();) {
|
|
||||||
auto &instruction = *iter;
|
|
||||||
if (instruction->isPhi()) {
|
|
||||||
auto predBlocks = basicBlock->getPredecessors();
|
|
||||||
// 寻找源和目的
|
|
||||||
// 目的就是phi指令的第一个操作数
|
|
||||||
// 源就是phi指令的后续操作数
|
|
||||||
auto destination = instruction->getOperand(0);
|
|
||||||
int predBlockindex = 0;
|
|
||||||
for (auto &predBlock : predBlocks) {
|
|
||||||
++predBlockindex;
|
|
||||||
// 判断前驱块儿只有一个后继还是多个后继
|
|
||||||
// 如果有多个
|
|
||||||
auto source = instruction->getOperand(predBlockindex);
|
|
||||||
if (source == destination) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// std::cout << predBlock->getNumSuccessors() << std::endl;
|
|
||||||
if (predBlock->getNumSuccessors() > 1) {
|
|
||||||
// 创建一个basicblock
|
|
||||||
auto newbasicBlock = function.second->addBasicBlock();
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << " phidel.L" << pBuilder->getLabelIndex();
|
|
||||||
newbasicBlock->setName(ss.str());
|
|
||||||
ss.str("");
|
|
||||||
// // 修改前驱后继关系
|
|
||||||
basicBlock->replacePredecessor(predBlock, newbasicBlock);
|
|
||||||
// predBlock = newbasicBlock;
|
|
||||||
newbasicBlock->addPredecessor(predBlock);
|
|
||||||
newbasicBlock->addSuccessor(basicBlock.get());
|
|
||||||
predBlock->removeSuccessor(basicBlock.get());
|
|
||||||
predBlock->addSuccessor(newbasicBlock);
|
|
||||||
// std::cout << "the block name is " << basicBlock->getName() << std::endl;
|
|
||||||
// for (auto pb : basicBlock->getPredecessors()) {
|
|
||||||
// // newbasicBlock->addPredecessor(pb);
|
|
||||||
// std::cout << pb->getName() << std::endl;
|
|
||||||
// }
|
|
||||||
// sysy::BasicBlock::conectBlocks(newbasicBlock, static_cast<BasicBlock *>(basicBlock.get()));
|
|
||||||
// 若后为跳转指令,应该修改跳转指令所到达的位置
|
|
||||||
auto thelastinst = predBlock->end();
|
|
||||||
(--thelastinst);
|
|
||||||
|
|
||||||
if (thelastinst->get()->isConditional() || thelastinst->get()->isUnconditional()) { // 如果是跳转指令
|
|
||||||
auto opnum = thelastinst->get()->getNumOperands();
|
|
||||||
for (size_t i = 0; i < opnum; i++) {
|
|
||||||
if (thelastinst->get()->getOperand(i) == basicBlock.get()) {
|
|
||||||
thelastinst->get()->replaceOperand(i, newbasicBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 在新块中插入store指令
|
|
||||||
pBuilder->setPosition(newbasicBlock, newbasicBlock->end());
|
|
||||||
// pBuilder->createStoreInst(source, destination);
|
|
||||||
if (source->isInt() || source->isFloat()) {
|
|
||||||
pBuilder->createStoreInst(source, destination);
|
|
||||||
} else {
|
|
||||||
auto loadInst = pBuilder->createLoadInst(source);
|
|
||||||
pBuilder->createStoreInst(loadInst, destination);
|
|
||||||
}
|
|
||||||
// pBuilder->createMoveInst(Instruction::kMove, destination->getType(), destination, source,
|
|
||||||
// newbasicBlock);
|
|
||||||
pBuilder->setPosition(newbasicBlock, newbasicBlock->end());
|
|
||||||
pBuilder->createUncondBrInst(basicBlock.get(), {});
|
|
||||||
} else {
|
|
||||||
// 如果前驱块只有一个后继
|
|
||||||
auto thelastinst = predBlock->end();
|
|
||||||
(--thelastinst);
|
|
||||||
// std::cout << predBlock->getName() << std::endl;
|
|
||||||
// std::cout << thelastinst->get() << std::endl;
|
|
||||||
// std::cout << "First point 11 " << std::endl;
|
|
||||||
if (thelastinst->get()->isConditional() || thelastinst->get()->isUnconditional()) {
|
|
||||||
// 在跳转语句前insert st指令
|
|
||||||
pBuilder->setPosition(predBlock, thelastinst);
|
|
||||||
} else {
|
|
||||||
pBuilder->setPosition(predBlock, predBlock->end());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source->isInt() || source->isFloat()) {
|
|
||||||
pBuilder->createStoreInst(source, destination);
|
|
||||||
} else {
|
|
||||||
auto loadInst = pBuilder->createLoadInst(source);
|
|
||||||
pBuilder->createStoreInst(loadInst, destination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 删除phi指令
|
|
||||||
auto &instructions = basicBlock->getInstructions();
|
|
||||||
usedelete(iter->get());
|
|
||||||
iter = instructions.erase(iter);
|
|
||||||
if (basicBlock->getNumInstructions() == 0) {
|
|
||||||
if (basicBlock->getNumSuccessors() == 1) {
|
|
||||||
pBuilder->setPosition(basicBlock.get(), basicBlock->end());
|
|
||||||
pBuilder->createUncondBrInst(basicBlock->getSuccessors()[0], {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Reg2Mem::usedelete(Instruction *instr) {
|
|
||||||
for (auto &use : instr->getOperands()) {
|
|
||||||
auto val = use->getValue();
|
|
||||||
val->removeUse(use);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sysy
|
|
||||||
@@ -4,20 +4,23 @@
|
|||||||
#include "RISCv64LLIR.h"
|
#include "RISCv64LLIR.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
extern int DEBUG;
|
||||||
|
extern int DEEPDEBUG;
|
||||||
|
|
||||||
namespace sysy {
|
namespace sysy {
|
||||||
|
|
||||||
class RISCv64AsmPrinter {
|
class RISCv64AsmPrinter {
|
||||||
public:
|
public:
|
||||||
RISCv64AsmPrinter(MachineFunction* mfunc);
|
RISCv64AsmPrinter(MachineFunction* mfunc);
|
||||||
// 主入口
|
// 主入口
|
||||||
void run(std::ostream& os);
|
void run(std::ostream& os, bool debug = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 打印各个部分
|
// 打印各个部分
|
||||||
void printPrologue();
|
void printPrologue();
|
||||||
void printEpilogue();
|
void printEpilogue();
|
||||||
void printBasicBlock(MachineBasicBlock* mbb);
|
void printBasicBlock(MachineBasicBlock* mbb, bool debug = false);
|
||||||
void printInstruction(MachineInstr* instr);
|
void printInstruction(MachineInstr* instr, bool debug = false);
|
||||||
|
|
||||||
// 辅助函数
|
// 辅助函数
|
||||||
std::string regToString(PhysicalReg reg);
|
std::string regToString(PhysicalReg reg);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include "IR.h"
|
#include "IR.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
extern int DEBUG;
|
||||||
|
extern int DEEPDEBUG;
|
||||||
|
|
||||||
namespace sysy {
|
namespace sysy {
|
||||||
|
|
||||||
// RISCv64CodeGen 现在是一个高层驱动器
|
// RISCv64CodeGen 现在是一个高层驱动器
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#include "RISCv64LLIR.h"
|
#include "RISCv64LLIR.h"
|
||||||
|
|
||||||
|
extern int DEBUG;
|
||||||
|
extern int DEEPDEBUG;
|
||||||
|
|
||||||
namespace sysy {
|
namespace sysy {
|
||||||
|
|
||||||
class RISCv64ISel {
|
class RISCv64ISel {
|
||||||
@@ -31,6 +34,8 @@ private:
|
|||||||
DAGNode* get_operand_node(Value* val_ir, std::map<Value*, DAGNode*>&, std::vector<std::unique_ptr<DAGNode>>&);
|
DAGNode* get_operand_node(Value* val_ir, std::map<Value*, DAGNode*>&, std::vector<std::unique_ptr<DAGNode>>&);
|
||||||
DAGNode* create_node(int kind, Value* val, std::map<Value*, DAGNode*>&, std::vector<std::unique_ptr<DAGNode>>&);
|
DAGNode* create_node(int kind, Value* val, std::map<Value*, DAGNode*>&, std::vector<std::unique_ptr<DAGNode>>&);
|
||||||
|
|
||||||
|
void print_dag(const std::vector<std::unique_ptr<DAGNode>>& dag, const std::string& bb_name);
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
Function* F; // 当前处理的高层IR函数
|
Function* F; // 当前处理的高层IR函数
|
||||||
std::unique_ptr<MachineFunction> MFunc; // 正在构建的底层LLIR函数
|
std::unique_ptr<MachineFunction> MFunc; // 正在构建的底层LLIR函数
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ enum class RVOpcodes {
|
|||||||
// 新增伪指令,用于解耦栈帧处理
|
// 新增伪指令,用于解耦栈帧处理
|
||||||
FRAME_LOAD, // 从栈帧加载 (AllocaInst)
|
FRAME_LOAD, // 从栈帧加载 (AllocaInst)
|
||||||
FRAME_STORE, // 保存到栈帧 (AllocaInst)
|
FRAME_STORE, // 保存到栈帧 (AllocaInst)
|
||||||
|
FRAME_ADDR, // [新] 获取栈帧变量的地址
|
||||||
};
|
};
|
||||||
|
|
||||||
class MachineOperand;
|
class MachineOperand;
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ int main(int argc, char **argv) {
|
|||||||
// 设置 DEBUG 模式(如果指定了 'asmd')
|
// 设置 DEBUG 模式(如果指定了 'asmd')
|
||||||
if (argStopAfter == "asmd") {
|
if (argStopAfter == "asmd") {
|
||||||
DEBUG = 1;
|
DEBUG = 1;
|
||||||
// DEEPDEBUG = 1;
|
DEEPDEBUG = 1;
|
||||||
}
|
}
|
||||||
sysy::RISCv64CodeGen codegen(moduleIR); // 传入优化后的 moduleIR
|
sysy::RISCv64CodeGen codegen(moduleIR); // 传入优化后的 moduleIR
|
||||||
string asmCode = codegen.code_gen();
|
string asmCode = codegen.code_gen();
|
||||||
|
|||||||
227
test_script/runit-riscv64-single.sh
Normal file
227
test_script/runit-riscv64-single.sh
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# runit-riscv64-single.sh - 用于在 RISC-V 虚拟机内部测试单个或少量 .s 文件的脚本
|
||||||
|
# 模仿 runit-riscv64.sh 的功能,但以具体文件路径作为输入。
|
||||||
|
|
||||||
|
# --- 配置区 ---
|
||||||
|
# 假设此脚本位于项目根目录 (例如 /home/ubuntu/debug)
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
|
LIB_DIR="${SCRIPT_DIR}/lib"
|
||||||
|
TMP_DIR="${SCRIPT_DIR}/tmp" # 临时可执行文件将存放在这里
|
||||||
|
TESTDATA_DIR="${SCRIPT_DIR}/testdata" # 用于查找 .in/.out 文件
|
||||||
|
|
||||||
|
# 定义编译器
|
||||||
|
GCC_NATIVE="gcc" # VM 内部的原生 gcc
|
||||||
|
|
||||||
|
# --- 初始化变量 ---
|
||||||
|
GCC_TIMEOUT=10 # gcc 编译超时 (秒)
|
||||||
|
EXEC_TIMEOUT=5 # 程序自动化执行超时 (秒)
|
||||||
|
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
|
||||||
|
S_FILES=() # 存储用户提供的 .s 文件列表
|
||||||
|
PASSED_CASES=0
|
||||||
|
FAILED_CASES_LIST=""
|
||||||
|
|
||||||
|
# --- 函数定义 ---
|
||||||
|
show_help() {
|
||||||
|
echo "用法: $0 [文件1.s] [文件2.s] ... [选项]"
|
||||||
|
echo "在 VM 内部编译并测试指定的 .s 文件。"
|
||||||
|
echo ""
|
||||||
|
echo "如果找到对应的 .in/.out 文件,则进行自动化测试。否则,进入交互模式。"
|
||||||
|
echo ""
|
||||||
|
echo "选项:"
|
||||||
|
echo " -ct N 设置 gcc 编译超时为 N 秒 (默认: 10)。"
|
||||||
|
echo " -t N 设置程序自动化执行超时为 N 秒 (默认: 5)。"
|
||||||
|
echo " -ml N, --max-lines N 当输出对比失败时,最多显示 N 行内容 (默认: 50)。"
|
||||||
|
echo " -h, --help 显示此帮助信息并退出。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 显示文件内容并根据行数截断的函数
|
||||||
|
display_file_content() {
|
||||||
|
local file_path="$1"
|
||||||
|
local title="$2"
|
||||||
|
local max_lines="$3"
|
||||||
|
|
||||||
|
if [ ! -f "$file_path" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "$title"
|
||||||
|
local line_count
|
||||||
|
line_count=$(wc -l < "$file_path")
|
||||||
|
|
||||||
|
if [ "$line_count" -gt "$max_lines" ]; then
|
||||||
|
head -n "$max_lines" "$file_path"
|
||||||
|
echo -e "\e[33m[... 输出已截断,共 ${line_count} 行 ...]\e[0m"
|
||||||
|
else
|
||||||
|
cat "$file_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 参数解析 ---
|
||||||
|
# 从参数中分离出 .s 文件和选项
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-ct|-t|-ml|--max-lines)
|
||||||
|
# 选项和其值将在下一个循环中处理
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
# 检查是否是带值的选项
|
||||||
|
if ! [[ ${args_processed+x} ]]; then
|
||||||
|
args_processed=true # 标记已处理过参数
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-ct) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -ct 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
-t) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -t 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
-ml|--max-lines) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then MAX_OUTPUT_LINES="$2"; shift; else echo "错误: --max-lines 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
*.s) S_FILES+=("$1") ;;
|
||||||
|
*) if ! [[ "$1" =~ ^[0-9]+$ ]]; then echo "未知选项或无效文件: $1"; show_help; exit 1; fi ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.s)
|
||||||
|
if [[ -f "$arg" ]]; then
|
||||||
|
S_FILES+=("$arg")
|
||||||
|
else
|
||||||
|
echo "警告: 文件不存在,已忽略: $arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 主逻辑开始 ---
|
||||||
|
if [ ${#S_FILES[@]} -eq 0 ]; then
|
||||||
|
echo "错误: 未提供任何 .s 文件作为输入。"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${TMP_DIR}"
|
||||||
|
TOTAL_CASES=${#S_FILES[@]}
|
||||||
|
|
||||||
|
echo "SysY VM 内单例测试运行器启动..."
|
||||||
|
echo "超时设置: gcc=${GCC_TIMEOUT}s, 运行=${EXEC_TIMEOUT}s"
|
||||||
|
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for s_file in "${S_FILES[@]}"; do
|
||||||
|
is_passed=1
|
||||||
|
|
||||||
|
# 从 .s 文件名反向推导原始测试用例路径
|
||||||
|
base_name_from_s_file=$(basename "$s_file" .s)
|
||||||
|
original_test_name_underscored=$(echo "$base_name_from_s_file" | sed 's/_sysyc_riscv64$//')
|
||||||
|
category=$(echo "$original_test_name_underscored" | cut -d'_' -f1)
|
||||||
|
test_file_base=$(echo "$original_test_name_underscored" | cut -d'_' -f2-)
|
||||||
|
original_relative_path="${category}/${test_file_base}"
|
||||||
|
|
||||||
|
executable_file="${TMP_DIR}/${base_name_from_s_file}"
|
||||||
|
input_file="${TESTDATA_DIR}/${original_relative_path}.in"
|
||||||
|
output_reference_file="${TESTDATA_DIR}/${original_relative_path}.out"
|
||||||
|
output_actual_file="${TMP_DIR}/${base_name_from_s_file}.actual_out"
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "正在处理: ${s_file}"
|
||||||
|
echo " (关联测试用例: ${original_relative_path}.sy)"
|
||||||
|
|
||||||
|
# 步骤 1: GCC 编译
|
||||||
|
echo " 使用 gcc 编译 (超时 ${GCC_TIMEOUT}s)..."
|
||||||
|
timeout -s KILL ${GCC_TIMEOUT} "${GCC_NATIVE}" "${s_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static -g
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "\e[31m错误: GCC 编译失败或超时。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 2: 执行与测试
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
|
# 检查是自动化测试还是交互模式
|
||||||
|
if [ -f "${input_file}" ] || [ -f "${output_reference_file}" ]; then
|
||||||
|
# --- 自动化测试模式 ---
|
||||||
|
echo " 检测到 .in/.out 文件,进入自动化测试模式..."
|
||||||
|
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
|
||||||
|
|
||||||
|
exec_cmd="\"${executable_file}\""
|
||||||
|
[ -f "${input_file}" ] && exec_cmd+=" < \"${input_file}\""
|
||||||
|
exec_cmd+=" > \"${output_actual_file}\""
|
||||||
|
|
||||||
|
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
|
||||||
|
ACTUAL_RETURN_CODE=$?
|
||||||
|
|
||||||
|
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
||||||
|
echo -e "\e[31m 执行超时。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
else
|
||||||
|
if [ -f "${output_reference_file}" ]; then
|
||||||
|
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
||||||
|
if [[ "$LAST_LINE_TRIMMED" =~ ^[-+]?[0-9]+$ ]]; then
|
||||||
|
EXPECTED_RETURN_CODE="$LAST_LINE_TRIMMED"
|
||||||
|
EXPECTED_STDOUT_FILE="${TMP_DIR}/${base_name_from_s_file}.expected_stdout"
|
||||||
|
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
||||||
|
if [ "$ACTUAL_RETURN_CODE" -ne "$EXPECTED_RETURN_CODE" ]; then echo -e "\e[31m 返回码测试失败: 期望 ${EXPECTED_RETURN_CODE}, 实际 ${ACTUAL_RETURN_CODE}\e[0m"; is_passed=0; fi
|
||||||
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
||||||
|
echo -e "\e[31m 标准输出测试失败。\e[0m"; is_passed=0
|
||||||
|
display_file_content "${EXPECTED_STDOUT_FILE}" " \e[36m--- 期望输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m--- 实际输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m----------------\e[0m"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
||||||
|
echo -e "\e[32m 标准输出测试成功。\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31m 标准输出测试失败。\e[0m"; is_passed=0
|
||||||
|
display_file_content "${output_reference_file}" " \e[36m--- 期望输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m--- 实际输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m----------------\e[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# --- 交互模式 ---
|
||||||
|
echo -e "\e[33m"
|
||||||
|
echo " **********************************************************"
|
||||||
|
echo " ** 未找到 .in 或 .out 文件,进入交互模式。 **"
|
||||||
|
echo " ** 程序即将运行,你可以直接在终端中输入。 **"
|
||||||
|
echo " ** 按下 Ctrl+D (EOF) 或以其他方式结束程序以继续。 **"
|
||||||
|
echo " **********************************************************"
|
||||||
|
echo -e "\e[0m"
|
||||||
|
"${executable_file}"
|
||||||
|
INTERACTIVE_RET_CODE=$?
|
||||||
|
echo -e "\e[33m\n 交互模式执行完毕,程序返回码: ${INTERACTIVE_RET_CODE}\e[0m"
|
||||||
|
echo " 注意: 交互模式的结果未经验证。"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
|
echo -e "\e[32m状态: 通过\e[0m"
|
||||||
|
((PASSED_CASES++))
|
||||||
|
else
|
||||||
|
echo -e "\e[31m状态: 失败\e[0m"
|
||||||
|
FAILED_CASES_LIST+="${original_relative_path}.sy\n"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 打印最终总结 ---
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "所有测试完成"
|
||||||
|
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
||||||
|
|
||||||
|
if [ -n "$FAILED_CASES_LIST" ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "\e[31m未通过的测例:\e[0m"
|
||||||
|
echo -e "${FAILED_CASES_LIST}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
|
||||||
|
if [ "$PASSED_CASES" -eq "$TOTAL_CASES" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,16 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# run_vm_tests.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本
|
# runit-riscv64.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本
|
||||||
# 此脚本应位于您的项目根目录 (例如 /home/ubuntu/debug)
|
# 此脚本应位于您的项目根目录 (例如 /home/ubuntu/debug)
|
||||||
# 假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。
|
# 假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。
|
||||||
# 脚本的目录结构应该为:
|
|
||||||
# .
|
|
||||||
# ├── runit.sh
|
|
||||||
# ├── lib
|
|
||||||
# │ └── libsysy_riscv.a
|
|
||||||
# └── testdata
|
|
||||||
# ├── functional
|
|
||||||
# └── performance
|
|
||||||
|
|
||||||
# 定义相对于脚本位置的目录
|
# 定义相对于脚本位置的目录
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
@@ -22,30 +14,47 @@ TESTDATA_DIR="${SCRIPT_DIR}/testdata"
|
|||||||
GCC_NATIVE="gcc" # VM 内部的 gcc
|
GCC_NATIVE="gcc" # VM 内部的 gcc
|
||||||
|
|
||||||
# --- 新增功能: 初始化变量 ---
|
# --- 新增功能: 初始化变量 ---
|
||||||
TIMEOUT_SECONDS=5 # 默认运行时超时时间为 5 秒
|
GCC_TIMEOUT=10 # 默认 gcc 编译超时 (秒)
|
||||||
COMPILE_TIMEOUT_SECONDS=10 # 默认编译超时时间为 10 秒
|
EXEC_TIMEOUT=5 # 默认运行时超时 (秒)
|
||||||
|
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
|
||||||
TOTAL_CASES=0
|
TOTAL_CASES=0
|
||||||
PASSED_CASES=0
|
PASSED_CASES=0
|
||||||
|
FAILED_CASES_LIST="" # 用于存储未通过的测例列表
|
||||||
|
|
||||||
# 显示帮助信息的函数
|
# 显示帮助信息的函数
|
||||||
show_help() {
|
show_help() {
|
||||||
echo "用法: $0 [选项]"
|
echo "用法: $0 [选项]"
|
||||||
echo "此脚本用于在 RISC-V 虚拟机内部,对之前生成的 .s 汇编文件进行汇编、链接和测试。"
|
echo "此脚本用于在 RISC-V 虚拟机内部,对之前生成的 .s 汇编文件进行汇编、链接和测试。"
|
||||||
echo "假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。"
|
echo "测试会按文件名升序进行。"
|
||||||
echo ""
|
echo ""
|
||||||
echo "选项:"
|
echo "选项:"
|
||||||
echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。"
|
echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。"
|
||||||
echo " -t, --timeout N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。"
|
echo " -ct M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。"
|
||||||
echo " -ct, --compile-timeout M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。"
|
echo " -t N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。"
|
||||||
|
echo " -ml N, --max-lines N 当输出对比失败时,最多显示 N 行内容 (默认: 50)。"
|
||||||
echo " -h, --help 显示此帮助信息并退出。"
|
echo " -h, --help 显示此帮助信息并退出。"
|
||||||
echo ""
|
}
|
||||||
echo "执行步骤:"
|
|
||||||
echo "1. 遍历 'tmp/' 目录下的所有 .s 汇编文件。"
|
# 显示文件内容并根据行数截断的函数
|
||||||
echo "2. 在指定的超时时间内使用 VM 内部的 gcc 将 .s 文件汇编并链接为可执行文件。"
|
display_file_content() {
|
||||||
echo "3. 在指定的超时时间内运行编译后的可执行文件。"
|
local file_path="$1"
|
||||||
echo "4. 根据对应的 .out 文件内容进行返回值和/或标准输出的比较。"
|
local title="$2"
|
||||||
echo "5. 输出比较时会忽略行尾多余的换行符。"
|
local max_lines="$3"
|
||||||
echo "6. 所有测试结束后,报告总通过率。"
|
|
||||||
|
if [ ! -f "$file_path" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "$title"
|
||||||
|
local line_count
|
||||||
|
line_count=$(wc -l < "$file_path")
|
||||||
|
|
||||||
|
if [ "$line_count" -gt "$max_lines" ]; then
|
||||||
|
head -n "$max_lines" "$file_path"
|
||||||
|
echo -e "\e[33m[... 输出已截断,共 ${line_count} 行 ...]\e[0m"
|
||||||
|
else
|
||||||
|
cat "$file_path"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# 清理临时文件的函数
|
# 清理临时文件的函数
|
||||||
@@ -69,23 +78,14 @@ while [[ "$#" -gt 0 ]]; do
|
|||||||
clean_tmp
|
clean_tmp
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-t|--timeout)
|
-t)
|
||||||
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
|
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -t 需要一个正整数参数。" >&2; exit 1; fi
|
||||||
TIMEOUT_SECONDS="$2"
|
|
||||||
shift # 移过参数值
|
|
||||||
else
|
|
||||||
echo "错误: --timeout 需要一个正整数参数。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
-ct|--compile-timeout)
|
-ct)
|
||||||
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
|
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -ct 需要一个正整数参数。" >&2; exit 1; fi
|
||||||
COMPILE_TIMEOUT_SECONDS="$2"
|
;;
|
||||||
shift # 移过参数值
|
-ml|--max-lines)
|
||||||
else
|
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then MAX_OUTPUT_LINES="$2"; shift; else echo "错误: --max-lines 需要一个正整数参数。" >&2; exit 1; fi
|
||||||
echo "错误: --compile-timeout 需要一个正整数参数。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
show_help
|
show_help
|
||||||
@@ -101,32 +101,27 @@ while [[ "$#" -gt 0 ]]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo "SysY VM 内部测试运行器启动..."
|
echo "SysY VM 内部测试运行器启动..."
|
||||||
echo "编译超时设置为: ${COMPILE_TIMEOUT_SECONDS} 秒"
|
echo "GCC 编译超时设置为: ${GCC_TIMEOUT} 秒"
|
||||||
echo "运行时超时设置为: ${TIMEOUT_SECONDS} 秒"
|
echo "运行时超时设置为: ${EXEC_TIMEOUT} 秒"
|
||||||
|
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
|
||||||
echo "汇编文件目录: ${TMP_DIR}"
|
echo "汇编文件目录: ${TMP_DIR}"
|
||||||
echo "库文件目录: ${LIB_DIR}"
|
|
||||||
echo "测试数据目录: ${TESTDATA_DIR}"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 查找 tmp 目录下的所有 .s 汇编文件
|
# 查找 tmp 目录下的所有 .s 汇编文件并排序
|
||||||
s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s")
|
s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s" | sort -V)
|
||||||
TOTAL_CASES=$(echo "$s_files" | wc -w)
|
TOTAL_CASES=$(echo "$s_files" | wc -w)
|
||||||
|
|
||||||
# 遍历找到的每个 .s 文件
|
# 使用 here-string (<<<) 避免子 shell 问题
|
||||||
echo "$s_files" | while read s_file; do
|
while IFS= read -r s_file; do
|
||||||
# --- 新增功能: 初始化用例通过状态 ---
|
|
||||||
is_passed=1 # 1 表示通过, 0 表示失败
|
is_passed=1 # 1 表示通过, 0 表示失败
|
||||||
|
|
||||||
# 从 .s 文件名中提取原始的测试用例名称部分
|
|
||||||
base_name_from_s_file=$(basename "$s_file" .s)
|
base_name_from_s_file=$(basename "$s_file" .s)
|
||||||
original_test_name_underscored=$(echo "$base_name_from_s_file" | sed 's/_sysyc_riscv64$//')
|
original_test_name_underscored=$(echo "$base_name_from_s_file" | sed 's/_sysyc_riscv64$//')
|
||||||
|
|
||||||
# 将 `original_test_name_underscored` 分割成类别和文件名
|
|
||||||
category=$(echo "$original_test_name_underscored" | cut -d'_' -f1)
|
category=$(echo "$original_test_name_underscored" | cut -d'_' -f1)
|
||||||
test_file_base=$(echo "$original_test_name_underscored" | cut -d'_' -f2-)
|
test_file_base=$(echo "$original_test_name_underscored" | cut -d'_' -f2-)
|
||||||
original_relative_path="${category}/${test_file_base}"
|
original_relative_path="${category}/${test_file_base}"
|
||||||
|
|
||||||
# 定义可执行文件、输入文件、参考输出文件和实际输出文件的路径
|
|
||||||
executable_file="${TMP_DIR}/${base_name_from_s_file}"
|
executable_file="${TMP_DIR}/${base_name_from_s_file}"
|
||||||
input_file="${TESTDATA_DIR}/${original_relative_path}.in"
|
input_file="${TESTDATA_DIR}/${original_relative_path}.in"
|
||||||
output_reference_file="${TESTDATA_DIR}/${original_relative_path}.out"
|
output_reference_file="${TESTDATA_DIR}/${original_relative_path}.out"
|
||||||
@@ -136,42 +131,43 @@ echo "$s_files" | while read s_file; do
|
|||||||
echo " 对应的测试用例路径: ${original_relative_path}"
|
echo " 对应的测试用例路径: ${original_relative_path}"
|
||||||
|
|
||||||
# 步骤 1: 使用 VM 内部的 gcc 编译 .s 到可执行文件
|
# 步骤 1: 使用 VM 内部的 gcc 编译 .s 到可执行文件
|
||||||
echo " 使用 gcc 汇编并链接 (超时 ${COMPILE_TIMEOUT_SECONDS}s)..."
|
echo " 使用 gcc 汇编并链接 (超时 ${GCC_TIMEOUT}s)..."
|
||||||
# --- 修改点: 为 gcc 增加 timeout ---
|
timeout -s KILL ${GCC_TIMEOUT} "${GCC_NATIVE}" "${s_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static -g
|
||||||
timeout ${COMPILE_TIMEOUT_SECONDS} "${GCC_NATIVE}" "${s_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static -g
|
|
||||||
GCC_STATUS=$?
|
GCC_STATUS=$?
|
||||||
if [ $GCC_STATUS -eq 124 ]; then
|
if [ $GCC_STATUS -eq 124 ]; then
|
||||||
echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时 (超过 ${COMPILE_TIMEOUT_SECONDS} 秒)\e[0m"
|
echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
elif [ $GCC_STATUS -ne 0 ]; then
|
elif [ $GCC_STATUS -ne 0 ]; then
|
||||||
echo -e "\e[31m错误: GCC 汇编/链接 ${s_file} 失败,退出码: ${GCC_STATUS}\e[0m"
|
echo -e "\e[31m错误: GCC 汇编/链接 ${s_file} 失败,退出码: ${GCC_STATUS}\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
else
|
fi
|
||||||
|
|
||||||
|
# 步骤 2: 只有当编译成功时才执行
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
echo " 生成的可执行文件: ${executable_file}"
|
echo " 生成的可执行文件: ${executable_file}"
|
||||||
echo " 正在执行 (超时 ${TIMEOUT_SECONDS}s): \"${executable_file}\""
|
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
|
||||||
|
|
||||||
# 步骤 2: 执行编译后的文件并比较/报告结果
|
exec_cmd="\"${executable_file}\""
|
||||||
if [ -f "${output_reference_file}" ]; then
|
if [ -f "${input_file}" ]; then
|
||||||
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
exec_cmd+=" < \"${input_file}\""
|
||||||
|
fi
|
||||||
|
exec_cmd+=" > \"${output_actual_file}\""
|
||||||
|
|
||||||
if [[ "$LAST_LINE_TRIMMED" =~ ^[-+]?[0-9]+$ ]]; then
|
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
|
||||||
EXPECTED_RETURN_CODE="$LAST_LINE_TRIMMED"
|
ACTUAL_RETURN_CODE=$?
|
||||||
EXPECTED_STDOUT_FILE="${TMP_DIR}/${base_name_from_s_file}.expected_stdout"
|
|
||||||
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
|
||||||
echo " 检测到 .out 文件同时包含标准输出和期望的返回码。"
|
|
||||||
echo " 期望返回码: ${EXPECTED_RETURN_CODE}"
|
|
||||||
|
|
||||||
if [ -f "${input_file}" ]; then
|
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
||||||
timeout ${TIMEOUT_SECONDS} "${executable_file}" < "${input_file}" > "${output_actual_file}"
|
echo -e "\e[31m 执行超时: ${original_relative_path}.sy 运行超过 ${EXEC_TIMEOUT} 秒\e[0m"
|
||||||
else
|
is_passed=0
|
||||||
timeout ${TIMEOUT_SECONDS} "${executable_file}" > "${output_actual_file}"
|
else
|
||||||
fi
|
if [ -f "${output_reference_file}" ]; then
|
||||||
ACTUAL_RETURN_CODE=$?
|
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
||||||
|
|
||||||
|
if [[ "$LAST_LINE_TRIMMED" =~ ^[-+]?[0-9]+$ ]]; then
|
||||||
|
EXPECTED_RETURN_CODE="$LAST_LINE_TRIMMED"
|
||||||
|
EXPECTED_STDOUT_FILE="${TMP_DIR}/${base_name_from_s_file}.expected_stdout"
|
||||||
|
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
||||||
|
|
||||||
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
|
||||||
echo -e "\e[31m 执行超时: ${original_relative_path}.sy 运行超过 ${TIMEOUT_SECONDS} 秒\e[0m"
|
|
||||||
is_passed=0
|
|
||||||
else
|
|
||||||
if [ "$ACTUAL_RETURN_CODE" -eq "$EXPECTED_RETURN_CODE" ]; then
|
if [ "$ACTUAL_RETURN_CODE" -eq "$EXPECTED_RETURN_CODE" ]; then
|
||||||
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
|
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
|
||||||
else
|
else
|
||||||
@@ -179,65 +175,51 @@ echo "$s_files" | while read s_file; do
|
|||||||
is_passed=0
|
is_passed=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
||||||
echo -e "\e[32m 标准输出测试成功\e[0m"
|
|
||||||
else
|
|
||||||
echo -e "\e[31m 标准输出测试失败\e[0m"
|
echo -e "\e[31m 标准输出测试失败\e[0m"
|
||||||
echo " 差异:"
|
|
||||||
diff "${output_actual_file}" "${EXPECTED_STDOUT_FILE}"
|
|
||||||
is_passed=0
|
is_passed=0
|
||||||
|
display_file_content "${EXPECTED_STDOUT_FILE}" " \e[36m---------- 期望输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m---------- 实际输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m------------------------------\e[0m"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " 检测到 .out 文件为纯标准输出参考。"
|
|
||||||
if [ -f "${input_file}" ]; then
|
|
||||||
timeout ${TIMEOUT_SECONDS} "${executable_file}" < "${input_file}" > "${output_actual_file}"
|
|
||||||
else
|
else
|
||||||
timeout ${TIMEOUT_SECONDS} "${executable_file}" > "${output_actual_file}"
|
if [ $ACTUAL_RETURN_CODE -ne 0 ]; then
|
||||||
fi
|
echo -e "\e[33m警告: 程序以非零状态 ${ACTUAL_RETURN_CODE} 退出 (纯输出比较模式)。\e[0m"
|
||||||
EXEC_STATUS=$?
|
|
||||||
|
|
||||||
if [ $EXEC_STATUS -eq 124 ]; then
|
|
||||||
echo -e "\e[31m 执行超时: ${original_relative_path}.sy 运行超过 ${TIMEOUT_SECONDS} 秒\e[0m"
|
|
||||||
is_passed=0
|
|
||||||
else
|
|
||||||
if [ $EXEC_STATUS -ne 0 ]; then
|
|
||||||
echo -e "\e[33m警告: 程序以非零状态 ${EXEC_STATUS} 退出 (纯输出比较模式)。\e[0m"
|
|
||||||
fi
|
fi
|
||||||
if diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
||||||
echo -e "\e[32m 成功: 输出与参考输出匹配\e[0m"
|
echo -e "\e[32m 成功: 输出与参考输出匹配\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31m 失败: 输出不匹配\e[0m"
|
echo -e "\e[31m 失败: 输出不匹配\e[0m"
|
||||||
echo " 差异:"
|
|
||||||
diff "${output_actual_file}" "${output_reference_file}"
|
|
||||||
is_passed=0
|
is_passed=0
|
||||||
|
display_file_content "${output_reference_file}" " \e[36m---------- 期望输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m---------- 实际输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m------------------------------\e[0m"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " 未找到 .out 文件。正在运行并报告返回码。"
|
|
||||||
timeout ${TIMEOUT_SECONDS} "${executable_file}"
|
|
||||||
EXEC_STATUS=$?
|
|
||||||
if [ $EXEC_STATUS -eq 124 ]; then
|
|
||||||
echo -e "\e[31m 执行超时: ${original_relative_path}.sy 运行超过 ${TIMEOUT_SECONDS} 秒\e[0m"
|
|
||||||
is_passed=0
|
|
||||||
else
|
else
|
||||||
echo " ${original_relative_path}.sy 的返回码: ${EXEC_STATUS}"
|
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- 新增功能: 更新通过用例计数 ---
|
|
||||||
if [ "$is_passed" -eq 1 ]; then
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
((PASSED_CASES++))
|
((PASSED_CASES++))
|
||||||
|
else
|
||||||
|
FAILED_CASES_LIST+="${original_relative_path}.sy\n"
|
||||||
fi
|
fi
|
||||||
echo "" # 为测试用例之间添加一个空行
|
echo ""
|
||||||
done
|
done <<< "$s_files"
|
||||||
|
|
||||||
# --- 新增功能: 打印最终总结 ---
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "测试完成"
|
echo "测试完成"
|
||||||
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
||||||
|
|
||||||
|
if [ -n "$FAILED_CASES_LIST" ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "\e[31m未通过的测例:\e[0m"
|
||||||
|
echo -e "${FAILED_CASES_LIST}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
if [ "$PASSED_CASES" -eq "$TOTAL_CASES" ]; then
|
if [ "$PASSED_CASES" -eq "$TOTAL_CASES" ]; then
|
||||||
|
|||||||
270
test_script/runit-single.sh
Normal file
270
test_script/runit-single.sh
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# runit-single.sh - 用于编译和测试单个或少量 SysY 程序的脚本
|
||||||
|
# 模仿 runit.sh 的功能,但以具体文件路径作为输入。
|
||||||
|
|
||||||
|
# --- 配置区 ---
|
||||||
|
# 请根据你的环境修改这些路径
|
||||||
|
# 假设此脚本位于你的项目根目录或一个脚本目录中
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
|
# 默认寻找项目根目录下的 build 和 lib
|
||||||
|
BUILD_BIN_DIR="${SCRIPT_DIR}/../build/bin"
|
||||||
|
LIB_DIR="${SCRIPT_DIR}/../lib"
|
||||||
|
# 临时文件会存储在脚本所在目录的 tmp 子目录中
|
||||||
|
TMP_DIR="${SCRIPT_DIR}/tmp"
|
||||||
|
|
||||||
|
# 定义编译器和模拟器
|
||||||
|
SYSYC="${BUILD_BIN_DIR}/sysyc"
|
||||||
|
GCC_RISCV64="riscv64-linux-gnu-gcc"
|
||||||
|
QEMU_RISCV64="qemu-riscv64"
|
||||||
|
|
||||||
|
# --- 初始化变量 ---
|
||||||
|
EXECUTE_MODE=false
|
||||||
|
SYSYC_TIMEOUT=10 # sysyc 编译超时 (秒)
|
||||||
|
GCC_TIMEOUT=10 # gcc 编译超时 (秒)
|
||||||
|
EXEC_TIMEOUT=5 # qemu 自动化执行超时 (秒)
|
||||||
|
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
|
||||||
|
SY_FILES=() # 存储用户提供的 .sy 文件列表
|
||||||
|
PASSED_CASES=0
|
||||||
|
FAILED_CASES_LIST=""
|
||||||
|
|
||||||
|
# --- 函数定义 ---
|
||||||
|
show_help() {
|
||||||
|
echo "用法: $0 [文件1.sy] [文件2.sy] ... [选项]"
|
||||||
|
echo "编译并测试指定的 .sy 文件。"
|
||||||
|
echo ""
|
||||||
|
echo "如果找到对应的 .in/.out 文件,则进行自动化测试。否则,进入交互模式。"
|
||||||
|
echo ""
|
||||||
|
echo "选项:"
|
||||||
|
echo " -e, --executable 编译为可执行文件并运行测试 (必须)。"
|
||||||
|
echo " -sct N 设置 sysyc 编译超时为 N 秒 (默认: 10)。"
|
||||||
|
echo " -gct N 设置 gcc 交叉编译超时为 N 秒 (默认: 10)。"
|
||||||
|
echo " -et N 设置 qemu 自动化执行超时为 N 秒 (默认: 5)。"
|
||||||
|
echo " -ml N, --max-lines N 当输出对比失败时,最多显示 N 行内容 (默认: 50)。"
|
||||||
|
echo " -h, --help 显示此帮助信息并退出。"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 新增功能: 显示文件内容并根据行数截断 ---
|
||||||
|
display_file_content() {
|
||||||
|
local file_path="$1"
|
||||||
|
local title="$2"
|
||||||
|
local max_lines="$3"
|
||||||
|
|
||||||
|
if [ ! -f "$file_path" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "$title"
|
||||||
|
local line_count
|
||||||
|
line_count=$(wc -l < "$file_path")
|
||||||
|
|
||||||
|
if [ "$line_count" -gt "$max_lines" ]; then
|
||||||
|
head -n "$max_lines" "$file_path"
|
||||||
|
echo -e "\e[33m[... 输出已截断,共 ${line_count} 行 ...]\e[0m"
|
||||||
|
else
|
||||||
|
cat "$file_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- 参数解析 ---
|
||||||
|
# 从参数中分离出 .sy 文件和选项
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-e|--executable)
|
||||||
|
EXECUTE_MODE=true
|
||||||
|
;;
|
||||||
|
-sct|-gct|-et|-ml|--max-lines)
|
||||||
|
# 选项和其值将在下一个循环中处理
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
# 检查是否是带值的选项
|
||||||
|
if ! [[ ${args_processed+x} ]]; then
|
||||||
|
args_processed=true # 标记已处理过参数
|
||||||
|
# 重新处理所有参数
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-sct) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then SYSYC_TIMEOUT="$2"; shift; else echo "错误: -sct 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
-gct) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -gct 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
-et) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -et 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
-ml|--max-lines) if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then MAX_OUTPUT_LINES="$2"; shift; else echo "错误: --max-lines 需要一个正整数参数。" >&2; exit 1; fi ;;
|
||||||
|
*.sy) SY_FILES+=("$1") ;;
|
||||||
|
-e|--executable) ;; # 已在外部处理
|
||||||
|
*) if ! [[ "$1" =~ ^[0-9]+$ ]]; then echo "未知选项或无效文件: $1"; show_help; exit 1; fi ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.sy)
|
||||||
|
if [[ -f "$arg" ]]; then
|
||||||
|
SY_FILES+=("$arg")
|
||||||
|
else
|
||||||
|
echo "警告: 文件不存在,已忽略: $arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 主逻辑开始 ---
|
||||||
|
if ! ${EXECUTE_MODE}; then
|
||||||
|
echo "错误: 请提供 -e 或 --executable 选项来运行测试。"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#SY_FILES[@]} -eq 0 ]; then
|
||||||
|
echo "错误: 未提供任何 .sy 文件作为输入。"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${TMP_DIR}"
|
||||||
|
TOTAL_CASES=${#SY_FILES[@]}
|
||||||
|
|
||||||
|
echo "SysY 单例测试运行器启动..."
|
||||||
|
echo "超时设置: sysyc=${SYSYC_TIMEOUT}s, gcc=${GCC_TIMEOUT}s, qemu=${EXEC_TIMEOUT}s"
|
||||||
|
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for sy_file in "${SY_FILES[@]}"; do
|
||||||
|
is_passed=1
|
||||||
|
base_name=$(basename "${sy_file}" .sy)
|
||||||
|
source_dir=$(dirname "${sy_file}")
|
||||||
|
|
||||||
|
ir_file="${TMP_DIR}/${base_name}_sysyc_riscv64.ll"
|
||||||
|
assembly_file="${TMP_DIR}/${base_name}.s"
|
||||||
|
executable_file="${TMP_DIR}/${base_name}"
|
||||||
|
input_file="${source_dir}/${base_name}.in"
|
||||||
|
output_reference_file="${source_dir}/${base_name}.out"
|
||||||
|
output_actual_file="${TMP_DIR}/${base_name}.actual_out"
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "正在处理: ${sy_file}"
|
||||||
|
|
||||||
|
# 步骤 1: sysyc 编译
|
||||||
|
echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..."
|
||||||
|
timeout -s KILL ${SYSYC_TIMEOUT} "${SYSYC}" -s ir "${sy_file}" > "${ir_file}"
|
||||||
|
SYSYC_STATUS=$?
|
||||||
|
if [ $SYSYC_STATUS -eq 124 ]; then
|
||||||
|
echo -e "\e[31m错误: SysY 编译 ${sy_file} IR超时\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
elif [ $SYSYC_STATUS -ne 0 ]; then
|
||||||
|
echo -e "\e[31m错误: SysY 编译 ${sy_file} IR失败,退出码: ${SYSYC_STATUS}\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
fi
|
||||||
|
timeout -s KILL ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}"
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "\e[31m错误: SysY 编译失败或超时。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 2: GCC 编译
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
|
echo " 使用 gcc 编译 (超时 ${GCC_TIMEOUT}s)..."
|
||||||
|
timeout -s KILL ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "\e[31m错误: GCC 编译失败或超时。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 3: 执行与测试
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
|
# 检查是自动化测试还是交互模式
|
||||||
|
if [ -f "${input_file}" ] || [ -f "${output_reference_file}" ]; then
|
||||||
|
# --- 自动化测试模式 ---
|
||||||
|
echo " 检测到 .in/.out 文件,进入自动化测试模式..."
|
||||||
|
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
|
||||||
|
|
||||||
|
exec_cmd="${QEMU_RISCV64} \"${executable_file}\""
|
||||||
|
[ -f "${input_file}" ] && exec_cmd+=" < \"${input_file}\""
|
||||||
|
exec_cmd+=" > \"${output_actual_file}\""
|
||||||
|
|
||||||
|
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
|
||||||
|
ACTUAL_RETURN_CODE=$?
|
||||||
|
|
||||||
|
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
||||||
|
echo -e "\e[31m 执行超时。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
else
|
||||||
|
if [ -f "${output_reference_file}" ]; then
|
||||||
|
# 此处逻辑与 runit.sh 相同
|
||||||
|
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
||||||
|
if [[ "$LAST_LINE_TRIMMED" =~ ^[-+]?[0-9]+$ ]]; then
|
||||||
|
EXPECTED_RETURN_CODE="$LAST_LINE_TRIMMED"
|
||||||
|
EXPECTED_STDOUT_FILE="${TMP_DIR}/${base_name}.expected_stdout"
|
||||||
|
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
||||||
|
if [ "$ACTUAL_RETURN_CODE" -ne "$EXPECTED_RETURN_CODE" ]; then echo -e "\e[31m 返回码测试失败: 期望 ${EXPECTED_RETURN_CODE}, 实际 ${ACTUAL_RETURN_CODE}\e[0m"; is_passed=0; fi
|
||||||
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
||||||
|
echo -e "\e[31m 标准输出测试失败。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
# --- 本次修改点: 使用新函数显示输出 ---
|
||||||
|
display_file_content "${EXPECTED_STDOUT_FILE}" " \e[36m--- 期望输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m--- 实际输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m----------------\e[0m"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
||||||
|
echo -e "\e[32m 标准输出测试成功。\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31m 标准输出测试失败。\e[0m"
|
||||||
|
is_passed=0
|
||||||
|
# --- 本次修改点: 使用新函数显示输出 ---
|
||||||
|
display_file_content "${output_reference_file}" " \e[36m--- 期望输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
display_file_content "${output_actual_file}" " \e[36m--- 实际输出 ---\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
|
echo -e " \e[36m----------------\e[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# --- 交互模式 ---
|
||||||
|
echo -e "\e[33m"
|
||||||
|
echo " **********************************************************"
|
||||||
|
echo " ** 未找到 .in 或 .out 文件,进入交互模式。 **"
|
||||||
|
echo " ** 程序即将运行,你可以直接在终端中输入。 **"
|
||||||
|
echo " ** 按下 Ctrl+D (EOF) 或以其他方式结束程序以继续。 **"
|
||||||
|
echo " **********************************************************"
|
||||||
|
echo -e "\e[0m"
|
||||||
|
"${QEMU_RISCV64}" "${executable_file}"
|
||||||
|
INTERACTIVE_RET_CODE=$?
|
||||||
|
echo -e "\e[33m\n 交互模式执行完毕,程序返回码: ${INTERACTIVE_RET_CODE}\e[0m"
|
||||||
|
# 交互模式无法自动判断对错,默认算通过,但会提示
|
||||||
|
echo " 注意: 交互模式的结果未经验证。"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
|
echo -e "\e[32m状态: 通过\e[0m"
|
||||||
|
((PASSED_CASES++))
|
||||||
|
else
|
||||||
|
echo -e "\e[31m状态: 失败\e[0m"
|
||||||
|
FAILED_CASES_LIST+="${sy_file}\n"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 打印最终总结 ---
|
||||||
|
echo "======================================================================"
|
||||||
|
echo "所有测试完成"
|
||||||
|
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
||||||
|
|
||||||
|
if [ -n "$FAILED_CASES_LIST" ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "\e[31m未通过的测例:\e[0m"
|
||||||
|
echo -e "${FAILED_CASES_LIST}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "======================================================================"
|
||||||
|
|
||||||
|
if [ "$PASSED_CASES" -eq "$TOTAL_CASES" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -21,6 +21,7 @@ EXECUTE_MODE=false
|
|||||||
SYSYC_TIMEOUT=10 # sysyc 编译超时 (秒)
|
SYSYC_TIMEOUT=10 # sysyc 编译超时 (秒)
|
||||||
GCC_TIMEOUT=10 # gcc 编译超时 (秒)
|
GCC_TIMEOUT=10 # gcc 编译超时 (秒)
|
||||||
EXEC_TIMEOUT=5 # qemu 执行超时 (秒)
|
EXEC_TIMEOUT=5 # qemu 执行超时 (秒)
|
||||||
|
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
|
||||||
TOTAL_CASES=0
|
TOTAL_CASES=0
|
||||||
PASSED_CASES=0
|
PASSED_CASES=0
|
||||||
FAILED_CASES_LIST="" # 用于存储未通过的测例列表
|
FAILED_CASES_LIST="" # 用于存储未通过的测例列表
|
||||||
@@ -36,9 +37,32 @@ show_help() {
|
|||||||
echo " -sct N 设置 sysyc 编译超时为 N 秒 (默认: 10)。"
|
echo " -sct N 设置 sysyc 编译超时为 N 秒 (默认: 10)。"
|
||||||
echo " -gct N 设置 gcc 交叉编译超时为 N 秒 (默认: 10)。"
|
echo " -gct N 设置 gcc 交叉编译超时为 N 秒 (默认: 10)。"
|
||||||
echo " -et N 设置 qemu 执行超时为 N 秒 (默认: 5)。"
|
echo " -et N 设置 qemu 执行超时为 N 秒 (默认: 5)。"
|
||||||
|
echo " -ml N, --max-lines N 当输出对比失败时,最多显示 N 行内容 (默认: 50)。"
|
||||||
echo " -h, --help 显示此帮助信息并退出。"
|
echo " -h, --help 显示此帮助信息并退出。"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 显示文件内容并根据行数截断的函数
|
||||||
|
display_file_content() {
|
||||||
|
local file_path="$1"
|
||||||
|
local title="$2"
|
||||||
|
local max_lines="$3"
|
||||||
|
|
||||||
|
if [ ! -f "$file_path" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "$title"
|
||||||
|
local line_count
|
||||||
|
line_count=$(wc -l < "$file_path")
|
||||||
|
|
||||||
|
if [ "$line_count" -gt "$max_lines" ]; then
|
||||||
|
head -n "$max_lines" "$file_path"
|
||||||
|
echo -e "\e[33m[... 输出已截断,共 ${line_count} 行 ...]\e[0m"
|
||||||
|
else
|
||||||
|
cat "$file_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 清理临时文件的函数
|
# 清理临时文件的函数
|
||||||
clean_tmp() {
|
clean_tmp() {
|
||||||
echo "正在清理临时目录: ${TMP_DIR}"
|
echo "正在清理临时目录: ${TMP_DIR}"
|
||||||
@@ -67,6 +91,9 @@ while [[ "$#" -gt 0 ]]; do
|
|||||||
-et)
|
-et)
|
||||||
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -et 需要一个正整数参数。" >&2; exit 1; fi
|
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -et 需要一个正整数参数。" >&2; exit 1; fi
|
||||||
;;
|
;;
|
||||||
|
-ml|--max-lines)
|
||||||
|
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then MAX_OUTPUT_LINES="$2"; shift; else echo "错误: --max-lines 需要一个正整数参数。" >&2; exit 1; fi
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
@@ -86,6 +113,7 @@ echo "临时目录: ${TMP_DIR}"
|
|||||||
echo "执行模式: ${EXECUTE_MODE}"
|
echo "执行模式: ${EXECUTE_MODE}"
|
||||||
if ${EXECUTE_MODE}; then
|
if ${EXECUTE_MODE}; then
|
||||||
echo "超时设置: sysyc=${SYSYC_TIMEOUT}s, gcc=${GCC_TIMEOUT}s, qemu=${EXEC_TIMEOUT}s"
|
echo "超时设置: sysyc=${SYSYC_TIMEOUT}s, gcc=${GCC_TIMEOUT}s, qemu=${EXEC_TIMEOUT}s"
|
||||||
|
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -93,8 +121,7 @@ echo ""
|
|||||||
sy_files=$(find "${TESTDATA_DIR}" -name "*.sy" | sort -V)
|
sy_files=$(find "${TESTDATA_DIR}" -name "*.sy" | sort -V)
|
||||||
TOTAL_CASES=$(echo "$sy_files" | wc -w)
|
TOTAL_CASES=$(echo "$sy_files" | wc -w)
|
||||||
|
|
||||||
# --- 本次修复: 使用 here-string (<<<) 代替管道 (|) 来避免子 shell 问题 ---
|
# --- 修复: 使用 here-string (<<<) 代替管道 (|) 来避免子 shell 问题 ---
|
||||||
# 这样可以确保循环内的 PASSED_CASES 变量修改在循环结束后依然有效
|
|
||||||
while IFS= read -r sy_file; do
|
while IFS= read -r sy_file; do
|
||||||
is_passed=1 # 1 表示通过, 0 表示失败
|
is_passed=1 # 1 表示通过, 0 表示失败
|
||||||
|
|
||||||
@@ -111,7 +138,7 @@ while IFS= read -r sy_file; do
|
|||||||
|
|
||||||
# 步骤 1: 使用 sysyc 编译 .sy 到 .s
|
# 步骤 1: 使用 sysyc 编译 .sy 到 .s
|
||||||
echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..."
|
echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..."
|
||||||
timeout ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}"
|
timeout -s KILL ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}"
|
||||||
SYSYC_STATUS=$?
|
SYSYC_STATUS=$?
|
||||||
if [ $SYSYC_STATUS -eq 124 ]; then
|
if [ $SYSYC_STATUS -eq 124 ]; then
|
||||||
echo -e "\e[31m错误: SysY 编译 ${sy_file} 超时\e[0m"
|
echo -e "\e[31m错误: SysY 编译 ${sy_file} 超时\e[0m"
|
||||||
@@ -125,7 +152,7 @@ while IFS= read -r sy_file; do
|
|||||||
if ${EXECUTE_MODE} && [ "$is_passed" -eq 1 ]; then
|
if ${EXECUTE_MODE} && [ "$is_passed" -eq 1 ]; then
|
||||||
# 步骤 2: 使用 riscv64-linux-gnu-gcc 编译 .s 到可执行文件
|
# 步骤 2: 使用 riscv64-linux-gnu-gcc 编译 .s 到可执行文件
|
||||||
echo " 使用 gcc 编译 (超时 ${GCC_TIMEOUT}s)..."
|
echo " 使用 gcc 编译 (超时 ${GCC_TIMEOUT}s)..."
|
||||||
timeout ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static
|
timeout -s KILL ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static
|
||||||
GCC_STATUS=$?
|
GCC_STATUS=$?
|
||||||
if [ $GCC_STATUS -eq 124 ]; then
|
if [ $GCC_STATUS -eq 124 ]; then
|
||||||
echo -e "\e[31m错误: GCC 编译 ${assembly_file} 超时\e[0m"
|
echo -e "\e[31m错误: GCC 编译 ${assembly_file} 超时\e[0m"
|
||||||
@@ -136,11 +163,9 @@ while IFS= read -r sy_file; do
|
|||||||
fi
|
fi
|
||||||
elif ! ${EXECUTE_MODE}; then
|
elif ! ${EXECUTE_MODE}; then
|
||||||
echo " 跳过执行模式。仅生成汇编文件。"
|
echo " 跳过执行模式。仅生成汇编文件。"
|
||||||
# 如果只编译不执行,只要编译成功就算通过
|
|
||||||
if [ "$is_passed" -eq 1 ]; then
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
((PASSED_CASES++))
|
((PASSED_CASES++))
|
||||||
else
|
else
|
||||||
# --- 本次修改点 ---
|
|
||||||
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
|
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
@@ -151,22 +176,19 @@ while IFS= read -r sy_file; do
|
|||||||
if [ "$is_passed" -eq 1 ]; then
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
|
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
|
||||||
|
|
||||||
# 准备执行命令
|
|
||||||
exec_cmd="${QEMU_RISCV64} \"${executable_file}\""
|
exec_cmd="${QEMU_RISCV64} \"${executable_file}\""
|
||||||
if [ -f "${input_file}" ]; then
|
if [ -f "${input_file}" ]; then
|
||||||
exec_cmd+=" < \"${input_file}\""
|
exec_cmd+=" < \"${input_file}\""
|
||||||
fi
|
fi
|
||||||
exec_cmd+=" > \"${output_actual_file}\""
|
exec_cmd+=" > \"${output_actual_file}\""
|
||||||
|
|
||||||
# 执行并捕获返回码
|
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
|
||||||
eval "timeout ${EXEC_TIMEOUT} ${exec_cmd}"
|
|
||||||
ACTUAL_RETURN_CODE=$?
|
ACTUAL_RETURN_CODE=$?
|
||||||
|
|
||||||
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
|
||||||
echo -e "\e[31m 执行超时: ${sy_file} 运行超过 ${EXEC_TIMEOUT} 秒\e[0m"
|
echo -e "\e[31m 执行超时: ${sy_file} 运行超过 ${EXEC_TIMEOUT} 秒\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
else
|
else
|
||||||
# 检查是否存在 .out 文件以进行比较
|
|
||||||
if [ -f "${output_reference_file}" ]; then
|
if [ -f "${output_reference_file}" ]; then
|
||||||
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
|
||||||
|
|
||||||
@@ -175,70 +197,54 @@ while IFS= read -r sy_file; do
|
|||||||
EXPECTED_STDOUT_FILE="${TMP_DIR}/${output_base_name}_sysyc_riscv64.expected_stdout"
|
EXPECTED_STDOUT_FILE="${TMP_DIR}/${output_base_name}_sysyc_riscv64.expected_stdout"
|
||||||
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
|
||||||
|
|
||||||
# 比较返回码
|
|
||||||
if [ "$ACTUAL_RETURN_CODE" -eq "$EXPECTED_RETURN_CODE" ]; then
|
if [ "$ACTUAL_RETURN_CODE" -eq "$EXPECTED_RETURN_CODE" ]; then
|
||||||
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
|
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31m 返回码测试失败: 期望: ${EXPECTED_RETURN_CODE}, 实际: ${ACTUAL_RETURN_CODE}\e[0m"
|
echo -e "\e[31m 返回码测试失败: 期望: ${EXPECTED_RETURN_CODE}, 实际: ${ACTUAL_RETURN_CODE}\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
fi
|
fi
|
||||||
# 比较标准输出
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
||||||
if diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${EXPECTED_STDOUT_FILE}") >/dev/null 2>&1; then
|
|
||||||
echo -e "\e[32m 标准输出测试成功\e[0m"
|
|
||||||
else
|
|
||||||
echo -e "\e[31m 标准输出测试失败\e[0m"
|
echo -e "\e[31m 标准输出测试失败\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
echo -e " \e[36m---------- 期望输出 ----------\e[0m"
|
display_file_content "${EXPECTED_STDOUT_FILE}" " \e[36m---------- 期望输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
cat "${EXPECTED_STDOUT_FILE}"
|
display_file_content "${output_actual_file}" " \e[36m---------- 实际输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
echo -e " \e[36m---------- 实际输出 ----------\e[0m"
|
|
||||||
cat "${output_actual_file}"
|
|
||||||
echo -e " \e[36m------------------------------\e[0m"
|
echo -e " \e[36m------------------------------\e[0m"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# 纯标准输出比较
|
|
||||||
if [ $ACTUAL_RETURN_CODE -ne 0 ]; then
|
if [ $ACTUAL_RETURN_CODE -ne 0 ]; then
|
||||||
echo -e "\e[33m警告: 程序以非零状态 ${ACTUAL_RETURN_CODE} 退出 (纯输出比较模式)。\e[0m"
|
echo -e "\e[33m警告: 程序以非零状态 ${ACTUAL_RETURN_CODE} 退出 (纯输出比较模式)。\e[0m"
|
||||||
fi
|
fi
|
||||||
if diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
if ! diff -q <(sed ':a;N;$!ba;s/\n*$//' "${output_actual_file}") <(sed ':a;N;$!ba;s/\n*$//' "${output_reference_file}") >/dev/null 2>&1; then
|
||||||
echo -e "\e[32m 成功: 输出与参考输出匹配\e[0m"
|
echo -e "\e[32m 成功: 输出与参考输出匹配\e[0m"
|
||||||
else
|
else
|
||||||
echo -e "\e[31m 失败: 输出不匹配\e[0m"
|
echo -e "\e[31m 失败: 输出不匹配\e[0m"
|
||||||
is_passed=0
|
is_passed=0
|
||||||
echo -e " \e[36m---------- 期望输出 ----------\e[0m"
|
display_file_content "${output_reference_file}" " \e[36m---------- 期望输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
cat "${output_reference_file}"
|
display_file_content "${output_actual_file}" " \e[36m---------- 实际输出 ----------\e[0m" "${MAX_OUTPUT_LINES}"
|
||||||
echo -e " \e[36m---------- 实际输出 ----------\e[0m"
|
|
||||||
cat "${output_actual_file}"
|
|
||||||
echo -e " \e[36m------------------------------\e[0m"
|
echo -e " \e[36m------------------------------\e[0m"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# 没有 .out 文件,只报告返回码
|
|
||||||
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
|
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 更新通过用例计数
|
|
||||||
# --- 本次修改点 ---
|
|
||||||
if [ "$is_passed" -eq 1 ]; then
|
if [ "$is_passed" -eq 1 ]; then
|
||||||
((PASSED_CASES++))
|
((PASSED_CASES++))
|
||||||
else
|
else
|
||||||
# 将失败的用例名称添加到列表中
|
|
||||||
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
|
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
|
||||||
fi
|
fi
|
||||||
echo "" # 添加空行以提高可读性
|
echo ""
|
||||||
done <<< "$sy_files"
|
done <<< "$sy_files"
|
||||||
|
|
||||||
# --- 新增功能: 打印最终总结 ---
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "测试完成"
|
echo "测试完成"
|
||||||
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
|
||||||
|
|
||||||
# --- 本次修改点: 打印未通过的测例列表 ---
|
|
||||||
if [ -n "$FAILED_CASES_LIST" ]; then
|
if [ -n "$FAILED_CASES_LIST" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "\e[31m未通过的测例:\e[0m"
|
echo -e "\e[31m未通过的测例:\e[0m"
|
||||||
# 使用 -e 来解释换行符 \n
|
|
||||||
echo -e "${FAILED_CASES_LIST}"
|
echo -e "${FAILED_CASES_LIST}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user