You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

517 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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 <<EOF
用法: $0 [选项]
选项:
-c, --category CAT 测试类别: functional, h_functional, performance, all (默认 all)
--ref REF 参考编译器: gcc (默认), clang, both
-t, --timeout SEC 单个用例超时秒数 (默认 $TIMEOUT_SEC)
-j, --jobs N 并行数 (默认 CPU 核数)
-q, --quiet 只输出最终分数
-d, --test-dir DIR 测试用例目录 (默认 2026test)
-h, --help 显示帮助
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--category) CATEGORY="$2"; shift 2 ;;
--ref) REF="$2"; shift 2 ;;
-t|--timeout) TIMEOUT_SEC="$2"; shift 2 ;;
-j|--jobs) JOBS="$2"; shift 2 ;;
-q|--quiet) QUIET=true; shift ;;
-d|--test-dir) TEST_DIR="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo -e "${RED}未知选项: $1${NC}"; usage ;;
esac
done
# 验证 --ref
if [[ "$REF" != "gcc" && "$REF" != "clang" && "$REF" != "both" ]]; then
echo -e "${RED}错误: --ref 必须是 gcc, clang 或 both${NC}"
exit 1
fi
# 环境检查
if [[ ! -x "$COMPILER" ]]; then
echo -e "${RED}错误: 编译器未找到: $COMPILER${NC}"
exit 1
fi
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"
# 收集测试用例
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}"