/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Compiler/test/effects.jl
1 633 строки
58 KB
Jameson Nash
Compiler: Infer precise `fetch(::Task)` return types and optimize task invocation (#59221)
29 июл 2026, 11:50
Не верифицирован
29 июл 2026, 11:50
10d617e
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license using Test include("setup_Compiler.jl") include("irutils.jl") # Test that the Core._apply_iterate bail path taints effects function f_apply_bail(f) f(()...) return nothing end @test !Compiler.is_removable_if_unused(Base.infer_effects(f_apply_bail)) @test !fully_eliminated((Function,)) do f f_apply_bail(f) nothing end # Test that effect modeling for return_type doesn't incorrectly pick # up the effects of the function being analyzed f_throws() = error() @noinline function return_type_unused(x) Compiler.return_type(f_throws, Tuple{}) return x+1 end @test Compiler.is_removable_if_unused(Base.infer_effects(return_type_unused, (Int,))) @test fully_eliminated((Int,)) do x return_type_unused(x) return nothing end # Test that ambiguous calls don't accidentally get nothrow effect ambig_effects_test(a::Int, b) = 1 ambig_effects_test(a, b::Int) = 1 ambig_effects_test(a, b) = 1 @test !Compiler.is_nothrow(Base.infer_effects(ambig_effects_test, (Int, Any))) global ambig_unknown_type_global::Any = 1 @noinline function conditionally_call_ambig(b::Bool, a) if b ambig_effects_test(a, ambig_unknown_type_global) end return 0 end @test !fully_eliminated((Bool,)) do b conditionally_call_ambig(b, 1) return nothing end # `supertype(::UnionAll)` can throw (its recursion hits `supertype(::Union)` for # inputs like `Union{S,T} where {S,T}`), so it must be `:foldable` rather than # `:total` and a dead call to it must not be eliminated (issue #61988) @test Compiler.is_foldable(Base.infer_effects(supertype, (UnionAll,))) @test !Compiler.is_nothrow(Base.infer_effects(supertype, (UnionAll,))) @test !fully_eliminated((UnionAll,)) do x supertype(x) return nothing end @test_throws MethodError (x -> (supertype(x); nothing))(Union{S,T} where {S,T}) # Test that a missing methtable identification gets tainted # appropriately struct FCallback; f::Union{Nothing, Function}; end f_invoke_callback(fc) = let f=fc.f; (f !== nothing && f(); nothing); end @test !Compiler.is_removable_if_unused(Base.infer_effects(f_invoke_callback, (FCallback,))) @test !fully_eliminated((FCallback,)) do fc f_invoke_callback(fc) return nothing end # @assume_effects override const ___CONST_DICT___ = Dict{Any,Any}(Symbol(c) => i for (i, c) in enumerate('a':'z')) Base.@assume_effects :foldable concrete_eval( f, args...; kwargs...) = f(args...; kwargs...) @test fully_eliminated() do concrete_eval(getindex, ___CONST_DICT___, :a) end # :removable override Base.@assume_effects :removable removable_call( f, args...; kwargs...) = f(args...; kwargs...) @test fully_eliminated() do @noinline removable_call(getindex, ___CONST_DICT___, :a) nothing end # terminates_globally override # https://github.com/JuliaLang/julia/issues/41694 Base.@assume_effects :terminates_globally function issue41694(x) res = 1 0 ≤ x < 20 || error("bad fact") while x > 1 res *= x x -= 1 end return res end @test Compiler.is_foldable(Base.infer_effects(issue41694, (Int,))) @test fully_eliminated() do issue41694(2) end Base.@assume_effects :terminates_globally function recur_termination1(x) x == 0 && return 1 0 ≤ x < 20 || error("bad fact") return x * recur_termination1(x-1) end @test Compiler.is_foldable(Base.infer_effects(recur_termination1, (Int,))) @test Compiler.is_terminates(Base.infer_effects(recur_termination1, (Int,))) function recur_termination2() Base.@assume_effects :total !:terminates_globally recur_termination1(12) end @test fully_eliminated(recur_termination2) @test fully_eliminated() do; recur_termination2(); end Base.@assume_effects :terminates_globally function recur_termination21(x) x == 0 && return 1 0 ≤ x < 20 || error("bad fact") return recur_termination22(x) end recur_termination22(x) = x * recur_termination21(x-1) @test Compiler.is_foldable(Base.infer_effects(recur_termination21, (Int,))) @test Compiler.is_foldable(Base.infer_effects(recur_termination22, (Int,))) @test Compiler.is_terminates(Base.infer_effects(recur_termination21, (Int,))) @test Compiler.is_terminates(Base.infer_effects(recur_termination22, (Int,))) function recur_termination2x() Base.@assume_effects :total !:terminates_globally recur_termination21(12) + recur_termination22(12) end @test fully_eliminated(recur_termination2x) @test fully_eliminated() do; recur_termination2x(); end # anonymous function support for `@assume_effects` @test fully_eliminated() do map((2,3,4)) do x # this :terminates_locally allows this anonymous function to be constant-folded Base.@assume_effects :terminates_locally res = 1 0 ≤ x < 20 || error("bad fact") while x > 1 res *= x x -= 1 end return res end end # control flow backedge should taint `terminates` @test Base.infer_effects((Int,)) do n for i = 1:n; end end |> !Compiler.is_terminates # interprocedural-recursion should taint `terminates` **appropriately** function sumrecur(a, x) isempty(a) && return x return sumrecur(Base.tail(a), x + first(a)) end @test Base.infer_effects(sumrecur, (Tuple{Int,Int,Int},Int)) |> Compiler.is_terminates @test Base.infer_effects(sumrecur, (Tuple{Int,Int,Int,Vararg{Int}},Int)) |> !Compiler.is_terminates # https://github.com/JuliaLang/julia/issues/45781 @test Base.infer_effects((Float32,)) do a out1 = promote_type(Irrational{:π}, Bool) out2 = sin(a) out1, out2 end |> Compiler.is_terminates # refine :consistent-cy effect inference using the return type information @test Base.infer_effects((Any,)) do x taint = Ref{Any}(x) # taints :consistent-cy, but will be adjusted throw(taint) end |> Compiler.is_consistent @test Base.infer_effects((Int,)) do x if x < 0 taint = Ref(x) # taints :consistent-cy, but will be adjusted throw(DomainError(x, taint)) end return nothing end |> Compiler.is_consistent @test Base.infer_effects((Int,)) do x if x < 0 taint = Ref(x) # taints :consistent-cy, but will be adjusted throw(DomainError(x, taint)) end return x == 0 ? nothing : x # should `Union` of isbitstype objects nicely end |> Compiler.is_consistent @test Base.infer_effects((Symbol,Any)) do s, x if s === :throw taint = Ref{Any}(":throw option given") # taints :consistent-cy, but will be adjusted throw(taint) end return s # should handle `Symbol` nicely end |> Compiler.is_consistent @test Base.infer_effects((Int,)) do x return Ref(x) end |> !Compiler.is_consistent @test Base.infer_effects((Int,)) do x return x < 0 ? Ref(x) : nothing end |> !Compiler.is_consistent @test Base.infer_effects((Int,)) do x if x < 0 throw(DomainError(x, lazy"$x is negative")) end return nothing end |> Compiler.is_foldable # :the_exception expression should taint :consistent-cy global inconsistent_var::Int = 42 function throw_inconsistent() # this is still :consistent throw(inconsistent_var) end function catch_inconsistent() try throw_inconsistent() catch err err end end @test !Compiler.is_consistent(Base.infer_effects(catch_inconsistent)) cache_inconsistent() = catch_inconsistent() function compare_inconsistent() a = cache_inconsistent() global inconsistent_var = 0 b = cache_inconsistent() global inconsistent_var = 42 return a === b end @test !compare_inconsistent() # return type information shouldn't be able to refine it also function catch_inconsistent(x::T) where T v = x try throw_inconsistent() catch err v = err::T end return v end @test !Compiler.is_consistent(Base.infer_effects(catch_inconsistent, (Int,))) cache_inconsistent(x) = catch_inconsistent(x) function compare_inconsistent(x::T) where T x = one(T) a = cache_inconsistent(x) global inconsistent_var = 0 b = cache_inconsistent(x) global inconsistent_var = 42 return a === b end @test !compare_inconsistent(3) # Effect modeling for Core.compilerbarrier @test Base.infer_effects(Base.inferencebarrier, Tuple{Any}) |> Compiler.is_removable_if_unused # effects modeling for allocation/access of uninitialized fields struct Maybe{T} x::T Maybe{T}() where T = new{T}() Maybe{T}(x) where T = new{T}(x) Maybe(x::T) where T = new{T}(x) end Base.getindex(x::Maybe) = x.x struct SyntacticallyDefined{T} x::T end @test Base.infer_effects() do Maybe{Int}() end |> !Compiler.is_consistent @test Base.infer_effects() do Maybe{Int}()[] end |> !Compiler.is_consistent @test !fully_eliminated() do Maybe{Int}()[] end @test Base.infer_effects() do Maybe{String}() end |> Compiler.is_consistent @test Base.infer_effects() do Maybe{String}()[] end |> Compiler.is_consistent @test Base.infer_effects() do Maybe{Some{Base.RefValue{Int}}}() end |> Compiler.is_consistent let f() = Maybe{String}()[] @test Base.return_types() do f() # this call should be concrete evaluated end |> only === Union{} end @test Base.infer_effects() do Ref{Int}() end |> !Compiler.is_consistent @test Base.infer_effects() do Ref{Int}()[] end |> !Compiler.is_consistent @test !fully_eliminated() do Ref{Int}()[] end @test Base.infer_effects() do Ref{String}()[] end |> Compiler.is_consistent let f() = Ref{String}()[] @test Base.return_types() do f() # this call should be concrete evaluated end |> only === Union{} end @test Base.infer_effects((SyntacticallyDefined{Float64}, Symbol)) do w, s getfield(w, s) end |> Compiler.is_foldable # effects propagation for `Core.invoke` calls # https://github.com/JuliaLang/julia/issues/44763 global x44763::Int = 0 increase_x44763!(n) = (global x44763; x44763 += n) invoke44763(x) = @invoke increase_x44763!(x) @test Base.return_types() do invoke44763(42) end |> only === Int @test x44763 == 0 # `@inbounds`/`@boundscheck` expression should taint :consistent correctly # https://github.com/JuliaLang/julia/issues/48099 function A1_inbounds() r = 0 @inbounds begin @boundscheck r += 1 end return r end @test !Compiler.is_consistent(Base.infer_effects(A1_inbounds)) # Test that purity doesn't try to accidentally run unreachable code due to # boundscheck elimination function f_boundscheck_elim(n) # Inbounds here assumes that this is only ever called with `n==0`, but of # course the compiler has no way of knowing that, so it must not attempt # to run the `@inbounds getfield(sin, 1)` that `ntuple` generates. ntuple(x->(@inbounds ()[x]), n) end @test !Compiler.is_noub(Base.infer_effects(f_boundscheck_elim, (Int,))) @test Tuple{} <: only(Base.return_types(f_boundscheck_elim, (Int,))) # Test that purity modeling doesn't accidentally introduce new world age issues f_redefine_me(x) = x+1 f_call_redefine() = f_redefine_me(0) f_mk_opaque() = Base.Experimental.@opaque ()->Base.inferencebarrier(f_call_redefine)() const op_capture_world = f_mk_opaque() f_redefine_me(x) = x+2 @test op_capture_world() == 1 @test f_mk_opaque()() == 2 # backedge insertion for Any-typed, effect-free frame const CONST_DICT = let d = Dict() for c in 'A':'z' push!(d, c => Int(c)) end d end Base.@assume_effects :foldable getcharid(c) = CONST_DICT[c] @noinline callf(f, args...) = f(args...) function entry_to_be_invalidated(c) return callf(getcharid, c) end @test Base.infer_effects((Char,)) do x entry_to_be_invalidated(x) end |> Compiler.is_foldable @test fully_eliminated(; retval=97) do entry_to_be_invalidated('a') end getcharid(c) = CONST_DICT[c] # now this is not eligible for concrete evaluation @test Base.infer_effects((Char,)) do x entry_to_be_invalidated(x) end |> !Compiler.is_foldable @test !fully_eliminated() do entry_to_be_invalidated('a') end @test !Compiler.builtin_nothrow(Compiler.fallback_lattice, Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any) # effects modeling for assignment to globals global glob_assign_int::Int = 0 f_glob_assign_int() = global glob_assign_int = 1 let effects = Base.infer_effects(f_glob_assign_int, (); optimize=false) @test Compiler.is_consistent(effects) @test !Compiler.is_effect_free(effects) @test Compiler.is_nothrow(effects) end # effects modeling for setglobal! global SETGLOBAL!_NOTHROW::Int = 0 let effects = Base.infer_effects(; optimize=false) do setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42) end @test Compiler.is_consistent(effects) @test !Compiler.is_effect_free(effects) @test Compiler.is_nothrow(effects) end # we should taint `nothrow` if the binding doesn't exist and isn't fixed yet, setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42) let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet) @test !Compiler.is_nothrow(effects) end @test_throws ErrorException setglobal!_nothrow_undefinedyet() # This declares the binding as ::Any @eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 let effects = Base.infer_effects(global_assignment_undefinedyet) @test Compiler.is_nothrow(effects) end # Again with type mismatch global UNDEFINEDYET2::String = "0" setglobal!_nothrow_undefinedyet2() = setglobal!(@__MODULE__, :UNDEFINEDYET2, 42) @eval global_assignment_undefinedyet2() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET2)) = 42 let effects = Base.infer_effects(global_assignment_undefinedyet2) @test !Compiler.is_nothrow(effects) end let effects = Base.infer_effects(setglobal!_nothrow_undefinedyet2) @test !Compiler.is_nothrow(effects) end @test_throws TypeError setglobal!_nothrow_undefinedyet2() module ExportMutableGlobal global mutable_global_for_setglobal_test::Int = 0 export mutable_global_for_setglobal_test end using .ExportMutableGlobal: mutable_global_for_setglobal_test f_assign_imported() = global mutable_global_for_setglobal_test = 42 let effects = Base.infer_effects(f_assign_imported) @test !Compiler.is_nothrow(effects) end @test_throws ErrorException f_assign_imported() # Nothrow for setfield! mutable struct SetfieldNothrow x::Int end f_setfield_nothrow() = SetfieldNothrow(0).x = 1 let effects = Base.infer_effects(f_setfield_nothrow, ()) @test Compiler.is_nothrow(effects) @test Compiler.is_effect_free(effects) # see EFFECT_FREE_IF_INACCESSIBLEMEMONLY end # even if 2-arg `getfield` may throw, it should be still `:consistent` @test Compiler.is_consistent(Base.infer_effects(getfield, (NTuple{5, Float64}, Int))) # SimpleVector allocation is consistent @test Compiler.is_consistent(Base.infer_effects(Core.svec)) @test Base.infer_effects() do Core.svec(nothing, 1, "foo") end |> Compiler.is_consistent # fastmath operations are in-`:consistent` @test !Compiler.is_consistent(Base.infer_effects((a,b)->@fastmath(a+b), (Float64,Float64))) # issue 46122: @assume_effects for @ccall @test Base.infer_effects((Vector{Int},)) do a Base.@assume_effects :effect_free @ccall this_call_does_not_really_exist(a::Any)::Ptr{Int} end |> Compiler.is_effect_free # `getfield_effects` handles access to union object nicely let 𝕃 = Compiler.fallback_lattice getfield_effects = Compiler.getfield_effects @test Compiler.is_consistent(getfield_effects(𝕃, Any[Some{String}, Core.Const(:value)], String)) @test Compiler.is_consistent(getfield_effects(𝕃, Any[Some{Symbol}, Core.Const(:value)], Symbol)) @test Compiler.is_consistent(getfield_effects(𝕃, Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String})) end @test Base.infer_effects((Bool,)) do c obj = c ? Some{String}("foo") : Some{Symbol}(:bar) return getfield(obj, :value) end |> Compiler.is_consistent # getfield is nothrow when bounds checking is turned off @test Base.infer_effects((Tuple{Int,Int},Int)) do t, i getfield(t, i, false) end |> Compiler.is_nothrow @test Base.infer_effects((Tuple{Int,Int},Symbol)) do t, i getfield(t, i, false) end |> Compiler.is_nothrow @test Base.infer_effects((Tuple{Int,Int},String)) do t, i getfield(t, i, false) # invalid name type end |> !Compiler.is_nothrow @test Base.infer_effects((Some{Any},)) do some getfield(some, 1, :not_atomic) end |> Compiler.is_nothrow @test Base.infer_effects((Some{Any},)) do some getfield(some, 1, :invalid_atomic_spec) end |> !Compiler.is_nothrow @test Base.infer_effects((Some{Any},Bool)) do some, boundscheck getfield(some, 1, boundscheck) end |> Compiler.is_nothrow @test Base.infer_effects((Some{Any},Bool)) do some, boundscheck getfield(some, 1, :not_atomic, boundscheck) end |> Compiler.is_nothrow @test Base.infer_effects((Some{Any},Bool)) do some, boundscheck getfield(some, 1, :invalid_atomic_spec, boundscheck) end |> !Compiler.is_nothrow @test Base.infer_effects((Some{Any},Any)) do some, boundscheck getfield(some, 1, :not_atomic, boundscheck) end |> !Compiler.is_nothrow @test Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int))) # :inaccessiblememonly effect const global constant_global::Int = 42 const global ConstantType = Ref global nonconstant_global::Int = 42 const global constant_mutable_global = Ref(0) const global constant_global_nonisbits = Some(:foo) @test Base.infer_effects() do constant_global end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do ConstantType end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do ConstantType{Any}() end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do constant_global_nonisbits end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do getglobal(@__MODULE__, :constant_global) end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do nonconstant_global end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects() do getglobal(@__MODULE__, :nonconstant_global) end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects((Symbol,)) do name getglobal(@__MODULE__, name) end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects((Int,)) do v global nonconstant_global = v end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects((Int,)) do v setglobal!(@__MODULE__, :nonconstant_global, v) end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects((Int,)) do v constant_mutable_global[] = v end |> !Compiler.is_inaccessiblememonly module ConsistentModule const global constant_global::Int = 42 const global ConstantType = Ref end # module @test Base.infer_effects() do ConsistentModule.constant_global end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do ConsistentModule.ConstantType end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do ConsistentModule.ConstantType{Any}() end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do getglobal(@__MODULE__, :ConsistentModule).constant_global end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do getglobal(@__MODULE__, :ConsistentModule).ConstantType end |> Compiler.is_inaccessiblememonly @test Base.infer_effects() do getglobal(@__MODULE__, :ConsistentModule).ConstantType{Any}() end |> Compiler.is_inaccessiblememonly @test Base.infer_effects((Module,)) do M M.constant_global end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects((Module,)) do M M.ConstantType end |> !Compiler.is_inaccessiblememonly @test Base.infer_effects() do M M.ConstantType{Any}() end |> !Compiler.is_inaccessiblememonly # the `:inaccessiblememonly` helper effect allows us to prove `:consistent`-cy of frames # including `getfield` / `isdefined` accessing to local mutable object mutable struct SafeRef{T} x::T end Base.getindex(x::SafeRef) = x.x; Base.setindex!(x::SafeRef, v) = x.x = v; Base.isassigned(x::SafeRef) = true; function mutable_consistent(s) SafeRef(s)[] end @test Compiler.is_inaccessiblememonly(Base.infer_effects(mutable_consistent, (Symbol,))) @test fully_eliminated(; retval=:foo) do mutable_consistent(:foo) end function nested_mutable_consistent(s) SafeRef(SafeRef(SafeRef(SafeRef(SafeRef(s)))))[][][][][] end @test Compiler.is_inaccessiblememonly(Base.infer_effects(nested_mutable_consistent, (Symbol,))) @test fully_eliminated(; retval=:foo) do nested_mutable_consistent(:foo) end const consistent_global = Some(:foo) @test Base.infer_effects() do consistent_global.value end |> Compiler.is_consistent const inconsistent_global = SafeRef(:foo) @test Base.infer_effects() do inconsistent_global[] end |> !Compiler.is_consistent const inconsistent_condition_ref = Ref{Bool}(false) @test Base.infer_effects() do if inconsistent_condition_ref[] return 0 else return 1 end end |> !Compiler.is_consistent # should handle va-method properly callgetfield1(xs...) = getfield(getfield(xs, 1), 1) @test !Compiler.is_inaccessiblememonly(Base.infer_effects(callgetfield1, (Base.RefValue{Symbol},))) const GLOBAL_XS = Ref(:julia) global_getfield() = callgetfield1(GLOBAL_XS) @test let Base.Experimental.@force_compile global_getfield() end === :julia GLOBAL_XS[] = :julia2 @test let Base.Experimental.@force_compile global_getfield() end === :julia2 # the `:inaccessiblememonly` helper effect allows us to prove `:effect_free`-ness of frames # including `setfield!` modifying local mutable object const global_ref = Ref{Any}() global const global_bit::Int = 42 makeref() = Ref{Any}() setref!(ref, @nospecialize v) = ref[] = v @noinline function removable_if_unused1() x = makeref() setref!(x, 42) x end @noinline function removable_if_unused2() x = makeref() setref!(x, global_bit) x end for f = Any[removable_if_unused1, removable_if_unused2] effects = Base.infer_effects(f) @test Compiler.is_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) @test Compiler.is_removable_if_unused(effects) @test @eval fully_eliminated() do $f() nothing end end @noinline function removable_if_unused3(v) x = makeref() setref!(x, v) x end let effects = Base.infer_effects(removable_if_unused3, (Int,)) @test Compiler.is_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) @test Compiler.is_removable_if_unused(effects) end @test fully_eliminated((Int,)) do v removable_if_unused3(v) nothing end @noinline function unremovable_if_unused1!(x) setref!(x, 42) end @test !Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused1!, (typeof(global_ref),))) @test !Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused1!, (Any,))) @noinline function unremovable_if_unused2!() setref!(global_ref, 42) end @test !Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused2!)) @noinline function unremovable_if_unused3!() getfield(@__MODULE__, :global_ref)[] = nothing end @test !Compiler.is_removable_if_unused(Base.infer_effects(unremovable_if_unused3!)) # array ops # ========= # allocation # ---------- # low-level constructor @noinline construct_array(@nospecialize(T), args...) = Array{T}(undef, args...) # should eliminate safe but dead allocations let good_dims = [1, 2, 3, 4, 10] Ns = [1, 2, 3, 4, 10] for dim = good_dims, N = Ns Int64(dim)^N > typemax(Int) && continue dims = ntuple(i->dim, N) @test @eval Base.infer_effects() do construct_array(Int, $(dims...)) end |> Compiler.is_removable_if_unused @test @eval fully_eliminated() do construct_array(Int, $(dims...)) nothing end end end # should analyze throwness correctly let bad_dims = [-1, typemax(Int)] for dim in bad_dims, N in [1, 2, 3, 4, 10] for T in Any[Int, Union{Missing,Nothing}, Missing, Any] dims = ntuple(i->dim, N) @test @eval Base.infer_effects() do construct_array($T, $(dims...)) end |> !Compiler.is_removable_if_unused @test @eval !fully_eliminated() do construct_array($T, $(dims...)) nothing end @test_throws "invalid " @eval construct_array($T, $(dims...)) end end end # high-level interfaces # getindex for safesig = Any[ (Type{Int},) (Type{Int}, Int) (Type{Int}, Int, Int) (Type{Number},) (Type{Number}, Number) (Type{Number}, Int) (Type{Any},) (Type{Any}, Any,) (Type{Any}, Any, Any) ] let effects = Base.infer_effects(getindex, safesig) @test Compiler.is_consistent_if_notreturned(effects) @test Compiler.is_removable_if_unused(effects) end end for unsafesig = Any[ (Type{Int}, String) (Type{Int}, Any) (Type{Number}, AbstractString) (Type{Number}, Any) ] let effects = Base.infer_effects(getindex, unsafesig) @test !Compiler.is_nothrow(effects) end end # vect for safesig = Any[ () (Int,) (Int, Int) ] let effects = Base.infer_effects(Base.vect, safesig) @test Compiler.is_consistent_if_notreturned(effects) @test Compiler.is_removable_if_unused(effects) end end # array getindex let tt = (MemoryRef{Any},Symbol,Bool) @testset let effects = Base.infer_effects(Core.memoryrefget, tt) @test Compiler.is_consistent_if_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end end # array setindex! let tt = (MemoryRef{Any},Any,Symbol,Bool) @testset let effects = Base.infer_effects(Core.memoryrefset!, tt) @test Compiler.is_consistent_if_inaccessiblememonly(effects) @test Compiler.is_effect_free_if_inaccessiblememonly(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end end # nothrow for arrayset @test Base.infer_effects((MemoryRef{Int},Int)) do a, v Core.memoryrefset!(a, v, :not_atomic, true) end |> !Compiler.is_nothrow @test Base.infer_effects((MemoryRef{Int},Int)) do a, v a[] = v # may throw end |> !Compiler.is_nothrow # when bounds checking is turned off, it should be safe @test Base.infer_effects((MemoryRef{Int},Int)) do a, v Core.memoryrefset!(a, v, :not_atomic, false) end |> Compiler.is_nothrow @test Base.infer_effects((MemoryRef{Number},Number)) do a, v Core.memoryrefset!(a, v, :not_atomic, false) end |> Compiler.is_nothrow # arraysize # --------- let effects = Base.infer_effects(size, (Array,Int)) @test Compiler.is_consistent_if_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end # Test that arraysize has proper effect modeling @test fully_eliminated(M->(size(M, 2); nothing), (Matrix{Float64},)) # arraylen # -------- let effects = Base.infer_effects(length, (Vector{Any},)) @test Compiler.is_consistent_if_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) @test Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end # resize # ------ #for op = Any[ # Base._growbeg!, # Base._growend!, # Base._deletebeg!, # Base._deleteend!, # ] # let effects = Base.infer_effects(op, (Vector, Int)) # @test Compiler.is_effect_free_if_inaccessiblememonly(effects) # @test Compiler.is_terminates(effects) # @test !Compiler.is_nothrow(effects) # end #end @test Compiler.is_noub(Base.infer_effects(Base._growbeg!, (Vector{Int}, Int))) @test Compiler.is_noub(Base.infer_effects(Base._growbeg!, (Vector{Any}, Int))) @test Compiler.is_noub(Base.infer_effects(Base._growend!, (Vector{Int}, Int))) @test Compiler.is_noub(Base.infer_effects(Base._growend!, (Vector{Any}, Int))) # tuple indexing # -------------- @test Compiler.is_foldable(Base.infer_effects(iterate, Tuple{Tuple{Int, Int}, Int})) # end to end # ---------- #function simple_vec_ops(T, op!, op, xs...) # a = T[] # op!(a, xs...) # return op(a) #end #for T = Any[Int,Any], op! = Any[push!,pushfirst!], op = Any[length,size], # xs = Any[(Int,), (Int,Int,)] # let effects = Base.infer_effects(simple_vec_ops, (Type{T},typeof(op!),typeof(op),xs...)) # @test Compiler.is_foldable(effects) # end #end # Test that builtin_effects handles vararg correctly @test !Compiler.is_nothrow(Compiler.builtin_effects(Compiler.fallback_lattice, Core.isdefined, Any[String, Vararg{Any}], Bool)) # Test that :new can be eliminated even if an sparam is unknown struct SparamUnused{T} x SparamUnused(x::T) where {T} = new{T}(x) end mksparamunused(x) = (SparamUnused(x); nothing) let src = code_typed1(mksparamunused, (Any,)) @test count(isnew, src.code) == 0 end struct WrapperOneField{T} x::T end # Effects for getfield of type instance @test Base.infer_effects(Tuple{Nothing}) do x WrapperOneField{typeof(x)}.instance end |> Compiler.is_foldable_nothrow @test Base.infer_effects(Tuple{WrapperOneField{Float64}, Symbol}) do w, s getfield(w, s) end |> Compiler.is_foldable @test Base.infer_effects(Tuple{WrapperOneField{Symbol}, Symbol}) do w, s getfield(w, s) end |> Compiler.is_foldable # Flow-sensitive consistent for _typevar @test Base.infer_effects() do return WrapperOneField == (WrapperOneField{T} where T) end |> Compiler.is_foldable_nothrow # Test that dead `@inbounds` does not taint consistency # https://github.com/JuliaLang/julia/issues/48243 @test Base.infer_effects(Tuple{Int64}) do i false && @inbounds (1,2,3)[i] return 1 end |> Compiler.is_foldable_nothrow @test Base.infer_effects(Tuple{Int64}) do i @inbounds (1,2,3)[i] end |> !Compiler.is_noub @test Base.infer_effects(Tuple{Tuple{Int64}}) do x @inbounds x[1] end |> Compiler.is_foldable_nothrow # Test that :new of non-concrete, but otherwise known type # does not taint consistency. @eval struct ImmutRef{T} x::T ImmutRef(x) = $(Expr(:new, :(ImmutRef{typeof(x)}), :x)) end @test Compiler.is_foldable(Base.infer_effects(ImmutRef, Tuple{Any})) @test Compiler.is_foldable_nothrow(Base.infer_effects(typejoin, ())) # nothrow-ness of subtyping operations # https://github.com/JuliaLang/julia/pull/48566 @test !Compiler.is_nothrow(Base.infer_effects((A,B)->A<:B, (Any,Any))) @test !Compiler.is_nothrow(Base.infer_effects((A,B)->A>:B, (Any,Any))) # GotoIfNot should properly mark itself as throwing when given a non-Bool # https://github.com/JuliaLang/julia/pull/48583 gotoifnot_throw_check_48583(x) = x ? x : 0 @test !Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Missing,))) @test !Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Any,))) @test Compiler.is_nothrow(Base.infer_effects(gotoifnot_throw_check_48583, (Bool,))) # unknown :static_parameter should taint :nothrow # https://github.com/JuliaLang/julia/issues/46771 unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = (T; nothing) unknown_sparam_nothrow1(x::Ref{T}) where T = (T; nothing) unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Int},))) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{<:Integer},))) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Ref{T}} where {T},))) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type,))) @test !Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Nothing,))) @test !Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Union{Type{Int},Nothing},))) @test !Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Any,))) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow1, (Ref,))) @test Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) # purely abstract recursion should not taint :terminates # https://github.com/JuliaLang/julia/issues/48983 abstractly_recursive1() = abstractly_recursive2() abstractly_recursive2() = (Base._return_type(abstractly_recursive1, Tuple{}); 1) abstractly_recursive3() = abstractly_recursive2() @test_broken Compiler.is_terminates(Base.infer_effects(abstractly_recursive3, ())) actually_recursive1(x) = actually_recursive2(x) actually_recursive2(x) = (x <= 0) ? 1 : actually_recursive1(x - 1) actually_recursive3(x) = actually_recursive2(x) @test !Compiler.is_terminates(Base.infer_effects(actually_recursive3, (Int,))) # `isdefined` effects struct MaybeSome{T} value::T MaybeSome(x::T) where T = new{T}(x) MaybeSome{T}(x::T) where T = new{T}(x) MaybeSome{T}() where T = new{T}() end const undefined_ref = Ref{String}() const defined_ref = Ref{String}("julia") const undefined_some = MaybeSome{String}() const defined_some = MaybeSome{String}("julia") let effects = Base.infer_effects() do isdefined(undefined_ref, :x) end @test !Compiler.is_consistent(effects) @test Compiler.is_nothrow(effects) end let effects = Base.infer_effects() do isdefined(defined_ref, :x) end @test !Compiler.is_consistent(effects) @test Compiler.is_nothrow(effects) end let effects = Base.infer_effects() do isdefined(undefined_some, :value) end @test Compiler.is_consistent(effects) @test Compiler.is_nothrow(effects) end let effects = Base.infer_effects() do isdefined(defined_some, :value) end @test Compiler.is_consistent(effects) @test Compiler.is_nothrow(effects) end # high-level interface test isassigned_effects(s) = isassigned(Ref(s)) @test Compiler.is_consistent(Base.infer_effects(isassigned_effects, (Symbol,))) @test fully_eliminated(; retval=true) do isassigned_effects(:foo) end # inference on throw block should be disabled only when the effects are already known to be # concrete-eval ineligible: function optimize_throw_block_for_effects(x) a = [x] if x < 0 throw(ArgumentError(lazy"negative number given: $x")) end return a end let effects = Base.infer_effects(optimize_throw_block_for_effects, (Int,)) @test Compiler.is_consistent_if_notreturned(effects) @test Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end # :isdefined effects @test @eval Base.infer_effects() do @isdefined($(gensym("some_undef_symbol"))) end |> !Compiler.is_consistent # `@isdefined`-guarded read of a slot whose value is a `MustAlias` must still refine # the slot's `undef` info function isdefined_alias_loop(t::Tuple) local prev s = "" for x in t @isdefined(prev) && (s = prev) prev = x end return s end @test Compiler.is_nothrow(Base.infer_effects(isdefined_alias_loop, (Tuple{String,String},))) # Effects of Base.hasfield (#50198) hf50198(s) = hasfield(typeof((;x=1, y=2)), s) f50198() = (hf50198(Ref(:x)[]); nothing) @test fully_eliminated(f50198) # Effects properly applied to flags by irinterp (#50311) f50311(x, s) = Symbol(s) g50311(x) = Val{f50311((1.0, x), "foo")}() @test fully_eliminated(g50311, Tuple{Float64}) # getglobal effects const my_defined_var = 42 @test Base.infer_effects() do getglobal(@__MODULE__, :my_defined_var, :monotonic) end |> Compiler.is_foldable_nothrow @test Base.infer_effects() do getglobal(@__MODULE__, :my_defined_var, :foo) end |> !Compiler.is_nothrow @test Base.infer_effects() do getglobal(@__MODULE__, :my_defined_var, :foo, nothing) end |> !Compiler.is_nothrow # irinterp should refine `:nothrow` information only if profitable Base.@assume_effects :nothrow function irinterp_nothrow_override(x, y) z = sin(y) if x return "julia" end return z end @test Base.infer_effects((Float64,)) do y isinf(y) && return zero(y) irinterp_nothrow_override(true, y) end |> Compiler.is_nothrow # Effects for :compilerbarrier f1_compilerbarrier(b) = Base.compilerbarrier(:type, b) f2_compilerbarrier(b) = Base.compilerbarrier(:conditional, b) f3_compilerbarrier(b) = Base.compilerbarrier(:blackbox, b) @test !Compiler.is_consistent(Base.infer_effects(f1_compilerbarrier, (Bool,))) @test Compiler.is_consistent(Base.infer_effects(f2_compilerbarrier, (Bool,))) # :blackbox is not consistent (prevents CSE/constant-folding) but is nothrow @test !Compiler.is_consistent(Base.infer_effects(f3_compilerbarrier, (Bool,))) @test Compiler.is_nothrow(Base.infer_effects(f3_compilerbarrier, (Bool,))) # Optimizer-refined effects function f1_optrefine(b) if Base.inferencebarrier(b) error() end return b end @test !Compiler.is_consistent(Base.infer_effects(f1_optrefine, (Bool,))) function f2_optrefine() if Ref(false)[] error() end return true end @test !Compiler.is_nothrow(Base.infer_effects(f2_optrefine; optimize=false)) @test Compiler.is_nothrow(Base.infer_effects(f2_optrefine)) function f3_optrefine(x) @fastmath sqrt(x) return x end @test !Compiler.is_consistent(Base.infer_effects(f3_optrefine; optimize=false)) @test Compiler.is_consistent(Base.infer_effects(f3_optrefine, (Float64,))) # Check that :consistent is properly modeled for throwing statements const GLOBAL_MUTABLE_SWITCH = Ref{Bool}(false) check_switch(switch::Base.RefValue{Bool}) = (switch[] && error(); return nothing) check_switch2() = check_switch(GLOBAL_MUTABLE_SWITCH) @test (Base.return_types(check_switch2) |> only) === Nothing GLOBAL_MUTABLE_SWITCH[] = true # Check that flipping the switch doesn't accidentally change the return type @test (Base.return_types(check_switch2) |> only) === Nothing @test !Compiler.is_consistent(Base.infer_effects(check_switch, (Base.RefValue{Bool},))) # post-opt IPO analysis refinement of `:effect_free`-ness function post_opt_refine_effect_free(y, c=true) x = Ref(c) if x[] return true else r = y[] isa Number y[] = nothing end return r end @test Compiler.is_effect_free(Base.infer_effects(post_opt_refine_effect_free, (Base.RefValue{Any},))) @test Base.infer_effects((Base.RefValue{Any},)) do y post_opt_refine_effect_free(y, true) end |> Compiler.is_effect_free # Check EA-based refinement of :effect_free Base.@assume_effects :nothrow @noinline _noinline_set!(x) = (x[] = 1; nothing) function set_ref_with_unused_arg_1(_) x = Ref(0) _noinline_set!(x) return nothing end function set_ref_with_unused_arg_2(_) x = @noinline Ref(0) _noinline_set!(x) return nothing end function set_arg_ref!(x) _noinline_set!(x) y = Ref(false) y[] && (Main.x = x) return nothing end function set_arr_with_unused_arg_1(_) x = Int[0] _noinline_set!(x) return nothing end function set_arr_with_unused_arg_2(_) x = @noinline Int[0] _noinline_set!(x) return nothing end function set_arg_arr!(x) _noinline_set!(x) y = Bool[false] y[] && (Main.x = x) return nothing end # This is inferable by type analysis only since the arguments have no mutable memory @test Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Base.RefValue{Int},))) @test Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Vector{Int},))) for func in (set_ref_with_unused_arg_1, set_ref_with_unused_arg_2, set_arr_with_unused_arg_1, set_arr_with_unused_arg_2) effects = Base.infer_effects(func, (Nothing,)) @test Compiler.is_inaccessiblememonly(effects) @test Compiler.is_effect_free(effects) end # These need EA @test Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_1, (Base.RefValue{Int},))) @test Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_2, (Base.RefValue{Int},))) @test Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_ref!, (Base.RefValue{Int},))) @test_broken Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_1, (Vector{Int},))) @test_broken Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_2, (Vector{Int},))) @test_broken Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_arr!, (Vector{Int},))) # EA-based refinement of :effect_free function f_EA_refine(ax, b) bx = Ref{Any}() @noinline bx[] = b return ax[] + b end @test Compiler.is_effect_free(Base.infer_effects(f_EA_refine, (Base.RefValue{Int},Int))) function issue51837(; openquotechar::Char, newlinechar::Char) ncodeunits(openquotechar) == 1 || throw(ArgumentError("`openquotechar` must be a single-byte character")) if !isnothing(newlinechar) ncodeunits(newlinechar) > 1 && throw(ArgumentError("`newlinechar` must be a single-byte character.")) end return nothing end @test Base.infer_effects() do openquotechar::Char, newlinechar::Char issue51837(; openquotechar, newlinechar) end |> !Compiler.is_nothrow @test_throws ArgumentError issue51837(; openquotechar='α', newlinechar='\n') # idempotency of effects derived by post-opt analysis callgetfield(x, f) = getfield(x, f, Base.@_boundscheck) @test Base.infer_effects(callgetfield, (Some{Any},Symbol)).noub === Compiler.NOUB_IF_NOINBOUNDS callgetfield1(x, f) = getfield(x, f, Base.@_boundscheck) callgetfield_simple(x, f) = callgetfield1(x, f) @test Base.infer_effects(callgetfield_simple, (Some{Any},Symbol)).noub === Base.infer_effects(callgetfield_simple, (Some{Any},Symbol)).noub === Compiler.ALWAYS_TRUE callgetfield2(x, f) = getfield(x, f, Base.@_boundscheck) callgetfield_inbounds(x, f) = @inbounds callgetfield2(x, f) @test Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === Compiler.ALWAYS_FALSE # noub modeling for memory ops let (memoryrefnew, memoryrefget, memoryref_isassigned, memoryrefset!) = (Core.memoryrefnew, Core.memoryrefget, Core.memoryref_isassigned, Core.memoryrefset!) function builtin_effects(@nospecialize xs...) interp = Compiler.NativeInterpreter() 𝕃 = Compiler.typeinf_lattice(interp) rt = Compiler.builtin_tfunction(interp, xs..., nothing) return Compiler.builtin_effects(𝕃, xs..., rt) end @test Compiler.is_noub(builtin_effects(memoryrefnew, Any[Memory,])) @test Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int])) @test Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int,Core.Const(true)])) @test !Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int,Core.Const(false)])) @test !Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int,Bool])) @test Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int,Int])) @test !Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Int,Vararg{Bool}])) @test !Compiler.is_noub(builtin_effects(memoryrefnew, Any[MemoryRef,Vararg{Any}])) @test Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(true)])) @test !Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(false)])) @test !Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Bool])) @test Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Int])) @test !Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Vararg{Bool}])) @test !Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Vararg{Any}])) @test Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(true)])) @test !Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(false)])) @test !Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Bool])) @test Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Int])) @test !Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Vararg{Bool}])) @test !Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Vararg{Any}])) @test Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(true)])) @test !Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(false)])) @test !Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Bool])) @test Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Int])) @test !Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Vararg{Bool}])) @test !Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Vararg{Any}])) # `:boundscheck` taint should be refined by post-opt analysis @test Base.infer_effects() do xs::Vector{Any}, i::Int memoryrefget(memoryrefnew(getfield(xs, :ref), i, Base.@_boundscheck), :not_atomic, Base.@_boundscheck) end |> Compiler.is_noub_if_noinbounds end # high level tests @test Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Int},Int))) @test Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Any},Int))) @test Compiler.is_noub_if_noinbounds(Base.infer_effects(setindex!, (Vector{Int},Int,Int))) @test Compiler.is_noub_if_noinbounds(Base.infer_effects(Base._setindex!, (Vector{Any},Any,Int))) @test Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Int},Int))) @test Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Any},Int))) @test Base.infer_effects((Vector{Int},Int)) do xs, i xs[i] end |> Compiler.is_noub @test Base.infer_effects((Vector{Any},Int)) do xs, i xs[i] end |> Compiler.is_noub @test Base.infer_effects((Vector{Int},Int,Int)) do xs, x, i xs[i] = x end |> Compiler.is_noub @test Base.infer_effects((Vector{Any},Any,Int)) do xs, x, i xs[i] = x end |> Compiler.is_noub @test Base.infer_effects((Vector{Int},Int)) do xs, i @inbounds xs[i] end |> !Compiler.is_noub @test Base.infer_effects((Vector{Any},Int)) do xs, i @inbounds xs[i] end |> !Compiler.is_noub Base.@propagate_inbounds getindex_propagate(xs, i) = xs[i] getindex_dont_propagate(xs, i) = xs[i] @test Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex_propagate, (Vector{Any},Int))) @test Compiler.is_noub(Base.infer_effects(getindex_dont_propagate, (Vector{Any},Int))) @test Base.infer_effects((Vector{Any},Int)) do xs, i @inbounds getindex_propagate(xs, i) end |> !Compiler.is_noub @test Base.infer_effects((Vector{Any},Int)) do xs, i @inbounds getindex_dont_propagate(xs, i) end |> Compiler.is_noub # refine `:nothrow` when `exct` is known to be `Bottom` @test Base.infer_exception_type(getindex, (Vector{Int},Int)) == BoundsError function getindex_nothrow(xs::Vector{Int}, i::Int) try return xs[i] catch err err isa BoundsError && return nothing rethrow(err) end end @test Compiler.is_nothrow(Base.infer_effects(getindex_nothrow, (Vector{Int}, Int))) # callsite `@assume_effects` annotation let ast = code_lowered((Int,)) do x Base.@assume_effects :total identity(x) end |> only ssaflag = ast.ssaflags[findfirst(!iszero, ast.ssaflags)::Int] override = Compiler.decode_statement_effects_override(ssaflag) # if this gets broken, check if this is synced with expr.jl @test override.consistent && override.effect_free && override.nothrow && override.terminates_globally && !override.terminates_locally && override.notaskstate && override.inaccessiblememonly && override.noub && !override.noub_if_noinbounds end @test Base.infer_effects((Float64,)) do x isinf(x) && return 0.0 return Base.@assume_effects :nothrow sin(x) end |> Compiler.is_nothrow let effects = Base.infer_effects((Vector{Float64},)) do xs isempty(xs) && return 0.0 Base.@assume_effects :nothrow begin x = Base.@assume_effects :noub @inbounds xs[1] isinf(x) && return 0.0 return sin(x) end end # all nested overrides should be applied @test Compiler.is_nothrow(effects) @test Compiler.is_noub(effects) end @test Base.infer_effects((Int,)) do x res = 1 0 ≤ x < 20 || error("bad fact") Base.@assume_effects :terminates_locally while x > 1 res *= x x -= 1 end return res end |> Compiler.is_terminates # https://github.com/JuliaLang/julia/issues/52531 const a52531 = Core.Ref(1) @eval getref52531() = $(QuoteNode(a52531)).x @test !Compiler.is_consistent(Base.infer_effects(getref52531)) let global set_a52531!, get_a52531 _a::Int = -1 set_a52531!(a::Int) = (_a = a; return get_a52531()) get_a52531() = _a end @test !Compiler.is_consistent(Base.infer_effects(set_a52531!, (Int,))) @test !Compiler.is_consistent(Base.infer_effects(get_a52531, ())) @test get_a52531() == -1 @test set_a52531!(1) == 1 @test get_a52531() == 1 let global is_initialized52531, set_initialized52531! _is_initialized = false set_initialized52531!(flag::Bool) = (_is_initialized = flag) is_initialized52531() = _is_initialized end top_52531(_) = (set_initialized52531!(true); nothing) @test !Compiler.is_consistent(Base.infer_effects(is_initialized52531)) @test !Compiler.is_removable_if_unused(Base.infer_effects(set_initialized52531!, (Bool,))) @test !is_initialized52531() top_52531(0) @test is_initialized52531() const ref52843 = Ref{Int}() @eval func52843() = ($ref52843[] = 1; nothing) @test !Compiler.is_foldable(Base.infer_effects(func52843)) let; Base.Experimental.@force_compile; func52843(); end @test ref52843[] == 1 @test Compiler.is_inaccessiblememonly(Base.infer_effects(identity∘identity, Tuple{Any})) @test Compiler.is_inaccessiblememonly(Base.infer_effects(()->Vararg, Tuple{})) # pointerref nothrow for invalid pointer @test !Compiler.intrinsic_nothrow(Core.Intrinsics.pointerref, Any[Type{Ptr{Vector{Int64}}}, Int, Int]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.pointerref, Any[Type{Ptr{T}} where T, Int, Int]) # post-opt :consistent-cy analysis correctness # https://github.com/JuliaLang/julia/issues/53508 @test !Compiler.is_consistent(Base.infer_effects(getindex, (UnitRange{Int},Int))) @test !Compiler.is_consistent(Base.infer_effects(getindex, (Base.OneTo{Int},Int))) @noinline f53613() = @assert isdefined(@__MODULE__, :v53613) g53613() = f53613() h53613() = g53613() @test !Compiler.is_consistent(Base.infer_effects(f53613)) @test !Compiler.is_consistent(Base.infer_effects(g53613)) @test_throws AssertionError f53613() @test_throws AssertionError g53613() @test_throws AssertionError h53613() global v53613 = nothing @test f53613() === nothing @test g53613() === nothing @test h53613() === nothing # tuple/svec effects @test Base.infer_effects((Vector{Any},)) do xs Core.tuple(xs...) end |> Compiler.is_nothrow @test Base.infer_effects((Vector{Any},)) do xs Core.svec(xs...) end |> Compiler.is_nothrow # effects for unknown `:foreigncall`s @test Base.infer_effects() do @ccall unsafecall()::Cvoid end == Compiler.EFFECTS_UNKNOWN # fpext @test Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float32}, Float16]) @test Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float64}, Float16]) @test Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float64}, Float32]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float16}, Float16]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float16}, Float32]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float32}, Float32]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float32}, Float64]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Int32}, Float16]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.fpext, Any[Type{Float32}, Int16]) # Float intrinsics require float arguments @test Base.infer_effects((Int16,)) do x return Core.Intrinsics.abs_float(x) end |> !Compiler.is_nothrow @test Base.infer_effects((Int32, Int32)) do x, y return Core.Intrinsics.add_float(x, y) end |> !Compiler.is_nothrow @test Base.infer_effects((Int32, Int32)) do x, y return Core.Intrinsics.add_float(x, y) end |> !Compiler.is_nothrow @test Base.infer_effects((Int64, Int64, Int64)) do x, y, z return Core.Intrinsics.fma_float(x, y, z) end |> !Compiler.is_nothrow @test Base.infer_effects((Int64,)) do x return Core.Intrinsics.fptoui(UInt32, x) end |> !Compiler.is_nothrow @test Base.infer_effects((Int64,)) do x return Core.Intrinsics.fptosi(Int32, x) end |> !Compiler.is_nothrow @test Base.infer_effects((Int64,)) do x return Core.Intrinsics.sitofp(Int64, x) end |> !Compiler.is_nothrow @test Base.infer_effects((UInt64,)) do x return Core.Intrinsics.uitofp(Int64, x) end |> !Compiler.is_nothrow # effects modeling for pointer-related intrinsics let effects = Base.infer_effects(Core.Intrinsics.pointerref, Tuple{Vararg{Any}}) @test !Compiler.is_consistent(effects) @test Compiler.is_effect_free(effects) @test !Compiler.is_inaccessiblememonly(effects) end let effects = Base.infer_effects(Core.Intrinsics.pointerset, Tuple{Vararg{Any}}) @test Compiler.is_consistent(effects) @test !Compiler.is_effect_free(effects) end @test Compiler.intrinsic_nothrow(Core.Intrinsics.add_ptr, Any[Ptr{Int}, UInt]) @test Compiler.intrinsic_nothrow(Core.Intrinsics.sub_ptr, Any[Ptr{Int}, UInt]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.add_ptr, Any[UInt, UInt]) @test !Compiler.intrinsic_nothrow(Core.Intrinsics.sub_ptr, Any[UInt, UInt]) @test Compiler.is_nothrow(Base.infer_effects(+, Tuple{Ptr{UInt8}, UInt})) # effects modeling for atomic intrinsics # these functions especially need to be marked !effect_free since they imply synchronization for atomicfunc = Any[ Core.Intrinsics.atomic_pointerref, Core.Intrinsics.atomic_pointerset, Core.Intrinsics.atomic_pointerswap, Core.Intrinsics.atomic_pointerreplace, Core.Intrinsics.atomic_fence] @test !Compiler.is_effect_free(Base.infer_effects(atomicfunc, Tuple{Vararg{Any}})) end # effects modeling for intrinsics that can do arbitrary things let effects = Base.infer_effects(Core.Intrinsics.llvmcall, Tuple{Vararg{Any}}) @test effects == Compiler.Effects() end let effects = Base.infer_effects(Core.Intrinsics.atomic_pointermodify, Tuple{Vararg{Any}}) @test effects == Compiler.Effects() end # JuliaLang/julia#57780 let effects = Base.infer_effects(Base._unsetindex!, (MemoryRef{String},)) @test !Compiler.is_effect_free(effects) end # builtin functions that can do arbitrary things should have the top effects @test Base.infer_effects(Core._call_in_world_total, Tuple{Vararg{Any}}) == Compiler.Effects() @test Base.infer_effects(Core.invoke_in_world, Tuple{Vararg{Any}}) == Compiler.Effects() @test Base.infer_effects(invokelatest, Tuple{Vararg{Any}}) == Compiler.Effects() @test Base.infer_effects(invoke, Tuple{Vararg{Any}}) == Compiler.Effects() bitsizeof_int() = Core.bitsizeof(Int) let effects = Base.infer_effects(bitsizeof_int) @test Compiler.is_foldable_nothrow(effects) @test Compiler.is_inaccessiblememonly(effects) end # Core._svec_ref effects modeling (required for external abstract interpreter that doesn't run optimization) let effects = Base.infer_effects((Core.SimpleVector,Int); optimize=false) do svec, i Core._svec_ref(svec, i) end @test Compiler.is_consistent(effects) @test Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end @test Compiler.is_nothrow(Base.infer_effects(length, (Core.SimpleVector,))) # https://github.com/JuliaLang/julia/issues/60009 function null_offset(offset) Ptr{UInt8}(C_NULL) + offset end @test null_offset(Int(100)) == Ptr{UInt8}(UInt(100)) # https://github.com/JuliaLang/julia/issues/61435 function catch_error_61435(f, x) try f(x) catch return :caught end end let f = (x) -> Core.Intrinsics.sext_int(Int16, x) @test Compiler.is_nothrow(Base.infer_effects(f, (Int8,))) @test !Compiler.is_nothrow(Base.infer_effects(f, (Int16,))) @test !Compiler.is_nothrow(Base.infer_effects(f, (Int32,))) @test catch_error_61435(f, Int16(0)) === :caught end let f = (x) -> Core.Intrinsics.zext_int(UInt16, x) @test Compiler.is_nothrow(Base.infer_effects(f, (UInt8,))) @test !Compiler.is_nothrow(Base.infer_effects(f, (UInt16,))) @test !Compiler.is_nothrow(Base.infer_effects(f, (UInt32,))) @test catch_error_61435(f, UInt16(0)) === :caught end let f = (x) -> Core.Intrinsics.trunc_int(Int16, x) @test !Compiler.is_nothrow(Base.infer_effects(f, (Int8,))) @test !Compiler.is_nothrow(Base.infer_effects(f, (Int16,))) @test Compiler.is_nothrow(Base.infer_effects(f, (Int32,))) @test catch_error_61435(f, Int16(0)) === :caught end # Intrinsic width checks use logical primitive widths rather than storage sizes. primitive type EffectsUInt17 17 end primitive type EffectsUInt23 23 end let f = (x) -> Core.Intrinsics.bitcast(EffectsUInt17, x) @test !Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt23,))) @test Base.infer_exception_type(f, (EffectsUInt23,)) === ErrorException @test !fully_eliminated((EffectsUInt23,)) do x f(x) return nothing end end let f = (x) -> Core.Intrinsics.zext_int(EffectsUInt23, x) @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt17,))) end let f = (x) -> Core.Intrinsics.sext_int(EffectsUInt23, x) @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt17,))) end let f = (x) -> Core.Intrinsics.trunc_int(EffectsUInt17, x) @test Compiler.is_nothrow(Base.infer_effects(f, (EffectsUInt23,))) end # issue #57324 module Issue57324 struct T <: AbstractVector{Float64} m::Memory{UInt64} end function f(w) r = Base.OneTo(w.m[1]) setindex!(w, 0.0, r[1]) end Base.setindex!(w::T, v, i::Int) = _setindex!(w, i) function _setindex!(w, i) w.m[w.m[1]] = 0 > i ? nothing : 0 w end Base.size(::T) = (0,) end let effects = Base.infer_effects(Issue57324.f, (Issue57324.T,)) @test Compiler.is_terminates(effects) @test Compiler.is_notaskstate(effects) @test Compiler.is_nortcall(effects) end # issue #61590 @test !Compiler.is_consistent(Base.infer_effects(getproperty, (Core.TypeName, Symbol))) @test !Compiler.is_consistent(Base.infer_effects(getfield, (Core.TypeName, Symbol))) # task_result_type effects modeling (should have !consistent effect) let effects = Base.infer_effects(Core.task_result_type, (Task,)) @test !Compiler.is_consistent(effects) # !consistent bit should be set @test Compiler.is_effect_free(effects) @test Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) end let effects = Base.infer_effects(Core.task_result_type, (Union{Task,Int},)) @test Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) end for argtypes in ((), (Int,), (Task, Task)) @test !Compiler.is_nothrow(Base.infer_effects(Core.task_result_type, argtypes)) end # Core._task effects modeling: creating a task terminates and has no UB, but # accesses task state (scope inheritance, parent RNG split) and may throw let effects = Base.infer_effects(Core._task, (Function, Int)) @test !Compiler.is_consistent(effects) @test !Compiler.is_effect_free(effects) @test !Compiler.is_nothrow(effects) @test Compiler.is_terminates(effects) @test !Compiler.is_notaskstate(effects) @test Compiler.is_noub(effects) end