diff --git a/test/choosetests.jl b/test/choosetests.jl index 099dfa18a71c5..f5775bbc00911 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -140,8 +140,8 @@ function choosetests(choices = []) "strings/io", "strings/types"]) # do subarray before sparse but after linalg filtertests!(tests, "subarray") - filtertests!(tests, "compiler", ["compiler/inference", "compiler/validation", - "compiler/ssair", "compiler/irpasses", "compiler/codegen", + filtertests!(tests, "compiler", ["compiler/inference", "compiler/effects", + "compiler/validation", "compiler/ssair", "compiler/irpasses", "compiler/codegen", "compiler/inline", "compiler/contextual", "compiler/AbstractInterpreter", "compiler/EscapeAnalysis/local", "compiler/EscapeAnalysis/interprocedural"]) filtertests!(tests, "compiler/EscapeAnalysis", [ diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl new file mode 100644 index 0000000000000..960ea2de817be --- /dev/null +++ b/test/compiler/effects.jl @@ -0,0 +1,158 @@ +using Test +include("irutils.jl") + +# control flow backedge should taint `terminates` +@test Base.infer_effects((Int,)) do n + for i = 1:n; end +end |> !Core.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 |> Core.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 |> Core.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 |> Core.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 |> Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + return Ref(x) +end |> !Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + return x < 0 ? Ref(x) : nothing +end |> !Core.Compiler.is_consistent +@test Base.infer_effects((Int,)) do x + if x < 0 + throw(DomainError(x, lazy"$x is negative")) + end + return nothing +end |> Core.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 + +# 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 getfield(sin, x)), n) +end +@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] + +# 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 |> Core.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 |> !Core.Compiler.is_foldable +@test !fully_eliminated() do + entry_to_be_invalidated('a') +end + +@test !Core.Compiler.builtin_nothrow(Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any) + +# Nothrow 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, ()) + @test !Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) +end +# Nothrow for setglobal! +global SETGLOBAL!_NOTHROW::Int = 0 +let effects = Base.infer_effects() do + setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42) + end + @test Core.Compiler.is_nothrow(effects) +end + +# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet, +# as the cached effects can be easily wrong otherwise +# since the inference curently doesn't track "world-age" of global variables +@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 +setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42) +let effects = Base.infer_effects() do + global_assignment_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + setglobal!_nothrow_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +global UNDEFINEDYET::String = "0" +let effects = Base.infer_effects() do + global_assignment_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +let effects = Base.infer_effects() do + setglobal!_nothrow_undefinedyet() + end + @test !Core.Compiler.is_nothrow(effects) +end +@test_throws ErrorException setglobal!_nothrow_undefinedyet() + +# Nothrow for setfield! +mutable struct SetfieldNothrow + x::Int +end +f_setfield_nothrow() = SetfieldNothrow(0).x = 1 +let effects = Base.infer_effects(f_setfield_nothrow, ()) + # Technically effect free even though we use the heap, since the + # object doesn't escape, but the compiler doesn't know that. + #@test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_nothrow(effects) +end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 2ae3c09228eb3..3772ce0d2440e 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4056,27 +4056,6 @@ end end |> only === Union{} end -# 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 - -# 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 getfield(sin, x)), n) -end -@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2] - -@test !Core.Compiler.builtin_nothrow(Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any) - # Test that max_methods works as expected @Base.Experimental.max_methods 1 function f_max_methods end f_max_methods(x::Int) = 1 @@ -4102,145 +4081,12 @@ end Core.Compiler.return_type(+, NTuple{2, Rational}) end == Rational +# vararg-tuple comparison within `PartialStruct` # https://github.com/JuliaLang/julia/issues/44965 let t = Core.Compiler.tuple_tfunc(Any[Core.Const(42), Vararg{Any}]) @test Core.Compiler.issimplertype(t, t) end -# 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 - -# 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 |> Core.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 |> !Core.Compiler.is_foldable -@test !fully_eliminated() do - entry_to_be_invalidated('a') -end - -# control flow backedge should taint `terminates` -@test Base.infer_effects((Int,)) do n - for i = 1:n; end -end |> !Core.Compiler.is_terminates - -# Nothrow 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, ()) - @test !Core.Compiler.is_effect_free(effects) - @test Core.Compiler.is_nothrow(effects) -end -# Nothrow for setglobal! -global SETGLOBAL!_NOTHROW::Int = 0 -let effects = Base.infer_effects() do - setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42) - end - @test Core.Compiler.is_nothrow(effects) -end - -# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet, -# as the cached effects can be easily wrong otherwise -# since the inference curently doesn't track "world-age" of global variables -@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42 -setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42) -let effects = Base.infer_effects() do - global_assignment_undefinedyet() - end - @test !Core.Compiler.is_nothrow(effects) -end -let effects = Base.infer_effects() do - setglobal!_nothrow_undefinedyet() - end - @test !Core.Compiler.is_nothrow(effects) -end -global UNDEFINEDYET::String = "0" -let effects = Base.infer_effects() do - global_assignment_undefinedyet() - end - @test !Core.Compiler.is_nothrow(effects) -end -let effects = Base.infer_effects() do - setglobal!_nothrow_undefinedyet() - end - @test !Core.Compiler.is_nothrow(effects) -end -@test_throws ErrorException setglobal!_nothrow_undefinedyet() - -# Nothrow for setfield! -mutable struct SetfieldNothrow - x::Int -end -f_setfield_nothrow() = SetfieldNothrow(0).x = 1 -let effects = Base.infer_effects(f_setfield_nothrow, ()) - # Technically effect free even though we use the heap, since the - # object doesn't escape, but the compiler doesn't know that. - #@test Core.Compiler.is_effect_free(effects) - @test Core.Compiler.is_nothrow(effects) -end - -# 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 |> Core.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 |> Core.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 |> Core.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 |> Core.Compiler.is_consistent -@test Base.infer_effects((Int,)) do x - return Ref(x) -end |> !Core.Compiler.is_consistent -@test Base.infer_effects((Int,)) do x - return x < 0 ? Ref(x) : nothing -end |> !Core.Compiler.is_consistent -@test Base.infer_effects((Int,)) do x - if x < 0 - throw(DomainError(x, lazy"$x is negative")) - end - return nothing -end |> Core.Compiler.is_foldable - # check the inference convergence with an empty vartable: # the inference state for the toplevel chunk below will have an empty vartable, # and so we may fail to terminate (or optimize) it if we don't update vartables correctly