Skip to content

Commit 4278ded

Browse files
authored
further defer jl_insert_backedges after loading (JuliaLang#56447)
Finish fully breaking the dependency between method insertions and inferring whether the cache is valid. The cache should be inferable in parallel and in aggregate after all loading is finished. This prepares us for moving this code into Julia (Core.Compiler) next.
1 parent 671cd5e commit 4278ded

File tree

2 files changed

+103
-80
lines changed

2 files changed

+103
-80
lines changed

src/staticdata.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,12 +4034,13 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i
40344034
// allocate a world for the new methods, and insert them there, invalidating content as needed
40354035
size_t world = jl_atomic_load_relaxed(&jl_world_counter) + 1;
40364036
jl_activate_methods(extext_methods, internal_methods, world);
4037+
// TODO: inject new_ext_cis into caches here, so the system can see them immediately as potential candidates (before validation)
40374038
// allow users to start running in this updated world
40384039
jl_atomic_store_release(&jl_world_counter, world);
4039-
// but one of those immediate users is going to be our cache updates
4040-
jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)new_ext_cis, world); // restore external backedges (needs to be last)
40414040
// now permit more methods to be added again
40424041
JL_UNLOCK(&world_counter_lock);
4042+
// but one of those immediate users is going to be our cache insertions
4043+
jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)new_ext_cis); // restore existing caches (needs to be last)
40434044
// reinit ccallables
40444045
jl_reinit_ccallable(&ccallable_list, base, pkgimage_handle);
40454046
arraylist_free(&ccallable_list);

src/staticdata_utils.c

Lines changed: 100 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -751,56 +751,58 @@ static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key)
751751
}
752752
}
753753

754-
static size_t verify_invokesig(jl_value_t *invokesig, jl_method_t *expected, size_t minworld)
754+
static void verify_invokesig(jl_value_t *invokesig, jl_method_t *expected, size_t world, size_t *minworld, size_t *maxworld)
755755
{
756756
assert(jl_is_type(invokesig));
757757
assert(jl_is_method(expected));
758-
size_t min_valid = 0;
759-
size_t max_valid = ~(size_t)0;
760758
if (jl_egal(invokesig, expected->sig)) {
761759
// the invoke match is `expected` for `expected->sig`, unless `expected` is invalid
762-
if (jl_atomic_load_relaxed(&expected->deleted_world) < max_valid)
763-
max_valid = 0;
760+
*minworld = jl_atomic_load_relaxed(&expected->primary_world);
761+
*maxworld = jl_atomic_load_relaxed(&expected->deleted_world);
762+
assert(*minworld <= world);
763+
if (*maxworld < world)
764+
*maxworld = 0;
764765
}
765766
else {
767+
*minworld = 1;
768+
*maxworld = ~(size_t)0;
766769
jl_methtable_t *mt = jl_method_get_table(expected);
767770
if ((jl_value_t*)mt == jl_nothing) {
768-
max_valid = 0;
771+
*maxworld = 0;
769772
}
770773
else {
771-
jl_value_t *matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, minworld, &min_valid, &max_valid);
774+
jl_value_t *matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, world, minworld, maxworld);
772775
if (matches == jl_nothing) {
773-
max_valid = 0;
776+
*maxworld = 0;
774777
}
775778
else {
776779
if (((jl_method_match_t*)matches)->method != expected) {
777-
max_valid = 0;
780+
*maxworld = 0;
778781
}
779782
}
780783
}
781784
}
782-
return max_valid;
783785
}
784786

785-
static size_t verify_call(jl_value_t *sig, jl_svec_t *expecteds, size_t i, size_t n, size_t minworld, jl_value_t **matches JL_REQUIRE_ROOTED_SLOT)
787+
static void verify_call(jl_value_t *sig, jl_svec_t *expecteds, size_t i, size_t n, size_t world, size_t *minworld, size_t *maxworld, jl_value_t **matches JL_REQUIRE_ROOTED_SLOT)
786788
{
787789
// verify that these edges intersect with the same methods as before
788-
size_t min_valid = 0;
789-
size_t max_valid = ~(size_t)0;
790+
*minworld = 1;
791+
*maxworld = ~(size_t)0;
790792
int ambig = 0;
791793
// TODO: possibly need to included ambiguities too (for the optimizer correctness)?
792794
jl_value_t *result = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing,
793795
_jl_debug_method_invalidation ? INT32_MAX : n,
794-
0, minworld, &min_valid, &max_valid, &ambig);
796+
0, world, minworld, maxworld, &ambig);
795797
*matches = result;
796798
if (result == jl_nothing) {
797-
max_valid = 0;
799+
*maxworld = 0;
798800
}
799801
else {
800802
// setdiff!(result, expected)
801803
size_t j, k, ins = 0;
802804
if (jl_array_nrows(result) != n) {
803-
max_valid = 0;
805+
*maxworld = 0;
804806
}
805807
for (k = 0; k < jl_array_nrows(result); k++) {
806808
jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(result, k))->method;
@@ -822,29 +824,33 @@ static size_t verify_call(jl_value_t *sig, jl_svec_t *expecteds, size_t i, size_
822824
// intersection has a new method or a method was
823825
// deleted--this is now probably no good, just invalidate
824826
// everything about it now
825-
max_valid = 0;
827+
*maxworld = 0;
826828
if (!_jl_debug_method_invalidation)
827829
break;
828830
jl_array_ptr_set(result, ins++, match);
829831
}
830832
}
831-
if (max_valid != ~(size_t)0 && _jl_debug_method_invalidation)
833+
if (*maxworld != ~(size_t)0 && _jl_debug_method_invalidation)
832834
jl_array_del_end((jl_array_t*)result, jl_array_nrows(result) - ins);
833835
}
834-
return max_valid;
835836
}
836837

837838
// Test all edges relevant to a method:
838839
//// Visit the entire call graph, starting from edges[idx] to determine if that method is valid
839840
//// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable
840841
//// and slightly modified with an early termination option once the computation reaches its minimum
841-
static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_t *maxworld, arraylist_t *stack, htable_t *visiting)
842+
static int jl_verify_method(jl_code_instance_t *codeinst, size_t *minworld, size_t *maxworld, arraylist_t *stack, htable_t *visiting)
842843
{
844+
size_t world = jl_atomic_load_relaxed(&codeinst->min_world);
843845
size_t max_valid2 = jl_atomic_load_relaxed(&codeinst->max_world);
844846
if (max_valid2 != WORLD_AGE_REVALIDATION_SENTINEL) {
847+
*minworld = world;
845848
*maxworld = max_valid2;
846849
return 0;
847850
}
851+
*minworld = 1;
852+
size_t current_world = jl_atomic_load_relaxed(&jl_world_counter);
853+
*maxworld = current_world;
848854
assert(jl_is_method_instance(codeinst->def) && jl_is_method(codeinst->def->def.method));
849855
void **bp = ptrhash_bp(visiting, codeinst);
850856
if (*bp != HT_NOTFOUND)
@@ -862,21 +868,22 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_
862868
// verify current edges
863869
for (size_t j = 0; j < jl_svec_len(callees); ) {
864870
jl_value_t *edge = jl_svecref(callees, j);
871+
size_t min_valid2;
865872
size_t max_valid2;
866873
assert(!jl_is_method(edge)); // `Method`-edge isn't allowed for the optimized one-edge format
867874
if (jl_is_code_instance(edge))
868875
edge = (jl_value_t*)((jl_code_instance_t*)edge)->def;
869876
if (jl_is_method_instance(edge)) {
870877
jl_method_instance_t *mi = (jl_method_instance_t*)edge;
871878
sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); // TODO: ??
872-
max_valid2 = verify_call(sig, callees, j, 1, minworld, &matches);
879+
verify_call(sig, callees, j, 1, world, &min_valid2, &max_valid2, &matches);
873880
sig = NULL;
874881
j += 1;
875882
}
876883
else if (jl_is_long(edge)) {
877884
jl_value_t *sig = jl_svecref(callees, j + 1);
878885
size_t nedges = jl_unbox_long(edge);
879-
max_valid2 = verify_call(sig, callees, j + 2, nedges, minworld, &matches);
886+
verify_call(sig, callees, j + 2, nedges, world, &min_valid2, &max_valid2, &matches);
880887
j += 2 + nedges;
881888
edge = sig;
882889
}
@@ -896,9 +903,11 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_
896903
assert(jl_is_method(callee));
897904
meth = (jl_method_t*)callee;
898905
}
899-
max_valid2 = verify_invokesig(edge, meth, minworld);
906+
verify_invokesig(edge, meth, world, &min_valid2, &max_valid2);
900907
j += 2;
901908
}
909+
if (*minworld < min_valid2)
910+
*minworld = min_valid2;
902911
if (*maxworld > max_valid2)
903912
*maxworld = max_valid2;
904913
if (max_valid2 != ~(size_t)0 && _jl_debug_method_invalidation) {
@@ -917,14 +926,19 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_
917926
// verify recursive edges (if valid, or debugging)
918927
size_t cycle = depth;
919928
jl_code_instance_t *cause = codeinst;
920-
if (*maxworld == ~(size_t)0 || _jl_debug_method_invalidation) {
929+
if (*maxworld != 0 || _jl_debug_method_invalidation) {
921930
for (size_t j = 0; j < jl_svec_len(callees); j++) {
922931
jl_value_t *edge = jl_svecref(callees, j);
923932
if (!jl_is_code_instance(edge))
924933
continue;
925934
jl_code_instance_t *callee = (jl_code_instance_t*)edge;
926-
size_t max_valid2 = ~(size_t)0;
927-
size_t child_cycle = jl_verify_method(callee, minworld, &max_valid2, stack, visiting);
935+
size_t min_valid2;
936+
size_t max_valid2;
937+
size_t child_cycle = jl_verify_method(callee, &min_valid2, &max_valid2, stack, visiting);
938+
if (*minworld < min_valid2)
939+
*minworld = min_valid2;
940+
if (*minworld > max_valid2)
941+
max_valid2 = 0;
928942
if (*maxworld > max_valid2) {
929943
cause = callee;
930944
*maxworld = max_valid2;
@@ -947,12 +961,18 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_
947961
// cycle as also having a failed edge.
948962
while (stack->len >= depth) {
949963
jl_code_instance_t *child = (jl_code_instance_t*)arraylist_pop(stack);
950-
if (*maxworld != jl_atomic_load_relaxed(&child->max_world))
951-
jl_atomic_store_relaxed(&child->max_world, *maxworld);
964+
if (jl_atomic_load_relaxed(&jl_n_threads) == 1) {
965+
// a different thread might simultaneously come to a different, but equally valid, alternative result
966+
assert(jl_atomic_load_relaxed(&child->max_world) == WORLD_AGE_REVALIDATION_SENTINEL);
967+
assert(*minworld <= jl_atomic_load_relaxed(&child->min_world));
968+
}
969+
if (*maxworld != 0)
970+
jl_atomic_store_relaxed(&child->min_world, *minworld);
971+
jl_atomic_store_relaxed(&child->max_world, *maxworld);
952972
void **bp = ptrhash_bp(visiting, codeinst);
953973
assert(*bp == (char*)HT_NOTFOUND + stack->len + 1);
954974
*bp = HT_NOTFOUND;
955-
if (_jl_debug_method_invalidation && *maxworld != ~(size_t)0) {
975+
if (_jl_debug_method_invalidation && *maxworld < current_world) {
956976
jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)child);
957977
loctag = jl_cstr_to_string("verify_methods");
958978
JL_GC_PUSH1(&loctag);
@@ -966,26 +986,30 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t minworld, size_
966986
return 0;
967987
}
968988

969-
static size_t jl_verify_method_graph(jl_code_instance_t *codeinst, size_t minworld, arraylist_t *stack, htable_t *visiting)
989+
static void jl_verify_method_graph(jl_code_instance_t *codeinst, arraylist_t *stack, htable_t *visiting)
970990
{
991+
size_t minworld;
992+
size_t maxworld;
971993
assert(stack->len == 0);
972994
for (size_t i = 0, hsz = visiting->size; i < hsz; i++)
973995
assert(visiting->table[i] == HT_NOTFOUND);
974-
size_t maxworld = ~(size_t)0;
975-
int child_cycle = jl_verify_method(codeinst, minworld, &maxworld, stack, visiting);
996+
int child_cycle = jl_verify_method(codeinst, &minworld, &maxworld, stack, visiting);
976997
assert(child_cycle == 0); (void)child_cycle;
977998
assert(stack->len == 0);
978999
for (size_t i = 0, hsz = visiting->size / 2; i < hsz; i++) {
9791000
assert(visiting->table[2 * i + 1] == HT_NOTFOUND);
9801001
visiting->table[2 * i] = HT_NOTFOUND;
9811002
}
982-
return maxworld;
1003+
if (jl_atomic_load_relaxed(&jl_n_threads) == 1) { // a different thread might simultaneously come to a different, but equally valid, alternative result
1004+
assert(maxworld == 0 || jl_atomic_load_relaxed(&codeinst->min_world) == minworld);
1005+
assert(jl_atomic_load_relaxed(&codeinst->max_world) == maxworld);
1006+
}
9831007
}
9841008

9851009
// Restore backedges to external targets
9861010
// `edges` = [caller1, ...], the list of worklist-owned code instances internally
9871011
// `ext_ci_list` = [caller1, ...], the list of worklist-owned code instances externally
988-
static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list, size_t minworld)
1012+
static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list)
9891013
{
9901014
// determine which CodeInstance objects are still valid in our image
9911015
// to enable any applicable new codes
@@ -1001,61 +1025,59 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list, size
10011025
jl_code_instance_t *codeinst = (jl_code_instance_t*)jl_array_ptr_ref(edges, i);
10021026
jl_svec_t *callees = jl_atomic_load_relaxed(&codeinst->edges);
10031027
jl_method_instance_t *caller = codeinst->def;
1004-
if (jl_atomic_load_relaxed(&codeinst->min_world) != minworld) {
1005-
if (external && jl_atomic_load_relaxed(&codeinst->max_world) != WORLD_AGE_REVALIDATION_SENTINEL) {
1006-
assert(jl_atomic_load_relaxed(&codeinst->min_world) == 1);
1007-
assert(jl_atomic_load_relaxed(&codeinst->max_world) == ~(size_t)0);
1008-
}
1009-
else {
1010-
continue;
1011-
}
1012-
}
1013-
size_t maxvalid = jl_verify_method_graph(codeinst, minworld, &stack, &visiting);
1014-
assert(jl_atomic_load_relaxed(&codeinst->max_world) == maxvalid);
1015-
if (maxvalid == ~(size_t)0) {
1016-
// if this callee is still valid, add all the backedges
1017-
for (size_t j = 0; j < jl_svec_len(callees); ) {
1018-
jl_value_t *edge = jl_svecref(callees, j);
1019-
if (jl_is_long(edge)) {
1020-
j += 2; // skip over signature and count but not methods
1021-
continue;
1022-
}
1023-
else if (jl_is_method(edge)) {
1024-
j += 1;
1025-
continue;
1026-
}
1027-
if (jl_is_code_instance(edge))
1028-
edge = (jl_value_t*)((jl_code_instance_t*)edge)->def;
1029-
if (jl_is_method_instance(edge)) {
1030-
jl_method_instance_add_backedge((jl_method_instance_t*)edge, NULL, codeinst);
1031-
j += 1;
1032-
}
1033-
else if (jl_is_mtable(edge)) {
1034-
jl_methtable_t *mt = (jl_methtable_t*)edge;
1035-
jl_value_t *sig = jl_svecref(callees, j + 1);
1036-
jl_method_table_add_backedge(mt, sig, codeinst);
1037-
j += 2;
1038-
}
1039-
else {
1040-
jl_value_t *callee = jl_svecref(callees, j + 1);
1041-
if (jl_is_code_instance(callee))
1042-
callee = (jl_value_t*)((jl_code_instance_t*)callee)->def;
1043-
else if (jl_is_method(callee)) {
1044-
j += 2;
1028+
jl_verify_method_graph(codeinst, &stack, &visiting);
1029+
size_t minvalid = jl_atomic_load_relaxed(&codeinst->min_world);
1030+
size_t maxvalid = jl_atomic_load_relaxed(&codeinst->max_world);
1031+
if (maxvalid >= minvalid) {
1032+
if (jl_atomic_load_relaxed(&jl_world_counter) == maxvalid) {
1033+
// if this callee is still valid, add all the backedges
1034+
for (size_t j = 0; j < jl_svec_len(callees); ) {
1035+
jl_value_t *edge = jl_svecref(callees, j);
1036+
if (jl_is_long(edge)) {
1037+
j += 2; // skip over signature and count but not methods
10451038
continue;
10461039
}
1047-
jl_method_instance_add_backedge((jl_method_instance_t*)callee, edge, codeinst);
1048-
j += 2;
1040+
else if (jl_is_method(edge)) {
1041+
j += 1;
1042+
continue;
1043+
}
1044+
if (jl_is_code_instance(edge))
1045+
edge = (jl_value_t*)((jl_code_instance_t*)edge)->def;
1046+
if (jl_is_method_instance(edge)) {
1047+
jl_method_instance_add_backedge((jl_method_instance_t*)edge, NULL, codeinst);
1048+
j += 1;
1049+
}
1050+
else if (jl_is_mtable(edge)) {
1051+
jl_methtable_t *mt = (jl_methtable_t*)edge;
1052+
jl_value_t *sig = jl_svecref(callees, j + 1);
1053+
jl_method_table_add_backedge(mt, sig, codeinst);
1054+
j += 2;
1055+
}
1056+
else {
1057+
jl_value_t *callee = jl_svecref(callees, j + 1);
1058+
if (jl_is_code_instance(callee))
1059+
callee = (jl_value_t*)((jl_code_instance_t*)callee)->def;
1060+
else if (jl_is_method(callee)) {
1061+
j += 2;
1062+
continue;
1063+
}
1064+
jl_method_instance_add_backedge((jl_method_instance_t*)callee, edge, codeinst);
1065+
j += 2;
1066+
}
10491067
}
10501068
}
1069+
if (jl_atomic_load_relaxed(&jl_world_counter) == maxvalid) {
1070+
maxvalid = ~(size_t)0;
1071+
jl_atomic_store_relaxed(&codeinst->max_world, maxvalid);
1072+
}
10511073
if (external) {
10521074
jl_value_t *owner = codeinst->owner;
10531075
JL_GC_PROMISE_ROOTED(owner);
10541076

10551077
// See #53586, #53109
10561078
assert(jl_atomic_load_relaxed(&codeinst->inferred));
10571079

1058-
if (jl_rettype_inferred(owner, caller, minworld, maxvalid) != jl_nothing) {
1080+
if (jl_rettype_inferred(owner, caller, minvalid, maxvalid) != jl_nothing) {
10591081
// We already got a code instance for this world age range from somewhere else - we don't need
10601082
// this one.
10611083
}

0 commit comments

Comments
 (0)