#!/usr/bin/env bash # ============================================================ # score.sh — 按比赛计分公式计算本地分数 # # 公式(与比赛平台一致): # 性能分 = 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" # 默认,可用 -d 覆盖 RESULT_DIR="${PROJECT_DIR}/score_results" SYLIB="${PROJECT_DIR}/sylib/sylib.c" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' # 规范比较:忽略尾部空格和尾部空行(与 2026test.sh 的 canon_compare 一致) canon_diff() { local f1="$1" f2="$2" diff -q \ <(sed 's/\r$//; s/[[:space:]]*$//' "$f1" \ | awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \ <(sed 's/\r$//; s/[[:space:]]*$//' "$f2" \ | awk '{lines[NR]=$0} END{last=NR; while(last>0&&lines[last]=="")last--; for(i=1;i<=last;i++)print lines[i]}') \ >/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" TIMEOUT_SEC=300 CATEGORY="all" JOBS="$(nproc)" QUIET=false REF="gcc" # gcc | clang | both usage() { cat </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" # 收集测试用例 declare -a CATEGORIES if [[ "$CATEGORY" == "all" ]]; then CATEGORIES=("functional" "h_functional" "performance") else CATEGORIES=("$CATEGORY") fi declare -a TEST_FILES for cat in "${CATEGORIES[@]}"; do d="$TEST_DIR/$cat" if [[ -d "$d" ]]; then while IFS= read -r -d '' f; do TEST_FILES+=("$f") done < <(find "$d" -maxdepth 1 -name '*.sy' -print0 | sort -z) fi done TOTAL_TESTS="${#TEST_FILES[@]}" if [[ "$TOTAL_TESTS" -eq 0 ]]; then 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 "参考编译器: ${REF_LABEL}" echo -e "超时: ${TIMEOUT_SEC}s 并行: ${JOBS}" echo -e "${BLUE}========================================================${NC}" echo "" fi # ============================================================ # Phase 1: 创建工作目录 + 批量 SysY → C 预处理 # ============================================================ PREPROC_MAP="$RESULT_DIR/.preprocess_map" : > "$PREPROC_MAP" 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" if ! $QUIET; then echo -e "SysY→C 预处理完成 (${TOTAL_TESTS} 文件)" echo "" fi # ============================================================ # Phase 2: 并行执行所有测试 # ============================================================ run_worker() { local idx="$1" sy_file="$2" 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" 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 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 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)) [[ $our_time -lt 1 ]] && our_time=1 local our_actual="$work_dir/our.actual.out" { cat "$our_out" if [[ -s "$our_out" ]] && (( $(tail -c 1 "$our_out" | wc -l) == 0 )); then printf '\n' fi echo "$exit_code" } > "$our_actual" if [[ -f "$out_file" ]]; then canon_diff "$our_actual" "$out_file" && our_correct=1 else our_correct=1 fi fi fi # ---- 链接并运行 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 # ---- 链接并运行 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 "$clang_ref_exe" < "$in_file" > "$clang_ref_out" 2>/dev/null || ref_exit=$? else timeout "$TIMEOUT_SEC" "$QEMU" $QEMU_FLAGS "$clang_ref_exe" > "$clang_ref_out" 2>/dev/null || ref_exit=$? fi ref_end="$(date +%s%3N)" clang_ref_time=$((ref_end - ref_start)) fi fi [[ $clang_ref_time -lt 1 ]] && clang_ref_time=1 # 写结果(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_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 (输出不匹配)\n" \ "$cat_name/$base_name" "$our_time" 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 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: 收集结果并计算分数 # ============================================================ 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 done done # 几何平均 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 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" 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 "" 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 "参考编译器: $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}"