intel/ds: Add end_event_dyn() and CREATE_DUAL_EVENT_CALLBACK_DYN macro

Add a separate end_event_dyn() that takes a std::string by value for
dynamic event names. The [=] lambda capture deep-copies the string into
the closure, avoiding a dangling pointer when the Trace() continuation
runs after the caller's stack frame is gone.

The existing end_event() with const char* remains for string literals
and long-lived pointers (e.g. payload->str), where no copy is needed.

CREATE_DUAL_EVENT_CALLBACK_DYN formats the event name via snprintf and
passes the result as a std::string to end_event_dyn(). Follow-up patches
will use this macro to label events with runtime dimensions.

Signed-off-by: Michael Cheng <michael.cheng@intel.com>
Reviewed-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41374>
This commit is contained in:
Michael Cheng 2026-05-08 01:07:34 -07:00 committed by Marge Bot
parent 41a3213dca
commit ecbc6625cf

View file

@ -23,6 +23,7 @@
#include <stdio.h>
#include <stdarg.h>
#include <string>
#include "common/intel_gem.h"
#include "perf/intel_perf.h"
@ -307,6 +308,74 @@ end_event(struct intel_ds_queue *queue, uint64_t ts_ns,
stage->start_ns[level] = 0;
}
/* Variant for dynamic event names (for example, names formatted in
* stack-local buffers).
*
* Takes a std::string by value so the [=] lambda capture copies string storage
* into the Trace() closure. This avoids dangling pointers when Trace() runs
* after the caller's stack frame is gone.
*
* Keep end_event() with const char* for NULL, string literals, and other
* long-lived pointers to avoid std::string construction on the common path.
*/
static void
end_event_dyn(struct intel_ds_queue *queue, uint64_t ts_ns,
enum intel_ds_queue_stage stage_id,
uint32_t submission_id,
uint16_t tracepoint_idx,
std::string app_event,
const void *payload = nullptr,
const void *indirect_data = nullptr,
trace_payload_as_extra_func payload_as_extra = nullptr)
{
struct intel_ds_device *device = queue->device;
if (queue->stages[stage_id].level == 0)
return;
uint32_t level = --queue->stages[stage_id].level;
struct intel_ds_stage *stage = &queue->stages[stage_id];
uint64_t start_ns = stage->start_ns[level];
if (!start_ns)
return;
IntelRenderpassDataSource::Trace([=](IntelRenderpassDataSource::TraceContext tctx) {
setup_incremental_state(tctx, device);
sync_timestamp(tctx, device);
uint64_t evt_id = device->event_id++;
uint64_t stage_iid = !app_event.empty() ?
tctx.GetDataSourceLocked()->debug_marker_stage(tctx, app_event.c_str()) :
device->tracepoint_iids[tracepoint_idx];
auto packet = tctx.NewTracePacket();
packet->set_timestamp(start_ns);
packet->set_timestamp_clock_id(device->gpu_clock_id);
assert(ts_ns >= start_ns);
auto event = packet->set_gpu_render_stage_event();
event->set_gpu_id(device->gpu_id);
event->set_hw_queue_iid(stage->queue_iid);
event->set_stage_iid(stage_iid);
event->set_context(device->iid);
event->set_event_id(evt_id);
event->set_duration(ts_ns - start_ns);
event->set_submission_id(submission_id);
if ((payload || indirect_data) && payload_as_extra) {
payload_as_extra(event, payload, indirect_data);
}
});
stage->start_ns[level] = 0;
}
static size_t
snprintf_stages(char *buf, size_t buf_size,
enum intel_ds_barrier_type type,
@ -462,6 +531,51 @@ extern "C" {
&trace_payload_as_extra_intel_end_##event_name); \
} \
/*
* Like CREATE_DUAL_EVENT_CALLBACK, but for dynamic event names.
*
* The name is formatted with snprintf() into a stack-local buffer, copied into
* a std::string, and passed by value to end_event_dyn(). That keeps the name
* alive in the Trace() lambda closure and avoids dangling stack pointers.
*/
#define CREATE_DUAL_EVENT_CALLBACK_DYN(event_name, stage, name_fmt, ...) \
void \
intel_ds_begin_##event_name(struct intel_ds_device *device, \
uint64_t ts_ns, \
uint16_t tp_idx, \
const void *flush_data, \
const struct trace_intel_begin_##event_name *payload, \
const void *indirect_data) \
{ \
const struct intel_ds_flush_data *flush = \
(const struct intel_ds_flush_data *) flush_data; \
begin_event(flush->queue, ts_ns, stage); \
} \
\
void \
intel_ds_end_##event_name(struct intel_ds_device *device, \
uint64_t ts_ns, \
uint16_t tp_idx, \
const void *flush_data, \
const struct trace_intel_end_##event_name *payload, \
const void *indirect_data) \
{ \
const struct intel_ds_flush_data *flush = \
(const struct intel_ds_flush_data *) flush_data; \
UNUSED const uint32_t *indirect = \
(const uint32_t *) indirect_data; \
char buf[64]; \
std::string name; \
if ((name_fmt) != NULL) { \
snprintf(buf, sizeof(buf), (name_fmt), ##__VA_ARGS__); \
name = buf; \
} \
end_event_dyn(flush->queue, ts_ns, stage, flush->submission_id, \
tp_idx, std::move(name), payload, indirect_data, \
(trace_payload_as_extra_func) \
&trace_payload_as_extra_intel_end_##event_name); \
} \
CREATE_DUAL_EVENT_CALLBACK(frame, INTEL_DS_QUEUE_STAGE_FRAME)
CREATE_DUAL_EVENT_CALLBACK(batch, INTEL_DS_QUEUE_STAGE_CMD_BUFFER)
CREATE_DUAL_EVENT_CALLBACK(cmd_buffer, INTEL_DS_QUEUE_STAGE_CMD_BUFFER)