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.

364 lines
12 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
# ============================================================
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"
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'
GCC="aarch64-linux-gnu-gcc"
GCC_COMPILE_FLAGS="-std=gnu89 -O2 -x c"
GCC_LINK_FLAGS="-static"
QEMU="qemu-aarch64"
QEMU_FLAGS="-L /usr/aarch64-linux-gnu -s 209715200"
COMPILER_FLAGS="-O"
TIMEOUT_SEC=300
CATEGORY="all"
JOBS="$(nproc)"
QUIET=false
usage() {
cat <<EOF
用法: $0 [选项]
选项:
-c, --category CAT 测试类别: functional, h_functional, performance, all (默认 all)
-t, --timeout SEC 单个用例超时秒数 (默认 $TIMEOUT_SEC)
-j, --jobs N 并行数 (默认 CPU 核数)
-q, --quiet 只输出最终分数
-h, --help 显示帮助
EOF
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--category) CATEGORY="$2"; shift 2 ;;
-t|--timeout) TIMEOUT_SEC="$2"; shift 2 ;;
-j|--jobs) JOBS="$2"; shift 2 ;;
-q|--quiet) QUIET=true; shift ;;
-h|--help) usage ;;
*) echo -e "${RED}未知选项: $1${NC}"; usage ;;
esac
done
# 环境检查
if [[ ! -x "$COMPILER" ]]; then
echo -e "${RED}错误: 编译器未找到: $COMPILER${NC}"
exit 1
fi
if ! command -v "$GCC" &>/dev/null; then
echo -e "${RED}错误: 参考编译器未找到: $GCC${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}错误: 未找到测试用例${NC}"
exit 1
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 "超时: ${TIMEOUT_SEC}s 并行: ${JOBS}"
echo -e "${BLUE}========================================================${NC}"
echo ""
fi
# ============================================================
# 单个测试执行(通过临时脚本,避免函数导出兼容性问题)
# ============================================================
run_one() {
local sy_file="$1"
local idx="$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"
mkdir -p "$work_dir"
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)"
if [[ $ref_exit -eq 0 ]]; then
ref_time=$((ref_end - ref_start))
ref_ok=1
fi
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 "${BLUE}正在运行 ${TOTAL_TESTS} 个测试 (${JOBS} 并行)...${NC}"
echo ""
fi
# 并行执行:使用后台任务 + 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 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))
[[ $our_time -lt 1 ]] && our_time=1
if [[ $exit_code -eq 0 ]]; then
if [[ -f "$out_file" ]]; then
# .out 文件格式: stdout + 换行 + 退出码 (与 2026test.sh 一致)
local our_actual="$work_dir/our.actual.out"
{ cat "$our_out"; printf '\n'; echo "$exit_code"; } > "$our_actual"
diff -q "$our_actual" "$out_file" >/dev/null 2>&1 && our_correct=1
else
our_correct=1
fi
fi
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
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_exit -eq 0 ]] && ref_time=$((ref_end - ref_start))
fi
fi
[[ $ref_time -lt 1 ]] && ref_time=1
echo "$our_correct $our_time $ref_time $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
}
# 简单后台并行
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
done
wait
echo ""
# ============================================================
# 收集结果
# ============================================================
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")
fi
fi
fi
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
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
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)
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 ""
# 保存
{
echo "正确分: $CORRECT_SCORE"
echo "性能分: $PERF_SCORE"
echo "总分: $TOTAL_SCORE"
echo "几何平均: $GEOM_MEAN"
echo "通过: $passed / $TOTAL_TESTS"
} > "$RESULT_DIR/score.txt"
echo -e "${BLUE}结果已保存到: ${RESULT_DIR}/score.txt${NC}"