Skip to content

Commit b6c2a59

Browse files
KenoKristofferC
authored andcommitted
Fix #32579 - Issue in typeconstraint accumulation (#32605)
The bug here is a bit subtle, but perhaps best illustrated with the included test case: ``` function f32579(x::Int64, b::Bool) if b x = nothing end if isa(x, Int64) y = x else y = x end if isa(y, Nothing) z = y else z = y end return z === nothing end ``` The code just after SSA conversion looks like: ``` 2 1 ─ goto #3 if not _3 3 2 ─ %2 = Main.nothing::Core.Compiler.Const(nothing, false) 5 3 ┄ %3 = φ (#2 => %2, #1 => _2)::Union{Nothing, Int64} │ %4 = (%3 isa Main.Int64)::Bool └── goto #5 if not %4 6 4 ─ %6 = π (%3, Int64) └── goto #6 8 5 ─ %8 = π (%3, Nothing) 10 6 ┄ %9 = φ (#4 => %6, #5 => %8)::Union{Nothing, Int64} │ %10 = (%9 isa Main.Nothing)::Bool └── goto #8 if not %10 11 7 ─ %12 = π (%9, Nothing) └── goto #9 13 8 ─ %14 = π (%9, Int64) 15 9 ┄ %15 = φ (#7 => %12, #8 => %14)::Union{Nothing, Int64} │ %16 = (%15 === Main.nothing)::Bool └── return %16 ``` Now, we have special code in SROA (despite it not really being an SROA transform) that looks at `===` and replaces it by a nest of phis of booleans. The reasoning for this transform is that it eliminates a use of a value where we only care about the type and not the content, thus making it more likely that the value will subsequently be eligible for SROA. In addition, while it goes along resolving which values feed into any particular phi, it accumulates and type conditions it encounters along the way. Thus in the example above, something like the following happens: - We look at %14, which πs to %9 with an Int64 constraint, so we only consider the #4 predecessor for %9 (due to the constraint), until we get to %3, where we again only consider the #1 predecessor, where we find the argument (of type Int64) and conclude the result is always false - Now we pop the next item of the stack from our original phi, look at %12, which πs to %9 with a Nothing constraint. At this point we used to terminate the search because we already looked at %9. However, crucially, we looked at %9 only with an Int64 constraint, so we missed the fact that `nothing` was in fact a possible value for this phi. The result was a missing entry in the generated phi node: ``` 1 ─ goto #3 if not b 2 ─ %2 = Main.nothing::Core.Compiler.Const(nothing, false) 3 ┄ %3 = φ (#1 => false)::Bool │ %4 = φ (#2 => %2, #1 => _2)::Union{Nothing, Int64} │ %5 = (%4 isa Main.Int64)::Bool └── goto #5 if not %5 4 ─ %7 = π (%4, Int64) └── goto #6 5 ─ %9 = π (%4, Nothing) 6 ┄ %10 = φ (#4 => %3, #5 => %3)::Bool │ %11 = φ (#4 => %7, #5 => %9)::Union{Nothing, Int64} │ %12 = (%11 isa Main.Nothing)::Bool └── goto #8 if not %12 7 ─ goto #9 8 ─ nothing::Nothing 9 ┄ %16 = φ (#7 => %10, #8 => %10)::Bool └── return %16 ``` (note the missing #2 predecessor in phi node %3), which would result in an undefined value at runtime, though in this case LLVM would have taken advantage of that to just return 0: ``` define i8 @julia_f32579_16051(i64, i8) { top: ; @ REPL[1]:15 within `f32579' ret i8 0 } ``` Compare this now to the optimized IR with this patch: ``` 1 ─ goto #3 if not b 2 ─ %2 = Main.nothing::Core.Compiler.Const(nothing, false) 3 ┄ %3 = φ (#2 => true, #1 => false)::Bool │ %4 = φ (#2 => %2, #1 => _2)::Union{Nothing, Int64} │ %5 = (%4 isa Main.Int64)::Bool └── goto #5 if not %5 4 ─ %7 = π (%4, Int64) └── goto #6 5 ─ %9 = π (%4, Nothing) 6 ┄ %10 = φ (#4 => %3, #5 => %3)::Bool │ %11 = φ (#4 => %7, #5 => %9)::Union{Nothing, Int64} │ %12 = (%11 isa Main.Nothing)::Bool └── goto #8 if not %12 7 ─ goto #9 8 ─ nothing::Nothing 9 ┄ %16 = φ (#7 => %10, #8 => %10)::Bool └── return %16 ``` The %3 phi node has its missing entry and the generated LLVM code correctly returns `b`: ``` define i8 @julia_f32579_16112(i64, i8) { top: %2 = and i8 %1, 1 ; @ REPL[1]:15 within `f32579' ret i8 %2 } ``` (cherry picked from commit 0a12944)
1 parent 5283847 commit b6c2a59

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

base/compiler/ssair/passes.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
178178
found_def = false
179179
## Track which PhiNodes, SSAValue intermediaries
180180
## we forwarded through.
181-
visited = IdSet{Any}()
181+
visited = IdDict{Any, Any}()
182182
worklist_defs = Any[]
183183
worklist_constraints = Any[]
184184
leaves = Any[]
@@ -187,7 +187,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
187187
while !isempty(worklist_defs)
188188
defssa = pop!(worklist_defs)
189189
typeconstraint = pop!(worklist_constraints)
190-
push!(visited, defssa)
190+
visited[defssa] = typeconstraint
191191
def = compact[defssa]
192192
if isa(def, PhiNode)
193193
push!(visited_phinodes, defssa)
@@ -211,9 +211,15 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
211211
if isa(val, AnySSAValue)
212212
new_def, new_constraint = simple_walk_constraint(compact, val, typeconstraint)
213213
if isa(new_def, AnySSAValue)
214-
if !(new_def in visited)
214+
if !haskey(visited, new_def)
215215
push!(worklist_defs, new_def)
216216
push!(worklist_constraints, new_constraint)
217+
elseif !(new_constraint <: visited[new_def])
218+
# We have reached the same definition via a different
219+
# path, with a different type constraint. We may have
220+
# to redo some work here with the wider typeconstraint
221+
push!(worklist_defs, new_def)
222+
push!(worklist_constraints, tmerge(new_constraint, visited[new_def]))
217223
end
218224
continue
219225
end

test/compiler/ssair.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,23 @@ let
9696
Meta.isexpr(ex, :meta)
9797
end
9898
end
99+
100+
# Issue #32579 - Optimizer bug involving type constraints
101+
function f32579(x::Int, b::Bool)
102+
if b
103+
x = nothing
104+
end
105+
if isa(x, Int)
106+
y = x
107+
else
108+
y = x
109+
end
110+
if isa(y, Nothing)
111+
z = y
112+
else
113+
z = y
114+
end
115+
return z === nothing
116+
end
117+
@test f32579(0, true) === true
118+
@test f32579(0, false) === false

0 commit comments

Comments
 (0)