ITADN

Optimizer hangs in `kill_edge_terminator!`: `stmt === nothing && continue` never advances `idx`

#62818Openkirked 创建于 15 小时前
K
kirkedcommented
Certain functions make `adce_pass!` spin forever inside `kill_edge_terminator!`. The hang is at **compile time** — `code_typed` alone reproduces it, no execution needed — and there is no diagnostic of any kind; the process simply never returns. ## Reproducer ```julia function f(c) r = Ref(false) while true if c !== nothing r[] = true end if r[] continue else break end end end f(nothing) # never returns ``` `code_typed(f, (Nothing,))` and `precompile(f, (Nothing,))` hang identically, so this is purely an optimizer issue. With `c::Nothing`, `c !== nothing` is statically false, so `r[]` is provably `false` and the loop should `break` on its first iteration — `f(nothing)` should return immediately. ## Affected versions | Version | Result | |---|---| | 1.7.2 | ok | | 1.10.12 | ok | | 1.11.0 | **hangs** | | 1.11.9 | **hangs** | | 1.12.7 | **hangs** | | 1.13.0-rc3 | **hangs** | Reproduced on official macOS aarch64 binaries (1.10.12, 1.11.0, 1.11.9, 1.13.0-rc3). ``` Julia Version 1.13.0-rc3 Commit a861d5fe286 (2026-08-13 18:20 UTC) Build Info: Official https://julialang.org release Platform Info: OS: macOS (arm64-apple-darwin25.5.0) CPU: 8 × Apple M1 Pro WORD_SIZE: 64 LLVM: libLLVM-20.1.8 (ORCJIT, apple-m1) GC: Built with stock GC Threads: 1 default, 1 interactive, 1 GC (on 6 virtual cores) ``` ## Backtrace (SIGTERM into the hung process, 1.13.0-rc3) ``` size at ./essentials.jl:10 [inlined] length at ./essentials.jl:11 [inlined] getindex at ./../usr/share/julia/Compiler/src/ssair/ir.jl:318 [inlined] kill_edge_terminator! at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1440 kill_edge_terminator! at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1413 kill_edge_terminator! at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1413 process_node! at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1524 iterate_compact at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1964 iterate at ./../usr/share/julia/Compiler/src/ssair/ir.jl:1886 [inlined] adce_pass! at ./../usr/share/julia/Compiler/src/ssair/passes.jl:2161 run_passes_ipo_safe at ./../usr/share/julia/Compiler/src/optimize.jl:1049 run_passes_ipo_safe at ./../usr/share/julia/Compiler/src/optimize.jl:1062 [inlined] optimize at ./../usr/share/julia/Compiler/src/optimize.jl:1035 finish_nocycle at ./../usr/share/julia/Compiler/src/typeinfer.jl:231 ``` ## Root cause `Compiler/src/ssair/ir.jl`, lines 1432–1449 on 1.13.0-rc3: ```julia # Remove this edge from all phi nodes in `to` block # NOTE: It is possible for `to` to contain only `nothing` statements, # so we must be careful to stop at its last statement if to <= active_bb bb = result_bbs[bb_rename_succ[to]] stmts = compacted_stmt_range(compact, bb, active_bb, to) idx = first(stmts) while idx <= last(stmts) stmt = compact.result[idx][:stmt] stmt === nothing && continue # <-- never advances `idx` isa(stmt, PhiNode) || break i = findfirst(x::Int32->x==bb_rename_pred[from], stmt.edges) if i !== nothing deleteat!(stmt.edges, i) deleteat!(stmt.values, i) end idx += 1 end ``` Line 1441 re-enters the `while` without incrementing `idx`, so the first `nothing` statement in the block makes the loop spin on that index forever. Sampling the backtrace of the hung process repeatedly lands on lines 1439, 1440 and 1441 — the only three lines of this loop — which pins the spin here rather than in the recursion at 1413. The comment immediately above the loop anticipates precisely the `nothing`-statement case that the guard mishandles. The apparent fix is to advance the index when skipping: ```julia if stmt === nothing idx += 1 continue end ``` ## Notes - The identical `stmt === nothing && continue` is present in 1.10.12 (`base/compiler/ssair/ir.jl:1234`) and 1.11 (`ir.jl:1322`), so the spin itself is a long-standing latent bug. What changed in 1.11 is that this path now gets reached with a leading `nothing` statement for the shape above; pinning down which 1.10→1.11 optimizer change routes here would need a bisect. - #60697 widened the guard on this branch from `to < active_bb` to `to <= active_bb` for 1.13, so the loop is reachable in strictly more cases there. - Variations, in case they help characterize the triggering IR shape: - a plain (unboxed) local `Bool` flag instead of `Ref{Bool}` — no hang - `Ref{Int}` with `if r[] != 0` — no hang - `if r[] == true` instead of `if r[]` — no hang; branching on the loaded `Bool` directly appears to be required - a `mutable struct` field of type `Bool` in place of `Ref` — still hangs, so this is not `Ref`-specific - `-O0` still hangs. `--compile=min` avoids it.
0 条评论