Skip to content

Commit 9a7388c

Browse files
vtjnashclaude
authored andcommitted
correctly encode and validate fully_covers as edges (#58861)
This is a bugfix and code cleanup, since it wasn't properly encoding and checking for newly "missing"-type backedges `fully_covering` during verification. Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit 6efd218)
1 parent a28d9b4 commit 9a7388c

File tree

5 files changed

+74
-60
lines changed

5 files changed

+74
-60
lines changed

Compiler/src/stmtinfo.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function _add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo, mi_edge::Boo
6060
end
6161
end
6262
nmatches = length(info.results)
63-
if nmatches == length(info.edges) == 1
63+
if nmatches == length(info.edges) == 1 && fully_covering(info)
6464
# try the optimized format for the representation, if possible and applicable
6565
# if this doesn't succeed, the backedge will be less precise,
6666
# but the forward edge will maintain the precision
@@ -78,13 +78,15 @@ function _add_edges_impl(edges::Vector{Any}, info::MethodMatchInfo, mi_edge::Boo
7878
end
7979
end
8080
# add check for whether this lookup already existed in the edges list
81+
# encode nmatches as negative if fully_covers is false
82+
encoded_nmatches = fully_covering(info) ? nmatches : -nmatches
8183
for i in 1:length(edges)
82-
if edges[i] === nmatches && edges[i+1] == info.atype
84+
if edges[i] === encoded_nmatches && edges[i+1] == info.atype
8385
# TODO: must also verify the CodeInstance match too
8486
return nothing
8587
end
8688
end
87-
push!(edges, nmatches, info.atype)
89+
push!(edges, encoded_nmatches, info.atype)
8890
for i = 1:nmatches
8991
edge = info.edges[i]
9092
m = info.results[i]
@@ -101,7 +103,7 @@ function add_one_edge!(edges::Vector{Any}, edge::MethodInstance)
101103
i = 1
102104
while i <= length(edges)
103105
edgeᵢ = edges[i]
104-
edgeᵢ isa Int && (i += 2 + edgeᵢ; continue)
106+
edgeᵢ isa Int && (i += 2 + abs(edgeᵢ); continue)
105107
edgeᵢ isa CodeInstance && (edgeᵢ = get_ci_mi(edgeᵢ))
106108
edgeᵢ isa MethodInstance || (i += 1; continue)
107109
if edgeᵢ === edge && !(i > 1 && edges[i-1] isa Type)
@@ -116,7 +118,7 @@ function add_one_edge!(edges::Vector{Any}, edge::CodeInstance)
116118
i = 1
117119
while i <= length(edges)
118120
edgeᵢ_orig = edgeᵢ = edges[i]
119-
edgeᵢ isa Int && (i += 2 + edgeᵢ; continue)
121+
edgeᵢ isa Int && (i += 2 + abs(edgeᵢ); continue)
120122
edgeᵢ isa CodeInstance && (edgeᵢ = get_ci_mi(edgeᵢ))
121123
edgeᵢ isa MethodInstance || (i += 1; continue)
122124
if edgeᵢ === edge.def && !(i > 1 && edges[i-1] isa Type)

base/staticdata.jl

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,15 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi
177177
end
178178
if edge isa MethodInstance
179179
sig = edge.specTypes
180-
min_valid2, max_valid2, matches = verify_call(sig, callees, j, 1, world)
180+
min_valid2, max_valid2, matches = verify_call(sig, callees, j, 1, world, true)
181181
j += 1
182182
elseif edge isa Int
183183
sig = callees[j+1]
184-
min_valid2, max_valid2, matches = verify_call(sig, callees, j+2, edge, world)
185-
j += 2 + edge
184+
# Handle negative counts (fully_covers=false)
185+
nmatches = abs(edge)
186+
fully_covers = edge > 0
187+
min_valid2, max_valid2, matches = verify_call(sig, callees, j+2, nmatches, world, fully_covers)
188+
j += 2 + nmatches
186189
edge = sig
187190
elseif edge isa Core.Binding
188191
j += 1
@@ -288,7 +291,7 @@ function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visi
288291
return 0, minworld, maxworld
289292
end
290293

291-
function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n::Int, world::UInt)
294+
function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n::Int, world::UInt, fully_covers::Bool)
292295
# verify that these edges intersect with the same methods as before
293296
mi = nothing
294297
if n == 1
@@ -304,20 +307,25 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n
304307
mi = t::MethodInstance
305308
end
306309
meth = mi.def::Method
307-
if !iszero(mi.dispatch_status & METHOD_SIG_LATEST_ONLY)
310+
# Fast path is legal when fully_covers=true OR when METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC is unset
311+
if (fully_covers || iszero(meth.dispatch_status & METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC)) &&
312+
!iszero(mi.dispatch_status & METHOD_SIG_LATEST_ONLY)
308313
minworld = meth.primary_world
309314
@assert minworld world
310315
maxworld = typemax(UInt)
311316
result = Any[] # result is unused
312317
return minworld, maxworld, result
313318
end
314319
end
315-
if !iszero(meth.dispatch_status & METHOD_SIG_LATEST_ONLY)
316-
minworld = meth.primary_world
317-
@assert minworld world
318-
maxworld = typemax(UInt)
319-
result = Any[] # result is unused
320-
return minworld, maxworld, result
320+
# Fast path is legal when fully_covers=true OR when METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC is unset
321+
if fully_covers || iszero(meth.dispatch_status & METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC)
322+
if !iszero(meth.dispatch_status & METHOD_SIG_LATEST_ONLY)
323+
minworld = meth.primary_world
324+
@assert minworld world
325+
maxworld = typemax(UInt)
326+
result = Any[] # result is unused
327+
return minworld, maxworld, result
328+
end
321329
end
322330
end
323331
end
@@ -382,6 +390,8 @@ end
382390
const METHOD_SIG_LATEST_WHICH = 0x1
383391
# true indicates this method would be returned as the only result from `methods` when calling `method.sig` in the current latest world
384392
const METHOD_SIG_LATEST_ONLY = 0x2
393+
# true indicates there exists some other method that is not more specific than this one in the current latest world (which might be more fully covering)
394+
const METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC = 0x8
385395

386396
function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UInt)
387397
@assert invokesig isa Type

src/gf.c

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,12 +2140,6 @@ static int jl_type_intersection2(jl_value_t *t1, jl_value_t *t2, jl_value_t **is
21402140
}
21412141

21422142

2143-
enum morespec_options {
2144-
morespec_unknown,
2145-
morespec_isnot,
2146-
morespec_is
2147-
};
2148-
21492143
// check if `type` is replacing `m` with an ambiguity here, given other methods in `d` that already match it
21502144
static int is_replacing(char ambig, jl_value_t *type, jl_method_t *m, jl_method_t *const *d, size_t n, jl_value_t *isect, jl_value_t *isect2, char *morespec)
21512145
{
@@ -2155,17 +2149,15 @@ static int is_replacing(char ambig, jl_value_t *type, jl_method_t *m, jl_method_
21552149
// see if m2 also fully covered this intersection
21562150
if (m == m2 || !(jl_subtype(isect, m2->sig) || (isect2 && jl_subtype(isect2, m2->sig))))
21572151
continue;
2158-
if (morespec[k] == (char)morespec_unknown)
2159-
morespec[k] = (char)(jl_type_morespecific(m2->sig, type) ? morespec_is : morespec_isnot);
2160-
if (morespec[k] == (char)morespec_is)
2152+
if (morespec[k])
21612153
// not actually shadowing this--m2 will still be better
21622154
return 0;
21632155
// if type is not more specific than m (thus now dominating it)
21642156
// then there is a new ambiguity here,
21652157
// since m2 was also a previous match over isect,
21662158
// see if m was previously dominant over all m2
21672159
// or if this was already ambiguous before
2168-
if (ambig == morespec_is && !jl_type_morespecific(m->sig, m2->sig)) {
2160+
if (ambig && !jl_type_morespecific(m->sig, m2->sig)) {
21692161
// m and m2 were previously ambiguous over the full intersection of mi with type, and will still be ambiguous with addition of type
21702162
return 0;
21712163
}
@@ -2659,17 +2651,27 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
26592651
oldvalue = get_intersect_matches(jl_atomic_load_relaxed(&mt->defs), newentry, &replaced, max_world);
26602652

26612653
int invalidated = 0;
2662-
int only = !(jl_atomic_load_relaxed(&method->dispatch_status) & METHOD_SIG_PRECOMPILE_MANY); // will compute if this will be currently the only result that would returned from `ml_matches` given `sig`
2654+
int dispatch_bits = METHOD_SIG_LATEST_WHICH; // Always set LATEST_WHICH
2655+
// Check precompiled dispatch status bits
2656+
int precompiled_status = jl_atomic_load_relaxed(&method->dispatch_status);
2657+
if (!(precompiled_status & METHOD_SIG_PRECOMPILE_MANY))
2658+
dispatch_bits |= METHOD_SIG_LATEST_ONLY; // Tentatively set, will be cleared if not applicable
2659+
if (precompiled_status & METHOD_SIG_PRECOMPILE_HAS_NOTMORESPECIFIC)
2660+
dispatch_bits |= METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC;
26632661
if (replaced) {
26642662
oldvalue = (jl_value_t*)replaced;
26652663
jl_method_t *m = replaced->func.method;
26662664
invalidated = 1;
26672665
method_overwrite(newentry, m);
2668-
// this is an optimized version of below, given we know the type-intersection is exact
2666+
// This is an optimized version of below, given we know the type-intersection is exact
26692667
jl_method_table_invalidate(m, max_world);
26702668
int m_dispatch = jl_atomic_load_relaxed(&m->dispatch_status);
2671-
jl_atomic_store_relaxed(&m->dispatch_status, 0);
2672-
only = m_dispatch & METHOD_SIG_LATEST_ONLY;
2669+
// Clear METHOD_SIG_LATEST_ONLY and METHOD_SIG_LATEST_WHICH bits, only keeping NOTMORESPECIFIC
2670+
jl_atomic_store_relaxed(&m->dispatch_status, m_dispatch & METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC);
2671+
// Edge case: don't set dispatch_bits |= METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC unconditionally since `m` is not an visible method for invalidations
2672+
dispatch_bits |= (m_dispatch & METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC);
2673+
if (!(m_dispatch & METHOD_SIG_LATEST_ONLY))
2674+
dispatch_bits &= ~METHOD_SIG_LATEST_ONLY;
26732675
}
26742676
else {
26752677
jl_method_t *const *d;
@@ -2685,13 +2687,28 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
26852687

26862688
oldmi = jl_alloc_vec_any(0);
26872689
char *morespec = (char*)alloca(n);
2688-
memset(morespec, morespec_unknown, n);
2690+
// Compute all morespec values upfront
2691+
for (j = 0; j < n; j++)
2692+
morespec[j] = (char)jl_type_morespecific(d[j]->sig, type);
26892693
for (j = 0; j < n; j++) {
26902694
jl_method_t *m = d[j];
2691-
if (morespec[j] == (char)morespec_is) {
2692-
only = 0;
2693-
continue;
2695+
// Compute ambig state: is there an ambiguity between new method and old m?
2696+
char ambig = !morespec[j] && !jl_type_morespecific(type, m->sig);
2697+
// Compute updates to the dispatch state bits
2698+
int m_dispatch = jl_atomic_load_relaxed(&m->dispatch_status);
2699+
if (morespec[j] || ambig) {
2700+
// !morespecific(new, old)
2701+
dispatch_bits &= ~METHOD_SIG_LATEST_ONLY;
2702+
m_dispatch |= METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC;
2703+
}
2704+
if (!morespec[j]) {
2705+
// !morespecific(old, new)
2706+
dispatch_bits |= METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC;
2707+
m_dispatch &= ~METHOD_SIG_LATEST_ONLY;
26942708
}
2709+
jl_atomic_store_relaxed(&m->dispatch_status, m_dispatch);
2710+
if (morespec[j])
2711+
continue;
26952712
loctag = jl_atomic_load_relaxed(&m->specializations); // use loctag for a gcroot
26962713
_Atomic(jl_method_instance_t*) *data;
26972714
size_t l;
@@ -2703,27 +2720,19 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
27032720
data = (_Atomic(jl_method_instance_t*)*) &loctag;
27042721
l = 1;
27052722
}
2706-
enum morespec_options ambig = morespec_unknown;
27072723
for (size_t i = 0; i < l; i++) {
27082724
jl_method_instance_t *mi = jl_atomic_load_relaxed(&data[i]);
27092725
if ((jl_value_t*)mi == jl_nothing)
27102726
continue;
27112727
isect3 = jl_type_intersection(m->sig, (jl_value_t*)mi->specTypes);
27122728
if (jl_type_intersection2(type, isect3, &isect, &isect2)) {
2729+
// Replacing a method--see if this really was the selected method previously
2730+
// over the intersection (not ambiguous) and the new method will be selected now (morespec_is).
27132731
// TODO: this only checks pair-wise for ambiguities, but the ambiguities could arise from the interaction of multiple methods
27142732
// and thus might miss a case where we introduce an ambiguity between two existing methods
27152733
// We could instead work to sort this into 3 groups `morespecific .. ambiguous .. lesspecific`, with `type` in ambiguous,
27162734
// such that everything in `morespecific` dominates everything in `ambiguous`, and everything in `ambiguous` dominates everything in `lessspecific`
27172735
// And then compute where each isect falls, and whether it changed group--necessitating invalidation--or not.
2718-
if (morespec[j] == (char)morespec_unknown)
2719-
morespec[j] = (char)(jl_type_morespecific(m->sig, type) ? morespec_is : morespec_isnot);
2720-
if (morespec[j] == (char)morespec_is)
2721-
// not actually shadowing--the existing method is still better
2722-
break;
2723-
if (ambig == morespec_unknown)
2724-
ambig = jl_type_morespecific(type, m->sig) ? morespec_isnot : morespec_is;
2725-
// replacing a method--see if this really was the selected method previously
2726-
// over the intersection (not ambiguous) and the new method will be selected now (morespec_is)
27272736
int replaced_dispatch = is_replacing(ambig, type, m, d, n, isect, isect2, morespec);
27282737
// found that this specialization dispatch got replaced by m
27292738
// call invalidate_backedges(mi, max_world, "jl_method_table_insert");
@@ -2740,20 +2749,6 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
27402749
invalidated |= invalidatedmi;
27412750
}
27422751
}
2743-
// now compute and store updates to METHOD_SIG_LATEST_ONLY
2744-
int m_dispatch = jl_atomic_load_relaxed(&m->dispatch_status);
2745-
if (m_dispatch & METHOD_SIG_LATEST_ONLY) {
2746-
if (morespec[j] == (char)morespec_unknown)
2747-
morespec[j] = (char)(jl_type_morespecific(m->sig, type) ? morespec_is : morespec_isnot);
2748-
if (morespec[j] == (char)morespec_isnot)
2749-
jl_atomic_store_relaxed(&m->dispatch_status, ~METHOD_SIG_LATEST_ONLY & m_dispatch);
2750-
}
2751-
if (only) {
2752-
if (morespec[j] == (char)morespec_is || ambig == morespec_is ||
2753-
(ambig == morespec_unknown && !jl_type_morespecific(type, m->sig))) {
2754-
only = 0;
2755-
}
2756-
}
27572752
}
27582753
}
27592754

@@ -2802,7 +2797,7 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry)
28022797
jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag);
28032798
}
28042799
jl_atomic_store_relaxed(&newentry->max_world, ~(size_t)0);
2805-
jl_atomic_store_relaxed(&method->dispatch_status, METHOD_SIG_LATEST_WHICH | (only ? METHOD_SIG_LATEST_ONLY : 0)); // TODO: this should be sequenced fully after the world counter store
2800+
jl_atomic_store_relaxed(&method->dispatch_status, dispatch_bits); // TODO: this should be sequenced fully after the world counter store
28062801
JL_GC_POP();
28072802
}
28082803

src/julia_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,8 @@ typedef union {
677677
#define METHOD_SIG_LATEST_WHICH 0b0001
678678
#define METHOD_SIG_LATEST_ONLY 0b0010
679679
#define METHOD_SIG_PRECOMPILE_MANY 0b0100
680+
#define METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC 0b1000 // indicates there exists some other method that is not more specific than this one
681+
#define METHOD_SIG_PRECOMPILE_HAS_NOTMORESPECIFIC 0b10000 // precompiled version of METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC
680682

681683
JL_DLLEXPORT jl_code_instance_t *jl_engine_reserve(jl_method_instance_t *m, jl_value_t *owner);
682684
JL_DLLEXPORT void jl_engine_fulfill(jl_code_instance_t *ci, jl_code_info_t *src);

src/staticdata.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1834,7 +1834,12 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
18341834
if (jl_atomic_load_relaxed(&newm->primary_world) > 1) {
18351835
jl_atomic_store_relaxed(&newm->primary_world, ~(size_t)0); // min-world
18361836
int dispatch_status = jl_atomic_load_relaxed(&newm->dispatch_status);
1837-
jl_atomic_store_relaxed(&newm->dispatch_status, dispatch_status & METHOD_SIG_LATEST_ONLY ? 0 : METHOD_SIG_PRECOMPILE_MANY);
1837+
int new_dispatch_status = 0;
1838+
if (!(dispatch_status & METHOD_SIG_LATEST_ONLY))
1839+
new_dispatch_status |= METHOD_SIG_PRECOMPILE_MANY;
1840+
if (dispatch_status & METHOD_SIG_LATEST_HAS_NOTMORESPECIFIC)
1841+
new_dispatch_status |= METHOD_SIG_PRECOMPILE_HAS_NOTMORESPECIFIC;
1842+
jl_atomic_store_relaxed(&newm->dispatch_status, new_dispatch_status);
18381843
arraylist_push(&s->fixup_objs, (void*)reloc_offset);
18391844
}
18401845
}

0 commit comments

Comments
 (0)