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: inter-procedural conditional constraint back-propagation #38905

Merged
merged 3 commits into from
Feb 26, 2021

Conversation

aviatesk
Copy link
Member

@aviatesk aviatesk commented Dec 16, 2020

This PR propagates Conditionals inter-procedurally when a
Conditional at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
Conditional, it will be converted into InterConditional object,
which is implemented in Core and can be directly put into the global
cache. Finally after going back to caller frame, InterConditional will
be re-converted into Conditional in the context of the caller frame.

improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:

isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]

(and now we don't need something like #38636)

benchmarks

A compile time comparison:

on the current master (82d79ce)

Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib

on this PR (37e279b)

Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib

Here is a sample code that benefits from this PR:

function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@btime summer($(ary))

on the current master (82d79ce)

❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)

on this PR (37e279b)

❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)

caveats

Within the Conditional/InterConditional framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
::Expr constraint to be imposed on the first argument of
Meta.isexpr, but the current heuristic ends up picking up a constraint
on the second argument (i.e. ex.head === head).

isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]

I think We can get rid of this limitation by extending Conditional and
InterConditional
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

EDIT: 3df027a implements the "pick up" logic within a caller
(i.e. within abstract_call_gf_by_type), which allows us to choose a
constraint more appropriately, and now the Meta.isexpr case is fixed.
Still there is a limitation of multiple conditional constraint
back-propagation; the following broken test case will explain the future
work.

is_int_and_int(a, b) = isa(a, Int) && isa(b, Int)
@test_broken Base.return_types((Any,Any)) do a, b
    is_int_and_int(a, b) && return a, b # (a::Int, b::Int) ideally, but (a::Any, b::Int)
    0, 0
end == Any[Tuple{Int,Int}]

@aviatesk aviatesk requested review from vtjnash, Keno and JeffBezanson and removed request for vtjnash, JeffBezanson and Keno December 16, 2020 11:41
@aviatesk aviatesk changed the title inference: inter-procedural conditional constraint back-propagation wip: inference: inter-procedural conditional constraint back-propagation Dec 16, 2020
@jpsamaroo
Copy link
Member

Slightly off-topic, but #37342 got accidentally linked for automatic closing.

base/compiler/abstractinterpretation.jl Outdated Show resolved Hide resolved
base/compiler/typeinfer.jl Outdated Show resolved Hide resolved
base/compiler/typeinfer.jl Outdated Show resolved Hide resolved
@aviatesk aviatesk changed the title wip: inference: inter-procedural conditional constraint back-propagation inference: inter-procedural conditional constraint back-propagation Dec 17, 2020
@aviatesk
Copy link
Member Author

All tests turned green. Ready for review.

base/compiler/typelattice.jl Outdated Show resolved Hide resolved
@KristofferC
Copy link
Member

Out of curiosity, any measurements regarding compile times? For example, building the sysimage, time to first plot etc.

base/compiler/typelattice.jl Outdated Show resolved Hide resolved
@aviatesk

This comment has been minimized.

@aviatesk

This comment has been minimized.

@aviatesk aviatesk force-pushed the backprop2 branch 2 times, most recently from 04655dc to b13fbcc Compare December 18, 2020 03:15
@@ -1648,7 +1648,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
if contains_is(argtypes_vec, Union{})
return Const(Union{})
end
rt = abstract_call(interp, nothing, argtypes_vec, sv, -1).rt
rt = widenconditional(abstract_call(interp, nothing, argtypes_vec, sv, -1).rt)
Copy link
Member Author

@aviatesk aviatesk Dec 18, 2020

Choose a reason for hiding this comment

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

Now this change isn't necessary for this PR to work (abstract_call no longer returns InterConditional), but I think widenconditional is better here since it may produce more accurate result since it can be widen to Const, so let me include this as is.

@aviatesk
Copy link
Member Author

aviatesk commented Dec 18, 2020

Okay, I addressed the review comments, and this is good to go, I'd say. Any further comments or reviews ?

@aviatesk aviatesk force-pushed the backprop2 branch 2 times, most recently from ef2ab51 to 889add5 Compare December 19, 2020 16:14
aviatesk added a commit to aviatesk/LoweredCodeUtils.jl that referenced this pull request Dec 21, 2020
- `@isssa`/`@isslotnum` hacks circumvent JuliaLang/julia#37342
  and gets rid of type assertions and accompanying inference overheads
  (they are really, really minor and negligible though)
  NOTE: well, we won't need them once JuliaLang/julia#38905
  gets merged
- improve inferrability around `pcexec` within `selective_eval!`
simeonschaub added a commit that referenced this pull request Apr 17, 2021
With #38905 merged, this shouldn't apply in general anymore. I am sure with `==` being a generic function, there will still be cases where using `===` will lead to more precise inference, but I don't think this is worth specifically mentioning in the performance tips.
KristofferC pushed a commit that referenced this pull request Apr 20, 2021
With #38905 merged, this shouldn't apply in general anymore. I am sure with `==` being a generic function, there will still be cases where using `===` will lead to more precise inference, but I don't think this is worth specifically mentioning in the performance tips.
ElOceanografo pushed a commit to ElOceanografo/julia that referenced this pull request May 4, 2021
With JuliaLang#38905 merged, this shouldn't apply in general anymore. I am sure with `==` being a generic function, there will still be cases where using `===` will lead to more precise inference, but I don't think this is worth specifically mentioning in the performance tips.
antoine-levitt pushed a commit to antoine-levitt/julia that referenced this pull request May 9, 2021
With JuliaLang#38905 merged, this shouldn't apply in general anymore. I am sure with `==` being a generic function, there will still be cases where using `===` will lead to more precise inference, but I don't think this is worth specifically mentioning in the performance tips.
johanmon pushed a commit to johanmon/julia that referenced this pull request Jul 5, 2021
With JuliaLang#38905 merged, this shouldn't apply in general anymore. I am sure with `==` being a generic function, there will still be cases where using `===` will lead to more precise inference, but I don't think this is worth specifically mentioning in the performance tips.
aviatesk added a commit that referenced this pull request Oct 7, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 11, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 11, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 12, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 18, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 20, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 20, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
aviatesk added a commit that referenced this pull request Oct 21, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.
vtjnash pushed a commit that referenced this pull request Oct 21, 2021
The PR #38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements #38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.

We also improve constant-prop' heuristics in these ways:

- remove `const_prop_rettype_heuristic` since it handles rare cases,
  where const-prop' doens't seem to be worthwhile, e.g. it won't be
  so useful to try to propagate `Const(Tuple{DataType,DataType})` for
  `Const(convert)(::Const(Tuple{DataType,DataType}), ::Tuple{DataType,DataType} -> Tuple{DataType,DataType}`
- rename `is_allconst` to `is_all_overridden`
- also minor refactors and improvements added
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Feb 22, 2022
The PR JuliaLang#38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements JuliaLang#38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.

We also improve constant-prop' heuristics in these ways:

- remove `const_prop_rettype_heuristic` since it handles rare cases,
  where const-prop' doens't seem to be worthwhile, e.g. it won't be
  so useful to try to propagate `Const(Tuple{DataType,DataType})` for
  `Const(convert)(::Const(Tuple{DataType,DataType}), ::Tuple{DataType,DataType} -> Tuple{DataType,DataType}`
- rename `is_allconst` to `is_all_overridden`
- also minor refactors and improvements added
nalimilan added a commit that referenced this pull request Mar 2, 2022
This is more generic and will allow packages to define custom
`missing`-like types allowing to distinguish several kinds of
missing values like in e.g. Stata and SAS. This should have
no performance impact now thanks to #38905.
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Mar 8, 2022
The PR JuliaLang#38905 only "back-propagates" conditional constraint
(from callee to caller), but currently we don't "forward" it
(caller to callee), and so inter-procedural constraint propagation
won't happen for e.g.:
```julia
ifelselike(cnd, x, y) = cnd ? x : y
@test Base.return_types((Any,Int,)) do x, y
    ifelselike(isa(x, Int), x, y)
end |> only == Int
```

This commit complements JuliaLang#38905 and enables further inter-procedural
conditional constraint propagation by forwarding `Conditional` to
callees when it imposes a constraint on any other argument,
during constant propagation.

We also improve constant-prop' heuristics in these ways:

- remove `const_prop_rettype_heuristic` since it handles rare cases,
  where const-prop' doens't seem to be worthwhile, e.g. it won't be
  so useful to try to propagate `Const(Tuple{DataType,DataType})` for
  `Const(convert)(::Const(Tuple{DataType,DataType}), ::Tuple{DataType,DataType} -> Tuple{DataType,DataType}`
- rename `is_allconst` to `is_all_overridden`
- also minor refactors and improvements added
aviatesk added a commit that referenced this pull request May 27, 2022
When I introduced this new lattice within #38905, I defined this in
`Core` for some performance reason, but now I'm sure it is really
necessary. Rather, it is easier to develop Julia-level inference routine
in pure Julia, so this commit moves the definition of `InterConditional`
to `Core.Compiler`.
aviatesk added a commit that referenced this pull request May 30, 2022
When I introduced this new lattice within #38905, I defined this in
`Core` for some performance reason, but now I'm sure it is really
necessary. Rather, it is easier to develop Julia-level inference routine
in pure Julia, so this commit moves the definition of `InterConditional`
to `Core.Compiler`.
aviatesk added a commit that referenced this pull request May 30, 2022
When I introduced this new lattice within #38905, I defined this in
`Core` for some performance reason, but now I'm sure it is really
necessary. Rather, it is easier to develop Julia-level inference routine
in pure Julia, so this commit moves the definition of `InterConditional`
to `Core.Compiler`.
aviatesk added a commit that referenced this pull request May 30, 2022
When I introduced this new lattice within #38905, I defined this in
`Core` for some performance reason, but now I'm sure it is really
necessary. Rather, it is easier to develop Julia-level inference routine
in pure Julia, so this commit moves the definition of `InterConditional`
to `Core.Compiler`.
nalimilan added a commit that referenced this pull request Nov 27, 2023
This is more generic and will allow packages to define custom
`missing`-like types allowing to distinguish several kinds of
missing values like in e.g. Stata and SAS. This should have
no performance impact now thanks to #38905 and #49301.
nalimilan added a commit that referenced this pull request Jan 7, 2024
This is more generic and will allow packages to define custom
`missing`-like types allowing to distinguish several kinds of
missing values like in e.g. Stata and SAS (see
https://github.com/nalimilan/TypedMissings.jl). This should have no
performance impact now thanks to #38905.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:inference Type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Type check in callee does not propagate to caller
7 participants