Skip to content

Commit 5e3f967

Browse files
KenoKristofferC
authored andcommitted
bpart: Track whether any binding replacement has happened in image modules (#57433)
This implements the optimization proposed in #57426 by keeping track of whether any bindings were replaced in image modules (excluding `Main` as facilitated by #57426). In addition, we augment serialization to keep track of whether a method body contains any GlobalRefs that point to a loaded (system or package) image. If both of these flags are true, we can skip scanning the body of the method, since we know that we neither need to add any additional backedges nor were any of the referenced bindings invalidated. The performance impact on end-to-end load time is small, but measurable. Overall `@time using ModelingToolkit` consistently improves about 5% using this PR. However, I should note that using time is still about 40% slower than 1.11. This is not necessarily an Apples-to-Apples comparison as there were substantial other changes on 1.12 (as well as current load-time-tunings targeting older versions), but I wanted to put the number context. (cherry picked from commit f6e2b98)
1 parent 1c9d39d commit 5e3f967

File tree

13 files changed

+122
-20
lines changed

13 files changed

+122
-20
lines changed

base/client.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ function exec_options(opts)
265265
distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL)
266266
if distributed_mode
267267
let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed"))
268-
Core.eval(MainInclude, :(const Distributed = $Distributed))
268+
MainInclude.Distributed = Distributed
269269
Core.eval(Main, :(using Base.MainInclude.Distributed))
270270
invokelatest(Distributed.process_opts, opts)
271271
end
@@ -400,7 +400,7 @@ function load_InteractiveUtils(mod::Module=Main)
400400
try
401401
# TODO: we have to use require_stdlib here because it is a dependency of REPL, but we would sort of prefer not to
402402
let InteractiveUtils = require_stdlib(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils"))
403-
Core.eval(MainInclude, :(const InteractiveUtils = $InteractiveUtils))
403+
MainInclude.InteractiveUtils = InteractiveUtils
404404
end
405405
catch ex
406406
@warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace())
@@ -535,6 +535,10 @@ The thrown errors are collected in a stack of exceptions.
535535
"""
536536
global err = nothing
537537

538+
# Used for memoizing require_stdlib of these modules
539+
global InteractiveUtils::Module
540+
global Distributed::Module
541+
538542
# weakly exposes ans and err variables to Main
539543
export ans, err
540544
end

base/invalidation.jl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,26 +180,34 @@ function binding_was_invalidated(b::Core.Binding)
180180
b.partitions.min_world > unsafe_load(cglobal(:jl_require_world, UInt))
181181
end
182182

183-
function scan_new_method!(methods_with_invalidated_source::IdSet{Method}, method::Method)
183+
function scan_new_method!(methods_with_invalidated_source::IdSet{Method}, method::Method, image_backedges_only::Bool)
184184
isdefined(method, :source) || return
185+
if image_backedges_only && !has_image_globalref(method)
186+
return
187+
end
185188
src = _uncompressed_ir(method)
186189
mod = method.module
187190
foreachgr(src) do gr::GlobalRef
188191
b = convert(Core.Binding, gr)
189-
binding_was_invalidated(b) && push!(methods_with_invalidated_source, method)
192+
if binding_was_invalidated(b)
193+
# TODO: We could turn this into an addition if condition. For now, use it as a reasonably cheap
194+
# additional consistency chekc
195+
@assert !image_backedges_only
196+
push!(methods_with_invalidated_source, method)
197+
end
190198
maybe_add_binding_backedge!(b, method)
191199
end
192200
end
193201

194-
function scan_new_methods(extext_methods::Vector{Any}, internal_methods::Vector{Any})
202+
function scan_new_methods(extext_methods::Vector{Any}, internal_methods::Vector{Any}, image_backedges_only::Bool)
195203
methods_with_invalidated_source = IdSet{Method}()
196204
for method in internal_methods
197205
if isa(method, Method)
198-
scan_new_method!(methods_with_invalidated_source, method)
206+
scan_new_method!(methods_with_invalidated_source, method, image_backedges_only)
199207
end
200208
end
201209
for tme::Core.TypeMapEntry in extext_methods
202-
scan_new_method!(methods_with_invalidated_source, tme.func::Method)
210+
scan_new_method!(methods_with_invalidated_source, tme.func::Method, image_backedges_only)
203211
end
204212
return methods_with_invalidated_source
205213
end

base/runtime_internals.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,3 +1656,5 @@ isempty(mt::Core.MethodTable) = (mt.defs === nothing)
16561656
uncompressed_ir(m::Method) = isdefined(m, :source) ? _uncompressed_ir(m) :
16571657
isdefined(m, :generator) ? error("Method is @generated; try `code_lowered` instead.") :
16581658
error("Code for this Method is not available.")
1659+
1660+
has_image_globalref(m::Method) = ccall(:jl_ir_flag_has_image_globalref, Bool, (Any,), m.source)

base/staticdata.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ end
2525
function insert_backedges(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{Any}}, extext_methods::Vector{Any}, internal_methods::Vector{Any})
2626
# determine which CodeInstance objects are still valid in our image
2727
# to enable any applicable new codes
28-
methods_with_invalidated_source = Base.scan_new_methods(extext_methods, internal_methods)
28+
backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt)
29+
methods_with_invalidated_source = Base.scan_new_methods(extext_methods, internal_methods, backedges_only)
2930
stack = CodeInstance[]
3031
visiting = IdDict{CodeInstance,Int}()
3132
_insert_backedges(edges, stack, visiting, methods_with_invalidated_source)

src/ircode.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,14 +547,15 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal)
547547
}
548548
}
549549

550-
static jl_code_info_flags_t code_info_flags(uint8_t propagate_inbounds, uint8_t has_fcall,
550+
static jl_code_info_flags_t code_info_flags(uint8_t propagate_inbounds, uint8_t has_fcall, uint8_t has_image_globalref,
551551
uint8_t nospecializeinfer, uint8_t isva,
552552
uint8_t inlining, uint8_t constprop, uint8_t nargsmatchesmethod,
553553
jl_array_t *ssaflags)
554554
{
555555
jl_code_info_flags_t flags;
556556
flags.bits.propagate_inbounds = propagate_inbounds;
557557
flags.bits.has_fcall = has_fcall;
558+
flags.bits.has_image_globalref = has_image_globalref;
558559
flags.bits.nospecializeinfer = nospecializeinfer;
559560
flags.bits.isva = isva;
560561
flags.bits.inlining = inlining;
@@ -1036,7 +1037,7 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code)
10361037
};
10371038

10381039
uint8_t nargsmatchesmethod = code->nargs == m->nargs;
1039-
jl_code_info_flags_t flags = code_info_flags(code->propagate_inbounds, code->has_fcall,
1040+
jl_code_info_flags_t flags = code_info_flags(code->propagate_inbounds, code->has_fcall, code->has_image_globalref,
10401041
code->nospecializeinfer, code->isva,
10411042
code->inlining, code->constprop,
10421043
nargsmatchesmethod,
@@ -1134,6 +1135,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t
11341135
code->constprop = flags.bits.constprop;
11351136
code->propagate_inbounds = flags.bits.propagate_inbounds;
11361137
code->has_fcall = flags.bits.has_fcall;
1138+
code->has_image_globalref = flags.bits.has_image_globalref;
11371139
code->nospecializeinfer = flags.bits.nospecializeinfer;
11381140
code->isva = flags.bits.isva;
11391141
code->purity.bits = read_uint16(s.s);
@@ -1228,6 +1230,16 @@ JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_string_t *data)
12281230
return flags.bits.has_fcall;
12291231
}
12301232

1233+
JL_DLLEXPORT uint8_t jl_ir_flag_has_image_globalref(jl_string_t *data)
1234+
{
1235+
if (jl_is_code_info(data))
1236+
return ((jl_code_info_t*)data)->has_image_globalref;
1237+
assert(jl_is_string(data));
1238+
jl_code_info_flags_t flags;
1239+
flags.packed = jl_string_data(data)[ir_offset_flags];
1240+
return flags.bits.has_image_globalref;
1241+
}
1242+
12311243
JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_string_t *data)
12321244
{
12331245
if (jl_is_code_info(data))

src/jltypes.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3485,7 +3485,7 @@ void jl_init_types(void) JL_GC_DISABLED
34853485
jl_code_info_type =
34863486
jl_new_datatype(jl_symbol("CodeInfo"), core,
34873487
jl_any_type, jl_emptysvec,
3488-
jl_perm_symsvec(22,
3488+
jl_perm_symsvec(23,
34893489
"code",
34903490
"debuginfo",
34913491
"ssavaluetypes",
@@ -3502,13 +3502,14 @@ void jl_init_types(void) JL_GC_DISABLED
35023502
"nargs",
35033503
"propagate_inbounds",
35043504
"has_fcall",
3505+
"has_image_globalref",
35053506
"nospecializeinfer",
35063507
"isva",
35073508
"inlining",
35083509
"constprop",
35093510
"purity",
35103511
"inlining_cost"),
3511-
jl_svec(22,
3512+
jl_svec(23,
35123513
jl_array_any_type,
35133514
jl_debuginfo_type,
35143515
jl_any_type,
@@ -3527,6 +3528,7 @@ void jl_init_types(void) JL_GC_DISABLED
35273528
jl_bool_type,
35283529
jl_bool_type,
35293530
jl_bool_type,
3531+
jl_bool_type,
35303532
jl_uint8_type,
35313533
jl_uint8_type,
35323534
jl_uint16_type,

src/julia.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ typedef struct _jl_code_info_t {
312312
// various boolean properties:
313313
uint8_t propagate_inbounds;
314314
uint8_t has_fcall;
315+
uint8_t has_image_globalref;
315316
uint8_t nospecializeinfer;
316317
uint8_t isva;
317318
// uint8 settings
@@ -2263,6 +2264,7 @@ JL_DLLEXPORT jl_value_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code);
22632264
JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t *metadata, jl_value_t *data);
22642265
JL_DLLEXPORT uint8_t jl_ir_flag_inlining(jl_value_t *data) JL_NOTSAFEPOINT;
22652266
JL_DLLEXPORT uint8_t jl_ir_flag_has_fcall(jl_value_t *data) JL_NOTSAFEPOINT;
2267+
JL_DLLEXPORT uint8_t jl_ir_flag_has_image_globalref(jl_value_t *data) JL_NOTSAFEPOINT;
22662268
JL_DLLEXPORT uint16_t jl_ir_inlining_cost(jl_value_t *data) JL_NOTSAFEPOINT;
22672269
JL_DLLEXPORT ssize_t jl_ir_nslots(jl_value_t *data) JL_NOTSAFEPOINT;
22682270
JL_DLLEXPORT uint8_t jl_ir_slotflag(jl_value_t *data, size_t i) JL_NOTSAFEPOINT;

src/julia_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ STATIC_INLINE jl_value_t *undefref_check(jl_datatype_t *dt, jl_value_t *v) JL_NO
650650
typedef struct {
651651
uint16_t propagate_inbounds:1;
652652
uint16_t has_fcall:1;
653+
uint16_t has_image_globalref:1;
653654
uint16_t nospecializeinfer:1;
654655
uint16_t isva:1;
655656
uint16_t nargsmatchesmethod:1;

src/method.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ jl_code_info_t *jl_new_code_info_from_ir(jl_expr_t *ir)
486486
is_flag_stmt = 1;
487487
else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_return_sym)
488488
jl_array_ptr_set(body, j, jl_new_struct(jl_returnnode_type, jl_exprarg(st, 0)));
489+
else if (jl_is_globalref(st)) {
490+
jl_globalref_t *gr = (jl_globalref_t*)st;
491+
if (jl_object_in_image((jl_value_t*)gr->mod))
492+
li->has_image_globalref = 1;
493+
}
489494
else {
490495
if (jl_is_expr(st) && ((jl_expr_t*)st)->head == jl_assign_sym)
491496
st = jl_exprarg(st, 1);
@@ -593,6 +598,7 @@ JL_DLLEXPORT jl_code_info_t *jl_new_code_info_uninit(void)
593598
src->max_world = ~(size_t)0;
594599
src->propagate_inbounds = 0;
595600
src->has_fcall = 0;
601+
src->has_image_globalref = 0;
596602
src->nospecializeinfer = 0;
597603
src->constprop = 0;
598604
src->inlining = 0;

src/module.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,10 +1294,19 @@ JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b,
12941294
new_world);
12951295
}
12961296

1297+
extern JL_DLLEXPORT _Atomic(size_t) jl_first_image_replacement_world;
12971298
JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked2(jl_binding_t *b,
12981299
jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, size_t kind, size_t new_world)
12991300
{
13001301
check_safe_newbinding(b->globalref->mod, b->globalref->name);
1302+
1303+
// Check if this is a replacing a binding in the system or a package image.
1304+
// Until the first such replacement, we can fast-path validation.
1305+
// For these purposes, we consider the `Main` module to be a non-sysimg module.
1306+
// This is legal, because we special case the `Main` in check_safe_import_from.
1307+
if (jl_object_in_image((jl_value_t*)b) && b->globalref->mod != jl_main_module && jl_atomic_load_relaxed(&jl_first_image_replacement_world) == ~(size_t)0)
1308+
jl_atomic_store_relaxed(&jl_first_image_replacement_world, new_world);
1309+
13011310
assert(jl_atomic_load_relaxed(&b->partitions) == old_bpart);
13021311
jl_atomic_store_release(&old_bpart->max_world, new_world-1);
13031312
jl_binding_partition_t *new_bpart = new_binding_partition();

0 commit comments

Comments
 (0)