Skip to content

Commit 39d1346

Browse files
art049claude
andcommitted
feat(tracegrind): add ENTER_INLINED/EXIT_INLINED events for inline function tracking
Track inlined function transitions at the BB level using Valgrind's debug info API. This bumps the trace format to v3 with two new event types (4=ENTER_INLINED, 5=EXIT_INLINED), updates the analyzer script to handle them, and adds regression tests for enter and nested inlined scenarios. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c699fd1 commit 39d1346

25 files changed

Lines changed: 364 additions & 53 deletions

tracegrind/bb.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ static BB* new_bb(obj_node* obj, PtrdiffT offset,
143143
bb->fn = 0;
144144
bb->line = 0;
145145
bb->is_entry = 0;
146+
bb->inl_fn = NULL;
146147
bb->bbcc_list = 0;
147148
bb->last_bbcc = 0;
148149

tracegrind/bbcc.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,21 @@ void TG_(setup_bbcc)(BB* bb)
830830
}
831831

832832
TG_(current_state).bbcc = bbcc;
833+
834+
/* Check for inline function transition */
835+
if (TG_(current_state).collect) {
836+
thread_info* ti = TG_(get_current_thread)();
837+
if (ti && bb->inl_fn != ti->cur_inl_fn) {
838+
if (ti->cur_inl_fn != NULL) {
839+
TG_(trace_emit_exit_inlined)(TG_(current_tid), bb, ti->cur_inl_fn);
840+
}
841+
if (bb->inl_fn != NULL) {
842+
TG_(trace_emit_enter_inlined)(TG_(current_tid), bb);
843+
}
844+
ti->cur_inl_fn = bb->inl_fn;
845+
}
846+
}
847+
833848
/* Even though this will be set in instrumented code directly before
834849
* side exits, it needs to be set to 0 here in case an exception
835850
* happens in first instructions of the BB */

tracegrind/callstack.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,14 @@ void TG_(push_call_stack)(BBCC* from, UInt jmp, BBCC* to, Addr sp, Bool skip)
234234

235235
/* Emit trace sample on function entry */
236236
if (!skip && TG_(current_state).collect) {
237+
/* Emit EXIT_INLINED if we're entering a new function while inside inlined code */
238+
thread_info* ti = TG_(get_current_thread)();
239+
if (ti && ti->cur_inl_fn != NULL && TG_(current_state).bbcc) {
240+
TG_(trace_emit_exit_inlined)(TG_(current_tid),
241+
TG_(current_state).bbcc->bb,
242+
ti->cur_inl_fn);
243+
ti->cur_inl_fn = NULL;
244+
}
237245
fn_node* to_fn = to->cxt->fn[0];
238246
TG_(trace_emit_sample)(TG_(current_tid), True, to_fn);
239247
}
@@ -335,6 +343,14 @@ void TG_(pop_call_stack)(void)
335343

336344
/* Emit trace sample on function exit */
337345
if (TG_(current_state).collect) {
346+
/* Emit EXIT_INLINED if we're leaving while inside inlined code */
347+
thread_info* ti = TG_(get_current_thread)();
348+
if (ti && ti->cur_inl_fn != NULL && TG_(current_state).bbcc) {
349+
TG_(trace_emit_exit_inlined)(TG_(current_tid),
350+
TG_(current_state).bbcc->bb,
351+
ti->cur_inl_fn);
352+
ti->cur_inl_fn = NULL;
353+
}
338354
TG_(trace_emit_sample)(TG_(current_tid), False, to_fn);
339355
}
340356

tracegrind/dump.c

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ static void msgpack_write_header(void)
116116

117117
/* version */
118118
msgpack_write_key(&hdr, "version");
119-
msgpack_write_uint(&hdr, 2);
119+
msgpack_write_uint(&hdr, 3);
120120

121121
/* format */
122122
msgpack_write_key(&hdr, "format");
123123
msgpack_write_str(&hdr, "tracegrind-msgpack", -1);
124124

125125
/* event_schemas - discriminated union: each event type has its own schema */
126126
msgpack_write_key(&hdr, "event_schemas");
127-
msgpack_write_map_header(&hdr, 4); /* 4 event types: MARKER, ENTER, EXIT, FORK */
127+
msgpack_write_map_header(&hdr, 6); /* 6 event types: MARKER, ENTER, EXIT, FORK, ENTER_INLINED, EXIT_INLINED */
128128

129129
/* Event type 0 (MARKER) schema */
130130
msgpack_write_key(&hdr, "0");
@@ -156,6 +156,20 @@ static void msgpack_write_header(void)
156156
msgpack_write_str(&hdr, "event", -1);
157157
msgpack_write_str(&hdr, "child_pid", -1);
158158

159+
/* Event type 4 (ENTER_INLINED) schema - same columns as ENTER/EXIT */
160+
msgpack_write_key(&hdr, "4");
161+
msgpack_write_array_header(&hdr, mp_state.ncols);
162+
for (Int i = 0; i < mp_state.ncols; i++) {
163+
msgpack_write_str(&hdr, mp_state.col_names[i], -1);
164+
}
165+
166+
/* Event type 5 (EXIT_INLINED) schema - same columns as ENTER/EXIT */
167+
msgpack_write_key(&hdr, "5");
168+
msgpack_write_array_header(&hdr, mp_state.ncols);
169+
for (Int i = 0; i < mp_state.ncols; i++) {
170+
msgpack_write_str(&hdr, mp_state.col_names[i], -1);
171+
}
172+
159173
/* Compress and write header chunk */
160174
SizeT src_size = hdr.size;
161175
SizeT dst_capacity = tg_lz4_compress_bound(src_size);
@@ -164,8 +178,8 @@ static void msgpack_write_header(void)
164178
SizeT compressed_size = tg_lz4_compress(
165179
compressed, dst_capacity, hdr.data, src_size);
166180

167-
/* Magic + version (8 bytes): "TGMP" + version(4) - version 2 */
168-
UChar magic[8] = {'T', 'G', 'M', 'P', 0x02, 0x00, 0x00, 0x00};
181+
/* Magic + version (8 bytes): "TGMP" + version(4) - version 3 */
182+
UChar magic[8] = {'T', 'G', 'M', 'P', 0x03, 0x00, 0x00, 0x00};
169183
VG_(write)(TG_(trace_out).fd, magic, 8);
170184

171185
/* Header chunk size (4 bytes uncompressed, 4 bytes compressed) */
@@ -439,6 +453,92 @@ void TG_(trace_emit_sample)(ThreadId tid, Bool is_enter,
439453
deltas, es->size);
440454
}
441455

456+
void TG_(trace_emit_enter_inlined)(ThreadId tid, BB* bb)
457+
{
458+
Int i;
459+
460+
if (!TG_(trace_out).initialized) return;
461+
if (TG_(trace_out).fd < 0) return;
462+
463+
thread_info* ti = TG_(get_current_thread)();
464+
if (!ti) return;
465+
466+
EventSet* es = TG_(sets).full;
467+
FullCost current_cost = TG_(current_state).cost;
468+
469+
if (!ti->last_sample_cost) {
470+
ti->last_sample_cost = TG_(get_eventset_cost)(es);
471+
TG_(init_cost)(es, ti->last_sample_cost);
472+
}
473+
474+
TG_(trace_out).seq++;
475+
476+
const HChar* fn_name = bb->inl_fn;
477+
const HChar* obj_name = bb->obj ? bb->obj->name : "???";
478+
const HChar* file_name = (bb->fn && bb->fn->file) ? bb->fn->file->name : "???";
479+
UInt line = bb->line;
480+
481+
ULong deltas[64];
482+
tl_assert(es->size <= 64);
483+
if (current_cost && ti->last_sample_cost) {
484+
for (i = 0; i < es->size; i++) {
485+
deltas[i] = current_cost[i] - ti->last_sample_cost[i];
486+
}
487+
TG_(copy_cost)(es, ti->last_sample_cost, current_cost);
488+
} else {
489+
for (i = 0; i < es->size; i++) {
490+
deltas[i] = 0;
491+
}
492+
}
493+
494+
msgpack_add_row(TG_(trace_out).seq, (Int)tid, TG_EV_ENTER_INLINED,
495+
fn_name, obj_name, file_name, (Int)line,
496+
deltas, es->size);
497+
}
498+
499+
void TG_(trace_emit_exit_inlined)(ThreadId tid, BB* bb, const HChar* inl_fn)
500+
{
501+
Int i;
502+
503+
if (!TG_(trace_out).initialized) return;
504+
if (TG_(trace_out).fd < 0) return;
505+
506+
thread_info* ti = TG_(get_current_thread)();
507+
if (!ti) return;
508+
509+
EventSet* es = TG_(sets).full;
510+
FullCost current_cost = TG_(current_state).cost;
511+
512+
if (!ti->last_sample_cost) {
513+
ti->last_sample_cost = TG_(get_eventset_cost)(es);
514+
TG_(init_cost)(es, ti->last_sample_cost);
515+
}
516+
517+
TG_(trace_out).seq++;
518+
519+
const HChar* fn_name = inl_fn;
520+
const HChar* obj_name = bb->obj ? bb->obj->name : "???";
521+
const HChar* file_name = (bb->fn && bb->fn->file) ? bb->fn->file->name : "???";
522+
UInt line = bb->line;
523+
524+
ULong deltas[64];
525+
tl_assert(es->size <= 64);
526+
if (current_cost && ti->last_sample_cost) {
527+
for (i = 0; i < es->size; i++) {
528+
deltas[i] = current_cost[i] - ti->last_sample_cost[i];
529+
}
530+
TG_(copy_cost)(es, ti->last_sample_cost, current_cost);
531+
} else {
532+
for (i = 0; i < es->size; i++) {
533+
deltas[i] = 0;
534+
}
535+
}
536+
537+
msgpack_add_row(TG_(trace_out).seq, (Int)tid, TG_EV_EXIT_INLINED,
538+
fn_name, obj_name, file_name, (Int)line,
539+
deltas, es->size);
540+
}
541+
442542
/*
443543
* Emit a FORK event when a child process is created.
444544
* Called from the post-syscall handler when fork/clone returns in parent.

tracegrind/fn.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,14 @@ fn_node* TG_(get_fn_node)(BB* bb)
593593
&dirname, &filename, &fnname, &line_num, &di);
594594

595595
DiEpoch ep = VG_(current_DiEpoch)();
596+
597+
/* Check if BB start address is in inlined code */
598+
{
599+
const HChar* inl_fn_name = NULL;
600+
VG_(get_inline_fnname)(ep, bb_addr(bb), &inl_fn_name);
601+
bb->inl_fn = inl_fn_name; /* NULL if not inlined */
602+
}
603+
596604
if (0 == VG_(strcmp)(fnname, "???")) {
597605
int p;
598606
static HChar buf[32]; // for sure large enough

tracegrind/global.h

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@ typedef enum {
8181

8282
/* Trace event types */
8383
typedef enum {
84-
TG_EV_MARKER = 0,
85-
TG_EV_ENTER = 1,
86-
TG_EV_EXIT = 2,
87-
TG_EV_FORK = 3
84+
TG_EV_MARKER = 0,
85+
TG_EV_ENTER = 1,
86+
TG_EV_EXIT = 2,
87+
TG_EV_FORK = 3,
88+
TG_EV_ENTER_INLINED = 4,
89+
TG_EV_EXIT_INLINED = 5
8890
} TraceEventType;
8991

9092
typedef struct _CommandLineOptions CommandLineOptions;
@@ -142,7 +144,7 @@ struct _Statistics {
142144
ULong bb_executions;
143145

144146
Int context_counter;
145-
Int bb_retranslations;
147+
Int bb_retranslations;
146148

147149
Int distinct_objs;
148150
Int distinct_files;
@@ -238,7 +240,7 @@ struct _jCC {
238240
};
239241

240242

241-
/*
243+
/*
242244
* Info for one instruction of a basic block.
243245
*/
244246
typedef struct _InstrInfo InstrInfo;
@@ -284,12 +286,12 @@ struct _BB {
284286

285287
VgSectKind sect_kind; /* section of this BB, e.g. PLT */
286288
UInt instr_count;
287-
289+
288290
/* filled by TG_(get_fn_node) if debug info is available */
289291
fn_node* fn; /* debug info for this BB */
290292
UInt line;
291293
Bool is_entry; /* True if this BB is a function entry */
292-
294+
293295
BBCC* bbcc_list; /* BBCCs for same BB (see next_bbcc in BBCC) */
294296
BBCC* last_bbcc; /* Temporary: Cached for faster access (LRU) */
295297

@@ -299,6 +301,8 @@ struct _BB {
299301
* allocated directly after this struct */
300302
Bool cjmp_inverted; /* is last side exit actually fall through? */
301303

304+
const HChar* inl_fn; /* inlined function name at BB start, or NULL */
305+
302306
UInt instr_len;
303307
UInt cost_count;
304308
InstrInfo instr[0]; /* info on instruction sizes and costs */
@@ -365,12 +369,12 @@ struct _BBCC {
365369
* recursion BBCCs. Shared. */
366370
BBCC* next_bbcc; /* Chain of BBCCs for same BB */
367371
BBCC* lru_next_bbcc; /* BBCC executed next the last time */
368-
372+
369373
jCC* lru_from_jcc; /* Temporary: Cached for faster access (LRU) */
370374
jCC* lru_to_jcc; /* Temporary: Cached for faster access (LRU) */
371-
FullCost skipped; /* cost for skipped functions called from
375+
FullCost skipped; /* cost for skipped functions called from
372376
* jmp_addr. Allocated lazy */
373-
377+
374378
BBCC* next; /* entry chain in hash */
375379
ULong* cost; /* start of 64bit costs for this BBCC */
376380
ULong ecounter_sum; /* execution counter for first instruction of BB */
@@ -432,7 +436,7 @@ struct _obj_node {
432436
*
433437
* <nonskipped> is 0 if the function called is not skipped (usual case).
434438
* Otherwise, it is the last non-skipped BBCC. This one gets all
435-
* the calls to non-skipped functions and all costs in skipped
439+
* the calls to non-skipped functions and all costs in skipped
436440
* instructions.
437441
*/
438442
struct _call_entry {
@@ -464,14 +468,14 @@ struct _exec_state {
464468
/* the signum of the handler, 0 for main thread context
465469
*/
466470
Int sig;
467-
471+
468472
/* the old call stack pointer at entering the signal handler */
469473
Int orig_sp;
470-
474+
471475
FullCost cost;
472476
Bool collect;
473477
Context* cxt;
474-
478+
475479
/* number of conditional jumps passed in last BB */
476480
Int jmps_passed;
477481
BBCC* bbcc; /* last BB executed */
@@ -491,7 +495,7 @@ typedef struct _cxt_hash cxt_hash;
491495
struct _cxt_hash {
492496
UInt size, entries;
493497
Context** table;
494-
};
498+
};
495499

496500
/* Thread specific state structures, i.e. parts of a thread state.
497501
* There are variables for the current state of each part,
@@ -540,7 +544,7 @@ struct _exec_stack {
540544
exec_state* entry[MAX_SIGHANDLERS];
541545
};
542546

543-
/* Thread State
547+
/* Thread State
544548
*
545549
* This structure stores thread specific info while a thread is *not*
546550
* running. See function switch_thread() for save/restore on thread switch.
@@ -562,6 +566,9 @@ struct _thread_info {
562566
/* CSV trace: per-thread snapshot of cost at last sample emission */
563567
FullCost last_sample_cost;
564568

569+
/* Inline tracking: current inlined function name (NULL if not in inlined code) */
570+
const HChar* cur_inl_fn;
571+
565572
/* thread specific data structure containers */
566573
fn_array fn_active;
567574
jcc_hash jccs;
@@ -580,7 +587,7 @@ struct cachesim_if
580587
void (*clear)(void);
581588
void (*printstat)(Int,Int,Int);
582589
void (*finish)(void);
583-
590+
584591
void (*log_1I0D)(InstrInfo*) VG_REGPARM(1);
585592
void (*log_2I0D)(InstrInfo*, InstrInfo*) VG_REGPARM(2);
586593
void (*log_3I0D)(InstrInfo*, InstrInfo*, InstrInfo*) VG_REGPARM(3);
@@ -731,6 +738,8 @@ void TG_(run_post_signal_on_call_stack_bottom)(void);
731738
void TG_(trace_open_output)(void);
732739
void TG_(trace_reopen_child)(void);
733740
void TG_(trace_emit_sample)(ThreadId tid, Bool is_enter, fn_node* fn);
741+
void TG_(trace_emit_enter_inlined)(ThreadId tid, BB* bb);
742+
void TG_(trace_emit_exit_inlined)(ThreadId tid, BB* bb, const HChar* inl_fn);
734743
void TG_(trace_emit_fork)(ThreadId tid, Int child_pid);
735744
void TG_(trace_emit_marker)(ThreadId tid, const HChar* marker);
736745
void TG_(trace_close_output)(void);

0 commit comments

Comments
 (0)