Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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", [
Expand Down
158 changes: 158 additions & 0 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
@@ -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
156 changes: 1 addition & 155 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down