Skip to content

Commit 8bdacc3

Browse files
authored
Add basic infrastructure for binding replacement (#56224)
Now that I've had a few months to recover from the slog of adding `BindingPartition`, it's time to renew my quest to finish #54654. This adds the basic infrastructure for having multiple partitions, including making the lookup respect the `world` argument - on-demand allocation of missing partitions, `Base.delete_binding` and the `@world` macro. Not included is any inference or invalidation support, or any support for the runtime to create partitions itself (only `Base.delete_binding` does that for now), which will come in subsequent PRs.
1 parent 1c67d0c commit 8bdacc3

12 files changed

+214
-44
lines changed

base/essentials.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,50 @@ function isiterable(T)::Bool
12501250
return hasmethod(iterate, Tuple{T})
12511251
end
12521252

1253+
"""
1254+
@world(sym, world)
1255+
1256+
Resolve the binding `sym` in world `world`. See [`invoke_in_world`](@ref) for running
1257+
arbitrary code in fixed worlds. `world` may be `UnitRange`, in which case the macro
1258+
will error unless the binding is valid and has the same value across the entire world
1259+
range.
1260+
1261+
The `@world` macro is primarily used in the priniting of bindings that are no longer available
1262+
in the current world.
1263+
1264+
## Example
1265+
```
1266+
julia> struct Foo; a::Int; end
1267+
Foo
1268+
1269+
julia> fold = Foo(1)
1270+
1271+
julia> Int(Base.get_world_counter())
1272+
26866
1273+
1274+
julia> struct Foo; a::Int; b::Int end
1275+
Foo
1276+
1277+
julia> fold
1278+
@world(Foo, 26866)(1)
1279+
```
1280+
1281+
!!! compat "Julia 1.12"
1282+
This functionality requires at least Julia 1.12.
1283+
"""
1284+
macro world(sym, world)
1285+
if isa(sym, Symbol)
1286+
return :($(_resolve_in_world)($world, $(QuoteNode(GlobalRef(__module__, sym)))))
1287+
elseif isa(sym, GlobalRef)
1288+
return :($(_resolve_in_world)($world, $(QuoteNode(sym))))
1289+
else
1290+
error("`@world` requires a symbol or GlobalRef")
1291+
end
1292+
end
1293+
1294+
_resolve_in_world(world::Integer, gr::GlobalRef) =
1295+
invoke_in_world(UInt(world), Core.getglobal, gr.mod, gr.name)
1296+
12531297
# Special constprop heuristics for various binary opes
12541298
typename(typeof(function + end)).constprop_heuristic = Core.SAMETYPE_HEURISTIC
12551299
typename(typeof(function - end)).constprop_heuristic = Core.SAMETYPE_HEURISTIC

base/range.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,3 +1680,14 @@ function show(io::IO, r::LogRange{T}) where {T}
16801680
show(io, length(r))
16811681
print(io, ')')
16821682
end
1683+
1684+
# Implementation detail of @world
1685+
# The rest of this is defined in essentials.jl, but UnitRange is not available
1686+
function _resolve_in_world(worlds::UnitRange, gr::GlobalRef)
1687+
# Validate that this binding's reference covers the entire world range
1688+
bpart = lookup_binding_partition(first(worlds), gr)
1689+
if bpart.max_world < last(world)
1690+
error("Binding does not cover the full world range")
1691+
end
1692+
_resolve_in_world(last(world), gr)
1693+
end

base/runtime_internals.jl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,19 @@ function _fieldnames(@nospecialize t)
218218
return t.name.names
219219
end
220220

221-
const BINDING_KIND_GLOBAL = 0x0
222-
const BINDING_KIND_CONST = 0x1
223-
const BINDING_KIND_CONST_IMPORT = 0x2
221+
# N.B.: Needs to be synced with julia.h
222+
const BINDING_KIND_CONST = 0x0
223+
const BINDING_KIND_CONST_IMPORT = 0x1
224+
const BINDING_KIND_GLOBAL = 0x2
224225
const BINDING_KIND_IMPLICIT = 0x3
225226
const BINDING_KIND_EXPLICIT = 0x4
226227
const BINDING_KIND_IMPORTED = 0x5
227228
const BINDING_KIND_FAILED = 0x6
228229
const BINDING_KIND_DECLARED = 0x7
229230
const BINDING_KIND_GUARD = 0x8
230231

232+
is_some_const_binding(kind::UInt8) = (kind == BINDING_KIND_CONST || kind == BINDING_KIND_CONST_IMPORT)
233+
231234
function lookup_binding_partition(world::UInt, b::Core.Binding)
232235
ccall(:jl_get_binding_partition, Ref{Core.BindingPartition}, (Any, UInt), b, world)
233236
end
@@ -236,9 +239,27 @@ function lookup_binding_partition(world::UInt, gr::Core.GlobalRef)
236239
ccall(:jl_get_globalref_partition, Ref{Core.BindingPartition}, (Any, UInt), gr, world)
237240
end
238241

242+
partition_restriction(bpart::Core.BindingPartition) = ccall(:jl_bpart_get_restriction_value, Any, (Any,), bpart)
243+
239244
binding_kind(bpart::Core.BindingPartition) = ccall(:jl_bpart_get_kind, UInt8, (Any,), bpart)
240245
binding_kind(m::Module, s::Symbol) = binding_kind(lookup_binding_partition(tls_world_age(), GlobalRef(m, s)))
241246

247+
"""
248+
delete_binding(mod::Module, sym::Symbol)
249+
250+
Force the binding `mod.sym` to be undefined again, allowing it be redefined.
251+
Note that this operation is very expensive, requirinig a full scan of all code in the system,
252+
as well as potential recompilation of any methods that (may) have used binding
253+
information.
254+
255+
!!! warning
256+
The implementation of this functionality is currently incomplete. Do not use
257+
this method on versions that contain this disclaimer except for testing.
258+
"""
259+
function delete_binding(mod::Module, sym::Symbol)
260+
ccall(:jl_disable_binding, Cvoid, (Any,), GlobalRef(mod, sym))
261+
end
262+
242263
"""
243264
fieldname(x::DataType, i::Integer)
244265

base/show.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,21 @@ function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing})
10351035
return false
10361036
end
10371037

1038+
function check_world_bounded(tn::Core.TypeName)
1039+
bnd = ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), tn.module, tn.name, true)
1040+
isdefined(bnd, :partitions) || return nothing
1041+
partition = @atomic bnd.partitions
1042+
while true
1043+
if is_some_const_binding(binding_kind(partition)) && partition_restriction(partition) <: tn.wrapper
1044+
max_world = @atomic partition.max_world
1045+
max_world == typemax(UInt) && return nothing
1046+
return Int(partition.min_world):Int(max_world)
1047+
end
1048+
isdefined(partition, :next) || return nothing
1049+
partition = @atomic partition.next
1050+
end
1051+
end
1052+
10381053
function show_type_name(io::IO, tn::Core.TypeName)
10391054
if tn === UnionAll.name
10401055
# by coincidence, `typeof(Type)` is a valid representation of the UnionAll type.
@@ -1063,7 +1078,10 @@ function show_type_name(io::IO, tn::Core.TypeName)
10631078
end
10641079
end
10651080
end
1081+
world = check_world_bounded(tn)
1082+
world !== nothing && print(io, "@world(")
10661083
show_sym(io, sym)
1084+
world !== nothing && print(io, ", ", world, ")")
10671085
quo && print(io, ")")
10681086
globfunc && print(io, ")")
10691087
nothing

src/clangsa/GCChecker.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,7 @@ bool GCChecker::isGCTrackedType(QualType QT) {
824824
Name.ends_with_insensitive("jl_tupletype_t") ||
825825
Name.ends_with_insensitive("jl_gc_tracked_buffer_t") ||
826826
Name.ends_with_insensitive("jl_binding_t") ||
827+
Name.ends_with_insensitive("jl_binding_partition_t") ||
827828
Name.ends_with_insensitive("jl_ordereddict_t") ||
828829
Name.ends_with_insensitive("jl_tvar_t") ||
829830
Name.ends_with_insensitive("jl_typemap_t") ||
@@ -847,6 +848,7 @@ bool GCChecker::isGCTrackedType(QualType QT) {
847848
Name.ends_with_insensitive("jl_stenv_t") ||
848849
Name.ends_with_insensitive("jl_varbinding_t") ||
849850
Name.ends_with_insensitive("set_world") ||
851+
Name.ends_with_insensitive("jl_ptr_kind_union_t") ||
850852
Name.ends_with_insensitive("jl_codectx_t")) {
851853
return true;
852854
}

src/julia.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,7 @@ typedef struct _jl_weakref_t {
620620
jl_value_t *value;
621621
} jl_weakref_t;
622622

623+
// N.B: Needs to be synced with runtime_internals.jl
623624
enum jl_partition_kind {
624625
// Constant: This binding partition is a constant declared using `const`
625626
// ->restriction holds the constant value
@@ -684,7 +685,7 @@ typedef struct __attribute__((aligned(8))) _jl_binding_partition_t {
684685
_Atomic(jl_ptr_kind_union_t) restriction;
685686
size_t min_world;
686687
_Atomic(size_t) max_world;
687-
_Atomic(struct _jl_binding_partition_t*) next;
688+
_Atomic(struct _jl_binding_partition_t *) next;
688689
size_t reserved; // Reserved for ->kind. Currently this holds the low bits of ->restriction during serialization
689690
} jl_binding_partition_t;
690691

@@ -1845,8 +1846,8 @@ JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, size_t len) JL_NOTSAFEPOINT;
18451846
JL_DLLEXPORT jl_sym_t *jl_gensym(void);
18461847
JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, size_t len);
18471848
JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void);
1848-
JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
1849-
JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
1849+
JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b JL_PROPAGATES_ROOT);
1850+
JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b JL_PROPAGATES_ROOT);
18501851
JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, jl_sym_t *name);
18511852
JL_DLLEXPORT jl_method_t *jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, jl_module_t *module);
18521853
JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, size_t world, jl_code_instance_t **cache);
@@ -2008,8 +2009,8 @@ JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_s
20082009
JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs);
20092010
JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs);
20102011
JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED);
2011-
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT;
2012-
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind) JL_NOTSAFEPOINT;
2012+
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED);
2013+
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind);
20132014
JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from);
20142015
JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s);
20152016
JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname);

src/julia_internal.h

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -888,13 +888,10 @@ EXTERN_INLINE_DECLARE enum jl_partition_kind decode_restriction_kind(jl_ptr_kind
888888
#endif
889889
}
890890

891-
STATIC_INLINE jl_value_t *decode_restriction_value(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT
891+
STATIC_INLINE jl_value_t *decode_restriction_value(jl_ptr_kind_union_t JL_PROPAGATES_ROOT pku) JL_NOTSAFEPOINT
892892
{
893893
#ifdef _P64
894894
jl_value_t *val = (jl_value_t*)(pku & ~0x7);
895-
// This is a little bit of a lie at the moment - it is one of the things that
896-
// can go wrong with binding replacement.
897-
JL_GC_PROMISE_ROOTED(val);
898895
return val;
899896
#else
900897
return pku.val;
@@ -928,14 +925,8 @@ STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFE
928925
return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED;
929926
}
930927

931-
EXTERN_INLINE_DECLARE jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) JL_NOTSAFEPOINT {
932-
if (!b)
933-
return NULL;
934-
assert(jl_is_binding(b));
935-
return jl_atomic_load_relaxed(&b->partitions);
936-
}
937-
938-
JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr, size_t world);
928+
JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world);
929+
JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr JL_PROPAGATES_ROOT, size_t world);
939930

940931
EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT {
941932
return decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction));

0 commit comments

Comments
 (0)