chore: 更新脚本和 sylib——clang 参考支持 + 提交脚本

- score.sh 支持 gcc/clang 双参考编译器
- diff_test.sh/diff_test_llvm.sh 小修复
- sylib.c 添加 _sysy_starttime/_sysy_stoptime 别名
- submit.sh 比赛平台提交辅助脚本
lzk
lzkk 2 days ago
parent 1b9c8cc40d
commit f9bea1bf85

@ -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

@ -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

@ -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 <<EOF
@ -51,9 +75,11 @@ usage() {
选项:
-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
@ -62,23 +88,44 @@ EOF
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 ;;
--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
if ! command -v "$GCC" &>/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}"

@ -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 " 登录后触发功能/性能测试"

@ -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) {

Loading…
Cancel
Save