Skip to content

Commit 239a87c

Browse files
committed
Add basic infrastructure for binding replacement
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 727a57e commit 239a87c

10 files changed

+196
-32
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
@@ -1032,6 +1032,21 @@ function is_global_function(tn::Core.TypeName, globname::Union{Symbol,Nothing})
10321032
return false
10331033
end
10341034

1035+
function check_world_bounded(tn::Core.TypeName)
1036+
bnd = ccall(:jl_get_module_binding, Ref{Core.Binding}, (Any, Any, Cint), tn.module, tn.name, true)
1037+
isdefined(bnd, :partitions) || return nothing
1038+
partition = @atomic bnd.partitions
1039+
while true
1040+
if is_some_const_binding(binding_kind(partition)) && partition_restriction(partition) <: tn.wrapper
1041+
max_world = @atomic partition.max_world
1042+
max_world == typemax(UInt) && return nothing
1043+
return Int(partition.min_world):Int(max_world)
1044+
end
1045+
isdefined(partition, :next) || return nothing
1046+
partition = @atomic partition.next
1047+
end
1048+
end
1049+
10351050
function show_type_name(io::IO, tn::Core.TypeName)
10361051
if tn === UnionAll.name
10371052
# by coincidence, `typeof(Type)` is a valid representation of the UnionAll type.
@@ -1060,7 +1075,10 @@ function show_type_name(io::IO, tn::Core.TypeName)
10601075
end
10611076
end
10621077
end
1078+
world = check_world_bounded(tn)
1079+
world !== nothing && print(io, "@world(")
10631080
show_sym(io, sym)
1081+
world !== nothing && print(io, ", ", world, ")")
10641082
quo && print(io, ")")
10651083
globfunc && print(io, ")")
10661084
nothing

src/julia.h

Lines changed: 2 additions & 1 deletion
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

src/julia_internal.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -926,13 +926,7 @@ STATIC_INLINE int jl_bkind_is_some_guard(enum jl_partition_kind kind) JL_NOTSAFE
926926
return kind == BINDING_KIND_FAILED || kind == BINDING_KIND_GUARD || kind == BINDING_KIND_DECLARED;
927927
}
928928

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

938932
EXTERN_INLINE_DECLARE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT {

src/module.c

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,51 @@ extern "C" {
1313
#endif
1414

1515
// In this translation unit and this translation unit only emit this symbol `extern` for use by julia
16-
EXTERN_INLINE_DEFINE jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) JL_NOTSAFEPOINT;
1716
EXTERN_INLINE_DEFINE uint8_t jl_bpart_get_kind(jl_binding_partition_t *bpart) JL_NOTSAFEPOINT;
1817
extern inline enum jl_partition_kind decode_restriction_kind(jl_ptr_kind_union_t pku) JL_NOTSAFEPOINT;
1918

19+
static jl_binding_partition_t *new_binding_partition(void)
20+
{
21+
jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type);
22+
jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD));
23+
bpart->min_world = 0;
24+
jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1);
25+
jl_atomic_store_relaxed(&bpart->next, NULL);
26+
#ifdef _P64
27+
bpart->reserved = 0;
28+
#endif
29+
return bpart;
30+
}
31+
32+
jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) {
33+
if (!b)
34+
return NULL;
35+
assert(jl_is_binding(b));
36+
jl_value_t *parent = (jl_value_t*)b;
37+
_Atomic(jl_binding_partition_t *)*insert = &b->partitions;
38+
jl_binding_partition_t *bpart = jl_atomic_load_relaxed(insert);
39+
size_t max_world = (size_t)-1;
40+
while (1) {
41+
while (bpart && world < bpart->min_world) {
42+
insert = &bpart->next;
43+
max_world = bpart->min_world - 1;
44+
parent = (jl_value_t *)bpart;
45+
bpart = jl_atomic_load_relaxed(&bpart->next);
46+
}
47+
if (bpart && world <= jl_atomic_load_relaxed(&bpart->max_world))
48+
return bpart;
49+
jl_binding_partition_t *new_bpart = new_binding_partition();
50+
jl_atomic_store_relaxed(&new_bpart->next, bpart);
51+
if (bpart)
52+
new_bpart->min_world = jl_atomic_load_relaxed(&bpart->max_world) + 1;
53+
new_bpart->max_world = max_world;
54+
if (jl_atomic_cmpswap(insert, &bpart, new_bpart)) {
55+
jl_gc_wb(parent, new_bpart);
56+
return new_bpart;
57+
}
58+
}
59+
}
60+
2061
JL_DLLEXPORT jl_binding_partition_t *jl_get_globalref_partition(jl_globalref_t *gr, size_t world)
2162
{
2263
if (!gr)
@@ -188,19 +229,6 @@ static jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_bin
188229
return g;
189230
}
190231

191-
static jl_binding_partition_t *new_binding_partition(void)
192-
{
193-
jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type);
194-
jl_atomic_store_relaxed(&bpart->restriction, encode_restriction(NULL, BINDING_KIND_GUARD));
195-
bpart->min_world = 0;
196-
jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1);
197-
jl_atomic_store_relaxed(&bpart->next, NULL);
198-
#ifdef _P64
199-
bpart->reserved = 0;
200-
#endif
201-
return bpart;
202-
}
203-
204232
static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name)
205233
{
206234
jl_task_t *ct = jl_current_task;
@@ -215,9 +243,7 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name)
215243
JL_GC_PUSH1(&b);
216244
b->globalref = jl_new_globalref(mod, name, b);
217245
jl_gc_wb(b, b->globalref);
218-
jl_binding_partition_t *bpart = new_binding_partition();
219-
jl_atomic_store_relaxed(&b->partitions, bpart);
220-
jl_gc_wb(b, bpart);
246+
jl_atomic_store_relaxed(&b->partitions, NULL);
221247
JL_GC_POP();
222248
return b;
223249
}
@@ -324,6 +350,12 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b)
324350
return decode_restriction_value(pku);
325351
}
326352

353+
JL_DLLEXPORT jl_value_t *jl_bpart_get_restriction_value(jl_binding_partition_t *bpart)
354+
{
355+
jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction);
356+
return decode_restriction_value(pku);
357+
}
358+
327359
typedef struct _modstack_t {
328360
jl_module_t *m;
329361
jl_sym_t *var;
@@ -952,6 +984,28 @@ JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var
952984
jl_gc_wb(bpart, val);
953985
}
954986

987+
extern jl_mutex_t world_counter_lock;
988+
JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr)
989+
{
990+
jl_binding_t *b = gr->binding;
991+
b = jl_resolve_owner(b, gr->mod, gr->name, NULL);
992+
jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
993+
994+
if (decode_restriction_kind(jl_atomic_load_relaxed(&bpart->restriction)) == BINDING_KIND_GUARD) {
995+
// Already guard
996+
return;
997+
}
998+
999+
JL_LOCK(&world_counter_lock);
1000+
jl_task_t *ct = jl_current_task;
1001+
size_t new_max_world = jl_atomic_load_acquire(&jl_world_counter);
1002+
// TODO: Trigger invalidation here
1003+
(void)ct;
1004+
jl_atomic_store_release(&bpart->max_world, new_max_world);
1005+
jl_atomic_store_release(&jl_world_counter, new_max_world + 1);
1006+
JL_UNLOCK(&world_counter_lock);
1007+
}
1008+
9551009
JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr)
9561010
{
9571011
jl_binding_t *b = gr->binding;

src/staticdata.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3757,9 +3757,12 @@ static void jl_restore_system_image_from_stream_(ios_t *f, jl_image_t *image, jl
37573757
if ((jl_value_t*)b == jl_nothing)
37583758
continue;
37593759
jl_binding_partition_t *bpart = jl_atomic_load_relaxed(&b->partitions);
3760-
jl_atomic_store_relaxed(&bpart->restriction,
3761-
encode_restriction((jl_value_t*)jl_atomic_load_relaxed(&bpart->restriction), bpart->reserved));
3762-
bpart->reserved = 0;
3760+
while (bpart) {
3761+
jl_atomic_store_relaxed(&bpart->restriction,
3762+
encode_restriction((jl_value_t*)jl_atomic_load_relaxed(&bpart->restriction), bpart->reserved));
3763+
bpart->reserved = 0;
3764+
bpart = jl_atomic_load_relaxed(&bpart->next);
3765+
}
37633766
}
37643767
#endif
37653768
}

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const TESTNAMES = [
2929
"channels", "iostream", "secretbuffer", "specificity",
3030
"reinterpretarray", "syntax", "corelogging", "missing", "asyncmap",
3131
"smallarrayshrink", "opaque_closure", "filesystem", "download",
32-
"scopedvalues", "compileall"
32+
"scopedvalues", "compileall", "rebinding"
3333
]
3434

3535
const INTERNET_REQUIRED_LIST = [

test/rebinding.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module Rebinding
4+
using Test
5+
6+
@test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_GUARD
7+
struct Foo
8+
x::Int
9+
end
10+
x = Foo(1)
11+
12+
@test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_CONST
13+
@test !contains(repr(x), "@world")
14+
Base.delete_binding(@__MODULE__, :Foo)
15+
16+
@test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_GUARD
17+
@test contains(repr(x), "@world")
18+
end

0 commit comments

Comments
 (0)