|
|
#!/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"
|
|
|
# 本次运行的我方计时 TSV:stem<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 ]]
|