You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This PR propagates `Conditional`s 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.:
```julia
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 JuliaLang#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:
```julia
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`).
```julia
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.
---
- closesJuliaLang#38636
- closesJuliaLang#37342
0 commit comments