-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
In HalideRuntime.h, we document the meaning of a trace event's parent as follows:
Halide/src/runtime/HalideRuntime.h
Lines 643 to 659 in 5423753
| * halide_trace returns a unique ID which will be passed to future | |
| * events that "belong" to the earlier event as the parent id. The | |
| * ownership hierarchy looks like: | |
| * | |
| * begin_pipeline | |
| * +--trace_tag (if any) | |
| * +--trace_tag (if any) | |
| * ... | |
| * +--begin_realization | |
| * | +--produce | |
| * | | +--load/store | |
| * | | +--end_produce | |
| * | +--consume | |
| * | | +--load | |
| * | | +--end_consume | |
| * | +--end_realization | |
| * +--end_pipeline |
From this, we see that loads may be owned by either produce events or consume events, and that these are further tied to a specific realization. Every trace packet also contains a reference to a func name1, as shown here:
Halide/src/runtime/HalideRuntime.h
Lines 720 to 728 in 5423753
| /** Get the func name, assuming this packet is laid out in memory | |
| * as it was written. It comes after the value. */ | |
| HALIDE_ALWAYS_INLINE const char *func() const { | |
| return (const char *)value() + type.lanes * type.bytes(); | |
| } | |
| HALIDE_ALWAYS_INLINE char *func() { | |
| return (char *)value() + type.lanes * type.bytes(); | |
| } |
Clearly, for a load packet, this is the name of the func loaded from.
So one is left to assume that the parent PRODUCE node of a load would be tied to the func whose realization is doing the loading, right? Unfortunately, no. The lowering in Tracing.cpp uses the load target's name both for writing out the name in the packet directly, as seen here:
Lines 141 to 149 in 5423753
| TraceEventBuilder builder; | |
| builder.func = op->name; | |
| builder.value = {value_var}; | |
| builder.coordinates = op->args; | |
| builder.type = op->type; | |
| builder.event = halide_trace_load; | |
| builder.parent_id = trace_parent; | |
| builder.value_index = op->value_index; | |
| Expr trace = builder.build(); |
and also for determining the parent ID, which always points to a CONSUME node, as seen here:
Lines 112 to 123 in 5423753
| if (op->call_type == Call::Halide) { | |
| auto it = env.find(op->name); | |
| internal_assert(it != env.end()) << op->name << " not in environment\n"; | |
| Function f = it->second; | |
| internal_assert(!f.can_be_inlined() || !f.schedule().compute_level().is_inlined()); | |
| trace_it = trace_all_loads || f.is_tracing_loads(); | |
| trace_parent = Variable::make(Int(32), op->name + ".trace_id"); | |
| if (trace_it) { | |
| add_trace_tags(op->name, f.get_trace_tags()); | |
| touch(funcs_touched, op->name, op->value_index, op->type); | |
| } |
Even more vexingly, loads to ImageParams have the pipeline itself as the parent, which is not even permitted under the documentation.
Lines 124 to 135 in 5423753
| } else if (op->call_type == Call::Image) { | |
| // op->param is defined when we're loading from an ImageParam, and undefined | |
| // when we're loading from an inlined Buffer. | |
| trace_it = trace_all_loads || (op->param.defined() && op->param.is_tracing_loads()); | |
| trace_parent = Variable::make(Int(32), "pipeline.trace_id"); | |
| if (trace_it) { | |
| if (op->param.defined()) { | |
| add_trace_tags(op->name, op->param.get_trace_tags()); | |
| } | |
| touch(images_touched, op->name, op->value_index, op->type); | |
| } | |
| } |
In all cases, op refers to a Call node in the IR.
Footnotes
-
Because a func might be realized multiple times in parallel, I believe the name alone is insufficient to determine a true load target, unless there is some additional invariant we can use to disambiguate. ↩