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

Make Base.ifelse a generic function #37343

Merged
merged 1 commit into from
Oct 26, 2021
Merged

Conversation

c42f
Copy link
Member

@c42f c42f commented Sep 2, 2020

Fixes #32844, in particular, to provide a way for packages to extend ifelse without needing a new package for it, such as in SciML/ModelingToolkit.jl#568. CC @ChrisRackauckas

This is a WIP because it regresses some inference support for ifelse (likely undoing some of the work from #27068), in particular, the following test case now fails:

@noinline _f_ifelse_isa_() = rand(Bool) ? 1 : nothing
function _g_ifelse_isa_()
    x = _f_ifelse_isa_()
    ifelse(isa(x, Nothing), 1, x)
end
@test Base.return_types(_g_ifelse_isa_, ()) == [Int]

Inference question: Core.ifelse is handled in abstract_call_builtin() but it seems that the necessary information doesn't propagate from Base.ifelse to Core.ifelse. Why? Is this something we can expect to repair?

@ChrisRackauckas
Copy link
Member

As an upgrade path, I created https://github.com/SciML/IfElse.jl for me and @chriselrod (and whoever else) to start usinga common form, and when something gets added to Base we can deprecate that.

@vtjnash
Copy link
Member

vtjnash commented Sep 2, 2020

That question is the same as #37342

base/essentials.jl Outdated Show resolved Hide resolved
@vtjnash
Copy link
Member

vtjnash commented Apr 16, 2021

#37342 is fixed now. So, do we want to merge this or close it now?

@chriselrod
Copy link
Contributor

I'm in favor of merging it.

@StefanKarpinski StefanKarpinski added the triage This should be discussed on a triage call label Aug 17, 2021
@oscardssmith
Copy link
Member

triage says rebase and merge.

@oscardssmith oscardssmith removed the triage This should be discussed on a triage call label Aug 26, 2021
@c42f c42f force-pushed the cjf/ifelse-generic-function branch from 73e8316 to 7953e39 Compare August 27, 2021 05:00
@oscardssmith oscardssmith added the merge me PR is reviewed. Merge when all tests are passing label Aug 27, 2021
@c42f
Copy link
Member Author

c42f commented Aug 27, 2021

I've quickly rebased but it seems a deeper look is necessary because the test involving _f_ifelse_isa_ is still failing.

@vtjnash vtjnash removed the merge me PR is reviewed. Merge when all tests are passing label Sep 1, 2021
@chriselrod
Copy link
Contributor

@aviatesk have any ideas about this failure?

using Test

@noinline _f_ifelse_isa_() = rand(Bool) ? 1 : nothing
function _g_ifelse_isa_()
    x = _f_ifelse_isa_()
    ifelse(isa(x, Nothing), 1, x)
end
@test Base.return_types(_g_ifelse_isa_, ()) == [Int]

generic_ifelse(a, b, c) = Core.ifelse(a, b, c)
function _g_generic_ifelse_isa_()
    x = _f_ifelse_isa_()
    generic_ifelse(isa(x, Nothing), 1, x)
end
@test Base.return_types(_g_generic_ifelse_isa_, ()) == [Int]

I get

julia> @test Base.return_types(_g_ifelse_isa_, ()) == [Int]
Test Passed
  Expression: Base.return_types(_g_ifelse_isa_, ()) == [Int]
   Evaluated: Any[Int64] == DataType[Int64]

julia> @test Base.return_types(_g_generic_ifelse_isa_, ()) == [Int]
Test Failed at REPL[13]:1
  Expression: Base.return_types(_g_generic_ifelse_isa_, ()) == [Int]
   Evaluated: Any[Union{Nothing, Int64}] == DataType[Int64]
ERROR: There was an error during testing

Which is what's blocking this PR.

@aviatesk aviatesk self-assigned this Oct 6, 2021
@aviatesk
Copy link
Member

aviatesk commented Oct 7, 2021

#38905 isn't enough to enable extra constraint propagation for Base.ifelse: #38905 only "back-propagates" (callee to caller) conditional constraint, but currently we don't forward (caller to callee) it, and we need a general improvement here:

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

We can use a similar logic as #38905, and forward Conditional when it conveys a constraint imposed on other argument.
It slightly increases a complexity involved with Conditional, but I think it doesn't complicate our lattice impl and won't be that troublesome.

@c42f don't you mind if I rebase and add new commit onto this branch ? Or do you prefer I cut a new branch for it ?

@aviatesk
Copy link
Member

aviatesk commented Oct 7, 2021

Now I think the inference improvement is better to be done separately from this PR. It will be a requirement for this PR, but just a general improvement. xref: #42529.

@aviatesk aviatesk removed their assignment Oct 7, 2021
@c42f
Copy link
Member Author

c42f commented Oct 7, 2021

Perfect, thanks @aviatesk, I appreciate someone who knows about inference coming to the rescue here :-)

I can rebase this branch and test again once #42529 is done (also happy for anyone else to do so if they get to it first).

@chriselrod
Copy link
Contributor

Now that #42529 (comment) has merged, rebase?

Allow user code to directly extend `Base.ifelse` rather than needing a
special package for it.
@aviatesk aviatesk changed the title WIP: Make Base.ifelse a generic function Make Base.ifelse a generic function Oct 26, 2021
@oscardssmith oscardssmith merged commit 4c3ae20 into master Oct 26, 2021
@oscardssmith oscardssmith deleted the cjf/ifelse-generic-function branch October 26, 2021 11:48
@KristofferC
Copy link
Member

Shouldn't this at least have a nanosolider run before getting merged? Also, the tests in #37343 (comment) should have been added?

@ChrisRackauckas
Copy link
Member

Will this get back ported to the LTS?

@oscardssmith
Copy link
Member

no. it's a feature not a bugfix

@oscardssmith
Copy link
Member

sorry if I merged this prematurely. as I understood the issue, the inference problems were fixed in a different pr.

@oschulz
Copy link
Contributor

oschulz commented Oct 26, 2021

Will this get back ported to the LTS?

That would be nice (yes, I know, it's not a fix, but it kinda is just a "low-level" implementation change, in a way).

@oscardssmith
Copy link
Member

I'd be more inclined to agree if this pr hadn't required a change to inference to avoid regressions.

@c42f
Copy link
Member Author

c42f commented Oct 28, 2021

Thanks @oscardssmith for pushing this along.

Also, the tests in #37343 (comment) should have been added?

These tests are already part of the Base test suite and more were added as part of the separate inference PR in #42529. Regarding running nanosolider, perhaps? Is there a way to run this retroactively?

@oscardssmith
Copy link
Member

Presumably there is a nightly nanosoldier run somewhere that should capture if there were any major differences.

@vtjnash
Copy link
Member

vtjnash commented Oct 28, 2021

@vtjnash
Copy link
Member

vtjnash commented Oct 28, 2021

4c3ae20

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
@c42f
Copy link
Member Author

c42f commented Nov 4, 2021

Some of those performance regressions look kind of bad. Should we revert this for now?

(Ref: 4c3ae20#commitcomment-58883242)

aviatesk added a commit to ianatol/EscapeAnalysis.jl that referenced this pull request Nov 6, 2021
@c42f
Copy link
Member Author

c42f commented Nov 12, 2021

@aviatesk do you have any thoughts about the performance regressions from the nanosolider run and whether there's anything that could be done about them in the short term?

@aviatesk
Copy link
Member

aviatesk commented Nov 12, 2021

I don't think there is much performance difference.
AFAIU the benchmark time isn't that reliable measure of performance (whereas any increase in memory allocation usually indicates an obvious performance regression).

I compared the performance difference of ["sparse", "matmul", ("At_mul_B", "dense 50x5, sparse 50x500 -> dense 5x500")] as an example ​with the following script:

include(normpath(ENV["JULIA_PKG_DEVDIR"], "BaseBenchmarks", "src", "utils", "RandUtils.jl"))

using .RandUtils
using BenchmarkTools
using SparseArrays
using LinearAlgebra

using LinearAlgebra: *, mul!

function allocmats_ds(om, ok, on, s, nnzc, T)
   ​m, k, n = map(x -> Int(s*x), (om, ok, on))
   ​densemat, sparsemat = samerand(T, m, k), samesprand(T, k, n, nnzc/k)
   ​tdensemat = transpose!(similar(densemat, reverse(size(densemat))), densemat)
   ​tsparsemat = transpose!(similar(sparsemat, reverse(size(sparsemat))), sparsemat)
   ​destmat = similar(densemat, m, n)
   ​return m, k, n, destmat,
       ​densemat, sparsemat,
       ​tdensemat, tsparsemat
end

for (om, ok, on) in (# order of matmul dimensions m, k, and n
   ​(10^2, 10^2, 10^2),  # dense square * sparse square -> dense square
   ​(10^1, 10^1, 10^3),  # dense square * sparse short -> dense short
   ​(10^2, 10^2, 10^1),  # dense square * sparse tall -> dense tall
   ​(10^1, 10^3, 10^3),  # dense short * sparse square -> dense short
   ​(10^1, 10^2, 10^3),  # dense short * sparse short -> dense short
   ​(10^1, 10^3, 10^2),  # dense short * sparse tall -> dense short
   ​(10^3, 10^1, 10^1),  # dense tall * sparse square -> dense tall
   ​(10^2, 10^1, 10^2),  # dense tall * sparse short -> dense square
   ​) # the preceding descriptions apply to dense-sparse matmul without# any transpositions. other cases are described below
   ​m, k, n, destmat, densemat, sparsemat, tdensemat, tsparsemat = allocmats_ds(om, ok, on, 1/2, 4, Float64)
   ​display(@benchmark *($densemat, $sparsemat))
   ​display(@benchmark *($densemat, $(Transpose(tsparsemat))))
end

But I couldn't find any effective performance difference on master and master with this PR reverted.

LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Feb 22, 2022
Allow user code to directly extend `Base.ifelse` rather than needing a
special package for it.
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Mar 8, 2022
Allow user code to directly extend `Base.ifelse` rather than needing a
special package for it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

make ifelse generic
10 participants