From f7e811b756b92a3636e1a4e940df4817a212e321 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Mon, 21 Jul 2025 14:44:48 +0800 Subject: [PATCH 01/12] =?UTF-8?q?[backend]=E8=A7=A3=E5=86=B3=E4=BA=86?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E6=89=93=E5=8D=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64AsmPrinter.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/RISCv64AsmPrinter.cpp b/src/RISCv64AsmPrinter.cpp index 0ad1c81..47e9773 100644 --- a/src/RISCv64AsmPrinter.cpp +++ b/src/RISCv64AsmPrinter.cpp @@ -87,10 +87,17 @@ void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) { if (opcode == RVOpcodes::RET) { printEpilogue(); } - if (opcode != RVOpcodes::LABEL) { - *OS << " "; + + if (opcode == RVOpcodes::LABEL) { + // 标签直接打印,不加缩进 + printOperand(instr->getOperands()[0].get()); + *OS << ":\n"; + return; // 处理完毕,直接返回 } + // 对于所有非标签指令,先打印缩进 + *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; @@ -126,8 +133,8 @@ void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) { 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: case RVOpcodes::FRAME_STORE: From bbfbf96b5e0158d0215527b4698901846f052865 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Mon, 21 Jul 2025 16:27:47 +0800 Subject: [PATCH 02/12] =?UTF-8?q?[backend]=E8=A7=A3=E5=86=B3=E4=BA=86?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=90=8E=E6=95=B0=E7=BB=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64AsmPrinter.cpp | 22 ++-- src/RISCv64Backend.cpp | 6 +- src/RISCv64ISel.cpp | 182 ++++++++++++++++++++++++++------ src/RISCv64RegAlloc.cpp | 12 +++ src/include/RISCv64AsmPrinter.h | 9 +- src/include/RISCv64Backend.h | 3 + src/include/RISCv64ISel.h | 3 + src/include/RISCv64LLIR.h | 1 + src/sysyc.cpp | 2 +- 9 files changed, 198 insertions(+), 42 deletions(-) diff --git a/src/RISCv64AsmPrinter.cpp b/src/RISCv64AsmPrinter.cpp index 47e9773..9482a58 100644 --- a/src/RISCv64AsmPrinter.cpp +++ b/src/RISCv64AsmPrinter.cpp @@ -18,7 +18,7 @@ bool isMemoryOp(RVOpcodes opcode) { RISCv64AsmPrinter::RISCv64AsmPrinter(MachineFunction* mfunc) : MFunc(mfunc) {} -void RISCv64AsmPrinter::run(std::ostream& os) { +void RISCv64AsmPrinter::run(std::ostream& os, bool debug) { OS = &os; *OS << ".globl " << MFunc->getName() << "\n"; @@ -27,7 +27,7 @@ void RISCv64AsmPrinter::run(std::ostream& os) { printPrologue(); for (auto& mbb : MFunc->getBlocks()) { - printBasicBlock(mbb.get()); + printBasicBlock(mbb.get(), debug); } } @@ -73,16 +73,16 @@ void RISCv64AsmPrinter::printEpilogue() { } } -void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb) { +void RISCv64AsmPrinter::printBasicBlock(MachineBasicBlock* mbb, bool debug) { if (!mbb->getName().empty()) { *OS << mbb->getName() << ":\n"; } 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(); if (opcode == RVOpcodes::RET) { printEpilogue(); @@ -137,9 +137,17 @@ void RISCv64AsmPrinter::printInstruction(MachineInstr* instr) { // *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: - // These should have been eliminated by RegAlloc - throw std::runtime_error("FRAME pseudo-instruction not eliminated before AsmPrinter"); + // 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; default: throw std::runtime_error("Unknown opcode in AsmPrinter"); } diff --git a/src/RISCv64Backend.cpp b/src/RISCv64Backend.cpp index cccbf40..4f45fde 100644 --- a/src/RISCv64Backend.cpp +++ b/src/RISCv64Backend.cpp @@ -61,6 +61,10 @@ std::string RISCv64CodeGen::function_gen(Function* func) { RISCv64ISel isel; std::unique_ptr 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()); @@ -81,7 +85,7 @@ std::string RISCv64CodeGen::function_gen(Function* func) { std::stringstream ss; RISCv64AsmPrinter printer(mfunc.get()); printer.run(ss); - + if (DEBUG) ss << ss1.str(); // 将指令选择阶段的结果也包含在最终输出中 return ss.str(); } diff --git a/src/RISCv64ISel.cpp b/src/RISCv64ISel.cpp index 005ba96..2da2373 100644 --- a/src/RISCv64ISel.cpp +++ b/src/RISCv64ISel.cpp @@ -4,6 +4,7 @@ #include #include // For std::fabs #include // For std::numeric_limits +#include namespace sysy { @@ -92,6 +93,10 @@ void RISCv64ISel::selectBasicBlock(BasicBlock* bb) { std::set selected_nodes; std::function 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); @@ -118,24 +123,37 @@ 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(ptr_val)) { auto instr = std::make_unique(RVOpcodes::FRAME_LOAD); instr->addOperand(std::make_unique(dest_vreg)); instr->addOperand(std::make_unique(getVReg(alloca))); CurMBB->addInstruction(std::move(instr)); } else if (auto global = dynamic_cast(ptr_val)) { + // 对于全局变量,先用 la 加载其地址,再用 lw 加载其值。 auto addr_vreg = getNewVReg(); auto la = std::make_unique(RVOpcodes::LA); la->addOperand(std::make_unique(addr_vreg)); @@ -150,6 +168,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { )); CurMBB->addInstruction(std::move(lw)); } else { + // 对于已经在虚拟寄存器中的指针地址,直接通过该地址加载。 auto ptr_vreg = getVReg(ptr_val); auto lw = std::make_unique(RVOpcodes::LW); lw->addOperand(std::make_unique(dest_vreg)); @@ -166,7 +185,13 @@ 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(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(RVOpcodes::LI); li->addOperand(std::make_unique(getVReg(val_const))); li->addOperand(std::make_unique(val_const->getInt())); @@ -174,13 +199,15 @@ void RISCv64ISel::selectNode(DAGNode* node) { } auto val_vreg = getVReg(val_to_store); + // [V1设计保留] 同样,对于向栈变量的存储,使用 FRAME_STORE 伪指令。 if (auto alloca = dynamic_cast(ptr_val)) { auto instr = std::make_unique(RVOpcodes::FRAME_STORE); instr->addOperand(std::make_unique(val_vreg)); instr->addOperand(std::make_unique(getVReg(alloca))); CurMBB->addInstruction(std::move(instr)); } else if (auto global = dynamic_cast(ptr_val)) { - auto addr_vreg = getNewVReg(); + // 向全局变量存储。 + auto addr_vreg = getNewVReg(); auto la = std::make_unique(RVOpcodes::LA); la->addOperand(std::make_unique(addr_vreg)); la->addOperand(std::make_unique(global->getName())); @@ -194,6 +221,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { )); CurMBB->addInstruction(std::move(sw)); } else { + // 向一个指针(存储在虚拟寄存器中)指向的地址存储。 auto ptr_vreg = getVReg(ptr_val); auto sw = std::make_unique(RVOpcodes::SW); sw->addOperand(std::make_unique(val_vreg)); @@ -211,36 +239,53 @@ void RISCv64ISel::selectNode(DAGNode* node) { Value* lhs = bin->getLhs(); Value* rhs = bin->getRhs(); + // [V2优点] 在BINARY节点内部按需加载常量操作数。 auto load_val_if_const = [&](Value* val) { if (auto c = dynamic_cast(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(RVOpcodes::LI); li->addOperand(std::make_unique(getVReg(c))); li->addOperand(std::make_unique(c->getInt())); 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); - auto rhs_vreg = getVReg(rhs); - - if (bin->getKind() == BinaryInst::kAdd) { - if (auto rhs_const = dynamic_cast(rhs)) { - if (rhs_const->getInt() >= -2048 && rhs_const->getInt() < 2048) { - auto instr = std::make_unique(RVOpcodes::ADDIW); - instr->addOperand(std::make_unique(dest_vreg)); - instr->addOperand(std::make_unique(lhs_vreg)); - instr->addOperand(std::make_unique(rhs_const->getInt())); - CurMBB->addInstruction(std::move(instr)); - return; - } + // 检查是否能应用立即数优化。 + bool rhs_is_imm_opt = false; + if (auto rhs_const = dynamic_cast(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); + } + + auto dest_vreg = getVReg(bin); + auto lhs_vreg = getVReg(lhs); + + // [V2优点] 融合 ADDIW 优化。 + if (rhs_is_imm_opt) { + auto rhs_const = dynamic_cast(rhs); + auto instr = std::make_unique(RVOpcodes::ADDIW); + instr->addOperand(std::make_unique(dest_vreg)); + instr->addOperand(std::make_unique(lhs_vreg)); + instr->addOperand(std::make_unique(rhs_const->getInt())); + CurMBB->addInstruction(std::move(instr)); + return; // 指令已生成,直接返回。 + } + + auto rhs_vreg = getVReg(rhs); + 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(opcode); instr->addOperand(std::make_unique(dest_vreg)); @@ -281,7 +326,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(instr)); break; } - case BinaryInst::kICmpEQ: { + case BinaryInst::kICmpEQ: { // 等于 (a == b) -> (subw; seqz) auto sub = std::make_unique(RVOpcodes::SUBW); sub->addOperand(std::make_unique(dest_vreg)); sub->addOperand(std::make_unique(lhs_vreg)); @@ -294,7 +339,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(seqz)); break; } - case BinaryInst::kICmpNE: { + case BinaryInst::kICmpNE: { // 不等于 (a != b) -> (subw; snez) auto sub = std::make_unique(RVOpcodes::SUBW); sub->addOperand(std::make_unique(dest_vreg)); sub->addOperand(std::make_unique(lhs_vreg)); @@ -307,7 +352,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(snez)); break; } - case BinaryInst::kICmpLT: { + case BinaryInst::kICmpLT: { // 小于 (a < b) -> slt auto instr = std::make_unique(RVOpcodes::SLT); instr->addOperand(std::make_unique(dest_vreg)); instr->addOperand(std::make_unique(lhs_vreg)); @@ -315,7 +360,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(instr)); break; } - case BinaryInst::kICmpGT: { + case BinaryInst::kICmpGT: { // 大于 (a > b) -> (b < a) -> slt auto instr = std::make_unique(RVOpcodes::SLT); instr->addOperand(std::make_unique(dest_vreg)); instr->addOperand(std::make_unique(rhs_vreg)); @@ -323,7 +368,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(instr)); break; } - case BinaryInst::kICmpLE: { + case BinaryInst::kICmpLE: { // 小于等于 (a <= b) -> !(b < a) -> (slt; xori) auto slt = std::make_unique(RVOpcodes::SLT); slt->addOperand(std::make_unique(dest_vreg)); slt->addOperand(std::make_unique(rhs_vreg)); @@ -337,7 +382,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(xori)); break; } - case BinaryInst::kICmpGE: { + case BinaryInst::kICmpGE: { // 大于等于 (a >= b) -> !(a < b) -> (slt; xori) auto slt = std::make_unique(RVOpcodes::SLT); slt->addOperand(std::make_unique(dest_vreg)); slt->addOperand(std::make_unique(lhs_vreg)); @@ -363,7 +408,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { auto src_vreg = getVReg(unary->getOperand()); switch (unary->getKind()) { - case UnaryInst::kNeg: { + case UnaryInst::kNeg: { // 取负: 0 - src auto instr = std::make_unique(RVOpcodes::SUBW); instr->addOperand(std::make_unique(dest_vreg)); instr->addOperand(std::make_unique(PhysicalReg::ZERO)); @@ -371,7 +416,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(instr)); break; } - case UnaryInst::kNot: { + case UnaryInst::kNot: { // 逻辑非: src == 0 ? 1 : 0 auto instr = std::make_unique(RVOpcodes::SEQZ); instr->addOperand(std::make_unique(dest_vreg)); instr->addOperand(std::make_unique(src_vreg)); @@ -386,6 +431,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { case DAGNode::CALL: { auto call = dynamic_cast(node->value); + // 处理函数参数,放入a0-a7物理寄存器 for (size_t i = 0; i < node->operands.size() && i < 8; ++i) { DAGNode* arg_node = node->operands[i]; auto arg_preg = static_cast(static_cast(PhysicalReg::A0) + i); @@ -409,7 +455,8 @@ void RISCv64ISel::selectNode(DAGNode* node) { auto call_instr = std::make_unique(RVOpcodes::CALL); call_instr->addOperand(std::make_unique(call->getCallee()->getName())); CurMBB->addInstruction(std::move(call_instr)); - + + // 处理返回值,从a0移动到目标虚拟寄存器 if (!call->getType()->isVoid()) { auto mv_instr = std::make_unique(RVOpcodes::MV); mv_instr->addOperand(std::make_unique(getVReg(call))); @@ -423,6 +470,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { auto ret_inst_ir = dynamic_cast(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(ret_val)) { auto li_instr = std::make_unique(RVOpcodes::LI); li_instr->addOperand(std::make_unique(PhysicalReg::A0)); @@ -435,6 +483,8 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(mv_instr)); } } + // [V1设计保留] 函数尾声(epilogue)不由RETURN节点生成, + // 而是由后续的AsmPrinter或其它Pass统一处理,这是一种常见且有效的模块化设计。 auto ret_mi = std::make_unique(RVOpcodes::RET); CurMBB->addInstruction(std::move(ret_mi)); break; @@ -442,11 +492,25 @@ void RISCv64ISel::selectNode(DAGNode* node) { case DAGNode::BRANCH: { if (auto cond_br = dynamic_cast(node->value)) { + // [V2优点] 采用更健壮的if-then-else分支逻辑。 + auto cond_vreg = getVReg(cond_br->getCondition()); + auto then_bb_name = cond_br->getThenBlock()->getName(); + auto else_bb_name = cond_br->getElseBlock()->getName(); + + // bne cond, zero, then_label (如果cond不为0,则跳转到then) auto br_instr = std::make_unique(RVOpcodes::BNE); - br_instr->addOperand(std::make_unique(getVReg(cond_br->getCondition()))); + br_instr->addOperand(std::make_unique(cond_vreg)); br_instr->addOperand(std::make_unique(PhysicalReg::ZERO)); - br_instr->addOperand(std::make_unique(cond_br->getThenBlock()->getName())); + br_instr->addOperand(std::make_unique(then_bb_name)); CurMBB->addInstruction(std::move(br_instr)); + + // 无条件跳转到else块 (如果上面分支未发生) + // 注意:在实际的CFG中,这个J指令可能不是必须的, + // 因为else块可能是下一个块。但为了通用性,这里生成它。 + auto j_instr = std::make_unique(RVOpcodes::J); + j_instr->addOperand(std::make_unique(else_bb_name)); + CurMBB->addInstruction(std::move(j_instr)); + } else if (auto uncond_br = dynamic_cast(node->value)) { auto j_instr = std::make_unique(RVOpcodes::J); j_instr->addOperand(std::make_unique(uncond_br->getBlock()->getName())); @@ -456,15 +520,58 @@ void RISCv64ISel::selectNode(DAGNode* node) { } 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(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(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(RVOpcodes::LI); + li->addOperand(std::make_unique(getVReg(const_val))); + li->addOperand(std::make_unique(const_val->getInt())); + CurMBB->addInstruction(std::move(li)); + } + if (auto const_size = dynamic_cast(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(RVOpcodes::LI); + li->addOperand(std::make_unique(getVReg(const_size))); + li->addOperand(std::make_unique(const_size->getInt())); + CurMBB->addInstruction(std::move(li)); + } + if (auto alloca = dynamic_cast(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(RVOpcodes::FRAME_ADDR); + instr->addOperand(std::make_unique(dest_addr_vreg)); // 目标虚拟寄存器 + instr->addOperand(std::make_unique(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(op); i->addOperand(std::make_unique(rd)); @@ -503,12 +610,14 @@ 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); @@ -516,6 +625,8 @@ 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(RVOpcodes::MV); mv->addOperand(std::make_unique(r_current_addr)); @@ -523,17 +634,22 @@ 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; } @@ -590,6 +706,12 @@ std::vector> 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(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)); diff --git a/src/RISCv64RegAlloc.cpp b/src/RISCv64RegAlloc.cpp index 2695f3b..d4c69e7 100644 --- a/src/RISCv64RegAlloc.cpp +++ b/src/RISCv64RegAlloc.cpp @@ -94,6 +94,18 @@ void RISCv64RegAlloc::eliminateFrameIndices() { std::make_unique(addr_vreg), std::make_unique(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(operands[0].get())->getVRegNum(); + unsigned alloca_vreg = static_cast(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(RVOpcodes::ADDI); + addi->addOperand(std::make_unique(dest_vreg)); + addi->addOperand(std::make_unique(PhysicalReg::S0)); // 基地址是帧指针 s0 + addi->addOperand(std::make_unique(offset)); + new_instructions.push_back(std::move(addi)); } else { new_instructions.push_back(std::move(instr_ptr)); } diff --git a/src/include/RISCv64AsmPrinter.h b/src/include/RISCv64AsmPrinter.h index 3ea71f6..f942a45 100644 --- a/src/include/RISCv64AsmPrinter.h +++ b/src/include/RISCv64AsmPrinter.h @@ -4,20 +4,23 @@ #include "RISCv64LLIR.h" #include +extern int DEBUG; +extern int DEEPDEBUG; + namespace sysy { class RISCv64AsmPrinter { public: RISCv64AsmPrinter(MachineFunction* mfunc); // 主入口 - void run(std::ostream& os); + void run(std::ostream& os, bool debug = false); private: // 打印各个部分 void printPrologue(); void printEpilogue(); - void printBasicBlock(MachineBasicBlock* mbb); - void printInstruction(MachineInstr* instr); + void printBasicBlock(MachineBasicBlock* mbb, bool debug = false); + void printInstruction(MachineInstr* instr, bool debug = false); // 辅助函数 std::string regToString(PhysicalReg reg); diff --git a/src/include/RISCv64Backend.h b/src/include/RISCv64Backend.h index 33f7831..403d586 100644 --- a/src/include/RISCv64Backend.h +++ b/src/include/RISCv64Backend.h @@ -4,6 +4,9 @@ #include "IR.h" #include +extern int DEBUG; +extern int DEEPDEBUG; + namespace sysy { // RISCv64CodeGen 现在是一个高层驱动器 diff --git a/src/include/RISCv64ISel.h b/src/include/RISCv64ISel.h index 795b2b8..6c5e421 100644 --- a/src/include/RISCv64ISel.h +++ b/src/include/RISCv64ISel.h @@ -3,6 +3,9 @@ #include "RISCv64LLIR.h" +extern int DEBUG; +extern int DEEPDEBUG; + namespace sysy { class RISCv64ISel { diff --git a/src/include/RISCv64LLIR.h b/src/include/RISCv64LLIR.h index 6310741..86de7d4 100644 --- a/src/include/RISCv64LLIR.h +++ b/src/include/RISCv64LLIR.h @@ -46,6 +46,7 @@ enum class RVOpcodes { // 新增伪指令,用于解耦栈帧处理 FRAME_LOAD, // 从栈帧加载 (AllocaInst) FRAME_STORE, // 保存到栈帧 (AllocaInst) + FRAME_ADDR, // [新] 获取栈帧变量的地址 }; class MachineOperand; diff --git a/src/sysyc.cpp b/src/sysyc.cpp index 692225d..cac39a9 100644 --- a/src/sysyc.cpp +++ b/src/sysyc.cpp @@ -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(); From 24d8e730f18cb229b552a43d3ddd9a7f031e5338 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Mon, 21 Jul 2025 17:36:22 +0800 Subject: [PATCH 03/12] =?UTF-8?q?[backend]=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=84=9A=E6=9C=AC=EF=BC=8C=E5=87=8F=E5=B0=91?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=BF=9B=E7=A8=8B=E5=8D=A1=E6=AD=BB=E6=83=85?= =?UTF-8?q?=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_script/runit-riscv64.sh | 176 ++++++++++++++--------------------- test_script/runit.sh | 15 +-- 2 files changed, 76 insertions(+), 115 deletions(-) diff --git a/test_script/runit-riscv64.sh b/test_script/runit-riscv64.sh index a1c7fc7..9978d90 100644 --- a/test_script/runit-riscv64.sh +++ b/test_script/runit-riscv64.sh @@ -1,16 +1,8 @@ #!/bin/bash -# run_vm_tests.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本 +# runit-riscv64.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)" @@ -22,30 +14,23 @@ TESTDATA_DIR="${SCRIPT_DIR}/testdata" GCC_NATIVE="gcc" # VM 内部的 gcc # --- 新增功能: 初始化变量 --- -TIMEOUT_SECONDS=5 # 默认运行时超时时间为 5 秒 -COMPILE_TIMEOUT_SECONDS=10 # 默认编译超时时间为 10 秒 +GCC_TIMEOUT=10 # 默认 gcc 编译超时 (秒) +EXEC_TIMEOUT=5 # 默认运行时超时 (秒) TOTAL_CASES=0 PASSED_CASES=0 +FAILED_CASES_LIST="" # 用于存储未通过的测例列表 # 显示帮助信息的函数 show_help() { echo "用法: $0 [选项]" echo "此脚本用于在 RISC-V 虚拟机内部,对之前生成的 .s 汇编文件进行汇编、链接和测试。" - echo "假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。" + echo "测试会按文件名升序进行。" echo "" echo "选项:" echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。" - echo " -t, --timeout N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。" - echo " -ct, --compile-timeout M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。" + echo " -ct M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。" + echo " -t N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。" echo " -h, --help 显示此帮助信息并退出。" - echo "" - echo "执行步骤:" - echo "1. 遍历 'tmp/' 目录下的所有 .s 汇编文件。" - echo "2. 在指定的超时时间内使用 VM 内部的 gcc 将 .s 文件汇编并链接为可执行文件。" - echo "3. 在指定的超时时间内运行编译后的可执行文件。" - echo "4. 根据对应的 .out 文件内容进行返回值和/或标准输出的比较。" - echo "5. 输出比较时会忽略行尾多余的换行符。" - echo "6. 所有测试结束后,报告总通过率。" } # 清理临时文件的函数 @@ -69,23 +54,11 @@ while [[ "$#" -gt 0 ]]; do clean_tmp exit 0 ;; - -t|--timeout) - if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then - TIMEOUT_SECONDS="$2" - shift # 移过参数值 - else - echo "错误: --timeout 需要一个正整数参数。" >&2 - exit 1 - fi + -t) + if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -t 需要一个正整数参数。" >&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 + -ct) + if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -ct 需要一个正整数参数。" >&2; exit 1; fi ;; -h|--help) show_help @@ -101,32 +74,26 @@ while [[ "$#" -gt 0 ]]; do done echo "SysY VM 内部测试运行器启动..." -echo "编译超时设置为: ${COMPILE_TIMEOUT_SECONDS} 秒" -echo "运行时超时设置为: ${TIMEOUT_SECONDS} 秒" +echo "GCC 编译超时设置为: ${GCC_TIMEOUT} 秒" +echo "运行时超时设置为: ${EXEC_TIMEOUT} 秒" echo "汇编文件目录: ${TMP_DIR}" -echo "库文件目录: ${LIB_DIR}" -echo "测试数据目录: ${TESTDATA_DIR}" echo "" -# 查找 tmp 目录下的所有 .s 汇编文件 -s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s") +# 查找 tmp 目录下的所有 .s 汇编文件并排序 +s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s" | sort -V) TOTAL_CASES=$(echo "$s_files" | wc -w) -# 遍历找到的每个 .s 文件 -echo "$s_files" | while read s_file; do - # --- 新增功能: 初始化用例通过状态 --- +# 使用 here-string (<<<) 避免子 shell 问题 +while IFS= read -r 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" @@ -136,42 +103,43 @@ echo "$s_files" | while read s_file; do echo " 对应的测试用例路径: ${original_relative_path}" # 步骤 1: 使用 VM 内部的 gcc 编译 .s 到可执行文件 - 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 + echo " 使用 gcc 汇编并链接 (超时 ${GCC_TIMEOUT}s)..." + timeout -s KILL ${GCC_TIMEOUT} "${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} 超时 (超过 ${COMPILE_TIMEOUT_SECONDS} 秒)\e[0m" + echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时\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 - else + fi + + # 步骤 2: 只有当编译成功时才执行 + if [ "$is_passed" -eq 1 ]; then echo " 生成的可执行文件: ${executable_file}" - echo " 正在执行 (超时 ${TIMEOUT_SECONDS}s): \"${executable_file}\"" + echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..." - # 步骤 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}" + 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=$? - 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 运行超过 ${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 [ "$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 @@ -183,61 +151,53 @@ echo "$s_files" | while read s_file; do echo -e "\e[32m 标准输出测试成功\e[0m" else echo -e "\e[31m 标准输出测试失败\e[0m" - echo " 差异:" - diff "${output_actual_file}" "${EXPECTED_STDOUT_FILE}" is_passed=0 + 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 - fi - else - 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" + 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" else echo -e "\e[31m 失败: 输出不匹配\e[0m" - echo " 差异:" - diff "${output_actual_file}" "${output_reference_file}" is_passed=0 + 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 - 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}" + echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}" fi fi fi - # --- 新增功能: 更新通过用例计数 --- if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) + else + FAILED_CASES_LIST+="${original_relative_path}.sy\n" fi - echo "" # 为测试用例之间添加一个空行 -done + echo "" +done <<< "$s_files" -# --- 新增功能: 打印最终总结 --- 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 diff --git a/test_script/runit.sh b/test_script/runit.sh index ce66b5d..2c34c83 100644 --- a/test_script/runit.sh +++ b/test_script/runit.sh @@ -93,7 +93,7 @@ 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 表示失败 @@ -111,7 +111,8 @@ while IFS= read -r sy_file; do # 步骤 1: 使用 sysyc 编译 .sy 到 .s echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..." - timeout ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}" + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + timeout -s KILL ${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" @@ -125,7 +126,8 @@ 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 ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + timeout -s KILL ${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" @@ -140,7 +142,6 @@ while IFS= read -r sy_file; do if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) else - # --- 本次修改点 --- FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n" fi echo "" @@ -159,7 +160,8 @@ while IFS= read -r sy_file; do exec_cmd+=" > \"${output_actual_file}\"" # 执行并捕获返回码 - eval "timeout ${EXEC_TIMEOUT} ${exec_cmd}" + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}" ACTUAL_RETURN_CODE=$? if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then @@ -219,7 +221,6 @@ while IFS= read -r sy_file; do fi # 更新通过用例计数 - # --- 本次修改点 --- if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) else @@ -234,7 +235,7 @@ echo "========================================" echo "测试完成" echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]" -# --- 本次修改点: 打印未通过的测例列表 --- +# --- 打印未通过的测例列表 --- if [ -n "$FAILED_CASES_LIST" ]; then echo "" echo -e "\e[31m未通过的测例:\e[0m" From 3baccbc03ab99d768b25ab84b7edf955a518ea27 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Mon, 21 Jul 2025 17:49:06 +0800 Subject: [PATCH 04/12] =?UTF-8?q?[backend]=E8=A7=A3=E5=86=B3=E4=BA=86?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=A8=8B=E5=BA=8F=E5=8F=AF=E8=83=BD=E7=9A=84?= =?UTF-8?q?=E6=8C=82=E8=B5=B7=E9=97=AE=E9=A2=98=EF=BC=8C=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E4=BA=86=E7=94=A8=E4=BA=8E=E5=8D=95=E4=B8=AA=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9A=84=E6=96=B0=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_script/runit-riscv64.sh | 176 +++++++++++---------------- test_script/runit-single.sh | 225 +++++++++++++++++++++++++++++++++++ test_script/runit.sh | 15 +-- 3 files changed, 301 insertions(+), 115 deletions(-) create mode 100644 test_script/runit-single.sh diff --git a/test_script/runit-riscv64.sh b/test_script/runit-riscv64.sh index a1c7fc7..9978d90 100644 --- a/test_script/runit-riscv64.sh +++ b/test_script/runit-riscv64.sh @@ -1,16 +1,8 @@ #!/bin/bash -# run_vm_tests.sh - 用于在 RISC-V 虚拟机内部汇编、链接和测试 SysY 程序的脚本 +# runit-riscv64.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)" @@ -22,30 +14,23 @@ TESTDATA_DIR="${SCRIPT_DIR}/testdata" GCC_NATIVE="gcc" # VM 内部的 gcc # --- 新增功能: 初始化变量 --- -TIMEOUT_SECONDS=5 # 默认运行时超时时间为 5 秒 -COMPILE_TIMEOUT_SECONDS=10 # 默认编译超时时间为 10 秒 +GCC_TIMEOUT=10 # 默认 gcc 编译超时 (秒) +EXEC_TIMEOUT=5 # 默认运行时超时 (秒) TOTAL_CASES=0 PASSED_CASES=0 +FAILED_CASES_LIST="" # 用于存储未通过的测例列表 # 显示帮助信息的函数 show_help() { echo "用法: $0 [选项]" echo "此脚本用于在 RISC-V 虚拟机内部,对之前生成的 .s 汇编文件进行汇编、链接和测试。" - echo "假设当前运行环境已经是 RISC-V 64 位架构,可以直接执行编译后的程序。" + echo "测试会按文件名升序进行。" echo "" echo "选项:" echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。" - echo " -t, --timeout N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。" - echo " -ct, --compile-timeout M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。" + echo " -ct M 设置 gcc 编译的超时时间为 M 秒 (默认: 10)。" + echo " -t N 设置每个测试用例的运行时超时为 N 秒 (默认: 5)。" echo " -h, --help 显示此帮助信息并退出。" - echo "" - echo "执行步骤:" - echo "1. 遍历 'tmp/' 目录下的所有 .s 汇编文件。" - echo "2. 在指定的超时时间内使用 VM 内部的 gcc 将 .s 文件汇编并链接为可执行文件。" - echo "3. 在指定的超时时间内运行编译后的可执行文件。" - echo "4. 根据对应的 .out 文件内容进行返回值和/或标准输出的比较。" - echo "5. 输出比较时会忽略行尾多余的换行符。" - echo "6. 所有测试结束后,报告总通过率。" } # 清理临时文件的函数 @@ -69,23 +54,11 @@ while [[ "$#" -gt 0 ]]; do clean_tmp exit 0 ;; - -t|--timeout) - if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then - TIMEOUT_SECONDS="$2" - shift # 移过参数值 - else - echo "错误: --timeout 需要一个正整数参数。" >&2 - exit 1 - fi + -t) + if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then EXEC_TIMEOUT="$2"; shift; else echo "错误: -t 需要一个正整数参数。" >&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 + -ct) + if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then GCC_TIMEOUT="$2"; shift; else echo "错误: -ct 需要一个正整数参数。" >&2; exit 1; fi ;; -h|--help) show_help @@ -101,32 +74,26 @@ while [[ "$#" -gt 0 ]]; do done echo "SysY VM 内部测试运行器启动..." -echo "编译超时设置为: ${COMPILE_TIMEOUT_SECONDS} 秒" -echo "运行时超时设置为: ${TIMEOUT_SECONDS} 秒" +echo "GCC 编译超时设置为: ${GCC_TIMEOUT} 秒" +echo "运行时超时设置为: ${EXEC_TIMEOUT} 秒" echo "汇编文件目录: ${TMP_DIR}" -echo "库文件目录: ${LIB_DIR}" -echo "测试数据目录: ${TESTDATA_DIR}" echo "" -# 查找 tmp 目录下的所有 .s 汇编文件 -s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s") +# 查找 tmp 目录下的所有 .s 汇编文件并排序 +s_files=$(find "${TMP_DIR}" -maxdepth 1 -name "*.s" | sort -V) TOTAL_CASES=$(echo "$s_files" | wc -w) -# 遍历找到的每个 .s 文件 -echo "$s_files" | while read s_file; do - # --- 新增功能: 初始化用例通过状态 --- +# 使用 here-string (<<<) 避免子 shell 问题 +while IFS= read -r 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" @@ -136,42 +103,43 @@ echo "$s_files" | while read s_file; do echo " 对应的测试用例路径: ${original_relative_path}" # 步骤 1: 使用 VM 内部的 gcc 编译 .s 到可执行文件 - 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 + echo " 使用 gcc 汇编并链接 (超时 ${GCC_TIMEOUT}s)..." + timeout -s KILL ${GCC_TIMEOUT} "${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} 超时 (超过 ${COMPILE_TIMEOUT_SECONDS} 秒)\e[0m" + echo -e "\e[31m错误: GCC 编译/链接 ${s_file} 超时\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 - else + fi + + # 步骤 2: 只有当编译成功时才执行 + if [ "$is_passed" -eq 1 ]; then echo " 生成的可执行文件: ${executable_file}" - echo " 正在执行 (超时 ${TIMEOUT_SECONDS}s): \"${executable_file}\"" + echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..." - # 步骤 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}" + 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=$? - 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 运行超过 ${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 [ "$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 @@ -183,61 +151,53 @@ echo "$s_files" | while read s_file; do echo -e "\e[32m 标准输出测试成功\e[0m" else echo -e "\e[31m 标准输出测试失败\e[0m" - echo " 差异:" - diff "${output_actual_file}" "${EXPECTED_STDOUT_FILE}" is_passed=0 + 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 - fi - else - 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" + 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" else echo -e "\e[31m 失败: 输出不匹配\e[0m" - echo " 差异:" - diff "${output_actual_file}" "${output_reference_file}" is_passed=0 + 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 - 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}" + echo " 无参考输出文件。程序返回码: ${ACTUAL_RETURN_CODE}" fi fi fi - # --- 新增功能: 更新通过用例计数 --- if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) + else + FAILED_CASES_LIST+="${original_relative_path}.sy\n" fi - echo "" # 为测试用例之间添加一个空行 -done + echo "" +done <<< "$s_files" -# --- 新增功能: 打印最终总结 --- 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 diff --git a/test_script/runit-single.sh b/test_script/runit-single.sh new file mode 100644 index 0000000..70f1196 --- /dev/null +++ b/test_script/runit-single.sh @@ -0,0 +1,225 @@ +#!/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 自动化执行超时 (秒) +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 " -h, --help 显示此帮助信息并退出。" +} + +# --- 参数解析 --- +# 从参数中分离出 .sy 文件和选项 +for arg in "$@"; do + case "$arg" in + -e|--executable) + EXECUTE_MODE=true + ;; + -sct|-gct|-et) + # 选项和其值将在下一个循环中处理 + ;; + -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 ;; + *.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 "" + +for sy_file in "${SY_FILES[@]}"; do + is_passed=1 + base_name=$(basename "${sy_file}" .sy) + source_dir=$(dirname "${sy_file}") + + 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 "${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="\"${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 + 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 ! 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[31m 标准输出测试失败。\e[0m" + is_passed=0 + 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 + 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 diff --git a/test_script/runit.sh b/test_script/runit.sh index ce66b5d..2c34c83 100644 --- a/test_script/runit.sh +++ b/test_script/runit.sh @@ -93,7 +93,7 @@ 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 表示失败 @@ -111,7 +111,8 @@ while IFS= read -r sy_file; do # 步骤 1: 使用 sysyc 编译 .sy 到 .s echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..." - timeout ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}" + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + timeout -s KILL ${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" @@ -125,7 +126,8 @@ 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 ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + timeout -s KILL ${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" @@ -140,7 +142,6 @@ while IFS= read -r sy_file; do if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) else - # --- 本次修改点 --- FAILED_CASES_LIST+="${relative_path_no_ext}.sy\n" fi echo "" @@ -159,7 +160,8 @@ while IFS= read -r sy_file; do exec_cmd+=" > \"${output_actual_file}\"" # 执行并捕获返回码 - eval "timeout ${EXEC_TIMEOUT} ${exec_cmd}" + # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- + eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}" ACTUAL_RETURN_CODE=$? if [ "$ACTUAL_RETURN_CODE" -eq 124 ]; then @@ -219,7 +221,6 @@ while IFS= read -r sy_file; do fi # 更新通过用例计数 - # --- 本次修改点 --- if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) else @@ -234,7 +235,7 @@ echo "========================================" echo "测试完成" echo "测试通过率: [${PASSED_CASES}/${TOTAL_CASES}]" -# --- 本次修改点: 打印未通过的测例列表 --- +# --- 打印未通过的测例列表 --- if [ -n "$FAILED_CASES_LIST" ]; then echo "" echo -e "\e[31m未通过的测例:\e[0m" From 9c87cb397bf1c68187cd722a4d0c7c0515e92e07 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 00:07:54 +0800 Subject: [PATCH 05/12] =?UTF-8?q?[backend]=E8=A7=A3=E5=86=B3=E4=BA=86?= =?UTF-8?q?=E9=9D=9E=E9=9B=B6=E6=95=B0=E7=BB=84=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64ISel.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/RISCv64ISel.cpp b/src/RISCv64ISel.cpp index 2da2373..fdef521 100644 --- a/src/RISCv64ISel.cpp +++ b/src/RISCv64ISel.cpp @@ -238,7 +238,52 @@ void RISCv64ISel::selectNode(DAGNode* node) { auto bin = dynamic_cast(node->value); Value* lhs = bin->getLhs(); Value* rhs = bin->getRhs(); + + // 检查是否是“基地址+偏移量”的地址计算模式 + if (bin->getKind() == BinaryInst::kAdd) { + Value* base = nullptr; + Value* offset = nullptr; + // 判断哪个是基地址(AllocaInst),哪个是偏移量 + if (dynamic_cast(lhs)) { + base = lhs; + offset = rhs; + } else if (dynamic_cast(rhs)) { + base = rhs; + offset = lhs; + } + + // 如果成功匹配到该模式 + if (base) { + // [最终修复] + // 1. 先为偏移量加载常量(如果它是常量的话) + if (auto const_offset = dynamic_cast(offset)) { + auto li = std::make_unique(RVOpcodes::LI); + li->addOperand(std::make_unique(getVReg(const_offset))); + li->addOperand(std::make_unique(const_offset->getInt())); + CurMBB->addInstruction(std::move(li)); + } + + // 2. 使用FRAME_ADDR伪指令来获取基地址 + auto base_addr_vreg = getNewVReg(); // 创建一个新的临时vreg来存放基地址 + auto frame_addr_instr = std::make_unique(RVOpcodes::FRAME_ADDR); + frame_addr_instr->addOperand(std::make_unique(base_addr_vreg)); + frame_addr_instr->addOperand(std::make_unique(getVReg(base))); + CurMBB->addInstruction(std::move(frame_addr_instr)); + + // 3. 生成真正的add指令,计算最终地址 + auto final_addr_vreg = getVReg(bin); // 这是整个二元运算的结果vreg + auto offset_vreg = getVReg(offset); + auto add_instr = std::make_unique(RVOpcodes::ADD); // 指针运算是64位 + add_instr->addOperand(std::make_unique(final_addr_vreg)); + add_instr->addOperand(std::make_unique(base_addr_vreg)); + add_instr->addOperand(std::make_unique(offset_vreg)); + CurMBB->addInstruction(std::move(add_instr)); + + return; // 地址计算处理完毕,直接返回 + } + } + // [V2优点] 在BINARY节点内部按需加载常量操作数。 auto load_val_if_const = [&](Value* val) { if (auto c = dynamic_cast(val)) { From e8fe710c2652d1dccc341be903f7b32f73f3de42 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 00:09:41 +0800 Subject: [PATCH 06/12] =?UTF-8?q?[backend]=E6=9B=B4=E6=96=B0=E4=BA=86?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=84=9A=E6=9C=AC=EF=BC=8C=E9=99=90=E5=88=B6?= =?UTF-8?q?=E6=9C=80=E5=A4=A7=E6=89=93=E5=8D=B0=E8=A1=8C=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E7=B3=9F=E8=B9=8B=E7=BB=88=E7=AB=AF=E8=BE=93?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_script/runit-riscv64.sh | 46 ++++++++++++++++++------- test_script/runit-single.sh | 53 ++++++++++++++++++++++++++--- test_script/runit.sh | 65 +++++++++++++++++++----------------- 3 files changed, 118 insertions(+), 46 deletions(-) diff --git a/test_script/runit-riscv64.sh b/test_script/runit-riscv64.sh index 9978d90..c125bfb 100644 --- a/test_script/runit-riscv64.sh +++ b/test_script/runit-riscv64.sh @@ -16,6 +16,7 @@ GCC_NATIVE="gcc" # VM 内部的 gcc # --- 新增功能: 初始化变量 --- GCC_TIMEOUT=10 # 默认 gcc 编译超时 (秒) EXEC_TIMEOUT=5 # 默认运行时超时 (秒) +MAX_OUTPUT_LINES=50 # 对比失败时显示的最大行数 TOTAL_CASES=0 PASSED_CASES=0 FAILED_CASES_LIST="" # 用于存储未通过的测例列表 @@ -30,9 +31,32 @@ show_help() { echo " -c, --clean 清理 'tmp' 目录下的所有生成文件。" echo " -ct M 设置 gcc 编译的超时时间为 M 秒 (默认: 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 +} + # 清理临时文件的函数 clean_tmp() { echo "正在清理临时目录: ${TMP_DIR}" @@ -60,6 +84,9 @@ while [[ "$#" -gt 0 ]]; do -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 + ;; -h|--help) show_help exit 0 @@ -76,6 +103,7 @@ done echo "SysY VM 内部测试运行器启动..." echo "GCC 编译超时设置为: ${GCC_TIMEOUT} 秒" echo "运行时超时设置为: ${EXEC_TIMEOUT} 秒" +echo "失败输出最大行数: ${MAX_OUTPUT_LINES}" echo "汇编文件目录: ${TMP_DIR}" echo "" @@ -147,30 +175,24 @@ 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[32m 标准输出测试成功\e[0m" - else + 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 - echo -e " \e[36m---------- 期望输出 ----------\e[0m" - cat "${EXPECTED_STDOUT_FILE}" - echo -e " \e[36m---------- 实际输出 ----------\e[0m" - cat "${output_actual_file}" + 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 + 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 - echo -e " \e[36m---------- 期望输出 ----------\e[0m" - cat "${output_reference_file}" - echo -e " \e[36m---------- 实际输出 ----------\e[0m" - cat "${output_actual_file}" + 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 diff --git a/test_script/runit-single.sh b/test_script/runit-single.sh index 70f1196..cb5e5b4 100644 --- a/test_script/runit-single.sh +++ b/test_script/runit-single.sh @@ -23,6 +23,7 @@ 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="" @@ -39,9 +40,33 @@ 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 +} + + # --- 参数解析 --- # 从参数中分离出 .sy 文件和选项 for arg in "$@"; do @@ -49,7 +74,7 @@ for arg in "$@"; do -e|--executable) EXECUTE_MODE=true ;; - -sct|-gct|-et) + -sct|-gct|-et|-ml|--max-lines) # 选项和其值将在下一个循环中处理 ;; -h|--help) @@ -66,6 +91,7 @@ for arg in "$@"; do -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 ;; @@ -102,6 +128,7 @@ 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 @@ -109,6 +136,7 @@ for sy_file in "${SY_FILES[@]}"; do 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" @@ -120,6 +148,15 @@ for sy_file in "${SY_FILES[@]}"; do # 步骤 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" @@ -144,7 +181,7 @@ for sy_file in "${SY_FILES[@]}"; do echo " 检测到 .in/.out 文件,进入自动化测试模式..." echo " 正在执行 (超时 ${EXEC_TIMEOUT}s)..." - exec_cmd="\"${executable_file}\"" + exec_cmd="${QEMU_RISCV64} \"${executable_file}\"" [ -f "${input_file}" ] && exec_cmd+=" < \"${input_file}\"" exec_cmd+=" > \"${output_actual_file}\"" @@ -166,13 +203,21 @@ for sy_file in "${SY_FILES[@]}"; do 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 - 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" + # --- 本次修改点: 使用新函数显示输出 --- + 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 - 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" + # --- 本次修改点: 使用新函数显示输出 --- + 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 diff --git a/test_script/runit.sh b/test_script/runit.sh index 2c34c83..f17e7b2 100644 --- a/test_script/runit.sh +++ b/test_script/runit.sh @@ -21,6 +21,7 @@ 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="" # 用于存储未通过的测例列表 @@ -36,9 +37,32 @@ 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}" @@ -67,6 +91,9 @@ 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 @@ -86,6 +113,7 @@ 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 "" @@ -94,7 +122,6 @@ sy_files=$(find "${TESTDATA_DIR}" -name "*.sy" | sort -V) TOTAL_CASES=$(echo "$sy_files" | wc -w) # --- 修复: 使用 here-string (<<<) 代替管道 (|) 来避免子 shell 问题 --- -# 这样可以确保循环内的 PASSED_CASES 变量修改在循环结束后依然有效 while IFS= read -r sy_file; do is_passed=1 # 1 表示通过, 0 表示失败 @@ -111,7 +138,6 @@ while IFS= read -r sy_file; do # 步骤 1: 使用 sysyc 编译 .sy 到 .s echo " 使用 sysyc 编译 (超时 ${SYSYC_TIMEOUT}s)..." - # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- timeout -s KILL ${SYSYC_TIMEOUT} "${SYSYC}" -S "${sy_file}" -o "${assembly_file}" SYSYC_STATUS=$? if [ $SYSYC_STATUS -eq 124 ]; then @@ -126,7 +152,6 @@ 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)..." - # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- timeout -s KILL ${GCC_TIMEOUT} "${GCC_RISCV64}" "${assembly_file}" -o "${executable_file}" -L"${LIB_DIR}" -lsysy_riscv -static GCC_STATUS=$? if [ $GCC_STATUS -eq 124 ]; then @@ -138,7 +163,6 @@ while IFS= read -r sy_file; do fi elif ! ${EXECUTE_MODE}; then echo " 跳过执行模式。仅生成汇编文件。" - # 如果只编译不执行,只要编译成功就算通过 if [ "$is_passed" -eq 1 ]; then ((PASSED_CASES++)) else @@ -152,15 +176,12 @@ 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}\"" - # 执行并捕获返回码 - # --- 本次修改点: 增加 -s KILL 确保超时后进程被终止 --- eval "timeout -s KILL ${EXEC_TIMEOUT} ${exec_cmd}" ACTUAL_RETURN_CODE=$? @@ -168,7 +189,6 @@ while IFS= read -r sy_file; do 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:]') @@ -177,69 +197,54 @@ 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 - echo -e "\e[32m 标准输出测试成功\e[0m" - else + 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 - echo -e " \e[36m---------- 期望输出 ----------\e[0m" - cat "${EXPECTED_STDOUT_FILE}" - echo -e " \e[36m---------- 实际输出 ----------\e[0m" - cat "${output_actual_file}" + 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 + 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 - echo -e " \e[36m---------- 期望输出 ----------\e[0m" - cat "${output_reference_file}" - echo -e " \e[36m---------- 实际输出 ----------\e[0m" - cat "${output_actual_file}" + 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 - # 没有 .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 From fd6fe22020df1384b04a6f5489d59b69faf4cab5 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 00:11:42 +0800 Subject: [PATCH 07/12] =?UTF-8?q?[backend]=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E5=AF=B9=E5=85=A8=E5=B1=80=E6=95=B0=E7=BB=84=E7=9A=84=E8=AE=BF?= =?UTF-8?q?=E5=AD=98=E5=9C=B0=E5=9D=80=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/AddressCalculationExpansion.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/AddressCalculationExpansion.cpp b/src/AddressCalculationExpansion.cpp index d157528..12712ae 100644 --- a/src/AddressCalculationExpansion.cpp +++ b/src/AddressCalculationExpansion.cpp @@ -57,11 +57,22 @@ bool AddressCalculationExpansion::run() { } } } else if (GlobalValue* globalValue = dynamic_cast(basePointer)) { - std::cerr << "Warning: GlobalValue dimension handling needs explicit implementation for GEP expansion. Skipping GEP for: "; - SysYPrinter::printValue(globalValue); - std::cerr << "\n"; - ++it; - continue; + // 遍历 GlobalValue 的所有维度操作数 + for (const auto& use_ptr : globalValue->getDims()) { + Value* dimValue = use_ptr->getValue(); + // 将维度值转换为常量整数 + if (ConstantInteger* constVal = dynamic_cast(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 { 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); From cf88ca77cb22868d9922eed8e351ee1f85af4383 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 00:16:37 +0800 Subject: [PATCH 08/12] =?UTF-8?q?[backend]=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=85=A8=E5=B1=80=E6=95=B0=E7=BB=84=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E7=9A=84=E8=AE=A1=E7=AE=97=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64ISel.cpp | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/RISCv64ISel.cpp b/src/RISCv64ISel.cpp index fdef521..f8a1e8e 100644 --- a/src/RISCv64ISel.cpp +++ b/src/RISCv64ISel.cpp @@ -239,23 +239,21 @@ void RISCv64ISel::selectNode(DAGNode* node) { Value* lhs = bin->getLhs(); Value* rhs = bin->getRhs(); - // 检查是否是“基地址+偏移量”的地址计算模式 if (bin->getKind() == BinaryInst::kAdd) { Value* base = nullptr; Value* offset = nullptr; - // 判断哪个是基地址(AllocaInst),哪个是偏移量 - if (dynamic_cast(lhs)) { + // [修改] 扩展基地址的判断,使其可以识别 AllocaInst 或 GlobalValue + if (dynamic_cast(lhs) || dynamic_cast(lhs)) { base = lhs; offset = rhs; - } else if (dynamic_cast(rhs)) { + } else if (dynamic_cast(rhs) || dynamic_cast(rhs)) { base = rhs; offset = lhs; } - // 如果成功匹配到该模式 + // 如果成功匹配到地址计算模式 if (base) { - // [最终修复] // 1. 先为偏移量加载常量(如果它是常量的话) if (auto const_offset = dynamic_cast(offset)) { auto li = std::make_unique(RVOpcodes::LI); @@ -264,14 +262,25 @@ void RISCv64ISel::selectNode(DAGNode* node) { CurMBB->addInstruction(std::move(li)); } - // 2. 使用FRAME_ADDR伪指令来获取基地址 + // 2. [修改] 根据基地址的类型,生成不同的指令来获取基地址 auto base_addr_vreg = getNewVReg(); // 创建一个新的临时vreg来存放基地址 - auto frame_addr_instr = std::make_unique(RVOpcodes::FRAME_ADDR); - frame_addr_instr->addOperand(std::make_unique(base_addr_vreg)); - frame_addr_instr->addOperand(std::make_unique(getVReg(base))); - CurMBB->addInstruction(std::move(frame_addr_instr)); - // 3. 生成真正的add指令,计算最终地址 + // 情况一:基地址是局部栈变量 + if (auto alloca_base = dynamic_cast(base)) { + auto frame_addr_instr = std::make_unique(RVOpcodes::FRAME_ADDR); + frame_addr_instr->addOperand(std::make_unique(base_addr_vreg)); + frame_addr_instr->addOperand(std::make_unique(getVReg(alloca_base))); + CurMBB->addInstruction(std::move(frame_addr_instr)); + } + // 情况二:基地址是全局变量 + else if (auto global_base = dynamic_cast(base)) { + auto la_instr = std::make_unique(RVOpcodes::LA); + la_instr->addOperand(std::make_unique(base_addr_vreg)); + la_instr->addOperand(std::make_unique(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(RVOpcodes::ADD); // 指针运算是64位 @@ -283,7 +292,7 @@ void RISCv64ISel::selectNode(DAGNode* node) { return; // 地址计算处理完毕,直接返回 } } - + // [V2优点] 在BINARY节点内部按需加载常量操作数。 auto load_val_if_const = [&](Value* val) { if (auto c = dynamic_cast(val)) { From 20a5c5cbfb1e88d2613e5b0b30f563b63691c1f4 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 02:25:30 +0800 Subject: [PATCH 09/12] =?UTF-8?q?[backend]=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=9C=A8=E8=99=9A=E6=8B=9F=E6=9C=BA=E5=86=85?= =?UTF-8?q?=E9=83=A8=E5=8D=95=E6=96=87=E4=BB=B6=E8=B0=83=E8=AF=95=E7=9A=84?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_script/runit-riscv64-single.sh | 227 ++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 test_script/runit-riscv64-single.sh diff --git a/test_script/runit-riscv64-single.sh b/test_script/runit-riscv64-single.sh new file mode 100644 index 0000000..3e2c5c7 --- /dev/null +++ b/test_script/runit-riscv64-single.sh @@ -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 From b20bda2f52ac6cae5840604e8ef379e4bff1225a Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 21:36:22 +0800 Subject: [PATCH 10/12] =?UTF-8?q?[backend]=E5=BC=95=E5=85=A5=E4=BA=86?= =?UTF-8?q?=E6=A0=88=E4=B8=8A=E7=9A=8420=E5=AD=97=E8=8A=82=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64RegAlloc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RISCv64RegAlloc.cpp b/src/RISCv64RegAlloc.cpp index d4c69e7..0c0a4a3 100644 --- a/src/RISCv64RegAlloc.cpp +++ b/src/RISCv64RegAlloc.cpp @@ -27,7 +27,8 @@ void RISCv64RegAlloc::run() { void RISCv64RegAlloc::eliminateFrameIndices() { StackFrameInfo& frame_info = MFunc->getFrameInfo(); - int current_offset = 0; + int current_offset = 20; // 这里写20是为了在$s0和第一个变量之间留出20字节的安全区, + // 以防止一些函数调用方面的恶性bug。 Function* F = MFunc->getFunc(); RISCv64ISel* isel = MFunc->getISel(); From 2040670f8c2d2d832c3f24058a4a56e41dfb593e Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Tue, 22 Jul 2025 22:50:25 +0800 Subject: [PATCH 11/12] =?UTF-8?q?[backend]=E6=B7=BB=E5=8A=A0=E4=BA=86DAG?= =?UTF-8?q?=E5=9B=BE=E6=89=93=E5=8D=B0=E5=87=BD=E6=95=B0=EF=BC=9B=E4=B8=BA?= =?UTF-8?q?=E5=88=86=E6=94=AF=E6=8C=87=E4=BB=A4=E5=BC=95=E5=85=A5=E4=BA=86?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E7=89=A9=E5=8C=96=EF=BC=9B=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E5=87=BD=E6=95=B0=E5=8F=82=E6=95=B0=E6=BA=A2=E5=87=BA?= =?UTF-8?q?=E5=88=B0=E6=A0=88=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64ISel.cpp | 210 +++++++++++++++++++++++++++++++++++--- src/include/RISCv64ISel.h | 2 + 2 files changed, 196 insertions(+), 16 deletions(-) diff --git a/src/RISCv64ISel.cpp b/src/RISCv64ISel.cpp index f8a1e8e..2ab47b5 100644 --- a/src/RISCv64ISel.cpp +++ b/src/RISCv64ISel.cpp @@ -83,6 +83,10 @@ 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_to_node; for(const auto& node : dag) { if (node->value) { @@ -486,7 +490,9 @@ void RISCv64ISel::selectNode(DAGNode* node) { case DAGNode::CALL: { auto call = dynamic_cast(node->value); // 处理函数参数,放入a0-a7物理寄存器 - for (size_t i = 0; i < node->operands.size() && i < 8; ++i) { + 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]; auto arg_preg = static_cast(static_cast(PhysicalReg::A0) + i); @@ -505,11 +511,63 @@ 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(RVOpcodes::ADDI); + alloc_instr->addOperand(std::make_unique(PhysicalReg::SP)); + alloc_instr->addOperand(std::make_unique(PhysicalReg::SP)); + alloc_instr->addOperand(std::make_unique(-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(arg_node->value); + auto li = std::make_unique(RVOpcodes::LI); + li->addOperand(std::make_unique(src_vreg)); + li->addOperand(std::make_unique(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(RVOpcodes::SD); + sd_instr->addOperand(std::make_unique(src_vreg)); + sd_instr->addOperand(std::make_unique( + std::make_unique(PhysicalReg::SP), + std::make_unique(offset) + )); + CurMBB->addInstruction(std::move(sd_instr)); + } + } auto call_instr = std::make_unique(RVOpcodes::CALL); call_instr->addOperand(std::make_unique(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(RVOpcodes::ADDI); + dealloc_instr->addOperand(std::make_unique(PhysicalReg::SP)); + dealloc_instr->addOperand(std::make_unique(PhysicalReg::SP)); + dealloc_instr->addOperand(std::make_unique(stack_space)); + CurMBB->addInstruction(std::move(dealloc_instr)); + } // 处理返回值,从a0移动到目标虚拟寄存器 if (!call->getType()->isVoid()) { auto mv_instr = std::make_unique(RVOpcodes::MV); @@ -545,33 +603,59 @@ void RISCv64ISel::selectNode(DAGNode* node) { } case DAGNode::BRANCH: { - if (auto cond_br = dynamic_cast(node->value)) { - // [V2优点] 采用更健壮的if-then-else分支逻辑。 - auto cond_vreg = getVReg(cond_br->getCondition()); - auto then_bb_name = cond_br->getThenBlock()->getName(); - auto else_bb_name = cond_br->getElseBlock()->getName(); + // 处理条件分支 + if (auto cond_br = dynamic_cast(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(condition)) { + // 如果条件是常量,直接生成一个无条件跳转J,而不是BNE + if (const_cond->getInt() != 0) { // 条件为 true + auto j_instr = std::make_unique(RVOpcodes::J); + j_instr->addOperand(std::make_unique(then_bb_name)); + CurMBB->addInstruction(std::move(j_instr)); + } else { // 条件为 false + auto j_instr = std::make_unique(RVOpcodes::J); + j_instr->addOperand(std::make_unique(else_bb_name)); + CurMBB->addInstruction(std::move(j_instr)); + } + } + // 如果条件不是常量,则执行标准流程 + else { + // [修复] 为条件变量生成加载指令(如果它是常量的话,尽管上面已经处理了) + // 这一步是为了逻辑完整,以防有其他类型的常量没有被捕获 + if (auto const_val = dynamic_cast(condition)) { + auto li = std::make_unique(RVOpcodes::LI); + li->addOperand(std::make_unique(getVReg(const_val))); + li->addOperand(std::make_unique(const_val->getInt())); + CurMBB->addInstruction(std::move(li)); + } + + auto cond_vreg = getVReg(condition); - // bne cond, zero, then_label (如果cond不为0,则跳转到then) + // 生成 bne cond, zero, then_label (如果cond不为0,则跳转到then) auto br_instr = std::make_unique(RVOpcodes::BNE); br_instr->addOperand(std::make_unique(cond_vreg)); br_instr->addOperand(std::make_unique(PhysicalReg::ZERO)); br_instr->addOperand(std::make_unique(then_bb_name)); CurMBB->addInstruction(std::move(br_instr)); - // 无条件跳转到else块 (如果上面分支未发生) - // 注意:在实际的CFG中,这个J指令可能不是必须的, - // 因为else块可能是下一个块。但为了通用性,这里生成它。 + // 为else分支生成无条件跳转 (后续Pass可以优化掉不必要的跳转) auto j_instr = std::make_unique(RVOpcodes::J); j_instr->addOperand(std::make_unique(else_bb_name)); CurMBB->addInstruction(std::move(j_instr)); - - } else if (auto uncond_br = dynamic_cast(node->value)) { - auto j_instr = std::make_unique(RVOpcodes::J); - j_instr->addOperand(std::make_unique(uncond_br->getBlock()->getName())); - CurMBB->addInstruction(std::move(j_instr)); } - break; + } + // 处理无条件分支 + else if (auto uncond_br = dynamic_cast(node->value)) { + auto j_instr = std::make_unique(RVOpcodes::J); + j_instr->addOperand(std::make_unique(uncond_br->getBlock()->getName())); + CurMBB->addInstruction(std::move(j_instr)); } + break; + } case DAGNode::MEMSET: { // [V1设计保留] Memset的核心展开逻辑在虚拟寄存器层面是正确的,无需修改。 @@ -808,4 +892,98 @@ std::vector> RISCv64ISel::build_dag(BasicB return nodes_storage; } +// [新] 打印DAG图以供调试的辅助函数 +void RISCv64ISel::print_dag(const std::vector>& dag, const std::string& bb_name) { + // 检查是否有DEBUG宏或者全局变量,避免在非调试模式下打印 + // if (!DEBUG) return; + + std::cerr << "=== DAG for Basic Block: " << bb_name << " ===\n"; + std::set visited; + + // 为节点分配临时ID,方便阅读 + std::map 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 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(node->value)) { + std::cerr << inst->getKindString(); + if (!inst->getName().empty()) { + std::cerr << "(" << inst->getName() << ")"; + } + } else if (auto constant = dynamic_cast(node->value)) { + std::cerr << "Const(" << constant->getInt() << ")"; + } else if (auto global = dynamic_cast(node->value)) { + std::cerr << "Global(" << global->getName() << ")"; + } else if (auto alloca = dynamic_cast(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 \ No newline at end of file diff --git a/src/include/RISCv64ISel.h b/src/include/RISCv64ISel.h index 6c5e421..0bb977a 100644 --- a/src/include/RISCv64ISel.h +++ b/src/include/RISCv64ISel.h @@ -34,6 +34,8 @@ private: DAGNode* get_operand_node(Value* val_ir, std::map&, std::vector>&); DAGNode* create_node(int kind, Value* val, std::map&, std::vector>&); + void print_dag(const std::vector>& dag, const std::string& bb_name); + // 状态 Function* F; // 当前处理的高层IR函数 std::unique_ptr MFunc; // 正在构建的底层LLIR函数 From 0e492cd6d77d9742879e06264f1c3cbd639a9e79 Mon Sep 17 00:00:00 2001 From: Lixuanwang Date: Wed, 23 Jul 2025 18:43:40 +0800 Subject: [PATCH 12/12] =?UTF-8?q?[backend]=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=BA=8F=E8=A8=80=E7=9A=84=E6=A0=B9=E6=9C=AC?= =?UTF-8?q?=E6=80=A7=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/RISCv64AsmPrinter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RISCv64AsmPrinter.cpp b/src/RISCv64AsmPrinter.cpp index 9482a58..1995024 100644 --- a/src/RISCv64AsmPrinter.cpp +++ b/src/RISCv64AsmPrinter.cpp @@ -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 << " mv s0, sp\n"; + *OS << " addi s0, sp, " << aligned_stack_size << "\n"; } // 忠实还原保存函数入口参数的逻辑