Skip to content

Commit 19ae1ee

Browse files
jbachorikclaude
andcommitted
feat(walkvm): wextend=vt_carrier opt-in for carrier frame walking
- Add wextend argument (string-parsed, comma-separated tokens) - vt_carrier token enables walking through VT continuation boundaries to carrier thread frames; default stops at synthetic "JVM Continuation" root frame (no truncation, no carrier noise) - Add carrier_frames bit to StackWalkFeatures (replaces _padding bit) - Use FRAME_PC_SLOT for architecture-portable carrier frame extraction - Split VMContinuationEntry into DECLARE_V21_TYPES_DO to prevent assert(_VMContinuationEntry_size > 0) on JDK <21 debug builds - Expand DECLARE_V21_TYPES_DO at all four declaration/init/read/verify sites in vmStructs.h and vmStructs.cpp - Enable wextend=vt_carrier in VirtualThreadWallClockTest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7759c15 commit 19ae1ee

6 files changed

Lines changed: 44 additions & 18 deletions

File tree

ddprof-lib/src/main/cpp/arguments.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,19 @@ Error Arguments::parse(const char *args) {
300300
}
301301
}
302302

303+
CASE("wextend")
304+
if (value != NULL) {
305+
const char* p = value;
306+
while (*p != '\0') {
307+
const char* comma = strchr(p, ',');
308+
size_t len = comma != NULL ? (size_t)(comma - p) : strlen(p);
309+
if (len == 10 && strncmp(p, "vt_carrier", 10) == 0) {
310+
_features.carrier_frames = 1;
311+
}
312+
p += len + (comma != NULL ? 1 : 0);
313+
}
314+
}
315+
303316
CASE("attributes")
304317
if (value != NULL) {
305318
std::string input(value);

ddprof-lib/src/main/cpp/arguments.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ struct StackWalkFeatures {
122122
unsigned short vtable_target : 1; // show receiver classes of vtable/itable stubs
123123
unsigned short comp_task : 1; // display current compilation task for JIT threads
124124
unsigned short pc_addr : 1; // record exact PC address for each sample
125-
unsigned short _padding : 3; // pad structure to 16 bits
125+
unsigned short carrier_frames: 1; // walk through VT continuation boundary to carrier frames (wextend=vt_carrier)
126+
unsigned short _padding : 2; // pad structure to 16 bits
126127
};
127128

128129
struct Multiplier {

ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const void* VMStructs::_interpreted_frame_valid_end = nullptr;
4747
// Initialize type size to 0
4848
#define INIT_TYPE_SIZE(name, names) uint64_t VMStructs::TYPE_SIZE_NAME(name) = 0;
4949
DECLARE_TYPES_DO(INIT_TYPE_SIZE)
50+
DECLARE_V21_TYPES_DO(INIT_TYPE_SIZE)
5051
#undef INIT_TYPE_SIZE
5152

5253
#define offset_value -1
@@ -208,6 +209,7 @@ void VMStructs::init_type_sizes() {
208209
}
209210

210211
DECLARE_TYPES_DO(READ_TYPE_SIZE)
212+
DECLARE_V21_TYPES_DO(READ_TYPE_SIZE)
211213

212214
#undef READ_TYPE_SIZE
213215

@@ -276,6 +278,9 @@ void VMStructs::verify_offsets() {
276278
// Verify type sizes
277279
#define VERIFY_TYPE_SIZE(name, names) assert(TYPE_SIZE_NAME(name) > 0);
278280
DECLARE_TYPES_DO(VERIFY_TYPE_SIZE);
281+
if (hotspot_version >= 21) {
282+
DECLARE_V21_TYPES_DO(VERIFY_TYPE_SIZE);
283+
}
279284
#undef VERIFY_TYPE_SIZE
280285

281286

ddprof-lib/src/main/cpp/hotspot/vmStructs.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ inline T* cast_to(const void* ptr) {
115115
f(VMClassLoaderData, MATCH_SYMBOLS("ClassLoaderData")) \
116116
f(VMConstantPool, MATCH_SYMBOLS("ConstantPool")) \
117117
f(VMConstMethod, MATCH_SYMBOLS("ConstMethod")) \
118-
f(VMContinuationEntry, MATCH_SYMBOLS("ContinuationEntry")) \
119118
f(VMFlag, MATCH_SYMBOLS("JVMFlag", "Flag")) \
120119
f(VMJavaFrameAnchor, MATCH_SYMBOLS("JavaFrameAnchor")) \
121120
f(VMKlass, MATCH_SYMBOLS("Klass")) \
@@ -124,6 +123,10 @@ inline T* cast_to(const void* ptr) {
124123
f(VMSymbol, MATCH_SYMBOLS("Symbol")) \
125124
f(VMThread, MATCH_SYMBOLS("Thread"))
126125

126+
// Types only present in JDK 21+ (Project Loom); size is 0 on older JDKs
127+
#define DECLARE_V21_TYPES_DO(f) \
128+
f(VMContinuationEntry, MATCH_SYMBOLS("ContinuationEntry"))
129+
127130
/**
128131
* Following macros define field offsets, addresses or values of JVM classes that are exported by
129132
* vmStructs.
@@ -330,6 +333,7 @@ class VMStructs {
330333
static uint64_t TYPE_SIZE_NAME(name);
331334

332335
DECLARE_TYPES_DO(DECLARE_TYPE_SIZE_VAR)
336+
DECLARE_V21_TYPES_DO(DECLARE_TYPE_SIZE_VAR)
333337
#undef DECLARE_TYPE_SIZE_VAR
334338

335339
// Declare vmStructs' field offsets and addresses

ddprof-lib/src/main/cpp/stackWalker.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -353,40 +353,43 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
353353
anchor = vm_thread->anchor();
354354
}
355355

356-
// Advances through a continuation boundary to the carrier frame.
357-
// Walks cont_entry->parent() on repeated calls to handle nested continuations
358-
// (_parent is maintained by the JVM for its own bookkeeping; not triggered by
359-
// standard single-level VTs today, but required once any runtime layers
360-
// continuations on top of VTs). Returns true to continue the walk, false to break.
361-
// Synthetic root frame inserted when a continuation boundary is detected but
362-
// carrier frames cannot be captured (transient JVM state or invalid pointers).
363-
// Using BCI_NATIVE_FRAME rather than BCI_ERROR avoids marking the sample as
364-
// truncated — the virtual thread's own frames are complete; carrier internals
365-
// (ForkJoinWorkerThread pool mechanics) are not profiling-relevant anyway.
366356
static const char* CONT_ROOT_FRAME = "JVM Continuation";
367357

358+
// Advances through a continuation boundary to the carrier frame.
359+
// Without wextend=vt_carrier (default): always stops with a "JVM Continuation"
360+
// synthetic root frame — VT frames are complete, carrier internals are noise.
361+
// With wextend=carrier: attempts to walk through; failures emit BCI_ERROR
362+
// so the sample is truthfully marked truncated.
363+
// Walks cont_entry->parent() on repeated calls to handle nested continuations
364+
// (_parent not triggered by standard single-level VTs today, but required
365+
// once any runtime layers continuations on top of VTs).
366+
// Returns true to continue the walk, false to break.
368367
auto walkThroughContinuation = [&]() -> bool {
368+
if (!features.carrier_frames) {
369+
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
370+
return false;
371+
}
369372
cont_entry = (cont_entry != nullptr) ? cont_entry->parent() : vm_thread->contEntry();
370373
if (cont_entry == nullptr) {
371374
Counters::increment(WALKVM_CONT_ENTRY_NULL);
372-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
375+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_entry_null");
373376
return false;
374377
}
375378
uintptr_t entry_fp = cont_entry->entryFP();
376379
if (!StackWalkValidation::isValidFP(entry_fp)) {
377-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
380+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_entry_fp");
378381
return false;
379382
}
380383
// entry_fp has been range-checked by isValidFP above; any remaining
381384
// SIGSEGV from a stale/concurrently-freed pointer is caught by the
382385
// setjmp crash protection in walkVM (checkFault -> longjmp).
383386
uintptr_t carrier_fp = *(uintptr_t*)entry_fp;
384-
const void* carrier_pc = ((const void**)entry_fp)[1];
385-
uintptr_t carrier_sp = entry_fp + 2 * sizeof(void*);
387+
const void* carrier_pc = ((const void**)entry_fp)[FRAME_PC_SLOT];
388+
uintptr_t carrier_sp = entry_fp + (FRAME_PC_SLOT + 1) * sizeof(void*);
386389
if (!StackWalkValidation::isValidFP(carrier_fp) ||
387390
StackWalkValidation::inDeadZone(carrier_pc) ||
388391
!StackWalkValidation::isValidSP(carrier_sp, sp, bottom)) {
389-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
392+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_carrier_sp");
390393
return false;
391394
}
392395
sp = carrier_sp;

ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/VirtualThreadWallClockTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected boolean isPlatformSupported() {
5050

5151
@Override
5252
protected String getProfilerCommand() {
53-
return "wall=1ms,filter=";
53+
return "wall=1ms,filter=,wextend=vt_carrier";
5454
}
5555

5656
/**

0 commit comments

Comments
 (0)