|
|
|
|
@ -0,0 +1,978 @@
|
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# compare_ra.sh — 寄存器分配编译器分析工具
|
|
|
|
|
#
|
|
|
|
|
# 对比模式:
|
|
|
|
|
# ./scripts/compare_ra.sh --old feature/mir # 对比 feature/mir 分支 vs 本地
|
|
|
|
|
# ./scripts/compare_ra.sh --old 70234dd --new 8ece3ac # 对比两个指定 commit
|
|
|
|
|
# ./scripts/compare_ra.sh --old path/to/old/compiler --no-build # 使用已构建的旧编译器
|
|
|
|
|
#
|
|
|
|
|
# 单版本统计模式:
|
|
|
|
|
# ./scripts/compare_ra.sh --ref 5b8303d # 统计指定版本的数据
|
|
|
|
|
# ./scripts/compare_ra.sh --ref feature/ra --tests performance # 仅统计性能数据
|
|
|
|
|
# ./scripts/compare_ra.sh --ref /path/to/compiler --no-build # 使用预构建编译器
|
|
|
|
|
#
|
|
|
|
|
# 输出: 终端表格 + compare_result/ 目录下的详细文件
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
# ========== 参数解析 ==========
|
|
|
|
|
OLD_REF=""
|
|
|
|
|
NEW_REF="" # 空 = 使用本地 build 目录
|
|
|
|
|
SINGLE_REF="" # --ref: 单版本统计模式,不与其它版本比较
|
|
|
|
|
MODE="all" # asm | run | all
|
|
|
|
|
TEST_SET="all" # functional | performance | all
|
|
|
|
|
NO_BUILD=false
|
|
|
|
|
RUN_COUNT=1 # 单版本模式下每个测试的执行次数
|
|
|
|
|
OLD_COMPILER_PATH=""
|
|
|
|
|
NEW_COMPILER_PATH="./build/bin/compiler"
|
|
|
|
|
WORKTREE_DIR=""
|
|
|
|
|
NEW_WORKTREE_DIR=""
|
|
|
|
|
SINGLE_WORKTREE_DIR=""
|
|
|
|
|
KEEP_WORKTREE=false
|
|
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
|
echo "用法:"
|
|
|
|
|
echo " 对比模式: $0 --old <branch|commit|path> [选项]"
|
|
|
|
|
echo " 单版本模式: $0 --ref <branch|commit|path> [选项]"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "对比模式选项(--old 与 --ref 互斥):"
|
|
|
|
|
echo " --old <ref> 对比基线:git 分支名、commit hash、或旧编译器路径"
|
|
|
|
|
echo " --new <ref> 新版基线:git 分支名或 commit hash(默认使用本地 ./build/bin/compiler)"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "单版本模式选项:"
|
|
|
|
|
echo " --ref <ref> 统计指定版本的汇编和运行数据(不进行版本间对比)"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "通用选项:"
|
|
|
|
|
echo " --mode <mode> 模式: asm (仅汇编) | run (仅运行) | all (默认)"
|
|
|
|
|
echo " --tests <set> 测试集: functional | performance | all (默认)"
|
|
|
|
|
echo " --runs <N> 单版本模式下每个测试的执行次数(默认 1)"
|
|
|
|
|
echo " --no-build <ref> 指向编译器可执行文件路径,跳过构建"
|
|
|
|
|
echo " --keep-worktree 保留 git worktree(默认会删除)"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "示例:"
|
|
|
|
|
echo " $0 --old feature/mir # 对比 feature/mir 分支 vs 本地版本"
|
|
|
|
|
echo " $0 --old 70234dd --new 8ece3ac # 对比两个指定 commit"
|
|
|
|
|
echo " $0 --old feature/mir --tests performance --mode run # 仅对比性能测试的运行时间"
|
|
|
|
|
echo " $0 --old /tmp/old-compiler --no-build # 使用预构建的旧编译器"
|
|
|
|
|
echo " $0 --ref 5b8303d --mode asm # 统计 5b8303d 版本的汇编数据"
|
|
|
|
|
echo " $0 --ref feature/ra --tests performance # 统计 feature/ra 分支的性能数据"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--old) OLD_REF="$2"; shift 2 ;;
|
|
|
|
|
--new) NEW_REF="$2"; shift 2 ;;
|
|
|
|
|
--ref) SINGLE_REF="$2"; shift 2 ;;
|
|
|
|
|
--mode) MODE="$2"; shift 2 ;;
|
|
|
|
|
--tests) TEST_SET="$2"; shift 2 ;;
|
|
|
|
|
--runs) RUN_COUNT="$2"; shift 2 ;;
|
|
|
|
|
--no-build) NO_BUILD=true; shift ;;
|
|
|
|
|
--keep-worktree) KEEP_WORKTREE=true; shift ;;
|
|
|
|
|
*) echo "未知参数: $1"; usage ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ -n "$SINGLE_REF" ]]; then
|
|
|
|
|
# 单版本模式:--ref 与 --old / --new 互斥
|
|
|
|
|
if [[ -n "$OLD_REF" || -n "$NEW_REF" ]]; then
|
|
|
|
|
echo "错误: --ref 与 --old / --new 互斥"
|
|
|
|
|
usage
|
|
|
|
|
fi
|
|
|
|
|
elif [[ -z "$OLD_REF" ]]; then
|
|
|
|
|
echo "错误: 必须指定 --old(对比模式)或 --ref(单版本模式)"
|
|
|
|
|
usage
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ========== 路径设置 ==========
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
|
|
RESULT_DIR="$PROJECT_DIR/compare_result"
|
|
|
|
|
OLD_BUILD_DIR=""
|
|
|
|
|
OLD_COMPILER=""
|
|
|
|
|
|
|
|
|
|
rm -rf "$RESULT_DIR"
|
|
|
|
|
mkdir -p "$RESULT_DIR"
|
|
|
|
|
|
|
|
|
|
# ========== 构建旧版编译器 ==========
|
|
|
|
|
setup_old_compiler() {
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
# 用户直接提供编译器路径
|
|
|
|
|
if [[ ! -x "$OLD_REF" ]]; then
|
|
|
|
|
echo "错误: 旧编译器路径不存在或不可执行: $OLD_REF"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
OLD_COMPILER="$(realpath "$OLD_REF")"
|
|
|
|
|
echo "使用预构建旧编译器: $OLD_COMPILER"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 检查是否是 git ref
|
|
|
|
|
if ! git rev-parse --verify "$OLD_REF" >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: '$OLD_REF' 不是有效的 git 引用(分支/commit),或使用 --no-build 指定编译器路径"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
WORKTREE_DIR="$PROJECT_DIR/.worktree-old-$(echo "$OLD_REF" | tr '/' '-')"
|
|
|
|
|
OLD_BUILD_DIR="$WORKTREE_DIR/build"
|
|
|
|
|
|
|
|
|
|
echo "=== 准备旧版编译器 ==="
|
|
|
|
|
echo "Git 引用: $OLD_REF"
|
|
|
|
|
echo "Worktree: $WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
# 清理已有的 worktree
|
|
|
|
|
if [[ -d "$WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "移除已有 worktree..."
|
|
|
|
|
git worktree remove --force "$WORKTREE_DIR" 2>/dev/null || rm -rf "$WORKTREE_DIR"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
git worktree add "$WORKTREE_DIR" "$OLD_REF"
|
|
|
|
|
echo "Worktree 已创建: $WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
# 生成 ANTLR 语法分析器
|
|
|
|
|
echo "生成 ANTLR 语法分析器..."
|
|
|
|
|
java -jar "$WORKTREE_DIR/third_party/antlr-4.13.2-complete.jar" \
|
|
|
|
|
-Dlanguage=Cpp \
|
|
|
|
|
-visitor -no-listener \
|
|
|
|
|
-Xexact-output-dir \
|
|
|
|
|
-o "$OLD_BUILD_DIR/generated/antlr4" \
|
|
|
|
|
"$WORKTREE_DIR/src/antlr4/SysY.g4" > "$RESULT_DIR/build_old.log" 2>&1 || {
|
|
|
|
|
echo "错误: ANTLR 生成失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build_old.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 构建
|
|
|
|
|
echo "构建旧版编译器..."
|
|
|
|
|
cmake -S "$WORKTREE_DIR" -B "$OLD_BUILD_DIR" -DCMAKE_BUILD_TYPE=Release >> "$RESULT_DIR/build_old.log" 2>&1 || {
|
|
|
|
|
echo "错误: 旧版 cmake 配置失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build_old.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
cmake --build "$OLD_BUILD_DIR" -j"$(nproc 2>/dev/null || echo 4)" >> "$RESULT_DIR/build_old.log" 2>&1 || {
|
|
|
|
|
echo "错误: 旧版构建失败,日志:"
|
|
|
|
|
tail -30 "$RESULT_DIR/build_old.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OLD_COMPILER="$OLD_BUILD_DIR/bin/compiler"
|
|
|
|
|
if [[ ! -x "$OLD_COMPILER" ]]; then
|
|
|
|
|
echo "错误: 旧编译器未生成: $OLD_COMPILER"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "旧编译器已构建: $OLD_COMPILER"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 确保新版编译器存在 ==========
|
|
|
|
|
setup_new_compiler() {
|
|
|
|
|
# 如果指定了 --new <ref>,从 worktree 构建新版编译器
|
|
|
|
|
if [[ -n "$NEW_REF" ]]; then
|
|
|
|
|
setup_new_compiler_from_ref
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ! -x "$NEW_COMPILER_PATH" ]]; then
|
|
|
|
|
echo "错误: 新编译器不存在,请先构建: cmake -B build && cmake --build build -j"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
NEW_COMPILER="$(realpath "$NEW_COMPILER_PATH")"
|
|
|
|
|
echo "新版编译器: $NEW_COMPILER"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setup_new_compiler_from_ref() {
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
if [[ ! -x "$NEW_REF" ]]; then
|
|
|
|
|
echo "错误: 新编译器路径不存在或不可执行: $NEW_REF"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
NEW_COMPILER="$(realpath "$NEW_REF")"
|
|
|
|
|
echo "使用预构建新编译器: $NEW_COMPILER"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! git rev-parse --verify "$NEW_REF" >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: '$NEW_REF' 不是有效的 git 引用(分支/commit)"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
NEW_WORKTREE_DIR="$PROJECT_DIR/.worktree-new-$(echo "$NEW_REF" | tr '/' '-')"
|
|
|
|
|
local new_build_dir="$NEW_WORKTREE_DIR/build"
|
|
|
|
|
|
|
|
|
|
echo "=== 准备新版编译器 ==="
|
|
|
|
|
echo "Git 引用: $NEW_REF"
|
|
|
|
|
echo "Worktree: $NEW_WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
if [[ -d "$NEW_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "移除已有 worktree..."
|
|
|
|
|
git worktree remove --force "$NEW_WORKTREE_DIR" 2>/dev/null || rm -rf "$NEW_WORKTREE_DIR"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
git worktree add "$NEW_WORKTREE_DIR" "$NEW_REF"
|
|
|
|
|
echo "Worktree 已创建: $NEW_WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
echo "生成 ANTLR 语法分析器..."
|
|
|
|
|
java -jar "$NEW_WORKTREE_DIR/third_party/antlr-4.13.2-complete.jar" \
|
|
|
|
|
-Dlanguage=Cpp \
|
|
|
|
|
-visitor -no-listener \
|
|
|
|
|
-Xexact-output-dir \
|
|
|
|
|
-o "$new_build_dir/generated/antlr4" \
|
|
|
|
|
"$NEW_WORKTREE_DIR/src/antlr4/SysY.g4" > "$RESULT_DIR/build_new.log" 2>&1 || {
|
|
|
|
|
echo "错误: ANTLR 生成失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build_new.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "构建新版编译器..."
|
|
|
|
|
cmake -S "$NEW_WORKTREE_DIR" -B "$new_build_dir" -DCMAKE_BUILD_TYPE=Release >> "$RESULT_DIR/build_new.log" 2>&1 || {
|
|
|
|
|
echo "错误: 新版 cmake 配置失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build_new.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
cmake --build "$new_build_dir" -j"$(nproc 2>/dev/null || echo 4)" >> "$RESULT_DIR/build_new.log" 2>&1 || {
|
|
|
|
|
echo "错误: 新版构建失败,日志:"
|
|
|
|
|
tail -30 "$RESULT_DIR/build_new.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NEW_COMPILER="$new_build_dir/bin/compiler"
|
|
|
|
|
if [[ ! -x "$NEW_COMPILER" ]]; then
|
|
|
|
|
echo "错误: 新编译器未生成: $NEW_COMPILER"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "新编译器已构建: $NEW_COMPILER"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 单版本模式:构建编译器 ==========
|
|
|
|
|
setup_ref_compiler() {
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
if [[ ! -x "$SINGLE_REF" ]]; then
|
|
|
|
|
echo "错误: 编译器路径不存在或不可执行: $SINGLE_REF"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
NEW_COMPILER="$(realpath "$SINGLE_REF")"
|
|
|
|
|
echo "使用预构建编译器: $NEW_COMPILER"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! git rev-parse --verify "$SINGLE_REF" >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: '$SINGLE_REF' 不是有效的 git 引用(分支/commit),或使用 --no-build 指定编译器路径"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
SINGLE_WORKTREE_DIR="$PROJECT_DIR/.worktree-ref-$(echo "$SINGLE_REF" | tr '/' '-')"
|
|
|
|
|
local build_dir="$SINGLE_WORKTREE_DIR/build"
|
|
|
|
|
|
|
|
|
|
echo "=== 准备编译器 ==="
|
|
|
|
|
echo "Git 引用: $SINGLE_REF"
|
|
|
|
|
echo "Worktree: $SINGLE_WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
if [[ -d "$SINGLE_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "移除已有 worktree..."
|
|
|
|
|
git worktree remove --force "$SINGLE_WORKTREE_DIR" 2>/dev/null || rm -rf "$SINGLE_WORKTREE_DIR"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
git worktree add "$SINGLE_WORKTREE_DIR" "$SINGLE_REF"
|
|
|
|
|
echo "Worktree 已创建: $SINGLE_WORKTREE_DIR"
|
|
|
|
|
|
|
|
|
|
echo "生成 ANTLR 语法分析器..."
|
|
|
|
|
java -jar "$SINGLE_WORKTREE_DIR/third_party/antlr-4.13.2-complete.jar" \
|
|
|
|
|
-Dlanguage=Cpp \
|
|
|
|
|
-visitor -no-listener \
|
|
|
|
|
-Xexact-output-dir \
|
|
|
|
|
-o "$build_dir/generated/antlr4" \
|
|
|
|
|
"$SINGLE_WORKTREE_DIR/src/antlr4/SysY.g4" > "$RESULT_DIR/build.log" 2>&1 || {
|
|
|
|
|
echo "错误: ANTLR 生成失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "构建编译器..."
|
|
|
|
|
cmake -S "$SINGLE_WORKTREE_DIR" -B "$build_dir" -DCMAKE_BUILD_TYPE=Release >> "$RESULT_DIR/build.log" 2>&1 || {
|
|
|
|
|
echo "错误: cmake 配置失败,日志:"
|
|
|
|
|
tail -20 "$RESULT_DIR/build.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
cmake --build "$build_dir" -j"$(nproc 2>/dev/null || echo 4)" >> "$RESULT_DIR/build.log" 2>&1 || {
|
|
|
|
|
echo "错误: 构建失败,日志:"
|
|
|
|
|
tail -30 "$RESULT_DIR/build.log"
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NEW_COMPILER="$build_dir/bin/compiler"
|
|
|
|
|
if [[ ! -x "$NEW_COMPILER" ]]; then
|
|
|
|
|
echo "错误: 编译器未生成: $NEW_COMPILER"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
echo "编译器已构建: $NEW_COMPILER"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 版本信息 ==========
|
|
|
|
|
# 获取 worktree 目录中的 commit 信息
|
|
|
|
|
get_worktree_commit() {
|
|
|
|
|
local wt_dir="$1"
|
|
|
|
|
if [[ -d "$wt_dir/.git" ]] || git -C "$wt_dir" rev-parse --git-dir >/dev/null 2>&1; then
|
|
|
|
|
git -C "$wt_dir" log -1 --format="%h %s" 2>/dev/null || echo "unknown"
|
|
|
|
|
else
|
|
|
|
|
echo "unknown"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 获取本地仓库状态描述
|
|
|
|
|
get_local_version() {
|
|
|
|
|
local commit; commit=$(git log -1 --format="%h %s" 2>/dev/null || echo "unknown")
|
|
|
|
|
if git status --porcelain | grep -q '^[ M]'; then
|
|
|
|
|
echo "$commit (+uncommitted changes)"
|
|
|
|
|
else
|
|
|
|
|
echo "$commit"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
show_compiler_versions() {
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== 编译器版本信息 ==="
|
|
|
|
|
|
|
|
|
|
# 旧版版本
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
echo "旧版: $OLD_REF (预构建)"
|
|
|
|
|
elif [[ -n "$WORKTREE_DIR" && -d "$WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "旧版: $(get_worktree_commit "$WORKTREE_DIR")"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 新版版本
|
|
|
|
|
if [[ -n "$NEW_REF" ]]; then
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
echo "新版: $NEW_REF (预构建)"
|
|
|
|
|
elif [[ -n "$NEW_WORKTREE_DIR" && -d "$NEW_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "新版: $(get_worktree_commit "$NEW_WORKTREE_DIR")"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
echo "新版: $(get_local_version)"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 工具检查 ==========
|
|
|
|
|
check_tools() {
|
|
|
|
|
if [[ "$NO_BUILD" == false ]]; then
|
|
|
|
|
if ! command -v java >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: 未找到 java,构建时需要 ANTLR 生成语法分析器"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
|
|
|
|
echo "警告: 未找到 aarch64-linux-gnu-gcc,汇编模式可用但运行模式不可用"
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$MODE" == "run" || "$MODE" == "all" ]]; then
|
|
|
|
|
if ! command -v qemu-aarch64 >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: 未找到 qemu-aarch64,无法运行测试"
|
|
|
|
|
echo " apt install qemu-user (Ubuntu/Debian)"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
|
|
|
|
echo "错误: 未找到 aarch64-linux-gnu-gcc,无法链接可执行文件"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 获取测试列表 ==========
|
|
|
|
|
get_tests() {
|
|
|
|
|
local test_dir="$PROJECT_DIR/test/test_case"
|
|
|
|
|
local tests=()
|
|
|
|
|
|
|
|
|
|
if [[ "$TEST_SET" == "functional" || "$TEST_SET" == "all" ]]; then
|
|
|
|
|
for f in "$test_dir/functional"/*.sy; do
|
|
|
|
|
[[ -f "$f" ]] && tests+=("$f")
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$TEST_SET" == "performance" || "$TEST_SET" == "all" ]]; then
|
|
|
|
|
for f in "$test_dir/performance"/*.sy; do
|
|
|
|
|
[[ -f "$f" ]] && tests+=("$f")
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ${#tests[@]} -eq 0 ]]; then
|
|
|
|
|
echo "错误: 没有找到测试用例"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "${tests[@]}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 汇编对比 ==========
|
|
|
|
|
compare_asm() {
|
|
|
|
|
local test_file="$1"
|
|
|
|
|
local stem; stem=$(basename "$test_file" .sy)
|
|
|
|
|
local test_dir; test_dir=$(dirname "$test_file")
|
|
|
|
|
|
|
|
|
|
local old_asm="$RESULT_DIR/asm/$stem.old.s"
|
|
|
|
|
local new_asm="$RESULT_DIR/asm/$stem.new.s"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$RESULT_DIR/asm"
|
|
|
|
|
|
|
|
|
|
"$OLD_COMPILER" --emit-asm "$test_file" > "$old_asm" 2>/dev/null || {
|
|
|
|
|
echo "OLD_BUILD_FAIL" > "$RESULT_DIR/asm/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
"$NEW_COMPILER" --emit-asm "$test_file" > "$new_asm" 2>/dev/null || {
|
|
|
|
|
echo "NEW_BUILD_FAIL" > "$RESULT_DIR/asm/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 统计指令数(排除标签行、伪指令行、空行)
|
|
|
|
|
local old_inst new_inst old_mem new_mem old_branches new_branches
|
|
|
|
|
old_inst=$(grep -cE '^\s+\w+\s' "$old_asm" 2>/dev/null || echo 0)
|
|
|
|
|
new_inst=$(grep -cE '^\s+\w+\s' "$new_asm" 2>/dev/null || echo 0)
|
|
|
|
|
old_mem=$(grep -cE '\b(ldr|str|ldur|stur|ldp|stp)\b' "$old_asm" 2>/dev/null || echo 0)
|
|
|
|
|
new_mem=$(grep -cE '\b(ldr|str|ldur|stur|ldp|stp)\b' "$new_asm" 2>/dev/null || echo 0)
|
|
|
|
|
old_branches=$(grep -cE '\bb(|\.\w+)\s' "$old_asm" 2>/dev/null || echo 0)
|
|
|
|
|
new_branches=$(grep -cE '\bb(|\.\w+)\s' "$new_asm" 2>/dev/null || echo 0)
|
|
|
|
|
|
|
|
|
|
# 计算变化百分比
|
|
|
|
|
local inst_pct mem_pct
|
|
|
|
|
if [[ "$old_inst" -gt 0 ]]; then
|
|
|
|
|
inst_pct=$(echo "scale=1; ($new_inst - $old_inst) * 100 / $old_inst" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
else
|
|
|
|
|
inst_pct="N/A"
|
|
|
|
|
fi
|
|
|
|
|
if [[ "$old_mem" -gt 0 ]]; then
|
|
|
|
|
mem_pct=$(echo "scale=1; ($new_mem - $old_mem) * 100 / $old_mem" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
else
|
|
|
|
|
mem_pct="N/A"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 保存结果
|
|
|
|
|
echo "$stem $old_inst $new_inst $inst_pct $old_mem $new_mem $mem_pct $old_branches $new_branches" \
|
|
|
|
|
> "$RESULT_DIR/asm/$stem.result"
|
|
|
|
|
|
|
|
|
|
# 生成 diff
|
|
|
|
|
diff -u "$old_asm" "$new_asm" > "$RESULT_DIR/asm/$stem.diff" 2>/dev/null || true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 运行对比 ==========
|
|
|
|
|
compare_run() {
|
|
|
|
|
local test_file="$1"
|
|
|
|
|
local stem; stem=$(basename "$test_file" .sy)
|
|
|
|
|
local test_dir; test_dir=$(dirname "$test_file")
|
|
|
|
|
|
|
|
|
|
local old_exe="$RESULT_DIR/run/$stem.old"
|
|
|
|
|
local new_exe="$RESULT_DIR/run/$stem.new"
|
|
|
|
|
local old_out="$RESULT_DIR/run/$stem.old.out"
|
|
|
|
|
local new_out="$RESULT_DIR/run/$stem.new.out"
|
|
|
|
|
local stdin_file="$test_dir/$stem.in"
|
|
|
|
|
local expected_file="$test_dir/$stem.out"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$RESULT_DIR/run"
|
|
|
|
|
|
|
|
|
|
# 生成旧版可执行文件
|
|
|
|
|
local old_asm="$RESULT_DIR/run/$stem.old.s"
|
|
|
|
|
"$OLD_COMPILER" --emit-asm "$test_file" > "$old_asm" 2>/dev/null || {
|
|
|
|
|
echo "OLD_COMPILE_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
aarch64-linux-gnu-gcc -no-pie "$old_asm" -L"$PROJECT_DIR/sylib" -lsysy -static -o "$old_exe" 2>/dev/null || {
|
|
|
|
|
echo "OLD_LINK_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 生成新版可执行文件
|
|
|
|
|
local new_asm="$RESULT_DIR/run/$stem.new.s"
|
|
|
|
|
"$NEW_COMPILER" --emit-asm "$test_file" > "$new_asm" 2>/dev/null || {
|
|
|
|
|
echo "NEW_COMPILE_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
aarch64-linux-gnu-gcc -no-pie "$new_asm" -L"$PROJECT_DIR/sylib" -lsysy -static -o "$new_exe" 2>/dev/null || {
|
|
|
|
|
echo "NEW_LINK_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 运行旧版
|
|
|
|
|
local old_time="N/A" old_rc="N/A"
|
|
|
|
|
set +eo pipefail
|
|
|
|
|
if [[ -f "$stdin_file" ]]; then
|
|
|
|
|
old_time=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$old_exe" < "$stdin_file" > "$old_out" 2>/dev/null; echo $? > "$old_out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
else
|
|
|
|
|
old_time=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$old_exe" > "$old_out" 2>/dev/null; echo $? > "$old_out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
fi
|
|
|
|
|
old_rc=$(cat "$old_out.rc" 2>/dev/null || echo "1")
|
|
|
|
|
|
|
|
|
|
# 运行新版
|
|
|
|
|
local new_time="N/A" new_rc="N/A"
|
|
|
|
|
if [[ -f "$stdin_file" ]]; then
|
|
|
|
|
new_time=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$new_exe" < "$stdin_file" > "$new_out" 2>/dev/null; echo $? > "$new_out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
else
|
|
|
|
|
new_time=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$new_exe" > "$new_out" 2>/dev/null; echo $? > "$new_out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
fi
|
|
|
|
|
new_rc=$(cat "$new_out.rc" 2>/dev/null || echo "1")
|
|
|
|
|
set -eo pipefail
|
|
|
|
|
|
|
|
|
|
# 构造实际输出(程序输出 + 退出码),与 verify_asm.sh 格式一致
|
|
|
|
|
local old_actual="$RESULT_DIR/run/$stem.old.actual"
|
|
|
|
|
local new_actual="$RESULT_DIR/run/$stem.new.actual"
|
|
|
|
|
{
|
|
|
|
|
cat "$old_out"
|
|
|
|
|
if [[ -s "$old_out" ]] && (( $(tail -c 1 "$old_out" | wc -l) == 0 )); then
|
|
|
|
|
printf '\n'
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "$old_rc"
|
|
|
|
|
} > "$old_actual"
|
|
|
|
|
{
|
|
|
|
|
cat "$new_out"
|
|
|
|
|
if [[ -s "$new_out" ]] && (( $(tail -c 1 "$new_out" | wc -l) == 0 )); then
|
|
|
|
|
printf '\n'
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "$new_rc"
|
|
|
|
|
} > "$new_actual"
|
|
|
|
|
|
|
|
|
|
# 检查输出匹配(与 expected 文件比较,expected 格式为 stdout + exit_code)
|
|
|
|
|
local old_match="N" new_match="N"
|
|
|
|
|
if [[ -f "$expected_file" ]]; then
|
|
|
|
|
diff -w -q "$old_actual" "$expected_file" >/dev/null 2>&1 && old_match="Y"
|
|
|
|
|
diff -w -q "$new_actual" "$expected_file" >/dev/null 2>&1 && new_match="Y"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 速度比
|
|
|
|
|
local speedup="N/A"
|
|
|
|
|
if [[ "$old_time" != "N/A" && "$new_time" != "N/A" ]]; then
|
|
|
|
|
local old_sec new_sec
|
|
|
|
|
old_sec=$(echo "$old_time" | sed 's/m/ /' | awk '{print $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
new_sec=$(echo "$new_time" | sed 's/m/ /' | awk '{print $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
if [[ "$(echo "$new_sec > 0" | bc -l 2>/dev/null)" == "1" ]]; then
|
|
|
|
|
speedup=$(echo "scale=2; $old_sec / $new_sec" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "$stem $old_time $new_time $speedup $old_match $new_match $old_rc $new_rc" \
|
|
|
|
|
> "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 单版本汇编分析 ==========
|
|
|
|
|
analyze_asm() {
|
|
|
|
|
local test_file="$1"
|
|
|
|
|
local stem; stem=$(basename "$test_file" .sy)
|
|
|
|
|
|
|
|
|
|
local asm="$RESULT_DIR/asm/$stem.s"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$RESULT_DIR/asm"
|
|
|
|
|
|
|
|
|
|
"$NEW_COMPILER" --emit-asm "$test_file" > "$asm" 2>/dev/null || {
|
|
|
|
|
echo "COMPILE_FAIL" > "$RESULT_DIR/asm/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local inst mem branches
|
|
|
|
|
inst=$(grep -cE '^\s+\w+\s' "$asm" 2>/dev/null || echo 0)
|
|
|
|
|
mem=$(grep -cE '\b(ldr|str|ldur|stur|ldp|stp)\b' "$asm" 2>/dev/null || echo 0)
|
|
|
|
|
branches=$(grep -cE '\bb(|\.\w+)\s' "$asm" 2>/dev/null || echo 0)
|
|
|
|
|
|
|
|
|
|
echo "$stem $inst $mem $branches" > "$RESULT_DIR/asm/$stem.result"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 单版本运行分析 ==========
|
|
|
|
|
analyze_run() {
|
|
|
|
|
local test_file="$1"
|
|
|
|
|
local stem; stem=$(basename "$test_file" .sy)
|
|
|
|
|
local test_dir; test_dir=$(dirname "$test_file")
|
|
|
|
|
|
|
|
|
|
local exe="$RESULT_DIR/run/$stem"
|
|
|
|
|
local stdin_file="$test_dir/$stem.in"
|
|
|
|
|
local expected_file="$test_dir/$stem.out"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$RESULT_DIR/run"
|
|
|
|
|
|
|
|
|
|
local asm="$RESULT_DIR/run/$stem.s"
|
|
|
|
|
"$NEW_COMPILER" --emit-asm "$test_file" > "$asm" 2>/dev/null || {
|
|
|
|
|
echo "COMPILE_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
aarch64-linux-gnu-gcc -no-pie "$asm" -L"$PROJECT_DIR/sylib" -lsysy -static -o "$exe" 2>/dev/null || {
|
|
|
|
|
echo "LINK_FAIL" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 多次执行收集时间数据
|
|
|
|
|
local times=() total_sec=0 match="N" rc="N/A"
|
|
|
|
|
set +eo pipefail
|
|
|
|
|
for ((run=1; run<=RUN_COUNT; run++)); do
|
|
|
|
|
local out="$RESULT_DIR/run/$stem.run$run.out"
|
|
|
|
|
local time_val="N/A"
|
|
|
|
|
if [[ -f "$stdin_file" ]]; then
|
|
|
|
|
time_val=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" < "$stdin_file" > "$out" 2>/dev/null; echo $? > "$out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
else
|
|
|
|
|
time_val=$( { time qemu-aarch64 -L /usr/aarch64-linux-gnu "$exe" > "$out" 2>/dev/null; echo $? > "$out.rc"; } 2>&1 | grep real | awk '{print $2}' || echo "N/A")
|
|
|
|
|
fi
|
|
|
|
|
rc=$(cat "$out.rc" 2>/dev/null || echo "1")
|
|
|
|
|
times+=("$time_val")
|
|
|
|
|
|
|
|
|
|
# 转换为秒并累加
|
|
|
|
|
local sec
|
|
|
|
|
sec=$(echo "$time_val" | sed 's/m/ /;s/s//' | awk '{printf "%.3f", $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
total_sec=$(echo "scale=3; $total_sec + $sec" | bc 2>/dev/null || echo "$total_sec")
|
|
|
|
|
done
|
|
|
|
|
set -eo pipefail
|
|
|
|
|
|
|
|
|
|
# 计算平均时间
|
|
|
|
|
local avg_time="N/A"
|
|
|
|
|
if [[ "$(echo "$RUN_COUNT > 0" | bc -l 2>/dev/null)" == "1" ]]; then
|
|
|
|
|
local avg_sec
|
|
|
|
|
avg_sec=$(echo "scale=3; $total_sec / $RUN_COUNT" | bc 2>/dev/null || echo "0")
|
|
|
|
|
# 格式化为 m:ss.s 格式
|
|
|
|
|
local avg_min avg_s
|
|
|
|
|
avg_min=$(echo "scale=0; $avg_sec / 1" | bc 2>/dev/null || echo 0)
|
|
|
|
|
avg_min=$(( avg_min / 60 ))
|
|
|
|
|
avg_s=$(echo "scale=3; $avg_sec - $avg_min * 60" | bc 2>/dev/null || echo "0")
|
|
|
|
|
avg_time=$(printf "%dm%.3fs" "$avg_min" "$avg_s")
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 检查输出匹配(使用最后一次运行的输出)
|
|
|
|
|
local last_out="$RESULT_DIR/run/$stem.run$RUN_COUNT.out"
|
|
|
|
|
local actual="$RESULT_DIR/run/$stem.actual"
|
|
|
|
|
{
|
|
|
|
|
cat "$last_out"
|
|
|
|
|
if [[ -s "$last_out" ]] && (( $(tail -c 1 "$last_out" | wc -l) == 0 )); then
|
|
|
|
|
printf '\n'
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "$rc"
|
|
|
|
|
} > "$actual"
|
|
|
|
|
|
|
|
|
|
if [[ -f "$expected_file" ]]; then
|
|
|
|
|
diff -w -q "$actual" "$expected_file" >/dev/null 2>&1 && match="Y"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 保存结果: stem run1 run2 ... runN avg match rc
|
|
|
|
|
printf '%s' "$stem" > "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
for t in "${times[@]}"; do
|
|
|
|
|
printf ' %s' "$t" >> "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
done
|
|
|
|
|
printf ' %s %s %s\n' "$avg_time" "$match" "$rc" >> "$RESULT_DIR/run/$stem.result"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ========== 单版本主流程 ==========
|
|
|
|
|
main_single() {
|
|
|
|
|
echo "============================================="
|
|
|
|
|
echo " 寄存器分配编译器 — 单版本统计"
|
|
|
|
|
echo " 版本: $SINGLE_REF"
|
|
|
|
|
echo " 测试集: $TEST_SET"
|
|
|
|
|
echo " 模式: $MODE"
|
|
|
|
|
echo "============================================="
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
check_tools
|
|
|
|
|
setup_ref_compiler
|
|
|
|
|
|
|
|
|
|
# 版本信息
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=== 编译器版本信息 ==="
|
|
|
|
|
if [[ "$NO_BUILD" == true ]]; then
|
|
|
|
|
echo "版本: $SINGLE_REF (预构建)"
|
|
|
|
|
elif [[ -n "$SINGLE_WORKTREE_DIR" && -d "$SINGLE_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "版本: $(get_worktree_commit "$SINGLE_WORKTREE_DIR")"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
local tests
|
|
|
|
|
mapfile -t tests < <(get_tests)
|
|
|
|
|
local total=${#tests[@]}
|
|
|
|
|
echo "共 $total 个测试用例"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# ========== 汇编统计 ==========
|
|
|
|
|
if [[ "$MODE" == "asm" || "$MODE" == "all" ]]; then
|
|
|
|
|
echo "=== 汇编统计 ==="
|
|
|
|
|
local count=0
|
|
|
|
|
for test_file in "${tests[@]}"; do
|
|
|
|
|
analyze_asm "$test_file"
|
|
|
|
|
count=$((count + 1))
|
|
|
|
|
printf "\r 进度: %d/%d" "$count" "$total"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
printf "%-30s %8s %8s %8s\n" \
|
|
|
|
|
"测试用例" "指令数" "访存数" "分支数"
|
|
|
|
|
printf "%-30s %8s %8s %8s\n" \
|
|
|
|
|
"------------------------------" "--------" "--------" "--------"
|
|
|
|
|
|
|
|
|
|
local total_inst=0 total_mem=0 total_branches=0 valid_count=0
|
|
|
|
|
for f in "$RESULT_DIR/asm"/*.result; do
|
|
|
|
|
[[ -f "$f" ]] || continue
|
|
|
|
|
local result
|
|
|
|
|
result=$(cat "$f")
|
|
|
|
|
if [[ "$result" == *"FAIL"* ]]; then
|
|
|
|
|
printf "%-30s %8s\n" "$(basename "$f" .result)" "编译失败"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
read -r stem inst mem branches <<< "$result"
|
|
|
|
|
printf "%-30s %8d %8d %8d\n" "$stem" "$inst" "$mem" "$branches"
|
|
|
|
|
total_inst=$((total_inst + inst))
|
|
|
|
|
total_mem=$((total_mem + mem))
|
|
|
|
|
total_branches=$((total_branches + branches))
|
|
|
|
|
valid_count=$((valid_count + 1))
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "$valid_count" -gt 0 ]]; then
|
|
|
|
|
printf "%-30s %8d %8d %8d\n" \
|
|
|
|
|
"--- 合计 ---" "$total_inst" "$total_mem" "$total_branches"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
echo "汇编文件: $RESULT_DIR/asm/*.s"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ========== 运行统计 ==========
|
|
|
|
|
if [[ "$MODE" == "run" || "$MODE" == "all" ]]; then
|
|
|
|
|
echo "=== 运行统计 ==="
|
|
|
|
|
local count=0
|
|
|
|
|
for test_file in "${tests[@]}"; do
|
|
|
|
|
analyze_run "$test_file"
|
|
|
|
|
count=$((count + 1))
|
|
|
|
|
printf "\r 进度: %d/%d" "$count" "$total"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# 动态表头:每个 run 一列 + 平均 + 匹配 + 退出码
|
|
|
|
|
echo ""
|
|
|
|
|
if [[ "$RUN_COUNT" -eq 1 ]]; then
|
|
|
|
|
printf "%-30s %10s %6s %8s\n" \
|
|
|
|
|
"测试用例" "耗时" "匹配" "退出码"
|
|
|
|
|
printf "%-30s %10s %6s %8s\n" \
|
|
|
|
|
"------------------------------" "----------" "------" "--------"
|
|
|
|
|
else
|
|
|
|
|
printf "%-30s" "测试用例"
|
|
|
|
|
for ((i=1; i<=RUN_COUNT; i++)); do printf " %10s" "Run$i"; done
|
|
|
|
|
printf " %10s %6s %8s\n" "平均" "匹配" "退出码"
|
|
|
|
|
printf "%-30s" "------------------------------"
|
|
|
|
|
for ((i=1; i<=RUN_COUNT; i++)); do printf " %10s" "----------"; done
|
|
|
|
|
printf " %10s %6s %8s\n" "----------" "------" "--------"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local pass=0 total_valid=0
|
|
|
|
|
local total_avg_sec=0
|
|
|
|
|
for f in "$RESULT_DIR/run"/*.result; do
|
|
|
|
|
[[ -f "$f" ]] || continue
|
|
|
|
|
local result
|
|
|
|
|
result=$(cat "$f")
|
|
|
|
|
if [[ "$result" == *"FAIL"* ]]; then
|
|
|
|
|
printf "%-30s %10s\n" "$(basename "$f" .result)" "$result"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# 解析: stem run1 run2 ... runN avg match rc
|
|
|
|
|
local stem match rc avg_time
|
|
|
|
|
local times=()
|
|
|
|
|
read -r stem rest <<< "$result"
|
|
|
|
|
# rest 包含所有 run 时间 + avg + match + rc
|
|
|
|
|
# 从后往前取: rc, match, avg, then remaining are run times
|
|
|
|
|
set -- $rest
|
|
|
|
|
local num_runs=$RUN_COUNT
|
|
|
|
|
# total fields = num_runs + 2 (avg + match + rc) → wait: avg is already one field
|
|
|
|
|
# Actually: fields = run1 run2 ... runN avg match rc
|
|
|
|
|
# So last field = rc, second to last = match, third to last = avg
|
|
|
|
|
# Everything before that = run times
|
|
|
|
|
local total_fields=$#
|
|
|
|
|
rc="${@: -1}"
|
|
|
|
|
match="${@: -2:1}"
|
|
|
|
|
avg_time="${@: -3:1}"
|
|
|
|
|
# run times are fields 1 to (total_fields - 3)
|
|
|
|
|
local run_fields=$((total_fields - 3))
|
|
|
|
|
for ((i=1; i<=run_fields; i++)); do
|
|
|
|
|
times+=("${!i}")
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# 输出行
|
|
|
|
|
if [[ "$RUN_COUNT" -eq 1 ]]; then
|
|
|
|
|
printf "%-30s %10s %6s %8s\n" "$stem" "${times[0]}" "$match" "$rc"
|
|
|
|
|
else
|
|
|
|
|
printf "%-30s" "$stem"
|
|
|
|
|
for t in "${times[@]}"; do printf " %10s" "$t"; done
|
|
|
|
|
printf " %10s %6s %8s\n" "$avg_time" "$match" "$rc"
|
|
|
|
|
fi
|
|
|
|
|
[[ "$match" == "Y" ]] && pass=$((pass + 1))
|
|
|
|
|
total_valid=$((total_valid + 1))
|
|
|
|
|
|
|
|
|
|
# 累加平均时间(转换为秒)
|
|
|
|
|
local sec
|
|
|
|
|
sec=$(echo "$avg_time" | sed 's/m/ /;s/s//' | awk '{printf "%.3f", $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
total_avg_sec=$(echo "scale=3; $total_avg_sec + $sec" | bc 2>/dev/null || echo "$total_avg_sec")
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "$total_valid" -gt 0 ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
printf "输出匹配率: %d/%d\n" "$pass" "$total_valid"
|
|
|
|
|
echo ""
|
|
|
|
|
if [[ "$RUN_COUNT" -eq 1 ]]; then
|
|
|
|
|
printf "%-30s %10.3f\n" "--- 总时间 (秒) ---" "$total_avg_sec"
|
|
|
|
|
else
|
|
|
|
|
printf "%-30s %10.3f\n" "--- 总平均时间 (秒) ---" "$total_avg_sec"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ========== 清理 ==========
|
|
|
|
|
if [[ "$KEEP_WORKTREE" == false ]]; then
|
|
|
|
|
if [[ -n "$SINGLE_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "清理 worktree..."
|
|
|
|
|
git worktree remove --force "$SINGLE_WORKTREE_DIR" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "统计结果保存在: $RESULT_DIR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main_compare() {
|
|
|
|
|
echo "============================================="
|
|
|
|
|
echo " 寄存器分配编译器对比"
|
|
|
|
|
echo " 旧版: $OLD_REF"
|
|
|
|
|
if [[ -n "$NEW_REF" ]]; then
|
|
|
|
|
echo " 新版: $NEW_REF"
|
|
|
|
|
else
|
|
|
|
|
echo " 新版: 本地构建 (./build/bin/compiler)"
|
|
|
|
|
fi
|
|
|
|
|
echo " 测试集: $TEST_SET"
|
|
|
|
|
echo " 模式: $MODE"
|
|
|
|
|
echo "============================================="
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
check_tools
|
|
|
|
|
setup_old_compiler
|
|
|
|
|
setup_new_compiler
|
|
|
|
|
show_compiler_versions
|
|
|
|
|
|
|
|
|
|
local tests
|
|
|
|
|
mapfile -t tests < <(get_tests)
|
|
|
|
|
local total=${#tests[@]}
|
|
|
|
|
echo "共 $total 个测试用例"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# ========== 汇编对比 ==========
|
|
|
|
|
if [[ "$MODE" == "asm" || "$MODE" == "all" ]]; then
|
|
|
|
|
echo "=== 汇编质量对比 ==="
|
|
|
|
|
local count=0
|
|
|
|
|
for test_file in "${tests[@]}"; do
|
|
|
|
|
compare_asm "$test_file"
|
|
|
|
|
count=$((count + 1))
|
|
|
|
|
printf "\r 进度: %d/%d" "$count" "$total"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# 输出汇编对比表
|
|
|
|
|
echo ""
|
|
|
|
|
printf "%-30s %8s %8s %8s %8s %8s %8s\n" \
|
|
|
|
|
"测试用例" "旧指令数" "新指令数" "变化%" "旧访存" "新访存" "变化%"
|
|
|
|
|
printf "%-30s %8s %8s %8s %8s %8s %8s\n" \
|
|
|
|
|
"------------------------------" "--------" "--------" "--------" "--------" "--------" "--------"
|
|
|
|
|
|
|
|
|
|
local total_old_inst=0 total_new_inst=0 total_old_mem=0 total_new_mem=0 valid_count=0
|
|
|
|
|
for f in "$RESULT_DIR/asm"/*.result; do
|
|
|
|
|
[[ -f "$f" ]] || continue
|
|
|
|
|
local result
|
|
|
|
|
result=$(cat "$f")
|
|
|
|
|
if [[ "$result" == *"FAIL"* ]]; then
|
|
|
|
|
printf "%-30s %8s\n" "$(basename "$f" .result)" "编译失败"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
read -r stem old_inst new_inst inst_pct old_mem new_mem mem_pct _ _ <<< "$result"
|
|
|
|
|
printf "%-30s %8d %8d %7s%% %8d %8d %7s%%\n" \
|
|
|
|
|
"$stem" "$old_inst" "$new_inst" "$inst_pct" "$old_mem" "$new_mem" "$mem_pct"
|
|
|
|
|
total_old_inst=$((total_old_inst + old_inst))
|
|
|
|
|
total_new_inst=$((total_new_inst + new_inst))
|
|
|
|
|
total_old_mem=$((total_old_mem + old_mem))
|
|
|
|
|
total_new_mem=$((total_new_mem + new_mem))
|
|
|
|
|
valid_count=$((valid_count + 1))
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "$valid_count" -gt 0 ]]; then
|
|
|
|
|
local avg_inst_pct avg_mem_pct
|
|
|
|
|
avg_inst_pct=$(echo "scale=1; ($total_new_inst - $total_old_inst) * 100 / $total_old_inst" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
avg_mem_pct=$(echo "scale=1; ($total_new_mem - $total_old_mem) * 100 / $total_old_mem" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
printf "%-30s %8d %8d %7s%% %8d %8d %7s%%\n" \
|
|
|
|
|
"--- 合计 ---" "$total_old_inst" "$total_new_inst" "$avg_inst_pct" \
|
|
|
|
|
"$total_old_mem" "$total_new_mem" "$avg_mem_pct"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
echo "详细 diff 文件: $RESULT_DIR/asm/*.diff"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ========== 运行对比 ==========
|
|
|
|
|
if [[ "$MODE" == "run" || "$MODE" == "all" ]]; then
|
|
|
|
|
echo "=== 运行结果对比 ==="
|
|
|
|
|
local count=0
|
|
|
|
|
for test_file in "${tests[@]}"; do
|
|
|
|
|
compare_run "$test_file"
|
|
|
|
|
count=$((count + 1))
|
|
|
|
|
printf "\r 进度: %d/%d" "$count" "$total"
|
|
|
|
|
done
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
printf "%-30s %10s %10s %8s %6s %6s %8s %8s\n" \
|
|
|
|
|
"测试用例" "旧耗时" "新耗时" "加速比" "旧匹配" "新匹配" "旧退出码" "新退出码"
|
|
|
|
|
printf "%-30s %10s %10s %8s %6s %6s %8s %8s\n" \
|
|
|
|
|
"------------------------------" "----------" "----------" "--------" "------" "------" "--------" "--------"
|
|
|
|
|
|
|
|
|
|
local pass_old=0 pass_new=0 total_valid=0
|
|
|
|
|
local total_old_sec=0 total_new_sec=0
|
|
|
|
|
for f in "$RESULT_DIR/run"/*.result; do
|
|
|
|
|
[[ -f "$f" ]] || continue
|
|
|
|
|
local result
|
|
|
|
|
result=$(cat "$f")
|
|
|
|
|
if [[ "$result" == *"FAIL"* ]]; then
|
|
|
|
|
printf "%-30s %10s\n" "$(basename "$f" .result)" "$result"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
read -r stem old_time new_time speedup old_match new_match old_status new_status <<< "$result"
|
|
|
|
|
printf "%-30s %10s %10s %8s %6s %6s %8s %8s\n" \
|
|
|
|
|
"$stem" "$old_time" "$new_time" "$speedup" "$old_match" "$new_match" "$old_status" "$new_status"
|
|
|
|
|
[[ "$old_match" == "Y" ]] && pass_old=$((pass_old + 1))
|
|
|
|
|
[[ "$new_match" == "Y" ]] && pass_new=$((pass_new + 1))
|
|
|
|
|
total_valid=$((total_valid + 1))
|
|
|
|
|
|
|
|
|
|
# 累加时间(转换为秒)
|
|
|
|
|
local old_sec new_sec
|
|
|
|
|
old_sec=$(echo "$old_time" | sed 's/m/ /;s/s//' | awk '{printf "%.3f", $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
new_sec=$(echo "$new_time" | sed 's/m/ /;s/s//' | awk '{printf "%.3f", $1 * 60 + $2}' 2>/dev/null || echo 0)
|
|
|
|
|
total_old_sec=$(echo "scale=3; $total_old_sec + $old_sec" | bc 2>/dev/null || echo "$total_old_sec")
|
|
|
|
|
total_new_sec=$(echo "scale=3; $total_new_sec + $new_sec" | bc 2>/dev/null || echo "$total_new_sec")
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "$total_valid" -gt 0 ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
printf "输出匹配率: 旧版 %d/%d, 新版 %d/%d\n" "$pass_old" "$total_valid" "$pass_new" "$total_valid"
|
|
|
|
|
|
|
|
|
|
# 总时间汇总行
|
|
|
|
|
local total_speedup="N/A"
|
|
|
|
|
if [[ "$(echo "$total_new_sec > 0" | bc -l 2>/dev/null)" == "1" ]]; then
|
|
|
|
|
total_speedup=$(echo "scale=2; $total_old_sec / $total_new_sec" | bc 2>/dev/null || echo "N/A")
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
printf "%-30s %10s %10s %8s\n" "--- 总时间 ---" "旧版(秒)" "新版(秒)" "总加速比"
|
|
|
|
|
printf "%-30s %10.3f %10.3f %8s\n" "全部测试" "$total_old_sec" "$total_new_sec" "$total_speedup"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ========== 清理 ==========
|
|
|
|
|
if [[ "$KEEP_WORKTREE" == false ]]; then
|
|
|
|
|
if [[ -n "$WORKTREE_DIR" ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "清理旧 worktree..."
|
|
|
|
|
git worktree remove --force "$WORKTREE_DIR" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
if [[ -n "$NEW_WORKTREE_DIR" ]]; then
|
|
|
|
|
echo "清理新 worktree..."
|
|
|
|
|
git worktree remove --force "$NEW_WORKTREE_DIR" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo "对比结果保存在: $RESULT_DIR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
if [[ -n "$SINGLE_REF" ]]; then
|
|
|
|
|
main_single
|
|
|
|
|
else
|
|
|
|
|
main_compare
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main
|