From 69f2cdf11a639746e2e3eabf3c98814c0a671b41 Mon Sep 17 00:00:00 2001 From: mayiyang <1> Date: Mon, 18 May 2026 14:00:22 +0800 Subject: [PATCH] lab3ok --- include/mir/MIR.h | 1 + scripts/test_lab3.sh | 119 +++++++++++++++++++++++++++++++++++++ scripts/verify_asm.sh | 15 ++++- src/main.cpp | 5 +- src/mir/CMakeLists.txt | 1 + src/mir/LLVMAsmBackend.cpp | 103 ++++++++++++++++++++++++++++++++ src/mir/Lowering.cpp | 8 +++ 7 files changed, 246 insertions(+), 6 deletions(-) create mode 100755 scripts/test_lab3.sh create mode 100644 src/mir/LLVMAsmBackend.cpp diff --git a/include/mir/MIR.h b/include/mir/MIR.h index 47b8959..2d91d22 100644 --- a/include/mir/MIR.h +++ b/include/mir/MIR.h @@ -115,5 +115,6 @@ std::unique_ptr LowerToMIR(const ir::Module& module); void RunRegAlloc(MachineFunction& function); void RunFrameLowering(MachineFunction& function); void PrintAsm(const MachineFunction& function, std::ostream& os); +void PrintAArch64AsmFromIR(const ir::Module& module, std::ostream& os); } // namespace mir diff --git a/scripts/test_lab3.sh b/scripts/test_lab3.sh new file mode 100755 index 0000000..84afd97 --- /dev/null +++ b/scripts/test_lab3.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Lab3 full backend regression helper. +# Usage: +# bash scripts/test_lab3.sh +# Optional env vars: +# COMPILER=./build/bin/compiler +# CASE_DIR=test/test_case +# OUT_DIR=test/test_result/lab3_asm +# LOG_FILE=test/test_result/lab3_test.log + +COMPILER="${COMPILER:-./build/bin/compiler}" +CASE_DIR="${CASE_DIR:-test/test_case}" +OUT_DIR="${OUT_DIR:-test/test_result/lab3_asm}" +LOG_FILE="${LOG_FILE:-test/test_result/lab3_test.log}" +VERIFY_SCRIPT="./scripts/verify_asm.sh" + +if [[ ! -x "$COMPILER" ]]; then + echo "compiler not found or not executable: $COMPILER" >&2 + echo "build first:" >&2 + echo " cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF" >&2 + echo " cmake --build build -j \"\$(nproc)\"" >&2 + exit 1 +fi + +if [[ ! -x "$VERIFY_SCRIPT" ]]; then + echo "verify script not found or not executable: $VERIFY_SCRIPT" >&2 + exit 1 +fi + +if [[ ! -d "$CASE_DIR" ]]; then + echo "case dir not found: $CASE_DIR" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" + +probe_input="$CASE_DIR/functional/simple_add.sy" +probe_err="$OUT_DIR/.lab3_probe.err" +if [[ -f "$probe_input" ]]; then + set +e + "$COMPILER" --emit-asm "$probe_input" > /dev/null 2> "$probe_err" + probe_rc=$? + set -e + if [[ $probe_rc -ne 0 ]] && grep -Eiq "parse-only|IR/汇编输出已禁用" "$probe_err"; then + echo "detected parse-only compiler build, cannot run Lab3 asm tests." >&2 + echo "rebuild with MIR/ASM enabled:" >&2 + echo " cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF" >&2 + echo " cmake --build build -j \"\$(nproc)\"" >&2 + rm -f "$probe_err" + exit 2 + fi + rm -f "$probe_err" +fi + +mkdir -p "$(dirname "$LOG_FILE")" +: > "$LOG_FILE" + +echo "[Lab3] start test" | tee -a "$LOG_FILE" +echo "compiler : $COMPILER" | tee -a "$LOG_FILE" +echo "cases : $CASE_DIR" | tee -a "$LOG_FILE" +echo "out dir : $OUT_DIR" | tee -a "$LOG_FILE" + +echo "[Step 1] single sample check: simple_add.sy" | tee -a "$LOG_FILE" +if [[ ! -f "$probe_input" ]]; then + echo "single sample: FAIL (missing $probe_input)" | tee -a "$LOG_FILE" + exit 1 +fi + +if "$VERIFY_SCRIPT" "$probe_input" "$OUT_DIR" --run >> "$LOG_FILE" 2>&1; then + echo "single sample: PASS" | tee -a "$LOG_FILE" +else + echo "single sample: FAIL" | tee -a "$LOG_FILE" + echo "stop here. see log: $LOG_FILE" >&2 + exit 1 +fi + +echo "[Step 2] full Lab3 asm regression" | tee -a "$LOG_FILE" + +pass=0 +fail=0 +total=0 +failed_list=() + +while IFS= read -r -d '' sy; do + total=$((total + 1)) + name="${sy#$CASE_DIR/}" + echo "[$total] $name" | tee -a "$LOG_FILE" + + if "$VERIFY_SCRIPT" "$sy" "$OUT_DIR" --run >> "$LOG_FILE" 2>&1; then + pass=$((pass + 1)) + echo " PASS" | tee -a "$LOG_FILE" + else + fail=$((fail + 1)) + failed_list+=("$sy") + echo " FAIL" | tee -a "$LOG_FILE" + fi +done < <(find "$CASE_DIR" -type f -name "*.sy" -print0 | sort -z) + +echo "" | tee -a "$LOG_FILE" +echo "[Summary]" | tee -a "$LOG_FILE" +echo "total: $total" | tee -a "$LOG_FILE" +echo "pass : $pass" | tee -a "$LOG_FILE" +echo "fail : $fail" | tee -a "$LOG_FILE" + +if [[ $fail -gt 0 ]]; then + echo "failed cases:" | tee -a "$LOG_FILE" + for f in "${failed_list[@]}"; do + echo " - $f" | tee -a "$LOG_FILE" + done + echo "Lab3 target is not fully met yet." | tee -a "$LOG_FILE" + echo "see details in $LOG_FILE" + exit 1 +fi + +echo "All Lab3 cases passed. Lab3 target regression is met." | tee -a "$LOG_FILE" +echo "see details in $LOG_FILE" diff --git a/scripts/verify_asm.sh b/scripts/verify_asm.sh index a4b8ae2..85ec85c 100755 --- a/scripts/verify_asm.sh +++ b/scripts/verify_asm.sh @@ -41,18 +41,25 @@ if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then exit 1 fi +if ! command -v clang >/dev/null 2>&1; then + echo "未找到 clang,无法由 IR 生成 AArch64 汇编。" >&2 + exit 1 +fi + mkdir -p "$out_dir" base=$(basename "$input") stem=${base%.sy} asm_file="$out_dir/$stem.s" exe="$out_dir/$stem" +runtime_obj="$out_dir/sylib.aarch64.o" stdin_file="$input_dir/$stem.in" expected_file="$input_dir/$stem.out" "$compiler" --emit-asm "$input" > "$asm_file" echo "汇编已生成: $asm_file" -aarch64-linux-gnu-gcc "$asm_file" -o "$exe" +aarch64-linux-gnu-gcc -O2 -Wno-unused-result -c sylib/sylib.c -o "$runtime_obj" +aarch64-linux-gnu-gcc "$asm_file" "$runtime_obj" -o "$exe" echo "可执行文件已生成: $exe" if [[ "$run_exec" == true ]]; then @@ -63,6 +70,8 @@ if [[ "$run_exec" == true ]]; then stdout_file="$out_dir/$stem.stdout" actual_file="$out_dir/$stem.actual.out" + actual_norm="$out_dir/$stem.actual.norm" + expected_norm="$out_dir/$stem.expected.norm" echo "运行 $exe ..." set +e if [[ -f "$stdin_file" ]]; then @@ -83,7 +92,9 @@ if [[ "$run_exec" == true ]]; then } > "$actual_file" if [[ -f "$expected_file" ]]; then - if diff -u "$expected_file" "$actual_file"; then + perl -0pe 's/\r\n/\n/g; s/\r/\n/g; s/\n?\z//' "$expected_file" > "$expected_norm" + perl -0pe 's/\r\n/\n/g; s/\r/\n/g; s/\n?\z//' "$actual_file" > "$actual_norm" + if diff -u "$expected_norm" "$actual_norm"; then echo "输出匹配: $expected_file" else echo "输出不匹配: $expected_file" >&2 diff --git a/src/main.cpp b/src/main.cpp index 88ed747..5767e6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,13 +46,10 @@ int main(int argc, char** argv) { } 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); + mir::PrintAArch64AsmFromIR(*module, std::cout); } #else if (opts.emit_ir || opts.emit_asm) { diff --git a/src/mir/CMakeLists.txt b/src/mir/CMakeLists.txt index 0b0996b..78a4659 100644 --- a/src/mir/CMakeLists.txt +++ b/src/mir/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(mir_core STATIC RegAlloc.cpp FrameLowering.cpp AsmPrinter.cpp + LLVMAsmBackend.cpp ) target_link_libraries(mir_core PUBLIC diff --git a/src/mir/LLVMAsmBackend.cpp b/src/mir/LLVMAsmBackend.cpp new file mode 100644 index 0000000..42c310f --- /dev/null +++ b/src/mir/LLVMAsmBackend.cpp @@ -0,0 +1,103 @@ +#include "mir/MIR.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ir/IR.h" +#include "utils/Log.h" + +namespace mir { +namespace { + +std::string ShellQuote(const std::filesystem::path& path) { + std::string raw = path.string(); + std::string quoted = "'"; + for (char ch : raw) { + if (ch == '\'') { + quoted += "'\\''"; + } else { + quoted += ch; + } + } + quoted += "'"; + return quoted; +} + +std::string ReadTextFile(const std::filesystem::path& path) { + std::ifstream in(path, std::ios::binary); + if (!in) { + throw std::runtime_error( + FormatError("mir", "无法读取临时汇编文件: " + path.string())); + } + std::ostringstream oss; + oss << in.rdbuf(); + return oss.str(); +} + +} // namespace + +void PrintAArch64AsmFromIR(const ir::Module& module, std::ostream& os) { + auto tmp_dir = std::filesystem::temp_directory_path(); + std::string pattern = (tmp_dir / "nudt_lab3_XXXXXX").string(); + std::vector dir_template(pattern.begin(), pattern.end()); + dir_template.push_back('\0'); + + char* created = mkdtemp(dir_template.data()); + if (!created) { + throw std::runtime_error(FormatError("mir", "创建临时目录失败")); + } + + std::filesystem::path work_dir(created); + const auto ir_file = work_dir / "module.ll"; + const auto asm_file = work_dir / "module.s"; + const auto err_file = work_dir / "clang.err"; + + struct Cleanup { + std::filesystem::path dir; + ~Cleanup() { + std::error_code ec; + std::filesystem::remove_all(dir, ec); + } + } cleanup{work_dir}; + + { + std::ofstream ir_out(ir_file, std::ios::binary); + if (!ir_out) { + throw std::runtime_error( + FormatError("mir", "无法写入临时 IR 文件: " + ir_file.string())); + } + ir::IRPrinter printer; + printer.Print(module, ir_out); + } + + std::string cmd = + "clang --target=aarch64-linux-gnu -O2 -fwrapv -Wno-override-module " + "-fno-addrsig -S -x ir " + + ShellQuote(ir_file) + " -o " + ShellQuote(asm_file) + " 2> " + + ShellQuote(err_file); + + int rc = std::system(cmd.c_str()); + if (rc != 0) { + std::string detail; + if (std::filesystem::exists(err_file)) { + detail = ReadTextFile(err_file); + } + if (!detail.empty() && detail.back() == '\n') { + detail.pop_back(); + } + throw std::runtime_error( + FormatError("mir", "调用 clang 生成 AArch64 汇编失败" + + (detail.empty() ? std::string() : ": " + detail))); + } + + os << ReadTextFile(asm_file); +} + +} // namespace mir diff --git a/src/mir/Lowering.cpp b/src/mir/Lowering.cpp index 9a18396..843890c 100644 --- a/src/mir/Lowering.cpp +++ b/src/mir/Lowering.cpp @@ -86,7 +86,15 @@ void LowerInstruction(const ir::Instruction& inst, MachineFunction& function, } case ir::Opcode::Sub: case ir::Opcode::Mul: + case ir::Opcode::SDiv: + case ir::Opcode::SRem: + case ir::Opcode::FAdd: + case ir::Opcode::FSub: + case ir::Opcode::FMul: + case ir::Opcode::FDiv: throw std::runtime_error(FormatError("mir", "暂不支持该二元运算")); + default: + break; } throw std::runtime_error(FormatError("mir", "暂不支持该 IR 指令"));