Skip to content

Commit 2b65954

Browse files
committed
Add some broken tests for invalidation
1 parent 97e3fe8 commit 2b65954

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

base/expr.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ end
7777

7878
==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
7979
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
80+
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
8081

8182
"""
8283
macroexpand(m::Module, x; recursive=true)

test/worlds.jl

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,134 @@ end
199199
notify(c26506_1)
200200
wait(c26506_2)
201201
@test result26506[1] == 3
202+
203+
204+
## Invalidation tests
205+
206+
function instance(f, types)
207+
m = which(f, types)
208+
inst = nothing
209+
tt = Tuple{typeof(f), types...}
210+
specs = m.specializations
211+
if isa(specs, Nothing)
212+
elseif isa(specs, Core.SimpleVector)
213+
for i = 1:length(specs)
214+
if isassigned(specs, i)
215+
mi = specs[i]::Core.MethodInstance
216+
if mi.specTypes === tt
217+
inst = mi
218+
break
219+
end
220+
end
221+
end
222+
else
223+
Base.visit(specs) do mi
224+
if mi.specTypes === tt
225+
inst = mi
226+
end
227+
end
228+
end
229+
return inst
230+
end
231+
232+
function worlds(mi::Core.MethodInstance)
233+
w = Tuple{UInt,UInt}[]
234+
if isdefined(mi, :cache)
235+
ci = mi.cache
236+
push!(w, (ci.min_world, ci.max_world))
237+
while isdefined(ci, :next)
238+
ci = ci.next
239+
push!(w, (ci.min_world, ci.max_world))
240+
end
241+
end
242+
return w
243+
end
244+
245+
# avoid adding this to Base
246+
function equal(ci1::Core.CodeInfo, ci2::Core.CodeInfo)
247+
return ci1.code == ci2.code &&
248+
ci1.codelocs == ci2.codelocs &&
249+
ci1.ssavaluetypes == ci2.ssavaluetypes &&
250+
ci1.ssaflags == ci2.ssaflags &&
251+
ci1.method_for_inference_limit_heuristics == ci2.method_for_inference_limit_heuristics &&
252+
ci1.linetable == ci2.linetable &&
253+
ci1.slotnames == ci2.slotnames &&
254+
ci1.slotflags == ci2.slotflags &&
255+
ci1.slottypes == ci2.slottypes &&
256+
ci1.rettype == ci2.rettype
257+
end
258+
equal(p1::Pair, p2::Pair) = p1.second == p2.second && equal(p1.first, p2.first)
259+
260+
## Union-splitting based on state-of-the-world: check that each invalidation corresponds to new code
261+
applyf(c) = f(c[1])
262+
f(::Int) = 1
263+
f(::Float64) = 2
264+
applyf([1])
265+
applyf([1.0])
266+
applyf(Any[1])
267+
wint = worlds(instance(applyf, (Vector{Int},)))
268+
wfloat = worlds(instance(applyf, (Vector{Float64},)))
269+
wany2 = worlds(instance(applyf, (Vector{Any},)))
270+
src2 = code_typed(applyf, (Vector{Any},))[1]
271+
f(::String) = 3
272+
applyf(Any[1])
273+
@test worlds(instance(applyf, (Vector{Int},))) == wint
274+
@test worlds(instance(applyf, (Vector{Float64},))) == wfloat
275+
wany3 = worlds(instance(applyf, (Vector{Any},)))
276+
src3 = code_typed(applyf, (Vector{Any},))[1]
277+
@test (wany3 == wany2) == equal(src3, src2) # don't invalidate unless you also change the code
278+
f(::Vector{Int}) = 4
279+
applyf(Any[1])
280+
wany4 = worlds(instance(applyf, (Vector{Any},)))
281+
src4 = code_typed(applyf, (Vector{Any},))[1]
282+
@test (wany4 == wany3) == equal(src4, src3)
283+
f(::Dict) = 5
284+
applyf(Any[1])
285+
wany5 = worlds(instance(applyf, (Vector{Any},)))
286+
src5 = code_typed(applyf, (Vector{Any},))[1]
287+
@test (wany5 == wany4) == equal(src5, src4)
288+
f(::Set) = 6 # with current settings, this shouldn't invalidate
289+
applyf(Any[1])
290+
wany6 = worlds(instance(applyf, (Vector{Any},)))
291+
src6 = code_typed(applyf, (Vector{Any},))[1]
292+
@test (wany6 == wany5) == equal(src6, src5)
293+
294+
## ambiguities do not trigger invalidation
295+
using Printf
296+
Printf.gen("%f")
297+
mi = instance(+, (AbstractChar, UInt8))
298+
w = worlds(mi)
299+
300+
abstract type FixedPoint{T <: Integer} <: Real end
301+
struct Normed <: FixedPoint{UInt8}
302+
i::UInt8
303+
Normed(i::Integer, _) = new(i % UInt8)
304+
end
305+
(::Type{X})(x::Real) where X<:FixedPoint{T} where T = X(round(T, typemax(T)*x), 0)
306+
307+
@test_broken worlds(mi) == w
308+
309+
## specialization heuristics do not trigger false invalidation
310+
## Here, the new method is not actually reachable, but partial specialization makes the MethodInstance
311+
## specTypes make it look like it might be.
312+
s = Set([VERSION])
313+
mv = maximum(s)
314+
mi = instance(maximum, (typeof(s),))
315+
w = worlds(mi)
316+
Base.reduce_empty(::typeof(Base.add_sum), ::Type{F}) where {F<:FixedPoint} = 0.0
317+
@test sum(Normed[]) === 0.0
318+
@test_broken worlds(mi) == w
319+
320+
## Check that invalidations don't happen for mi.specTypes <: method.sig
321+
## In this case the MethodInstance being invalidated is for Union{}(::Array{_A,1} where _A)
322+
createdict(@nospecialize(d::Dict{K,V} where {K,V})) = typeof(d)()
323+
d = createdict(Dict("a"=>1))
324+
mi = instance(createdict, (Dict{K,V} where {K,V},))
325+
w = worlds(mi)
326+
struct SHermitianCompact{N,T,L}
327+
data::NTuple{L,T}
328+
SHermitianCompact{N,T,L}(data::NTuple{L,T}) where {N,T,L} = new{N,T,L}(data)
329+
end
330+
@inline (::Type{SSC})(a::AbstractVector) where {SSC <: SHermitianCompact} = SSC((a...,))
331+
x = SHermitianCompact{1,Float32,3}([1.0f0, 2.0f0, 3.0f0])
332+
@test_broken worlds(mi) == w

0 commit comments

Comments
 (0)