Skip to content

Commit a83399c

Browse files
committed
precompile: do not reanimate zombie external methods (#48475)
Backedges are only applicable to cache objects with max_world==Inf Fix #48391 (cherry picked from commit b9398a3)
1 parent d1b9ff9 commit a83399c

File tree

3 files changed

+87
-88
lines changed

3 files changed

+87
-88
lines changed

src/staticdata.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,7 +2240,6 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new
22402240
// edges: [caller1, ext_targets_indexes1, ...] for worklist-owned methods calling external methods
22412241
assert(edges_map == NULL);
22422242

2243-
htable_new(&external_mis, 0); // we need external_mis until after `jl_collect_edges` finishes
22442243
// Save the inferred code from newly inferred, external methods
22452244
*new_specializations = queue_external_cis(newly_inferred);
22462245

@@ -2268,14 +2267,9 @@ static void jl_prepare_serialization_data(jl_array_t *mod_array, jl_array_t *new
22682267
*edges = jl_alloc_vec_any(0);
22692268
*method_roots_list = jl_alloc_vec_any(0);
22702269
// Collect the new method roots
2271-
htable_t methods_with_newspecs;
2272-
htable_new(&methods_with_newspecs, 0);
2273-
jl_collect_methods(&methods_with_newspecs, *new_specializations);
2274-
jl_collect_new_roots(*method_roots_list, &methods_with_newspecs, worklist_key);
2275-
htable_free(&methods_with_newspecs);
2276-
jl_collect_edges(*edges, *ext_targets);
2277-
}
2278-
htable_free(&external_mis);
2270+
jl_collect_new_roots(*method_roots_list, *new_specializations, worklist_key);
2271+
jl_collect_edges(*edges, *ext_targets, *new_specializations);
2272+
}
22792273
assert(edges_map == NULL); // jl_collect_edges clears this when done
22802274

22812275
JL_GC_POP();

src/staticdata_utils.c

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
static htable_t new_code_instance_validate;
2-
static htable_t external_mis;
2+
33

44
// inverse of backedges graph (caller=>callees hash)
55
jl_array_t *edges_map JL_GLOBALLY_ROOTED = NULL; // rooted for the duration of our uses of this
@@ -108,11 +108,6 @@ JL_DLLEXPORT void jl_push_newly_inferred(jl_value_t* ci)
108108
}
109109

110110

111-
static int method_instance_in_queue(jl_method_instance_t *mi)
112-
{
113-
return ptrhash_get(&external_mis, mi) != HT_NOTFOUND;
114-
}
115-
116111
// compute whether a type references something internal to worklist
117112
// and thus could not have existed before deserialize
118113
// and thus does not need delayed unique-ing
@@ -216,10 +211,10 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited,
216211
return found;
217212
}
218213

219-
// given the list of CodeInstances that were inferred during the
220-
// build, select those that are (1) external, and (2) are inferred to be called
221-
// from the worklist or explicitly added by a `precompile` statement.
222-
// Also prepares for method_instance_in_queue queries.
214+
// Given the list of CodeInstances that were inferred during the build, select
215+
// those that are (1) external, (2) still valid, and (3) are inferred to be
216+
// called from the worklist or explicitly added by a `precompile` statement.
217+
// These will be preserved in the image.
223218
static jl_array_t *queue_external_cis(jl_array_t *list)
224219
{
225220
if (list == NULL)
@@ -238,17 +233,12 @@ static jl_array_t *queue_external_cis(jl_array_t *list)
238233
assert(jl_is_code_instance(ci));
239234
jl_method_instance_t *mi = ci->def;
240235
jl_method_t *m = mi->def.method;
241-
if (jl_is_method(m)) {
242-
if (jl_object_in_image((jl_value_t*)m->module)) {
243-
if (ptrhash_get(&external_mis, mi) == HT_NOTFOUND) {
244-
int found = has_backedge_to_worklist(mi, &visited, &stack);
245-
assert(found == 0 || found == 1);
246-
assert(stack.len == 0);
247-
if (found == 1) {
248-
ptrhash_put(&external_mis, mi, mi);
249-
jl_array_ptr_1d_push(new_specializations, (jl_value_t*)ci);
250-
}
251-
}
236+
if (jl_is_method(m) && jl_object_in_image((jl_value_t*)m->module)) {
237+
int found = has_backedge_to_worklist(mi, &visited, &stack);
238+
assert(found == 0 || found == 1);
239+
assert(stack.len == 0);
240+
if (found == 1 && ci->max_world == ~(size_t)0) {
241+
jl_array_ptr_1d_push(new_specializations, (jl_value_t*)ci);
252242
}
253243
}
254244
}
@@ -259,31 +249,25 @@ static jl_array_t *queue_external_cis(jl_array_t *list)
259249
}
260250

261251
// New roots for external methods
262-
static void jl_collect_methods(htable_t *mset, jl_array_t *new_specializations)
252+
static void jl_collect_new_roots(jl_array_t *roots, jl_array_t *new_specializations, uint64_t key)
263253
{
264-
size_t i, l = new_specializations ? jl_array_len(new_specializations) : 0;
265-
jl_value_t *v;
266-
jl_method_t *m;
267-
for (i = 0; i < l; i++) {
268-
v = jl_array_ptr_ref(new_specializations, i);
269-
assert(jl_is_code_instance(v));
270-
m = ((jl_code_instance_t*)v)->def->def.method;
254+
htable_t mset;
255+
htable_new(&mset, 0);
256+
size_t l = new_specializations ? jl_array_len(new_specializations) : 0;
257+
for (size_t i = 0; i < l; i++) {
258+
jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(new_specializations, i);
259+
assert(jl_is_code_instance(ci));
260+
jl_method_t *m = ci->def->def.method;
271261
assert(jl_is_method(m));
272-
ptrhash_put(mset, (void*)m, (void*)m);
262+
ptrhash_put(&mset, (void*)m, (void*)m);
273263
}
274-
}
275-
276-
static void jl_collect_new_roots(jl_array_t *roots, const htable_t *mset, uint64_t key)
277-
{
278-
size_t i, sz = mset->size;
279264
int nwithkey;
280-
jl_method_t *m;
281-
void *const *table = mset->table;
265+
void *const *table = mset.table;
282266
jl_array_t *newroots = NULL;
283267
JL_GC_PUSH1(&newroots);
284-
for (i = 0; i < sz; i += 2) {
268+
for (size_t i = 0; i < mset.size; i += 2) {
285269
if (table[i+1] != HT_NOTFOUND) {
286-
m = (jl_method_t*)table[i];
270+
jl_method_t *m = (jl_method_t*)table[i];
287271
assert(jl_is_method(m));
288272
nwithkey = nroots_with_key(m, key);
289273
if (nwithkey) {
@@ -305,6 +289,7 @@ static void jl_collect_new_roots(jl_array_t *roots, const htable_t *mset, uint64
305289
}
306290
}
307291
JL_GC_POP();
292+
htable_free(&mset);
308293
}
309294

310295
// Create the forward-edge map (caller => callees)
@@ -422,9 +407,18 @@ static void jl_record_edges(jl_method_instance_t *caller, arraylist_t *wq, jl_ar
422407
// Extract `edges` and `ext_targets` from `edges_map`
423408
// `edges` = [caller1, targets_indexes1, ...], the list of methods and their edges
424409
// `ext_targets` is [invokesig1, callee1, matches1, ...], the edges for each target
425-
static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets)
410+
static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets, jl_array_t *external_cis)
426411
{
427412
size_t world = jl_atomic_load_acquire(&jl_world_counter);
413+
htable_t external_mis;
414+
htable_new(&external_mis, 0);
415+
if (external_cis) {
416+
for (size_t i = 0; i < jl_array_len(external_cis); i++) {
417+
jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(external_cis, i);
418+
jl_method_instance_t *mi = ci->def;
419+
ptrhash_put(&external_mis, (void*)mi, (void*)mi);
420+
}
421+
}
428422
arraylist_t wq;
429423
arraylist_new(&wq, 0);
430424
void **table = (void**)jl_array_data(edges_map); // edges_map is caller => callees
@@ -438,10 +432,11 @@ static void jl_collect_edges(jl_array_t *edges, jl_array_t *ext_targets)
438432
continue;
439433
assert(jl_is_method_instance(caller) && jl_is_method(caller->def.method));
440434
if (!jl_object_in_image((jl_value_t*)caller->def.method->module) ||
441-
method_instance_in_queue(caller)) {
435+
ptrhash_get(&external_mis, caller) != HT_NOTFOUND) {
442436
jl_record_edges(caller, &wq, edges);
443437
}
444438
}
439+
htable_free(&external_mis);
445440
while (wq.len) {
446441
jl_method_instance_t *caller = (jl_method_instance_t*)arraylist_pop(&wq);
447442
jl_record_edges(caller, &wq, edges);
@@ -1060,6 +1055,7 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a
10601055
htable_reset(&visited, l);
10611056
for (i = 0; i < l; i++) {
10621057
jl_code_instance_t *ci = (jl_code_instance_t*)jl_array_ptr_ref(ci_list, i);
1058+
assert(ci->max_world == 1 || ci->max_world == ~(size_t)0);
10631059
assert(ptrhash_get(&visited, (void*)ci->def) == HT_NOTFOUND); // check that we don't have multiple cis for same mi
10641060
ptrhash_put(&visited, (void*)ci->def, (void*)ci);
10651061
}
@@ -1120,7 +1116,7 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_targets, jl_a
11201116
assert(jl_is_code_instance(ci));
11211117
jl_code_instance_t *codeinst = (jl_code_instance_t*)ci;
11221118
remove_code_instance_from_validation(codeinst); // mark it as handled
1123-
assert(codeinst->min_world >= world && codeinst->inferred);
1119+
assert(codeinst->min_world == world && codeinst->inferred);
11241120
codeinst->max_world = ~(size_t)0;
11251121
if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) {
11261122
jl_mi_cache_insert(caller, codeinst);
@@ -1160,8 +1156,8 @@ static void validate_new_code_instances(void)
11601156
//assert(0 && "unexpected unprocessed CodeInstance found");
11611157
jl_code_instance_t *ci = (jl_code_instance_t*)new_code_instance_validate.table[i];
11621158
JL_GC_PROMISE_ROOTED(ci); // TODO: this needs a root (or restructuring to avoid it)
1163-
assert(ci->min_world >= world && ci->inferred);
1164-
ci->max_world = ~(size_t)0;
1159+
assert(ci->min_world == world && ci->inferred);
1160+
assert(ci->max_world == ~(size_t)0);
11651161
jl_method_instance_t *caller = ci->def;
11661162
if (jl_rettype_inferred(caller, world, ~(size_t)0) == jl_nothing) {
11671163
jl_mi_cache_insert(caller, ci);

test/precompile.jl

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,64 +1658,73 @@ end
16581658
precompile_test_harness("DynamicExpressions") do load_path
16591659
# https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312
16601660
write(joinpath(load_path, "Float16MWE.jl"),
1661-
"""
1662-
module Float16MWE
1663-
struct Node{T}
1664-
val::T
1665-
end
1666-
doconvert(::Type{<:Node}, val) = convert(Float16, val)
1667-
precompile(Tuple{typeof(doconvert), Type{Node{Float16}}, Float64})
1668-
end # module Float16MWE
1669-
""")
1661+
"""
1662+
module Float16MWE
1663+
struct Node{T}
1664+
val::T
1665+
end
1666+
doconvert(::Type{<:Node}, val) = convert(Float16, val)
1667+
precompile(Tuple{typeof(doconvert), Type{Node{Float16}}, Float64})
1668+
end # module Float16MWE
1669+
""")
16701670
Base.compilecache(Base.PkgId("Float16MWE"))
1671-
(@eval (using Float16MWE))
1672-
Base.invokelatest() do
1673-
@test Float16MWE.doconvert(Float16MWE.Node{Float16}, -1.2) === Float16(-1.2)
1674-
end
1671+
@eval using Float16MWE
1672+
@test @invokelatest(Float16MWE.doconvert(Float16MWE.Node{Float16}, -1.2)) === Float16(-1.2)
16751673
end
16761674

16771675
precompile_test_harness("BadInvalidations") do load_path
16781676
write(joinpath(load_path, "BadInvalidations.jl"),
1679-
"""
1680-
module BadInvalidations
1677+
"""
1678+
module BadInvalidations
16811679
Base.Experimental.@compiler_options compile=min optimize=1
16821680
getval() = Base.a_method_to_overwrite_in_test()
16831681
getval()
1684-
end # module BadInvalidations
1685-
""")
1682+
end # module BadInvalidations
1683+
""")
16861684
Base.compilecache(Base.PkgId("BadInvalidations"))
1687-
(@eval Base a_method_to_overwrite_in_test() = inferencebarrier(2))
1688-
(@eval (using BadInvalidations))
1689-
Base.invokelatest() do
1690-
@test BadInvalidations.getval() === 2
1691-
end
1685+
@eval Base a_method_to_overwrite_in_test() = inferencebarrier(2)
1686+
@eval using BadInvalidations
1687+
@test Base.invokelatest(BadInvalidations.getval) === 2
16921688
end
16931689

16941690
# https://github.com/JuliaLang/julia/issues/48074
16951691
precompile_test_harness("WindowsCacheOverwrite") do load_path
16961692
# https://github.com/JuliaLang/julia/pull/47184#issuecomment-1364716312
16971693
write(joinpath(load_path, "WindowsCacheOverwrite.jl"),
1698-
"""
1699-
module WindowsCacheOverwrite
1700-
1701-
end # module
1702-
""")
1694+
"""
1695+
module WindowsCacheOverwrite
1696+
end # module
1697+
""")
17031698
ji, ofile = Base.compilecache(Base.PkgId("WindowsCacheOverwrite"))
1704-
(@eval (using WindowsCacheOverwrite))
1699+
@eval using WindowsCacheOverwrite
17051700

17061701
write(joinpath(load_path, "WindowsCacheOverwrite.jl"),
1707-
"""
1708-
module WindowsCacheOverwrite
1709-
1710-
f() = "something new"
1711-
1712-
end # module
1713-
""")
1702+
"""
1703+
module WindowsCacheOverwrite
1704+
f() = "something new"
1705+
end # module
1706+
""")
17141707

17151708
ji_2, ofile_2 = Base.compilecache(Base.PkgId("WindowsCacheOverwrite"))
17161709
@test ofile_2 == Base.ocachefile_from_cachefile(ji_2)
17171710
end
17181711

1712+
precompile_test_harness("Issue #48391") do load_path
1713+
write(joinpath(load_path, "I48391.jl"),
1714+
"""
1715+
module I48391
1716+
struct SurrealFinite <: Real end
1717+
precompile(Tuple{typeof(Base.isless), SurrealFinite, SurrealFinite})
1718+
Base.:(<)(x::SurrealFinite, y::SurrealFinite) = "good"
1719+
end
1720+
""")
1721+
ji, ofile = Base.compilecache(Base.PkgId("I48391"))
1722+
@eval using I48391
1723+
x = Base.invokelatest(I48391.SurrealFinite)
1724+
@test Base.invokelatest(isless, x, x) === "good"
1725+
@test_throws ErrorException isless(x, x)
1726+
end
1727+
17191728
empty!(Base.DEPOT_PATH)
17201729
append!(Base.DEPOT_PATH, original_depot_path)
17211730
empty!(Base.LOAD_PATH)

0 commit comments

Comments
 (0)