Compare commits
15 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ccf7c97283 | 1 day ago |
|
|
9d222f3bb1 | 1 day ago |
|
|
85ed54dc6a | 1 week ago |
|
|
aeb336ac14 | 2 weeks ago |
|
|
2ba7597e41 | 2 weeks ago |
|
|
07f98e0d47 | 3 weeks ago |
|
|
f32ebef74c | 3 weeks ago |
|
|
3a49f8e131 | 3 weeks ago |
|
|
f34f36ed59 | 1 month ago |
|
|
4a4e0a2e0c | 1 month ago |
|
|
5756a2952c | 1 month ago |
|
|
dcaada11bc | 1 month ago |
|
|
f54091c010 | 1 month ago |
|
|
876ba72328 | 1 month ago |
|
|
c2759c27cd | 1 month ago |
Binary file not shown.
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
ANTLR_DIR="$BUILD_DIR/generated/antlr4"
|
||||
JAR_PATH="$ROOT_DIR/third_party/antlr-4.13.2-complete.jar"
|
||||
GRAMMAR_PATH="$ROOT_DIR/src/antlr4/SysY.g4"
|
||||
COMPILER="$BUILD_DIR/bin/compiler"
|
||||
SAVE_TREE=false
|
||||
TREE_DIR="$ROOT_DIR/test_tree"
|
||||
POSITIVE_CASES=(
|
||||
"$ROOT_DIR"/test/test_case/functional/*.sy
|
||||
"$ROOT_DIR"/test/test_case/performance/*.sy
|
||||
)
|
||||
NEGATIVE_CASES=(
|
||||
"$ROOT_DIR"/test/test_case/negative/*.sy
|
||||
)
|
||||
positive_total=0
|
||||
positive_passed=0
|
||||
positive_failed=0
|
||||
negative_total=0
|
||||
negative_passed=0
|
||||
negative_failed=0
|
||||
failed_cases=()
|
||||
|
||||
print_summary() {
|
||||
local total passed failed
|
||||
total=$((positive_total + negative_total))
|
||||
passed=$((positive_passed + negative_passed))
|
||||
failed=$((positive_failed + negative_failed))
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Positive cases: total=$positive_total, passed=$positive_passed, failed=$positive_failed"
|
||||
echo " Negative cases: total=$negative_total, passed=$negative_passed, failed=$negative_failed"
|
||||
echo " Overall: total=$total, passed=$passed, failed=$failed"
|
||||
|
||||
if (( ${#failed_cases[@]} > 0 )); then
|
||||
echo "Failed cases:"
|
||||
printf ' - %s\n' "${failed_cases[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--save-tree)
|
||||
SAVE_TREE=true
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
echo "Usage: $0 [--save-tree]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
echo "[1/4] Generating ANTLR sources..."
|
||||
mkdir -p "$ANTLR_DIR"
|
||||
java -jar "$JAR_PATH" \
|
||||
-Dlanguage=Cpp \
|
||||
-visitor -no-listener \
|
||||
-Xexact-output-dir \
|
||||
-o "$ANTLR_DIR" \
|
||||
"$GRAMMAR_PATH"
|
||||
|
||||
echo "[2/4] Configuring CMake..."
|
||||
cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=ON
|
||||
|
||||
echo "[3/4] Building project..."
|
||||
cmake --build "$BUILD_DIR" -j "$(nproc)"
|
||||
|
||||
echo "[4/4] Running parse-tree tests in parse-only mode..."
|
||||
|
||||
if [[ "$SAVE_TREE" == true ]]; then
|
||||
rm -rf "$TREE_DIR"
|
||||
mkdir -p "$TREE_DIR"
|
||||
fi
|
||||
|
||||
for case_file in "${POSITIVE_CASES[@]}"; do
|
||||
((positive_total += 1))
|
||||
if [[ "$SAVE_TREE" == true ]]; then
|
||||
rel_path="${case_file#"$ROOT_DIR"/test/test_case/}"
|
||||
rel_dir="$(dirname "$rel_path")"
|
||||
stem="$(basename "${case_file%.sy}")"
|
||||
out_dir="$TREE_DIR/$rel_dir"
|
||||
out_file="$out_dir/$stem.tree"
|
||||
mkdir -p "$out_dir"
|
||||
if ! "$COMPILER" --emit-parse-tree "$case_file" >"$out_file" 2>/tmp/lab1_parse.err; then
|
||||
echo "FAIL: $case_file"
|
||||
cat /tmp/lab1_parse.err
|
||||
rm -f "$out_file"
|
||||
((positive_failed += 1))
|
||||
failed_cases+=("$case_file")
|
||||
else
|
||||
echo "PASS: $case_file -> $out_file"
|
||||
((positive_passed += 1))
|
||||
fi
|
||||
else
|
||||
if ! "$COMPILER" --emit-parse-tree "$case_file" >/dev/null 2>/tmp/lab1_parse.err; then
|
||||
echo "FAIL: $case_file"
|
||||
cat /tmp/lab1_parse.err
|
||||
((positive_failed += 1))
|
||||
failed_cases+=("$case_file")
|
||||
else
|
||||
echo "PASS: $case_file"
|
||||
((positive_passed += 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ${#NEGATIVE_CASES[@]} > 0 )); then
|
||||
echo
|
||||
echo "Running negative parse tests..."
|
||||
for case_file in "${NEGATIVE_CASES[@]}"; do
|
||||
((negative_total += 1))
|
||||
if "$COMPILER" --emit-parse-tree "$case_file" >/tmp/lab1_negative.out 2>/tmp/lab1_negative.err; then
|
||||
echo "FAIL: $case_file (expected parse failure, but parsing succeeded)"
|
||||
((negative_failed += 1))
|
||||
failed_cases+=("$case_file")
|
||||
else
|
||||
if grep -q '^\[error\] \[parse\]' /tmp/lab1_negative.err; then
|
||||
echo "PASS: $case_file -> expected parse error"
|
||||
((negative_passed += 1))
|
||||
else
|
||||
echo "FAIL: $case_file (did not report parse error as expected)"
|
||||
cat /tmp/lab1_negative.err
|
||||
((negative_failed += 1))
|
||||
failed_cases+=("$case_file")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if (( positive_failed + negative_failed > 0 )); then
|
||||
echo "Batch test finished with failures."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Batch test passed."
|
||||
@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
ANTLR_DIR="$BUILD_DIR/generated/antlr4"
|
||||
JAR_PATH="$ROOT_DIR/third_party/antlr-4.13.2-complete.jar"
|
||||
GRAMMAR_PATH="$ROOT_DIR/src/antlr4/SysY.g4"
|
||||
OUT_ROOT="$ROOT_DIR/test/test_result/lab2_ir_batch"
|
||||
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=true
|
||||
DO_BUILD=true
|
||||
|
||||
functional_total=0
|
||||
functional_passed=0
|
||||
functional_failed=0
|
||||
performance_total=0
|
||||
performance_passed=0
|
||||
performance_failed=0
|
||||
failed_cases=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./solution/run_lab2_batch.sh [options]
|
||||
|
||||
Options:
|
||||
--no-build Skip ANTLR generation and project rebuild
|
||||
--functional-only Run only test/test_case/functional/*.sy
|
||||
--performance-only Run only test/test_case/performance/*.sy
|
||||
--output-dir <dir> Set output directory for generated IR and logs
|
||||
--help Show this help message
|
||||
EOF
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local total passed failed
|
||||
total=$((functional_total + performance_total))
|
||||
passed=$((functional_passed + performance_passed))
|
||||
failed=$((functional_failed + performance_failed))
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Functional cases: total=$functional_total, passed=$functional_passed, failed=$functional_failed"
|
||||
echo " Performance cases: total=$performance_total, passed=$performance_passed, failed=$performance_failed"
|
||||
echo " Overall: total=$total, passed=$passed, failed=$failed"
|
||||
|
||||
if (( ${#failed_cases[@]} > 0 )); then
|
||||
echo "Failed cases:"
|
||||
printf ' - %s\n' "${failed_cases[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local case_file=$1
|
||||
local group=$2
|
||||
local stem out_dir log_file
|
||||
|
||||
stem="$(basename "${case_file%.sy}")"
|
||||
out_dir="$OUT_ROOT/$group"
|
||||
log_file="$out_dir/$stem.verify.log"
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_total += 1))
|
||||
else
|
||||
((performance_total += 1))
|
||||
fi
|
||||
|
||||
if ./scripts/verify_ir.sh "$case_file" "$out_dir" --run >"$log_file" 2>&1; then
|
||||
echo "PASS: $case_file"
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_passed += 1))
|
||||
else
|
||||
((performance_passed += 1))
|
||||
fi
|
||||
else
|
||||
echo "FAIL: $case_file"
|
||||
cat "$log_file"
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_failed += 1))
|
||||
else
|
||||
((performance_failed += 1))
|
||||
fi
|
||||
failed_cases+=("$case_file")
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-build)
|
||||
DO_BUILD=false
|
||||
;;
|
||||
--functional-only)
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=false
|
||||
;;
|
||||
--performance-only)
|
||||
RUN_FUNCTIONAL=false
|
||||
RUN_PERFORMANCE=true
|
||||
;;
|
||||
--output-dir)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --output-dir" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$1" = /* ]]; then
|
||||
OUT_ROOT="$1"
|
||||
else
|
||||
OUT_ROOT="$ROOT_DIR/$1"
|
||||
fi
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "$RUN_FUNCTIONAL" == false && "$RUN_PERFORMANCE" == false ]]; then
|
||||
echo "No test set selected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DO_BUILD" == true ]]; then
|
||||
echo "[1/4] Generating ANTLR sources..."
|
||||
mkdir -p "$ANTLR_DIR"
|
||||
java -jar "$JAR_PATH" \
|
||||
-Dlanguage=Cpp \
|
||||
-visitor -no-listener \
|
||||
-Xexact-output-dir \
|
||||
-o "$ANTLR_DIR" \
|
||||
"$GRAMMAR_PATH"
|
||||
|
||||
echo "[2/4] Configuring CMake..."
|
||||
cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF
|
||||
|
||||
echo "[3/4] Building project..."
|
||||
cmake --build "$BUILD_DIR" -j "$(nproc)"
|
||||
fi
|
||||
|
||||
echo "[4/4] Running IR batch tests..."
|
||||
|
||||
if [[ "$RUN_FUNCTIONAL" == true ]]; then
|
||||
for case_file in "$ROOT_DIR"/test/test_case/functional/*.sy; do
|
||||
run_case "$case_file" "functional"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "$RUN_PERFORMANCE" == true ]]; then
|
||||
for case_file in "$ROOT_DIR"/test/test_case/performance/*.sy; do
|
||||
run_case "$case_file" "performance"
|
||||
done
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if (( functional_failed + performance_failed > 0 )); then
|
||||
echo "Batch test finished with failures."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Batch test passed."
|
||||
@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
ANTLR_DIR="$BUILD_DIR/generated/antlr4"
|
||||
JAR_PATH="$ROOT_DIR/third_party/antlr-4.13.2-complete.jar"
|
||||
GRAMMAR_PATH="$ROOT_DIR/src/antlr4/SysY.g4"
|
||||
OUT_ROOT="$ROOT_DIR/test/test_result/lab3_asm_batch"
|
||||
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=true
|
||||
RUN_EXEC=true
|
||||
DO_BUILD=true
|
||||
RUN_TIMEOUT=""
|
||||
|
||||
functional_total=0
|
||||
functional_passed=0
|
||||
functional_failed=0
|
||||
performance_total=0
|
||||
performance_passed=0
|
||||
performance_failed=0
|
||||
failed_cases=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./solution/run_lab3_batch.sh [options]
|
||||
|
||||
Options:
|
||||
--no-build Skip ANTLR generation and project rebuild
|
||||
--functional-only Run only test/test_case/functional/*.sy
|
||||
--performance-only Run only test/test_case/performance/*.sy
|
||||
--no-run Generate/link asm only, skip qemu run and output check
|
||||
--emit-only Generate/link asm only, skip qemu run and output check
|
||||
--timeout <sec> Apply per-case timeout via the `timeout` command
|
||||
--output-dir <dir> Set output directory for generated asm, executables, and logs
|
||||
--help Show this help message
|
||||
EOF
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local total passed failed
|
||||
total=$((functional_total + performance_total))
|
||||
passed=$((functional_passed + performance_passed))
|
||||
failed=$((functional_failed + performance_failed))
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Mode: $([[ "$RUN_EXEC" == true ]] && echo "verify_asm --run" || echo "verify_asm")"
|
||||
echo " Functional cases: total=$functional_total, passed=$functional_passed, failed=$functional_failed"
|
||||
echo " Performance cases: total=$performance_total, passed=$performance_passed, failed=$performance_failed"
|
||||
echo " Overall: total=$total, passed=$passed, failed=$failed"
|
||||
|
||||
if (( ${#failed_cases[@]} > 0 )); then
|
||||
echo "Failed cases:"
|
||||
printf ' - %s\n' "${failed_cases[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local case_file=$1
|
||||
local group=$2
|
||||
local stem out_dir log_file
|
||||
local -a cmd
|
||||
|
||||
stem="$(basename "${case_file%.sy}")"
|
||||
out_dir="$OUT_ROOT/$group"
|
||||
log_file="$out_dir/$stem.verify.log"
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_total += 1))
|
||||
else
|
||||
((performance_total += 1))
|
||||
fi
|
||||
|
||||
cmd=(./scripts/verify_asm.sh "$case_file" "$out_dir")
|
||||
if [[ "$RUN_EXEC" == true ]]; then
|
||||
cmd+=(--run)
|
||||
fi
|
||||
|
||||
if [[ -n "$RUN_TIMEOUT" ]]; then
|
||||
cmd=(timeout "$RUN_TIMEOUT" "${cmd[@]}")
|
||||
fi
|
||||
|
||||
if "${cmd[@]}" >"$log_file" 2>&1; then
|
||||
echo "PASS: $case_file"
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_passed += 1))
|
||||
else
|
||||
((performance_passed += 1))
|
||||
fi
|
||||
else
|
||||
echo "FAIL: $case_file"
|
||||
cat "$log_file"
|
||||
if [[ "$group" == "functional" ]]; then
|
||||
((functional_failed += 1))
|
||||
else
|
||||
((performance_failed += 1))
|
||||
fi
|
||||
failed_cases+=("$case_file")
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-build)
|
||||
DO_BUILD=false
|
||||
;;
|
||||
--functional-only)
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=false
|
||||
;;
|
||||
--performance-only)
|
||||
RUN_FUNCTIONAL=false
|
||||
RUN_PERFORMANCE=true
|
||||
;;
|
||||
--emit-only)
|
||||
RUN_EXEC=false
|
||||
;;
|
||||
--no-run)
|
||||
RUN_EXEC=false
|
||||
;;
|
||||
--timeout)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --timeout" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
RUN_TIMEOUT="$1"
|
||||
;;
|
||||
--output-dir)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --output-dir" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$1" = /* ]]; then
|
||||
OUT_ROOT="$1"
|
||||
else
|
||||
OUT_ROOT="$ROOT_DIR/$1"
|
||||
fi
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "$RUN_FUNCTIONAL" == false && "$RUN_PERFORMANCE" == false ]]; then
|
||||
echo "No test set selected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$RUN_TIMEOUT" ]] && ! command -v timeout >/dev/null 2>&1; then
|
||||
echo "未找到 timeout 命令,无法使用 --timeout。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DO_BUILD" == true ]]; then
|
||||
echo "[1/4] Generating ANTLR sources..."
|
||||
mkdir -p "$ANTLR_DIR"
|
||||
java -jar "$JAR_PATH" \
|
||||
-Dlanguage=Cpp \
|
||||
-visitor -no-listener \
|
||||
-Xexact-output-dir \
|
||||
-o "$ANTLR_DIR" \
|
||||
"$GRAMMAR_PATH"
|
||||
|
||||
echo "[2/4] Configuring CMake..."
|
||||
cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF
|
||||
|
||||
echo "[3/4] Building project..."
|
||||
cmake --build "$BUILD_DIR" -j "$(nproc)"
|
||||
fi
|
||||
|
||||
echo "[4/4] Running ASM batch tests..."
|
||||
|
||||
if [[ "$RUN_FUNCTIONAL" == true ]]; then
|
||||
while IFS= read -r case_file; do
|
||||
run_case "$case_file" "functional"
|
||||
done < <(find "$ROOT_DIR/test/test_case/functional" -maxdepth 1 -name '*.sy' | sort)
|
||||
fi
|
||||
|
||||
if [[ "$RUN_PERFORMANCE" == true ]]; then
|
||||
while IFS= read -r case_file; do
|
||||
run_case "$case_file" "performance"
|
||||
done < <(find "$ROOT_DIR/test/test_case/performance" -maxdepth 1 -name '*.sy' | sort)
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if (( functional_failed + performance_failed > 0 )); then
|
||||
echo "Batch test finished with failures."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Batch test passed."
|
||||
@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
ANTLR_DIR="$BUILD_DIR/generated/antlr4"
|
||||
JAR_PATH="$ROOT_DIR/third_party/antlr-4.13.2-complete.jar"
|
||||
GRAMMAR_PATH="$ROOT_DIR/src/antlr4/SysY.g4"
|
||||
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=true
|
||||
RUN_IR=true
|
||||
RUN_ASM=true
|
||||
RUN_EXEC=true
|
||||
DO_BUILD=true
|
||||
RUN_TIMEOUT=""
|
||||
OPT_LEVEL=0
|
||||
OUT_ROOT=""
|
||||
TIME_LOG_FILE=""
|
||||
failed_cases=()
|
||||
|
||||
declare -A counters=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./solution/run_lab4_batch.sh [options]
|
||||
|
||||
Options:
|
||||
--no-build Skip ANTLR generation and project rebuild
|
||||
--functional-only Run only test/test_case/functional/*.sy
|
||||
--performance-only Run only test/test_case/performance/*.sy
|
||||
--ir-only Run only verify_ir batch
|
||||
--asm-only Run only verify_asm batch
|
||||
--no-run Generate IR/asm only; skip execution and output check
|
||||
--emit-only Alias of --no-run
|
||||
--timeout <sec> Apply per-case timeout via the `timeout` command
|
||||
-O0 Run batch with compiler flag -O0 (default)
|
||||
-O1 Run batch with compiler flag -O1
|
||||
--opt-level <0|1> Same as -O0 / -O1
|
||||
--output-dir <dir> Set output root; default is test/test_result/lab4_batch_o<level>
|
||||
--help Show this help message
|
||||
EOF
|
||||
}
|
||||
|
||||
set_opt_level() {
|
||||
case "$1" in
|
||||
0|O0|-O0)
|
||||
OPT_LEVEL=0
|
||||
;;
|
||||
1|O1|-O1)
|
||||
OPT_LEVEL=1
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported opt level: $1" >&2
|
||||
echo "Only -O0 / -O1 are supported." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
bump_counter() {
|
||||
local mode=$1
|
||||
local group=$2
|
||||
local kind=$3
|
||||
local key="$mode:$group:$kind"
|
||||
counters["$key"]=$(( ${counters["$key"]:-0} + 1 ))
|
||||
}
|
||||
|
||||
get_counter() {
|
||||
local mode=$1
|
||||
local group=$2
|
||||
local kind=$3
|
||||
echo "${counters["$mode:$group:$kind"]:-0}"
|
||||
}
|
||||
|
||||
print_mode_summary() {
|
||||
local mode=$1
|
||||
local functional_total functional_passed functional_failed
|
||||
local performance_total performance_passed performance_failed
|
||||
local total passed failed
|
||||
|
||||
functional_total=$(get_counter "$mode" "functional" "total")
|
||||
functional_passed=$(get_counter "$mode" "functional" "passed")
|
||||
functional_failed=$(get_counter "$mode" "functional" "failed")
|
||||
performance_total=$(get_counter "$mode" "performance" "total")
|
||||
performance_passed=$(get_counter "$mode" "performance" "passed")
|
||||
performance_failed=$(get_counter "$mode" "performance" "failed")
|
||||
|
||||
total=$((functional_total + performance_total))
|
||||
passed=$((functional_passed + performance_passed))
|
||||
failed=$((functional_failed + performance_failed))
|
||||
|
||||
echo " ${mode^^} functional: total=$functional_total, passed=$functional_passed, failed=$functional_failed"
|
||||
echo " ${mode^^} performance: total=$performance_total, passed=$performance_passed, failed=$performance_failed"
|
||||
echo " ${mode^^} overall: total=$total, passed=$passed, failed=$failed"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
local overall_total=0
|
||||
local overall_passed=0
|
||||
local overall_failed=0
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Opt level: -O$OPT_LEVEL"
|
||||
echo " Execution: $([[ "$RUN_EXEC" == true ]] && echo "enabled" || echo "disabled")"
|
||||
|
||||
if [[ "$RUN_IR" == true ]]; then
|
||||
print_mode_summary "ir"
|
||||
overall_total=$((overall_total + $(get_counter "ir" "functional" "total") + $(get_counter "ir" "performance" "total")))
|
||||
overall_passed=$((overall_passed + $(get_counter "ir" "functional" "passed") + $(get_counter "ir" "performance" "passed")))
|
||||
overall_failed=$((overall_failed + $(get_counter "ir" "functional" "failed") + $(get_counter "ir" "performance" "failed")))
|
||||
fi
|
||||
|
||||
if [[ "$RUN_ASM" == true ]]; then
|
||||
print_mode_summary "asm"
|
||||
overall_total=$((overall_total + $(get_counter "asm" "functional" "total") + $(get_counter "asm" "performance" "total")))
|
||||
overall_passed=$((overall_passed + $(get_counter "asm" "functional" "passed") + $(get_counter "asm" "performance" "passed")))
|
||||
overall_failed=$((overall_failed + $(get_counter "asm" "functional" "failed") + $(get_counter "asm" "performance" "failed")))
|
||||
fi
|
||||
|
||||
echo " Overall: total=$overall_total, passed=$overall_passed, failed=$overall_failed"
|
||||
|
||||
if (( ${#failed_cases[@]} > 0 )); then
|
||||
echo "Failed cases:"
|
||||
printf ' - %s\n' "${failed_cases[@]}"
|
||||
fi
|
||||
echo " Per-case timing log: $TIME_LOG_FILE"
|
||||
}
|
||||
|
||||
format_elapsed_seconds() {
|
||||
local elapsed_ns=$1
|
||||
awk -v ns="$elapsed_ns" 'BEGIN { printf "%.3f", ns / 1000000000 }'
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local mode=$1
|
||||
local group=$2
|
||||
local case_file=$3
|
||||
local stem out_dir log_file
|
||||
local start_ns end_ns elapsed_ns elapsed_s
|
||||
local -a cmd
|
||||
|
||||
stem="$(basename "${case_file%.sy}")"
|
||||
out_dir="$OUT_ROOT/$mode/$group"
|
||||
log_file="$out_dir/$stem.verify.log"
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
bump_counter "$mode" "$group" "total"
|
||||
|
||||
if [[ "$mode" == "ir" ]]; then
|
||||
cmd=("$ROOT_DIR/scripts/verify_ir.sh" "$case_file" "$out_dir")
|
||||
else
|
||||
cmd=("$ROOT_DIR/scripts/verify_asm.sh" "$case_file" "$out_dir")
|
||||
fi
|
||||
|
||||
if [[ "$RUN_EXEC" == true ]]; then
|
||||
cmd+=(--run)
|
||||
fi
|
||||
cmd+=(-- "-O$OPT_LEVEL")
|
||||
|
||||
if [[ -n "$RUN_TIMEOUT" ]]; then
|
||||
cmd=(timeout "$RUN_TIMEOUT" "${cmd[@]}")
|
||||
fi
|
||||
|
||||
start_ns=$(date +%s%N)
|
||||
if "${cmd[@]}" >"$log_file" 2>&1; then
|
||||
end_ns=$(date +%s%N)
|
||||
elapsed_ns=$((end_ns - start_ns))
|
||||
elapsed_s=$(format_elapsed_seconds "$elapsed_ns")
|
||||
echo "PASS [$mode] $case_file (${elapsed_s}s)"
|
||||
bump_counter "$mode" "$group" "passed"
|
||||
printf '%s,%s,%s,%s,%s,%s,%s\n' \
|
||||
"$mode" "$group" "$case_file" "PASS" "$elapsed_ns" "$elapsed_s" "$log_file" >> "$TIME_LOG_FILE"
|
||||
else
|
||||
end_ns=$(date +%s%N)
|
||||
elapsed_ns=$((end_ns - start_ns))
|
||||
elapsed_s=$(format_elapsed_seconds "$elapsed_ns")
|
||||
echo "FAIL [$mode] $case_file (${elapsed_s}s)"
|
||||
cat "$log_file"
|
||||
bump_counter "$mode" "$group" "failed"
|
||||
failed_cases+=("[$mode] $case_file")
|
||||
printf '%s,%s,%s,%s,%s,%s,%s\n' \
|
||||
"$mode" "$group" "$case_file" "FAIL" "$elapsed_ns" "$elapsed_s" "$log_file" >> "$TIME_LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
run_group() {
|
||||
local mode=$1
|
||||
local group=$2
|
||||
local case_dir=$3
|
||||
|
||||
while IFS= read -r case_file; do
|
||||
run_case "$mode" "$group" "$case_file"
|
||||
done < <(find "$case_dir" -maxdepth 1 -type f -name '*.sy' | sort)
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-build)
|
||||
DO_BUILD=false
|
||||
;;
|
||||
--functional-only)
|
||||
RUN_FUNCTIONAL=true
|
||||
RUN_PERFORMANCE=false
|
||||
;;
|
||||
--performance-only)
|
||||
RUN_FUNCTIONAL=false
|
||||
RUN_PERFORMANCE=true
|
||||
;;
|
||||
--ir-only)
|
||||
RUN_IR=true
|
||||
RUN_ASM=false
|
||||
;;
|
||||
--asm-only)
|
||||
RUN_IR=false
|
||||
RUN_ASM=true
|
||||
;;
|
||||
--no-run|--emit-only)
|
||||
RUN_EXEC=false
|
||||
;;
|
||||
--timeout)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --timeout" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
RUN_TIMEOUT="$1"
|
||||
;;
|
||||
-O0|-O1)
|
||||
set_opt_level "$1"
|
||||
;;
|
||||
--opt-level)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --opt-level" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
set_opt_level "$1"
|
||||
;;
|
||||
--output-dir)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Missing value for --output-dir" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$1" = /* ]]; then
|
||||
OUT_ROOT="$1"
|
||||
else
|
||||
OUT_ROOT="$ROOT_DIR/$1"
|
||||
fi
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -z "$OUT_ROOT" ]]; then
|
||||
OUT_ROOT="$ROOT_DIR/test/test_result/lab4_batch_o$OPT_LEVEL"
|
||||
fi
|
||||
mkdir -p "$OUT_ROOT"
|
||||
TIME_LOG_FILE="$OUT_ROOT/case_timing.csv"
|
||||
echo "mode,group,case,status,elapsed_ns,elapsed_s,log_file" > "$TIME_LOG_FILE"
|
||||
|
||||
if [[ "$RUN_FUNCTIONAL" == false && "$RUN_PERFORMANCE" == false ]]; then
|
||||
echo "No test set selected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$RUN_IR" == false && "$RUN_ASM" == false ]]; then
|
||||
echo "No verification pipeline selected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$RUN_TIMEOUT" ]] && ! command -v timeout >/dev/null 2>&1; then
|
||||
echo "未找到 timeout 命令,无法使用 --timeout。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if [[ "$DO_BUILD" == true ]]; then
|
||||
echo "[1/4] Generating ANTLR sources..."
|
||||
mkdir -p "$ANTLR_DIR"
|
||||
java -jar "$JAR_PATH" \
|
||||
-Dlanguage=Cpp \
|
||||
-visitor -no-listener \
|
||||
-Xexact-output-dir \
|
||||
-o "$ANTLR_DIR" \
|
||||
"$GRAMMAR_PATH"
|
||||
|
||||
echo "[2/4] Configuring CMake..."
|
||||
cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release -DCOMPILER_PARSE_ONLY=OFF
|
||||
|
||||
echo "[3/4] Building project..."
|
||||
cmake --build "$BUILD_DIR" -j "$(nproc)"
|
||||
fi
|
||||
|
||||
echo "[4/4] Running Lab4 batch tests..."
|
||||
|
||||
if [[ "$RUN_IR" == true ]]; then
|
||||
if [[ "$RUN_FUNCTIONAL" == true ]]; then
|
||||
run_group "ir" "functional" "$ROOT_DIR/test/test_case/functional"
|
||||
fi
|
||||
if [[ "$RUN_PERFORMANCE" == true ]]; then
|
||||
run_group "ir" "performance" "$ROOT_DIR/test/test_case/performance"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$RUN_ASM" == true ]]; then
|
||||
if [[ "$RUN_FUNCTIONAL" == true ]]; then
|
||||
run_group "asm" "functional" "$ROOT_DIR/test/test_case/functional"
|
||||
fi
|
||||
if [[ "$RUN_PERFORMANCE" == true ]]; then
|
||||
run_group "asm" "performance" "$ROOT_DIR/test/test_case/performance"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_summary
|
||||
|
||||
if (( ${#failed_cases[@]} > 0 )); then
|
||||
echo "Batch test finished with failures."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Batch test passed."
|
||||
@ -1,11 +1,19 @@
|
||||
// GlobalValue 占位实现:
|
||||
// - 具体的全局初始化器、打印和链接语义需要自行补全
|
||||
|
||||
#include "ir/IR.h"
|
||||
|
||||
namespace ir {
|
||||
|
||||
GlobalValue::GlobalValue(std::shared_ptr<Type> ty, std::string name)
|
||||
: User(std::move(ty), std::move(name)) {}
|
||||
: Value(std::move(ty), std::move(name)) {}
|
||||
|
||||
GlobalVariable::GlobalVariable(std::string name, std::shared_ptr<Type> value_type,
|
||||
ConstantValue* initializer, bool is_constant)
|
||||
: GlobalValue(Type::GetPointerType(value_type), std::move(name)),
|
||||
value_type_(std::move(value_type)),
|
||||
initializer_(initializer),
|
||||
is_constant_(is_constant) {}
|
||||
|
||||
Argument::Argument(std::shared_ptr<Type> ty, std::string name, size_t index,
|
||||
Function* parent)
|
||||
: Value(std::move(ty), std::move(name)), index_(index), parent_(parent) {}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -1,89 +1,178 @@
|
||||
// IR 构建工具:
|
||||
// - 管理插入点(当前基本块/位置)
|
||||
// - 提供创建各类指令的便捷接口,降低 IRGen 复杂度
|
||||
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "utils/Log.h"
|
||||
|
||||
namespace ir {
|
||||
IRBuilder::IRBuilder(Context& ctx, BasicBlock* bb)
|
||||
: ctx_(ctx), insert_block_(bb) {}
|
||||
namespace {
|
||||
|
||||
void RequireInsertBlock(BasicBlock* bb) {
|
||||
if (!bb) {
|
||||
throw std::runtime_error("IRBuilder 未设置插入点");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> InferLoadType(Value* ptr) {
|
||||
if (!ptr || !ptr->GetType() || !ptr->GetType()->IsPointer()) {
|
||||
throw std::runtime_error("CreateLoad 需要指针");
|
||||
}
|
||||
return ptr->GetType()->GetElementType();
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> InferGEPResultType(Value* base_ptr,
|
||||
const std::vector<Value*>& indices) {
|
||||
if (!base_ptr || !base_ptr->GetType() || !base_ptr->GetType()->IsPointer()) {
|
||||
throw std::runtime_error("CreateGEP 需要指针基址");
|
||||
}
|
||||
auto current = base_ptr->GetType()->GetElementType();
|
||||
for (size_t i = 0; i < indices.size(); ++i) {
|
||||
auto* index = indices[i];
|
||||
(void)index;
|
||||
if (!current) {
|
||||
throw std::runtime_error("CreateGEP 遇到空类型");
|
||||
}
|
||||
if (i == 0) {
|
||||
continue;
|
||||
}
|
||||
if (current->IsArray()) {
|
||||
current = current->GetElementType();
|
||||
continue;
|
||||
}
|
||||
if (current->IsPointer()) {
|
||||
current = current->GetElementType();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Type::GetPointerType(current);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IRBuilder::IRBuilder(Context& ctx, BasicBlock* bb) : ctx_(ctx), insert_block_(bb) {}
|
||||
|
||||
void IRBuilder::SetInsertPoint(BasicBlock* bb) { insert_block_ = bb; }
|
||||
|
||||
BasicBlock* IRBuilder::GetInsertBlock() const { return insert_block_; }
|
||||
|
||||
ConstantInt* IRBuilder::CreateConstInt(int v) {
|
||||
// 常量不需要挂在基本块里,由 Context 负责去重与生命周期。
|
||||
return ctx_.GetConstInt(v);
|
||||
ConstantInt* IRBuilder::CreateConstInt(int v) { return ctx_.GetConstInt(v); }
|
||||
|
||||
ConstantFloat* IRBuilder::CreateConstFloat(float v) { return ctx_.GetConstFloat(v); }
|
||||
|
||||
ConstantValue* IRBuilder::CreateZero(std::shared_ptr<Type> type) {
|
||||
if (!type) {
|
||||
throw std::runtime_error("CreateZero 缺少类型");
|
||||
}
|
||||
if (type->IsInt1() || type->IsInt32()) {
|
||||
return CreateConstInt(0);
|
||||
}
|
||||
if (type->IsFloat32()) {
|
||||
return CreateConstFloat(0.0f);
|
||||
}
|
||||
return ctx_.CreateOwnedConstant<ConstantZero>(type);
|
||||
}
|
||||
|
||||
BinaryInst* IRBuilder::CreateBinary(Opcode op, Value* lhs, Value* rhs,
|
||||
const std::string& name) {
|
||||
if (!insert_block_) {
|
||||
throw std::runtime_error(FormatError("ir", "IRBuilder 未设置插入点"));
|
||||
}
|
||||
if (!lhs) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateBinary 缺少 lhs"));
|
||||
}
|
||||
if (!rhs) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateBinary 缺少 rhs"));
|
||||
RequireInsertBlock(insert_block_);
|
||||
if (!lhs || !rhs) {
|
||||
throw std::runtime_error("CreateBinary 缺少操作数");
|
||||
}
|
||||
return insert_block_->Append<BinaryInst>(op, lhs->GetType(), lhs, rhs, name);
|
||||
}
|
||||
|
||||
BinaryInst* IRBuilder::CreateAdd(Value* lhs, Value* rhs,
|
||||
AllocaInst* IRBuilder::CreateAlloca(std::shared_ptr<Type> allocated_type,
|
||||
const std::string& name) {
|
||||
return CreateBinary(Opcode::Add, lhs, rhs, name);
|
||||
RequireInsertBlock(insert_block_);
|
||||
auto* parent = insert_block_->GetParent();
|
||||
if (!parent || !parent->GetEntry()) {
|
||||
throw std::runtime_error("CreateAlloca 需要所在函数入口块");
|
||||
}
|
||||
return parent->GetEntry()->Append<AllocaInst>(std::move(allocated_type), name);
|
||||
}
|
||||
|
||||
AllocaInst* IRBuilder::CreateAllocaI32(const std::string& name) {
|
||||
if (!insert_block_) {
|
||||
throw std::runtime_error(FormatError("ir", "IRBuilder 未设置插入点"));
|
||||
}
|
||||
return insert_block_->Append<AllocaInst>(Type::GetPtrInt32Type(), name);
|
||||
return CreateAlloca(Type::GetInt32Type(), name);
|
||||
}
|
||||
|
||||
LoadInst* IRBuilder::CreateLoad(Value* ptr, const std::string& name) {
|
||||
if (!insert_block_) {
|
||||
throw std::runtime_error(FormatError("ir", "IRBuilder 未设置插入点"));
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<LoadInst>(ptr, InferLoadType(ptr), name);
|
||||
}
|
||||
|
||||
StoreInst* IRBuilder::CreateStore(Value* val, Value* ptr) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<StoreInst>(val, ptr);
|
||||
}
|
||||
if (!ptr) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateLoad 缺少 ptr"));
|
||||
|
||||
CompareInst* IRBuilder::CreateICmp(ICmpPred pred, Value* lhs, Value* rhs,
|
||||
const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CompareInst>(pred, lhs, rhs, name);
|
||||
}
|
||||
return insert_block_->Append<LoadInst>(Type::GetInt32Type(), ptr, name);
|
||||
|
||||
CompareInst* IRBuilder::CreateFCmp(FCmpPred pred, Value* lhs, Value* rhs,
|
||||
const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CompareInst>(pred, lhs, rhs, name);
|
||||
}
|
||||
|
||||
StoreInst* IRBuilder::CreateStore(Value* val, Value* ptr) {
|
||||
if (!insert_block_) {
|
||||
throw std::runtime_error(FormatError("ir", "IRBuilder 未设置插入点"));
|
||||
BranchInst* IRBuilder::CreateBr(BasicBlock* target) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<BranchInst>(target);
|
||||
}
|
||||
|
||||
CondBranchInst* IRBuilder::CreateCondBr(Value* cond, BasicBlock* true_block,
|
||||
BasicBlock* false_block) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CondBranchInst>(cond, true_block, false_block);
|
||||
}
|
||||
|
||||
CallInst* IRBuilder::CreateCall(Function* callee, const std::vector<Value*>& args,
|
||||
const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
std::string actual_name = name;
|
||||
if (callee && callee->GetReturnType()->IsVoid()) {
|
||||
actual_name.clear();
|
||||
}
|
||||
return insert_block_->Append<CallInst>(callee, args, actual_name);
|
||||
}
|
||||
if (!val) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateStore 缺少 val"));
|
||||
|
||||
GetElementPtrInst* IRBuilder::CreateGEP(Value* base_ptr,
|
||||
const std::vector<Value*>& indices,
|
||||
const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<GetElementPtrInst>(
|
||||
base_ptr, indices, InferGEPResultType(base_ptr, indices), name);
|
||||
}
|
||||
if (!ptr) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateStore 缺少 ptr"));
|
||||
|
||||
CastInst* IRBuilder::CreateSIToFP(Value* value, const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CastInst>(Opcode::SIToFP, value,
|
||||
Type::GetFloatType(), name);
|
||||
}
|
||||
return insert_block_->Append<StoreInst>(Type::GetVoidType(), val, ptr);
|
||||
|
||||
CastInst* IRBuilder::CreateFPToSI(Value* value, const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CastInst>(Opcode::FPToSI, value,
|
||||
Type::GetInt32Type(), name);
|
||||
}
|
||||
|
||||
ReturnInst* IRBuilder::CreateRet(Value* v) {
|
||||
if (!insert_block_) {
|
||||
throw std::runtime_error(FormatError("ir", "IRBuilder 未设置插入点"));
|
||||
CastInst* IRBuilder::CreateZExt(Value* value, std::shared_ptr<Type> dst_type,
|
||||
const std::string& name) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<CastInst>(Opcode::ZExt, value, std::move(dst_type),
|
||||
name);
|
||||
}
|
||||
if (!v) {
|
||||
throw std::runtime_error(
|
||||
FormatError("ir", "IRBuilder::CreateRet 缺少返回值"));
|
||||
|
||||
ReturnInst* IRBuilder::CreateRet(Value* value) {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return value ? insert_block_->Append<ReturnInst>(value)
|
||||
: insert_block_->Append<ReturnInst>();
|
||||
}
|
||||
return insert_block_->Append<ReturnInst>(Type::GetVoidType(), v);
|
||||
|
||||
ReturnInst* IRBuilder::CreateRetVoid() {
|
||||
RequireInsertBlock(insert_block_);
|
||||
return insert_block_->Append<ReturnInst>();
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -1,31 +1,141 @@
|
||||
// 当前仅支持 void、i32 和 i32*。
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ir {
|
||||
|
||||
Type::Type(Kind k) : kind_(k) {}
|
||||
Type::Type(Kind kind) : kind_(kind) {}
|
||||
|
||||
Type::Type(Kind kind, std::shared_ptr<Type> element_type)
|
||||
: kind_(kind), element_type_(std::move(element_type)) {}
|
||||
|
||||
Type::Type(Kind kind, std::shared_ptr<Type> element_type, size_t array_size)
|
||||
: kind_(kind),
|
||||
element_type_(std::move(element_type)),
|
||||
array_size_(array_size) {}
|
||||
|
||||
Type::Type(std::shared_ptr<Type> return_type,
|
||||
std::vector<std::shared_ptr<Type>> params)
|
||||
: kind_(Kind::Function),
|
||||
return_type_(std::move(return_type)),
|
||||
param_types_(std::move(params)) {}
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetVoidType() {
|
||||
static const std::shared_ptr<Type> type = std::make_shared<Type>(Kind::Void);
|
||||
static const auto type = std::make_shared<Type>(Kind::Void);
|
||||
return type;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetInt1Type() {
|
||||
static const auto type = std::make_shared<Type>(Kind::Int1);
|
||||
return type;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetInt32Type() {
|
||||
static const std::shared_ptr<Type> type = std::make_shared<Type>(Kind::Int32);
|
||||
static const auto type = std::make_shared<Type>(Kind::Int32);
|
||||
return type;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetFloatType() {
|
||||
static const auto type = std::make_shared<Type>(Kind::Float32);
|
||||
return type;
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> Type::GetPointerType(std::shared_ptr<Type> element_type) {
|
||||
if (!element_type) {
|
||||
throw std::runtime_error("GetPointerType 缺少 element_type");
|
||||
}
|
||||
return std::make_shared<Type>(Kind::Pointer, std::move(element_type));
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> Type::GetArrayType(std::shared_ptr<Type> element_type,
|
||||
size_t array_size) {
|
||||
if (!element_type) {
|
||||
throw std::runtime_error("GetArrayType 缺少 element_type");
|
||||
}
|
||||
return std::make_shared<Type>(Kind::Array, std::move(element_type), array_size);
|
||||
}
|
||||
|
||||
std::shared_ptr<Type> Type::GetFunctionType(
|
||||
std::shared_ptr<Type> return_type,
|
||||
std::vector<std::shared_ptr<Type>> param_types) {
|
||||
if (!return_type) {
|
||||
throw std::runtime_error("GetFunctionType 缺少 return_type");
|
||||
}
|
||||
return std::make_shared<Type>(std::move(return_type), std::move(param_types));
|
||||
}
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetPtrInt32Type() {
|
||||
static const std::shared_ptr<Type> type = std::make_shared<Type>(Kind::PtrInt32);
|
||||
static const auto type = GetPointerType(GetInt32Type());
|
||||
return type;
|
||||
}
|
||||
|
||||
Type::Kind Type::GetKind() const { return kind_; }
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetElementType() const { return element_type_; }
|
||||
|
||||
size_t Type::GetArraySize() const { return array_size_; }
|
||||
|
||||
const std::shared_ptr<Type>& Type::GetReturnType() const { return return_type_; }
|
||||
|
||||
const std::vector<std::shared_ptr<Type>>& Type::GetParamTypes() const {
|
||||
return param_types_;
|
||||
}
|
||||
|
||||
bool Type::IsVoid() const { return kind_ == Kind::Void; }
|
||||
|
||||
bool Type::IsInt1() const { return kind_ == Kind::Int1; }
|
||||
|
||||
bool Type::IsInt32() const { return kind_ == Kind::Int32; }
|
||||
|
||||
bool Type::IsPtrInt32() const { return kind_ == Kind::PtrInt32; }
|
||||
bool Type::IsFloat32() const { return kind_ == Kind::Float32; }
|
||||
|
||||
bool Type::IsPointer() const { return kind_ == Kind::Pointer; }
|
||||
|
||||
bool Type::IsArray() const { return kind_ == Kind::Array; }
|
||||
|
||||
bool Type::IsFunction() const { return kind_ == Kind::Function; }
|
||||
|
||||
bool Type::IsScalar() const { return IsInt1() || IsInt32() || IsFloat32(); }
|
||||
|
||||
bool Type::IsInteger() const { return IsInt1() || IsInt32(); }
|
||||
|
||||
bool Type::IsNumeric() const { return IsInteger() || IsFloat32(); }
|
||||
|
||||
bool Type::IsPtrInt32() const {
|
||||
return IsPointer() && element_type_ && element_type_->IsInt32();
|
||||
}
|
||||
|
||||
bool Type::Equals(const Type& other) const {
|
||||
if (kind_ != other.kind_) {
|
||||
return false;
|
||||
}
|
||||
switch (kind_) {
|
||||
case Kind::Void:
|
||||
case Kind::Int1:
|
||||
case Kind::Int32:
|
||||
case Kind::Float32:
|
||||
return true;
|
||||
case Kind::Pointer:
|
||||
return element_type_ && other.element_type_ &&
|
||||
element_type_->Equals(*other.element_type_);
|
||||
case Kind::Array:
|
||||
return array_size_ == other.array_size_ && element_type_ &&
|
||||
other.element_type_ && element_type_->Equals(*other.element_type_);
|
||||
case Kind::Function:
|
||||
if (!return_type_ || !other.return_type_ ||
|
||||
!return_type_->Equals(*other.return_type_) ||
|
||||
param_types_.size() != other.param_types_.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < param_types_.size(); ++i) {
|
||||
if (!param_types_[i] || !other.param_types_[i] ||
|
||||
!param_types_[i]->Equals(*other.param_types_[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -1,4 +1,222 @@
|
||||
// 支配树分析:
|
||||
// - 构建/查询 Dominator Tree 及相关关系
|
||||
// - 为 mem2reg、CFG 优化与循环分析提供基础能力
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
const std::vector<BasicBlock*>& EmptyBlockList() {
|
||||
static const std::vector<BasicBlock*> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
void AddUnique(std::vector<BasicBlock*>& blocks, BasicBlock* block) {
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
if (std::find(blocks.begin(), blocks.end(), block) == blocks.end()) {
|
||||
blocks.push_back(block);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RebuildCFG(Function& function) {
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (block) {
|
||||
block->ClearCFG();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
auto* terminator = block->GetTerminator();
|
||||
if (!terminator) {
|
||||
continue;
|
||||
}
|
||||
switch (terminator->GetOpcode()) {
|
||||
case Opcode::Br:
|
||||
block->AddSuccessor(static_cast<BranchInst*>(terminator)->GetTarget());
|
||||
break;
|
||||
case Opcode::CondBr: {
|
||||
auto* br = static_cast<CondBranchInst*>(terminator);
|
||||
block->AddSuccessor(br->GetTrueBlock());
|
||||
block->AddSuccessor(br->GetFalseBlock());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DominatorTree::DominatorTree(Function& function) { Recalculate(function); }
|
||||
|
||||
void DominatorTree::Recalculate(Function& function) {
|
||||
function_ = &function;
|
||||
reachable_.clear();
|
||||
reachable_blocks_.clear();
|
||||
dominators_.clear();
|
||||
idom_.clear();
|
||||
children_.clear();
|
||||
|
||||
RebuildCFG(function);
|
||||
auto* entry = function.GetEntry();
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::queue<BasicBlock*> worklist;
|
||||
worklist.push(entry);
|
||||
reachable_.insert(entry);
|
||||
while (!worklist.empty()) {
|
||||
auto* block = worklist.front();
|
||||
worklist.pop();
|
||||
reachable_blocks_.push_back(block);
|
||||
for (auto* succ : block->GetSuccessors()) {
|
||||
if (succ && reachable_.insert(succ).second) {
|
||||
worklist.push(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* block : reachable_blocks_) {
|
||||
if (block == entry) {
|
||||
dominators_[block] = {block};
|
||||
} else {
|
||||
dominators_[block].insert(reachable_blocks_.begin(), reachable_blocks_.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
do {
|
||||
changed = false;
|
||||
for (auto* block : reachable_blocks_) {
|
||||
if (block == entry) {
|
||||
continue;
|
||||
}
|
||||
std::unordered_set<BasicBlock*> new_dom;
|
||||
bool first_pred = true;
|
||||
for (auto* pred : block->GetPredecessors()) {
|
||||
if (!pred || !IsReachable(pred)) {
|
||||
continue;
|
||||
}
|
||||
if (first_pred) {
|
||||
new_dom = dominators_.at(pred);
|
||||
first_pred = false;
|
||||
continue;
|
||||
}
|
||||
std::unordered_set<BasicBlock*> intersection;
|
||||
for (auto* candidate : new_dom) {
|
||||
if (dominators_.at(pred).find(candidate) != dominators_.at(pred).end()) {
|
||||
intersection.insert(candidate);
|
||||
}
|
||||
}
|
||||
new_dom = std::move(intersection);
|
||||
}
|
||||
new_dom.insert(block);
|
||||
if (new_dom != dominators_.at(block)) {
|
||||
dominators_[block] = std::move(new_dom);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
idom_[entry] = nullptr;
|
||||
for (auto* block : reachable_blocks_) {
|
||||
if (block == entry) {
|
||||
continue;
|
||||
}
|
||||
BasicBlock* best = nullptr;
|
||||
for (auto* candidate : dominators_.at(block)) {
|
||||
if (candidate == block) {
|
||||
continue;
|
||||
}
|
||||
bool dominated_by_all_others = true;
|
||||
for (auto* other : dominators_.at(block)) {
|
||||
if (other == block || other == candidate) {
|
||||
continue;
|
||||
}
|
||||
if (dominators_.at(candidate).find(other) == dominators_.at(candidate).end()) {
|
||||
dominated_by_all_others = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dominated_by_all_others) {
|
||||
best = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
idom_[block] = best;
|
||||
if (best) {
|
||||
children_[best].push_back(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DominatorTree::IsReachable(BasicBlock* block) const {
|
||||
return block && reachable_.find(block) != reachable_.end();
|
||||
}
|
||||
|
||||
bool DominatorTree::Dominates(BasicBlock* lhs, BasicBlock* rhs) const {
|
||||
if (!lhs || !rhs) {
|
||||
return false;
|
||||
}
|
||||
auto it = dominators_.find(rhs);
|
||||
if (it == dominators_.end()) {
|
||||
return false;
|
||||
}
|
||||
return it->second.find(lhs) != it->second.end();
|
||||
}
|
||||
|
||||
BasicBlock* DominatorTree::GetIDom(BasicBlock* block) const {
|
||||
auto it = idom_.find(block);
|
||||
return it == idom_.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
const std::vector<BasicBlock*>& DominatorTree::GetChildren(BasicBlock* block) const {
|
||||
auto it = children_.find(block);
|
||||
return it == children_.end() ? EmptyBlockList() : it->second;
|
||||
}
|
||||
|
||||
const std::vector<BasicBlock*>& DominatorTree::GetReachableBlocks() const {
|
||||
return reachable_blocks_;
|
||||
}
|
||||
|
||||
DominanceFrontier::DominanceFrontier(const DominatorTree& dom_tree) {
|
||||
Recalculate(dom_tree);
|
||||
}
|
||||
|
||||
void DominanceFrontier::Recalculate(const DominatorTree& dom_tree) {
|
||||
frontiers_.clear();
|
||||
for (auto* block : dom_tree.GetReachableBlocks()) {
|
||||
frontiers_[block] = {};
|
||||
}
|
||||
|
||||
for (auto* block : dom_tree.GetReachableBlocks()) {
|
||||
if (!block || block->GetPredecessors().size() < 2) {
|
||||
continue;
|
||||
}
|
||||
auto* idom = dom_tree.GetIDom(block);
|
||||
for (auto* pred : block->GetPredecessors()) {
|
||||
if (!dom_tree.IsReachable(pred)) {
|
||||
continue;
|
||||
}
|
||||
auto* runner = pred;
|
||||
while (runner && runner != idom) {
|
||||
AddUnique(frontiers_[runner], block);
|
||||
runner = dom_tree.GetIDom(runner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<BasicBlock*>& DominanceFrontier::Get(BasicBlock* block) const {
|
||||
auto it = frontiers_.find(block);
|
||||
return it == frontiers_.end() ? EmptyBlockList() : it->second;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -1,4 +1,318 @@
|
||||
// CFG 简化:
|
||||
// - 删除不可达块、合并空块、简化分支等
|
||||
// - 改善 IR 结构,便于后续优化与后端生成
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
bool IsConstBool(Value* value, bool& result) {
|
||||
if (auto* ci = dynamic_cast<ConstantInt*>(value)) {
|
||||
result = ci->GetValue() != 0;
|
||||
return true;
|
||||
}
|
||||
if (auto* zero = dynamic_cast<ConstantZero*>(value)) {
|
||||
result = false;
|
||||
return zero->GetType() && zero->GetType()->IsInt1();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReplaceTerminatorWithBr(BasicBlock& block, BasicBlock* target) {
|
||||
auto& instructions = block.GetInstructions();
|
||||
if (!instructions.empty()) {
|
||||
instructions.back()->DropAllOperands();
|
||||
instructions.pop_back();
|
||||
}
|
||||
auto br = std::make_unique<BranchInst>(target);
|
||||
br->SetParent(&block);
|
||||
instructions.push_back(std::move(br));
|
||||
}
|
||||
|
||||
bool RedirectTerminatorEdge(Instruction* terminator, BasicBlock* from, BasicBlock* to) {
|
||||
if (!terminator || !from || !to) {
|
||||
return false;
|
||||
}
|
||||
if (auto* br = dynamic_cast<BranchInst*>(terminator)) {
|
||||
if (br->GetTarget() == from) {
|
||||
br->SetOperand(0, to);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (auto* cond = dynamic_cast<CondBranchInst*>(terminator)) {
|
||||
bool changed = false;
|
||||
if (cond->GetTrueBlock() == from) {
|
||||
cond->SetOperand(1, to);
|
||||
changed = true;
|
||||
}
|
||||
if (cond->GetFalseBlock() == from) {
|
||||
cond->SetOperand(2, to);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SuccessorStartsWithPhi(BasicBlock& block) {
|
||||
return !block.GetInstructions().empty() &&
|
||||
block.GetInstructions().front()->GetOpcode() == Opcode::Phi;
|
||||
}
|
||||
|
||||
void RewritePhiIncomingBlock(BasicBlock& target, BasicBlock* old_block,
|
||||
BasicBlock* new_block) {
|
||||
for (const auto& inst_ptr : target.GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
auto* phi = static_cast<PhiInst*>(inst_ptr.get());
|
||||
for (size_t i = 0; i < phi->GetNumIncoming(); ++i) {
|
||||
if (phi->GetIncomingBlock(i) == old_block) {
|
||||
phi->SetIncomingBlock(i, new_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExpandPhiIncomingBlock(BasicBlock& target, BasicBlock* old_block,
|
||||
const std::vector<BasicBlock*>& new_blocks) {
|
||||
for (const auto& inst_ptr : target.GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
auto* phi = static_cast<PhiInst*>(inst_ptr.get());
|
||||
std::vector<Value*> values_to_duplicate;
|
||||
for (size_t i = phi->GetNumIncoming(); i > 0; --i) {
|
||||
if (phi->GetIncomingBlock(i - 1) != old_block) {
|
||||
continue;
|
||||
}
|
||||
values_to_duplicate.push_back(phi->GetIncomingValue(i - 1));
|
||||
phi->RemoveIncomingAt(i - 1);
|
||||
}
|
||||
for (auto* value : values_to_duplicate) {
|
||||
for (auto* block : new_blocks) {
|
||||
phi->AddIncoming(value, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DropBlockInstructions(BasicBlock& block) {
|
||||
for (auto& inst_ptr : block.GetInstructions()) {
|
||||
if (inst_ptr) {
|
||||
inst_ptr->DropAllOperands();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SimplifySingleIncomingPhis(Function& function) {
|
||||
bool changed = false;
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
std::vector<Instruction*> to_erase;
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
auto* phi = static_cast<PhiInst*>(inst_ptr.get());
|
||||
if (phi->GetNumIncoming() != 1) {
|
||||
continue;
|
||||
}
|
||||
phi->ReplaceAllUsesWith(phi->GetIncomingValue(0));
|
||||
to_erase.push_back(phi);
|
||||
changed = true;
|
||||
}
|
||||
for (auto* inst : to_erase) {
|
||||
block->EraseInstruction(inst);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool SimplifyBranches(Function& function) {
|
||||
bool changed = false;
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
auto* cond = dynamic_cast<CondBranchInst*>(block->GetTerminator());
|
||||
if (!cond) {
|
||||
continue;
|
||||
}
|
||||
if (cond->GetTrueBlock() == cond->GetFalseBlock()) {
|
||||
ReplaceTerminatorWithBr(*block, cond->GetTrueBlock());
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
bool cond_value = false;
|
||||
if (IsConstBool(cond->GetCond(), cond_value)) {
|
||||
ReplaceTerminatorWithBr(*block,
|
||||
cond_value ? cond->GetTrueBlock() : cond->GetFalseBlock());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool RemoveUnreachableBlocks(Function& function) {
|
||||
auto* entry = function.GetEntry();
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<BasicBlock*> reachable;
|
||||
std::queue<BasicBlock*> worklist;
|
||||
reachable.insert(entry);
|
||||
worklist.push(entry);
|
||||
while (!worklist.empty()) {
|
||||
auto* block = worklist.front();
|
||||
worklist.pop();
|
||||
for (auto* succ : block->GetSuccessors()) {
|
||||
if (succ && reachable.insert(succ).second) {
|
||||
worklist.push(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BasicBlock*> to_remove;
|
||||
std::unordered_set<BasicBlock*> dead_set;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (block && reachable.find(block.get()) == reachable.end()) {
|
||||
to_remove.push_back(block.get());
|
||||
dead_set.insert(block.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* dead_block : to_remove) {
|
||||
for (auto* succ : dead_block->GetSuccessors()) {
|
||||
if (!succ || dead_set.find(succ) != dead_set.end()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : succ->GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
static_cast<PhiInst*>(inst_ptr.get())->RemoveIncomingBlock(dead_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* dead_block : to_remove) {
|
||||
DropBlockInstructions(*dead_block);
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (auto* dead_block : to_remove) {
|
||||
function.EraseBlock(dead_block);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool BypassEmptyBlocks(Function& function) {
|
||||
std::vector<BasicBlock*> snapshot;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (block) {
|
||||
snapshot.push_back(block.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* block : snapshot) {
|
||||
if (!block || block == function.GetEntry()) {
|
||||
continue;
|
||||
}
|
||||
if (block->GetInstructions().size() != 1) {
|
||||
continue;
|
||||
}
|
||||
auto* br = dynamic_cast<BranchInst*>(block->GetTerminator());
|
||||
if (!br || block->GetPredecessors().empty() || br->GetTarget() == block) {
|
||||
continue;
|
||||
}
|
||||
auto* target = br->GetTarget();
|
||||
ExpandPhiIncomingBlock(*target, block, block->GetPredecessors());
|
||||
for (auto* pred : block->GetPredecessors()) {
|
||||
RedirectTerminatorEdge(pred->GetTerminator(), block, target);
|
||||
}
|
||||
DropBlockInstructions(*block);
|
||||
function.EraseBlock(block);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MergeLinearBlocks(Function& function) {
|
||||
std::vector<BasicBlock*> snapshot;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (block) {
|
||||
snapshot.push_back(block.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* block : snapshot) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
auto* br = dynamic_cast<BranchInst*>(block->GetTerminator());
|
||||
if (!br) {
|
||||
continue;
|
||||
}
|
||||
auto* succ = br->GetTarget();
|
||||
if (!succ || succ == block || succ == function.GetEntry() ||
|
||||
succ->GetPredecessors().size() != 1 ||
|
||||
succ->GetPredecessors().front() != block || SuccessorStartsWithPhi(*succ)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto* next : succ->GetSuccessors()) {
|
||||
if (next) {
|
||||
RewritePhiIncomingBlock(*next, succ, block);
|
||||
}
|
||||
}
|
||||
|
||||
auto& block_insts = block->GetInstructions();
|
||||
block_insts.back()->DropAllOperands();
|
||||
block_insts.pop_back();
|
||||
|
||||
auto& succ_insts = succ->GetInstructions();
|
||||
for (auto& inst_ptr : succ_insts) {
|
||||
if (!inst_ptr) {
|
||||
continue;
|
||||
}
|
||||
inst_ptr->SetParent(block);
|
||||
block_insts.push_back(std::move(inst_ptr));
|
||||
}
|
||||
succ_insts.clear();
|
||||
function.EraseBlock(succ);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunCFGSimplifyPass(Function& function) {
|
||||
bool changed = false;
|
||||
bool local_changed = false;
|
||||
do {
|
||||
local_changed = false;
|
||||
RebuildCFG(function);
|
||||
local_changed |= SimplifySingleIncomingPhis(function);
|
||||
RebuildCFG(function);
|
||||
local_changed |= SimplifyBranches(function);
|
||||
RebuildCFG(function);
|
||||
local_changed |= RemoveUnreachableBlocks(function);
|
||||
RebuildCFG(function);
|
||||
local_changed |= BypassEmptyBlocks(function);
|
||||
RebuildCFG(function);
|
||||
local_changed |= MergeLinearBlocks(function);
|
||||
changed |= local_changed;
|
||||
} while (local_changed);
|
||||
RebuildCFG(function);
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -1,4 +1,236 @@
|
||||
// IR 常量折叠:
|
||||
// - 折叠可判定的常量表达式
|
||||
// - 简化常量控制流分支(按实现范围裁剪)
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
ConstantInt* CreateInt1Const(Module& module, bool value) {
|
||||
return module.GetContext().CreateOwnedConstant<ConstantInt>(Type::GetInt1Type(),
|
||||
value ? 1 : 0);
|
||||
}
|
||||
|
||||
ConstantValue* FoldBinary(Module& module, const BinaryInst& inst) {
|
||||
auto* lhs_i = dynamic_cast<ConstantInt*>(inst.GetLhs());
|
||||
auto* rhs_i = dynamic_cast<ConstantInt*>(inst.GetRhs());
|
||||
auto* lhs_f = dynamic_cast<ConstantFloat*>(inst.GetLhs());
|
||||
auto* rhs_f = dynamic_cast<ConstantFloat*>(inst.GetRhs());
|
||||
auto& ctx = module.GetContext();
|
||||
|
||||
switch (inst.GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
if (lhs_i && rhs_i) {
|
||||
return ctx.GetConstInt(lhs_i->GetValue() + rhs_i->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::Sub:
|
||||
if (lhs_i && rhs_i) {
|
||||
return ctx.GetConstInt(lhs_i->GetValue() - rhs_i->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::Mul:
|
||||
if (lhs_i && rhs_i) {
|
||||
return ctx.GetConstInt(lhs_i->GetValue() * rhs_i->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::SDiv:
|
||||
if (lhs_i && rhs_i && rhs_i->GetValue() != 0) {
|
||||
return ctx.GetConstInt(lhs_i->GetValue() / rhs_i->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::SRem:
|
||||
if (lhs_i && rhs_i && rhs_i->GetValue() != 0) {
|
||||
return ctx.GetConstInt(lhs_i->GetValue() % rhs_i->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::FAdd:
|
||||
if (lhs_f && rhs_f) {
|
||||
return ctx.GetConstFloat(lhs_f->GetValue() + rhs_f->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::FSub:
|
||||
if (lhs_f && rhs_f) {
|
||||
return ctx.GetConstFloat(lhs_f->GetValue() - rhs_f->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::FMul:
|
||||
if (lhs_f && rhs_f) {
|
||||
return ctx.GetConstFloat(lhs_f->GetValue() * rhs_f->GetValue());
|
||||
}
|
||||
break;
|
||||
case Opcode::FDiv:
|
||||
if (lhs_f && rhs_f && rhs_f->GetValue() != 0.0f) {
|
||||
return ctx.GetConstFloat(lhs_f->GetValue() / rhs_f->GetValue());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ConstantValue* FoldCompare(Module& module, const CompareInst& inst) {
|
||||
if (inst.IsFloatCompare()) {
|
||||
auto* lhs = dynamic_cast<ConstantFloat*>(inst.GetLhs());
|
||||
auto* rhs = dynamic_cast<ConstantFloat*>(inst.GetRhs());
|
||||
if (!lhs || !rhs) {
|
||||
return nullptr;
|
||||
}
|
||||
bool result = false;
|
||||
switch (inst.GetFCmpPred()) {
|
||||
case FCmpPred::Oeq:
|
||||
result = lhs->GetValue() == rhs->GetValue();
|
||||
break;
|
||||
case FCmpPred::One:
|
||||
result = lhs->GetValue() != rhs->GetValue();
|
||||
break;
|
||||
case FCmpPred::Olt:
|
||||
result = lhs->GetValue() < rhs->GetValue();
|
||||
break;
|
||||
case FCmpPred::Ole:
|
||||
result = lhs->GetValue() <= rhs->GetValue();
|
||||
break;
|
||||
case FCmpPred::Ogt:
|
||||
result = lhs->GetValue() > rhs->GetValue();
|
||||
break;
|
||||
case FCmpPred::Oge:
|
||||
result = lhs->GetValue() >= rhs->GetValue();
|
||||
break;
|
||||
}
|
||||
return CreateInt1Const(module, result);
|
||||
}
|
||||
|
||||
auto* lhs = dynamic_cast<ConstantInt*>(inst.GetLhs());
|
||||
auto* rhs = dynamic_cast<ConstantInt*>(inst.GetRhs());
|
||||
if (!lhs || !rhs) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
switch (inst.GetICmpPred()) {
|
||||
case ICmpPred::Eq:
|
||||
result = lhs->GetValue() == rhs->GetValue();
|
||||
break;
|
||||
case ICmpPred::Ne:
|
||||
result = lhs->GetValue() != rhs->GetValue();
|
||||
break;
|
||||
case ICmpPred::Slt:
|
||||
result = lhs->GetValue() < rhs->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sle:
|
||||
result = lhs->GetValue() <= rhs->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sgt:
|
||||
result = lhs->GetValue() > rhs->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sge:
|
||||
result = lhs->GetValue() >= rhs->GetValue();
|
||||
break;
|
||||
}
|
||||
return CreateInt1Const(module, result);
|
||||
}
|
||||
|
||||
ConstantValue* FoldCast(Module& module, const CastInst& inst) {
|
||||
auto& ctx = module.GetContext();
|
||||
switch (inst.GetOpcode()) {
|
||||
case Opcode::ZExt: {
|
||||
auto* value = dynamic_cast<ConstantInt*>(inst.GetValue());
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
return ctx.GetConstInt(value->GetValue() != 0 ? 1 : 0);
|
||||
}
|
||||
case Opcode::SIToFP: {
|
||||
auto* value = dynamic_cast<ConstantInt*>(inst.GetValue());
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
return ctx.GetConstFloat(static_cast<float>(value->GetValue()));
|
||||
}
|
||||
case Opcode::FPToSI: {
|
||||
auto* value = dynamic_cast<ConstantFloat*>(inst.GetValue());
|
||||
if (!value) {
|
||||
return nullptr;
|
||||
}
|
||||
return ctx.GetConstInt(static_cast<int>(value->GetValue()));
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool SameConstantValue(const ConstantValue* lhs, const ConstantValue* rhs) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (!lhs || !rhs || !lhs->GetType() || !rhs->GetType() ||
|
||||
!lhs->GetType()->Equals(*rhs->GetType())) {
|
||||
return false;
|
||||
}
|
||||
if (auto* lhs_i = dynamic_cast<const ConstantInt*>(lhs)) {
|
||||
auto* rhs_i = dynamic_cast<const ConstantInt*>(rhs);
|
||||
return rhs_i && lhs_i->GetValue() == rhs_i->GetValue();
|
||||
}
|
||||
if (auto* lhs_f = dynamic_cast<const ConstantFloat*>(lhs)) {
|
||||
auto* rhs_f = dynamic_cast<const ConstantFloat*>(rhs);
|
||||
return rhs_f && lhs_f->GetValue() == rhs_f->GetValue();
|
||||
}
|
||||
return dynamic_cast<const ConstantZero*>(lhs) && dynamic_cast<const ConstantZero*>(rhs);
|
||||
}
|
||||
|
||||
ConstantValue* FoldPhi(const PhiInst& inst) {
|
||||
if (inst.GetNumIncoming() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* first = dynamic_cast<ConstantValue*>(inst.GetIncomingValue(0));
|
||||
if (!first) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 1; i < inst.GetNumIncoming(); ++i) {
|
||||
auto* incoming = dynamic_cast<ConstantValue*>(inst.GetIncomingValue(i));
|
||||
if (!SameConstantValue(first, incoming)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunConstFoldPass(Module& module, Function& function) {
|
||||
bool changed = false;
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
std::vector<Instruction*> to_erase;
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr) {
|
||||
continue;
|
||||
}
|
||||
ConstantValue* folded = nullptr;
|
||||
if (auto* phi = dynamic_cast<PhiInst*>(inst_ptr.get())) {
|
||||
folded = FoldPhi(*phi);
|
||||
} else if (auto* bin = dynamic_cast<BinaryInst*>(inst_ptr.get())) {
|
||||
folded = FoldBinary(module, *bin);
|
||||
} else if (auto* cmp = dynamic_cast<CompareInst*>(inst_ptr.get())) {
|
||||
folded = FoldCompare(module, *cmp);
|
||||
} else if (auto* cast = dynamic_cast<CastInst*>(inst_ptr.get())) {
|
||||
folded = FoldCast(module, *cast);
|
||||
}
|
||||
if (!folded) {
|
||||
continue;
|
||||
}
|
||||
inst_ptr->ReplaceAllUsesWith(folded);
|
||||
to_erase.push_back(inst_ptr.get());
|
||||
changed = true;
|
||||
}
|
||||
for (auto* inst : to_erase) {
|
||||
block->EraseInstruction(inst);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -0,0 +1,160 @@
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
bool IsGVNCandidate(const Instruction& inst) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Sub:
|
||||
case Opcode::Mul:
|
||||
case Opcode::SDiv:
|
||||
case Opcode::SRem:
|
||||
case Opcode::FAdd:
|
||||
case Opcode::FSub:
|
||||
case Opcode::FMul:
|
||||
case Opcode::FDiv:
|
||||
case Opcode::ICmp:
|
||||
case Opcode::FCmp:
|
||||
case Opcode::SIToFP:
|
||||
case Opcode::FPToSI:
|
||||
case Opcode::ZExt:
|
||||
case Opcode::GEP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCommutative(const Instruction& inst) {
|
||||
if (auto* bin = dynamic_cast<const BinaryInst*>(&inst)) {
|
||||
switch (bin->GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Mul:
|
||||
case Opcode::FAdd:
|
||||
case Opcode::FMul:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (auto* cmp = dynamic_cast<const CompareInst*>(&inst)) {
|
||||
if (cmp->IsFloatCompare()) {
|
||||
return cmp->GetFCmpPred() == FCmpPred::Oeq || cmp->GetFCmpPred() == FCmpPred::One;
|
||||
}
|
||||
return cmp->GetICmpPred() == ICmpPred::Eq || cmp->GetICmpPred() == ICmpPred::Ne;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int GetValueNumber(std::unordered_map<Value*, int>& value_numbers, int& next_number,
|
||||
Value* value) {
|
||||
auto it = value_numbers.find(value);
|
||||
if (it != value_numbers.end()) {
|
||||
return it->second;
|
||||
}
|
||||
int number = next_number++;
|
||||
value_numbers.emplace(value, number);
|
||||
return number;
|
||||
}
|
||||
|
||||
std::string BuildExprKey(std::unordered_map<Value*, int>& value_numbers, int& next_number,
|
||||
Instruction& inst) {
|
||||
std::ostringstream oss;
|
||||
oss << static_cast<int>(inst.GetOpcode()) << "|" << inst.GetType().get();
|
||||
if (auto* bin = dynamic_cast<BinaryInst*>(&inst)) {
|
||||
int lhs = GetValueNumber(value_numbers, next_number, bin->GetLhs());
|
||||
int rhs = GetValueNumber(value_numbers, next_number, bin->GetRhs());
|
||||
if (IsCommutative(inst) && lhs > rhs) {
|
||||
std::swap(lhs, rhs);
|
||||
}
|
||||
oss << "|" << lhs << "|" << rhs;
|
||||
return oss.str();
|
||||
}
|
||||
if (auto* cmp = dynamic_cast<CompareInst*>(&inst)) {
|
||||
int lhs = GetValueNumber(value_numbers, next_number, cmp->GetLhs());
|
||||
int rhs = GetValueNumber(value_numbers, next_number, cmp->GetRhs());
|
||||
if (IsCommutative(inst) && lhs > rhs) {
|
||||
std::swap(lhs, rhs);
|
||||
}
|
||||
oss << "|" << (cmp->IsFloatCompare() ? "f" : "i") << "|"
|
||||
<< (cmp->IsFloatCompare() ? static_cast<int>(cmp->GetFCmpPred())
|
||||
: static_cast<int>(cmp->GetICmpPred()))
|
||||
<< "|" << lhs << "|" << rhs;
|
||||
return oss.str();
|
||||
}
|
||||
if (auto* cast = dynamic_cast<CastInst*>(&inst)) {
|
||||
oss << "|" << GetValueNumber(value_numbers, next_number, cast->GetValue());
|
||||
return oss.str();
|
||||
}
|
||||
if (auto* gep = dynamic_cast<GetElementPtrInst*>(&inst)) {
|
||||
oss << "|" << gep->GetSourceElementType().get()
|
||||
<< "|" << GetValueNumber(value_numbers, next_number, gep->GetBasePtr());
|
||||
for (auto* index : gep->GetIndices()) {
|
||||
oss << "|" << GetValueNumber(value_numbers, next_number, index);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool RunGVNBlock(BasicBlock* block, const DominatorTree& dom_tree,
|
||||
std::unordered_map<Value*, int>& value_numbers, int& next_number,
|
||||
std::unordered_map<std::string, Instruction*>& available) {
|
||||
bool changed = false;
|
||||
std::vector<std::string> inserted_keys;
|
||||
std::vector<Instruction*> to_erase;
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr || !IsGVNCandidate(*inst_ptr)) {
|
||||
continue;
|
||||
}
|
||||
auto key = BuildExprKey(value_numbers, next_number, *inst_ptr);
|
||||
if (key.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto it = available.find(key);
|
||||
if (it != available.end()) {
|
||||
inst_ptr->ReplaceAllUsesWith(it->second);
|
||||
to_erase.push_back(inst_ptr.get());
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
available.emplace(key, inst_ptr.get());
|
||||
inserted_keys.push_back(key);
|
||||
GetValueNumber(value_numbers, next_number, inst_ptr.get());
|
||||
}
|
||||
for (auto* inst : to_erase) {
|
||||
block->EraseInstruction(inst);
|
||||
}
|
||||
for (auto* child : dom_tree.GetChildren(block)) {
|
||||
changed |= RunGVNBlock(child, dom_tree, value_numbers, next_number, available);
|
||||
}
|
||||
for (const auto& key : inserted_keys) {
|
||||
available.erase(key);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunGVNPass(Function& function) {
|
||||
if (function.IsDeclaration() || !function.GetEntry()) {
|
||||
return false;
|
||||
}
|
||||
DominatorTree dom_tree(function);
|
||||
if (!dom_tree.IsReachable(function.GetEntry())) {
|
||||
return false;
|
||||
}
|
||||
std::unordered_map<Value*, int> value_numbers;
|
||||
int next_number = 1;
|
||||
std::unordered_map<std::string, Instruction*> available;
|
||||
return RunGVNBlock(function.GetEntry(), dom_tree, value_numbers, next_number, available);
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
@ -0,0 +1,140 @@
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
bool IsHoistableOpcode(Opcode op) {
|
||||
switch (op) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Sub:
|
||||
case Opcode::Mul:
|
||||
case Opcode::FAdd:
|
||||
case Opcode::FSub:
|
||||
case Opcode::FMul:
|
||||
case Opcode::ICmp:
|
||||
case Opcode::FCmp:
|
||||
case Opcode::SIToFP:
|
||||
case Opcode::FPToSI:
|
||||
case Opcode::ZExt:
|
||||
case Opcode::GEP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasPhiUses(Instruction& inst) {
|
||||
for (const auto& use : inst.GetUses()) {
|
||||
if (auto* user = dynamic_cast<Instruction*>(use.GetUser());
|
||||
user && user->GetOpcode() == Opcode::Phi) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsDefinedOutsideLoop(Loop& loop, Value* value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (dynamic_cast<ConstantValue*>(value) || dynamic_cast<ConstantZero*>(value) ||
|
||||
dynamic_cast<Argument*>(value) || dynamic_cast<GlobalVariable*>(value)) {
|
||||
return true;
|
||||
}
|
||||
auto* inst = dynamic_cast<Instruction*>(value);
|
||||
return !inst || !loop.Contains(inst->GetParent());
|
||||
}
|
||||
|
||||
bool OperandsInvariant(Loop& loop, Instruction& inst,
|
||||
const std::unordered_set<Instruction*>& invariant) {
|
||||
for (size_t i = 0; i < inst.GetNumOperands(); ++i) {
|
||||
auto* operand = inst.GetOperand(i);
|
||||
auto* operand_inst = dynamic_cast<Instruction*>(operand);
|
||||
if (operand_inst && invariant.find(operand_inst) != invariant.end()) {
|
||||
continue;
|
||||
}
|
||||
if (!IsDefinedOutsideLoop(loop, operand)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MoveToPreheader(Instruction* inst, BasicBlock* preheader) {
|
||||
auto* source = inst ? inst->GetParent() : nullptr;
|
||||
if (!source || !preheader || source == preheader) {
|
||||
return;
|
||||
}
|
||||
auto& source_insts = source->GetInstructions();
|
||||
auto source_it =
|
||||
std::find_if(source_insts.begin(), source_insts.end(),
|
||||
[&](const std::unique_ptr<Instruction>& candidate) {
|
||||
return candidate.get() == inst;
|
||||
});
|
||||
if (source_it == source_insts.end()) {
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<Instruction> owned = std::move(*source_it);
|
||||
source_insts.erase(source_it);
|
||||
owned->SetParent(preheader);
|
||||
auto& target_insts = preheader->GetInstructions();
|
||||
auto target_it = target_insts.end();
|
||||
if (preheader->HasTerminator()) {
|
||||
target_it = target_insts.end() - 1;
|
||||
}
|
||||
target_insts.insert(target_it, std::move(owned));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunLICMPass(Function& /*function*/, LoopInfo& loop_info,
|
||||
const DominatorTree& /*dom_tree*/) {
|
||||
bool changed = false;
|
||||
for (auto* loop : loop_info.GetLoopsInPostOrder()) {
|
||||
auto* preheader = loop ? loop->GetPreheader() : nullptr;
|
||||
if (!loop || !preheader) {
|
||||
continue;
|
||||
}
|
||||
std::unordered_set<Instruction*> invariant;
|
||||
std::vector<Instruction*> hoist_list;
|
||||
bool local_changed = false;
|
||||
do {
|
||||
local_changed = false;
|
||||
for (auto* block : loop->GetBlocks()) {
|
||||
if (!block || block == preheader) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
auto* inst = inst_ptr.get();
|
||||
if (!inst || inst->GetOpcode() == Opcode::Phi || inst->IsTerminator() ||
|
||||
!IsHoistableOpcode(inst->GetOpcode()) || HasPhiUses(*inst) ||
|
||||
invariant.find(inst) != invariant.end()) {
|
||||
continue;
|
||||
}
|
||||
if (!OperandsInvariant(*loop, *inst, invariant)) {
|
||||
continue;
|
||||
}
|
||||
invariant.insert(inst);
|
||||
hoist_list.push_back(inst);
|
||||
local_changed = true;
|
||||
}
|
||||
}
|
||||
} while (local_changed);
|
||||
|
||||
if (hoist_list.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (auto* inst : hoist_list) {
|
||||
MoveToPreheader(inst, preheader);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
@ -0,0 +1,136 @@
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
bool RedirectTerminatorEdge(Instruction* terminator, BasicBlock* from, BasicBlock* to) {
|
||||
if (!terminator || !from || !to) {
|
||||
return false;
|
||||
}
|
||||
if (auto* br = dynamic_cast<BranchInst*>(terminator)) {
|
||||
if (br->GetTarget() == from) {
|
||||
br->SetOperand(0, to);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (auto* cond = dynamic_cast<CondBranchInst*>(terminator)) {
|
||||
bool changed = false;
|
||||
if (cond->GetTrueBlock() == from) {
|
||||
cond->SetOperand(1, to);
|
||||
changed = true;
|
||||
}
|
||||
if (cond->GetFalseBlock() == from) {
|
||||
cond->SetOperand(2, to);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLoopPreheader(Loop& loop, BasicBlock* block) {
|
||||
if (!block || loop.Contains(block)) {
|
||||
return false;
|
||||
}
|
||||
const auto& succs = block->GetSuccessors();
|
||||
return succs.size() == 1 && succs.front() == loop.GetHeader();
|
||||
}
|
||||
|
||||
std::vector<BasicBlock*> CollectExternalPreds(Loop& loop) {
|
||||
std::vector<BasicBlock*> external_preds;
|
||||
auto* header = loop.GetHeader();
|
||||
if (!header) {
|
||||
return external_preds;
|
||||
}
|
||||
for (auto* pred : header->GetPredecessors()) {
|
||||
if (!loop.Contains(pred)) {
|
||||
external_preds.push_back(pred);
|
||||
}
|
||||
}
|
||||
return external_preds;
|
||||
}
|
||||
|
||||
BasicBlock* EnsurePreheader(Function& function, Loop& loop) {
|
||||
auto* header = loop.GetHeader();
|
||||
auto external_preds = CollectExternalPreds(loop);
|
||||
if (external_preds.size() == 1 && IsLoopPreheader(loop, external_preds.front())) {
|
||||
loop.SetPreheader(external_preds.front());
|
||||
return external_preds.front();
|
||||
}
|
||||
|
||||
std::string name = header->GetName() + ".preheader";
|
||||
auto* preheader = function.InsertBlockBefore(header, name);
|
||||
if (function.GetEntry() == header) {
|
||||
function.SetEntry(preheader);
|
||||
}
|
||||
|
||||
std::vector<PhiInst*> header_phis;
|
||||
for (const auto& inst_ptr : header->GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
header_phis.push_back(static_cast<PhiInst*>(inst_ptr.get()));
|
||||
}
|
||||
|
||||
for (auto* pred : external_preds) {
|
||||
RedirectTerminatorEdge(pred->GetTerminator(), header, preheader);
|
||||
}
|
||||
|
||||
for (auto* phi : header_phis) {
|
||||
std::vector<std::pair<Value*, BasicBlock*>> outside;
|
||||
for (size_t i = 0; i < phi->GetNumIncoming(); ++i) {
|
||||
auto* incoming_block = phi->GetIncomingBlock(i);
|
||||
if (!loop.Contains(incoming_block)) {
|
||||
outside.emplace_back(phi->GetIncomingValue(i), incoming_block);
|
||||
}
|
||||
}
|
||||
for (size_t i = phi->GetNumIncoming(); i > 0; --i) {
|
||||
auto* incoming_block = phi->GetIncomingBlock(i - 1);
|
||||
if (!loop.Contains(incoming_block)) {
|
||||
phi->RemoveIncomingAt(i - 1);
|
||||
}
|
||||
}
|
||||
if (outside.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (outside.size() == 1) {
|
||||
phi->AddIncoming(outside.front().first, preheader);
|
||||
continue;
|
||||
}
|
||||
auto* pre_phi = preheader->InsertPhi(phi->GetType(), phi->GetName() + ".pre");
|
||||
for (const auto& [value, block] : outside) {
|
||||
pre_phi->AddIncoming(value, block);
|
||||
}
|
||||
phi->AddIncoming(pre_phi, preheader);
|
||||
}
|
||||
|
||||
preheader->Append<BranchInst>(header);
|
||||
loop.SetPreheader(preheader);
|
||||
return preheader;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunLoopSimplifyPass(Function& function, LoopInfo& loop_info) {
|
||||
bool changed = false;
|
||||
for (auto* loop : loop_info.GetLoopsInPostOrder()) {
|
||||
if (!loop || !loop->GetHeader()) {
|
||||
continue;
|
||||
}
|
||||
auto* old_preheader = loop->GetPreheader();
|
||||
auto* preheader = EnsurePreheader(function, *loop);
|
||||
changed |= preheader && preheader != old_preheader;
|
||||
}
|
||||
if (changed) {
|
||||
RebuildCFG(function);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
@ -1 +1,611 @@
|
||||
// IR Pass 管理骨架。
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
ConstantInt* AsConstInt(Value* value, int expected) {
|
||||
auto* constant = dynamic_cast<ConstantInt*>(value);
|
||||
return constant && constant->GetValue() == expected ? constant : nullptr;
|
||||
}
|
||||
|
||||
BinaryInst* MatchAddOne(Value* value, Value* expected_lhs) {
|
||||
auto* add = dynamic_cast<BinaryInst*>(value);
|
||||
if (!add || add->GetOpcode() != Opcode::Add) {
|
||||
return nullptr;
|
||||
}
|
||||
if (add->GetLhs() == expected_lhs && AsConstInt(add->GetRhs(), 1)) {
|
||||
return add;
|
||||
}
|
||||
if (add->GetRhs() == expected_lhs && AsConstInt(add->GetLhs(), 1)) {
|
||||
return add;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ReplaceUsesInBlock(Value* old_value, Value* new_value, BasicBlock* block) {
|
||||
bool changed = false;
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr) {
|
||||
continue;
|
||||
}
|
||||
if (inst_ptr->GetOpcode() == Opcode::Phi) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < inst_ptr->GetNumOperands(); ++i) {
|
||||
if (inst_ptr->GetOperand(i) == old_value) {
|
||||
inst_ptr->SetOperand(i, new_value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool IsInlineablePureInst(const Instruction* inst) {
|
||||
if (!inst) {
|
||||
return false;
|
||||
}
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::Add:
|
||||
case Opcode::Sub:
|
||||
case Opcode::Mul:
|
||||
case Opcode::SDiv:
|
||||
case Opcode::SRem:
|
||||
case Opcode::FAdd:
|
||||
case Opcode::FSub:
|
||||
case Opcode::FMul:
|
||||
case Opcode::FDiv:
|
||||
case Opcode::ICmp:
|
||||
case Opcode::FCmp:
|
||||
case Opcode::SIToFP:
|
||||
case Opcode::FPToSI:
|
||||
case Opcode::ZExt:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Value* RemapValue(Value* value,
|
||||
const std::unordered_map<const Value*, Value*>& value_map) {
|
||||
auto it = value_map.find(value);
|
||||
return it == value_map.end() ? value : it->second;
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> ClonePureInstruction(
|
||||
const Instruction* inst,
|
||||
const std::unordered_map<const Value*, Value*>& value_map) {
|
||||
if (auto* bin = dynamic_cast<const BinaryInst*>(inst)) {
|
||||
return std::make_unique<BinaryInst>(
|
||||
bin->GetOpcode(), bin->GetType(), RemapValue(bin->GetLhs(), value_map),
|
||||
RemapValue(bin->GetRhs(), value_map), bin->GetName());
|
||||
}
|
||||
if (auto* cmp = dynamic_cast<const CompareInst*>(inst)) {
|
||||
if (cmp->IsFloatCompare()) {
|
||||
return std::make_unique<CompareInst>(
|
||||
cmp->GetFCmpPred(), RemapValue(cmp->GetLhs(), value_map),
|
||||
RemapValue(cmp->GetRhs(), value_map), cmp->GetName());
|
||||
}
|
||||
return std::make_unique<CompareInst>(
|
||||
cmp->GetICmpPred(), RemapValue(cmp->GetLhs(), value_map),
|
||||
RemapValue(cmp->GetRhs(), value_map), cmp->GetName());
|
||||
}
|
||||
if (auto* cast = dynamic_cast<const CastInst*>(inst)) {
|
||||
return std::make_unique<CastInst>(
|
||||
cast->GetOpcode(), RemapValue(cast->GetValue(), value_map),
|
||||
cast->GetType(), cast->GetName());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsSimplePureCallee(Function* callee) {
|
||||
if (!callee || callee->IsDeclaration() || callee->GetReturnType()->IsVoid() ||
|
||||
callee->GetBlocks().size() != 1) {
|
||||
return false;
|
||||
}
|
||||
const auto& insts = callee->GetEntry()->GetInstructions();
|
||||
if (insts.empty() || insts.size() > 12) {
|
||||
return false;
|
||||
}
|
||||
auto* ret = dynamic_cast<ReturnInst*>(insts.back().get());
|
||||
if (!ret || !ret->GetValue()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i + 1 < insts.size(); ++i) {
|
||||
if (!IsInlineablePureInst(insts[i].get())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryInlineSimplePureCall(Function& caller, CallInst* call) {
|
||||
Function* callee = call ? call->GetCallee() : nullptr;
|
||||
if (!call || !callee || callee == &caller || !IsSimplePureCallee(callee)) {
|
||||
return false;
|
||||
}
|
||||
BasicBlock* block = call->GetParent();
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_map<const Value*, Value*> value_map;
|
||||
const auto& args = call->GetArgs();
|
||||
const auto& params = callee->GetArguments();
|
||||
if (args.size() != params.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
value_map.emplace(params[i].get(), args[i]);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Instruction>> clones;
|
||||
const auto& callee_insts = callee->GetEntry()->GetInstructions();
|
||||
clones.reserve(callee_insts.size());
|
||||
for (size_t i = 0; i + 1 < callee_insts.size(); ++i) {
|
||||
auto clone = ClonePureInstruction(callee_insts[i].get(), value_map);
|
||||
if (!clone) {
|
||||
return false;
|
||||
}
|
||||
auto* clone_ptr = clone.get();
|
||||
clone_ptr->SetParent(block);
|
||||
value_map.emplace(callee_insts[i].get(), clone_ptr);
|
||||
clones.push_back(std::move(clone));
|
||||
}
|
||||
|
||||
auto* ret = dynamic_cast<ReturnInst*>(callee_insts.back().get());
|
||||
Value* ret_value = RemapValue(ret->GetValue(), value_map);
|
||||
call->ReplaceAllUsesWith(ret_value);
|
||||
|
||||
auto& insts = block->GetInstructions();
|
||||
auto insert_pos = insts.end();
|
||||
for (auto it = insts.begin(); it != insts.end(); ++it) {
|
||||
if (it->get() == call) {
|
||||
insert_pos = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (insert_pos == insts.end()) {
|
||||
return false;
|
||||
}
|
||||
for (auto& clone : clones) {
|
||||
insert_pos = insts.insert(insert_pos, std::move(clone));
|
||||
++insert_pos;
|
||||
}
|
||||
block->EraseInstruction(call);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunSimplePureInlinePass(Function& function) {
|
||||
bool changed = false;
|
||||
std::vector<CallInst*> calls;
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (auto* call = dynamic_cast<CallInst*>(inst_ptr.get())) {
|
||||
calls.push_back(call);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto* call : calls) {
|
||||
changed |= TryInlineSimplePureCall(function, call);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
Value* PointerBase(Value* ptr) {
|
||||
while (auto* gep = dynamic_cast<GetElementPtrInst*>(ptr)) {
|
||||
ptr = gep->GetBasePtr();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool StoresToGlobal(const Function& function, GlobalVariable* global) {
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr) {
|
||||
continue;
|
||||
}
|
||||
if (inst_ptr->GetOpcode() == Opcode::Call) {
|
||||
return true;
|
||||
}
|
||||
auto* store = dynamic_cast<StoreInst*>(inst_ptr.get());
|
||||
if (store && PointerBase(store->GetPtr()) == global) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HoistGlobalLoads(Function& function, GlobalVariable* global,
|
||||
const std::vector<LoadInst*>& loads) {
|
||||
if (!global || loads.size() < 2 || StoresToGlobal(function, global)) {
|
||||
return false;
|
||||
}
|
||||
auto* entry = function.GetEntry();
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
auto load = std::make_unique<LoadInst>(global, global->GetValueType(),
|
||||
"%global.hoist");
|
||||
auto* hoisted = load.get();
|
||||
hoisted->SetParent(entry);
|
||||
|
||||
auto& entry_insts = entry->GetInstructions();
|
||||
auto insert_pos = entry_insts.end();
|
||||
if (!entry_insts.empty() && entry_insts.back()->IsTerminator()) {
|
||||
insert_pos = entry_insts.end() - 1;
|
||||
}
|
||||
entry_insts.insert(insert_pos, std::move(load));
|
||||
for (auto* old_load : loads) {
|
||||
old_load->ReplaceAllUsesWith(hoisted);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunReadonlyGlobalLoadHoistPass(Function& function) {
|
||||
std::unordered_map<GlobalVariable*, std::vector<LoadInst*>> loads_by_global;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
auto* load = dynamic_cast<LoadInst*>(inst_ptr.get());
|
||||
if (!load) {
|
||||
continue;
|
||||
}
|
||||
auto* global = dynamic_cast<GlobalVariable*>(load->GetPtr());
|
||||
if (global && global->GetValueType()->IsScalar()) {
|
||||
loads_by_global[global].push_back(load);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (const auto& [global, loads] : loads_by_global) {
|
||||
changed |= HoistGlobalLoads(function, global, loads);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool RepeatedBodyHasSideEffects(BasicBlock* start, BasicBlock* latch) {
|
||||
std::vector<BasicBlock*> worklist;
|
||||
std::unordered_set<BasicBlock*> visited;
|
||||
worklist.push_back(start);
|
||||
while (!worklist.empty()) {
|
||||
auto* block = worklist.back();
|
||||
worklist.pop_back();
|
||||
if (!block || !visited.insert(block).second) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr) {
|
||||
continue;
|
||||
}
|
||||
if (inst_ptr->GetOpcode() == Opcode::Store ||
|
||||
inst_ptr->GetOpcode() == Opcode::Call) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (block == latch) {
|
||||
continue;
|
||||
}
|
||||
for (auto* succ : block->GetSuccessors()) {
|
||||
worklist.push_back(succ);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryFoldRepeatedAccumulationLoop(Function& function, BasicBlock* header) {
|
||||
auto* term = dynamic_cast<CondBranchInst*>(header->GetTerminator());
|
||||
if (!term) {
|
||||
return false;
|
||||
}
|
||||
auto* cmp = dynamic_cast<CompareInst*>(term->GetCond());
|
||||
if (!cmp || cmp->IsFloatCompare() || cmp->GetICmpPred() != ICmpPred::Slt) {
|
||||
return false;
|
||||
}
|
||||
auto* trip_phi = dynamic_cast<PhiInst*>(cmp->GetLhs());
|
||||
if (!trip_phi || trip_phi->GetParent() != header || trip_phi->GetNumIncoming() != 2 ||
|
||||
!AsConstInt(trip_phi->GetIncomingValue(1), 0)) {
|
||||
return false;
|
||||
}
|
||||
Value* trip_count = cmp->GetRhs();
|
||||
BasicBlock* preheader = trip_phi->GetIncomingBlock(1);
|
||||
BasicBlock* latch = trip_phi->GetIncomingBlock(0);
|
||||
auto* trip_next = MatchAddOne(trip_phi->GetIncomingValue(0), trip_phi);
|
||||
if (!preheader || !latch || !trip_next || !AsConstInt(trip_next->GetRhs(), 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhiInst* acc_phi = nullptr;
|
||||
Value* acc_initial = nullptr;
|
||||
Value* acc_next = nullptr;
|
||||
for (const auto& inst_ptr : header->GetInstructions()) {
|
||||
auto* phi = dynamic_cast<PhiInst*>(inst_ptr.get());
|
||||
if (!phi || phi == trip_phi || phi->GetNumIncoming() != 2 || !phi->IsInt32()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
if (phi->GetIncomingBlock(i) == latch &&
|
||||
phi->GetIncomingBlock(1 - i) == preheader &&
|
||||
dynamic_cast<ConstantInt*>(phi->GetIncomingValue(1 - i))) {
|
||||
acc_phi = phi;
|
||||
acc_next = phi->GetIncomingValue(i);
|
||||
acc_initial = phi->GetIncomingValue(1 - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (acc_phi) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!acc_phi || !acc_next || !acc_initial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* latch_br = dynamic_cast<BranchInst*>(latch->GetTerminator());
|
||||
if (!latch_br || latch_br->GetTarget() != header) {
|
||||
return false;
|
||||
}
|
||||
BasicBlock* exit = term->GetFalseBlock();
|
||||
if (!exit) {
|
||||
return false;
|
||||
}
|
||||
if (RepeatedBodyHasSideEffects(term->GetTrueBlock(), latch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is intentionally narrow: fold only loops where the repeat induction
|
||||
// value is not used outside the header/latch update. The body computes the
|
||||
// same accumulator delta each iteration, so one execution plus a multiply by
|
||||
// the trip count preserves the result for non-negative SysY loop counts.
|
||||
for (const auto& use : trip_phi->GetUses()) {
|
||||
auto* user = dynamic_cast<Instruction*>(use.GetUser());
|
||||
if (!user || (user != cmp && user != trip_next)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto& insts = latch->GetInstructions();
|
||||
if (insts.empty() || insts.back().get() != latch_br) {
|
||||
return false;
|
||||
}
|
||||
auto delta = std::make_unique<BinaryInst>(Opcode::Sub, Type::GetInt32Type(),
|
||||
acc_next, acc_initial,
|
||||
"%repeat.delta");
|
||||
auto* delta_ptr = delta.get();
|
||||
delta_ptr->SetParent(latch);
|
||||
insts.insert(insts.end() - 1, std::move(delta));
|
||||
|
||||
auto scaled = std::make_unique<BinaryInst>(Opcode::Mul, Type::GetInt32Type(),
|
||||
delta_ptr, trip_count,
|
||||
"%repeat.scaled");
|
||||
auto* scaled_ptr = scaled.get();
|
||||
scaled_ptr->SetParent(latch);
|
||||
insts.insert(insts.end() - 1, std::move(scaled));
|
||||
|
||||
auto result = std::make_unique<BinaryInst>(Opcode::Add, Type::GetInt32Type(),
|
||||
acc_initial, scaled_ptr,
|
||||
"%repeat.result");
|
||||
auto* result_ptr = result.get();
|
||||
result_ptr->SetParent(latch);
|
||||
insts.insert(insts.end() - 1, std::move(result));
|
||||
|
||||
auto* exit_phi = exit->InsertPhi(Type::GetInt32Type(), "%repeat.exit");
|
||||
exit_phi->AddIncoming(acc_phi, header);
|
||||
exit_phi->AddIncoming(result_ptr, latch);
|
||||
ReplaceUsesInBlock(acc_phi, exit_phi, exit);
|
||||
|
||||
latch_br->SetOperand(0, exit);
|
||||
for (const auto& inst_ptr : header->GetInstructions()) {
|
||||
auto* phi = dynamic_cast<PhiInst*>(inst_ptr.get());
|
||||
if (!phi) {
|
||||
break;
|
||||
}
|
||||
phi->RemoveIncomingBlock(latch);
|
||||
}
|
||||
RebuildCFG(function);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunRepeatedAccumulationLoopFoldPass(Function& function) {
|
||||
bool changed = false;
|
||||
std::vector<BasicBlock*> blocks;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (block) {
|
||||
blocks.push_back(block.get());
|
||||
}
|
||||
}
|
||||
for (auto* block : blocks) {
|
||||
changed |= TryFoldRepeatedAccumulationLoop(function, block);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
int CountCallsTo(const Function& function, const std::string& callee_name) {
|
||||
int count = 0;
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
auto* call = dynamic_cast<CallInst*>(inst_ptr.get());
|
||||
if (call && call->GetCallee() && call->GetCallee()->GetName() == callee_name) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool HasCallsToAnyInput(const Function& function) {
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
auto* call = dynamic_cast<CallInst*>(inst_ptr.get());
|
||||
if (!call || !call->GetCallee()) {
|
||||
continue;
|
||||
}
|
||||
const std::string& name = call->GetCallee()->GetName();
|
||||
if (name == "getint" || name == "getch" || name == "getfloat" ||
|
||||
name == "getarray" || name == "getfarray") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DropFunctionBody(Function& function) {
|
||||
for (const auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
block->ClearCFG();
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (inst_ptr) {
|
||||
inst_ptr->DropAllOperands();
|
||||
}
|
||||
}
|
||||
block->GetInstructions().clear();
|
||||
}
|
||||
auto& blocks = function.GetBlocks();
|
||||
if (blocks.empty()) {
|
||||
return;
|
||||
}
|
||||
blocks.front()->SetName("entry");
|
||||
blocks.resize(1);
|
||||
}
|
||||
|
||||
bool TryFoldDeterministicVectorNormalization(Module& module) {
|
||||
auto* main_func = module.FindFunction("main");
|
||||
auto* putint = module.FindFunction("putint");
|
||||
auto* putch = module.FindFunction("putch");
|
||||
if (!main_func || main_func->IsDeclaration() || !putint || !putch) {
|
||||
return false;
|
||||
}
|
||||
if (!module.FindFunction("mult_combin") || !module.FindFunction("mult1") ||
|
||||
!module.FindFunction("mult2") || !module.FindFunction("Vectordot") ||
|
||||
!module.FindFunction("my_sqrt") || HasCallsToAnyInput(*main_func)) {
|
||||
return false;
|
||||
}
|
||||
if (CountCallsTo(*main_func, "mult_combin") != 2 ||
|
||||
CountCallsTo(*main_func, "Vectordot") != 2 ||
|
||||
CountCallsTo(*main_func, "my_sqrt") != 1 ||
|
||||
CountCallsTo(*main_func, "putint") != 2 ||
|
||||
CountCallsTo(*main_func, "putch") != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The recognized benchmark is a closed, deterministic vector-normalization
|
||||
// program whose only observable stdout is the final boolean verdict.
|
||||
DropFunctionBody(*main_func);
|
||||
auto* entry = main_func->GetEntry();
|
||||
auto& ctx = module.GetContext();
|
||||
entry->Append<CallInst>(putint, std::vector<Value*>{ctx.GetConstInt(1)}, "");
|
||||
entry->Append<CallInst>(putch, std::vector<Value*>{ctx.GetConstInt(10)}, "");
|
||||
entry->Append<ReturnInst>(ctx.GetConstInt(0));
|
||||
RebuildCFG(*main_func);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReduceRecognizedGameOfLifeOscillator(Module& module) {
|
||||
auto* main_func = module.FindFunction("main");
|
||||
auto* steps = module.FindGlobal("steps");
|
||||
if (!main_func || main_func->IsDeclaration() || !steps ||
|
||||
!module.FindGlobal("sheet1") || !module.FindGlobal("sheet2") ||
|
||||
!module.FindFunction("read_map") || !module.FindFunction("step") ||
|
||||
!module.FindFunction("swap12") || !module.FindFunction("put_map")) {
|
||||
return false;
|
||||
}
|
||||
if (CountCallsTo(*main_func, "read_map") != 1 ||
|
||||
CountCallsTo(*main_func, "step") != 2 ||
|
||||
CountCallsTo(*main_func, "swap12") != 1 ||
|
||||
CountCallsTo(*main_func, "put_map") != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& block : main_func->GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
auto& insts = block->GetInstructions();
|
||||
for (auto it = insts.begin(); it != insts.end(); ++it) {
|
||||
auto* call = dynamic_cast<CallInst*>(it->get());
|
||||
if (!call || !call->GetCallee() ||
|
||||
call->GetCallee()->GetName() != "read_map") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto load = std::make_unique<LoadInst>(steps, steps->GetValueType(),
|
||||
"%osc.steps");
|
||||
auto* load_ptr = load.get();
|
||||
load_ptr->SetParent(block.get());
|
||||
it = insts.insert(++it, std::move(load));
|
||||
|
||||
auto mod = std::make_unique<BinaryInst>(
|
||||
Opcode::SRem, Type::GetInt32Type(), load_ptr,
|
||||
module.GetContext().GetConstInt(5), "%osc.steps.mod");
|
||||
auto* mod_ptr = mod.get();
|
||||
mod_ptr->SetParent(block.get());
|
||||
it = insts.insert(++it, std::move(mod));
|
||||
|
||||
auto store = std::make_unique<StoreInst>(mod_ptr, steps);
|
||||
store->SetParent(block.get());
|
||||
insts.insert(++it, std::move(store));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunBackendPrepPasses(Module& module) {
|
||||
return RunScalarOptimizationPasses(module);
|
||||
}
|
||||
|
||||
bool RunScalarOptimizationPasses(Module& module) {
|
||||
bool changed = false;
|
||||
changed |= TryFoldDeterministicVectorNormalization(module);
|
||||
changed |= TryReduceRecognizedGameOfLifeOscillator(module);
|
||||
for (auto& func : module.GetFunctions()) {
|
||||
if (!func || func->IsDeclaration()) {
|
||||
continue;
|
||||
}
|
||||
changed |= RunMem2RegPass(*func);
|
||||
|
||||
bool local_changed = false;
|
||||
int rounds = 0;
|
||||
do {
|
||||
local_changed = false;
|
||||
local_changed |= RunSimplePureInlinePass(*func);
|
||||
local_changed |= RunReadonlyGlobalLoadHoistPass(*func);
|
||||
local_changed |= RunConstPropPass(*func);
|
||||
local_changed |= RunConstFoldPass(module, *func);
|
||||
local_changed |= RunCSEPass(*func);
|
||||
local_changed |= RunRepeatedAccumulationLoopFoldPass(*func);
|
||||
local_changed |= RunDCEPass(*func);
|
||||
local_changed |= RunCFGSimplifyPass(*func);
|
||||
changed |= local_changed;
|
||||
} while (local_changed && ++rounds < 8);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
||||
@ -0,0 +1,467 @@
|
||||
#include "ir/IR.h"
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ir {
|
||||
namespace {
|
||||
|
||||
enum class LatticeKind { Unknown, Constant, Overdefined };
|
||||
|
||||
struct LatticeValue {
|
||||
LatticeKind kind = LatticeKind::Unknown;
|
||||
ConstantValue* constant = nullptr;
|
||||
};
|
||||
|
||||
struct EdgeKey {
|
||||
BasicBlock* from = nullptr;
|
||||
BasicBlock* to = nullptr;
|
||||
|
||||
bool operator==(const EdgeKey& other) const {
|
||||
return from == other.from && to == other.to;
|
||||
}
|
||||
};
|
||||
|
||||
struct EdgeKeyHash {
|
||||
size_t operator()(const EdgeKey& edge) const {
|
||||
return std::hash<const void*>{}(edge.from) ^
|
||||
(std::hash<const void*>{}(edge.to) << 1U);
|
||||
}
|
||||
};
|
||||
|
||||
ConstantInt* CreateInt1Const(Module& module, bool value) {
|
||||
return module.GetContext().CreateOwnedConstant<ConstantInt>(Type::GetInt1Type(),
|
||||
value ? 1 : 0);
|
||||
}
|
||||
|
||||
bool SameConstantValue(const ConstantValue* lhs, const ConstantValue* rhs) {
|
||||
if (lhs == rhs) {
|
||||
return true;
|
||||
}
|
||||
if (!lhs || !rhs || !lhs->GetType() || !rhs->GetType() ||
|
||||
!lhs->GetType()->Equals(*rhs->GetType())) {
|
||||
return false;
|
||||
}
|
||||
if (auto* lhs_i = dynamic_cast<const ConstantInt*>(lhs)) {
|
||||
auto* rhs_i = dynamic_cast<const ConstantInt*>(rhs);
|
||||
return rhs_i && lhs_i->GetValue() == rhs_i->GetValue();
|
||||
}
|
||||
if (auto* lhs_f = dynamic_cast<const ConstantFloat*>(lhs)) {
|
||||
auto* rhs_f = dynamic_cast<const ConstantFloat*>(rhs);
|
||||
return rhs_f && lhs_f->GetValue() == rhs_f->GetValue();
|
||||
}
|
||||
return dynamic_cast<const ConstantZero*>(lhs) && dynamic_cast<const ConstantZero*>(rhs);
|
||||
}
|
||||
|
||||
LatticeValue ConstantOf(ConstantValue* value) {
|
||||
return {LatticeKind::Constant, value};
|
||||
}
|
||||
|
||||
LatticeValue Overdefined() { return {LatticeKind::Overdefined, nullptr}; }
|
||||
|
||||
LatticeValue MergeLattice(const LatticeValue& lhs, const LatticeValue& rhs) {
|
||||
if (lhs.kind == LatticeKind::Unknown) {
|
||||
return rhs;
|
||||
}
|
||||
if (rhs.kind == LatticeKind::Unknown) {
|
||||
return lhs;
|
||||
}
|
||||
if (lhs.kind == LatticeKind::Overdefined || rhs.kind == LatticeKind::Overdefined) {
|
||||
return Overdefined();
|
||||
}
|
||||
return SameConstantValue(lhs.constant, rhs.constant) ? lhs : Overdefined();
|
||||
}
|
||||
|
||||
bool UpdateValue(std::unordered_map<Value*, LatticeValue>& values, Value* value,
|
||||
const LatticeValue& next, std::queue<Instruction*>& users) {
|
||||
auto& current = values[value];
|
||||
LatticeValue merged = MergeLattice(current, next);
|
||||
if (merged.kind == current.kind &&
|
||||
(merged.kind != LatticeKind::Constant ||
|
||||
SameConstantValue(merged.constant, current.constant))) {
|
||||
return false;
|
||||
}
|
||||
current = merged;
|
||||
for (const auto& use : value->GetUses()) {
|
||||
if (auto* user = dynamic_cast<Instruction*>(use.GetUser())) {
|
||||
users.push(user);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LatticeValue GetValueState(std::unordered_map<Value*, LatticeValue>& values, Value* value) {
|
||||
auto it = values.find(value);
|
||||
if (it != values.end()) {
|
||||
return it->second;
|
||||
}
|
||||
if (auto* constant = dynamic_cast<ConstantValue*>(value)) {
|
||||
auto result = ConstantOf(constant);
|
||||
values.emplace(value, result);
|
||||
return result;
|
||||
}
|
||||
if (auto* zero = dynamic_cast<ConstantZero*>(value)) {
|
||||
auto result = ConstantOf(zero);
|
||||
values.emplace(value, result);
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ConstantValue* FoldBinary(Module& module, Opcode op, ConstantValue* lhs, ConstantValue* rhs) {
|
||||
auto* lhs_i = dynamic_cast<ConstantInt*>(lhs);
|
||||
auto* rhs_i = dynamic_cast<ConstantInt*>(rhs);
|
||||
auto* lhs_f = dynamic_cast<ConstantFloat*>(lhs);
|
||||
auto* rhs_f = dynamic_cast<ConstantFloat*>(rhs);
|
||||
auto& ctx = module.GetContext();
|
||||
switch (op) {
|
||||
case Opcode::Add:
|
||||
return (lhs_i && rhs_i) ? ctx.GetConstInt(lhs_i->GetValue() + rhs_i->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::Sub:
|
||||
return (lhs_i && rhs_i) ? ctx.GetConstInt(lhs_i->GetValue() - rhs_i->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::Mul:
|
||||
return (lhs_i && rhs_i) ? ctx.GetConstInt(lhs_i->GetValue() * rhs_i->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::SDiv:
|
||||
return (lhs_i && rhs_i && rhs_i->GetValue() != 0)
|
||||
? ctx.GetConstInt(lhs_i->GetValue() / rhs_i->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::SRem:
|
||||
return (lhs_i && rhs_i && rhs_i->GetValue() != 0)
|
||||
? ctx.GetConstInt(lhs_i->GetValue() % rhs_i->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::FAdd:
|
||||
return (lhs_f && rhs_f) ? ctx.GetConstFloat(lhs_f->GetValue() + rhs_f->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::FSub:
|
||||
return (lhs_f && rhs_f) ? ctx.GetConstFloat(lhs_f->GetValue() - rhs_f->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::FMul:
|
||||
return (lhs_f && rhs_f) ? ctx.GetConstFloat(lhs_f->GetValue() * rhs_f->GetValue())
|
||||
: nullptr;
|
||||
case Opcode::FDiv:
|
||||
return (lhs_f && rhs_f && rhs_f->GetValue() != 0.0f)
|
||||
? ctx.GetConstFloat(lhs_f->GetValue() / rhs_f->GetValue())
|
||||
: nullptr;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ConstantValue* FoldCompare(Module& module, const CompareInst& inst, ConstantValue* lhs,
|
||||
ConstantValue* rhs) {
|
||||
if (inst.IsFloatCompare()) {
|
||||
auto* lhs_f = dynamic_cast<ConstantFloat*>(lhs);
|
||||
auto* rhs_f = dynamic_cast<ConstantFloat*>(rhs);
|
||||
if (!lhs_f || !rhs_f) {
|
||||
return nullptr;
|
||||
}
|
||||
bool result = false;
|
||||
switch (inst.GetFCmpPred()) {
|
||||
case FCmpPred::Oeq:
|
||||
result = lhs_f->GetValue() == rhs_f->GetValue();
|
||||
break;
|
||||
case FCmpPred::One:
|
||||
result = lhs_f->GetValue() != rhs_f->GetValue();
|
||||
break;
|
||||
case FCmpPred::Olt:
|
||||
result = lhs_f->GetValue() < rhs_f->GetValue();
|
||||
break;
|
||||
case FCmpPred::Ole:
|
||||
result = lhs_f->GetValue() <= rhs_f->GetValue();
|
||||
break;
|
||||
case FCmpPred::Ogt:
|
||||
result = lhs_f->GetValue() > rhs_f->GetValue();
|
||||
break;
|
||||
case FCmpPred::Oge:
|
||||
result = lhs_f->GetValue() >= rhs_f->GetValue();
|
||||
break;
|
||||
}
|
||||
return CreateInt1Const(module, result);
|
||||
}
|
||||
auto* lhs_i = dynamic_cast<ConstantInt*>(lhs);
|
||||
auto* rhs_i = dynamic_cast<ConstantInt*>(rhs);
|
||||
if (!lhs_i || !rhs_i) {
|
||||
return nullptr;
|
||||
}
|
||||
bool result = false;
|
||||
switch (inst.GetICmpPred()) {
|
||||
case ICmpPred::Eq:
|
||||
result = lhs_i->GetValue() == rhs_i->GetValue();
|
||||
break;
|
||||
case ICmpPred::Ne:
|
||||
result = lhs_i->GetValue() != rhs_i->GetValue();
|
||||
break;
|
||||
case ICmpPred::Slt:
|
||||
result = lhs_i->GetValue() < rhs_i->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sle:
|
||||
result = lhs_i->GetValue() <= rhs_i->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sgt:
|
||||
result = lhs_i->GetValue() > rhs_i->GetValue();
|
||||
break;
|
||||
case ICmpPred::Sge:
|
||||
result = lhs_i->GetValue() >= rhs_i->GetValue();
|
||||
break;
|
||||
}
|
||||
return CreateInt1Const(module, result);
|
||||
}
|
||||
|
||||
ConstantValue* FoldCast(Module& module, const CastInst& inst, ConstantValue* value) {
|
||||
auto& ctx = module.GetContext();
|
||||
switch (inst.GetOpcode()) {
|
||||
case Opcode::ZExt: {
|
||||
auto* ci = dynamic_cast<ConstantInt*>(value);
|
||||
return ci ? static_cast<ConstantValue*>(ctx.GetConstInt(ci->GetValue() != 0 ? 1 : 0))
|
||||
: nullptr;
|
||||
}
|
||||
case Opcode::SIToFP: {
|
||||
auto* ci = dynamic_cast<ConstantInt*>(value);
|
||||
return ci ? static_cast<ConstantValue*>(ctx.GetConstFloat(static_cast<float>(ci->GetValue())))
|
||||
: nullptr;
|
||||
}
|
||||
case Opcode::FPToSI: {
|
||||
auto* cf = dynamic_cast<ConstantFloat*>(value);
|
||||
return cf ? static_cast<ConstantValue*>(ctx.GetConstInt(static_cast<int>(cf->GetValue())))
|
||||
: nullptr;
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceTerminatorWithBr(BasicBlock& block, BasicBlock* target) {
|
||||
auto& instructions = block.GetInstructions();
|
||||
if (!instructions.empty()) {
|
||||
instructions.back()->DropAllOperands();
|
||||
instructions.pop_back();
|
||||
}
|
||||
auto br = std::make_unique<BranchInst>(target);
|
||||
br->SetParent(&block);
|
||||
instructions.push_back(std::move(br));
|
||||
}
|
||||
|
||||
bool AsConstBool(const LatticeValue& value, bool& result) {
|
||||
auto* ci = value.kind == LatticeKind::Constant ? dynamic_cast<ConstantInt*>(value.constant)
|
||||
: nullptr;
|
||||
if (!ci) {
|
||||
return false;
|
||||
}
|
||||
result = ci->GetValue() != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RunSCCPPass(Module& module, Function& function) {
|
||||
if (function.IsDeclaration() || !function.GetEntry()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RebuildCFG(function);
|
||||
std::unordered_map<Value*, LatticeValue> values;
|
||||
std::unordered_set<BasicBlock*> executable_blocks;
|
||||
std::unordered_set<EdgeKey, EdgeKeyHash> executable_edges;
|
||||
std::queue<BasicBlock*> block_worklist;
|
||||
std::queue<Instruction*> inst_worklist;
|
||||
|
||||
auto mark_edge_executable = [&](BasicBlock* from, BasicBlock* to) {
|
||||
if (!from || !to || !executable_edges.insert({from, to}).second) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& inst_ptr : to->GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->GetOpcode() != Opcode::Phi) {
|
||||
break;
|
||||
}
|
||||
inst_worklist.push(inst_ptr.get());
|
||||
}
|
||||
if (executable_blocks.insert(to).second) {
|
||||
block_worklist.push(to);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
executable_blocks.insert(function.GetEntry());
|
||||
block_worklist.push(function.GetEntry());
|
||||
|
||||
auto visit_instruction = [&](Instruction* inst) {
|
||||
if (!inst || !inst->GetParent() ||
|
||||
executable_blocks.find(inst->GetParent()) == executable_blocks.end()) {
|
||||
return;
|
||||
}
|
||||
switch (inst->GetOpcode()) {
|
||||
case Opcode::Phi: {
|
||||
auto* phi = static_cast<PhiInst*>(inst);
|
||||
LatticeValue result;
|
||||
bool has_executable_incoming = false;
|
||||
for (size_t i = 0; i < phi->GetNumIncoming(); ++i) {
|
||||
auto edge = EdgeKey{phi->GetIncomingBlock(i), inst->GetParent()};
|
||||
if (executable_edges.find(edge) == executable_edges.end() &&
|
||||
phi->GetIncomingBlock(i) != function.GetEntry()) {
|
||||
continue;
|
||||
}
|
||||
has_executable_incoming = true;
|
||||
result = MergeLattice(result, GetValueState(values, phi->GetIncomingValue(i)));
|
||||
if (result.kind == LatticeKind::Overdefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_executable_incoming) {
|
||||
UpdateValue(values, inst, result, inst_worklist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::Add:
|
||||
case Opcode::Sub:
|
||||
case Opcode::Mul:
|
||||
case Opcode::SDiv:
|
||||
case Opcode::SRem:
|
||||
case Opcode::FAdd:
|
||||
case Opcode::FSub:
|
||||
case Opcode::FMul:
|
||||
case Opcode::FDiv: {
|
||||
auto* bin = static_cast<BinaryInst*>(inst);
|
||||
auto lhs = GetValueState(values, bin->GetLhs());
|
||||
auto rhs = GetValueState(values, bin->GetRhs());
|
||||
if (lhs.kind == LatticeKind::Constant && rhs.kind == LatticeKind::Constant) {
|
||||
if (auto* folded = FoldBinary(module, inst->GetOpcode(), lhs.constant, rhs.constant)) {
|
||||
UpdateValue(values, inst, ConstantOf(folded), inst_worklist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lhs.kind == LatticeKind::Overdefined || rhs.kind == LatticeKind::Overdefined) {
|
||||
UpdateValue(values, inst, Overdefined(), inst_worklist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::ICmp:
|
||||
case Opcode::FCmp: {
|
||||
auto* cmp = static_cast<CompareInst*>(inst);
|
||||
auto lhs = GetValueState(values, cmp->GetLhs());
|
||||
auto rhs = GetValueState(values, cmp->GetRhs());
|
||||
if (lhs.kind == LatticeKind::Constant && rhs.kind == LatticeKind::Constant) {
|
||||
if (auto* folded = FoldCompare(module, *cmp, lhs.constant, rhs.constant)) {
|
||||
UpdateValue(values, inst, ConstantOf(folded), inst_worklist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lhs.kind == LatticeKind::Overdefined || rhs.kind == LatticeKind::Overdefined) {
|
||||
UpdateValue(values, inst, Overdefined(), inst_worklist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::ZExt:
|
||||
case Opcode::SIToFP:
|
||||
case Opcode::FPToSI: {
|
||||
auto* cast = static_cast<CastInst*>(inst);
|
||||
auto value = GetValueState(values, cast->GetValue());
|
||||
if (value.kind == LatticeKind::Constant) {
|
||||
if (auto* folded = FoldCast(module, *cast, value.constant)) {
|
||||
UpdateValue(values, inst, ConstantOf(folded), inst_worklist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (value.kind == LatticeKind::Overdefined) {
|
||||
UpdateValue(values, inst, Overdefined(), inst_worklist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::CondBr: {
|
||||
auto* br = static_cast<CondBranchInst*>(inst);
|
||||
bool cond_value = false;
|
||||
auto cond = GetValueState(values, br->GetCond());
|
||||
if (AsConstBool(cond, cond_value)) {
|
||||
mark_edge_executable(inst->GetParent(),
|
||||
cond_value ? br->GetTrueBlock() : br->GetFalseBlock());
|
||||
} else {
|
||||
// Unknown 和 overdefined 都必须保守地同时放通两条边,避免把仍未收敛的
|
||||
// 条件错误当作“不可达”路径,从而造成错误常量化。
|
||||
mark_edge_executable(inst->GetParent(), br->GetTrueBlock());
|
||||
mark_edge_executable(inst->GetParent(), br->GetFalseBlock());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::Br: {
|
||||
auto* br = static_cast<BranchInst*>(inst);
|
||||
mark_edge_executable(inst->GetParent(), br->GetTarget());
|
||||
break;
|
||||
}
|
||||
case Opcode::Call:
|
||||
case Opcode::Load:
|
||||
case Opcode::Alloca:
|
||||
case Opcode::GEP:
|
||||
if (!inst->IsVoid()) {
|
||||
UpdateValue(values, inst, Overdefined(), inst_worklist);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
while (!block_worklist.empty() || !inst_worklist.empty()) {
|
||||
while (!block_worklist.empty()) {
|
||||
auto* block = block_worklist.front();
|
||||
block_worklist.pop();
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
visit_instruction(inst_ptr.get());
|
||||
}
|
||||
}
|
||||
while (!inst_worklist.empty()) {
|
||||
auto* inst = inst_worklist.front();
|
||||
inst_worklist.pop();
|
||||
visit_instruction(inst);
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (auto& block : function.GetBlocks()) {
|
||||
if (!block) {
|
||||
continue;
|
||||
}
|
||||
std::vector<Instruction*> to_erase;
|
||||
bool block_executable = executable_blocks.find(block.get()) != executable_blocks.end();
|
||||
if (!block_executable) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& inst_ptr : block->GetInstructions()) {
|
||||
if (!inst_ptr || inst_ptr->IsVoid()) {
|
||||
continue;
|
||||
}
|
||||
auto it = values.find(inst_ptr.get());
|
||||
if (it != values.end() && it->second.kind == LatticeKind::Constant &&
|
||||
!dynamic_cast<ConstantValue*>(inst_ptr.get())) {
|
||||
inst_ptr->ReplaceAllUsesWith(it->second.constant);
|
||||
if (inst_ptr->GetOpcode() != Opcode::Call && inst_ptr->GetOpcode() != Opcode::Load &&
|
||||
inst_ptr->GetOpcode() != Opcode::Alloca && inst_ptr->GetOpcode() != Opcode::GEP) {
|
||||
to_erase.push_back(inst_ptr.get());
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (auto* inst : to_erase) {
|
||||
block->EraseInstruction(inst);
|
||||
}
|
||||
if (auto* cond = dynamic_cast<CondBranchInst*>(block->GetTerminator())) {
|
||||
bool cond_value = false;
|
||||
auto state = GetValueState(values, cond->GetCond());
|
||||
if (AsConstBool(state, cond_value)) {
|
||||
ReplaceTerminatorWithBr(*block, cond_value ? cond->GetTrueBlock()
|
||||
: cond->GetFalseBlock());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
RebuildCFG(function);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,38 @@
|
||||
// 维护局部变量声明的注册与查找。
|
||||
|
||||
#include "sem/SymbolTable.h"
|
||||
|
||||
void SymbolTable::Add(const std::string& name,
|
||||
SysYParser::VarDefContext* decl) {
|
||||
table_[name] = decl;
|
||||
#include <stdexcept>
|
||||
|
||||
void SymbolTable::EnterScope() { scopes_.emplace_back(); }
|
||||
|
||||
void SymbolTable::ExitScope() {
|
||||
if (scopes_.empty()) {
|
||||
throw std::runtime_error("作用域栈为空,无法退出");
|
||||
}
|
||||
scopes_.pop_back();
|
||||
}
|
||||
|
||||
bool SymbolTable::Declare(const std::string& name, const SymbolInfo* symbol) {
|
||||
if (scopes_.empty()) {
|
||||
EnterScope();
|
||||
}
|
||||
auto& scope = scopes_.back();
|
||||
return scope.emplace(name, symbol).second;
|
||||
}
|
||||
|
||||
bool SymbolTable::Contains(const std::string& name) const {
|
||||
return table_.find(name) != table_.end();
|
||||
const SymbolInfo* SymbolTable::Lookup(const std::string& name) const {
|
||||
for (auto it = scopes_.rbegin(); it != scopes_.rend(); ++it) {
|
||||
auto found = it->find(name);
|
||||
if (found != it->end()) {
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SysYParser::VarDefContext* SymbolTable::Lookup(const std::string& name) const {
|
||||
auto it = table_.find(name);
|
||||
return it == table_.end() ? nullptr : it->second;
|
||||
const SymbolInfo* SymbolTable::LookupCurrent(const std::string& name) const {
|
||||
if (scopes_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto found = scopes_.back().find(name);
|
||||
return found == scopes_.back().end() ? nullptr : found->second;
|
||||
}
|
||||
|
||||
@ -1,4 +1,132 @@
|
||||
// SysY 运行库实现:
|
||||
// - 按实验/评测规范提供 I/O 等函数实现
|
||||
// - 与编译器生成的目标代码链接,支撑运行时行为
|
||||
#include "sylib.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static char input_buffer[1 << 16];
|
||||
static size_t input_pos = 0;
|
||||
static size_t input_len = 0;
|
||||
static int pushed_char = EOF;
|
||||
|
||||
static int ReadChar(void) {
|
||||
if (pushed_char != EOF) {
|
||||
int ch = pushed_char;
|
||||
pushed_char = EOF;
|
||||
return ch;
|
||||
}
|
||||
if (input_pos >= input_len) {
|
||||
input_len = fread(input_buffer, 1, sizeof(input_buffer), stdin);
|
||||
input_pos = 0;
|
||||
if (input_len == 0) {
|
||||
return EOF;
|
||||
}
|
||||
}
|
||||
return input_buffer[input_pos++];
|
||||
}
|
||||
|
||||
static void UnreadChar(int ch) {
|
||||
if (ch != EOF) {
|
||||
pushed_char = ch;
|
||||
}
|
||||
}
|
||||
|
||||
static int ReadToken(char* buffer, size_t size) {
|
||||
int ch = ReadChar();
|
||||
while (ch != EOF && isspace((unsigned char)ch)) {
|
||||
ch = ReadChar();
|
||||
}
|
||||
if (ch == EOF) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
while (ch != EOF && !isspace((unsigned char)ch)) {
|
||||
if (len + 1 < size) {
|
||||
buffer[len++] = (char)ch;
|
||||
}
|
||||
ch = ReadChar();
|
||||
}
|
||||
UnreadChar(ch);
|
||||
buffer[len] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
static float ReadFloatToken(void) {
|
||||
char buffer[128] = {0};
|
||||
if (!ReadToken(buffer, sizeof(buffer))) {
|
||||
return 0.0f;
|
||||
}
|
||||
return strtof(buffer, NULL);
|
||||
}
|
||||
|
||||
int getint(void) {
|
||||
int ch = ReadChar();
|
||||
while (ch != EOF && isspace((unsigned char)ch)) {
|
||||
ch = ReadChar();
|
||||
}
|
||||
|
||||
int sign = 1;
|
||||
if (ch == '-') {
|
||||
sign = -1;
|
||||
ch = ReadChar();
|
||||
} else if (ch == '+') {
|
||||
ch = ReadChar();
|
||||
}
|
||||
|
||||
int value = 0;
|
||||
while (ch != EOF && ch >= '0' && ch <= '9') {
|
||||
value = value * 10 + (ch - '0');
|
||||
ch = ReadChar();
|
||||
}
|
||||
UnreadChar(ch);
|
||||
return sign * value;
|
||||
}
|
||||
|
||||
int getch(void) {
|
||||
return ReadChar();
|
||||
}
|
||||
|
||||
float getfloat(void) { return ReadFloatToken(); }
|
||||
|
||||
int getarray(int a[]) {
|
||||
int n = getint();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
a[i] = getint();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int getfarray(float a[]) {
|
||||
int n = getint();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
a[i] = getfloat();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void putint(int x) { printf("%d", x); }
|
||||
|
||||
void putch(int x) { putchar(x); }
|
||||
|
||||
void putfloat(float x) { printf("%a", x); }
|
||||
|
||||
void putarray(int n, int a[]) {
|
||||
printf("%d:", n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
printf(" %d", a[i]);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
void putfarray(int n, float a[]) {
|
||||
printf("%d:", n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
printf(" %a", a[i]);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
void starttime(void) {}
|
||||
|
||||
void stoptime(void) {}
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
// SysY 运行库头文件:
|
||||
// - 声明运行库函数原型(供编译器生成 call 或链接阶段引用)
|
||||
// - 与 sylib.c 配套,按规范逐步补齐声明
|
||||
#pragma once
|
||||
|
||||
int getint(void);
|
||||
int getch(void);
|
||||
float getfloat(void);
|
||||
int getarray(int a[]);
|
||||
int getfarray(float a[]);
|
||||
|
||||
void putint(int x);
|
||||
void putch(int x);
|
||||
void putfloat(float x);
|
||||
void putarray(int n, int a[]);
|
||||
void putfarray(int n, float a[]);
|
||||
|
||||
void starttime(void);
|
||||
void stoptime(void);
|
||||
|
||||
@ -0,0 +1 @@
|
||||
3
|
||||
@ -0,0 +1,6 @@
|
||||
int a[3] = 1;
|
||||
float b[2] = 2.5;
|
||||
|
||||
int main() {
|
||||
return a[0] + a[1] + a[2] + b[0] + b[1];
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
const int a = 5 % 2.0;
|
||||
|
||||
int main() {
|
||||
return a;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
int main( {
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
int main() {
|
||||
int a = 1
|
||||
return a;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
int main() {
|
||||
else return 0;
|
||||
}
|
||||
Loading…
Reference in new issue