Skip to content

Commit 10d0a0f

Browse files
authored
Add edge-invalidation tests and implementation (#52)
In Julia 1.12, method insertion/deletion invalidations are logged to a separate stream from edge invalidations. Our previous tests didn't cover edge invalidations, and so failed to "notice" the separation of the streams. This adds tests for edge invalidations and modifies the implementation of `@recompile_invalidations` to cover them.
1 parent a26f75e commit 10d0a0f

File tree

2 files changed

+115
-19
lines changed

2 files changed

+115
-19
lines changed

src/invalidations.jl

+30-8
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ macro recompile_invalidations(expr)
1313
end
1414

1515
function recompile_invalidations(__module__::Module, @nospecialize expr)
16-
list = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1)
16+
listi = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1)
17+
liste = Base.StaticData.debug_method_invalidation(true)
1718
try
1819
Core.eval(__module__, expr)
1920
finally
2021
ccall(:jl_debug_method_invalidation, Any, (Cint,), 0)
22+
Base.StaticData.debug_method_invalidation(false)
2123
end
2224
if ccall(:jl_generating_output, Cint, ()) == 1
23-
foreach(precompile_mi, invalidation_leaves(list))
25+
foreach(precompile_mi, invalidation_leaves(listi, liste))
2426
end
2527
nothing
2628
end
2729

28-
function invalidation_leaves(invlist)
30+
function invalidation_leaves(listi, liste)
2931
umis = Set{Core.MethodInstance}()
3032
# `queued` is a queue of length 0 or 1 of invalidated MethodInstances.
3133
# We wait to read the `depth` to find out if it's a leaf.
@@ -37,18 +39,19 @@ function invalidation_leaves(invlist)
3739
queued, depth = item, nextdepth
3840
end
3941

40-
i, ilast = firstindex(invlist), lastindex(invlist)
42+
# Process method insertion/deletion events
43+
i, ilast = firstindex(listi), lastindex(listi)
4144
while i <= ilast
42-
item = invlist[i]
45+
item = listi[i]
4346
if isa(item, Core.MethodInstance)
44-
if i < lastindex(invlist)
45-
nextitem = invlist[i+1]
47+
if i < lastindex(listi)
48+
nextitem = listi[i+1]
4649
if nextitem == "invalidate_mt_cache"
4750
cachequeued(nothing, 0)
4851
i += 2
4952
continue
5053
end
51-
if nextitem ("jl_method_table_disable", "jl_method_table_insert", "verify_methods")
54+
if nextitem ("jl_method_table_disable", "jl_method_table_insert")
5255
cachequeued(nothing, 0)
5356
push!(umis, item)
5457
end
@@ -65,5 +68,24 @@ function invalidation_leaves(invlist)
6568
end
6669
i += 1
6770
end
71+
72+
# Process edge-validation events
73+
i, ilast = firstindex(liste), lastindex(liste)
74+
while i <= ilast
75+
tag = liste[i + 1] # the tag is always second
76+
if tag == "method_globalref"
77+
push!(umis, Core.Compiler.get_ci_mi(liste[i + 2]))
78+
i += 4
79+
elseif tag == "insert_backedges_callee"
80+
push!(umis, Core.Compiler.get_ci_mi(liste[i + 2]))
81+
i += 4
82+
elseif tag == "verify_methods"
83+
push!(umis, Core.Compiler.get_ci_mi(liste[i]))
84+
i += 3
85+
else
86+
error("Unknown tag found in invalidation list: ", tag)
87+
end
88+
end
89+
6890
return umis
6991
end

test/runtests.jl

+85-11
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ using Base: specializations
6767
@test success(run(`$(Base.julia_cmd()) --project=$(joinpath(@__DIR__, "PC_D")) -e $script 1`))
6868
Pkg.activate(projfile)
6969

70+
preffile = joinpath(@__DIR__, "PC_D", "LocalPreferences.toml")
71+
if isfile(preffile)
72+
rm(preffile)
73+
end
74+
7075
oldval = PrecompileTools.verbose[]
7176
PrecompileTools.verbose[] = true
7277
mktemp() do path, io
@@ -85,27 +90,28 @@ using Base: specializations
8590
# Mimic the format written to `_jl_debug_method_invalidation`
8691
# As a source of MethodInstances, `getproperty` has lots
8792
m = which(getproperty, (Any, Symbol))
88-
mis = Core.MethodInstance[]
93+
mis, cis = Core.MethodInstance[], Core.CodeInstance[]
8994
for mi in specializations(m)
90-
length(mis) >= 10 && break
95+
length(cis) >= 10 && break
9196
mi === nothing && continue
9297
push!(mis, mi)
98+
isdefined(mi, :cache) && push!(cis, mi.cache)
9399
end
94-
# These mimic the invalidation lists in SnoopCompile's `test/snoopr.jl`
100+
# These mimic the format of the logs produced by `jl_debug_method_invalidation` (both variants)
95101
invs = Any[mis[1], 0, mis[2], 1, Tuple{}, m, "jl_method_table_insert"]
96-
@test PrecompileTools.invalidation_leaves(invs) == Set([mis[2]])
102+
@test PrecompileTools.invalidation_leaves(invs, []) == Set([mis[2]])
97103
invs = Any[mis[1], 0, mis[2], 1, mis[3], 1, Tuple{}, m, "jl_method_table_insert"]
98-
@test PrecompileTools.invalidation_leaves(invs) == Set([mis[2], mis[3]])
104+
@test PrecompileTools.invalidation_leaves(invs, []) == Set([mis[2], mis[3]])
99105
invs = Any[mis[1], 0, mis[2], 1, Tuple{}, mis[1], 1, mis[3], "jl_method_table_insert", m, "jl_method_table_insert"]
100-
@test PrecompileTools.invalidation_leaves(invs) == Set(mis[1:3])
106+
@test PrecompileTools.invalidation_leaves(invs, []) == Set(mis[1:3])
101107
invs = Any[mis[1], 1, mis[2], "jl_method_table_disable", m, "jl_method_table_disable"]
102-
@test PrecompileTools.invalidation_leaves(invs) == Set([mis[1], mis[2]])
108+
@test PrecompileTools.invalidation_leaves(invs, []) == Set([mis[1], mis[2]])
103109
invs = Any[mis[1], 1, mis[2], "jl_method_table_disable", mis[3], "jl_method_table_insert", m]
104-
@test Set([mis[1], mis[2]]) PrecompileTools.invalidation_leaves(invs)
110+
@test Set([mis[1], mis[2]]) PrecompileTools.invalidation_leaves(invs, [])
105111
invs = Any[mis[1], 1, mis[2], "jl_method_table_insert", mis[2], "invalidate_mt_cache", m, "jl_method_table_insert"]
106-
@test PrecompileTools.invalidation_leaves(invs) == Set([mis[1], mis[2]])
107-
invs = Any[Tuple{}, "insert_backedges_callee", 55, Any[m], mis[2], "verify_methods", 55]
108-
@test PrecompileTools.invalidation_leaves(invs) == Set([mis[2]])
112+
@test PrecompileTools.invalidation_leaves(invs, []) == Set([mis[1], mis[2]])
113+
invs = Any[m, "method_globalref", cis[1], nothing, Tuple{}, "insert_backedges_callee", cis[2], Any[m], cis[3], "verify_methods", cis[4]]
114+
@test PrecompileTools.invalidation_leaves([], invs) == Set(Core.Compiler.get_ci_mi.(cis[1:3]))
109115

110116
# Coverage isn't on during package precompilation, so let's test a few things here
111117
PrecompileTools.precompile_mi(mis[1])
@@ -115,6 +121,7 @@ using Base: specializations
115121
mktempdir() do dir
116122
push!(LOAD_PATH, dir)
117123
cd(dir) do
124+
# Method insertion invalidations
118125
for ((pkg1, pkg2, pkg3), recompile) in ((("RC_A", "RC_B", "RC_C"), false,),
119126
(("RC_D", "RC_E", "RC_F"), true))
120127
Pkg.generate(pkg1)
@@ -174,6 +181,73 @@ using Base: specializations
174181
wc = Base.get_world_counter()
175182
@test recompile ? mi.cache.max_world >= wc : mi.cache.max_world < wc
176183
end
184+
185+
# Edge-invalidation
186+
for ((pkg1, pkg2, pkg3), recompile) in ((("RC_G", "RC_H", "RC_I"), false,),
187+
(("RC_J", "RC_K", "RC_L"), true))
188+
Pkg.generate(pkg1)
189+
open(joinpath(dir, pkg1, "src", pkg1*".jl"), "w") do io
190+
println(io, """
191+
module $pkg1
192+
nbits(::Int8) = 8
193+
nbits(::Int16) = 16
194+
end
195+
""")
196+
end
197+
Pkg.generate(pkg2)
198+
Pkg.activate(joinpath(dir, pkg2))
199+
Pkg.develop(PackageSpec(path=joinpath(dir, pkg1)))
200+
open(joinpath(dir, pkg2, "src", pkg2*".jl"), "w") do io
201+
println(io, """
202+
module $pkg2
203+
using $pkg1
204+
call_nbits(c) = $pkg1.nbits(only(c))
205+
begin
206+
Base.Experimental.@force_compile
207+
call_nbits(Any[Int8(5)])
208+
end
209+
end
210+
""")
211+
end
212+
Pkg.generate(pkg3)
213+
Pkg.activate(joinpath(dir, pkg3))
214+
Pkg.develop(PackageSpec(path=joinpath(dir, pkg1)))
215+
Pkg.develop(PackageSpec(path=joinpath(dir, pkg2)))
216+
Pkg.develop(PackageSpec(path=dirname(@__DIR__))) # depend on PrecompileTools
217+
open(joinpath(dir, pkg3, "src", pkg3*".jl"), "w") do io
218+
if recompile
219+
println(io, """
220+
module $pkg3
221+
using PrecompileTools
222+
@recompile_invalidations begin
223+
using $pkg1
224+
$pkg1.nbits(::Int32) = 32 # This will cause an edge invalidation
225+
using $pkg2
226+
end
227+
end
228+
""")
229+
else
230+
println(io, """
231+
module $pkg3
232+
using PrecompileTools
233+
using $pkg1
234+
$pkg1.nbits(::Int32) = 32 # This will cause an edge invalidation
235+
using $pkg2
236+
end
237+
""")
238+
end
239+
end
240+
241+
@eval using $(Symbol(pkg3))
242+
mod3 = Base.@invokelatest getglobal(@__MODULE__, Symbol(pkg3))
243+
mod2 = Base.@invokelatest getglobal(mod3, Symbol(pkg2))
244+
mod1 = Base.@invokelatest getglobal(mod2, Symbol(pkg1))
245+
m = only(methods(mod2.call_nbits))
246+
mi = first(specializations(m))
247+
wc = Base.get_world_counter()
248+
@test recompile ? mi.cache.max_world >= wc : mi.cache.max_world < wc
249+
end
250+
177251
end
178252
pop!(LOAD_PATH)
179253
end

0 commit comments

Comments
 (0)