Skip to content

Commit

Permalink
Add --trace-dispatch (#55848)
Browse files Browse the repository at this point in the history
  • Loading branch information
kpamnany authored Sep 30, 2024
1 parent 2a2878c commit bb25910
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 8 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ variables. ([#53742]).
* New `--trace-compile-timing` option to report how long each method reported by `--trace-compile` took
to compile, in ms. ([#54662])
* `--trace-compile` now prints recompiled methods in yellow or with a trailing comment if color is not supported ([#55763])
* New `--trace-dispatch` option to report methods that are dynamically dispatched ([#55848]).

Multi-threading changes
-----------------------
Expand Down
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct JLOptions
can_inline::Int8
polly::Int8
trace_compile::Ptr{UInt8}
trace_dispatch::Ptr{UInt8}
fast_math::Int8
worker::Int8
cookie::Ptr{UInt8}
Expand Down
4 changes: 4 additions & 0 deletions doc/man/julia.1
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ Methods that were recompiled are printed in yellow or with a trailing comment if
--trace-compile-timing=
If --trace-compile is enabled show how long each took to compile in ms

.TP
--trace-dispatch={stderr|name}
Print precompile statements for methods dispatched during execution or save to stderr or a path.

.TP
-image-codegen
Force generate code in imaging mode
Expand Down
1 change: 1 addition & 0 deletions doc/src/manual/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ The following is a complete list of command-line switches available when launchi
|`--output-incremental={yes\|no*}` |Generate an incremental output file (rather than complete)|
|`--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|
|`--trace-compile-timing` |If --trace-compile is enabled show how long each took to compile in ms|
|`--trace-dispatch={stderr\|name}` |Print precompile statements for methods dispatched during execution or save to stderr or a path.|
|`--image-codegen` |Force generate code in imaging mode|
|`--permalloc-pkgimg={yes\|no*}` |Copy the data section of package images into memory|
|`--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.|
Expand Down
57 changes: 55 additions & 2 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,38 @@ static void record_precompile_statement(jl_method_instance_t *mi, double compila
JL_UNLOCK(&precomp_statement_out_lock);
}

jl_mutex_t dispatch_statement_out_lock;

static void record_dispatch_statement(jl_method_instance_t *mi)
{
static ios_t f_dispatch;
static JL_STREAM* s_dispatch = NULL;
jl_method_t *def = mi->def.method;
if (!jl_is_method(def))
return;

JL_LOCK(&dispatch_statement_out_lock);
if (s_dispatch == NULL) {
const char *t = jl_options.trace_dispatch;
if (!strncmp(t, "stderr", 6)) {
s_dispatch = JL_STDERR;
}
else {
if (ios_file(&f_dispatch, t, 1, 1, 1, 1) == NULL)
jl_errorf("cannot open dispatch statement file \"%s\" for writing", t);
s_dispatch = (JL_STREAM*) &f_dispatch;
}
}
if (!jl_has_free_typevars(mi->specTypes)) {
jl_printf(s_dispatch, "precompile(");
jl_static_show(s_dispatch, mi->specTypes);
jl_printf(s_dispatch, ")\n");
if (s_dispatch != JL_STDERR)
ios_flush(&f_dispatch);
}
JL_UNLOCK(&dispatch_statement_out_lock);
}

// If waitcompile is 0, this will return NULL if compiling is on-going in the JIT. This is
// useful for the JIT itself, since it just doesn't cause redundant work or missed updates,
// but merely causes it to look into the current JIT worklist.
Expand Down Expand Up @@ -3067,7 +3099,8 @@ static void jl_compile_now(jl_method_instance_t *mi)
JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world)
{
size_t tworld = jl_typeinf_world;
jl_atomic_store_relaxed(&mi->precompiled, 1);
uint8_t miflags = jl_atomic_load_relaxed(&mi->flags) | JL_MI_FLAGS_MASK_PRECOMPILED;
jl_atomic_store_relaxed(&mi->flags, miflags);
if (jl_generating_output()) {
jl_compile_now(mi);
// In addition to full compilation of the compilation-signature, if `types` is more specific (e.g. due to nospecialize),
Expand All @@ -3082,7 +3115,8 @@ JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tuplet
types2 = jl_type_intersection_env((jl_value_t*)types, (jl_value_t*)mi->def.method->sig, &tpenv2);
jl_method_instance_t *mi2 = jl_specializations_get_linfo(mi->def.method, (jl_value_t*)types2, tpenv2);
JL_GC_POP();
jl_atomic_store_relaxed(&mi2->precompiled, 1);
miflags = jl_atomic_load_relaxed(&mi2->flags) | JL_MI_FLAGS_MASK_PRECOMPILED;
jl_atomic_store_relaxed(&mi2->flags, miflags);
if (jl_rettype_inferred_native(mi2, world, world) == jl_nothing)
(void)jl_type_infer(mi2, world, SOURCE_MODE_NOT_REQUIRED);
if (jl_typeinf_func && jl_atomic_load_relaxed(&mi->def.method->primary_world) <= tworld) {
Expand Down Expand Up @@ -3358,6 +3392,16 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t
jl_method_error(F, args, nargs, world);
// unreachable
}
// mfunc is about to be dispatched
if (jl_options.trace_dispatch != NULL) {
uint8_t miflags = jl_atomic_load_relaxed(&mfunc->flags);
uint8_t was_dispatched = miflags & JL_MI_FLAGS_MASK_DISPATCHED;
if (!was_dispatched) {
miflags |= JL_MI_FLAGS_MASK_DISPATCHED;
jl_atomic_store_relaxed(&mfunc->flags, miflags);
record_dispatch_statement(mfunc);
}
}
}

#ifdef JL_TRACE
Expand Down Expand Up @@ -3480,6 +3524,15 @@ jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, jl_value
jl_gc_sync_total_bytes(last_alloc); // discard allocation count from compilation
}
JL_GC_PROMISE_ROOTED(mfunc);
if (jl_options.trace_dispatch != NULL) {
uint8_t miflags = jl_atomic_load_relaxed(&mfunc->flags);
uint8_t was_dispatched = miflags & JL_MI_FLAGS_MASK_DISPATCHED;
if (!was_dispatched) {
miflags |= JL_MI_FLAGS_MASK_DISPATCHED;
jl_atomic_store_relaxed(&mfunc->flags, miflags);
record_dispatch_statement(mfunc);
}
}
size_t world = jl_current_task->world_age;
return _jl_invoke(gf, args, nargs - 1, mfunc, world);
}
Expand Down
8 changes: 8 additions & 0 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ JL_DLLEXPORT void jl_init_options(void)
1, // can_inline
JL_OPTIONS_POLLY_ON, // polly
NULL, // trace_compile
NULL, // trace_dispatch
JL_OPTIONS_FAST_MATH_DEFAULT,
0, // worker
NULL, // cookie
Expand Down Expand Up @@ -294,6 +295,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
opt_polly,
opt_trace_compile,
opt_trace_compile_timing,
opt_trace_dispatch,
opt_math_mode,
opt_worker,
opt_bind_to,
Expand Down Expand Up @@ -372,6 +374,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
{ "polly", required_argument, 0, opt_polly },
{ "trace-compile", required_argument, 0, opt_trace_compile },
{ "trace-compile-timing", no_argument, 0, opt_trace_compile_timing },
{ "trace-dispatch", required_argument, 0, opt_trace_dispatch },
{ "math-mode", required_argument, 0, opt_math_mode },
{ "handle-signals", required_argument, 0, opt_handle_signals },
// hidden command line options
Expand Down Expand Up @@ -828,6 +831,11 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
case opt_trace_compile_timing:
jl_options.trace_compile_timing = 1;
break;
case opt_trace_dispatch:
jl_options.trace_dispatch = strdup(optarg);
if (!jl_options.trace_dispatch)
jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno));
break;
case opt_math_mode:
if (!strcmp(optarg,"ieee"))
jl_options.fast_math = JL_OPTIONS_FAST_MATH_OFF;
Expand Down
1 change: 1 addition & 0 deletions src/jloptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ typedef struct {
int8_t can_inline;
int8_t polly;
const char *trace_compile;
const char *trace_dispatch;
int8_t fast_math;
int8_t worker;
const char *cookie;
Expand Down
2 changes: 1 addition & 1 deletion src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3617,7 +3617,7 @@ void jl_init_types(void) JL_GC_DISABLED
"backedges",
"cache",
"cache_with_orig",
"precompiled"),
"flags"),
jl_svec(7,
jl_new_struct(jl_uniontype_type, jl_method_type, jl_module_type),
jl_any_type,
Expand Down
8 changes: 7 additions & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,14 @@ struct _jl_method_instance_t {
jl_array_t *backedges; // list of method-instances which call this method-instance; `invoke` records (invokesig, caller) pairs
_Atomic(struct _jl_code_instance_t*) cache;
uint8_t cache_with_orig; // !cache_with_specTypes
_Atomic(uint8_t) precompiled; // true if this instance was generated by an explicit `precompile(...)` call

// flags for this method instance
// bit 0: generated by an explicit `precompile(...)`
// bit 1: dispatched
_Atomic(uint8_t) flags;
};
#define JL_MI_FLAGS_MASK_PRECOMPILED 0x01
#define JL_MI_FLAGS_MASK_DISPATCHED 0x02

// OpaqueClosure
typedef struct _jl_opaque_closure_t {
Expand Down
2 changes: 1 addition & 1 deletion src/method.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void)
mi->backedges = NULL;
jl_atomic_store_relaxed(&mi->cache, NULL);
mi->cache_with_orig = 0;
jl_atomic_store_relaxed(&mi->precompiled, 0);
jl_atomic_store_relaxed(&mi->flags, 0);
return mi;
}

Expand Down
2 changes: 1 addition & 1 deletion src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -1718,7 +1718,7 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
else if (jl_is_method_instance(v)) {
assert(f == s->s);
jl_method_instance_t *newmi = (jl_method_instance_t*)&f->buf[reloc_offset];
jl_atomic_store_relaxed(&newmi->precompiled, 0);
jl_atomic_store_relaxed(&newmi->flags, 0);
}
else if (jl_is_code_instance(v)) {
assert(f == s->s);
Expand Down
3 changes: 2 additions & 1 deletion src/staticdata_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ static int has_backedge_to_worklist(jl_method_instance_t *mi, htable_t *visited,
if (jl_is_method(mod))
mod = ((jl_method_t*)mod)->module;
assert(jl_is_module(mod));
if (jl_atomic_load_relaxed(&mi->precompiled) || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) {
uint8_t is_precompiled = jl_atomic_load_relaxed(&mi->flags) & JL_MI_FLAGS_MASK_PRECOMPILED;
if (is_precompiled || !jl_object_in_image((jl_value_t*)mod) || type_in_worklist(mi->specTypes)) {
return 1;
}
if (!mi->backedges) {
Expand Down
22 changes: 22 additions & 0 deletions test/cmdlineargs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,17 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no`
# tested in test/parallel.jl)
@test errors_not_signals(`$exename --worker=true`)

# --trace-compile
let
io = IOBuffer()
v = writereadpipeline(
"foo(x) = begin Base.Experimental.@force_compile; x; end; foo(1)",
`$exename --trace-compile=stderr -i`,
stderr=io)
_stderr = String(take!(io))
@test occursin("precompile(Tuple{typeof(Main.foo), Int", _stderr)
end

# --trace-compile-timing
let
io = IOBuffer()
Expand All @@ -798,6 +809,17 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no`
@test occursin(" ms =# precompile(Tuple{typeof(Main.foo), Int", _stderr)
end

# --trace-dispatch
let
io = IOBuffer()
v = writereadpipeline(
"foo(x) = begin Base.Experimental.@force_compile; x; end; foo(1)",
`$exename --trace-dispatch=stderr -i`,
stderr=io)
_stderr = String(take!(io))
@test occursin("precompile(Tuple{typeof(Main.foo), Int", _stderr)
end

# test passing arguments
mktempdir() do dir
testfile, io = mktemp(dir)
Expand Down
2 changes: 1 addition & 1 deletion test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ for (T, c) in (
(Core.CodeInfo, []),
(Core.CodeInstance, [:next, :min_world, :max_world, :inferred, :debuginfo, :ipo_purity_bits, :invoke, :specptr, :specsigflags, :precompile]),
(Core.Method, [:primary_world, :deleted_world]),
(Core.MethodInstance, [:cache, :precompiled]),
(Core.MethodInstance, [:cache, :flags]),
(Core.MethodTable, [:defs, :leafcache, :cache, :max_args]),
(Core.TypeMapEntry, [:next, :min_world, :max_world]),
(Core.TypeMapLevel, [:arg1, :targ, :name1, :tname, :list, :any]),
Expand Down

0 comments on commit bb25910

Please sign in to comment.