Skip to content

Commit 75946ce

Browse files
authored
Add binding invalidations to log (#58226)
Currently we log the invalidated backedges of a binding invalidation, but the actual trigger of the invalidation is not logged. This is needed to allow SnoopCompile to attribute a cause to those invalidations.
1 parent bc30cf2 commit 75946ce

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

base/invalidation.jl

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,25 @@ function invalidate_method_for_globalref!(gr::GlobalRef, method::Method, invalid
6767
binding = convert(Core.Binding, gr)
6868
if isdefined(method, :source)
6969
src = _uncompressed_ir(method)
70-
old_stmts = src.code
7170
invalidate_all = should_invalidate_code_for_globalref(gr, src)
7271
end
72+
invalidated_any = false
7373
for mi in specializations(method)
7474
isdefined(mi, :cache) || continue
7575
ci = mi.cache
76+
invalidated = false
7677
while true
7778
if ci.max_world > new_max_world && (invalidate_all || scan_edge_list(ci, binding))
7879
ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), ci, new_max_world)
80+
invalidated = true
7981
end
8082
isdefined(ci, :next) || break
8183
ci = ci.next
8284
end
85+
invalidated && ccall(:jl_maybe_log_binding_invalidation, Cvoid, (Any,), mi)
86+
invalidated_any |= invalidated
8387
end
88+
return invalidated_any
8489
end
8590

8691
export_affecting_partition_flags(bpart::Core.BindingPartition) =
@@ -104,18 +109,21 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core
104109
need_to_invalidate_export = export_affecting_partition_flags(invalidated_bpart) !==
105110
export_affecting_partition_flags(new_bpart)
106111

112+
invalidated_any = false
113+
queued_bindings = Tuple{Core.Binding, Core.BindingPartition, Core.BindingPartition}[] # defer handling these to keep the logging coherent
107114
if need_to_invalidate_code
108115
if (b.flags & BINDING_FLAG_ANY_IMPLICIT_EDGES) != 0
109116
nmethods = ccall(:jl_module_scanned_methods_length, Csize_t, (Any,), gr.mod)
110117
for i = 1:nmethods
111118
method = ccall(:jl_module_scanned_methods_getindex, Any, (Any, Csize_t), gr.mod, i)::Method
112-
invalidate_method_for_globalref!(gr, method, invalidated_bpart, new_max_world)
119+
invalidated_any |= invalidate_method_for_globalref!(gr, method, invalidated_bpart, new_max_world)
113120
end
114121
end
115122
if isdefined(b, :backedges)
116123
for edge in b.backedges
117124
if isa(edge, CodeInstance)
118125
ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), edge, new_max_world)
126+
invalidated_any = true
119127
elseif isa(edge, Core.Binding)
120128
isdefined(edge, :partitions) || continue
121129
latest_bpart = edge.partitions
@@ -124,9 +132,9 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core
124132
if is_some_binding_imported(binding_kind(latest_bpart))
125133
partition_restriction(latest_bpart) === b || continue
126134
end
127-
invalidate_code_for_globalref!(edge, latest_bpart, latest_bpart, new_max_world)
135+
push!(queued_bindings, (edge, latest_bpart, latest_bpart))
128136
else
129-
invalidate_method_for_globalref!(gr, edge::Method, invalidated_bpart, new_max_world)
137+
invalidated_any |= invalidate_method_for_globalref!(gr, edge::Method, invalidated_bpart, new_max_world)
130138
end
131139
end
132140
end
@@ -148,11 +156,16 @@ function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core
148156
ccall(:jl_maybe_reresolve_implicit, Any, (Any, Csize_t), user_binding, new_max_world) :
149157
latest_bpart
150158
if need_to_invalidate_code || new_bpart !== latest_bpart
151-
invalidate_code_for_globalref!(convert(Core.Binding, user_binding), latest_bpart, new_bpart, new_max_world)
159+
push!(queued_bindings, (convert(Core.Binding, user_binding), latest_bpart, new_bpart))
152160
end
153161
end
154162
end
155163
end
164+
invalidated_any && ccall(:jl_maybe_log_binding_invalidation, Cvoid, (Any,), invalidated_bpart)
165+
for (edge, invalidated_bpart, new_bpart) in queued_bindings
166+
invalidated_any |= invalidate_code_for_globalref!(edge, invalidated_bpart, new_bpart, new_max_world)
167+
end
168+
return invalidated_any
156169
end
157170
invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_bpart::Core.BindingPartition, new_max_world::UInt) =
158171
invalidate_code_for_globalref!(convert(Core.Binding, gr), invalidated_bpart, new_bpart, new_max_world)

src/gf.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,19 @@ JL_DLLEXPORT void jl_invalidate_code_instance(jl_code_instance_t *replaced, size
18911891
invalidate_code_instance(replaced, max_world, 1);
18921892
}
18931893

1894+
JL_DLLEXPORT void jl_maybe_log_binding_invalidation(jl_value_t *replaced)
1895+
{
1896+
if (_jl_debug_method_invalidation) {
1897+
if (replaced) {
1898+
jl_array_ptr_1d_push(_jl_debug_method_invalidation, replaced);
1899+
}
1900+
jl_value_t *loctag = jl_cstr_to_string("jl_maybe_log_binding_invalidation");
1901+
JL_GC_PUSH1(&loctag);
1902+
jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag);
1903+
JL_GC_POP();
1904+
}
1905+
}
1906+
18941907
static void _invalidate_backedges(jl_method_instance_t *replaced_mi, jl_code_instance_t *replaced_ci, size_t max_world, int depth) {
18951908
uint8_t recursion_flags = 0;
18961909
jl_array_t *backedges = jl_mi_get_backedges_mutate(replaced_mi, &recursion_flags);

test/precompile.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,12 @@ precompile_test_harness("code caching") do dir
967967
const gib = makewib(1)
968968
fib() = gib.ib.x
969969
970+
struct LogBindingInvalidation
971+
x::Int
972+
end
973+
const glbi = LogBindingInvalidation(1)
974+
flbi() = @__MODULE__().glbi.x
975+
970976
# force precompilation
971977
build_stale(37)
972978
stale('c')
@@ -991,10 +997,13 @@ precompile_test_harness("code caching") do dir
991997
useA() = $StaleA.stale("hello")
992998
useA2() = useA()
993999
1000+
useflbi() = $StaleA.flbi()
1001+
9941002
# force precompilation
9951003
begin
9961004
Base.Experimental.@force_compile
9971005
useA2()
1006+
useflbi()
9981007
end
9991008
precompile($StaleA.fib, ())
10001009
@@ -1035,6 +1044,13 @@ precompile_test_harness("code caching") do dir
10351044
end
10361045
const gib = makewib(2.0)
10371046
end)
1047+
# TODO: test a "method_globalref" invalidation also
1048+
Base.eval(MA, quote
1049+
struct LogBindingInvalidation # binding invalidations can't be done during precompilation
1050+
x::Float64
1051+
end
1052+
const glbi = LogBindingInvalidation(2.0)
1053+
end)
10381054
@eval using $StaleC
10391055
invalidations = Base.StaticData.debug_method_invalidation(true)
10401056
@eval using $StaleB
@@ -1096,6 +1112,16 @@ precompile_test_harness("code caching") do dir
10961112
@test !hasvalid(mi, world)
10971113
@test any(x -> x isa Core.CodeInstance && x.def === mi, invalidations)
10981114

1115+
idxb = findfirst(x -> x isa Core.Binding, invalidations)
1116+
@test invalidations[idxb+1] == "insert_backedges_callee"
1117+
idxv = findnext(==("verify_methods"), invalidations, idxb)
1118+
if invalidations[idxv-1].def.def.name === :getproperty
1119+
idxv = findnext(==("verify_methods"), invalidations, idxv+1)
1120+
end
1121+
@test invalidations[idxv-1].def.def.name === :flbi
1122+
idxv = findnext(==("verify_methods"), invalidations, idxv+1)
1123+
@test invalidations[idxv-1].def.def.name === :useflbi
1124+
10991125
m = only(methods(MB.map_nbits))
11001126
@test !hasvalid(m.specializations::Core.MethodInstance, world+1) # insert_backedges invalidations also trigger their backedges
11011127
end

test/worlds.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,34 @@ idxi = findfirst(==(m58080i), logmeths)
436436
@test logmeths[end-1] == m58080s
437437
@test logmeths[end] == "jl_method_table_insert"
438438

439+
# logging binding invalidations
440+
struct LogBindingInvalidation
441+
x::Int
442+
end
443+
makelbi(x) = LogBindingInvalidation(x)
444+
const glbi = makelbi(1)
445+
oLBI, oglbi = LogBindingInvalidation, glbi
446+
flbi() = @__MODULE__().glbi.x
447+
flbi()
448+
milbi1 = only(Base.specializations(only(methods(makelbi))))
449+
milbi2 = only(Base.specializations(only(methods(flbi))))
450+
logmeths = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1)
451+
struct LogBindingInvalidation
452+
x::Float64
453+
end
454+
const glbi = makelbi(2.0)
455+
@test flbi() === 2.0
456+
ccall(:jl_debug_method_invalidation, Any, (Cint,), 0)
457+
@test milbi1.cache.def logmeths
458+
@test milbi2.cache.next.def logmeths
459+
i = findfirst(x -> isa(x, Core.BindingPartition), logmeths)
460+
T = logmeths[i].restriction
461+
@test T === oLBI
462+
@test logmeths[i+1] == "jl_maybe_log_binding_invalidation"
463+
T = logmeths[end-1].restriction
464+
@test T === oglbi
465+
@test logmeths[end] == "jl_maybe_log_binding_invalidation"
466+
439467
# issue #50091 -- missing invoke edge affecting nospecialized dispatch
440468
module ExceptionUnwrapping
441469
@nospecialize

0 commit comments

Comments
 (0)