Skip to content

Commit 19b72ab

Browse files
Drop sysimage caches for --code-coverage=all (#59234)
1 parent 4222264 commit 19b72ab

File tree

6 files changed

+120
-0
lines changed

6 files changed

+120
-0
lines changed

Compiler/test/invalidation.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,39 @@ end
325325
# This test checks for invalidation of recursive backedges. However, unfortunately, the original failure
326326
# manifestation was an unreliable segfault or an assertion failure, so we don't have a more compact test.
327327
@test success(`$(Base.julia_cmd()) -e 'Base.typejoin(x, ::Type) = 0; exit()'`)
328+
329+
# Test drop_all_caches functionality
330+
@testset "drop_all_caches" begin
331+
# Run in subprocess to avoid disrupting the main test process
332+
script = """
333+
# Define test functions
334+
drop_cache_test_f(x) = x + 1
335+
drop_cache_test_g(x) = drop_cache_test_f(x) * 2
336+
337+
# Compile the functions and capture stderr
338+
drop_cache_test_g(5) == 12 || error("failure")
339+
340+
println(stderr, "==DROPPING ALL CACHES==")
341+
342+
# Drop all caches
343+
Base.drop_all_caches()
344+
345+
# Functions should still work (but will be recompiled on next call)
346+
drop_cache_test_g(5) == 12 || error("failure")
347+
348+
println(stderr, "SUCCESS: drop_all_caches test passed")
349+
exit(0)
350+
"""
351+
352+
io = Pipe()
353+
# Run the test in a subprocess because Base.drop_all_caches() is extreme
354+
result = run(pipeline(`$(Base.julia_cmd()[1]) --startup-file=no --trace-compile=stderr -e "$script"`, stderr=io))
355+
close(io.in)
356+
err = read(io, String)
357+
# println(err)
358+
@test success(result)
359+
err_before, err_after = split(err, "==DROPPING ALL CACHES==")
360+
@test occursin("SUCCESS: drop_all_caches test passed", err_after)
361+
@test occursin("precompile(Tuple{typeof(Main.drop_cache_test_g), $Int})", err_before)
362+
@test occursin("precompile(Tuple{typeof(Main.drop_cache_test_g), $Int}) # recompile", err_after)
363+
end

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Command-line option changes
2828

2929
* The option `--sysimage-native-code=no` has been deprecated.
3030
* The `JULIA_CPU_TARGET` environment variable now supports a `sysimage` keyword to match (or extend) the CPU target used to build the current system image ([#58970]).
31+
* The `--code-coverage=all` option now automatically throws away sysimage caches so that code coverage can be accurately measured on methods within the sysimage. It is thrown away after startup (and after startup.jl), before any user code is executed ([#59234])
3132

3233
Multi-threading changes
3334
-----------------------

base/client.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ function exec_options(opts)
280280
end
281281
end
282282

283+
# drop all caches if code coverage is enabled. Do it here not earlier, so julia has a chance
284+
# of starting up quickly
285+
if Base.JLOptions().code_coverage == 2
286+
Base.drop_all_caches()
287+
end
288+
283289
# process cmds list
284290
for (cmd, arg) in cmds
285291
if cmd == 'e'

base/reflection.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,3 +1453,20 @@ function destructure_callex(topmod::Module, @nospecialize(ex))
14531453
end
14541454
return f, args, kwargs
14551455
end
1456+
1457+
"""
1458+
Base.drop_all_caches()
1459+
1460+
Internal function to drop all native code caches and increment world age.
1461+
This invalidates all compiled code as if a method was added that intersects
1462+
with all existing methods.
1463+
"""
1464+
function drop_all_caches()
1465+
ccall(:jl_drop_all_caches, Cvoid, ())
1466+
1467+
# Reset loading.jl world age so that loading code is regenerated
1468+
_require_world_age[] = typemax(UInt)
1469+
1470+
# Call Base.Compiler.activate!() after dropping caching to activate coverage of the Compiler code itself
1471+
Base.Compiler.activate!()
1472+
end

src/gf.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5087,6 +5087,65 @@ JL_DLLEXPORT void jl_extern_c(jl_value_t *name, jl_value_t *declrt, jl_tupletype
50875087
JL_GC_POP();
50885088
}
50895089

5090+
// Drop all method caches and increment world age as if adding a method that intersects everything
5091+
static void invalidate_method_instance_caches(jl_method_instance_t *mi, size_t world)
5092+
{
5093+
if ((jl_value_t*)mi == jl_nothing)
5094+
return;
5095+
5096+
// Walk through all code instances for this method instance
5097+
jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache);
5098+
while (ci != NULL) {
5099+
// Invalidate this code instance by setting max_world to current world
5100+
if (jl_atomic_load_relaxed(&ci->max_world) == ~(size_t)0) {
5101+
jl_atomic_store_release(&ci->max_world, world);
5102+
}
5103+
ci = jl_atomic_load_relaxed(&ci->next);
5104+
}
5105+
}
5106+
5107+
static int invalidate_all_specializations(jl_typemap_entry_t *def, void *closure)
5108+
{
5109+
size_t world = *(size_t*)closure;
5110+
jl_method_t *method = def->func.method;
5111+
JL_LOCK(&method->writelock);
5112+
jl_value_t *specializations = jl_atomic_load_relaxed(&method->specializations);
5113+
if (jl_is_svec(specializations)) {
5114+
size_t i, l = jl_svec_len(specializations);
5115+
for (i = 0; i < l; i++) {
5116+
jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i);
5117+
invalidate_method_instance_caches(mi, world);
5118+
}
5119+
}
5120+
else if (specializations != NULL) {
5121+
jl_method_instance_t *mi = (jl_method_instance_t*)specializations;
5122+
invalidate_method_instance_caches(mi, world);
5123+
}
5124+
JL_UNLOCK(&method->writelock);
5125+
return 1;
5126+
}
5127+
5128+
static int invalidate_all_caches_visitor(jl_methtable_t *mt, void *env)
5129+
{
5130+
return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), invalidate_all_specializations, env);
5131+
}
5132+
5133+
JL_DLLEXPORT void jl_drop_all_caches(void)
5134+
{
5135+
JL_LOCK(&world_counter_lock);
5136+
5137+
// Get current world age - we'll invalidate everything at this world
5138+
size_t current_world = jl_atomic_load_relaxed(&jl_world_counter);
5139+
5140+
invalidate_all_caches_visitor(jl_method_table, &current_world);
5141+
5142+
// Increment world age - this forces all subsequent compilation to happen in the new world
5143+
size_t new_world = current_world + 1;
5144+
jl_atomic_store_release(&jl_world_counter, new_world);
5145+
5146+
JL_UNLOCK(&world_counter_lock);
5147+
}
5148+
50905149

50915150
#ifdef __cplusplus
50925151
}

src/julia.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,6 +1980,7 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, siz
19801980
JL_DLLEXPORT jl_code_info_t *jl_copy_code_info(jl_code_info_t *src);
19811981
JL_DLLEXPORT size_t jl_get_world_counter(void) JL_NOTSAFEPOINT;
19821982
JL_DLLEXPORT size_t jl_get_tls_world_age(void) JL_NOTSAFEPOINT;
1983+
JL_DLLEXPORT void jl_drop_all_caches(void);
19831984
JL_DLLEXPORT jl_value_t *jl_box_bool(int8_t x) JL_NOTSAFEPOINT;
19841985
JL_DLLEXPORT jl_value_t *jl_box_int8(int8_t x) JL_NOTSAFEPOINT;
19851986
JL_DLLEXPORT jl_value_t *jl_box_uint8(uint8_t x) JL_NOTSAFEPOINT;

0 commit comments

Comments
 (0)