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

inference: enable constant propagation for union-split signatures #39305

Merged
merged 2 commits into from
Mar 10, 2021

Conversation

aviatesk
Copy link
Member

@aviatesk aviatesk commented Jan 18, 2021

The inference precision of certain functions really relies on constant
propagation, but currently constant prop' won't happen when a call
signature is union split and so sometimes inference ends up looser
return type: e.g.

julia>
Base.return_types((Union{Tuple{Int,Nothing},Tuple{Int,Missing}},)) do t
           a, b = t
           a # I expected a::Int, but a::Union{Missing,Nothing,Int}
       end |> first Union{Missing, Nothing, Int64}

This PR:

  • enables constant prop' for each union signatures, by calling
    abstract_call_method_with_const_args just after each
    abstract_call_method
  • refactor abstract_call_method_with_const_args into two separate
    parts, 1.) heuristics to decide whether to do constant prop', 2.) try
    constant propagation

The added test cases will should showcase the cases where the
inference result could be improved by that.


I've not seen notable regression in latency with this PR.
Here is a sample benchmark of the impact of this PR on latency,
from which I guess this PR is acceptable ?

build time: master (caeacef)

Sysimage built. Summary:
Total ───────  61.615938 seconds
Base: ───────  26.575732 seconds 43.1313%
Stdlibs: ────  35.038024 seconds 56.8652%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1378/1378
Precompilation complete. Summary:
Total ─────── 116.417013 seconds
Generation ──  81.077365 seconds 69.6439%
Execution ───  35.339648 seconds 30.3561%
    LINK usr/lib/julia/sys.dylib

build time: this PR

Stdlibs total  ──── 34.077962 seconds
Sysimage built. Summary:
Total ───────  61.804573 seconds
Base: ───────  27.724077 seconds 44.8576%
Stdlibs: ────  34.077962 seconds 55.1383%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1362/1362
Precompilation complete. Summary:
Total ─────── 111.262672 seconds
Generation ──  83.535305 seconds 75.0794%
Execution ───  27.727367 seconds 24.9206%
    LINK usr/lib/julia/sys.dylib

first time to plot: master (caeacef)

julia> using Plots; @time plot(rand(10,3))
  3.614168 seconds (5.47 M allocations: 324.564 MiB, 5.73% gc time,
53.02% compilation time)

first time to plot: this PR

julia> using Plots; @time plot(rand(10,3))
  3.557919 seconds (5.53 M allocations: 328.812 MiB, 2.89% gc time,
51.94% compilation time)

@KristofferC
Copy link
Member

Related to #37637? (Also ref #37610).

@aviatesk
Copy link
Member Author

aviatesk commented Jan 18, 2021

Related to #37637? (Also ref #37610).

Ah yeah, I haven't noticed there's already an attempt.

@JeffBezanson
Copy link
Member

Thanks for working on this. I think we should refactor this code even more. Specifically, I would try to use a single loop over the applicable methods, and for each signature also try constant propagation if it might be profitable --- i.e. add a call to abstract_call_method_with_const_args for each call to abstract_call_method (with appropriate guards to skip it when possible).

@aviatesk

This comment has been minimized.

@JeffBezanson
Copy link
Member

Very nice! I think this is basically the right design.

@aviatesk
Copy link
Member Author

aviatesk commented Feb 6, 2021

Merged #38905 into this branch to see if it works w/o problem.

@nanosoldier runbenchmarks(ALL, vs = ":master")

@nanosoldier
Copy link
Collaborator

Something went wrong when running your job:

ProcessExitedException(2)

cc @christopher-dG

@aviatesk
Copy link
Member Author

I'd like to push this forward without waiting for #38905 to be merged; this could be reviewed as is.
/cc @JeffBezanson @Keno @vtjnash

aviatesk added a commit to aviatesk/JET.jl that referenced this pull request Feb 10, 2021
Eliminates the hacky overloads with the latest compiler interfaces.
Particularly we will use:
- [x] the bail out interfaces: 
<JuliaLang/julia#39439>
- [ ] new constant prop' infrastructures: 
<JuliaLang/julia#39305>

The previous code will be kept into `src/legacy` directory to keep the
v1.6 compatibility.
@aviatesk
Copy link
Member Author

nanosoldier results looked reasonable.

Some of the benchmark results show a certain regression:

ID time ratio memory ratio
["array", "index", "(\"sumelt\", \"BaseBenchmarks.ArrayBenchmarks.ArrayLSLS{Int32, 2}\")"] 1.85 (50%) ❌ 1.00 (1%)
["array", "index", "(\"sumelt_boundscheck\", \"100000:-1:1\")"] 2.00 (50%) ❌ 1.00 (1%)

And so I tried to repro that on my machine with something like this:

# ... BaseBenchmarks.jl definitions

for (A, str) in (arrays_iter..., ranges_iter...)
    if isa(A, ArrayLSLS{Int32, 2})
        @info "sumelt" str
        @btime perf_sumelt($A)
    end

    if A == 100000:-1:1
        @info "sumelt_boundscheck" str
        @btime perf_sumelt_boundscheck($A)
    end
end

and I couldn't find any effective performance difference:

on master

┌ Info: sumelt
└   str = "ArrayLSLS{Int32, 2}"
  251.105 μs (0 allocations: 0 bytes)
┌ Info: sumelt_boundscheck
└   str = "100000:-1:1"
  52.773 μs (0 allocations: 0 bytes)

on this PR

┌ Info: sumelt
└   str = "ArrayLSLS{Int32, 2}"
  250.922 μs (0 allocations: 0 bytes)
┌ Info: sumelt_boundscheck
└   str = "100000:-1:1"
  55.715 μs (0 allocations: 0 bytes)

My question is, how we should interpret nanosoldier benchmark results ? Is there any threshold that indicates a real performance regression ?

@vtjnash vtjnash merged commit 1f21f2d into JuliaLang:master Mar 10, 2021
@vtjnash
Copy link
Member

vtjnash commented Mar 10, 2021

No, computers aren't really that predictable, so it provides guidance, then you may have to check them yourself to confirm

@aviatesk aviatesk deleted the moreconstantprop branch March 10, 2021 23:15
aviatesk added a commit to aviatesk/JET.jl that referenced this pull request Mar 11, 2021
JuliaLang/julia#39305 is merged, and now we can
really clean up our `abstractinterpretation.jl`.
The legacy hacky overloads will be kept in 
`src/legacy/abstractinterpretation.jl`
for the compatbility with Julia v1.6.

JET benchmark will also show the impact analysis of constant prop' on
union-split signatures on JET analysis.
aviatesk added a commit to aviatesk/JET.jl that referenced this pull request Mar 11, 2021
* eliminates hacky overloads !

JuliaLang/julia#39305 is merged, and now we can
really clean up our `abstractinterpretation.jl`.
The legacy hacky overloads will be kept in 
`src/legacy/abstractinterpretation.jl`
for the compatbility with Julia v1.6.

JET benchmark will also show the impact analysis of constant prop' on
union-split signatures on JET analysis.

* add `at-doc` annotations

* styling tweaks

* codecov tweak
aviatesk added a commit to aviatesk/Cthulhu.jl that referenced this pull request Mar 14, 2021
… callsites

I think this is the proper update to 
JuliaLang/julia#39305.
We can use `ConstPropCallInfo` only for the successful constant-prop'ed
result and just keep using the usual `MICallInfo` for successful one,
because each of `ConstCallInfo.results` should correspond to each 
callsite
obtained from 
`ConstCallInfo.call::Union{MethodMatchInfo,UnionSplitInfo}`.

Basic tests are also added.
ElOceanografo pushed a commit to ElOceanografo/julia that referenced this pull request May 4, 2021
…liaLang#39305)

The inference precision of certain functions really relies on constant
propagation, but currently constant prop' won't happen when a call
signature is union split and so sometimes inference ends up looser
return type: e.g.
```julia
julia>
Base.return_types((Union{Tuple{Int,Nothing},Tuple{Int,Missing}},)) do t
           a, b = t
           a # I expected a::Int, but a::Union{Missing,Nothing,Int}
       end |> first Union{Missing, Nothing, Int64}
```

This PR:
- enables constant prop' for each union signatures, by calling
  `abstract_call_method_with_const_args` just after each
  `abstract_call_method`
- refactor `abstract_call_method_with_const_args` into two separate
  parts, 1.) heuristics to decide whether to do constant prop', 2.) try
  constant propagation
The added test cases will should showcase the cases where the
inference result could be improved by that.

---

I've not seen notable regression in latency with this PR.
Here is a sample benchmark of the impact of this PR on latency,
from which I guess this PR is acceptable ?

> build time: master (caeacef)
```bash
Sysimage built. Summary:
Total ───────  61.615938 seconds
Base: ───────  26.575732 seconds 43.1313%
Stdlibs: ────  35.038024 seconds 56.8652%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1378/1378
Precompilation complete. Summary:
Total ─────── 116.417013 seconds
Generation ──  81.077365 seconds 69.6439%
Execution ───  35.339648 seconds 30.3561%
    LINK usr/lib/julia/sys.dylib
```
> build time: this PR
```bash
Stdlibs total  ──── 34.077962 seconds
Sysimage built. Summary:
Total ───────  61.804573 seconds
Base: ───────  27.724077 seconds 44.8576%
Stdlibs: ────  34.077962 seconds 55.1383%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1362/1362
Precompilation complete. Summary:
Total ─────── 111.262672 seconds
Generation ──  83.535305 seconds 75.0794%
Execution ───  27.727367 seconds 24.9206%
    LINK usr/lib/julia/sys.dylib
```

> first time to plot: master (caeacef)
```julia
julia> using Plots; @time plot(rand(10,3))
  3.614168 seconds (5.47 M allocations: 324.564 MiB, 5.73% gc time,
53.02% compilation time)
```
> first time to plot: this PR
```julia
julia> using Plots; @time plot(rand(10,3))
  3.557919 seconds (5.53 M allocations: 328.812 MiB, 2.89% gc time,
51.94% compilation time)
```

---

- fixes JuliaLang#37610
- some part of this code was taken from JuliaLang#37637
- this PR is originally supposed to be alternative and more generalized
  version of JuliaLang#39296
antoine-levitt pushed a commit to antoine-levitt/julia that referenced this pull request May 9, 2021
…liaLang#39305)

The inference precision of certain functions really relies on constant
propagation, but currently constant prop' won't happen when a call
signature is union split and so sometimes inference ends up looser
return type: e.g.
```julia
julia>
Base.return_types((Union{Tuple{Int,Nothing},Tuple{Int,Missing}},)) do t
           a, b = t
           a # I expected a::Int, but a::Union{Missing,Nothing,Int}
       end |> first Union{Missing, Nothing, Int64}
```

This PR:
- enables constant prop' for each union signatures, by calling
  `abstract_call_method_with_const_args` just after each
  `abstract_call_method`
- refactor `abstract_call_method_with_const_args` into two separate
  parts, 1.) heuristics to decide whether to do constant prop', 2.) try
  constant propagation
The added test cases will should showcase the cases where the
inference result could be improved by that.

---

I've not seen notable regression in latency with this PR.
Here is a sample benchmark of the impact of this PR on latency,
from which I guess this PR is acceptable ?

> build time: master (caeacef)
```bash
Sysimage built. Summary:
Total ───────  61.615938 seconds
Base: ───────  26.575732 seconds 43.1313%
Stdlibs: ────  35.038024 seconds 56.8652%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1378/1378
Precompilation complete. Summary:
Total ─────── 116.417013 seconds
Generation ──  81.077365 seconds 69.6439%
Execution ───  35.339648 seconds 30.3561%
    LINK usr/lib/julia/sys.dylib
```
> build time: this PR
```bash
Stdlibs total  ──── 34.077962 seconds
Sysimage built. Summary:
Total ───────  61.804573 seconds
Base: ───────  27.724077 seconds 44.8576%
Stdlibs: ────  34.077962 seconds 55.1383%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 30/30
Executing precompile statements... 1362/1362
Precompilation complete. Summary:
Total ─────── 111.262672 seconds
Generation ──  83.535305 seconds 75.0794%
Execution ───  27.727367 seconds 24.9206%
    LINK usr/lib/julia/sys.dylib
```

> first time to plot: master (caeacef)
```julia
julia> using Plots; @time plot(rand(10,3))
  3.614168 seconds (5.47 M allocations: 324.564 MiB, 5.73% gc time,
53.02% compilation time)
```
> first time to plot: this PR
```julia
julia> using Plots; @time plot(rand(10,3))
  3.557919 seconds (5.53 M allocations: 328.812 MiB, 2.89% gc time,
51.94% compilation time)
```

---

- fixes JuliaLang#37610
- some part of this code was taken from JuliaLang#37637
- this PR is originally supposed to be alternative and more generalized
  version of JuliaLang#39296
aviatesk added a commit that referenced this pull request Oct 22, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 24, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 25, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 25, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 26, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 26, 2021
This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 26, 2021
…st-prop'ed sources

This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
aviatesk added a commit that referenced this pull request Oct 26, 2021
…st-prop'ed sources

This commit complements #39754 and #39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
N5N3 added a commit to N5N3/julia that referenced this pull request Oct 29, 2021
commit c054dbc
Author: Shuhei Kadowaki <[email protected]>
Date:   Fri Oct 29 01:31:55 2021 +0900

    optimizer: eliminate allocations (JuliaLang#42833)

commit 6a9737d
Author: Jeff Bezanson <[email protected]>
Date:   Thu Oct 28 12:23:53 2021 -0400

    fix JuliaLang#42659, move `jl_coverage_visit_line` to runtime library (JuliaLang#42810)

commit c762f10
Author: Marc Ittel <[email protected]>
Date:   Thu Oct 28 12:19:13 2021 +0200

    change `julia` to `julia-repl` in docstrings (JuliaLang#42824)

    Co-authored-by: Michael Abbott <[email protected]>

commit 9f52ec0
Author: Dilum Aluthge <[email protected]>
Date:   Thu Oct 28 05:30:11 2021 -0400

    CI (Buildkite): Update all rootfs images to the latest versions (JuliaLang#42802)

    * CI (Buildkite): Update all rootfs images to the latest versions

    * Re-sign all of the signed pipelines

commit 404e584
Author: DilumAluthgeBot <[email protected]>
Date:   Wed Oct 27 21:11:04 2021 -0400

    🤖 Bump the Statistics stdlib from 74897fe to 5256d57 (JuliaLang#42826)

    Co-authored-by: Dilum Aluthge <[email protected]>

commit c74814e
Author: Jeff Bezanson <[email protected]>
Date:   Wed Oct 27 16:34:46 2021 -0400

    reset `RandomDevice` file from `__init__` (JuliaLang#42537)

    This prevents us from seeing an invalid `IOStream` object from a saved
    system image, and also ensures the files are opened once for all
    threads.

commit 05ed348
Author: Jeff Bezanson <[email protected]>
Date:   Wed Oct 27 15:24:17 2021 -0400

    only visit nonfunction_mt once when traversing method tables (JuliaLang#42821)

commit d71b77d
Author: DilumAluthgeBot <[email protected]>
Date:   Tue Oct 26 20:39:08 2021 -0400

    🤖 Bump the Downloads stdlib from 5f1509d to dbb0625 (JuliaLang#42811)

    Co-authored-by: Dilum Aluthge <[email protected]>

commit b4fddc1
Author: DilumAluthgeBot <[email protected]>
Date:   Tue Oct 26 14:46:20 2021 -0400

    🤖 Bump the Pkg stdlib from bc32103f to 26918395 (JuliaLang#42806)

    Co-authored-by: Dilum Aluthge <[email protected]>

commit 6a386de
Author: Dilum Aluthge <[email protected]>
Date:   Tue Oct 26 12:15:51 2021 -0400

    CI (Buildkite): make sure to hit ignore any unencrypted repo keys, regardless of where they are located in the repository (JuliaLang#42803)

commit 021a6b5
Author: Shuhei Kadowaki <[email protected]>
Date:   Wed Oct 27 01:08:33 2021 +0900

    optimizer: clean up inlining test code (JuliaLang#42804)

commit 16eb196
Merge: 21ebabf 1510eaa
Author: Shuhei Kadowaki <[email protected]>
Date:   Tue Oct 26 23:25:41 2021 +0900

    Merge pull request JuliaLang#42766 from JuliaLang/avi/42754

    optimizer: fix JuliaLang#42754, inline union-split const-prop'ed sources

commit 21ebabf
Author: Kristoffer Carlsson <[email protected]>
Date:   Tue Oct 26 16:11:32 2021 +0200

    simplify code loading test now that TOML files are parsed with a real TOML parser (JuliaLang#42328)

commit 1510eaa
Author: Shuhei Kadowaki <[email protected]>
Date:   Mon Oct 25 01:35:12 2021 +0900

    optimizer: fix JuliaLang#42754, inline union-split const-prop'ed sources

    This commit complements JuliaLang#39754 and JuliaLang#39305: implements a logic to use
    constant-prop'ed results for inlining at union-split callsite.
    Currently it works only for cases when constant-prop' succeeded for all
    (union-split) signatures.

    > example
    ```julia
    julia> mutable struct X
               # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
               a::Union{Nothing, Int}
               b::Symbol
           end;

    julia> code_typed((X, Union{Nothing,Int})) do x, a
               # this `setproperty` call would be union-split and constant-prop will happen for
               # each signature: inlining would fail if we don't use constant-prop'ed source
               # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
               # end up very high if we don't propagate `sym::Const(:a)`
               x.a = a
               x
           end |> only |> first
    ```

    > before this commit
    ```julia
    CodeInfo(
    1 ─ %1 = Base.setproperty!::typeof(setproperty!)
    │   %2 = (isa)(a, Nothing)::Bool
    └──      goto #3 if not %2
    2 ─ %4 = π (a, Nothing)
    │        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
    └──      goto #6
    3 ─ %7 = (isa)(a, Int64)::Bool
    └──      goto #5 if not %7
    4 ─ %9 = π (a, Int64)
    │        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
    └──      goto #6
    5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
    └──      unreachable
    6 ┄      return x
    )
    ```

    > after this commit
    ```julia
    CodeInfo(
    1 ─ %1 = (isa)(a, Nothing)::Bool
    └──      goto #3 if not %1
    2 ─      Base.setfield!(x, :a, nothing)::Nothing
    └──      goto #6
    3 ─ %5 = (isa)(a, Int64)::Bool
    └──      goto #5 if not %5
    4 ─ %7 = π (a, Int64)
    │        Base.setfield!(x, :a, %7)::Int64
    └──      goto #6
    5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
    └──      unreachable
    6 ┄      return x
    )
    ```

commit 4c3ae20
Author: Chris Foster <[email protected]>
Date:   Tue Oct 26 21:48:32 2021 +1000

    Make Base.ifelse a generic function (JuliaLang#37343)

    Allow user code to directly extend `Base.ifelse` rather than needing a
    special package for it.

commit 2e388e3
Author: Shuhei Kadowaki <[email protected]>
Date:   Mon Oct 25 01:30:09 2021 +0900

    optimizer: eliminate excessive specialization in inlining code

    This commit includes several code quality improvements in inlining code:
    - eliminate excessive specializations around:
      * `item::Pair{Any, Any}` constructions
      * iterations on `Vector{Pair{Any, Any}}`
    - replace `Pair{Any, Any}` with new, more explicit data type `InliningCase`
    - remove dead code
@timholy
Copy link
Member

timholy commented Dec 5, 2021

git bisect identified this PR as the source of #43287

LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Feb 22, 2022
This commit complements JuliaLang#39754 and JuliaLang#39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Mar 8, 2022
This commit complements JuliaLang#39754 and JuliaLang#39305: implements a logic to use
constant-prop'ed results for inlining at union-split callsite.
Currently it works only for cases when constant-prop' succeeded for all
(union-split) signatures.

> example
```julia
julia> mutable struct X
           # NOTE in order to confuse `fieldtype_tfunc`, we need to have at least two fields with different types
           a::Union{Nothing, Int}
           b::Symbol
       end;

julia> code_typed((X, Union{Nothing,Int})) do x, a
           # this `setproperty` call would be union-split and constant-prop will happen for
           # each signature: inlining would fail if we don't use constant-prop'ed source
           # since the approximated inlining cost of `convert(fieldtype(X, sym), a)` would
           # end up very high if we don't propagate `sym::Const(:a)`
           x.a = a
           x
       end |> only |> first
```

> before this commit
```julia
CodeInfo(
1 ─ %1 = Base.setproperty!::typeof(setproperty!)
│   %2 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %2
2 ─ %4 = π (a, Nothing)
│        invoke %1(_2::X, 🅰️:Symbol, %4::Nothing)::Any
└──      goto #6
3 ─ %7 = (isa)(a, Int64)::Bool
└──      goto #5 if not %7
4 ─ %9 = π (a, Int64)
│        invoke %1(_2::X, 🅰️:Symbol, %9::Int64)::Any
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```

> after this commit
```julia
CodeInfo(
1 ─ %1 = (isa)(a, Nothing)::Bool
└──      goto #3 if not %1
2 ─      Base.setfield!(x, :a, nothing)::Nothing
└──      goto #6
3 ─ %5 = (isa)(a, Int64)::Bool
└──      goto #5 if not %5
4 ─ %7 = π (a, Int64)
│        Base.setfield!(x, :a, %7)::Int64
└──      goto #6
5 ─      Core.throw(ErrorException("fatal error in type inference (type bound)"))::Union{}
└──      unreachable
6 ┄      return x
)
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:inference Type inference needs nanosoldier run This PR should have benchmarks run on it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Imprecise inference when iterating a Pair with constant first type but varying second type.
6 participants