Skip to content

Commit 0aeb0ad

Browse files
authored
compiler: move effects-related test cases into dedicated file (#45992)
1 parent 558fbb6 commit 0aeb0ad

File tree

3 files changed

+161
-157
lines changed

3 files changed

+161
-157
lines changed

test/choosetests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ function choosetests(choices = [])
140140
"strings/io", "strings/types"])
141141
# do subarray before sparse but after linalg
142142
filtertests!(tests, "subarray")
143-
filtertests!(tests, "compiler", ["compiler/inference", "compiler/validation",
144-
"compiler/ssair", "compiler/irpasses", "compiler/codegen",
143+
filtertests!(tests, "compiler", ["compiler/inference", "compiler/effects",
144+
"compiler/validation", "compiler/ssair", "compiler/irpasses", "compiler/codegen",
145145
"compiler/inline", "compiler/contextual", "compiler/AbstractInterpreter",
146146
"compiler/EscapeAnalysis/local", "compiler/EscapeAnalysis/interprocedural"])
147147
filtertests!(tests, "compiler/EscapeAnalysis", [

test/compiler/effects.jl

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using Test
2+
include("irutils.jl")
3+
4+
# control flow backedge should taint `terminates`
5+
@test Base.infer_effects((Int,)) do n
6+
for i = 1:n; end
7+
end |> !Core.Compiler.is_terminates
8+
9+
# refine :consistent-cy effect inference using the return type information
10+
@test Base.infer_effects((Any,)) do x
11+
taint = Ref{Any}(x) # taints :consistent-cy, but will be adjusted
12+
throw(taint)
13+
end |> Core.Compiler.is_consistent
14+
@test Base.infer_effects((Int,)) do x
15+
if x < 0
16+
taint = Ref(x) # taints :consistent-cy, but will be adjusted
17+
throw(DomainError(x, taint))
18+
end
19+
return nothing
20+
end |> Core.Compiler.is_consistent
21+
@test Base.infer_effects((Int,)) do x
22+
if x < 0
23+
taint = Ref(x) # taints :consistent-cy, but will be adjusted
24+
throw(DomainError(x, taint))
25+
end
26+
return x == 0 ? nothing : x # should `Union` of isbitstype objects nicely
27+
end |> Core.Compiler.is_consistent
28+
@test Base.infer_effects((Symbol,Any)) do s, x
29+
if s === :throw
30+
taint = Ref{Any}(":throw option given") # taints :consistent-cy, but will be adjusted
31+
throw(taint)
32+
end
33+
return s # should handle `Symbol` nicely
34+
end |> Core.Compiler.is_consistent
35+
@test Base.infer_effects((Int,)) do x
36+
return Ref(x)
37+
end |> !Core.Compiler.is_consistent
38+
@test Base.infer_effects((Int,)) do x
39+
return x < 0 ? Ref(x) : nothing
40+
end |> !Core.Compiler.is_consistent
41+
@test Base.infer_effects((Int,)) do x
42+
if x < 0
43+
throw(DomainError(x, lazy"$x is negative"))
44+
end
45+
return nothing
46+
end |> Core.Compiler.is_foldable
47+
48+
# effects propagation for `Core.invoke` calls
49+
# https://github.com/JuliaLang/julia/issues/44763
50+
global x44763::Int = 0
51+
increase_x44763!(n) = (global x44763; x44763 += n)
52+
invoke44763(x) = @invoke increase_x44763!(x)
53+
@test Base.return_types() do
54+
invoke44763(42)
55+
end |> only === Int
56+
@test x44763 == 0
57+
58+
# Test that purity doesn't try to accidentally run unreachable code due to
59+
# boundscheck elimination
60+
function f_boundscheck_elim(n)
61+
# Inbounds here assumes that this is only ever called with n==0, but of
62+
# course the compiler has no way of knowing that, so it must not attempt
63+
# to run the @inbounds `getfield(sin, 1)`` that ntuple generates.
64+
ntuple(x->(@inbounds getfield(sin, x)), n)
65+
end
66+
@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2]
67+
68+
# Test that purity modeling doesn't accidentally introduce new world age issues
69+
f_redefine_me(x) = x+1
70+
f_call_redefine() = f_redefine_me(0)
71+
f_mk_opaque() = Base.Experimental.@opaque ()->Base.inferencebarrier(f_call_redefine)()
72+
const op_capture_world = f_mk_opaque()
73+
f_redefine_me(x) = x+2
74+
@test op_capture_world() == 1
75+
@test f_mk_opaque()() == 2
76+
77+
# backedge insertion for Any-typed, effect-free frame
78+
const CONST_DICT = let d = Dict()
79+
for c in 'A':'z'
80+
push!(d, c => Int(c))
81+
end
82+
d
83+
end
84+
Base.@assume_effects :foldable getcharid(c) = CONST_DICT[c]
85+
@noinline callf(f, args...) = f(args...)
86+
function entry_to_be_invalidated(c)
87+
return callf(getcharid, c)
88+
end
89+
@test Base.infer_effects((Char,)) do x
90+
entry_to_be_invalidated(x)
91+
end |> Core.Compiler.is_foldable
92+
@test fully_eliminated(; retval=97) do
93+
entry_to_be_invalidated('a')
94+
end
95+
getcharid(c) = CONST_DICT[c] # now this is not eligible for concrete evaluation
96+
@test Base.infer_effects((Char,)) do x
97+
entry_to_be_invalidated(x)
98+
end |> !Core.Compiler.is_foldable
99+
@test !fully_eliminated() do
100+
entry_to_be_invalidated('a')
101+
end
102+
103+
@test !Core.Compiler.builtin_nothrow(Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any)
104+
105+
# Nothrow for assignment to globals
106+
global glob_assign_int::Int = 0
107+
f_glob_assign_int() = global glob_assign_int += 1
108+
let effects = Base.infer_effects(f_glob_assign_int, ())
109+
@test !Core.Compiler.is_effect_free(effects)
110+
@test Core.Compiler.is_nothrow(effects)
111+
end
112+
# Nothrow for setglobal!
113+
global SETGLOBAL!_NOTHROW::Int = 0
114+
let effects = Base.infer_effects() do
115+
setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42)
116+
end
117+
@test Core.Compiler.is_nothrow(effects)
118+
end
119+
120+
# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet,
121+
# as the cached effects can be easily wrong otherwise
122+
# since the inference curently doesn't track "world-age" of global variables
123+
@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42
124+
setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42)
125+
let effects = Base.infer_effects() do
126+
global_assignment_undefinedyet()
127+
end
128+
@test !Core.Compiler.is_nothrow(effects)
129+
end
130+
let effects = Base.infer_effects() do
131+
setglobal!_nothrow_undefinedyet()
132+
end
133+
@test !Core.Compiler.is_nothrow(effects)
134+
end
135+
global UNDEFINEDYET::String = "0"
136+
let effects = Base.infer_effects() do
137+
global_assignment_undefinedyet()
138+
end
139+
@test !Core.Compiler.is_nothrow(effects)
140+
end
141+
let effects = Base.infer_effects() do
142+
setglobal!_nothrow_undefinedyet()
143+
end
144+
@test !Core.Compiler.is_nothrow(effects)
145+
end
146+
@test_throws ErrorException setglobal!_nothrow_undefinedyet()
147+
148+
# Nothrow for setfield!
149+
mutable struct SetfieldNothrow
150+
x::Int
151+
end
152+
f_setfield_nothrow() = SetfieldNothrow(0).x = 1
153+
let effects = Base.infer_effects(f_setfield_nothrow, ())
154+
# Technically effect free even though we use the heap, since the
155+
# object doesn't escape, but the compiler doesn't know that.
156+
#@test Core.Compiler.is_effect_free(effects)
157+
@test Core.Compiler.is_nothrow(effects)
158+
end

test/compiler/inference.jl

Lines changed: 1 addition & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -4056,27 +4056,6 @@ end
40564056
end |> only === Union{}
40574057
end
40584058

4059-
# Test that purity modeling doesn't accidentally introduce new world age issues
4060-
f_redefine_me(x) = x+1
4061-
f_call_redefine() = f_redefine_me(0)
4062-
f_mk_opaque() = @Base.Experimental.opaque ()->Base.inferencebarrier(f_call_redefine)()
4063-
const op_capture_world = f_mk_opaque()
4064-
f_redefine_me(x) = x+2
4065-
@test op_capture_world() == 1
4066-
@test f_mk_opaque()() == 2
4067-
4068-
# Test that purity doesn't try to accidentally run unreachable code due to
4069-
# boundscheck elimination
4070-
function f_boundscheck_elim(n)
4071-
# Inbounds here assumes that this is only ever called with n==0, but of
4072-
# course the compiler has no way of knowing that, so it must not attempt
4073-
# to run the @inbounds `getfield(sin, 1)`` that ntuple generates.
4074-
ntuple(x->(@inbounds getfield(sin, x)), n)
4075-
end
4076-
@test Tuple{} <: code_typed(f_boundscheck_elim, Tuple{Int})[1][2]
4077-
4078-
@test !Core.Compiler.builtin_nothrow(Core.get_binding_type, Any[Rational{Int}, Core.Const(:foo)], Any)
4079-
40804059
# Test that max_methods works as expected
40814060
@Base.Experimental.max_methods 1 function f_max_methods end
40824061
f_max_methods(x::Int) = 1
@@ -4102,145 +4081,12 @@ end
41024081
Core.Compiler.return_type(+, NTuple{2, Rational})
41034082
end == Rational
41044083

4084+
# vararg-tuple comparison within `PartialStruct`
41054085
# https://github.com/JuliaLang/julia/issues/44965
41064086
let t = Core.Compiler.tuple_tfunc(Any[Core.Const(42), Vararg{Any}])
41074087
@test Core.Compiler.issimplertype(t, t)
41084088
end
41094089

4110-
# https://github.com/JuliaLang/julia/issues/44763
4111-
global x44763::Int = 0
4112-
increase_x44763!(n) = (global x44763; x44763 += n)
4113-
invoke44763(x) = @invoke increase_x44763!(x)
4114-
@test Base.return_types() do
4115-
invoke44763(42)
4116-
end |> only === Int
4117-
@test x44763 == 0
4118-
4119-
# backedge insertion for Any-typed, effect-free frame
4120-
const CONST_DICT = let d = Dict()
4121-
for c in 'A':'z'
4122-
push!(d, c => Int(c))
4123-
end
4124-
d
4125-
end
4126-
Base.@assume_effects :foldable getcharid(c) = CONST_DICT[c]
4127-
@noinline callf(f, args...) = f(args...)
4128-
function entry_to_be_invalidated(c)
4129-
return callf(getcharid, c)
4130-
end
4131-
@test Base.infer_effects((Char,)) do x
4132-
entry_to_be_invalidated(x)
4133-
end |> Core.Compiler.is_foldable
4134-
@test fully_eliminated(; retval=97) do
4135-
entry_to_be_invalidated('a')
4136-
end
4137-
getcharid(c) = CONST_DICT[c] # now this is not eligible for concrete evaluation
4138-
@test Base.infer_effects((Char,)) do x
4139-
entry_to_be_invalidated(x)
4140-
end |> !Core.Compiler.is_foldable
4141-
@test !fully_eliminated() do
4142-
entry_to_be_invalidated('a')
4143-
end
4144-
4145-
# control flow backedge should taint `terminates`
4146-
@test Base.infer_effects((Int,)) do n
4147-
for i = 1:n; end
4148-
end |> !Core.Compiler.is_terminates
4149-
4150-
# Nothrow for assignment to globals
4151-
global glob_assign_int::Int = 0
4152-
f_glob_assign_int() = global glob_assign_int += 1
4153-
let effects = Base.infer_effects(f_glob_assign_int, ())
4154-
@test !Core.Compiler.is_effect_free(effects)
4155-
@test Core.Compiler.is_nothrow(effects)
4156-
end
4157-
# Nothrow for setglobal!
4158-
global SETGLOBAL!_NOTHROW::Int = 0
4159-
let effects = Base.infer_effects() do
4160-
setglobal!(@__MODULE__, :SETGLOBAL!_NOTHROW, 42)
4161-
end
4162-
@test Core.Compiler.is_nothrow(effects)
4163-
end
4164-
4165-
# we should taint `nothrow` if the binding doesn't exist and isn't fixed yet,
4166-
# as the cached effects can be easily wrong otherwise
4167-
# since the inference curently doesn't track "world-age" of global variables
4168-
@eval global_assignment_undefinedyet() = $(GlobalRef(@__MODULE__, :UNDEFINEDYET)) = 42
4169-
setglobal!_nothrow_undefinedyet() = setglobal!(@__MODULE__, :UNDEFINEDYET, 42)
4170-
let effects = Base.infer_effects() do
4171-
global_assignment_undefinedyet()
4172-
end
4173-
@test !Core.Compiler.is_nothrow(effects)
4174-
end
4175-
let effects = Base.infer_effects() do
4176-
setglobal!_nothrow_undefinedyet()
4177-
end
4178-
@test !Core.Compiler.is_nothrow(effects)
4179-
end
4180-
global UNDEFINEDYET::String = "0"
4181-
let effects = Base.infer_effects() do
4182-
global_assignment_undefinedyet()
4183-
end
4184-
@test !Core.Compiler.is_nothrow(effects)
4185-
end
4186-
let effects = Base.infer_effects() do
4187-
setglobal!_nothrow_undefinedyet()
4188-
end
4189-
@test !Core.Compiler.is_nothrow(effects)
4190-
end
4191-
@test_throws ErrorException setglobal!_nothrow_undefinedyet()
4192-
4193-
# Nothrow for setfield!
4194-
mutable struct SetfieldNothrow
4195-
x::Int
4196-
end
4197-
f_setfield_nothrow() = SetfieldNothrow(0).x = 1
4198-
let effects = Base.infer_effects(f_setfield_nothrow, ())
4199-
# Technically effect free even though we use the heap, since the
4200-
# object doesn't escape, but the compiler doesn't know that.
4201-
#@test Core.Compiler.is_effect_free(effects)
4202-
@test Core.Compiler.is_nothrow(effects)
4203-
end
4204-
4205-
# refine :consistent-cy effect inference using the return type information
4206-
@test Base.infer_effects((Any,)) do x
4207-
taint = Ref{Any}(x) # taints :consistent-cy, but will be adjusted
4208-
throw(taint)
4209-
end |> Core.Compiler.is_consistent
4210-
@test Base.infer_effects((Int,)) do x
4211-
if x < 0
4212-
taint = Ref(x) # taints :consistent-cy, but will be adjusted
4213-
throw(DomainError(x, taint))
4214-
end
4215-
return nothing
4216-
end |> Core.Compiler.is_consistent
4217-
@test Base.infer_effects((Int,)) do x
4218-
if x < 0
4219-
taint = Ref(x) # taints :consistent-cy, but will be adjusted
4220-
throw(DomainError(x, taint))
4221-
end
4222-
return x == 0 ? nothing : x # should `Union` of isbitstype objects nicely
4223-
end |> Core.Compiler.is_consistent
4224-
@test Base.infer_effects((Symbol,Any)) do s, x
4225-
if s === :throw
4226-
taint = Ref{Any}(":throw option given") # taints :consistent-cy, but will be adjusted
4227-
throw(taint)
4228-
end
4229-
return s # should handle `Symbol` nicely
4230-
end |> Core.Compiler.is_consistent
4231-
@test Base.infer_effects((Int,)) do x
4232-
return Ref(x)
4233-
end |> !Core.Compiler.is_consistent
4234-
@test Base.infer_effects((Int,)) do x
4235-
return x < 0 ? Ref(x) : nothing
4236-
end |> !Core.Compiler.is_consistent
4237-
@test Base.infer_effects((Int,)) do x
4238-
if x < 0
4239-
throw(DomainError(x, lazy"$x is negative"))
4240-
end
4241-
return nothing
4242-
end |> Core.Compiler.is_foldable
4243-
42444090
# check the inference convergence with an empty vartable:
42454091
# the inference state for the toplevel chunk below will have an empty vartable,
42464092
# and so we may fail to terminate (or optimize) it if we don't update vartables correctly

0 commit comments

Comments
 (0)