JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.69k stars 5.48k forks source link

complete effect inference failure when repeating effect inference #56321

Open nsajko opened 1 week ago

nsajko commented 1 week ago

Reproducer:

module Zeros
    export Zero
    struct Zero end
end

module PositiveIntegers
    module RecursiveStep
        using ...Zeros
        export recursive_step
        function recursive_step(@nospecialize t::Type)
            Union{Zero, t}
        end
    end
    module UpperBounds
        using ..RecursiveStep
        abstract type A end
        abstract type B{P <: recursive_step(A)} <: A    end
        abstract type C{P <: recursive_step(B)} <: B{P} end
    end
    const PositiveIntegerUpperBound = UpperBounds.A
    const PositiveIntegerUpperBoundTighter = UpperBounds.C
    using .RecursiveStep
    export
        natural_successor, natural_predecessor,
        NonnegativeInteger, NonnegativeIntegerUpperBound,
        PositiveInteger, PositiveIntegerUpperBound,
        type_assert_nonnegative_integer, type_assert_positive_integer
    struct PositiveInteger{
        Predecessor <: recursive_step(PositiveIntegerUpperBoundTighter),
    } <: PositiveIntegerUpperBoundTighter{Predecessor}
        predecessor::Predecessor
        global const NonnegativeInteger = recursive_step(PositiveInteger)
        global const NonnegativeIntegerUpperBound = recursive_step(PositiveIntegerUpperBound)
        global function natural_successor(p::P) where {P <: NonnegativeInteger}
            r = new{P}(p)
            type_assert_positive_integer(r)
        end
    end
    function type_assert_nonnegative_integer(@nospecialize x::NonnegativeInteger)
        x
    end
    function type_assert_positive_integer(@nospecialize x::PositiveInteger)
        x
    end
    function natural_predecessor(@nospecialize o::PositiveInteger)
        r = o.predecessor
        type_assert_nonnegative_integer(r)
    end
end

module IntegersGreaterThanOne
    using ..PositiveIntegers
    export IntegerGreaterThanOne, IntegerGreaterThanOneUpperBound
    const IntegerGreaterThanOne = let t = PositiveInteger
        t{P} where {P <: t}
    end
    const IntegerGreaterThanOneUpperBound = let t = PositiveIntegerUpperBound
        PositiveIntegers.UpperBounds.B{P} where {P <: t}
    end
end

module Utils
    using ..Zeros, ..PositiveIntegers, ..IntegersGreaterThanOne
    export minus_two, half_floor
    function minus_two(@nospecialize m::IntegerGreaterThanOne)
        natural_predecessor(natural_predecessor(m))
    end
    function half_floor(@nospecialize m::NonnegativeInteger)
        ret = if m isa IntegerGreaterThanOneUpperBound
            let n = minus_two(m), rec = @inline half_floor(n)
                type_assert_positive_integer(natural_successor(rec))
            end
        else
            Zero()
        end
        type_assert_nonnegative_integer(ret)
    end
end

V1.10 works fine, on v1.11 and v1.12 this happens:

julia> include("/home/nsajko/tuple_sorting/reproducer.jl")
Main.Utils

julia> Base.infer_effects(Utils.half_floor)  # OK
(+c,+e,!n,!t,+s,?m,+u,+o,+r)

julia> Base.infer_effects(Utils.half_floor)  # ?!??
(!c,!e,!n,!t,!s,!m,!u,!o,!r)

julia> versioninfo()
Julia Version 1.12.0-DEV.1466
Commit 73b85cfc04d (2024-10-23 01:12 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × AMD Ryzen 3 5300U with Radeon Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, znver2)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
Environment:
  JULIA_NUM_PRECOMPILE_TASKS = 4
  JULIA_PKG_PRECOMPILE_AUTO = 0
nsajko commented 1 week ago

Bisecting. It seems there are two different events of interest here:

  1. At some point the good effect inference of v1.10 turned bad.
  2. At some point after that, the effect inference became good again, but only for the first Base.infer_effects call, as shown above.
nsajko commented 1 week ago

Bisected the first event to 631b5c39b72d91bec33262e14ce96f74de9aa7b6 #51092. cc @vtjnash

nsajko commented 1 week ago

Bisected the second event to 65aeaf6c6528e8e309b16340958784d53e28ccc3 #54323. cc @aviatesk

Before:

julia> include("/tmp/reproducer.jl")
Main.Utils

julia> Base.infer_effects(Utils.half_floor)
(!c,!e,!n,!t,!s,!m,!u)′

julia> Base.infer_effects(Utils.half_floor)
(!c,!e,!n,!t,!s,!m,!u)′

julia> versioninfo()
Julia Version 1.12.0-DEV.654
Commit 583981fb43c (2024-06-05 00:17 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × AMD Ryzen 3 5300U with Radeon Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-17.0.6 (ORCJIT, znver2)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
Environment:
  JULIA_NUM_PRECOMPILE_TASKS = 4
  JULIA_PKG_PRECOMPILE_AUTO = 0

After:

julia> include("/tmp/reproducer.jl")
Main.Utils

julia> Base.infer_effects(Utils.half_floor)
(+c,+e,!n,!t,+s,?m,+u)

julia> Base.infer_effects(Utils.half_floor)
(!c,!e,!n,!t,!s,!m,!u)′

julia> versioninfo()
Julia Version 1.12.0-DEV.655
Commit 65aeaf6c652 (2024-06-05 01:11 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × AMD Ryzen 3 5300U with Radeon Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-17.0.6 (ORCJIT, znver2)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
Environment:
  JULIA_NUM_PRECOMPILE_TASKS = 4
  JULIA_PKG_PRECOMPILE_AUTO = 0
nsajko commented 1 week ago

I guess it should be possible to somehow preserve the good effect inference result in subsequent effect inference queries, right?