Skip to content

Commit f388837

Browse files
timholyKristofferC
authored andcommitted
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. (cherry picked from commit 75946ce)
1 parent f60e44b commit f388837

File tree

4 files changed

+108
-5
lines changed

4 files changed

+108
-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
@@ -1954,6 +1954,19 @@ JL_DLLEXPORT void jl_invalidate_code_instance(jl_code_instance_t *replaced, size
19541954
invalidate_code_instance(replaced, max_world, 1);
19551955
}
19561956

1957+
JL_DLLEXPORT void jl_maybe_log_binding_invalidation(jl_value_t *replaced)
1958+
{
1959+
if (_jl_debug_method_invalidation) {
1960+
if (replaced) {
1961+
jl_array_ptr_1d_push(_jl_debug_method_invalidation, replaced);
1962+
}
1963+
jl_value_t *loctag = jl_cstr_to_string("jl_maybe_log_binding_invalidation");
1964+
JL_GC_PUSH1(&loctag);
1965+
jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag);
1966+
JL_GC_POP();
1967+
}
1968+
}
1969+
19571970
static void _invalidate_backedges(jl_method_instance_t *replaced_mi, jl_code_instance_t *replaced_ci, size_t max_world, int depth) {
19581971
uint8_t recursion_flags = 0;
19591972
jl_array_t *backedges = jl_mi_get_backedges_mutate(replaced_mi, &recursion_flags);

test/precompile.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,23 @@ precompile_test_harness("code caching") do dir
956956
use_stale(c) = stale(c[1]) + not_stale("hello")
957957
build_stale(x) = use_stale(Any[x])
958958
959+
# bindings
960+
struct InvalidatedBinding
961+
x::Int
962+
end
963+
struct Wrapper
964+
ib::InvalidatedBinding
965+
end
966+
makewib(x) = Wrapper(InvalidatedBinding(x))
967+
const gib = makewib(1)
968+
fib() = gib.ib.x
969+
970+
struct LogBindingInvalidation
971+
x::Int
972+
end
973+
const glbi = LogBindingInvalidation(1)
974+
flbi() = @__MODULE__().glbi.x
975+
959976
# force precompilation
960977
build_stale(37)
961978
stale('c')
@@ -980,10 +997,13 @@ precompile_test_harness("code caching") do dir
980997
useA() = $StaleA.stale("hello")
981998
useA2() = useA()
982999
1000+
useflbi() = $StaleA.flbi()
1001+
9831002
# force precompilation
9841003
begin
9851004
Base.Experimental.@force_compile
9861005
useA2()
1006+
useflbi()
9871007
end
9881008
9891009
## Reporting tests
@@ -1014,6 +1034,25 @@ precompile_test_harness("code caching") do dir
10141034
@eval using $StaleA
10151035
MA = invokelatest(getfield, @__MODULE__, StaleA)
10161036
Base.eval(MA, :(nbits(::UInt8) = 8))
1037+
<<<<<<< HEAD
1038+
=======
1039+
Base.eval(MA, quote
1040+
struct InvalidatedBinding
1041+
x::Float64
1042+
end
1043+
struct Wrapper
1044+
ib::InvalidatedBinding
1045+
end
1046+
const gib = makewib(2.0)
1047+
end)
1048+
# TODO: test a "method_globalref" invalidation also
1049+
Base.eval(MA, quote
1050+
struct LogBindingInvalidation # binding invalidations can't be done during precompilation
1051+
x::Float64
1052+
end
1053+
const glbi = LogBindingInvalidation(2.0)
1054+
end)
1055+
>>>>>>> 75946ce31c (Add binding invalidations to log (#58226))
10171056
@eval using $StaleC
10181057
invalidations = Base.StaticData.debug_method_invalidation(true)
10191058
@eval using $StaleB
@@ -1070,6 +1109,16 @@ precompile_test_harness("code caching") do dir
10701109
@test !hasvalid(mi, world)
10711110
@test any(x -> x isa Core.CodeInstance && x.def === mi, invalidations)
10721111

1112+
idxb = findfirst(x -> x isa Core.Binding, invalidations)
1113+
@test invalidations[idxb+1] == "insert_backedges_callee"
1114+
idxv = findnext(==("verify_methods"), invalidations, idxb)
1115+
if invalidations[idxv-1].def.def.name === :getproperty
1116+
idxv = findnext(==("verify_methods"), invalidations, idxv+1)
1117+
end
1118+
@test invalidations[idxv-1].def.def.name === :flbi
1119+
idxv = findnext(==("verify_methods"), invalidations, idxv+1)
1120+
@test invalidations[idxv-1].def.def.name === :useflbi
1121+
10731122
m = only(methods(MB.map_nbits))
10741123
@test !hasvalid(m.specializations::Core.MethodInstance, world+1) # insert_backedges invalidations also trigger their backedges
10751124
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)