#!/usr/bin/env bash # test2_instcount.sh - Lab2(IR生成)指令数量统计脚本 set -u set -o pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' TEST_ROOT="./test_merged" OUTPUT_DIR="./lab2_instcount_results" COMPILER="./build/bin/compiler" VERIFY_SCRIPT="./scripts/verify_ir.sh" MAX_CASES=0 STOP_ON_FIRST_FAILURE=false START_FROM=1 KEEP_OLD=true RUN_TESTS=false # 指令数量统计相关变量 total_orig_inst=0 total_opt_inst=0 inst_cases_count=0 total_reduction_sum=0 reduction_count=0 total_increase_sum=0 increase_count=0 show_help() { cat << 'EOF' 用法: ./test2_instcount.sh [选项] 说明: 批量执行 Lab2(IR 生成)指令数量统计。 比较优化前后的IR指令数量变化。 结果保存在 lab2_incount_results 文件夹中。 选项: -m, --max N 最多执行 N 个用例(0 表示不限制,默认: 0) -x, --stop-on-fail 遇到第一个失败时停止 -s, --start-from N 从第 N 个测试用例开始(默认: 1) -k, --keep 保留旧的输出目录,不删除 -r, --run 运行测试用例验证正确性(默认不运行) -h, --help 显示帮助 示例: ./test2_instcount.sh # 仅统计指令数量 ./test2_instcount.sh -r # 统计指令数量并运行验证 ./test2_instcount.sh --max 10 # 只运行前10个 ./test2_instcount.sh --start-from 50 # 从第50个开始 EOF } while [[ $# -gt 0 ]]; do case "$1" in -m|--max) MAX_CASES="$2" shift 2 ;; -x|--stop-on-fail) STOP_ON_FIRST_FAILURE=true shift ;; -s|--start-from) START_FROM="$2" shift 2 ;; -k|--keep) KEEP_OLD=true shift ;; -r|--run) RUN_TESTS=true shift ;; -h|--help) show_help exit 0 ;; *) echo -e "${RED}错误: 未知选项 $1${NC}" show_help exit 1 ;; esac done if ! [[ "$START_FROM" =~ ^[0-9]+$ ]] || [[ "$START_FROM" -lt 1 ]]; then echo -e "${RED}错误: --start-from 需要正整数${NC}" exit 1 fi if [[ ! -d "$TEST_ROOT" ]]; then echo -e "${RED}错误: 测试目录不存在: $TEST_ROOT${NC}" exit 1 fi if [[ ! -x "$COMPILER" ]]; then echo -e "${RED}错误: 编译器不可执行: $COMPILER${NC}" echo -e "${YELLOW}提示: 请先构建: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j \"\$(nproc)\"${NC}" exit 1 fi if [[ "$RUN_TESTS" == "true" && ! -x "$VERIFY_SCRIPT" ]]; then echo -e "${RED}错误: verify脚本不可执行: $VERIFY_SCRIPT${NC}" exit 1 fi if ! [[ "$MAX_CASES" =~ ^[0-9]+$ ]]; then echo -e "${RED}错误: --max 需要非负整数${NC}" exit 1 fi mkdir -p "$OUTPUT_DIR" # 删除旧的输出目录(除非指定了 --keep) if [[ "$KEEP_OLD" != "true" ]]; then if [[ -d "$OUTPUT_DIR" ]]; then echo "正在删除旧的输出目录..." rm -rf "$OUTPUT_DIR"/* echo "已删除旧的输出目录" fi fi mkdir -p "$OUTPUT_DIR" LOG_FILE="$OUTPUT_DIR/lab2_instcount.log" FAIL_FILE="$OUTPUT_DIR/failed_cases.txt" ERROR_LOG_FILE="$OUTPUT_DIR/error_log.txt" INST_REPORT_FILE="$OUTPUT_DIR/instruction_report.txt" INST_SUMMARY_FILE="$OUTPUT_DIR/instruction_summary.txt" : > "$LOG_FILE" : > "$FAIL_FILE" : > "$ERROR_LOG_FILE" : > "$INST_SUMMARY_FILE" # 初始化指令报告文件 { echo "================================================" echo "Lab2 IR指令数量统计报告" echo "================================================" echo "测试模式: IR" echo "运行验证: ${RUN_TESTS}" echo "生成时间: $(date)" echo "================================================" echo "" printf "%-60s | %12s\n" "测试用例" "IR指令数" echo "------------------------------------------------" } > "$INST_REPORT_FILE" echo "Lab2 指令数量统计日志 - $(date)" >> "$LOG_FILE" echo "TEST_ROOT=$TEST_ROOT" >> "$LOG_FILE" echo "OUTPUT_DIR=$OUTPUT_DIR" >> "$LOG_FILE" echo "MAX_CASES=$MAX_CASES" >> "$LOG_FILE" echo "RUN_TESTS=$RUN_TESTS" >> "$LOG_FILE" echo "================================================" >> "$LOG_FILE" # 统计IR指令数量的函数 count_ir_instructions() { local ir_file="$1" if [[ ! -f "$ir_file" ]]; then echo "0" return fi local count=0 local term_count=0 local store_count=0 local call_count=0 # 统计赋值指令(包含变量定义) count=$(grep -cE '^\s*%?[a-zA-Z0-9_]+\s*=' "$ir_file" 2>/dev/null || echo "0") count=$(echo "$count" | tr -d '[:space:]') [[ ! "$count" =~ ^[0-9]+$ ]] && count=0 # 统计终止指令(ret, br, switch) term_count=$(grep -cE '^\s*(ret|br|switch)' "$ir_file" 2>/dev/null || echo "0") term_count=$(echo "$term_count" | tr -d '[:space:]') [[ ! "$term_count" =~ ^[0-9]+$ ]] && term_count=0 # 统计store指令 store_count=$(grep -cE '^\s*store ' "$ir_file" 2>/dev/null || echo "0") store_count=$(echo "$store_count" | tr -d '[:space:]') [[ ! "$store_count" =~ ^[0-9]+$ ]] && store_count=0 # 统计call/invoke指令 call_count=$(grep -cE '^\s*(call|invoke)' "$ir_file" 2>/dev/null || echo "0") call_count=$(echo "$call_count" | tr -d '[:space:]') [[ ! "$call_count" =~ ^[0-9]+$ ]] && call_count=0 echo $((count + term_count + store_count + call_count)) } # 运行单个测试用例 run_single_test() { local file="$1" local case_num="$2" local rel_path="${file#$TEST_ROOT/}" local filename="$(basename "$file")" local base_name="${filename%.sy}" local rel_dir="$(dirname "$rel_path")" local case_out_dir="$OUTPUT_DIR/$rel_dir" mkdir -p "$case_out_dir" # 删除已有的输出文件,确保重新生成 rm -f "$case_out_dir/${base_name}.ll" rm -f "$case_out_dir/${base_name}.o" rm -f "$case_out_dir/${base_name}" rm -f "$case_out_dir/${base_name}.stdout" rm -f "$case_out_dir/${base_name}.actual.out" rm -f "$case_out_dir/${base_name}_time.txt" echo -ne "${YELLOW}[$case_num] $filename ... ${NC}" local ir_file="$case_out_dir/${base_name}.ll" # 生成IR文件 set +e "$COMPILER" --emit-ir "$file" > "$ir_file" 2>/dev/null local ir_code=$? set -e if [[ $ir_code -ne 0 ]]; then echo -e "${RED}编译失败${NC}" echo "[FAILED] $file (compile error: ir=$ir_code)" >> "$LOG_FILE" FAILED=$((FAILED + 1)) echo "$file" >> "$FAIL_FILE" # 写入详细错误信息 echo "========================================" >> "$ERROR_LOG_FILE" echo "编译失败: $file" >> "$ERROR_LOG_FILE" echo "退出码: ir=$ir_code" >> "$ERROR_LOG_FILE" echo "时间: $(date)" >> "$ERROR_LOG_FILE" echo "========================================" >> "$ERROR_LOG_FILE" if [[ "$STOP_ON_FIRST_FAILURE" = true ]]; then echo -e "${RED}========================================${NC}" echo -e "${RED}在第一个失败处停止测试${NC}" echo -e "${RED}失败文件: $file${NC}" echo -e "${RED}日志: $LOG_FILE${NC}" echo -e "${RED}错误日志: $ERROR_LOG_FILE${NC}" echo -e "${RED}========================================${NC}" return 1 fi return 0 fi # 如果需要运行验证 if [[ "$RUN_TESTS" == "true" ]]; then "$VERIFY_SCRIPT" "$file" "$case_out_dir" --run > /dev/null 2>&1 local run_code=$? if [[ $run_code -ne 0 ]]; then echo -e "${RED}运行失败${NC}" echo "[FAILED] $file (runtime error, exit=$run_code)" >> "$LOG_FILE" FAILED=$((FAILED + 1)) echo "$file" >> "$FAIL_FILE" echo "========================================" >> "$ERROR_LOG_FILE" echo "运行失败: $file" >> "$ERROR_LOG_FILE" echo "退出码: $run_code" >> "$ERROR_LOG_FILE" echo "时间: $(date)" >> "$ERROR_LOG_FILE" echo "========================================" >> "$ERROR_LOG_FILE" if [[ "$STOP_ON_FIRST_FAILURE" = true ]]; then return 1 fi return 0 fi fi # 统计指令数量 local ir_count=0 ir_count=$(count_ir_instructions "$ir_file") SUCCESS=$((SUCCESS + 1)) # 累加统计 total_orig_inst=$((total_orig_inst + ir_count)) inst_cases_count=$((inst_cases_count + 1)) # 显示结果 echo -ne "${GREEN}成功${NC} " echo -ne "(指令数: ${ir_count})${NC}" # 写入报告 printf "%-60s | %12d\n" "$rel_path" "$ir_count" >> "$INST_REPORT_FILE" echo "$rel_path: inst=$ir_count" >> "$INST_SUMMARY_FILE" echo "" echo "[SUCCESS] $file (inst=$ir_count)" >> "$LOG_FILE" return 0 } # 主函数 main() { mapfile -d '' -t CASES < <(find "$TEST_ROOT" -type f -name '*.sy' -print0 | sort -z) TOTAL_FOUND=${#CASES[@]} if [[ $TOTAL_FOUND -eq 0 ]]; then echo -e "${YELLOW}未找到任何 .sy 用例,请检查目录: $TEST_ROOT${NC}" exit 0 fi TOTAL=0 SUCCESS=0 FAILED=0 echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Lab2 IR指令数量统计开始${NC}" echo -e "${BLUE}测试根目录: $TEST_ROOT${NC}" echo -e "${BLUE}找到用例数: $TOTAL_FOUND${NC}" echo -e "${BLUE}输出目录: $OUTPUT_DIR${NC}" echo -e "${BLUE}运行验证: $RUN_TESTS${NC}" if [[ "$KEEP_OLD" == "true" ]]; then echo -e "${BLUE}模式: 保留旧输出${NC}" else echo -e "${BLUE}模式: 全新输出${NC}" fi if [[ "$START_FROM" -gt 1 ]]; then echo -e "${BLUE}起始用例: $START_FROM${NC}" fi echo -e "${BLUE}========================================${NC}" for file in "${CASES[@]}"; do TOTAL=$((TOTAL + 1)) # 跳过起始用例之前的测试 if [[ $TOTAL -lt $START_FROM ]]; then continue fi # 检查是否已达到最大用例数限制 if [[ $MAX_CASES -gt 0 && $((TOTAL - START_FROM + 1)) -gt $MAX_CASES ]]; then break fi set +e run_single_test "$file" "$TOTAL" test_result=$? set -e if [[ $test_result -eq 1 ]] && [[ "$STOP_ON_FIRST_FAILURE" = true ]]; then break fi done # 生成指令报告摘要 { echo "" echo "================================================" echo "指令数量统计摘要" echo "================================================" echo "总指令数: $total_orig_inst" echo "测试用例数: $inst_cases_count" if [[ $inst_cases_count -gt 0 ]]; then local avg_inst=$((total_orig_inst / inst_cases_count)) echo "平均指令数: ${avg_inst}" fi echo "================================================" } >> "$INST_REPORT_FILE" # 计算成功率 RATE="0.00" if [[ $TOTAL -gt 0 ]]; then RATE=$(awk -v s="$SUCCESS" -v t="$TOTAL" 'BEGIN { printf "%.2f", (s*100.0)/t }') fi echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Lab2 指令数量统计完成${NC}" echo -e "${BLUE}执行用例: $TOTAL${NC}" echo -e "${GREEN}成功: $SUCCESS${NC}" echo -e "${RED}失败: $FAILED${NC}" echo -e "${BLUE}成功率: ${RATE}%${NC}" echo "" echo -e "${BLUE}指令数量统计:${NC}" echo " 总指令数: $total_orig_inst" echo " 测试用例数: $inst_cases_count" if [[ $inst_cases_count -gt 0 ]]; then local avg_inst=$((total_orig_inst / inst_cases_count)) echo -e " 平均指令数: ${avg_inst}" fi echo "" echo -e "${BLUE}详细报告: $INST_REPORT_FILE${NC}" echo -e "${BLUE}指令汇总: $INST_SUMMARY_FILE${NC}" echo -e "${BLUE}日志: $LOG_FILE${NC}" if [[ $FAILED -gt 0 ]]; then echo -e "${RED}失败清单: $FAIL_FILE${NC}" echo -e "${RED}错误日志: $ERROR_LOG_FILE${NC}" fi echo -e "${BLUE}========================================${NC}" exit 0 } main