Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --trace-dispatch #55848

Merged
merged 9 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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|

Expand Down
54 changes: 52 additions & 2 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2561,6 +2561,40 @@ 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_options.trace_dispatch == NULL)
return;
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very awkward (and slightly unsound) to throw errors from here. We should try to move this into the jloptions file so we can detect any issues earlier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I essentially duplicated record_precompile_statement from right above this, which does the same thing.

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 @@ -3068,7 +3102,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 @@ -3083,7 +3118,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 @@ -3360,6 +3396,13 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t *F, jl_value_t **args, uint
jl_int32hash_fast(jl_return_address()),
world);
JL_GC_PROMISE_ROOTED(mfunc);
uint8_t miflags = jl_atomic_load_relaxed(&mfunc->flags);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure this has no overhead? Could it go in a slow path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, not sure. I think the cost should be negligible. Thoughts @vtjnash?

Could add an outer check for jloptions.trace_dispatch? Remove the check from inside the record... function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this atomic load off the fast path.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably better but I think it should go in a slower lookup path in jl_lookup_generic; at least only when the fast cache misses.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Going by the comments in jl_lookup_generic_, there is a lookup in an associative cache, and if that misses, a lookup in the full cache. Can a method instance be in either of those caches if it hasn't been dispatched? If not, then I could move the call here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved it here; please take a look?

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);
}
return _jl_invoke(F, args, nargs, mfunc, world);
}

Expand Down Expand Up @@ -3466,6 +3509,13 @@ 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);
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 @@ -286,6 +287,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 @@ -363,6 +365,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 @@ -818,6 +821,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 @@ -1678,7 +1678,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