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.
nudt-compiler-cpp/scripts/lab3_build_test.sh

434 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
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VERIFY_SCRIPT="$REPO_ROOT/scripts/verify_asm.sh"
BUILD_DIR="$REPO_ROOT/build_lab3"
RUN_ROOT="$REPO_ROOT/output/logs/lab3"
LAST_RUN_FILE="$RUN_ROOT/last_run.txt"
LAST_FAILED_FILE="$RUN_ROOT/last_failed.txt"
RUN_NAME="lab3_$(date +%Y%m%d_%H%M%S)"
RUN_DIR="$RUN_ROOT/$RUN_NAME"
WHOLE_LOG="$RUN_DIR/whole.log"
FAIL_DIR="$RUN_DIR/failures"
LEGACY_SAVE_ASM=false
FAILED_ONLY=false
FALLBACK_TO_FULL=false
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
TEST_DIRS=()
TEST_FILES=()
while [[ $# -gt 0 ]]; do
case "$1" in
--save-asm)
LEGACY_SAVE_ASM=true
;;
--failed-only)
FAILED_ONLY=true
;;
*)
if [[ -f "$1" ]]; then
TEST_FILES+=("$1")
else
TEST_DIRS+=("$1")
fi
;;
esac
shift
done
mkdir -p "$RUN_DIR"
: > "$WHOLE_LOG"
printf '%s\n' "$RUN_DIR" > "$LAST_RUN_FILE"
log_plain() {
printf '%s\n' "$*"
printf '%s\n' "$*" >> "$WHOLE_LOG"
}
log_color() {
local color="$1"
shift
local message="$*"
printf '%b%s%b\n' "$color" "$message" "$NC"
printf '%s\n' "$message" >> "$WHOLE_LOG"
}
append_file_to_whole_log() {
local title="$1"
local file="$2"
{
printf '\n===== %s =====\n' "$title"
cat "$file"
printf '\n'
} >> "$WHOLE_LOG"
}
cleanup_tmp_dir() {
local dir="$1"
if [[ -d "$dir" ]]; then
rm -rf "$dir"
fi
}
discover_default_test_dirs() {
local roots=(
"$REPO_ROOT/test/test_case"
"$REPO_ROOT/test/class_test_case"
)
local root
for root in "${roots[@]}"; do
[[ -d "$root" ]] || continue
find "$root" -mindepth 1 -maxdepth 1 -type d -print0
done | sort -z
}
prune_empty_run_dirs() {
if [[ -d "$RUN_DIR/.tmp" ]]; then
rmdir "$RUN_DIR/.tmp" 2>/dev/null || true
fi
if [[ -d "$FAIL_DIR" ]]; then
rmdir "$FAIL_DIR" 2>/dev/null || true
fi
}
now_ns() {
date +%s%N
}
format_duration_ns() {
local ns="$1"
local sec=$((ns / 1000000000))
local us10=$(((ns % 1000000000) / 10000))
printf '%d.%05ds' "$sec" "$us10"
}
is_transient_io_failure() {
local log_file="$1"
[[ -f "$log_file" ]] || return 1
grep -Eq \
'Permission denied|Text file busy|Device or resource busy|Stale file handle|Input/output error|Resource temporarily unavailable|Read-only file system' \
"$log_file"
}
# ---------- baseline 读取 & timing ----------
# 共享基线数据(由 run_baseline.sh 生成)
BASELINE_TSV="$REPO_ROOT/output/baseline/gcc_timing.tsv"
# 本次运行的我方计时 TSVstem<TAB>our_ns<TAB>gcc_s
TIMING_TSV="$RUN_DIR/timing.tsv"
# 从共享 TSV 查找某 stem 的 GCC 基线耗时(秒),找不到返回 N/A
lookup_gcc_s() {
local stem="$1"
local val="N/A"
if [[ -f "$BASELINE_TSV" ]]; then
val=$(awk -F'\t' -v s="$stem" '$1==s{v=$2} END{if(v!="") print v; else print "N/A"}' "$BASELINE_TSV")
fi
echo "$val"
}
record_timing() {
local stem="$1"
local our_ns="$2"
local gcc_s="${3:-N/A}"
printf '%s\t%s\t%s\n' "$stem" "$our_ns" "$gcc_s" >> "$TIMING_TSV"
}
test_one() {
local sy_file="$1"
local rel="$2"
local timing_out="${3:-}"
local safe_name="${rel//\//_}"
local case_key="${safe_name%.sy}"
local tmp_dir="$RUN_DIR/.tmp/$case_key"
local fail_case_dir="$FAIL_DIR/$case_key"
local case_log="$tmp_dir/error.log"
local attempt=1
cleanup_tmp_dir "$fail_case_dir"
while true; do
cleanup_tmp_dir "$tmp_dir"
mkdir -p "$tmp_dir"
local verify_args=("$sy_file" "$tmp_dir" --run)
[[ -n "$timing_out" ]] && verify_args+=(--timing-out "$timing_out")
if "$VERIFY_SCRIPT" "${verify_args[@]}" > "$case_log" 2>&1; then
cleanup_tmp_dir "$tmp_dir"
return 0
fi
if [[ $attempt -eq 1 ]] && is_transient_io_failure "$case_log"; then
log_color "$YELLOW" "RETRY $rel (transient I/O failure)"
attempt=$((attempt + 1))
continue
fi
break
done
mkdir -p "$FAIL_DIR"
mv "$tmp_dir" "$fail_case_dir"
append_file_to_whole_log "$rel" "$fail_case_dir/error.log"
return 1
}
run_case() {
local sy_file="$1"
local rel
local case_start_ns
rel="$(realpath --relative-to="$REPO_ROOT" "$sy_file")"
case_start_ns=$(now_ns)
local base stem case_key
base="$(basename "$sy_file")"
stem="${base%.sy}"
# 与 run_baseline.sh 保持一致:去掉 test/ 前缀和 .sy 后缀
case_key="${rel#test/}"
case_key="${case_key%.sy}"
local timing_file
timing_file="$(mktemp)"
if test_one "$sy_file" "$rel" "$timing_file"; then
local compile_ns=0 run_ns=0
if [[ -f "$timing_file" ]]; then
compile_ns=$(grep '^compile_ns=' "$timing_file" | cut -d= -f2 || echo 0)
run_ns=$(grep '^run_ns=' "$timing_file" | cut -d= -f2 || echo 0)
fi
rm -f "$timing_file"
log_color "$GREEN" "PASS $rel [compile=$(format_duration_ns "$compile_ns") run=$(format_duration_ns "$run_ns")]"
PASS=$((PASS + 1))
local gcc_s
gcc_s=$(lookup_gcc_s "$case_key")
record_timing "$case_key" "$run_ns" "$gcc_s"
else
rm -f "$timing_file"
local case_elapsed_ns=$(( $(now_ns) - case_start_ns ))
log_color "$RED" "FAIL $rel [$(format_duration_ns "$case_elapsed_ns")]"
FAIL=$((FAIL + 1))
FAIL_LIST+=("$rel")
fi
}
TOTAL_START_NS=$(now_ns)
: > "$TIMING_TSV"
if [[ "$FAILED_ONLY" == true ]]; then
if [[ -f "$LAST_FAILED_FILE" ]]; then
while IFS= read -r sy_file; do
[[ -n "$sy_file" ]] || continue
[[ -f "$sy_file" ]] || continue
TEST_FILES+=("$sy_file")
done < "$LAST_FAILED_FILE"
fi
if [[ ${#TEST_FILES[@]} -eq 0 ]]; then
FALLBACK_TO_FULL=true
FAILED_ONLY=false
fi
fi
if [[ "$FAILED_ONLY" == false && ${#TEST_DIRS[@]} -eq 0 && ${#TEST_FILES[@]} -eq 0 ]]; then
while IFS= read -r -d '' test_dir; do
TEST_DIRS+=("$test_dir")
done < <(discover_default_test_dirs)
fi
log_plain "Run directory: $RUN_DIR"
log_plain "Whole log: $WHOLE_LOG"
if [[ "$LEGACY_SAVE_ASM" == true ]]; then
log_color "$YELLOW" "Warning: --save-asm is deprecated; successful case artifacts will still be deleted."
fi
if [[ "$FAILED_ONLY" == true ]]; then
log_plain "Mode: rerun cached failed cases only"
fi
if [[ "$FALLBACK_TO_FULL" == true ]]; then
log_color "$YELLOW" "No cached failed cases found, fallback to full suite."
fi
if [[ -f "$BASELINE_TSV" ]]; then
log_plain "Baseline TSV: $BASELINE_TSV (speedup ratios will be computed)"
else
log_color "$CYAN" "Tip: run scripts/run_baseline.sh first to enable GCC -O2 speedup analysis."
fi
if [[ ! -f "$VERIFY_SCRIPT" ]]; then
log_color "$RED" "missing verify script: $VERIFY_SCRIPT"
exit 1
fi
for tool in llc aarch64-linux-gnu-gcc qemu-aarch64; do
if ! command -v "$tool" >/dev/null 2>&1; then
log_color "$RED" "missing required tool: $tool"
exit 1
fi
done
log_plain "==> [1/2] Configure and build compiler"
BUILD_START_NS=$(now_ns)
if ! cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >> "$WHOLE_LOG" 2>&1; then
log_color "$RED" "CMake configure failed. See $WHOLE_LOG"
exit 1
fi
if ! cmake --build "$BUILD_DIR" -j "$(nproc)" >> "$WHOLE_LOG" 2>&1; then
log_color "$RED" "Compiler build failed. See $WHOLE_LOG"
exit 1
fi
BUILD_END_NS=$(now_ns)
BUILD_ELAPSED_NS=$((BUILD_END_NS - BUILD_START_NS))
log_plain "==> [2/2] Run ASM validation suite"
VALIDATION_START_NS=$(now_ns)
PASS=0
FAIL=0
FAIL_LIST=()
if [[ "$FAILED_ONLY" == true ]]; then
for sy_file in "${TEST_FILES[@]}"; do
run_case "$sy_file"
done
else
for sy_file in "${TEST_FILES[@]}"; do
run_case "$sy_file"
done
for test_dir in "${TEST_DIRS[@]}"; do
if [[ ! -d "$test_dir" ]]; then
log_color "$YELLOW" "skip missing dir: $test_dir"
continue
fi
while IFS= read -r -d '' sy_file; do
run_case "$sy_file"
done < <(find "$test_dir" -maxdepth 1 -type f -name '*.sy' -print0 | sort -z)
done
fi
rm -f "$LAST_FAILED_FILE"
if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then
for f in "${FAIL_LIST[@]}"; do
printf '%s/%s\n' "$REPO_ROOT" "$f" >> "$LAST_FAILED_FILE"
done
fi
prune_empty_run_dirs
VALIDATION_END_NS=$(now_ns)
VALIDATION_ELAPSED_NS=$((VALIDATION_END_NS - VALIDATION_START_NS))
TOTAL_END_NS=$(now_ns)
TOTAL_ELAPSED_NS=$((TOTAL_END_NS - TOTAL_START_NS))
log_plain ""
log_plain "summary: ${PASS} PASS / ${FAIL} FAIL / total $((PASS + FAIL))"
log_plain "build elapsed: $(format_duration_ns "$BUILD_ELAPSED_NS")"
log_plain "validation elapsed: $(format_duration_ns "$VALIDATION_ELAPSED_NS")"
log_plain "total elapsed: $(format_duration_ns "$TOTAL_ELAPSED_NS")"
# ---------- 计时与加速比分析 ----------
if [[ -s "$TIMING_TSV" ]]; then
log_plain ""
log_plain "==> Timing & Speedup Analysis"
# 检查本次结果中是否有任何 GCC 基线数据
HAS_BASELINE=false
if grep -qv $'\tN/A$' "$TIMING_TSV" 2>/dev/null; then
HAS_BASELINE=true
fi
if [[ "$HAS_BASELINE" == true ]]; then
# 将 TSV 展开为含计算值的临时文件case_key, our_s, gcc_s, speedup
_tmp_timing="$RUN_DIR/timing_computed.tsv"
while IFS=$'\t' read -r case_key our_ns gcc_s; do
our_s=$(awk "BEGIN{printf \"%.5f\", $our_ns / 1000000000}")
if [[ "$gcc_s" == "N/A" ]]; then
speedup="N/A"
else
speedup=$(awk "BEGIN{if($our_s>0) printf \"%.5f\", $gcc_s/$our_s; else print \"inf\"}")
fi
printf '%s\t%s\t%s\t%s\n' "$case_key" "$our_s" "$gcc_s" "$speedup"
done < "$TIMING_TSV" > "$_tmp_timing"
# 排序1加速比升序N/A 排最后)
log_plain ""
log_plain "--- [Sort 1] Speedup ratio ascending (worst speedup first) ---"
log_plain "$(printf '%-40s %10s %10s %10s' 'case' 'our(s)' 'gcc(s)' 'speedup')"
log_plain "$(printf '%0.s-' {1..76})"
{
grep -v $'\tN/A$' "$_tmp_timing" | sort -t$'\t' -k4 -n || true
grep $'\tN/A$' "$_tmp_timing" | sort -t$'\t' -k1 || true
} | while IFS=$'\t' read -r case_key our_s gcc_s speedup; do
disp="${case_key##*/}"
if [[ "$speedup" == "N/A" ]]; then
log_plain "$(printf '%-40s %10s %10s %10s' "$disp" "${our_s}s" "N/A" "N/A")"
else
log_plain "$(printf '%-40s %10s %10s %9sx' "$disp" "${our_s}s" "${gcc_s}s" "$speedup")"
fi
done
# 排序2我方总用时降序
log_plain ""
log_plain "--- [Sort 2] Our elapsed time descending (slowest first) ---"
log_plain "$(printf '%-40s %10s %10s %10s' 'case' 'our(s)' 'gcc(s)' 'speedup')"
log_plain "$(printf '%0.s-' {1..76})"
sort -t$'\t' -k2 -rn "$_tmp_timing" | \
while IFS=$'\t' read -r case_key our_s gcc_s speedup; do
disp="${case_key##*/}"
if [[ "$speedup" == "N/A" ]]; then
log_plain "$(printf '%-40s %10s %10s %10s' "$disp" "${our_s}s" "N/A" "N/A")"
else
log_plain "$(printf '%-40s %10s %10s %9sx' "$disp" "${our_s}s" "${gcc_s}s" "$speedup")"
fi
done
rm -f "$_tmp_timing"
else
# 无基线:只输出总用时降序
log_plain ""
log_plain "--- [Sort] Our elapsed time descending (slowest first) ---"
log_plain "$(printf '%-40s %10s' 'case' 'our(s)')"
log_plain "$(printf '%0.s-' {1..54})"
while IFS=$'\t' read -r case_key our_ns _; do
our_s=$(awk "BEGIN{printf \"%.5f\", $our_ns / 1000000000}")
printf '%s\t%s\n' "$case_key" "$our_s"
done < "$TIMING_TSV" | \
sort -t$'\t' -k2 -rn | \
while IFS=$'\t' read -r case_key our_s; do
disp="${case_key##*/}"
log_plain "$(printf '%-40s %10ss' "$disp" "$our_s")"
done
log_plain ""
log_color "$CYAN" "Tip: run scripts/run_baseline.sh to compute GCC -O2 baseline for speedup analysis."
fi
log_plain ""
log_plain "timing data saved to: $TIMING_TSV"
fi
# ---------- 失败用例列表 ----------
if [[ ${#FAIL_LIST[@]} -gt 0 ]]; then
log_plain "failed cases:"
for f in "${FAIL_LIST[@]}"; do
safe_name="${f//\//_}"
log_plain "- $f"
log_plain " artifacts: $FAIL_DIR/${safe_name%.sy}"
done
else
log_plain "all successful case artifacts have been deleted automatically."
fi
log_plain "whole log saved to: $WHOLE_LOG"
[[ $FAIL -eq 0 ]]