Compare commits

..

1 Commits

Author SHA1 Message Date
c2153b6fab [deploy]部署版本1 2025-07-20 00:10:24 +08:00
17 changed files with 221 additions and 1220 deletions

View File

@ -57,22 +57,11 @@ bool AddressCalculationExpansion::run() {
}
}
} else if (GlobalValue* globalValue = dynamic_cast<GlobalValue*>(basePointer)) {
// 遍历 GlobalValue 的所有维度操作数
for (const auto& use_ptr : globalValue->getDims()) {
Value* dimValue = use_ptr->getValue();
// 将维度值转换为常量整数
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;
}
}
std::cerr << "Warning: GlobalValue dimension handling needs explicit implementation for GEP expansion. Skipping GEP for: ";
SysYPrinter::printValue(globalValue);
std::cerr << "\n";
++it;
continue;
} else {
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);

View File

@ -31,7 +31,6 @@ add_executable(sysyc
RISCv64ISel.cpp
RISCv64RegAlloc.cpp
RISCv64AsmPrinter.cpp
RISCv64Passes.cpp
)
# 设置 include 路径,包含 ANTLR 运行时库和项目头文件

View File

@ -18,7 +18,7 @@ bool isMemoryOp(RVOpcodes opcode) {
RISCv64AsmPrinter::RISCv64AsmPrinter(MachineFunction* mfunc) : MFunc(mfunc) {}
void RISCv64AsmPrinter::run(std::ostream& os, bool debug) {
void RISCv64AsmPrinter::run(std::ostream& os) {
OS = &os;
*OS << ".globl " << MFunc->getName() << "\n";
@ -27,7 +27,7 @@ void RISCv64AsmPrinter::run(std::ostream& os, bool debug) {
printPrologue();
for (auto& mbb : MFunc->getBlocks()) {
printBasicBlock(mbb.get(), debug);
printBasicBlock(mbb.get());
}
}
@ -42,7 +42,7 @@ void RISCv64AsmPrinter::printPrologue() {
*OS << " addi sp, sp, -" << aligned_stack_size << "\n";
*OS << " sd ra, " << (aligned_stack_size - 8) << "(sp)\n";
*OS << " sd s0, " << (aligned_stack_size - 16) << "(sp)\n";
*OS << " addi s0, sp, " << aligned_stack_size << "\n";
*OS << " mv s0, sp\n";
}
// 忠实还原保存函数入口参数的逻辑
@ -73,31 +73,24 @@ void RISCv64AsmPrinter::printEpilogue() {
}
}
void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb, bool debug) {
void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb) {
if (!mbb->getName().empty()) {
*OS << mbb->getName() << ":\n";
}
for (auto& instr : mbb->getInstructions()) {
printInstruction(instr.get(), debug);
printInstruction(instr.get());
}
}
void RISCv64AsmPrinter::printInstruction(MachineInstr* instr, bool debug) {
void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) {
auto opcode = instr->getOpcode();
if (opcode == RVOpcodes::RET) {
printEpilogue();
}
if (opcode == RVOpcodes::LABEL) {
// 标签直接打印,不加缩进
printOperand(instr->getOperands()[0].get());
*OS << ":\n";
return; // 处理完毕,直接返回
if (opcode != RVOpcodes::LABEL) {
*OS << " ";
}
// 对于所有非标签指令,先打印缩进
*OS << " ";
switch (opcode) {
case RVOpcodes::ADD: *OS << "add "; break; case RVOpcodes::ADDI: *OS << "addi "; break;
case RVOpcodes::ADDW: *OS << "addw "; break; case RVOpcodes::ADDIW: *OS << "addiw "; break;
@ -133,21 +126,13 @@ void RISCv64AsmPrinter::printInstruction(MachineInstr* instr, bool debug) {
case RVOpcodes::SNEZ: *OS << "snez "; break;
case RVOpcodes::CALL: *OS << "call "; break;
case RVOpcodes::LABEL:
// printOperand(instr->getOperands()[0].get());
// *OS << ":";
printOperand(instr->getOperands()[0].get());
*OS << ":";
break;
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:
// It should have been eliminated by RegAlloc
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;
// These should have been eliminated by RegAlloc
throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter");
default:
throw std::runtime_error("Unknown opcode in AsmPrinter");
}

View File

@ -2,7 +2,6 @@
#include "RISCv64ISel.h"
#include "RISCv64RegAlloc.h"
#include "RISCv64AsmPrinter.h"
#include "RISCv64Passes.h" // 包含优化Pass的头文件
#include <sstream>
namespace sysy {
@ -12,7 +11,7 @@ std::string RISCv64CodeGen::code_gen() {
return module_gen();
}
// 模块级代码生成
// 模块级代码生成 (移植自原文件,处理.data段和驱动函数生成)
std::string RISCv64CodeGen::module_gen() {
std::stringstream ss;
@ -53,39 +52,21 @@ std::string RISCv64CodeGen::module_gen() {
return ss.str();
}
// function_gen 现在是包含具体优化名称的、完整的处理流水线
// function_gen 现在是新的、模块化的处理流水线
std::string RISCv64CodeGen::function_gen(Function* func) {
// === 完整的后端处理流水线 ===
// 阶段 1: 指令选择 (sysy::IR -> LLIR with virtual registers)
RISCv64ISel isel;
std::unique_ptr<MachineFunction> mfunc = isel.runOnFunction(func);
std::stringstream ss1;
RISCv64AsmPrinter printer1(mfunc.get());
printer1.run(ss1, true);
// 阶段 2: 指令调度 (Instruction Scheduling)
PreRA_Scheduler scheduler;
scheduler.runOnMachineFunction(mfunc.get());
// 阶段 3: 物理寄存器分配 (Register Allocation)
// 阶段 2: 寄存器分配 (包含栈帧布局, 活跃性分析, 图着色, spill/rewrite)
RISCv64RegAlloc reg_alloc(mfunc.get());
reg_alloc.run();
// 阶段 4: 窥孔优化 (Peephole Optimization)
PeepholeOptimizer peephole;
peephole.runOnMachineFunction(mfunc.get());
// 阶段 5: 局部指令调度 (Local Scheduling)
PostRA_Scheduler local_scheduler;
local_scheduler.runOnMachineFunction(mfunc.get());
// 阶段 6: 代码发射 (Code Emission)
// 阶段 3: 代码发射 (LLIR with physical regs -> Assembly Text)
std::stringstream ss;
RISCv64AsmPrinter printer(mfunc.get());
printer.run(ss);
if (DEBUG) ss << ss1.str(); // 将指令选择阶段的结果也包含在最终输出中
return ss.str();
}

View File

@ -4,7 +4,6 @@
#include <functional>
#include <cmath> // For std::fabs
#include <limits> // For std::numeric_limits
#include <iostream>
namespace sysy {
@ -83,10 +82,6 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
CurMBB = bb_map.at(bb);
auto dag = build_dag(bb);
if (DEBUG) { // 使用 DEBUG 宏或变量来控制是否打印
print_dag(dag, bb->getName());
}
std::map<Value*, DAGNode*> value_to_node;
for(const auto& node : dag) {
if (node->value) {
@ -97,10 +92,6 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
std::set<DAGNode*> selected_nodes;
std::function<void(DAGNode*)> select_recursive =
[&](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;
for (auto operand : node->operands) {
select_recursive(operand);
@ -127,37 +118,24 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) {
}
}
// 核心函数为DAG节点选择并生成MachineInstr (已修复和增强的完整版本)
// 核心函数为DAG节点选择并生成MachineInstr (忠实移植版)
void RISCv64ISel::selectNode(DAGNode* node) {
// 调用者select_recursive已经保证了操作数节点会先于当前节点被选择。
// 因此,这里我们只处理当前节点。
switch (node->kind) {
// [V2优点] 采纳“延迟物化”Late Materialization思想。
// 这两个节点仅作为标记不直接生成指令。它们的目的是在DAG中保留类型信息。
// 加载其值的责任被转移给了使用它们的父节点如STORE, BINARY等
// 这修复了之前版本中“使用未初始化虚拟寄存器”的根本性bug。
case DAGNode::CONSTANT:
case DAGNode::ALLOCA_ADDR:
if (node->value) {
// 确保它有一个关联的虚拟寄存器即可,不生成代码。
getVReg(node->value);
}
if (node->value) getVReg(node->value);
break;
case DAGNode::LOAD: {
auto dest_vreg = getVReg(node->value);
Value* ptr_val = node->operands[0]->value;
// [V1设计保留] 对于从栈变量加载,继续使用伪指令 FRAME_LOAD。
// 这种设计将栈帧布局的具体计算推迟到后续的 `eliminateFrameIndices` 阶段,保持了模块化。
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_LOAD);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
CurMBB->addInstruction(std::move(instr));
} else if (auto global = dynamic_cast<GlobalValue*>(ptr_val)) {
// 对于全局变量,先用 la 加载其地址,再用 lw 加载其值。
auto addr_vreg = getNewVReg();
auto la = std::make_unique<MachineInstr>(RVOpcodes::LA);
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
@ -172,7 +150,6 @@ void RISCv64ISel::selectNode(DAGNode* node) {
));
CurMBB->addInstruction(std::move(lw));
} else {
// 对于已经在虚拟寄存器中的指针地址,直接通过该地址加载。
auto ptr_vreg = getVReg(ptr_val);
auto lw = std::make_unique<MachineInstr>(RVOpcodes::LW);
lw->addOperand(std::make_unique<RegOperand>(dest_vreg));
@ -189,13 +166,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
Value* val_to_store = node->operands[0]->value;
Value* ptr_val = node->operands[1]->value;
// [V2优点] 在STORE节点内部负责加载作为源的常量。
// 如果要存储的值是一个常量,就在这里生成 `li` 指令加载它。
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);
li->addOperand(std::make_unique<RegOperand>(getVReg(val_const)));
li->addOperand(std::make_unique<ImmOperand>(val_const->getInt()));
@ -203,15 +174,13 @@ void RISCv64ISel::selectNode(DAGNode* node) {
}
auto val_vreg = getVReg(val_to_store);
// [V1设计保留] 同样,对于向栈变量的存储,使用 FRAME_STORE 伪指令。
if (auto alloca = dynamic_cast<AllocaInst*>(ptr_val)) {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::FRAME_STORE);
instr->addOperand(std::make_unique<RegOperand>(val_vreg));
instr->addOperand(std::make_unique<RegOperand>(getVReg(alloca)));
CurMBB->addInstruction(std::move(instr));
} 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);
la->addOperand(std::make_unique<RegOperand>(addr_vreg));
la->addOperand(std::make_unique<LabelOperand>(global->getName()));
@ -225,7 +194,6 @@ void RISCv64ISel::selectNode(DAGNode* node) {
));
CurMBB->addInstruction(std::move(sw));
} else {
// 向一个指针(存储在虚拟寄存器中)指向的地址存储。
auto ptr_vreg = getVReg(ptr_val);
auto sw = std::make_unique<MachineInstr>(RVOpcodes::SW);
sw->addOperand(std::make_unique<RegOperand>(val_vreg));
@ -242,108 +210,37 @@ void RISCv64ISel::selectNode(DAGNode* node) {
auto bin = dynamic_cast<BinaryInst*>(node->value);
Value* lhs = bin->getLhs();
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) {
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);
li->addOperand(std::make_unique<RegOperand>(getVReg(c)));
li->addOperand(std::make_unique<ImmOperand>(c->getInt()));
CurMBB->addInstruction(std::move(li));
}
};
// 检查是否能应用立即数优化。
bool rhs_is_imm_opt = false;
if (auto rhs_const = dynamic_cast<ConstantValue*>(rhs)) {
if (bin->getKind() == BinaryInst::kAdd && rhs_const->getInt() >= -2048 && rhs_const->getInt() < 2048) {
rhs_is_imm_opt = true;
}
}
// 仅在不能作为立即数操作数时才需要提前加载。
load_val_if_const(lhs);
if (!rhs_is_imm_opt) {
load_val_if_const(rhs);
}
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);
if (bin->getKind() == BinaryInst::kAdd) {
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;
}
}
}
switch (bin->getKind()) {
case BinaryInst::kAdd: {
// 区分指针运算64位和整数运算32位
RVOpcodes opcode = (lhs->getType()->isPointer() || rhs->getType()->isPointer()) ? RVOpcodes::ADD : RVOpcodes::ADDW;
auto instr = std::make_unique<MachineInstr>(opcode);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
@ -384,7 +281,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(instr));
break;
}
case BinaryInst::kICmpEQ: { // 等于 (a == b) -> (subw; seqz)
case BinaryInst::kICmpEQ: {
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
@ -397,7 +294,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(seqz));
break;
}
case BinaryInst::kICmpNE: { // 不等于 (a != b) -> (subw; snez)
case BinaryInst::kICmpNE: {
auto sub = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
sub->addOperand(std::make_unique<RegOperand>(dest_vreg));
sub->addOperand(std::make_unique<RegOperand>(lhs_vreg));
@ -410,7 +307,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(snez));
break;
}
case BinaryInst::kICmpLT: { // 小于 (a < b) -> slt
case BinaryInst::kICmpLT: {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
instr->addOperand(std::make_unique<RegOperand>(lhs_vreg));
@ -418,7 +315,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(instr));
break;
}
case BinaryInst::kICmpGT: { // 大于 (a > b) -> (b < a) -> slt
case BinaryInst::kICmpGT: {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SLT);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
instr->addOperand(std::make_unique<RegOperand>(rhs_vreg));
@ -426,7 +323,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(instr));
break;
}
case BinaryInst::kICmpLE: { // 小于等于 (a <= b) -> !(b < a) -> (slt; xori)
case BinaryInst::kICmpLE: {
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
slt->addOperand(std::make_unique<RegOperand>(rhs_vreg));
@ -440,7 +337,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(xori));
break;
}
case BinaryInst::kICmpGE: { // 大于等于 (a >= b) -> !(a < b) -> (slt; xori)
case BinaryInst::kICmpGE: {
auto slt = std::make_unique<MachineInstr>(RVOpcodes::SLT);
slt->addOperand(std::make_unique<RegOperand>(dest_vreg));
slt->addOperand(std::make_unique<RegOperand>(lhs_vreg));
@ -466,7 +363,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
auto src_vreg = getVReg(unary->getOperand());
switch (unary->getKind()) {
case UnaryInst::kNeg: { // 取负: 0 - src
case UnaryInst::kNeg: {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SUBW);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
@ -474,7 +371,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(instr));
break;
}
case UnaryInst::kNot: { // 逻辑非: src == 0 ? 1 : 0
case UnaryInst::kNot: {
auto instr = std::make_unique<MachineInstr>(RVOpcodes::SEQZ);
instr->addOperand(std::make_unique<RegOperand>(dest_vreg));
instr->addOperand(std::make_unique<RegOperand>(src_vreg));
@ -489,10 +386,7 @@ void RISCv64ISel::selectNode(DAGNode* node) {
case DAGNode::CALL: {
auto call = dynamic_cast<CallInst*>(node->value);
// 处理函数参数放入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) {
for (size_t i = 0; i < node->operands.size() && i < 8; ++i) {
DAGNode* arg_node = node->operands[i];
auto arg_preg = static_cast<PhysicalReg>(static_cast<int>(PhysicalReg::A0) + i);
@ -511,64 +405,11 @@ void RISCv64ISel::selectNode(DAGNode* node) {
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);
call_instr->addOperand(std::make_unique<LabelOperand>(call->getCallee()->getName()));
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()) {
auto mv_instr = std::make_unique<MachineInstr>(RVOpcodes::MV);
mv_instr->addOperand(std::make_unique<RegOperand>(getVReg(call)));
@ -582,7 +423,6 @@ void RISCv64ISel::selectNode(DAGNode* node) {
auto ret_inst_ir = dynamic_cast<ReturnInst*>(node->value);
if (ret_inst_ir && ret_inst_ir->hasReturnValue()) {
Value* ret_val = ret_inst_ir->getReturnValue();
// [V2优点] 在RETURN节点内加载常量返回值
if (auto const_val = dynamic_cast<ConstantValue*>(ret_val)) {
auto li_instr = std::make_unique<MachineInstr>(RVOpcodes::LI);
li_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::A0));
@ -595,121 +435,36 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(mv_instr));
}
}
// [V1设计保留] 函数尾声epilogue不由RETURN节点生成
// 而是由后续的AsmPrinter或其它Pass统一处理这是一种常见且有效的模块化设计。
auto ret_mi = std::make_unique<MachineInstr>(RVOpcodes::RET);
CurMBB->addInstruction(std::move(ret_mi));
break;
}
case DAGNode::BRANCH: {
// 处理条件分支
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)
if (auto cond_br = dynamic_cast<CondBrInst*>(node->value)) {
auto br_instr = std::make_unique<MachineInstr>(RVOpcodes::BNE);
br_instr->addOperand(std::make_unique<RegOperand>(cond_vreg));
br_instr->addOperand(std::make_unique<RegOperand>(getVReg(cond_br->getCondition())));
br_instr->addOperand(std::make_unique<RegOperand>(PhysicalReg::ZERO));
br_instr->addOperand(std::make_unique<LabelOperand>(then_bb_name));
br_instr->addOperand(std::make_unique<LabelOperand>(cond_br->getThenBlock()->getName()));
CurMBB->addInstruction(std::move(br_instr));
// 为else分支生成无条件跳转 (后续Pass可以优化掉不必要的跳转)
} 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>(else_bb_name));
j_instr->addOperand(std::make_unique<LabelOperand>(uncond_br->getBlock()->getName()));
CurMBB->addInstruction(std::move(j_instr));
}
}
// 处理无条件分支
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;
}
break;
}
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);
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_num_bytes = getVReg(memset->getSize());
auto r_value_byte = getVReg(memset->getValue());
// 为memset内部逻辑创建新的临时虚拟寄存器
auto r_counter = getNewVReg();
auto r_end_addr = getNewVReg();
auto r_current_addr = getNewVReg();
auto r_temp_val = getNewVReg();
// 定义一系列lambda表达式来简化指令创建
auto add_instr = [&](RVOpcodes op, unsigned rd, unsigned rs1, unsigned rs2) {
auto i = std::make_unique<MachineInstr>(op);
i->addOperand(std::make_unique<RegOperand>(rd));
@ -748,14 +503,12 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(i));
};
// 生成唯一的循环标签
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_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 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::SLLI, r_value_byte, r_temp_val, 8);
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
@ -763,8 +516,6 @@ void RISCv64ISel::selectNode(DAGNode* node) {
add_instr(RVOpcodes::OR, r_temp_val, r_temp_val, r_value_byte);
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::ADD, r_end_addr, r_dest_addr, r_num_bytes);
auto mv = std::make_unique<MachineInstr>(RVOpcodes::MV);
mv->addOperand(std::make_unique<RegOperand>(r_current_addr));
@ -772,22 +523,17 @@ void RISCv64ISel::selectNode(DAGNode* node) {
CurMBB->addInstruction(std::move(mv));
addi_instr(RVOpcodes::ANDI, r_counter, r_num_bytes, -8);
add_instr(RVOpcodes::ADD, r_counter, r_dest_addr, r_counter);
// 8字节主循环
label_instr(loop_start_label);
branch_instr(RVOpcodes::BGEU, r_current_addr, r_counter, loop_end_label);
store_instr(RVOpcodes::SD, r_temp_val, r_current_addr, 0);
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 8);
jump_instr(loop_start_label);
// 1字节收尾循环
label_instr(loop_end_label);
label_instr(remainder_label);
branch_instr(RVOpcodes::BGEU, r_current_addr, r_end_addr, done_label);
store_instr(RVOpcodes::SB, r_temp_val, r_current_addr, 0);
addi_instr(RVOpcodes::ADDI, r_current_addr, r_current_addr, 1);
jump_instr(remainder_label);
label_instr(done_label);
break;
}
@ -844,12 +590,6 @@ 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->getSize(), 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)) {
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));
@ -892,98 +632,4 @@ std::vector<std::unique_ptr<RISCv64ISel::DAGNode>> RISCv64ISel::build_dag(BasicB
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

View File

@ -1,54 +1,8 @@
// RISCv64Passes.cpp
#include "RISCv64Passes.h"
#include <iostream>
namespace sysy {
// --- 寄存器分配前优化 ---
void PreRA_Scheduler::runOnMachineFunction(MachineFunction* mfunc) {
// TODO: 在此实现寄存器分配前的指令调度。
// 遍历mfunc中的每一个MachineBasicBlock。
// 对每个基本块内的MachineInstr列表进行重排。
//
// 实现思路:
// 1. 分析每个基本块内指令的数据依赖关系,构建依赖图(DAG)。
// 2. 根据目标处理器的流水线特性(指令延迟等),使用列表调度等算法对指令进行重排。
// 3. 此时操作的是虚拟寄存器,只存在真依赖,调度自由度最大。
//
// std::cout << "Running Pre-RA Instruction Scheduler..." << std::endl;
}
// --- 寄存器分配后优化 ---
void PeepholeOptimizer::runOnMachineFunction(MachineFunction* mfunc) {
// TODO: 在此实现窥孔优化。
// 遍历mfunc中的每一个MachineBasicBlock。
// 对每个基本块内的MachineInstr列表进行扫描和替换。
//
// 实现思路:
// 1. 维护一个大小固定例如3-5条指令的滑动窗口。
// 2. 识别特定的冗余模式,例如:
// - `mv a0, a1` 后紧跟 `mv a1, a0` (可消除的交换)
// - `sw t0, 12(s0)` 后紧跟 `lw t1, 12(s0)` (冗余加载)
// - 强度削减: `mul x, x, 2` -> `slli x, x, 1`
// 3. 识别后直接修改MachineInstr列表删除、替换或插入指令
//
// std::cout << "Running Post-RA Peephole Optimizer..." << std::endl;
}
void PostRA_Scheduler::runOnMachineFunction(MachineFunction* mfunc) {
// TODO: 在此实现寄存器分配后的局部指令调度。
// 遍历mfunc中的每一个MachineBasicBlock。
// 重点关注由寄存器分配器插入的spill/fill代码。
//
// 实现思路:
// 1. 识别出用于spill/fill的lw/sw指令。
// 2. 在不违反数据依赖(包括物理寄存器引入的伪依赖)的前提下,
// 尝试将lw指令向上移动使其与使用它的指令之间有足够的距离以隐藏访存延迟。
// 3. 同样可以尝试将sw指令向下移动。
//
// std::cout << "Running Post-RA Local Scheduler..." << std::endl;
}
// 此处为未来优化Pass的实现
} // namespace sysy

View File

@ -27,8 +27,7 @@ void RISCv64RegAlloc::run() {
void RISCv64RegAlloc::eliminateFrameIndices() {
StackFrameInfo& frame_info = MFunc->getFrameInfo();
int current_offset = 20; // 这里写20是为了在$s0和第一个变量之间留出20字节的安全区
// 以防止一些函数调用方面的恶性bug。
int current_offset = 0;
Function* F = MFunc->getFunc();
RISCv64ISel* isel = MFunc->getISel();
@ -95,18 +94,6 @@ void RISCv64RegAlloc::eliminateFrameIndices() {
std::make_unique<RegOperand>(addr_vreg),
std::make_unique<ImmOperand>(0)));
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 {
new_instructions.push_back(std::move(instr_ptr));
}

View File

@ -4,23 +4,20 @@
#include "RISCv64LLIR.h"
#include <iostream>
extern int DEBUG;
extern int DEEPDEBUG;
namespace sysy {
class RISCv64AsmPrinter {
public:
RISCv64AsmPrinter(MachineFunction* mfunc);
// 主入口
void run(std::ostream& os, bool debug = false);
void run(std::ostream& os);
private:
// 打印各个部分
void printPrologue();
void printEpilogue();
void printBasicBlock(MachineBasicBlock* mbb, bool debug = false);
void printInstruction(MachineInstr* instr, bool debug = false);
void printBasicBlock(MachineBasicBlock* mbb);
void printInstruction(MachineInstr* instr);
// 辅助函数
std::string regToString(PhysicalReg reg);

View File

@ -4,9 +4,6 @@
#include "IR.h"
#include <string>
extern int DEBUG;
extern int DEEPDEBUG;
namespace sysy {
// RISCv64CodeGen 现在是一个高层驱动器

View File

@ -3,9 +3,6 @@
#include "RISCv64LLIR.h"
extern int DEBUG;
extern int DEEPDEBUG;
namespace sysy {
class RISCv64ISel {
@ -34,8 +31,6 @@ private:
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>>&);
void print_dag(const std::vector<std::unique_ptr<DAGNode>>& dag, const std::string& bb_name);
// 状态
Function* F; // 当前处理的高层IR函数
std::unique_ptr<MachineFunction> MFunc; // 正在构建的底层LLIR函数

View File

@ -46,7 +46,6 @@ enum class RVOpcodes {
// 新增伪指令,用于解耦栈帧处理
FRAME_LOAD, // 从栈帧加载 (AllocaInst)
FRAME_STORE, // 保存到栈帧 (AllocaInst)
FRAME_ADDR, // [新] 获取栈帧变量的地址
};
class MachineOperand;

View File

@ -1,3 +1,4 @@
// RISCv64Passes.h
#ifndef RISCV64_PASSES_H
#define RISCV64_PASSES_H
@ -5,56 +6,12 @@
namespace sysy {
/**
* @class Pass
* @brief 所有优化Pass的抽象基类 (可选,但推荐)
* * 定义一个通用的接口,所有优化都应该实现它。
*/
class Pass {
public:
virtual ~Pass() = default;
virtual void runOnMachineFunction(MachineFunction* mfunc) = 0;
};
// --- 寄存器分配前优化 ---
/**
* @class PreRA_Scheduler
* @brief 寄存器分配前的指令调度器
* * 在虚拟寄存器上进行操作,此时调度自由度最大,
* 主要目标是隐藏指令延迟,提高流水线效率。
*/
class PreRA_Scheduler : public Pass {
public:
void runOnMachineFunction(MachineFunction* mfunc) override;
};
// --- 寄存器分配后优化 ---
/**
* @class PeepholeOptimizer
* @brief 窥孔优化器
* * 在已分配物理寄存器的指令流上,通过一个小的滑动窗口来查找
* 并替换掉一些冗余或低效的指令模式。
*/
class PeepholeOptimizer : public Pass {
public:
void runOnMachineFunction(MachineFunction* mfunc) override;
};
/**
* @class PostRA_Scheduler
* @brief 寄存器分配后的局部指令调度器
* * 主要目标是优化寄存器分配器插入的spill/fill代码(lw/sw)
* 尝试将加载指令提前,以隐藏其访存延迟。
*/
class PostRA_Scheduler : public Pass {
public:
void runOnMachineFunction(MachineFunction* mfunc) override;
};
// 此处为未来优化Pass的基类或独立类定义
// 例如:
// class PeepholeOptimizer {
// public:
// void runOnMachineFunction(MachineFunction* mfunc);
// };
} // namespace sysy

View File

@ -218,7 +218,7 @@ int main(int argc, char **argv) {
// 设置 DEBUG 模式(如果指定了 'asmd'
if (argStopAfter == "asmd") {
DEBUG = 1;
DEEPDEBUG = 1;
// DEEPDEBUG = 1;
}
sysy::RISCv64CodeGen codegen(moduleIR); // 传入优化后的 moduleIR
string asmCode = codegen.code_gen();

View File

@ -1,227 +0,0 @@
#!/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

View File

@ -1,8 +1,16 @@
#!/bin/bash
# runit-riscv64.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本
# run_vm_tests.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本
# 此脚本应位于您的项目根目录 (例如 /home/ubuntu/debug)
# 假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。
# 脚本的目录结构应该为:
# .
# ├── runit.sh
# ├── lib
# │ └── libsysy_riscv.a
# └── testdata
# ├── functional
# └── performance
# 定义相对于脚本位置的目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
@ -14,47 +22,30 @@ TESTDATA_DIR="${SCRIPT_DIR}/testdata"
GCC_NATIVE="gcc" # VM 内部的 gcc
# --- 新增功能: 初始化变量 ---
GCC_TIMEOUT=10 # 默认 gcc 编译超时 (秒)
EXEC_TIMEOUT=5 # 默认运行时超时 (秒)
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
TIMEOUT_SECONDS=5 # 默认运行时超时时间为 5 秒
COMPILE_TIMEOUT_SECONDS=10 # 默认编译超时时间为 10 秒
TOTAL_CASES=0
PASSED_CASES=0
FAILED_CASES_LIST="" # 用于存储未通过的测例列表
# 显示帮助信息的函数
show_help() {
echo "用法: $0 [选项]"
echo "此脚本用于在 RISC-V 虚拟机内部,对之前生成的 .s 汇编文件进行汇编、链接和测试。"
echo "测试会按文件名升序进行。"
echo "假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。"
echo ""
echo "选项:"
echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。"
echo " -ct M 设置 gcc 编译的超时时间M 秒 (默认: 10)。"
echo " -t N 设置每个测试用例的运行时超时N 秒 (默认: 5)。"
echo " -ml N, --max-lines N 当输出对比失败时,最多显示 N 行内容 (默认: 50)。"
echo " -t, --timeout N 设置每个测试用例的运行时超时N 秒 (默认: 5)。"
echo " -ct, --compile-timeout M 设置 gcc 编译的超时时间M 秒 (默认: 10)。"
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
echo ""
echo "执行步骤:"
echo "1. 遍历 'tmp/' 目录下的所有 .s 汇编文件。"
echo "2. 在指定的超时时间内使用 VM 内部的 gcc 将 .s 文件汇编并链接为可执行文件。"
echo "3. 在指定的超时时间内运行编译后的可执行文件。"
echo "4. 根据对应的 .out 文件内容进行返回值和/或标准输出的比较。"
echo "5. 输出比较时会忽略行尾多余的换行符。"
echo "6. 所有测试结束后,报告总通过率。"
}
# 清理临时文件的函数
@ -78,14 +69,23 @@ while [[ "$#" -gt 0 ]]; do
clean_tmp
exit 0
;;
-t)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -t 需要一个正整数参数。" >&2; exit 1; fi
-t|--timeout)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
TIMEOUT_SECONDS="$2"
shift # 移过参数值
else
echo "错误: --timeout 需要一个正整数参数。" >&2
exit 1
fi
;;
-ct)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -ct 需要一个正整数参数。" >&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
-ct|--compile-timeout)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
COMPILE_TIMEOUT_SECONDS="$2"
shift # 移过参数值
else
echo "错误: --compile-timeout 需要一个正整数参数。" >&2
exit 1
fi
;;
-h|--help)
show_help
@ -101,27 +101,32 @@ while [[ "$#" -gt 0 ]]; do
done
echo "SysY VM 内部测试运行器启动..."
echo "GCC 编译超时设置为: ${GCC_TIMEOUT}"
echo "运行时超时设置为: ${EXEC_TIMEOUT}"
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
echo "编译超时设置为: ${COMPILE_TIMEOUT_SECONDS}"
echo "运行时超时设置为: ${TIMEOUT_SECONDS}"
echo "汇编文件目录: ${TMP_DIR}"
echo "库文件目录: ${LIB_DIR}"
echo "测试数据目录: ${TESTDATA_DIR}"
echo ""
# 查找 tmp 目录下的所有 .s 汇编文件并排序
s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s" | sort -V)
# 查找 tmp 目录下的所有 .s 汇编文件
s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s")
TOTAL_CASES=$(echo "$s_files" | wc -w)
# 使用 here-string (<<<) 避免子 shell 问题
while IFS= read -r s_file; do
# 遍历找到的每个 .s 文件
echo "$s_files" | while read s_file; do
# --- 新增功能: 初始化用例通过状态 ---
is_passed=1 # 1 表示通过, 0 表示失败
# 从 .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` 分割成类别和文件名
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"
@ -131,43 +136,42 @@ while IFS= read -r s_file; do
echo " 对应的测试用例路径: ${original_relative_path}"
# 步骤 1: 使用 VM 内部的 gcc 编译 .s 到可执行文件
echo " 使用 gcc 汇编并链接 (超时 ${GCC_TIMEOUT}s)..."
timeout -s KILL ${GCC_TIMEOUT} "${GCC_NATIVE}" "${s_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static -g
echo " 使用 gcc 汇编并链接 (超时 ${COMPILE_TIMEOUT_SECONDS}s)..."
# --- 修改点: 为 gcc 增加 timeout ---
timeout ${COMPILE_TIMEOUT_SECONDS} "${GCC_NATIVE}" "${s_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static -g
GCC_STATUS=$?
if [ $GCC_STATUS -eq 124 ]; then
echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时\e[0m"
echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时 (超过 ${COMPILE_TIMEOUT_SECONDS} 秒)\e[0m"
is_passed=0
elif [ $GCC_STATUS -ne 0 ]; then
echo -e "\e[31m错误: GCC 汇编/链接 ${s_file} 失败,退出码: ${GCC_STATUS}\e[0m"
is_passed=0
fi
# 步骤 2: 只有当编译成功时才执行
if [ "$is_passed" -eq 1 ]; then
else
echo " 生成的可执行文件: ${executable_file}"
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
echo " 正在执行 (超时 ${TIMEOUT_SECONDS}s): \"${executable_file}\""
exec_cmd="\"${executable_file}\""
if [ -f "${input_file}" ]; then
exec_cmd+=" < \"${input_file}\""
fi
exec_cmd+=" > \"${output_actual_file}\""
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
ACTUAL_RETURN_CODE=$?
# 步骤 2: 执行编译后的文件并比较/报告结果
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}"
echo " 检测到 .out 文件同时包含标准输出和期望的返回码。"
echo " 期望返回码: ${EXPECTED_RETURN_CODE}"
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
echo -e "\e[31m 执行超时: ${original_relative_path}.sy 运行超过 ${EXEC_TIMEOUT} 秒\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 [ -f "${input_file}" ]; then
timeout ${TIMEOUT_SECONDS} "${executable_file}" < "${input_file}" > "${output_actual_file}"
else
timeout ${TIMEOUT_SECONDS} "${executable_file}" > "${output_actual_file}"
fi
ACTUAL_RETURN_CODE=$?
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
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
else
@ -175,51 +179,65 @@ while IFS= read -r s_file; do
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 [ $ACTUAL_RETURN_CODE -ne 0 ]; then
echo -e "\e[33m警告: 程序以非零状态 ${ACTUAL_RETURN_CODE} 退出 (纯输出比较模式)。\e[0m"
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
echo -e "\e[32m 成功: 输出与参考输出匹配\e[0m"
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
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}"
echo " 检测到 .out 文件为纯标准输出参考。"
if [ -f "${input_file}" ]; then
timeout ${TIMEOUT_SECONDS} "${executable_file}" < "${input_file}" > "${output_actual_file}"
else
timeout ${TIMEOUT_SECONDS} "${executable_file}" > "${output_actual_file}"
fi
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
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"
echo " 差异:"
diff "${output_actual_file}" "${output_reference_file}"
is_passed=0
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
echo " ${original_relative_path}.sy 的返回码: ${EXEC_STATUS}"
fi
fi
fi
# --- 新增功能: 更新通过用例计数 ---
if [ "$is_passed" -eq 1 ]; then
((PASSED_CASES++))
else
FAILED_CASES_LIST+="${original_relative_path}.sy\n"
fi
echo ""
done <<< "$s_files"
echo "" # 为测试用例之间添加一个空行
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

View File

@ -1,270 +0,0 @@
#!/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

View File

@ -9,7 +9,7 @@ TESTDATA_DIR="${SCRIPT_DIR}/../testdata"
BUILD_BIN_DIR="${SCRIPT_DIR}/../build/bin"
LIB_DIR="${SCRIPT_DIR}/../lib"
# TMP_DIR="${SCRIPT_DIR}/tmp"
TMP_DIR="${SCRIPT_DIR}/tmp"
TMP_DIR="/home/ladev987/paraComp/debug/share_folder/tmp"
# 定义编译器和模拟器
SYSYC="${BUILD_BIN_DIR}/sysyc"
@ -21,7 +21,6 @@ EXECUTE_MODE=false
SYSYC_TIMEOUT=10 # sysyc 编译超时 (秒)
GCC_TIMEOUT=10 # gcc 编译超时 (秒)
EXEC_TIMEOUT=5 # qemu 执行超时 (秒)
MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数
TOTAL_CASES=0
PASSED_CASES=0
FAILED_CASES_LIST="" # 用于存储未通过的测例列表
@ -37,32 +36,9 @@ show_help() {
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
}
# 清理临时文件的函数
clean_tmp() {
echo "正在清理临时目录: ${TMP_DIR}"
@ -91,9 +67,6 @@ while [[ "$#" -gt 0 ]]; do
-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
;;
-h|--help)
show_help
exit 0
@ -113,7 +86,6 @@ echo "临时目录: ${TMP_DIR}"
echo "执行模式: ${EXECUTE_MODE}"
if ${EXECUTE_MODE}; then
echo "超时设置: sysyc=${SYSYC_TIMEOUT}s, gcc=${GCC_TIMEOUT}s, qemu=${EXEC_TIMEOUT}s"
echo "失败输出最大行数: ${MAX_OUTPUT_LINES}"
fi
echo ""
@ -121,7 +93,8 @@ echo ""
sy_files=$(find "${TESTDATA_DIR}" -name "*.sy" | sort -V)
TOTAL_CASES=$(echo "$sy_files" | wc -w)
# --- 修复: 使用 here-string (<<<) 代替管道 (|) 来避免子 shell 问题 ---
# --- 本次修复: 使用 here-string (<<<) 代替管道 (|) 来避免子 shell 问题 ---
# 这样可以确保循环内的 PASSED_CASES 变量修改在循环结束后依然有效
while IFS= read -r sy_file; do
is_passed=1 # 1 表示通过, 0 表示失败
@ -138,7 +111,7 @@ while IFS= read -r sy_file; do
# 步骤 1: 使用 sysyc 编译 .sy 到 .s
echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..."
timeout -s KILL ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}"
timeout ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}"
SYSYC_STATUS=$?
if [ $SYSYC_STATUS -eq 124 ]; then
echo -e "\e[31m错误: SysY 编译 ${sy_file} 超时\e[0m"
@ -152,7 +125,7 @@ while IFS= read -r sy_file; do
if ${EXECUTE_MODE} && [ "$is_passed" -eq 1 ]; then
# 步骤 2: 使用 riscv64-linux-gnu-gcc 编译 .s 到可执行文件
echo " 使用 gcc 编译 (超时 ${GCC_TIMEOUT}s)..."
timeout -s KILL ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static
timeout ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static
GCC_STATUS=$?
if [ $GCC_STATUS -eq 124 ]; then
echo -e "\e[31m错误: GCC 编译 ${assembly_file} 超时\e[0m"
@ -163,9 +136,11 @@ while IFS= read -r sy_file; do
fi
elif ! ${EXECUTE_MODE}; then
echo " 跳过执行模式。仅生成汇编文件。"
# 如果只编译不执行,只要编译成功就算通过
if [ "$is_passed" -eq 1 ]; then
((PASSED_CASES++))
else
# --- 本次修改点 ---
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
fi
echo ""
@ -176,19 +151,22 @@ while IFS= read -r sy_file; do
if [ "$is_passed" -eq 1 ]; then
echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..."
# 准备执行命令
exec_cmd="${QEMU_RISCV64} \"${executable_file}\""
if [ -f "${input_file}" ]; then
exec_cmd+=" < \"${input_file}\""
fi
exec_cmd+=" > \"${output_actual_file}\""
eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}"
# 执行并捕获返回码
eval "timeout ${EXEC_TIMEOUT} ${exec_cmd}"
ACTUAL_RETURN_CODE=$?
if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then
echo -e "\e[31m 执行超时: ${sy_file} 运行超过 ${EXEC_TIMEOUT} 秒\e[0m"
is_passed=0
else
# 检查是否存在 .out 文件以进行比较
if [ -f "${output_reference_file}" ]; then
LAST_LINE_TRIMMED=$(tail -n 1 "${output_reference_file}" | tr -d '[:space:]')
@ -197,54 +175,70 @@ while IFS= read -r sy_file; do
EXPECTED_STDOUT_FILE="${TMP_DIR}/${output_base_name}_sysyc_riscv64.expected_stdout"
head -n -1 "${output_reference_file}" > "${EXPECTED_STDOUT_FILE}"
# 比较返回码
if [ "$ACTUAL_RETURN_CODE" -eq "$EXPECTED_RETURN_CODE" ]; then
echo -e "\e[32m 返回码测试成功: (${ACTUAL_RETURN_CODE}) 与期望值 (${EXPECTED_RETURN_CODE}) 匹配\e[0m"
else
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
# 比较标准输出
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"
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"
cat "${EXPECTED_STDOUT_FILE}"
echo -e " \e[36m---------- 实际输出 ----------\e[0m"
cat "${output_actual_file}"
echo -e " \e[36m------------------------------\e[0m"
fi
else
# 纯标准输出比较
if [ $ACTUAL_RETURN_CODE -ne 0 ]; then
echo -e "\e[33m警告: 程序以非零状态 ${ACTUAL_RETURN_CODE} 退出 (纯输出比较模式)。\e[0m"
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"
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"
cat "${output_reference_file}"
echo -e " \e[36m---------- 实际输出 ----------\e[0m"
cat "${output_actual_file}"
echo -e " \e[36m------------------------------\e[0m"
fi
fi
else
# 没有 .out 文件,只报告返回码
echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}"
fi
fi
fi
# 更新通过用例计数
# --- 本次修改点 ---
if [ "$is_passed" -eq 1 ]; then
((PASSED_CASES++))
else
# 将失败的用例名称添加到列表中
FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n"
fi
echo ""
echo "" # 添加空行以提高可读性
done <<< "$sy_files"
# --- 新增功能: 打印最终总结 ---
echo "========================================"
echo "测试完成"
echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]"
# --- 本次修改点: 打印未通过的测例列表 ---
if [ -n "$FAILED_CASES_LIST" ]; then
echo ""
echo -e "\e[31m未通过的测例:\e[0m"
# 使用 -e 来解释换行符 \n
echo -e "${FAILED_CASES_LIST}"
fi