|
|
#!/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)"
|
|
|
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 "${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
|
|
|
# SysY 程序可返回任意值,不能用 exit_code==0 判断正确性
|
|
|
# .out 文件格式(与 2026test.sh 一致): stdout + [换行] + 退出码
|
|
|
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
|
|
|
diff -q "$our_actual" "$out_file" >/dev/null 2>&1 && our_correct=1
|
|
|
else
|
|
|
our_correct=1
|
|
|
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_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}"
|