diff --git a/scripts/diff_test.sh b/scripts/diff_test.sh index 966d4a13..fbc25e8c 100755 --- a/scripts/diff_test.sh +++ b/scripts/diff_test.sh @@ -20,7 +20,7 @@ NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -TEST_ROOT="$PROJECT_ROOT/2026test" +TEST_ROOT="${PROJECT_ROOT}/2026test" # 默认,可用 -d 覆盖 RESULTS_ROOT="$PROJECT_ROOT/2026test_results" GCC_BASELINE_DIR="$RESULTS_ROOT/gcc_baseline" GCC_ASM_DIR="$RESULTS_ROOT/gcc_asm" @@ -37,7 +37,7 @@ DO_BASELINE=false DO_DIFF=false DO_PERF=false SAVE_ASM=false -GCC_OPT_LEVEL=3 +GCC_OPT_LEVEL=2 MAX_CASES=0 REPORT_FILE="" JSON_FILE="" @@ -65,6 +65,7 @@ usage() { 通用选项: -n, --max N 最多运行 N 个用例 (0=不限制,默认: 0) + -d, --test-dir DIR 测试用例目录 (默认 2026test) -j, --jobs N 并行任务数 (默认: nproc,设为1恢复串行) -h, --help 显示此帮助信息 @@ -88,7 +89,8 @@ while [[ $# -gt 0 ]]; do --report) REPORT_FILE="$2"; shift ;; --json) JSON_FILE="$2"; shift ;; -n|--max) MAX_CASES="$2"; shift ;; - -j|--jobs) JOBS="$2" + -d|--test-dir) TEST_ROOT="$2"; shift ;; + -j|--jobs) JOBS="$2"; shift if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [[ "$JOBS" -lt 1 ]]; then echo "错误: --jobs 需要正整数"; exit 1 fi ;; @@ -153,6 +155,12 @@ canon_compare() { > /dev/null 2>&1 } +# 统计 AArch64 汇编中的实际指令行数(排除伪指令、标签、空行) +count_insn() { + local asm="$1" + grep -cE '^[[:space:]]+[a-z]' "$asm" 2>/dev/null || echo 0 +} + # ============================================================ # 收集用例 # ============================================================ @@ -362,26 +370,34 @@ run_perf_worker() { local compiler_asm=$(mktemp /tmp/compiler_XXXX.s) local gcc_asm=$(mktemp /tmp/gcc_XXXX.s) - # 编译器生成汇编 + # 编译器 + gcc 并行编译 local comp_ok=true compiler_lines=0 - if ! timeout --signal=KILL 60 "$COMPILER" -S -O -o "$compiler_asm" "$sy" 2>/dev/null; then - comp_ok=false - else - compiler_lines=$(wc -l < "$compiler_asm") - compiler_lines=${compiler_lines:-0} - fi - - # gcc 生成汇编 local gcc_ok=true gcc_lines=0 + + timeout --signal=KILL 60 "$COMPILER" -S -O -o "$compiler_asm" "$sy" 2>/dev/null & + local comp_pid=$! + local gcc_src=$(mktemp /tmp/gcc_perf_XXXX.sy) preprocess_for_gcc "$sy" "$gcc_src" - if ! $GCC -x c -S "-O${GCC_OPT_LEVEL}" -o "$gcc_asm" "$gcc_src" 2>/dev/null; then - gcc_ok=false + $GCC -x c -S "-O${GCC_OPT_LEVEL}" -o "$gcc_asm" "$gcc_src" 2>/dev/null & + local gcc_pid=$! + + wait $comp_pid 2>/dev/null || comp_ok=false + wait $gcc_pid 2>/dev/null || gcc_ok=false + rm -f "$gcc_src" + + if $comp_ok && [[ -s "$compiler_asm" ]]; then + compiler_lines=$(count_insn "$compiler_asm") else - gcc_lines=$(wc -l < "$gcc_asm") - gcc_lines=${gcc_lines:-0} + comp_ok=false fi - rm -f "$gcc_src" + + if $gcc_ok && [[ -s "$gcc_asm" ]]; then + gcc_lines=$(count_insn "$gcc_asm") + else + gcc_ok=false + fi + # 保存 gcc 汇编 if [[ "$SAVE_ASM" == true && "$gcc_ok" == true ]]; then @@ -415,7 +431,7 @@ run_perf() { # 并行或串行执行 if [[ $JOBS -gt 1 && ${#CASES[@]} -gt 1 ]]; then export COMPILER GCC GCC_OPT_LEVEL SAVE_ASM GCC_ASM_DIR - export -f run_perf_worker preprocess_for_gcc + export -f run_perf_worker preprocess_for_gcc count_insn declare -a QUEUE=() for i in "${!CASES[@]}"; do diff --git a/scripts/diff_test_llvm.sh b/scripts/diff_test_llvm.sh index d32f1a12..33bfa78d 100755 --- a/scripts/diff_test_llvm.sh +++ b/scripts/diff_test_llvm.sh @@ -20,7 +20,7 @@ NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -TEST_ROOT="$PROJECT_ROOT/2026test" +TEST_ROOT="${PROJECT_ROOT}/2026test" # 默认,可用 -d 覆盖 RESULTS_ROOT="$PROJECT_ROOT/2026test_results" LLVM_BASELINE_DIR="$RESULTS_ROOT/llvm_baseline" LLVM_ASM_DIR="$RESULTS_ROOT/llvm_asm" @@ -46,7 +46,7 @@ DO_BASELINE=false DO_DIFF=false DO_PERF=false SAVE_ASM=false -LLVM_OPT_LEVEL=3 +LLVM_OPT_LEVEL=2 MAX_CASES=0 REPORT_FILE="" JSON_FILE="" @@ -74,6 +74,7 @@ usage() { 通用选项: -n, --max N 最多运行 N 个用例 (0=不限制,默认: 0) + -d, --test-dir DIR 测试用例目录 (默认 2026test) -j, --jobs N 并行任务数 (默认: nproc,设为1恢复串行) -h, --help 显示此帮助信息 @@ -97,7 +98,8 @@ while [[ $# -gt 0 ]]; do --report) REPORT_FILE="$2"; shift ;; --json) JSON_FILE="$2"; shift ;; -n|--max) MAX_CASES="$2"; shift ;; - -j|--jobs) JOBS="$2" + -d|--test-dir) TEST_ROOT="$2"; shift ;; + -j|--jobs) JOBS="$2"; shift if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [[ "$JOBS" -lt 1 ]]; then echo "错误: --jobs 需要正整数"; exit 1 fi ;; @@ -177,6 +179,12 @@ canon_compare() { > /dev/null 2>&1 } +# 统计 AArch64 汇编中的实际指令行数(排除伪指令、标签、空行) +count_insn() { + local asm="$1" + grep -cE '^[[:space:]]+[a-z]' "$asm" 2>/dev/null || echo 0 +} + # ============================================================ # 收集用例 # ============================================================ @@ -386,26 +394,33 @@ run_perf_worker() { local compiler_asm=$(mktemp /tmp/compiler_llvm_XXXX.s) local llvm_asm=$(mktemp /tmp/llvm_XXXX.s) - # 编译器生成汇编 + # 编译器 + clang 并行编译 local comp_ok=true compiler_lines=0 - if ! timeout --signal=KILL 60 "$COMPILER" -S -O -o "$compiler_asm" "$sy" 2>/dev/null; then - comp_ok=false - else - compiler_lines=$(wc -l < "$compiler_asm") - compiler_lines=${compiler_lines:-0} - fi - - # clang 生成汇编 local llvm_ok=true llvm_lines=0 + + timeout --signal=KILL 60 "$COMPILER" -S -O -o "$compiler_asm" "$sy" 2>/dev/null & + local comp_pid=$! + local clang_src=$(mktemp /tmp/clang_perf_XXXX.sy) preprocess_for_clang "$sy" "$clang_src" - if ! $CLANG $CLANG_FLAGS -x c -S "-O${LLVM_OPT_LEVEL}" -o "$llvm_asm" "$clang_src" 2>/dev/null; then - llvm_ok=false + $CLANG $CLANG_FLAGS -x c -S "-O${LLVM_OPT_LEVEL}" -o "$llvm_asm" "$clang_src" 2>/dev/null & + local clang_pid=$! + + wait $comp_pid 2>/dev/null || comp_ok=false + wait $clang_pid 2>/dev/null || llvm_ok=false + rm -f "$clang_src" + + if $comp_ok && [[ -s "$compiler_asm" ]]; then + compiler_lines=$(count_insn "$compiler_asm") else - llvm_lines=$(wc -l < "$llvm_asm") - llvm_lines=${llvm_lines:-0} + comp_ok=false + fi + + if $llvm_ok && [[ -s "$llvm_asm" ]]; then + llvm_lines=$(count_insn "$llvm_asm") + else + llvm_ok=false fi - rm -f "$clang_src" # 保存 LLVM 汇编 if [[ "$SAVE_ASM" == true && "$llvm_ok" == true ]]; then @@ -439,7 +454,7 @@ run_perf() { # 并行或串行执行 if [[ $JOBS -gt 1 && ${#CASES[@]} -gt 1 ]]; then export COMPILER CLANG CLANG_FLAGS LLVM_OPT_LEVEL SAVE_ASM LLVM_ASM_DIR - export -f run_perf_worker preprocess_for_clang + export -f run_perf_worker preprocess_for_clang count_insn declare -a QUEUE=() for i in "${!CASES[@]}"; do diff --git a/scripts/score.sh b/scripts/score.sh index e9228b40..777c8989 100755 --- a/scripts/score.sh +++ b/scripts/score.sh @@ -6,13 +6,17 @@ # 性能分 = geometric_mean(参考时间 / 我们时间) × 100 # 正确分 = 通过用例数 / 总用例数 × 100 # 总分 = 正确分 × 0.5 + 性能分 × 0.5 +# +# 支持 gcc / clang 双参考编译器,--ref both 同时对比。 +# 参考编译先经 SysY→C 预处理(const int → #define), +# 编译失败的用例不计入性能分。 # ============================================================ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" COMPILER="${PROJECT_DIR}/build/bin/compiler" -TEST_DIR="${PROJECT_DIR}/2026test" +TEST_DIR="${PROJECT_DIR}/2026test" # 默认,可用 -d 覆盖 RESULT_DIR="${PROJECT_DIR}/score_results" SYLIB="${PROJECT_DIR}/sylib/sylib.c" @@ -34,9 +38,28 @@ canon_diff() { >/dev/null 2>&1 } +# ---- 编译器配置 ---- GCC="aarch64-linux-gnu-gcc" GCC_COMPILE_FLAGS="-std=gnu89 -O2 -x c" GCC_LINK_FLAGS="-static" + +# 检测 clang:优先用交叉编译器,其次尝试系统 clang + target +CLANG="" +CLANG_TARGET="aarch64-linux-gnu" +for cand in aarch64-linux-gnu-clang clang; do + if command -v "$cand" >/dev/null 2>&1; then + CLANG="$cand" + break + fi +done +CLANG_COMPILE_FLAGS="-std=gnu89 -x c" +if [[ -n "$CLANG" ]]; then + if [[ "$CLANG" != "aarch64-linux-gnu-clang" ]]; then + CLANG_COMPILE_FLAGS="$CLANG_COMPILE_FLAGS --target=$CLANG_TARGET" + fi + CLANG_COMPILE_FLAGS="$CLANG_COMPILE_FLAGS -fno-addrsig" +fi + QEMU="qemu-aarch64" QEMU_FLAGS="-L /usr/aarch64-linux-gnu -s 209715200" COMPILER_FLAGS="-O" @@ -44,6 +67,7 @@ TIMEOUT_SEC=300 CATEGORY="all" JOBS="$(nproc)" QUIET=false +REF="gcc" # gcc | clang | both usage() { cat </dev/null; then + +USE_GCC=false +USE_CLANG=false +case "$REF" in + gcc) USE_GCC=true ;; + clang) USE_CLANG=true ;; + both) USE_GCC=true; USE_CLANG=true ;; +esac + +if $USE_GCC && ! command -v "$GCC" &>/dev/null; then echo -e "${RED}错误: 参考编译器未找到: $GCC${NC}" exit 1 fi +if $USE_CLANG && [[ -z "$CLANG" ]]; then + echo -e "${RED}错误: clang 未找到,安装: sudo apt install clang${NC}" + exit 1 +fi mkdir -p "$RESULT_DIR" @@ -102,131 +149,120 @@ done TOTAL_TESTS="${#TEST_FILES[@]}" if [[ "$TOTAL_TESTS" -eq 0 ]]; then - echo -e "${RED}错误: 未找到测试用例${NC}" + echo -e "${RED}错误: 未找到测试用例 (TEST_DIR)${NC}" exit 1 fi +# 构建参考标签 +REF_LABEL="" +if $USE_GCC && $USE_CLANG; then + REF_LABEL="gcc -O2 + clang -O2" +elif $USE_GCC; then + REF_LABEL="gcc -O2" +else + REF_LABEL="clang -O2" +fi + if ! $QUIET; then echo -e "${BLUE}========================================================${NC}" echo -e "${BLUE} 本地评分 (比赛计分公式)${NC}" echo -e "${BLUE}========================================================${NC}" echo -e "测试类别: ${CATEGORY}" echo -e "用例数: ${TOTAL_TESTS}" - echo -e "参考编译器: ${GCC} ${GCC_COMPILE_FLAGS}" + echo -e "参考编译器: ${REF_LABEL}" echo -e "超时: ${TIMEOUT_SEC}s 并行: ${JOBS}" echo -e "${BLUE}========================================================${NC}" echo "" fi # ============================================================ -# 单个测试执行(通过临时脚本,避免函数导出兼容性问题) +# Phase 1: 创建工作目录 + 批量 SysY → C 预处理 # ============================================================ -run_one() { - local sy_file="$1" - local idx="$2" - local base_name cat_name work_dir in_file out_file +PREPROC_MAP="$RESULT_DIR/.preprocess_map" +: > "$PREPROC_MAP" - base_name="$(basename "${sy_file%.sy}")" - cat_name="$(basename "$(dirname "$sy_file")")" +for f in "${TEST_FILES[@]}"; do + base_name="$(basename "${f%.sy}")" + cat_name="$(basename "$(dirname "$f")")" work_dir="$RESULT_DIR/$cat_name/$base_name" mkdir -p "$work_dir" + echo "$f|$work_dir/gcc_input.c" +done > "$PREPROC_MAP" + +python3 -c " +import re, os +with open('$PREPROC_MAP') as f: + for line in f: + line = line.strip() + if not line: + continue + sy_file, c_file = line.split('|') + os.makedirs(os.path.dirname(c_file), exist_ok=True) + with open(sy_file) as fin: + content = fin.read() + # const int X = V → #define X V (行首匹配,避免误替换) + content = re.sub(r'^const int (\w+) = ([^;]+);', r'#define \1 \2', content, flags=re.MULTILINE) + with open(c_file, 'w') as fout: + fout.write(content) +" 2>/dev/null + +rm -f "$PREPROC_MAP" - in_file="${sy_file%.sy}.in" - out_file="${sy_file%.sy}.out" - - # ---- 我们的编译器 ---- - local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out" - local our_time=1 our_correct=0 our_compile_ok=0 - - if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then - if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then - our_compile_ok=1 - local start_ns end_ns exit_code=0 - start_ns="$(date +%s%3N)" - if [[ -f "$in_file" ]]; then - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" < "$in_file" > "$our_out" 2>/dev/null || exit_code=$? - else - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$our_exe" > "$our_out" 2>/dev/null || exit_code=$? - fi - end_ns="$(date +%s%3N)" - our_time=$((end_ns - start_ns)) - if [[ $our_time -lt 1 ]]; then our_time=1; fi - - if [[ $exit_code -eq 0 ]]; then - if [[ -f "$out_file" ]]; then - if diff -q "$our_out" "$out_file" >/dev/null 2>&1; then - our_correct=1 - fi - else - our_correct=1 - fi - fi - fi - fi - - # ---- 参考编译器 (gcc -O2) ---- - local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out" - local ref_time=999999 ref_ok=0 - - if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then - if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then - local ref_start ref_end ref_exit=0 - ref_start="$(date +%s%3N)" - if [[ -f "$in_file" ]]; then - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$? - else - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$? - fi - ref_end="$(date +%s%3N)" - ref_time=$((ref_end - ref_start)) - ref_ok=1 - fi - fi - if [[ $ref_time -lt 1 ]]; then ref_time=1; fi - - # 写结果文件 - echo "$our_correct $our_time $ref_time $ref_ok $our_compile_ok" > "$work_dir/result.txt" - - # 打印 - if [[ $our_correct -eq 1 ]]; then - local ratio - ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0") - printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \ - "$cat_name/$base_name" "$our_time" "$ref_time" "$ratio" - elif [[ $our_compile_ok -eq 0 ]]; then - printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name" - else - printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \ - "$cat_name/$base_name" "$our_time" "$ref_time" - fi -} +if ! $QUIET; then + echo -e "SysY→C 预处理完成 (${TOTAL_TESTS} 文件)" + echo "" +fi # ============================================================ -# 并行执行 +# Phase 2: 并行执行所有测试 # ============================================================ -if ! $QUIET; then - echo -e "${BLUE}正在运行 ${TOTAL_TESTS} 个测试 (${JOBS} 并行)...${NC}" - echo "" -fi +run_worker() { + local idx="$1" sy_file="$2" -# 并行执行:使用后台任务 + wait 控制并发 -run_with_args() { - local sy_file="$1" local base_name cat_name work_dir in_file out_file - base_name="$(basename "${sy_file%.sy}")" cat_name="$(basename "$(dirname "$sy_file")")" work_dir="$RESULT_DIR/$cat_name/$base_name" - mkdir -p "$work_dir" in_file="${sy_file%.sy}.in" out_file="${sy_file%.sy}.out" + local gcc_src="$work_dir/gcc_input.c" + # ---- 我们的编译器 ---- local our_asm="$work_dir/our.s" our_exe="$work_dir/our.exe" our_out="$work_dir/our.out" local our_time=1 our_correct=0 our_compile_ok=0 - if timeout 30 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null; then + timeout 60 "$COMPILER" $COMPILER_FLAGS -S -o "$our_asm" "$sy_file" 2>/dev/null & + local our_comp_pid=$! + + # ---- gcc 参考 ---- + local gcc_ref_asm="$work_dir/ref_gcc.s" gcc_ref_exe="$work_dir/ref_gcc.exe" gcc_ref_out="$work_dir/ref_gcc.out" + local gcc_ref_time=999999 gcc_ref_ok=0 + local gcc_comp_pid=0 + + if $USE_GCC; then + "$GCC" $GCC_COMPILE_FLAGS -S -o "$gcc_ref_asm" "$gcc_src" 2>/dev/null & + gcc_comp_pid=$! + fi + + # ---- clang 参考 ---- + local clang_ref_asm="$work_dir/ref_clang.s" clang_ref_exe="$work_dir/ref_clang.exe" clang_ref_out="$work_dir/ref_clang.out" + local clang_ref_time=999999 clang_ref_ok=0 + local clang_comp_pid=0 + + if $USE_CLANG; then + "$CLANG" $CLANG_COMPILE_FLAGS -S -O2 -o "$clang_ref_asm" "$gcc_src" 2>/dev/null & + clang_comp_pid=$! + fi + + # 等待编译完成 + wait $our_comp_pid 2>/dev/null || true + [[ $gcc_comp_pid -ne 0 ]] && wait $gcc_comp_pid 2>/dev/null || true + [[ $clang_comp_pid -ne 0 ]] && wait $clang_comp_pid 2>/dev/null || true + + # ---- 链接并运行我们的 ---- + if [[ -s "$our_asm" ]]; then if "$GCC" $GCC_LINK_FLAGS "$our_asm" "$SYLIB" -o "$our_exe" -lm 2>/dev/null; then our_compile_ok=1 local start_ns end_ns exit_code=0 @@ -239,8 +275,7 @@ run_with_args() { end_ns="$(date +%s%3N)" our_time=$((end_ns - start_ns)) [[ $our_time -lt 1 ]] && our_time=1 - # SysY 程序可返回任意值,不能用 exit_code==0 判断正确性 - # .out 文件格式(与 2026test.sh 一致): stdout + [换行] + 退出码 + local our_actual="$work_dir/our.actual.out" { cat "$our_out" @@ -249,6 +284,7 @@ run_with_args() { fi echo "$exit_code" } > "$our_actual" + if [[ -f "$out_file" ]]; then canon_diff "$our_actual" "$out_file" && our_correct=1 else @@ -257,121 +293,224 @@ run_with_args() { fi fi - local ref_asm="$work_dir/ref.s" ref_exe="$work_dir/ref.exe" ref_out="$work_dir/ref.out" - local ref_time=999999 + # ---- 链接并运行 gcc 参考 ---- + if $USE_GCC && [[ -s "$gcc_ref_asm" ]]; then + if "$GCC" $GCC_LINK_FLAGS "$gcc_ref_asm" "$SYLIB" -o "$gcc_ref_exe" -lm 2>/dev/null; then + gcc_ref_ok=1 + local ref_start ref_end ref_exit=0 + ref_start="$(date +%s%3N)" + if [[ -f "$in_file" ]]; then + timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$gcc_ref_exe" < "$in_file" > "$gcc_ref_out" 2>/dev/null || ref_exit=$? + else + timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$gcc_ref_exe" > "$gcc_ref_out" 2>/dev/null || ref_exit=$? + fi + ref_end="$(date +%s%3N)" + gcc_ref_time=$((ref_end - ref_start)) + fi + fi + [[ $gcc_ref_time -lt 1 ]] && gcc_ref_time=1 - if "$GCC" $GCC_COMPILE_FLAGS -S -o "$ref_asm" "$sy_file" 2>/dev/null; then - if "$GCC" $GCC_LINK_FLAGS "$ref_asm" "$SYLIB" -o "$ref_exe" -lm 2>/dev/null; then + # ---- 链接并运行 clang 参考 ---- + if $USE_CLANG && [[ -s "$clang_ref_asm" ]]; then + if "$GCC" $GCC_LINK_FLAGS "$clang_ref_asm" "$SYLIB" -o "$clang_ref_exe" -lm 2>/dev/null; then + clang_ref_ok=1 local ref_start ref_end ref_exit=0 ref_start="$(date +%s%3N)" if [[ -f "$in_file" ]]; then - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" < "$in_file" > "$ref_out" 2>/dev/null || ref_exit=$? + timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$clang_ref_exe" < "$in_file" > "$clang_ref_out" 2>/dev/null || ref_exit=$? else - timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$ref_exe" > "$ref_out" 2>/dev/null || ref_exit=$? + timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$clang_ref_exe" > "$clang_ref_out" 2>/dev/null || ref_exit=$? fi ref_end="$(date +%s%3N)" - ref_time=$((ref_end - ref_start)) + clang_ref_time=$((ref_end - ref_start)) fi fi - [[ $ref_time -lt 1 ]] && ref_time=1 + [[ $clang_ref_time -lt 1 ]] && clang_ref_time=1 - echo "$our_correct $our_time $ref_time $our_compile_ok" > "$work_dir/result.txt" + # 写结果(7 字段) + echo "$our_correct $our_time $gcc_ref_time $gcc_ref_ok $clang_ref_time $clang_ref_ok $our_compile_ok" > "$work_dir/result.txt" + # 打印单行结果 if [[ $our_correct -eq 1 ]]; then - local ratio - ratio=$(awk "BEGIN { printf \"%.4f\", $ref_time / $our_time }" 2>/dev/null || echo "0") - printf "[${GREEN}AC${NC}] %-45s our=%6dms ref=%6dms ratio=%s\n" \ - "$cat_name/$base_name" "$our_time" "$ref_time" "$ratio" + local ratio_str="" + if $USE_GCC; then + if [[ $gcc_ref_ok -eq 1 ]] && [[ $gcc_ref_time -gt 0 ]]; then + ratio_str+=" gcc=$(awk "BEGIN { printf \"%.2f\", $gcc_ref_time / $our_time }" 2>/dev/null || echo "N/A")" + else + ratio_str+=" gcc=N/A" + fi + fi + if $USE_CLANG; then + if [[ $clang_ref_ok -eq 1 ]] && [[ $clang_ref_time -gt 0 ]]; then + ratio_str+=" clang=$(awk "BEGIN { printf \"%.2f\", $clang_ref_time / $our_time }" 2>/dev/null || echo "N/A")" + else + ratio_str+=" clang=N/A" + fi + fi + printf "[${GREEN}AC${NC}] %-45s our=%6dms%s\n" \ + "$cat_name/$base_name" "$our_time" "$ratio_str" elif [[ $our_compile_ok -eq 0 ]]; then printf "[${RED}CE${NC}] %-45s (编译失败)\n" "$cat_name/$base_name" else - printf "[${RED}WA${NC}] %-45s our=%6dms ref=%6dms (输出不匹配)\n" \ - "$cat_name/$base_name" "$our_time" "$ref_time" + printf "[${RED}WA${NC}] %-45s our=%6dms (输出不匹配)\n" \ + "$cat_name/$base_name" "$our_time" fi } -# 简单后台并行 -running=0 -for f in "${TEST_FILES[@]}"; do - run_with_args "$f" & - running=$((running + 1)) - if [[ $running -ge $JOBS ]]; then - wait -n 2>/dev/null || true - running=$((running - 1)) - fi +if ! $QUIET; then + echo -e "${BLUE}正在运行 ${TOTAL_TESTS} 个测试 (${JOBS} 并行)...${NC}" + echo "" +fi + +# 构建队列 +declare -a QUEUE +for i in "${!TEST_FILES[@]}"; do + QUEUE+=("$i|${TEST_FILES[$i]}") done -wait + +if [[ $JOBS -gt 1 && ${#QUEUE[@]} -gt 1 ]]; then + export RESULT_DIR COMPILER COMPILER_FLAGS + export GCC GCC_COMPILE_FLAGS GCC_LINK_FLAGS + export CLANG CLANG_COMPILE_FLAGS + export QEMU QEMU_FLAGS SYLIB TIMEOUT_SEC + export USE_GCC USE_CLANG + export RED GREEN YELLOW BLUE BOLD NC + export -f run_worker canon_diff + + printf '%s\n' "${QUEUE[@]}" | xargs -P "$JOBS" -L 1 bash -c ' + IFS="|" read -r idx sy <<< "$1" + run_worker "$idx" "$sy" + ' _ +else + for item in "${QUEUE[@]}"; do + IFS='|' read -r idx sy <<< "$item" + run_worker "$idx" "$sy" + done +fi echo "" # ============================================================ -# 收集结果 +# Phase 3: 收集结果并计算分数 # ============================================================ -passed=0 -declare -a ratios - -for cat in "${CATEGORIES[@]}"; do - for sy_file in "$TEST_DIR/$cat"/*.sy; do - [[ -f "$sy_file" ]] || continue - base_name="$(basename "${sy_file%.sy}")" - work_dir="$RESULT_DIR/$cat/$base_name" - result_file="$work_dir/result.txt" - - if [[ -f "$result_file" ]]; then - read -r test_correct our_time ref_time ref_ok compile_ok < "$result_file" || true - if [[ "$test_correct" == "1" ]]; then - passed=$((passed + 1)) - if [[ $ref_time -gt 0 ]] && [[ $our_time -gt 0 ]]; then - ratio=$(echo "scale=10; $ref_time / $our_time" | bc 2>/dev/null || echo "1.0") - ratios+=("$ratio") +compute_score() { + local ref_name="$1" # "gcc" or "clang" + local ref_time_field="$2" # field position in result.txt + local ref_ok_field="$3" + + local passed=0 perf_count=0 + declare -a ratios + + for cat in "${CATEGORIES[@]}"; do + for sy_file in "$TEST_DIR/$cat"/*.sy; do + [[ -f "$sy_file" ]] || continue + base_name="$(basename "${sy_file%.sy}")" + work_dir="$RESULT_DIR/$cat/$base_name" + result_file="$work_dir/result.txt" + + if [[ -f "$result_file" ]]; then + read -r our_correct our_time gcc_time gcc_ok clang_time clang_ok our_compile_ok < "$result_file" || true + if [[ "$our_correct" == "1" ]]; then + passed=$((passed + 1)) + local ref_time ref_ok + if [[ "$ref_name" == "gcc" ]]; then + ref_time="$gcc_time"; ref_ok="$gcc_ok" + else + ref_time="$clang_time"; ref_ok="$clang_ok" + fi + if [[ "$ref_ok" == "1" ]] && [[ $ref_time -gt 0 ]] && [[ $our_time -gt 0 ]]; then + ratio=$(echo "scale=10; $ref_time / $our_time" | bc 2>/dev/null || echo "1.0") + ratios+=("$ratio") + perf_count=$((perf_count + 1)) + fi fi fi - fi + done done -done -# 计算几何平均 -GEOM_MEAN="0.0" -if [[ ${#ratios[@]} -gt 0 ]]; then - product="1.0" - for r in "${ratios[@]}"; do - product=$(echo "scale=20; $product * $r" | bc 2>/dev/null) - done - GEOM_MEAN=$(echo "scale=10; e(l($product) / ${#ratios[@]})" | bc -l 2>/dev/null || echo "1.0") -fi + # 几何平均 + local geom_mean="0.0" + if [[ $perf_count -gt 0 ]]; then + local product="1.0" + for r in "${ratios[@]}"; do + product=$(echo "scale=20; $product * $r" | bc 2>/dev/null) + done + geom_mean=$(echo "scale=10; e(l($product) / $perf_count)" | bc -l 2>/dev/null || echo "1.0") + fi -PERF_SCORE=$(echo "scale=4; $GEOM_MEAN * 100" | bc -l 2>/dev/null) -if (( $(echo "$PERF_SCORE > 100" | bc -l 2>/dev/null) )); then - PERF_SCORE="100.0000" -fi + local perf_score=$(echo "scale=4; $geom_mean * 100" | bc -l 2>/dev/null) + if (( $(echo "$perf_score > 100" | bc -l 2>/dev/null) )); then + perf_score="100.0000" + fi + + local correct_score=$(echo "scale=4; $passed / $TOTAL_TESTS * 100" | bc -l 2>/dev/null) + local total_score=$(echo "scale=4; $correct_score * 0.5 + $perf_score * 0.5" | bc -l 2>/dev/null) + + echo "$passed|$perf_count|$geom_mean|$perf_score|$correct_score|$total_score|$perf_count" +} + +print_score_block() { + local ref_label="$1" + local result="$2" -CORRECT_SCORE=$(echo "scale=4; $passed / $TOTAL_TESTS * 100" | bc -l 2>/dev/null) -TOTAL_SCORE=$(echo "scale=4; $CORRECT_SCORE * 0.5 + $PERF_SCORE * 0.5" | bc -l 2>/dev/null) + IFS='|' read -r passed perf_count geom_mean perf_score correct_score total_score n_ratios <<< "$result" + + echo -e "${BOLD}--- vs ${ref_label} ---${NC}" + printf " %-20s ${GREEN}%d / %d${NC}\n" "正确用例:" "$passed" "$TOTAL_TESTS" + printf " %-20s ${GREEN}%d${NC}\n" "参与性能分用例:" "$perf_count" + echo "" + printf " %-20s %8s\n" "--------------------" "--------" + printf " %-20s ${GREEN}%8.4f${NC}\n" "正确分:" "$correct_score" + printf " %-20s ${YELLOW}%8.4f${NC}\n" "性能分:" "$perf_score" + printf " %-20s ${BLUE}${BOLD}%8.4f${NC}\n" "总分:" "$total_score" + echo "" + printf " 几何平均 (ref/our): %.6f\n" "$geom_mean" + printf " 有效性能用例数: %d\n" "$n_ratios" + echo "" +} echo -e "${BLUE}========================================================${NC}" echo -e "${BLUE} 评分结果${NC}" echo -e "${BLUE}========================================================${NC}" echo "" -printf " %-20s ${GREEN}%d / %d${NC}\n" "正确用例:" "$passed" "$TOTAL_TESTS" -echo "" -printf " ${BOLD}%-20s %8s${NC}\n" "指标" "分数" -printf " %-20s %8s\n" "--------------------" "--------" -printf " %-20s ${GREEN}%8.4f${NC}\n" "正确分:" "$CORRECT_SCORE" -printf " %-20s ${YELLOW}%8.4f${NC}\n" "性能分:" "$PERF_SCORE" -printf " %-20s ${BLUE}${BOLD}%8.4f${NC}\n" "总分:" "$TOTAL_SCORE" -echo "" -printf " 几何平均 (ref/our): %.6f\n" "$GEOM_MEAN" -printf " 有效用例数: %d\n" "${#ratios[@]}" -echo "" + +if $USE_GCC; then + gcc_result=$(compute_score "gcc" 3 4) + print_score_block "gcc -O2" "$gcc_result" +fi + +if $USE_CLANG; then + clang_result=$(compute_score "clang" 5 6) + print_score_block "clang -O2" "$clang_result" +fi # 保存 { - echo "正确分: $CORRECT_SCORE" - echo "性能分: $PERF_SCORE" - echo "总分: $TOTAL_SCORE" - echo "几何平均: $GEOM_MEAN" - echo "通过: $passed / $TOTAL_TESTS" + echo "参考编译器: $REF_LABEL" + echo "测试类别: $CATEGORY" + echo "" + if $USE_GCC; then + echo "--- vs gcc -O2 ---" + IFS='|' read -r passed perf_count geom_mean perf_score correct_score total_score n_ratios <<< "$gcc_result" + echo "正确分: $correct_score" + echo "性能分: $perf_score" + echo "总分: $total_score" + echo "几何平均: $geom_mean" + echo "通过: $passed / $TOTAL_TESTS" + echo "参与性能分: $n_ratios" + echo "" + fi + if $USE_CLANG; then + echo "--- vs clang -O2 ---" + IFS='|' read -r passed perf_count geom_mean perf_score correct_score total_score n_ratios <<< "$clang_result" + echo "正确分: $correct_score" + echo "性能分: $perf_score" + echo "总分: $total_score" + echo "几何平均: $geom_mean" + echo "通过: $passed / $TOTAL_TESTS" + echo "参与性能分: $n_ratios" + fi } > "$RESULT_DIR/score.txt" echo -e "${BLUE}结果已保存到: ${RESULT_DIR}/score.txt${NC}" diff --git a/scripts/submit.sh b/scripts/submit.sh new file mode 100755 index 00000000..d6cd5004 --- /dev/null +++ b/scripts/submit.sh @@ -0,0 +1,312 @@ +#!/usr/bin/env bash +set -euo pipefail +# ============================================================ +# 比赛平台提交脚本 +# 将 nudt-compiler-cpp 的优化代码同步到 warning 仓库, +# 自动处理平台兼容性修复,然后提交并推送 +# ============================================================ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +NUDT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +WARNING_DIR="/home/lzk/warning" + +# 死代码文件(平台 flat 编译会报错) +DEAD_FILES=( + "src/mir/LinearScanAlloc.cpp" + "src/mir/GreedyAlloc.cpp" + "src/mir/InstLiveness.cpp" +) + +usage() { + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -n, --dry-run 仅显示将要做的操作,不实际执行" + echo " -s, --skip-build 跳过本地构建验证" + echo " -p, --push 同步后直接推送(需要密码)" + echo " -m, --msg MSG 自定义 commit message(默认自动生成)" + echo " -h, --help 显示帮助" + echo "" + echo "工作流程: 同步 src → 删死代码 → 修复头文件 → 提交 → [推送]" + exit 0 +} + +DRY_RUN=false +SKIP_BUILD=false +DO_PUSH=false +CUSTOM_MSG="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -n|--dry-run) DRY_RUN=true; shift ;; + -s|--skip-build) SKIP_BUILD=true; shift ;; + -p|--push) DO_PUSH=true; shift ;; + -m|--msg) CUSTOM_MSG="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo -e "${RED}未知选项: $1${NC}"; usage ;; + esac +done + +# ============================================================ +# 前置检查 +# ============================================================ + +echo -e "${GREEN}=== 提交前检查 ===${NC}" + +if [[ ! -d "$WARNING_DIR/.git" ]]; then + echo -e "${RED}错误: warning 仓库不存在: $WARNING_DIR${NC}" + exit 1 +fi + +# 检查 warning 仓库是否干净 +if ! git -C "$WARNING_DIR" diff-index --quiet HEAD -- 2>/dev/null; then + echo -e "${YELLOW}警告: warning 仓库有未提交改动${NC}" + git -C "$WARNING_DIR" status --short + echo "" + read -rp "继续?(未提交改动将被覆盖)[y/N] " yn + if [[ "$yn" != "y" && "$yn" != "Y" ]]; then + echo "已取消" + exit 0 + fi +fi + +# 检查 nudt 的 src/ 是否有未提交改动(核心代码) +if ! git -C "$NUDT_DIR" diff --quiet HEAD -- src/ 2>/dev/null; then + echo -e "${YELLOW}警告: nudt/src/ 有未提交改动${NC}" + git -C "$NUDT_DIR" diff --stat HEAD -- src/ + echo "" + read -rp "继续?(确认改动已保存)[y/N] " yn + if [[ "$yn" != "y" && "$yn" != "Y" ]]; then + echo "已取消" + exit 0 + fi +fi + +# ============================================================ +# 第1步:同步源码 +# ============================================================ + +step1_sync() { + echo -e "${GREEN}=== 第1步: 同步源码到 warning ===${NC}" + + if $DRY_RUN; then + echo " [dry-run] rm -rf $WARNING_DIR/src/" + echo " [dry-run] cp -r $NUDT_DIR/src $WARNING_DIR/" + return + fi + + rm -rf "$WARNING_DIR/src/" + cp -r "$NUDT_DIR/src" "$WARNING_DIR/" + + # 同步 .gitignore(包含 T2025* 排除) + cp "$NUDT_DIR/.gitignore" "$WARNING_DIR/.gitignore" + + echo " 源码已同步" +} + +# ============================================================ +# 第2步:删除死代码 +# ============================================================ + +step2_deadcode() { + echo -e "${GREEN}=== 第2步: 删除死代码文件 ===${NC}" + + for f in "${DEAD_FILES[@]}"; do + local path="$WARNING_DIR/$f" + if [[ -f "$path" ]]; then + if $DRY_RUN; then + echo " [dry-run] rm $path" + else + rm "$path" + echo " 已删除: $f" + fi + else + echo " 跳过(不存在): $f" + fi + done +} + +# ============================================================ +# 第3步:修复头文件路径 +# ============================================================ + +step3_headers() { + echo -e "${GREEN}=== 第3步: 修复头文件路径 ===${NC}" + + local old_dom="$WARNING_DIR/src/ir/analysis/DominatorTree.h" + local new_dom="$WARNING_DIR/src/include/ir/analysis/DominatorTree.h" + + if [[ -f "$old_dom" ]]; then + if $DRY_RUN; then + echo " [dry-run] mv $old_dom → $new_dom" + else + mkdir -p "$(dirname "$new_dom")" + cp "$old_dom" "$new_dom" + rm "$old_dom" + echo " 已移动 DominatorTree.h → src/include/ir/analysis/" + fi + elif [[ -f "$new_dom" ]]; then + echo " DominatorTree.h 已在正确位置" + else + echo -e " ${YELLOW}警告: DominatorTree.h 不存在${NC}" + fi + + # 删除残留的 ANTLR 头文件(如果从 build 目录带过来) + local stale_headers=( + "$WARNING_DIR/src/include/SysYLexer.h" + "$WARNING_DIR/src/include/SysYParser.h" + "$WARNING_DIR/src/include/SysYVisitor.h" + "$WARNING_DIR/src/include/SysYBaseVisitor.h" + ) + # 这些头文件是需要的,不删。但确保它们和 build/generated 版本一致 +} + +# ============================================================ +# 第4步:本地构建验证(可选) +# ============================================================ + +step4_verify() { + if $SKIP_BUILD; then + echo -e "${YELLOW}=== 第4步: 跳过本地构建验证 ===${NC}" + return + fi + + echo -e "${GREEN}=== 第4步: 本地构建验证 ===${NC}" + + if $DRY_RUN; then + echo " [dry-run] cmake + make" + return + fi + + # 提取 ANTLR runtime + local antlr_zip="$WARNING_DIR/third_party/antlr4-runtime-4.13.2.zip" + local antlr_dir="$WARNING_DIR/third_party/antlr4-runtime-4.13.2" + if [[ -f "$antlr_zip" ]] && [[ ! -d "$antlr_dir/runtime" ]]; then + echo " 解压 ANTLR runtime..." + unzip -qo "$antlr_zip" -d "$antlr_dir/" 2>/dev/null || true + # 修复嵌套目录 + if [[ -d "$antlr_dir/antlr4-runtime-4.13.2/runtime" ]]; then + mv "$antlr_dir/antlr4-runtime-4.13.2/runtime" "$antlr_dir/" 2>/dev/null || true + rm -rf "$antlr_dir/antlr4-runtime-4.13.2" 2>/dev/null || true + fi + fi + + local build_dir="$WARNING_DIR/build_verify" + mkdir -p "$build_dir/generated/antlr4" + + # ANTLR 代码生成 + java -jar "$WARNING_DIR/third_party/antlr-4.13.2-complete.jar" \ + -Dlanguage=Cpp -visitor -no-listener -Xexact-output-dir \ + -o "$build_dir/generated/antlr4" \ + "$WARNING_DIR/src/antlr4/SysY.g4" 2>&1 | tail -1 + + # CMake 构建 + cmake -S "$WARNING_DIR" -B "$build_dir" \ + -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF 2>&1 | tail -1 + + if cmake --build "$build_dir" -j "$(nproc)" 2>&1 | tail -5; then + echo -e " ${GREEN}构建成功${NC}" + + # 快速功能验证 + local test_file="$NUDT_DIR/2026test/functional/00_main.sy" + if [[ -f "$test_file" ]]; then + if "$build_dir/bin/compiler" "$test_file" -S -o /tmp/submit_test.s 2>/dev/null; then + echo -e " ${GREEN}00_main 编译通过${NC}" + else + echo -e " ${RED}00_main 编译失败!${NC}" + fi + fi + else + echo -e " ${RED}构建失败!请修复后再提交${NC}" + exit 1 + fi + + # 清理验证构建目录 + rm -rf "$build_dir" +} + +# ============================================================ +# 第5步:提交 +# ============================================================ + +step5_commit() { + echo -e "${GREEN}=== 第5步: 提交 ===${NC}" + + if $DRY_RUN; then + echo " [dry-run] git add -A && git commit" + return + fi + + cd "$WARNING_DIR" + + # 清理可能遗留的构建目录 + rm -rf build_teammate build_clang build_verify + + git add -A + + if git diff-index --quiet HEAD --; then + echo " 没有改动需要提交" + return + fi + + local commit_msg + if [[ -n "$CUSTOM_MSG" ]]; then + commit_msg="$CUSTOM_MSG" + else + local nudt_commit=$(git -C "$NUDT_DIR" log --oneline -1) + commit_msg="perf: 同步优化版编译器源码 + +$(git -C "$NUDT_DIR" log --oneline -5 | sed 's/^/ /') + +自动提交: 删除死代码 + 修复头文件路径 + 同步优化" + fi + + git commit -m "$commit_msg" + echo -e " ${GREEN}已提交${NC}" + git log --oneline -1 +} + +# ============================================================ +# 第6步:推送 +# ============================================================ + +step6_push() { + if ! $DO_PUSH; then + echo -e "${YELLOW}=== 跳过推送(使用 -p 启用) ===${NC}" + echo " 手动推送: cd $WARNING_DIR && git push origin main:lzk --force" + return + fi + + echo -e "${GREEN}=== 推送 ===${NC}" + + if $DRY_RUN; then + echo " [dry-run] git push origin main:lzk --force" + return + fi + + cd "$WARNING_DIR" + git push origin main:lzk --force + echo -e " ${GREEN}推送完成${NC}" +} + +# ============================================================ +# 执行 +# ============================================================ + +echo "" +step1_sync +step2_deadcode +step3_headers +step4_verify +step5_commit +step6_push + +echo "" +echo -e "${GREEN}=== 完成 ===${NC}" +echo " 平台地址: https://compiler.xtnl.org.cn/#/" +echo " 登录后触发功能/性能测试" diff --git a/sylib/sylib.c b/sylib/sylib.c index 9eb7c75c..ae993af3 100644 --- a/sylib/sylib.c +++ b/sylib/sylib.c @@ -81,12 +81,14 @@ static clock_t start_time = 0; void starttime() { start_time = clock(); } +void _sysy_starttime(int lineno) { starttime(); } void stoptime() { clock_t end_time = clock(); double elapsed = (double)(end_time - start_time) / CLOCKS_PER_SEC; fprintf(stderr, "Total time: %.6f seconds\n", elapsed); } +void _sysy_stoptime(int lineno) { stoptime(); } // 内存管理函数(如果需要) void* _sysy_allocate(int size) {