Skip to content

Commit 9211a93

Browse files
authored
Add edge kind for access to non-explicit partitioned bindings (#57009)
Our binding partion invalidation code scans the original source for any GlobalRefs that need to be invalidated. However, this is not the only source of access to binding partitions. Various intrinsics (in particular the `*global` ones) also operate on bindings. Since these are not manifest in the source, we instead use the existing `edges` mechanism to give them forward edges. This PR only includes the basic info type, and validation in the replacement case. There's a bit more work to do there, but I'm waiting on #56499 for that part, precompilation in particular.
1 parent d3964b6 commit 9211a93

File tree

9 files changed

+110
-66
lines changed

9 files changed

+110
-66
lines changed

Compiler/src/abstractinterpretation.jl

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,7 +2395,8 @@ function abstract_eval_getglobal(interp::AbstractInterpreter, sv::AbsIntState, s
23952395
if M isa Const && s isa Const
23962396
M, s = M.val, s.val
23972397
if M isa Module && s isa Symbol
2398-
return CallMeta(abstract_eval_globalref(interp, GlobalRef(M, s), saw_latestworld, sv), NoCallInfo())
2398+
(ret, bpart) = abstract_eval_globalref(interp, GlobalRef(M, s), saw_latestworld, sv)
2399+
return CallMeta(ret, bpart === nothing ? NoCallInfo() : GlobalAccessInfo(bpart))
23992400
end
24002401
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
24012402
elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol)
@@ -2473,8 +2474,8 @@ function abstract_eval_setglobal!(interp::AbstractInterpreter, sv::AbsIntState,
24732474
if isa(M, Const) && isa(s, Const)
24742475
M, s = M.val, s.val
24752476
if M isa Module && s isa Symbol
2476-
rt, exct = global_assignment_rt_exct(interp, sv, saw_latestworld, GlobalRef(M, s), v)
2477-
return CallMeta(rt, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), NoCallInfo())
2477+
(rt, exct), partition = global_assignment_rt_exct(interp, sv, saw_latestworld, GlobalRef(M, s), v)
2478+
return CallMeta(rt, exct, Effects(setglobal!_effects, nothrow=exct===Bottom), GlobalAccessInfo(partition))
24782479
end
24792480
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
24802481
end
@@ -2512,15 +2513,15 @@ function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState,
25122513
scm = abstract_eval_setglobal!(interp, sv, saw_latestworld, M, s, v)
25132514
scm.rt === Bottom && return scm
25142515
gcm = abstract_eval_getglobal(interp, sv, saw_latestworld, M, s)
2515-
return CallMeta(gcm.rt, Union{scm.exct,gcm.exct}, merge_effects(scm.effects, gcm.effects), NoCallInfo())
2516+
return CallMeta(gcm.rt, Union{scm.exct,gcm.exct}, merge_effects(scm.effects, gcm.effects), scm.info)
25162517
end
25172518

25182519
function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool,
25192520
@nospecialize(M), @nospecialize(s), @nospecialize(v), @nospecialize(order))
25202521
scm = abstract_eval_setglobal!(interp, sv, saw_latestworld, M, s, v, order)
25212522
scm.rt === Bottom && return scm
25222523
gcm = abstract_eval_getglobal(interp, sv, saw_latestworld, M, s, order)
2523-
return CallMeta(gcm.rt, Union{scm.exct,gcm.exct}, merge_effects(scm.effects, gcm.effects), NoCallInfo())
2524+
return CallMeta(gcm.rt, Union{scm.exct,gcm.exct}, merge_effects(scm.effects, gcm.effects), scm.info)
25242525
end
25252526

25262527
function abstract_eval_swapglobal!(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, argtypes::Vector{Any})
@@ -2569,7 +2570,7 @@ function abstract_eval_replaceglobal!(interp::AbstractInterpreter, sv::AbsIntSta
25692570
end
25702571
exct = Union{rte.exct, global_assignment_binding_rt_exct(interp, partition, v)[2]}
25712572
effects = merge_effects(rte.effects, Effects(setglobal!_effects, nothrow=exct===Bottom))
2572-
sg = CallMeta(Any, exct, effects, NoCallInfo())
2573+
sg = CallMeta(Any, exct, effects, GlobalAccessInfo(partition))
25732574
else
25742575
sg = abstract_eval_setglobal!(interp, sv, saw_latestworld, M, s, v)
25752576
end
@@ -2937,7 +2938,8 @@ function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(
29372938
return RTEffects(sv.ir.argtypes[e.n], Union{}, EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion
29382939
end
29392940
elseif isa(e, GlobalRef)
2940-
return abstract_eval_globalref(interp, e, sstate.saw_latestworld, sv)
2941+
# No need for an edge since an explicit GlobalRef will be picked up by the source scan
2942+
return abstract_eval_globalref(interp, e, sstate.saw_latestworld, sv)[1]
29412943
end
29422944
if isa(e, QuoteNode)
29432945
e = e.value
@@ -3193,14 +3195,31 @@ function abstract_eval_isdefined_expr(interp::AbstractInterpreter, e::Expr, ssta
31933195
end
31943196
return RTEffects(rt, Union{}, EFFECTS_TOTAL)
31953197
end
3196-
return abstract_eval_isdefined(interp, sym, sstate.saw_latestworld, sv)
3198+
rt = Bool
3199+
effects = EFFECTS_TOTAL
3200+
exct = Union{}
3201+
if isexpr(sym, :static_parameter)
3202+
n = sym.args[1]::Int
3203+
if 1 <= n <= length(sv.sptypes)
3204+
sp = sv.sptypes[n]
3205+
if !sp.undef
3206+
rt = Const(true)
3207+
elseif sp.typ === Bottom
3208+
rt = Const(false)
3209+
end
3210+
end
3211+
else
3212+
effects = EFFECTS_UNKNOWN
3213+
exct = Any
3214+
end
3215+
return RTEffects(rt, exct, effects)
31973216
end
31983217

31993218
const generic_isdefinedglobal_effects = Effects(EFFECTS_TOTAL, consistent=ALWAYS_FALSE, nothrow=false)
32003219
function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module, sym::Symbol, allow_import::Union{Bool, Nothing}, saw_latestworld::Bool, sv::AbsIntState)
32013220
rt = Bool
32023221
if saw_latestworld
3203-
return RTEffects(rt, Union{}, Effects(generic_isdefinedglobal_effects, nothrow=true))
3222+
return CallMeta(RTEffects(rt, Union{}, Effects(generic_isdefinedglobal_effects, nothrow=true)), NoCallInfo())
32043223
end
32053224

32063225
effects = EFFECTS_TOTAL
@@ -3222,7 +3241,7 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, mod::Module,
32223241
effects = Effects(generic_isdefinedglobal_effects, nothrow=true)
32233242
end
32243243
end
3225-
return RTEffects(rt, Union{}, effects)
3244+
return CallMeta(RTEffects(rt, Union{}, effects), GlobalAccessInfo(partition))
32263245
end
32273246

32283247
function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecialize(M), @nospecialize(s), @nospecialize(allow_import_arg), @nospecialize(order_arg), saw_latestworld::Bool, sv::AbsIntState)
@@ -3247,7 +3266,7 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecializ
32473266
if M isa Const && s isa Const
32483267
M, s = M.val, s.val
32493268
if M isa Module && s isa Symbol
3250-
return merge_exct(CallMeta(abstract_eval_isdefinedglobal(interp, M, s, allow_import, saw_latestworld, sv), NoCallInfo()), exct)
3269+
return merge_exct(abstract_eval_isdefinedglobal(interp, M, s, allow_import, saw_latestworld, sv), exct)
32513270
end
32523271
return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
32533272
elseif !hasintersect(widenconst(M), Module) || !hasintersect(widenconst(s), Symbol)
@@ -3258,26 +3277,6 @@ function abstract_eval_isdefinedglobal(interp::AbstractInterpreter, @nospecializ
32583277
return CallMeta(Bool, Union{exct, TypeError, UndefVarError}, generic_isdefinedglobal_effects, NoCallInfo())
32593278
end
32603279

3261-
function abstract_eval_isdefined(interp::AbstractInterpreter, @nospecialize(sym), saw_latestworld::Bool, sv::AbsIntState)
3262-
rt = Bool
3263-
effects = EFFECTS_TOTAL
3264-
exct = Union{}
3265-
if isexpr(sym, :static_parameter)
3266-
n = sym.args[1]::Int
3267-
if 1 <= n <= length(sv.sptypes)
3268-
sp = sv.sptypes[n]
3269-
if !sp.undef
3270-
rt = Const(true)
3271-
elseif sp.typ === Bottom
3272-
rt = Const(false)
3273-
end
3274-
end
3275-
else
3276-
effects = EFFECTS_UNKNOWN
3277-
end
3278-
return RTEffects(rt, exct, effects)
3279-
end
3280-
32813280
function abstract_eval_throw_undef_if_not(interp::AbstractInterpreter, e::Expr, sstate::StatementState, sv::AbsIntState)
32823281
condt = abstract_eval_value(interp, e.args[2], sstate, sv)
32833282
condval = maybe_extract_const_bool(condt)
@@ -3533,26 +3532,29 @@ end
35333532

35343533
function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, saw_latestworld::Bool, sv::AbsIntState)
35353534
if saw_latestworld
3536-
return RTEffects(Any, Any, generic_getglobal_effects)
3535+
return Pair{RTEffects, Union{Nothing, Core.BindingPartition}}(RTEffects(Any, Any, generic_getglobal_effects), nothing)
35373536
end
35383537
partition = abstract_eval_binding_partition!(interp, g, sv)
35393538
ret = abstract_eval_partition_load(interp, partition)
35403539
if ret.rt !== Union{} && ret.exct === UndefVarError && InferenceParams(interp).assume_bindings_static
35413540
if isdefined(g, :binding) && isdefined(g.binding, :value)
3542-
return RTEffects(ret.rt, Union{}, Effects(generic_getglobal_effects, nothrow=true))
3541+
ret = RTEffects(ret.rt, Union{}, Effects(generic_getglobal_effects, nothrow=true))
35433542
end
35443543
# We do not assume in general that assigned global bindings remain assigned.
35453544
# The existence of pkgimages allows them to revert in practice.
35463545
end
3547-
return ret
3546+
return Pair{RTEffects, Union{Nothing, Core.BindingPartition}}(ret, partition)
35483547
end
35493548

35503549
function global_assignment_rt_exct(interp::AbstractInterpreter, sv::AbsIntState, saw_latestworld::Bool, g::GlobalRef, @nospecialize(newty))
35513550
if saw_latestworld
3552-
return Pair{Any,Any}(newty, Union{ErrorException, TypeError})
3551+
return Pair{Pair{Any,Any}, Union{Core.BindingPartition, Nothing}}(
3552+
Pair{Any,Any}(newty, Union{ErrorException, TypeError}), nothing)
35533553
end
35543554
partition = abstract_eval_binding_partition!(interp, g, sv)
3555-
return global_assignment_binding_rt_exct(interp, partition, newty)
3555+
return Pair{Pair{Any,Any}, Union{Core.BindingPartition, Nothing}}(
3556+
global_assignment_binding_rt_exct(interp, partition, newty),
3557+
partition)
35563558
end
35573559

35583560
function global_assignment_binding_rt_exct(interp::AbstractInterpreter, partition::Core.BindingPartition, @nospecialize(newty))
@@ -3573,18 +3575,6 @@ function global_assignment_binding_rt_exct(interp::AbstractInterpreter, partitio
35733575
return Pair{Any,Any}(newty, Bottom)
35743576
end
35753577

3576-
function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, saw_latestworld::Bool, lhs::GlobalRef, @nospecialize(newty))
3577-
effect_free = ALWAYS_FALSE
3578-
nothrow = global_assignment_rt_exct(interp, frame, saw_latestworld, lhs, ignorelimited(newty))[2] === Union{}
3579-
inaccessiblememonly = ALWAYS_FALSE
3580-
if !nothrow
3581-
sub_curr_ssaflag!(frame, IR_FLAG_NOTHROW)
3582-
end
3583-
sub_curr_ssaflag!(frame, IR_FLAG_EFFECT_FREE)
3584-
merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; effect_free, nothrow, inaccessiblememonly))
3585-
return nothing
3586-
end
3587-
35883578
abstract_eval_ssavalue(s::SSAValue, sv::InferenceState) = abstract_eval_ssavalue(s, sv.ssavaluetypes)
35893579

35903580
function abstract_eval_ssavalue(s::SSAValue, ssavaluetypes::Vector{Any})

Compiler/src/stmtinfo.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,4 +481,18 @@ end
481481
add_edges_impl(edges::Vector{Any}, info::VirtualMethodMatchInfo) =
482482
_add_edges_impl(edges, info.info, #=mi_edge=#true)
483483

484+
"""
485+
info::GlobalAccessInfo <: CallInfo
486+
487+
Represents access to a global through runtime reflection, rather than as a manifest
488+
`GlobalRef` in the source code. Used for builtins (getglobal/setglobal/etc.) that
489+
perform such accesses.
490+
"""
491+
struct GlobalAccessInfo <: CallInfo
492+
bpart::Core.BindingPartition
493+
end
494+
GlobalAccessInfo(::Nothing) = NoCallInfo()
495+
add_edges_impl(edges::Vector{Any}, info::GlobalAccessInfo) =
496+
push!(edges, info.bpart)
497+
484498
@specialize

Compiler/src/typeinfer.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,9 @@ function store_backedges(caller::CodeInstance, edges::SimpleVector)
544544
# ignore `Method`-edges (from e.g. failed `abstract_call_method`)
545545
i += 1
546546
continue
547+
elseif isa(item, Core.BindingPartition)
548+
i += 1
549+
continue
547550
end
548551
if isa(item, CodeInstance)
549552
item = item.def

base/invalidation.jl

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ globalrefs(mod::Module) = GlobalRefIterator(mod)
99
function iterate(gri::GlobalRefIterator, i = 1)
1010
m = gri.mod
1111
table = ccall(:jl_module_get_bindings, Ref{SimpleVector}, (Any,), m)
12-
i == length(table) && return nothing
12+
i > length(table) && return nothing
1313
b = table[i]
1414
b === nothing && return iterate(gri, i+1)
1515
return ((b::Core.Binding).globalref, i+1)
@@ -85,18 +85,33 @@ function should_invalidate_code_for_globalref(gr::GlobalRef, src::CodeInfo)
8585
return found_any
8686
end
8787

88-
function invalidate_code_for_globalref!(gr::GlobalRef, new_max_world::UInt)
89-
valid_in_valuepos = false
90-
foreach_reachable_mtable(new_max_world) do mt::Core.MethodTable
91-
for method in MethodList(mt)
92-
if isdefined(method, :source)
93-
src = _uncompressed_ir(method)
94-
old_stmts = src.code
95-
if should_invalidate_code_for_globalref(gr, src)
88+
function scan_edge_list(ci::Core.CodeInstance, bpart::Core.BindingPartition)
89+
isdefined(ci, :edges) || return false
90+
edges = ci.edges
91+
i = 1
92+
while i <= length(edges)
93+
if isassigned(edges, i) && edges[i] === bpart
94+
return true
95+
end
96+
i += 1
97+
end
98+
return false
99+
end
100+
101+
function invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_max_world::UInt)
102+
try
103+
valid_in_valuepos = false
104+
foreach_reachable_mtable(new_max_world) do mt::Core.MethodTable
105+
for method in MethodList(mt)
106+
if isdefined(method, :source)
107+
src = _uncompressed_ir(method)
108+
old_stmts = src.code
109+
invalidate_all = should_invalidate_code_for_globalref(gr, src)
96110
for mi in specializations(method)
111+
isdefined(mi, :cache) || continue
97112
ci = mi.cache
98113
while true
99-
if ci.max_world > new_max_world
114+
if ci.max_world > new_max_world && (invalidate_all || scan_edge_list(ci, invalidated_bpart))
100115
ccall(:jl_invalidate_code_instance, Cvoid, (Any, UInt), ci, new_max_world)
101116
end
102117
isdefined(ci, :next) || break
@@ -105,7 +120,11 @@ function invalidate_code_for_globalref!(gr::GlobalRef, new_max_world::UInt)
105120
end
106121
end
107122
end
123+
return true
108124
end
109-
return true
125+
catch err
126+
bt = catch_backtrace()
127+
invokelatest(Base.println, "Internal Error during invalidation:")
128+
invokelatest(Base.display_error, err, bt)
110129
end
111130
end

src/module.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var
10321032
jl_gc_wb(bpart, val);
10331033
}
10341034

1035-
void jl_invalidate_binding_refs(jl_globalref_t *ref, size_t new_world)
1035+
void jl_invalidate_binding_refs(jl_globalref_t *ref, jl_binding_partition_t *invalidated_bpart, size_t new_world)
10361036
{
10371037
static jl_value_t *invalidate_code_for_globalref = NULL;
10381038
if (invalidate_code_for_globalref == NULL && jl_base_module != NULL)
@@ -1043,7 +1043,7 @@ void jl_invalidate_binding_refs(jl_globalref_t *ref, size_t new_world)
10431043
jl_error("Binding invalidation is not permitted during image generation.");
10441044
jl_value_t *boxed_world = jl_box_ulong(new_world);
10451045
JL_GC_PUSH1(&boxed_world);
1046-
jl_call2((jl_function_t*)invalidate_code_for_globalref, (jl_value_t*)ref, boxed_world);
1046+
jl_call3((jl_function_t*)invalidate_code_for_globalref, (jl_value_t*)ref, (jl_value_t*)invalidated_bpart, boxed_world);
10471047
JL_GC_POP();
10481048
}
10491049

@@ -1063,10 +1063,10 @@ JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr)
10631063
jl_task_t *ct = jl_current_task;
10641064
size_t last_world = ct->world_age;
10651065
size_t new_max_world = jl_atomic_load_acquire(&jl_world_counter);
1066+
jl_atomic_store_release(&bpart->max_world, new_max_world);
10661067
ct->world_age = jl_typeinf_world;
1067-
jl_invalidate_binding_refs(gr, new_max_world);
1068+
jl_invalidate_binding_refs(gr, bpart, new_max_world);
10681069
ct->world_age = last_world;
1069-
jl_atomic_store_release(&bpart->max_world, new_max_world);
10701070
jl_atomic_store_release(&jl_world_counter, new_max_world + 1);
10711071
JL_UNLOCK(&world_counter_lock);
10721072
}

src/staticdata_utils.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,10 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t *minworld, size
878878
size_t min_valid2;
879879
size_t max_valid2;
880880
assert(!jl_is_method(edge)); // `Method`-edge isn't allowed for the optimized one-edge format
881+
if (jl_is_binding_partition(edge)) {
882+
j += 1;
883+
continue;
884+
}
881885
if (jl_is_code_instance(edge))
882886
edge = (jl_value_t*)jl_get_ci_mi((jl_code_instance_t*)edge);
883887
if (jl_is_method_instance(edge)) {
@@ -1051,6 +1055,10 @@ static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list)
10511055
j += 1;
10521056
continue;
10531057
}
1058+
else if (jl_is_binding_partition(edge)) {
1059+
j += 1;
1060+
continue;
1061+
}
10541062
if (jl_is_code_instance(edge))
10551063
edge = (jl_value_t*)((jl_code_instance_t*)edge)->def;
10561064
if (jl_is_method_instance(edge)) {

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,11 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, baile
646646
sv::CC.InferenceState)
647647
if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
648648
if isdefined_globalref(g)
649-
return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
649+
return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
650+
CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL), nothing)
650651
end
651-
return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
652+
return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
653+
CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), nothing)
652654
end
653655
return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool,
654656
sv::CC.InferenceState)

sysimage.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \
4343
base/int.jl \
4444
base/indices.jl \
4545
base/iterators.jl \
46+
base/invalidation.jl \
4647
base/namedtuple.jl \
4748
base/number.jl \
4849
base/operators.jl \

test/rebinding.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ module Rebinding
3131

3232
# Tests for @world syntax
3333
@test Base.@world(Foo, defined_world_age) == typeof(x)
34-
@test Base.@world(Rebinding.Foo, defined_world_age) == typeof(x)
34+
nameof(@__MODULE__) === :Rebinding && @test Base.@world(Rebinding.Foo, defined_world_age) == typeof(x)
3535
@test Base.@world((@__MODULE__).Foo, defined_world_age) == typeof(x)
3636

3737
# Test invalidation (const -> undefined)
@@ -40,4 +40,11 @@ module Rebinding
4040
@test f_return_delete_me() == 1
4141
Base.delete_binding(@__MODULE__, :delete_me)
4242
@test_throws UndefVarError f_return_delete_me()
43+
44+
## + via indirect access
45+
const delete_me = 2
46+
f_return_delete_me_indirect() = getglobal(@__MODULE__, :delete_me)
47+
@test f_return_delete_me_indirect() == 2
48+
Base.delete_binding(@__MODULE__, :delete_me)
49+
@test_throws UndefVarError f_return_delete_me_indirect()
4350
end

0 commit comments

Comments
 (0)