Skip to content

Commit bb25910

Browse files
authored
Add --trace-dispatch (JuliaLang#55848)
1 parent 2a2878c commit bb25910

14 files changed

+106
-8
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ variables. ([#53742]).
5959
* New `--trace-compile-timing` option to report how long each method reported by `--trace-compile` took
6060
to compile, in ms. ([#54662])
6161
* `--trace-compile` now prints recompiled methods in yellow or with a trailing comment if color is not supported ([#55763])
62+
* New `--trace-dispatch` option to report methods that are dynamically dispatched ([#55848]).
6263

6364
Multi-threading changes
6465
-----------------------

base/options.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ struct JLOptions
3434
can_inline::Int8
3535
polly::Int8
3636
trace_compile::Ptr{UInt8}
37+
trace_dispatch::Ptr{UInt8}
3738
fast_math::Int8
3839
worker::Int8
3940
cookie::Ptr{UInt8}

doc/man/julia.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ Methods that were recompiled are printed in yellow or with a trailing comment if
290290
--trace-compile-timing=
291291
If --trace-compile is enabled show how long each took to compile in ms
292292

293+
.TP
294+
--trace-dispatch={stderr|name}
295+
Print precompile statements for methods dispatched during execution or save to stderr or a path.
296+
293297
.TP
294298
-image-codegen
295299
Force generate code in imaging mode

doc/src/manual/command-line-interface.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ The following is a complete list of command-line switches available when launchi
216216
|`--output-incremental={yes\|no*}` |Generate an incremental output file (rather than complete)|
217217
|`--trace-compile={stderr\|name}` |Print precompile statements for methods compiled during execution or save to stderr or a path. Methods that were recompiled are printed in yellow or with a trailing comment if color is not supported|
218218
|`--trace-compile-timing` |If --trace-compile is enabled show how long each took to compile in ms|
219+
|`--trace-dispatch={stderr\|name}` |Print precompile statements for methods dispatched during execution or save to stderr or a path.|
219220
|`--image-codegen` |Force generate code in imaging mode|
220221
|`--permalloc-pkgimg={yes\|no*}` |Copy the data section of package images into memory|
221222
|`--trim={no*|safe|unsafe|unsafe-warn}` |Build a sysimage including only code provably reachable from methods marked by calling `entrypoint`. The three non-default options differ in how they handle dynamic call sites. In safe mode, such sites result in compile-time errors. In unsafe mode, such sites are allowed but the resulting binary might be missing needed code and can throw runtime errors. With unsafe-warn, such sites will trigger warnings at compile-time and might error at runtime.|

src/gf.c

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2560,6 +2560,38 @@ static void record_precompile_statement(jl_method_instance_t *mi, double compila
25602560
JL_UNLOCK(&precomp_statement_out_lock);
25612561
}
25622562

2563+
jl_mutex_t dispatch_statement_out_lock;
2564+
2565+
static void record_dispatch_statement(jl_method_instance_t *mi)
2566+
{
2567+
static ios_t f_dispatch;
2568+
static JL_STREAM* s_dispatch = NULL;
2569+
jl_method_t *def = mi->def.method;
2570+
if (!jl_is_method(def))
2571+
return;
2572+
2573+
JL_LOCK(&dispatch_statement_out_lock);
2574+
if (s_dispatch == NULL) {
2575+
const char *t = jl_options.trace_dispatch;
2576+
if (!strncmp(t, "stderr", 6)) {
2577+
s_dispatch = JL_STDERR;
2578+
}
2579+
else {
2580+
if (ios_file(&f_dispatch, t, 1, 1, 1, 1) == NULL)
2581+
jl_errorf("cannot open dispatch statement file \"%s\" for writing", t);
2582+
s_dispatch = (JL_STREAM*) &f_dispatch;
2583+
}
2584+
}
2585+
if (!jl_has_free_typevars(mi->specTypes)) {
2586+
jl_printf(s_dispatch, "precompile(");
2587+
jl_static_show(s_dispatch, mi->specTypes);
2588+
jl_printf(s_dispatch, ")\n");
2589+
if (s_dispatch != JL_STDERR)
2590+
ios_flush(&f_dispatch);
2591+
}
2592+
JL_UNLOCK(&dispatch_statement_out_lock);
2593+
}
2594+
25632595
// If waitcompile is 0, this will return NULL if compiling is on-going in the JIT. This is
25642596
// useful for the JIT itself, since it just doesn't cause redundant work or missed updates,
25652597
// but merely causes it to look into the current JIT worklist.
@@ -3067,7 +3099,8 @@ static void jl_compile_now(jl_method_instance_t *mi)
30673099
JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world)
30683100
{
30693101
size_t tworld = jl_typeinf_world;
3070-
jl_atomic_store_relaxed(&mi->precompiled, 1);
3102+
uint8_t miflags = jl_atomic_load_relaxed(&mi->flags) | JL_MI_FLAGS_MASK_PRECOMPILED;
3103+
jl_atomic_store_relaxed(&mi->flags, miflags);
30713104
if (jl_generating_output()) {
30723105
jl_compile_now(mi);
30733106
// In addition to full compilation of the compilation-signature, if `types` is more specific (e.g. due to nospecialize),
@@ -3082,7 +3115,8 @@ JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tuplet
30823115
types2 = jl_type_intersection_env((jl_value_t*)types, (jl_value_t*)mi->def.method->sig, &tpenv2);
30833116
jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2);
30843117
JL_GC_POP();
3085-
jl_atomic_store_relaxed(&mi2->precompiled, 1);
3118+
miflags = jl_atomic_load_relaxed(&mi2->flags) | JL_MI_FLAGS_MASK_PRECOMPILED;
3119+
jl_atomic_store_relaxed(&mi2->flags, miflags);
30863120
if (jl_rettype_inferred_native(mi2, world, world) == jl_nothing)
30873121
(void)jl_type_infer(mi2, world, SOURCE_MODE_NOT_REQUIRED);
30883122
if (jl_typeinf_func && jl_atomic_load_relaxed(&mi->def.method->primary_world) <= tworld) {
@@ -3358,6 +3392,16 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t
33583392
jl_method_error(F, args, nargs, world);
33593393
// unreachable
33603394
}
3395+
// mfunc is about to be dispatched
3396+
if (jl_options.trace_dispatch != NULL) {
3397+
uint8_t miflags = jl_atomic_load_relaxed(&mfunc->flags);
3398+
uint8_t was_dispatched = miflags & JL_MI_FLAGS_MASK_DISPATCHED;
3399+
if (!was_dispatched) {
3400+
miflags |= JL_MI_FLAGS_MASK_DISPATCHED;
3401+
jl_atomic_store_relaxed(&mfunc->flags, miflags);
3402+
record_dispatch_statement(mfunc);
3403+
}
3404+
}
33613405
}
33623406

33633407
#ifdef JL_TRACE
@@ -3480,6 +3524,15 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value
34803524
jl_gc_sync_total_bytes(last_alloc); // discard allocation count from compilation
34813525
}
34823526
JL_GC_PROMISE_ROOTED(mfunc);
3527+
if (jl_options.trace_dispatch != NULL) {
3528+
uint8_t miflags = jl_atomic_load_relaxed(&mfunc->flags);
3529+
uint8_t was_dispatched = miflags & JL_MI_FLAGS_MASK_DISPATCHED;
3530+
if (!was_dispatched) {
3531+
miflags |= JL_MI_FLAGS_MASK_DISPATCHED;
3532+
jl_atomic_store_relaxed(&mfunc->flags, miflags);
3533+
record_dispatch_statement(mfunc);
3534+
}
3535+
}
34833536
size_t world = jl_current_task->world_age;
34843537
return _jl_invoke(gf, args, nargs - 1, mfunc, world);
34853538
}

src/jloptions.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ JL_DLLEXPORT void jl_init_options(void)
7777
1, // can_inline
7878
JL_OPTIONS_POLLY_ON, // polly
7979
NULL, // trace_compile
80+
NULL, // trace_dispatch
8081
JL_OPTIONS_FAST_MATH_DEFAULT,
8182
0, // worker
8283
NULL, // cookie
@@ -294,6 +295,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
294295
opt_polly,
295296
opt_trace_compile,
296297
opt_trace_compile_timing,
298+
opt_trace_dispatch,
297299
opt_math_mode,
298300
opt_worker,
299301
opt_bind_to,
@@ -372,6 +374,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
372374
{ "polly", required_argument, 0, opt_polly },
373375
{ "trace-compile", required_argument, 0, opt_trace_compile },
374376
{ "trace-compile-timing", no_argument, 0, opt_trace_compile_timing },
377+
{ "trace-dispatch", required_argument, 0, opt_trace_dispatch },
375378
{ "math-mode", required_argument, 0, opt_math_mode },
376379
{ "handle-signals", required_argument, 0, opt_handle_signals },
377380
// hidden command line options
@@ -828,6 +831,11 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
828831
case opt_trace_compile_timing:
829832
jl_options.trace_compile_timing = 1;
830833
break;
834+
case opt_trace_dispatch:
835+
jl_options.trace_dispatch = strdup(optarg);
836+
if (!jl_options.trace_dispatch)
837+
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
838+
break;
831839
case opt_math_mode:
832840
if (!strcmp(optarg,"ieee"))
833841
jl_options.fast_math = JL_OPTIONS_FAST_MATH_OFF;

src/jloptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef struct {
3838
int8_t can_inline;
3939
int8_t polly;
4040
const char *trace_compile;
41+
const char *trace_dispatch;
4142
int8_t fast_math;
4243
int8_t worker;
4344
const char *cookie;

src/jltypes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3617,7 +3617,7 @@ void jl_init_types(void) JL_GC_DISABLED
36173617
"backedges",
36183618
"cache",
36193619
"cache_with_orig",
3620-
"precompiled"),
3620+
"flags"),
36213621
jl_svec(7,
36223622
jl_new_struct(jl_uniontype_type, jl_method_type, jl_module_type),
36233623
jl_any_type,

src/julia.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,14 @@ struct _jl_method_instance_t {
410410
jl_array_t *backedges; // list of method-instances which call this method-instance; `invoke` records (invokesig, caller) pairs
411411
_Atomic(struct _jl_code_instance_t*) cache;
412412
uint8_t cache_with_orig; // !cache_with_specTypes
413-
_Atomic(uint8_t) precompiled; // true if this instance was generated by an explicit `precompile(...)` call
413+
414+
// flags for this method instance
415+
// bit 0: generated by an explicit `precompile(...)`
416+
// bit 1: dispatched
417+
_Atomic(uint8_t) flags;
414418
};
419+
#define JL_MI_FLAGS_MASK_PRECOMPILED 0x01
420+
#define JL_MI_FLAGS_MASK_DISPATCHED 0x02
415421

416422
// OpaqueClosure
417423
typedef struct _jl_opaque_closure_t {

src/method.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void)
629629
mi->backedges = NULL;
630630
jl_atomic_store_relaxed(&mi->cache, NULL);
631631
mi->cache_with_orig = 0;
632-
jl_atomic_store_relaxed(&mi->precompiled, 0);
632+
jl_atomic_store_relaxed(&mi->flags, 0);
633633
return mi;
634634
}
635635

src/staticdata.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
17181718
else if (jl_is_method_instance(v)) {
17191719
assert(f == s->s);
17201720
jl_method_instance_t *newmi = (jl_method_instance_t*)&f->buf[reloc_offset];
1721-
jl_atomic_store_relaxed(&newmi->precompiled, 0);
1721+
jl_atomic_store_relaxed(&newmi->flags, 0);
17221722
}
17231723
else if (jl_is_code_instance(v)) {
17241724
assert(f == s->s);

src/staticdata_utils.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited,
159159
if (jl_is_method(mod))
160160
mod = ((jl_method_t*)mod)->module;
161161
assert(jl_is_module(mod));
162-
if (jl_atomic_load_relaxed(&mi->precompiled) || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) {
162+
uint8_t is_precompiled = jl_atomic_load_relaxed(&mi->flags) & JL_MI_FLAGS_MASK_PRECOMPILED;
163+
if (is_precompiled || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) {
163164
return 1;
164165
}
165166
if (!mi->backedges) {

test/cmdlineargs.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,17 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no`
787787
# tested in test/parallel.jl)
788788
@test errors_not_signals(`$exename --worker=true`)
789789

790+
# --trace-compile
791+
let
792+
io = IOBuffer()
793+
v = writereadpipeline(
794+
"foo(x) = begin Base.Experimental.@force_compile; x; end; foo(1)",
795+
`$exename --trace-compile=stderr -i`,
796+
stderr=io)
797+
_stderr = String(take!(io))
798+
@test occursin("precompile(Tuple{typeof(Main.foo), Int", _stderr)
799+
end
800+
790801
# --trace-compile-timing
791802
let
792803
io = IOBuffer()
@@ -798,6 +809,17 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no`
798809
@test occursin(" ms =# precompile(Tuple{typeof(Main.foo), Int", _stderr)
799810
end
800811

812+
# --trace-dispatch
813+
let
814+
io = IOBuffer()
815+
v = writereadpipeline(
816+
"foo(x) = begin Base.Experimental.@force_compile; x; end; foo(1)",
817+
`$exename --trace-dispatch=stderr -i`,
818+
stderr=io)
819+
_stderr = String(take!(io))
820+
@test occursin("precompile(Tuple{typeof(Main.foo), Int", _stderr)
821+
end
822+
801823
# test passing arguments
802824
mktempdir() do dir
803825
testfile, io = mktemp(dir)

test/core.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ for (T, c) in (
3434
(Core.CodeInfo, []),
3535
(Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :ipo_purity_bits, :invoke, :specptr, :specsigflags, :precompile]),
3636
(Core.Method, [:primary_world, :deleted_world]),
37-
(Core.MethodInstance, [:cache, :precompiled]),
37+
(Core.MethodInstance, [:cache, :flags]),
3838
(Core.MethodTable, [:defs, :leafcache, :cache, :max_args]),
3939
(Core.TypeMapEntry, [:next, :min_world, :max_world]),
4040
(Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]),

0 commit comments

Comments
 (0)