Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

=== nothing is not == nothing with --code-coverage=user #32579

Closed
tkf opened this issue Jul 15, 2019 · 9 comments
Closed

=== nothing is not == nothing with --code-coverage=user #32579

tkf opened this issue Jul 15, 2019 · 9 comments
Labels
compiler:optimizer Optimization passes (mostly in base/compiler/ssair/) kind:bug Indicates an unexpected problem or unintended behavior

Comments

@tkf
Copy link
Member

tkf commented Jul 15, 2019

I just have a very strange bug that cannot be reproduced locally and replacing === nothing with == nothing (as below) fixes this bug.

diff --git a/examples/primes.jl b/examples/primes.jl
index 7dac9d2f..0f194e7f 100644
--- a/examples/primes.jl
+++ b/examples/primes.jl
@@ -11,7 +11,7 @@ using Transducers

 function sieve(xf, x)
     @info "sieve(xf, x=$x)"
-    if (@show mapfoldl(xf, right, (x,), init=nothing)) === nothing
+    if (@show mapfoldl(xf, right, (x,), init=nothing)) == nothing
         @info "$x is NOT prime"
         nothing, xf
     else

See: https://travis-ci.com/tkf/Transducers.jl/builds/119090143 (Ref JuliaFolds/Transducers.jl#17)

Maybe it's relevant to #32135?

@tkf
Copy link
Member Author

tkf commented Jul 15, 2019

@StefanKarpinski
Copy link
Sponsor Member

This seems like it shouldn’t happen.

@vtjnash
Copy link
Sponsor Member

vtjnash commented Jul 15, 2019

Seems like someone may have deleted the build log for the OP?

@Keno
Copy link
Member

Keno commented Jul 15, 2019

I can reproduce this locally if I try using Run.jl as in the travis script for that package.

@Keno
Copy link
Member

Keno commented Jul 15, 2019

Looks like the difference is the --code-coverage=user flag.

@Keno
Copy link
Member

Keno commented Jul 15, 2019

Looking at the IR, I see:

23 ┄ %44  = φ (#21 => false)::Bool
│    %45  = φ (#21 => _3, #22 => nothing)::Union{Nothing, Int64}
└───        goto #25
24 ─        goto #25
25 ┄ %48  = φ (#23 => %44)::Bool
│    %49  = φ (#23 => %45, #24 => nothing)::Union{Nothing, Int64}
└───        goto #26
26 ─ %51  = (isa)(%49, Nothing)::Bool
└───        goto #28 if not %51
27 ─ %53  = π (%49, Nothing)
└───        goto #31
28 ─ %55  = (isa)(%49, Int64)::Bool
└───        goto #30 if not %55
29 ─ %57  = π (%49, Int64)
└───        goto #31
30 ─        Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└───        $(Expr(:unreachable))::Union{}
31 ┄ %61  = φ (#27 => %48, #29 => %48)::Bool

which looks like an optimizer bug, probably in type lifting.

@Keno Keno added compiler:optimizer Optimization passes (mostly in base/compiler/ssair/) kind:bug Indicates an unexpected problem or unintended behavior labels Jul 15, 2019
@tkf
Copy link
Member Author

tkf commented Jul 15, 2019

using Run.jl

Ah, I thought I've tried this... But yes, I can reproduce it with Run.test() now.

Edit: maybe the easiest way to run the script is julia --startup-file=no --project=test --code-coverage=user examples/primes.jl

@tkf tkf changed the title === nothing is not == nothing (only in Travis) === nothing is not == nothing with --code-coverage=user Jul 15, 2019
@tkoolen
Copy link
Contributor

tkoolen commented Jul 16, 2019

Maybe related to #30872?

Keno added a commit that referenced this issue Jul 17, 2019
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
}
```
Keno added a commit that referenced this issue Jul 17, 2019
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
}
```
@Keno Keno closed this as completed in 0a12944 Jul 17, 2019
KristofferC pushed a commit that referenced this issue Jul 20, 2019
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)
KristofferC pushed a commit that referenced this issue Aug 26, 2019
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)
KristofferC pushed a commit that referenced this issue Aug 26, 2019
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)
@KristofferC
Copy link
Sponsor Member

Ref #32135 (comment) for another instance of this

KristofferC pushed a commit that referenced this issue Feb 20, 2020
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:optimizer Optimization passes (mostly in base/compiler/ssair/) kind:bug Indicates an unexpected problem or unintended behavior
Projects
None yet
Development

No branches or pull requests

6 participants