Skip to content

Commit

Permalink
Fix #32579 - Issue in typeconstraint accumulation (#32605)
Browse files Browse the repository at this point in the history
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
}
```
  • Loading branch information
Keno committed Jul 17, 2019
1 parent 261e2b9 commit 0a12944
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 3 deletions.
12 changes: 9 additions & 3 deletions base/compiler/ssair/passes.jl
Expand Up @@ -178,7 +178,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
found_def = false
## Track which PhiNodes, SSAValue intermediaries
## we forwarded through.
visited = IdSet{Any}()
visited = IdDict{Any, Any}()
worklist_defs = Any[]
worklist_constraints = Any[]
leaves = Any[]
Expand All @@ -187,7 +187,7 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
while !isempty(worklist_defs)
defssa = pop!(worklist_defs)
typeconstraint = pop!(worklist_constraints)
push!(visited, defssa)
visited[defssa] = typeconstraint
def = compact[defssa]
if isa(def, PhiNode)
push!(visited_phinodes, defssa)
Expand All @@ -211,9 +211,15 @@ function walk_to_defs(compact::IncrementalCompact, @nospecialize(defssa), @nospe
if isa(val, AnySSAValue)
new_def, new_constraint = simple_walk_constraint(compact, val, typeconstraint)
if isa(new_def, AnySSAValue)
if !(new_def in visited)
if !haskey(visited, new_def)
push!(worklist_defs, new_def)
push!(worklist_constraints, new_constraint)
elseif !(new_constraint <: visited[new_def])
# We have reached the same definition via a different
# path, with a different type constraint. We may have
# to redo some work here with the wider typeconstraint
push!(worklist_defs, new_def)
push!(worklist_constraints, tmerge(new_constraint, visited[new_def]))
end
continue
end
Expand Down
20 changes: 20 additions & 0 deletions test/compiler/ssair.jl
Expand Up @@ -96,3 +96,23 @@ let
Meta.isexpr(ex, :meta)
end
end

# Issue #32579 - Optimizer bug involving type constraints
function f32579(x::Int, b::Bool)
if b
x = nothing
end
if isa(x, Int)
y = x
else
y = x
end
if isa(y, Nothing)
z = y
else
z = y
end
return z === nothing
end
@test f32579(0, true) === true
@test f32579(0, false) === false

2 comments on commit 0a12944

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

Please sign in to comment.