diff --git a/doc/Lab3-指令选择与汇编生成.md b/doc/Lab3-指令选择与汇编生成.md index fa66dcb..3d8551f 100644 --- a/doc/Lab3-指令选择与汇编生成.md +++ b/doc/Lab3-指令选择与汇编生成.md @@ -1,60 +1,398 @@ -# Lab3:指令选择与汇编生成 +# Lab3 代码生成实现核查说明 -## 1. 本实验定位 +## 1. 文档定位 -本仓库当前提供了一个“最小可运行”的 IR -> AArch64 汇编示例链路。 -Lab3 的目标是在该示例基础上扩展后端语义覆盖范围,逐步把更多 SysY IR 正确翻译为目标平台汇编代码。 +本文档不是继续宣传“Lab3 已经按课件要求完整自研实现”,而是对当前仓库中的 Lab3 代码生成方案做一次基于课件标准的核查。 -## 2. Lab3 要求 +核查依据是以下三份材料: -需要同学完成: +- `lab03-code generation-2026.pdf` +- `lecture05-instruction selection-169.pdf` +- `lecture11-register allocation-part2-169.pdf` -1. 熟悉 MIR 相关数据结构与后端阶段接口。 -2. 理解当前 IR -> MIR -> 汇编输出的最小实现流程。 -3. 在现有框架上扩展后端代码生成能力,使其覆盖课程要求的 SysY 语义。 +本文档要回答的问题只有两个: -## 3. 相关文件 +1. 当前仓库里的 Lab3,是否严格按这三份课件的标准完成。 +2. 如果没有,是哪一部分没有按标准完成;当前真实实现到底是什么。 -以下文件与本实验内容相关,建议优先阅读。 +--- -- `include/mir/MIR.h` -- `src/mir/Lowering.cpp` -- `src/mir/RegAlloc.cpp` -- `src/mir/FrameLowering.cpp` -- `src/mir/AsmPrinter.cpp` +## 2. 核查结论 -## 4. 当前最小示例实现说明 +结论先给出: -当前 IR -> 汇编仅覆盖最小子集: +- 当前 Lab3 **可以跑通测试**。 +- 当前 Lab3 **不符合**“按 lecture05 手写指令选择 + 按 lecture11 手写线性扫描寄存器分配 + 按 lab03 手写栈布局”的严格标准。 -1. 仅支持单函数 `main`、单基本块的最小流程。 -2. 仅支持由当前 Lab2 最小 IR 产生的 `alloca`、`load`、`store`、`add`、`ret`。 -3. 局部变量与中间结果当前统一采用栈槽模型:所有值先映射到栈槽,再通过固定寄存器 `w0`、`w8`、`w9` 配合 `ldur/stur/add` 生成汇编。 -4. `RegAlloc` 当前仅执行最小一致性检查,不实现真实寄存器分配。 -5. `FrameLowering` 当前会插入最小序言/尾声,并按 16 字节对齐栈帧。 +更具体地说: -说明:当前阶段后端主要用于演示完整流程。即使中间值可以暂存在寄存器中,也会先写回栈槽,而不是直接构造更接近最终机器代码的寄存器流。后续实验中,同学可按需求继续扩展指令选择、寄存器分配、调用约定与控制流相关功能。 +1. 当前 `--emit-asm` 不是走仓库内自研 `mir` 后端生成汇编。 +2. 当前仓库里也不存在一个 lecture11 风格的线性扫描寄存器分配器。 +3. 当前仓库里也不存在一套覆盖完整 SysY 测试集的自研 AArch64 栈布局与调用约定实现。 +4. 当前测试之所以能全通过,是因为 `--emit-asm` 实际改成了: + - `SysY 前端 -> IR -> IR Pass Pipeline -> llc -> AArch64 汇编` -## 5. 构建与运行 +所以如果按照“工程结果”衡量: -```bash -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -cmake --build build -j "$(nproc)" -``` +- 这轮实现是成功的,汇编能生成,`test/` 能全过。 -## 6. Lab3 验证方式 +如果按照“课程实现路径”衡量: -可先用单个样例检查汇编输出是否基本正确: +- 这轮实现不是严格按你现在指定的课件标准完成的。 -```bash -./build/bin/compiler --emit-asm test/test_case/functional/simple_add.sy -``` +--- -推荐使用统一脚本验证 “源码 -> 汇编 -> 可执行程序” 整体链路。`--run` 模式下会自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对,用于验证后端代码生成的正确性: +## 3. 课件标准是什么 -```bash -./scripts/verify_asm.sh test/test_case/functional/simple_add.sy test/test_result/function/asm --run -``` +## 3.1 lecture05 对指令选择的要求 -若最终输出 `输出匹配: test/test_case/simple_add.out`,说明当前示例用例 `return a + b` 的完整后端链路已经跑通。 -但最终不能只检查 `simple_add`。完成 Lab3 后,应对 `test/test_case` 下全部测试用例逐个回归,确认代码生成结果能够通过统一验证;如有需要,也可以自行编写批量测试脚本统一执行。 +从 `lecture05-instruction selection-169.pdf` 中可以提炼出两点核心要求: + +1. 指令选择是把 IR 翻译为目标 ISA 指令序列的过程。 +2. 对本课程实验语境,采用的是“宏扩展 / 逐条翻译(one-by-one translation)”的思路。 + +也就是说,按这份课件理解,Lab3 期待的实现方式应当是: + +- 编译器自己遍历 IR +- 根据每一条 IR 指令选择对应的 AArch64 指令序列 +- 这个逻辑应体现在仓库自己的 lowering / instruction selection 代码中 + +而不是把 IR 直接交给外部成熟后端黑箱完成。 + +## 3.2 lecture11 对寄存器分配的要求 + +从 `lecture11-register allocation-part2-169.pdf` 的 `10.6 基于线性扫描的寄存器分配方法` 可提炼出下面这些明确要素: + +1. 中间表示应是三地址码或伪指令。 +2. 操作数应先用虚寄存器表示。 +3. 基本块线性排序,得到可编号的指令序列。 +4. 计算 live interval。 +5. 维护按结束点排序的 `active` 表。 +6. 实现: + - `ExpireOldIntervals(i)` + - `SpillAtInterval(i)` +7. 当物理寄存器不够时,做溢出并分配栈位置。 + +也就是说,按课件标准,仓库里应该能看到一套清晰的: + +- 虚寄存器表示 +- 活跃区间构造 +- 线性扫描主循环 +- spill / reload 策略 + +## 3.3 lab03 对栈布局与代码生成的要求 + +从 `lab03-code generation-2026.pdf` 中,可以提炼出下面这些与实现直接相关的要求: + +1. 实验方法明确写的是: + - 基于宏扩展的指令选择方法 + - 自顶向下逐条翻译 +2. 应从 `IR Module` 开始遍历,逐条翻译生成 ARM 汇编。 +3. 函数调用遵循 AAPCS64: + - 前 8 个整数参数通过 `x0~x7` + - 其余参数通过栈传递 + - 返回值通过 `x0` +4. 栈采用 `Full Descending`,高地址向低地址增长。 +5. 栈帧大小按 16 字节对齐。 +6. 需要正确处理: + - `x29(fp)` / `x30(lr)` + - `sp` + - caller / callee 保存规则 + - 栈上传参与局部对象布局 +7. 课件中给出的实验实现路线是: + - 遍历 Module / Function / BasicBlock / Instruction + - 将 IR 逐条翻译成对应的汇编代码 + +也就是说,按 Lab3 讲义标准,期望看到的是一套在仓库内显式实现的: + +- instruction lowering +- frame lowering +- call lowering +- assembly printing + +--- + +## 4. 当前代码实际是什么 + +## 4.1 `src/main.cpp` + +当前 `--emit-asm` 的入口逻辑已经不是: + +- `mir::LowerToMIR` +- `mir::RunRegAlloc` +- `mir::RunFrameLowering` +- `mir::PrintAsm` + +而是: + +1. 生成 IR。 +2. 跑 `ir::RunIRPassPipeline(*module)`。 +3. 调用 `EmitAsmWithLLC(*module, std::cout)`。 +4. 在 `EmitAsmWithLLC` 中: + - 用 `IRPrinter` 把 IR 写到临时 `.ll` + - 调用外部 `llc -mtriple=aarch64-linux-gnu -filetype=asm` + - 读取生成的 `.s` 并输出 + +因此,当前真正完成指令选择、寄存器分配、栈布局和 ABI 细节的主体,不是仓库内 `mir`,而是 LLVM 的 `llc`。 + +这是当前核查结论里最关键的一点。 + +## 4.2 `include/mir/MIR.h` + +当前 `MIR.h` 仍然只是非常小的骨架,主要特征如下: + +1. 物理寄存器只有: + - `W0` + - `W8` + - `W9` + - `X29` + - `X30` + - `SP` +2. 指令种类只有: + - `Prologue` + - `Epilogue` + - `MovImm` + - `LoadStack` + - `StoreStack` + - `AddRR` + - `Ret` +3. `MachineFunction` 只有一个 `entry_` 基本块。 +4. 没有虚寄存器。 +5. 没有 live interval 结构。 +6. 没有 CFG 级别的机器基本块管理。 + +这套数据结构本身就还没有达到 lecture11 线性扫描寄存器分配所需的表示能力。 + +## 4.3 `src/mir/Lowering.cpp` + +当前 `Lowering.cpp` 的事实是: + +1. 只支持极少数 IR 指令: + - `Alloca` + - `Store` + - `Load` + - `Add` + - `Ret` +2. `Sub`、`Mul` 直接报不支持。 +3. 其他大多数 IR 指令直接报不支持。 +4. 只支持单函数,且函数名必须是 `main`。 +5. 只处理入口基本块。 + +这与 lab03 讲义中要求的“遍历 Module / Function / BasicBlock / Instruction,逐条生成汇编”明显不一致。 + +## 4.4 `src/mir/RegAlloc.cpp` + +当前 `RunRegAlloc` 的行为仅仅是: + +- 遍历当前 MIR 指令的操作数 +- 检查寄存器是不是落在一小组允许的物理寄存器集合中 + +它没有做下面任何一件 lecture11 线性扫描要求的事情: + +1. 没有虚寄存器。 +2. 没有线性化指令编号。 +3. 没有 live interval。 +4. 没有 `active` 表。 +5. 没有 `ExpireOldIntervals`。 +6. 没有 `SpillAtInterval`。 +7. 没有物理寄存器池管理。 +8. 没有 spill / reload 代码插入。 + +所以这一部分不能被称为“按 lecture11 实现了线性扫描寄存器分配”。 + +## 4.5 `src/mir/FrameLowering.cpp` + +当前 `RunFrameLowering` 做的事情只有: + +1. 按 frame slot 顺序累计大小。 +2. 给每个 slot 分配一个负偏移。 +3. 把 frame size 对齐到 16 字节。 +4. 在入口插 `Prologue`。 +5. 在 `Ret` 前插 `Epilogue`。 + +它没有显式实现 lab03 课件要求的完整内容,例如: + +1. 参数区布局。 +2. 栈上传参。 +3. caller / callee saved 寄存器管理。 +4. 叶子函数 / 非叶子函数差异。 +5. 函数调用下的 outgoing arg area。 +6. 完整的 AAPCS64 栈帧组织。 + +因此,这一部分也不能称为“已经按 lab03 讲义要求完成了完整栈布局实现”。 + +## 4.6 `src/mir/AsmPrinter.cpp` + +当前汇编打印器只会输出极少数内容: + +- `.text/.global/.type/.size` +- `stp/ldp` +- `mov` +- `sub/add sp` +- `ldur/stur` +- `add` +- `ret` + +没有覆盖: + +- 条件跳转 +- 比较 +- 调用 `bl` +- 参数传递 +- 浮点指令 +- 更完整的算术和逻辑指令 +- 全局地址访问 + +因此也不可能独立支撑完整 SysY 测试集的后端输出。 + +--- + +## 5. 对照结论:哪些符合,哪些不符合 + +## 5.1 是否按 lecture05 的指令选择标准完成 + +结论:**不符合严格标准**。 + +原因: + +- 课件要求的是编译器内部做“宏扩展、逐条翻译”。 +- 当前仓库实际生成汇编时,已经绕开了内部 `mir::LowerToMIR` 主路径,改由 `llc` 完成最终 AArch64 instruction selection。 + +可以说: + +- 当前结果在“效果上”完成了指令选择。 +- 但不是“仓库内按 lecture05 自己写出来的 instruction selection”。 + +## 5.2 是否按 lecture11 的线性扫描寄存器分配标准完成 + +结论:**不符合**。 + +原因: + +- 当前 `RegAlloc.cpp` 只是一个物理寄存器白名单检查器。 +- 没有虚寄存器、活跃区间、`active` 表、溢出策略,也没有任何线性扫描主循环。 + +因此不能把当前实现描述成“已经按 lecture11 完成线性扫描寄存器分配”。 + +## 5.3 是否按 lab03 的栈布局和代码生成标准完成 + +结论:**不符合严格标准**。 + +原因: + +- 当前仓库内自研 `FrameLowering` 只实现了极小的顺序布局和简单序言/尾声。 +- 完整的 AAPCS64 参数传递、调用保存/被调用保存、栈上传参、非叶子函数等逻辑并未在仓库自研后端里完整实现。 +- 当前这些工作实际由 `llc` 负责完成。 + +所以: + +- 按“汇编结果”看,栈布局和 ABI 是正确的。 +- 按“仓库内是否手写完成了 lab03 要求的实现”看,不是。 + +--- + +## 6. 当前实现为什么仍然能全通过测试 + +虽然不符合你现在指定的“自研实现路径”标准,但当前版本仍然能跑通测试,原因很直接: + +1. 前端和 IR 生成已经稳定。 +2. IR pass pipeline 已经可用。 +3. 输出的 IR 足够接近 LLVM IR。 +4. `llc` 负责了: + - AArch64 指令选择 + - 寄存器分配 + - 栈帧布局 + - 调用约定处理 +5. `verify_asm.sh` 会把生成的汇编和 `sylib/sylib.c` 一起链接。 +6. `lab3_build_test.sh` 会批量编译并在 `qemu-aarch64` 上运行验证。 + +因此,当前全通过是一个真实结果,但其来源是“LLVM 后端能力”,不是“仓库内自研完整后端能力”。 + +--- + +## 7. 当前真实可宣称的成果 + +当前仓库可以真实宣称的 Lab3 成果是: + +1. `compiler --emit-asm` 已可生成 AArch64 汇编。 +2. 生成的汇编可与 `sylib` 链接,并在 `qemu-aarch64` 上运行。 +3. 当前 `test/` 目录全量测试通过。 +4. 已具备 Lab3 的单样例验证脚本和批量验证脚本。 + +当前**不能**真实宣称的成果是: + +1. 已按 lecture05 完整自研指令选择。 +2. 已按 lecture11 完整自研线性扫描寄存器分配。 +3. 已按 lab03 完整自研 AArch64 栈布局与调用约定实现。 + +--- + +## 8. 本次测试结果 + +本次实际跑过的 Lab3 批量验证结果是: + +- `214 PASS / 0 FAIL / total 214` + +默认测试范围包括: + +- `test/test_case` +- `test/class_test_case` + +对应日志目录为: + +- `output/logs/lab3/lab3_20260410_104639` + +完整日志文件为: + +- `output/logs/lab3/lab3_20260410_104639/whole.log` + +--- + +## 9. 如果要真正改成“按课件标准完成”,还缺什么 + +如果后续目标改成: + +- 必须让仓库里的自研后端本身满足 lecture05 / lecture11 / lab03 + +那么后续至少还需要补下面这些内容: + +1. 扩展 MIR 数据结构 + - 虚寄存器 + - 多基本块 + - CFG + - use/def 与编号 + - spill slot / stack object 表示 +2. 重新实现 instruction selection + - 覆盖 IR 的主要指令种类 + - 覆盖函数、调用、分支、数组、全局对象 +3. 手写线性扫描寄存器分配 + - linear order + - live interval + - active 表 + - spill / reload +4. 手写 frame lowering + - 参数区 + - caller/callee saved + - outgoing arg area + - 16-byte alignment + - 叶子/非叶子函数处理 +5. 扩展 asm printer + - 条件跳转 + - 比较 + - 调用 + - 整数/浮点算术 + - 内存寻址 + - 全局地址获取 + +也就是说,如果按课程标准继续推进,当前仓库还差的是“一套真正可独立工作的自研 AArch64 后端”,而不是只差一两个小补丁。 + +--- + +## 10. 最终建议 + +对当前仓库的 Lab3 状态,建议对外统一表述为: + +- 当前版本已经具备完整的 AArch64 汇编生成与测试验证链路,且 `test/` 全量通过。 +- 但当前实现采用的是 `IR -> llc` 的代码生成路径,不应表述为“已按 lecture05 / lecture11 / lab03 在仓库内部完整自研实现后端”。 + +这是当前最准确、也最不容易误导队友的说法。 diff --git a/scripts/lab3_build_test.sh b/scripts/lab3_build_test.sh new file mode 100644 index 0000000..4b8286f --- /dev/null +++ b/scripts/lab3_build_test.sh @@ -0,0 +1,243 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +VERIFY_SCRIPT="$REPO_ROOT/scripts/verify_asm.sh" +BUILD_DIR="$REPO_ROOT/build_lab3" +RUN_ROOT="$REPO_ROOT/output/logs/lab3" +LAST_RUN_FILE="$RUN_ROOT/last_run.txt" +LAST_FAILED_FILE="$RUN_ROOT/last_failed.txt" +RUN_NAME="lab3_$(date +%Y%m%d_%H%M%S)" +RUN_DIR="$RUN_ROOT/$RUN_NAME" +WHOLE_LOG="$RUN_DIR/whole.log" +FAIL_DIR="$RUN_DIR/failures" +LEGACY_SAVE_ASM=false +FAILED_ONLY=false +FALLBACK_TO_FULL=false + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +TEST_DIRS=() +TEST_FILES=() +while [[ $# -gt 0 ]]; do + case "$1" in + --save-asm) + LEGACY_SAVE_ASM=true + ;; + --failed-only) + FAILED_ONLY=true + ;; + *) + if [[ -f "$1" ]]; then + TEST_FILES+=("$1") + else + TEST_DIRS+=("$1") + fi + ;; + esac + shift +done + +mkdir -p "$RUN_DIR" +: > "$WHOLE_LOG" +printf '%s\n' "$RUN_DIR" > "$LAST_RUN_FILE" + +log_plain() { + printf '%s\n' "$*" + printf '%s\n' "$*" >> "$WHOLE_LOG" +} + +log_color() { + local color="$1" + shift + local message="$*" + printf '%b%s%b\n' "$color" "$message" "$NC" + printf '%s\n' "$message" >> "$WHOLE_LOG" +} + +append_file_to_whole_log() { + local title="$1" + local file="$2" + { + printf '\n===== %s =====\n' "$title" + cat "$file" + printf '\n' + } >> "$WHOLE_LOG" +} + +cleanup_tmp_dir() { + local dir="$1" + if [[ -d "$dir" ]]; then + rm -rf "$dir" + fi +} + +discover_default_test_dirs() { + local roots=( + "$REPO_ROOT/test/test_case" + "$REPO_ROOT/test/class_test_case" + ) + local root + for root in "${roots[@]}"; do + [[ -d "$root" ]] || continue + find "$root" -mindepth 1 -maxdepth 1 -type d -print0 + done | sort -z +} + +prune_empty_run_dirs() { + if [[ -d "$RUN_DIR/.tmp" ]]; then + rmdir "$RUN_DIR/.tmp" 2>/dev/null || true + fi + if [[ -d "$FAIL_DIR" ]]; then + rmdir "$FAIL_DIR" 2>/dev/null || true + fi +} + +test_one() { + local sy_file="$1" + local rel="$2" + local safe_name="${rel//\//_}" + local case_key="${safe_name%.sy}" + local tmp_dir="$RUN_DIR/.tmp/$case_key" + local fail_case_dir="$FAIL_DIR/$case_key" + local case_log="$tmp_dir/error.log" + + cleanup_tmp_dir "$tmp_dir" + cleanup_tmp_dir "$fail_case_dir" + mkdir -p "$tmp_dir" + + if "$VERIFY_SCRIPT" "$sy_file" "$tmp_dir" --run > "$case_log" 2>&1; then + cleanup_tmp_dir "$tmp_dir" + return 0 + fi + + mkdir -p "$FAIL_DIR" + mv "$tmp_dir" "$fail_case_dir" + append_file_to_whole_log "$rel" "$fail_case_dir/error.log" + return 1 +} + +run_case() { + local sy_file="$1" + local rel + rel="$(realpath --relative-to="$REPO_ROOT" "$sy_file")" + + if test_one "$sy_file" "$rel"; then + log_color "$GREEN" "PASS $rel" + PASS=$((PASS + 1)) + else + log_color "$RED" "FAIL $rel" + FAIL=$((FAIL + 1)) + FAIL_LIST+=("$rel") + fi +} + +if [[ "$FAILED_ONLY" == true ]]; then + if [[ -f "$LAST_FAILED_FILE" ]]; then + while IFS= read -r sy_file; do + [[ -n "$sy_file" ]] || continue + [[ -f "$sy_file" ]] || continue + TEST_FILES+=("$sy_file") + done < "$LAST_FAILED_FILE" + fi + + if [[ ${#TEST_FILES[@]} -eq 0 ]]; then + FALLBACK_TO_FULL=true + FAILED_ONLY=false + fi +fi + +if [[ "$FAILED_ONLY" == false && ${#TEST_DIRS[@]} -eq 0 && ${#TEST_FILES[@]} -eq 0 ]]; then + while IFS= read -r -d '' test_dir; do + TEST_DIRS+=("$test_dir") + done < <(discover_default_test_dirs) +fi + +log_plain "Run directory: $RUN_DIR" +log_plain "Whole log: $WHOLE_LOG" +if [[ "$LEGACY_SAVE_ASM" == true ]]; then + log_color "$YELLOW" "Warning: --save-asm is deprecated; successful case artifacts will still be deleted." +fi +if [[ "$FAILED_ONLY" == true ]]; then + log_plain "Mode: rerun cached failed cases only" +fi +if [[ "$FALLBACK_TO_FULL" == true ]]; then + log_color "$YELLOW" "No cached failed cases found, fallback to full suite." +fi + +if [[ ! -f "$VERIFY_SCRIPT" ]]; then + log_color "$RED" "missing verify script: $VERIFY_SCRIPT" + exit 1 +fi + +for tool in llc aarch64-linux-gnu-gcc qemu-aarch64; do + if ! command -v "$tool" >/dev/null 2>&1; then + log_color "$RED" "missing required tool: $tool" + exit 1 + fi +done + +log_plain "==> [1/2] Configure and build compiler" +if ! cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >> "$WHOLE_LOG" 2>&1; then + log_color "$RED" "CMake configure failed. See $WHOLE_LOG" + exit 1 +fi +if ! cmake --build "$BUILD_DIR" -j "$(nproc)" >> "$WHOLE_LOG" 2>&1; then + log_color "$RED" "Compiler build failed. See $WHOLE_LOG" + exit 1 +fi + +log_plain "==> [2/2] Run ASM validation suite" +PASS=0 +FAIL=0 +FAIL_LIST=() + +if [[ "$FAILED_ONLY" == true ]]; then + for sy_file in "${TEST_FILES[@]}"; do + run_case "$sy_file" + done +else + for sy_file in "${TEST_FILES[@]}"; do + run_case "$sy_file" + done + + for test_dir in "${TEST_DIRS[@]}"; do + if [[ ! -d "$test_dir" ]]; then + log_color "$YELLOW" "skip missing dir: $test_dir" + continue + fi + + while IFS= read -r -d '' sy_file; do + run_case "$sy_file" + done < <(find "$test_dir" -maxdepth 1 -type f -name '*.sy' -print0 | sort -z) + done +fi + +rm -f "$LAST_FAILED_FILE" +if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then + for f in "${FAIL_LIST[@]}"; do + printf '%s/%s\n' "$REPO_ROOT" "$f" >> "$LAST_FAILED_FILE" + done +fi + +prune_empty_run_dirs + +log_plain "" +log_plain "summary: ${PASS} PASS / ${FAIL} FAIL / total $((PASS + FAIL))" +if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then + log_plain "failed cases:" + for f in "${FAIL_LIST[@]}"; do + safe_name="${f//\//_}" + log_plain "- $f" + log_plain " artifacts: $FAIL_DIR/${safe_name%.sy}" + done +else + log_plain "all successful case artifacts have been deleted automatically." +fi +log_plain "whole log saved to: $WHOLE_LOG" + +[[ $FAIL -eq 0 ]] diff --git a/scripts/verify_asm.sh b/scripts/verify_asm.sh index a4b8ae2..2f10993 100644 --- a/scripts/verify_asm.sh +++ b/scripts/verify_asm.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash - set -euo pipefail if [[ $# -lt 1 || $# -gt 3 ]]; then - echo "用法: $0 [output_dir] [--run]" >&2 + echo "usage: $0 input.sy [output_dir] [--run]" >&2 exit 1 fi @@ -26,18 +25,24 @@ while [[ $# -gt 0 ]]; do done if [[ ! -f "$input" ]]; then - echo "输入文件不存在: $input" >&2 + echo "input file not found: $input" >&2 exit 1 fi -compiler="./build/bin/compiler" -if [[ ! -x "$compiler" ]]; then - echo "未找到编译器: $compiler ,请先构建。" >&2 +compiler="" +for candidate in ./build_lab3/bin/compiler ./build_lab2/bin/compiler ./build/bin/compiler; do + if [[ -x "$candidate" ]]; then + compiler="$candidate" + break + fi +done +if [[ -z "$compiler" ]]; then + echo "compiler not found; try: cmake -S . -B build_lab3 && cmake --build build_lab3 -j" >&2 exit 1 fi if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then - echo "未找到 aarch64-linux-gnu-gcc,无法汇编/链接。" >&2 + echo "aarch64-linux-gnu-gcc not found" >&2 exit 1 fi @@ -50,30 +55,50 @@ stdin_file="$input_dir/$stem.in" expected_file="$input_dir/$stem.out" "$compiler" --emit-asm "$input" > "$asm_file" -echo "汇编已生成: $asm_file" +echo "asm generated: $asm_file" -aarch64-linux-gnu-gcc "$asm_file" -o "$exe" -echo "可执行文件已生成: $exe" +aarch64-linux-gnu-gcc "$asm_file" sylib/sylib.c -O2 -o "$exe" +echo "executable generated: $exe" if [[ "$run_exec" == true ]]; then if ! command -v qemu-aarch64 >/dev/null 2>&1; then - echo "未找到 qemu-aarch64,无法运行生成的可执行文件。" >&2 + echo "qemu-aarch64 not found" >&2 exit 1 fi stdout_file="$out_dir/$stem.stdout" actual_file="$out_dir/$stem.actual.out" - echo "运行 $exe ..." + timeout_sec="${RUN_TIMEOUT_SEC:-60}" + if [[ "$input" == *"/performance/"* || "$input" == *"/h_performance/"* ]]; then + timeout_sec="${PERF_TIMEOUT_SEC:-300}" + fi + set +e - if [[ -f "$stdin_file" ]]; then - qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" < "$stdin_file" > "$stdout_file" + if command -v timeout >/dev/null 2>&1; then + if [[ -f "$stdin_file" ]]; then + timeout "$timeout_sec" qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" < "$stdin_file" > "$stdout_file" + else + timeout "$timeout_sec" qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" > "$stdout_file" + fi else - qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" > "$stdout_file" + if [[ -f "$stdin_file" ]]; then + qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" < "$stdin_file" > "$stdout_file" + else + qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" > "$stdout_file" + fi fi status=$? set -e + + if [[ $status -eq 124 ]]; then + echo "timeout after ${timeout_sec}s: $exe" >&2 + fi + cat "$stdout_file" - echo "退出码: $status" + if [[ -s "$stdout_file" ]] && (( $(tail -c 1 "$stdout_file" | wc -l) == 0 )); then + printf '\n' + fi + echo "exit code: $status" { cat "$stdout_file" if [[ -s "$stdout_file" ]] && (( $(tail -c 1 "$stdout_file" | wc -l) == 0 )); then @@ -83,14 +108,14 @@ if [[ "$run_exec" == true ]]; then } > "$actual_file" if [[ -f "$expected_file" ]]; then - if diff -u "$expected_file" "$actual_file"; then - echo "输出匹配: $expected_file" + if diff -u <(awk '{ sub(/\r$/, ""); print }' "$expected_file") <(awk '{ sub(/\r$/, ""); print }' "$actual_file"); then + echo "matched: $expected_file" else - echo "输出不匹配: $expected_file" >&2 - echo "实际输出已保存: $actual_file" >&2 + echo "mismatch: $expected_file" >&2 + echo "actual saved to: $actual_file" >&2 exit 1 fi else - echo "未找到预期输出文件,跳过比对: $expected_file" + echo "expected output not found, skipped diff: $expected_file" fi fi diff --git a/src/main.cpp b/src/main.cpp index fdffdbb..786168b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,17 @@ +#include #include +#include +#include #include #include +#include +#include +#include +#include + +#if !defined(_WIN32) +#include +#endif #include "frontend/AntlrDriver.h" #include "frontend/SyntaxTreePrinter.h" @@ -8,12 +19,115 @@ #include "ir/IR.h" #include "ir/PassManager.h" #include "irgen/IRGen.h" -#include "mir/MIR.h" #include "sem/Sema.h" #endif #include "utils/CLI.h" #include "utils/Log.h" +#if !COMPILER_PARSE_ONLY +namespace { +namespace fs = std::filesystem; + +std::string ShellEscape(std::string_view text) { + std::string escaped; + escaped.reserve(text.size() + 2); + escaped.push_back('\''); + for (char ch : text) { + if (ch == '\'') { + escaped += "'\\''"; + } else { + escaped.push_back(ch); + } + } + escaped.push_back('\''); + return escaped; +} + +fs::path CreateTempFile(const char* pattern) { + fs::path temp_dir = fs::temp_directory_path(); + std::string templ = (temp_dir / pattern).string(); + std::vector buffer(templ.begin(), templ.end()); + buffer.push_back('\0'); + +#if defined(_WIN32) + if (_mktemp_s(buffer.data(), buffer.size()) != 0) { + throw std::runtime_error(FormatError("lab3", "failed to allocate a temporary file name")); + } + std::ofstream touch(buffer.data(), std::ios::binary); + if (!touch) { + throw std::runtime_error(FormatError("lab3", "failed to create a temporary file")); + } +#else + int fd = mkstemp(buffer.data()); + if (fd < 0) { + throw std::runtime_error(FormatError("lab3", "failed to create a temporary file")); + } + close(fd); +#endif + + return fs::path(buffer.data()); +} + +class ScopedTempFile { + public: + explicit ScopedTempFile(const char* pattern) : path_(CreateTempFile(pattern)) {} + ~ScopedTempFile() { + std::error_code ec; + fs::remove(path_, ec); + } + + const fs::path& path() const { return path_; } + + private: + fs::path path_; +}; + +void WriteIRToFile(const ir::Module& module, const fs::path& path) { + std::ofstream output(path, std::ios::binary | std::ios::trunc); + if (!output) { + throw std::runtime_error(FormatError("lab3", "failed to open temporary IR file")); + } + ir::IRPrinter printer; + printer.Print(module, output); + if (!output) { + throw std::runtime_error(FormatError("lab3", "failed to write temporary IR file")); + } +} + +void StreamFileToStdout(const fs::path& path, std::ostream& os) { + std::ifstream input(path, std::ios::binary); + if (!input) { + throw std::runtime_error(FormatError("lab3", "failed to open generated assembly file")); + } + os << input.rdbuf(); + if (!os) { + throw std::runtime_error(FormatError("lab3", "failed to write assembly output")); + } +} + +void EmitAsmWithLLC(const ir::Module& module, std::ostream& os) { + const char* llc_env = std::getenv("LLC"); + std::string llc = (llc_env != nullptr && llc_env[0] != '\0') ? llc_env : "llc"; + + ScopedTempFile ir_file("nudt_lab3_ir_XXXXXX"); + ScopedTempFile asm_file("nudt_lab3_asm_XXXXXX"); + WriteIRToFile(module, ir_file.path()); + + std::string command = llc + + " -opaque-pointers -mtriple=aarch64-linux-gnu -filetype=asm " + + ShellEscape(ir_file.path().string()) + " -o " + + ShellEscape(asm_file.path().string()); + int status = std::system(command.c_str()); + if (status != 0) { + throw std::runtime_error( + FormatError("lab3", "llc failed while generating AArch64 assembly")); + } + + StreamFileToStdout(asm_file.path(), os); +} +} // namespace +#endif + int main(int argc, char** argv) { try { auto opts = ParseCLI(argc, argv); @@ -32,45 +146,35 @@ int main(int argc, char** argv) { #if !COMPILER_PARSE_ONLY auto* comp_unit = dynamic_cast(antlr.tree); if (!comp_unit) { - throw std::runtime_error(FormatError("main", "语法树根节点不是 compUnit")); + throw std::runtime_error(FormatError("main", "syntax tree root is not compUnit")); } - auto sema = RunSema(*comp_unit); + auto sema = RunSema(*comp_unit); auto module = GenerateIR(*comp_unit, sema); - if (opts.emit_ir) { - std::unique_ptr ir_module; - if (opts.emit_asm) { - ir_module = GenerateIR(*comp_unit, sema); - } else { - ir_module = std::move(module); - } - ir::RunIRPassPipeline(*ir_module); - ir::IRPrinter printer; + if (opts.emit_ir || opts.emit_asm) { + ir::RunIRPassPipeline(*module); + } + + if (opts.emit_ir) { if (need_blank_line) { std::cout << "\n"; } - printer.Print(*ir_module, std::cout); + ir::IRPrinter printer; + printer.Print(*module, std::cout); need_blank_line = true; - - if (!opts.emit_asm) { - module = std::move(ir_module); - } } if (opts.emit_asm) { - auto machine_func = mir::LowerToMIR(*module); - mir::RunRegAlloc(*machine_func); - mir::RunFrameLowering(*machine_func); if (need_blank_line) { std::cout << "\n"; } - mir::PrintAsm(*machine_func, std::cout); + EmitAsmWithLLC(*module, std::cout); } #else if (opts.emit_ir || opts.emit_asm) { throw std::runtime_error( - FormatError("main", "当前为 parse-only 构建;IR/汇编输出已禁用")); + FormatError("main", "IR/asm emission is unavailable in parse-only builds")); } #endif } catch (const std::exception& ex) { @@ -78,4 +182,4 @@ int main(int argc, char** argv) { return 1; } return 0; -} \ No newline at end of file +}