Skip to content

Commit 2f0cfb6

Browse files
schuayaduh95
andcommitted
deps: V8: backport c9c0abfa51f0
Original commit message: [stack traces] Reduce stack frame summarization costs During stack trace capture, Summarize() is the most expensive step — it creates a full TranslatedState for every optimized frame even though most frames are never inspected. This CL reduces that cost in two ways: 1. Lightweight Summarize() for optimized frames: instead of building a full TranslatedState, walk only the deopt translation frame headers and resolve function/receiver via ResolveTaggedValue(), falling back to the full TranslatedState path for wasm-inlined or unresolvable closures. 2. Deferred baseline frames: during CaptureSimpleStackTrace, baseline frames store the raw Code + PC offset and defer bytecode offset resolution to ExpandDeferredFrames(), which runs lazily before the stack trace is formatted or inspected. A new Torque bitfield flag (is_deferred_baseline_frame) marks entries in the raw capture array that still need resolution. All consumers (GetSimpleStackTrace, GetDetailedStackTraceFromCallSiteInfos, GetFormattedStack, PrintCurrentStackTrace) call ExpandDeferredFrames() before processing the array. Change-Id: I1fe8cce918ba129d655d66f608ac6aa0ed160920 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7722138 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Auto-Submit: Jakob Linke <jgruber@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#106237} Refs: v8/v8@74e153d Refs: v8/v8@c9c0abf Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com> PR-URL: #65764 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 8f85acd commit 2f0cfb6

10 files changed

Lines changed: 333 additions & 54 deletions

File tree

common.gypi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
# Reset this number to 0 on major V8 upgrades.
4545
# Increment by one for each non-official patch applied to deps/v8.
46-
'v8_embedder_string': '-node.30',
46+
'v8_embedder_string': '-node.31',
4747

4848
##### V8 defaults for Node.js #####
4949

deps/v8/src/deoptimizer/translated-state.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,27 @@ Address TranslatedState::DecompressIfNeeded(intptr_t value) {
18891889
}
18901890
}
18911891

1892+
// static
1893+
Tagged<Object> TranslatedState::ResolveTaggedValue(
1894+
DeoptTranslationIterator* it, Address fp,
1895+
Tagged<DeoptimizationLiteralArray> literals) {
1896+
TranslationOpcode opcode = it->NextOpcode();
1897+
switch (opcode) {
1898+
case TranslationOpcode::LITERAL: {
1899+
int literal_index = it->NextOperand();
1900+
return literals->get(literal_index);
1901+
}
1902+
case TranslationOpcode::TAGGED_STACK_SLOT: {
1903+
int slot_offset =
1904+
OptimizedJSFrame::StackSlotOffsetRelativeToFp(it->NextOperand());
1905+
intptr_t value = *reinterpret_cast<intptr_t*>(fp + slot_offset);
1906+
return Tagged<Object>(DecompressIfNeeded(value));
1907+
}
1908+
default:
1909+
UNREACHABLE();
1910+
}
1911+
}
1912+
18921913
TranslatedState::TranslatedState(const JavaScriptFrame* frame)
18931914
: purpose_(kFrameInspection) {
18941915
int deopt_index = SafepointEntry::kNoDeoptIndex;

deps/v8/src/deoptimizer/translated-state.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,15 @@ class TranslatedState {
511511
void VerifyMaterializedObjects();
512512
bool DoUpdateFeedback(DeoptimizeReason reason);
513513

514+
// Resolves one deopt translation value opcode to a raw Tagged<Object>,
515+
// reading from the live frame if needed. Only LITERAL and
516+
// TAGGED_STACK_SLOT are expected; other opcodes are UNREACHABLE.
517+
static Tagged<Object> ResolveTaggedValue(
518+
DeoptTranslationIterator* it, Address fp,
519+
Tagged<DeoptimizationLiteralArray> literals);
520+
521+
static Address DecompressIfNeeded(intptr_t value);
522+
514523
private:
515524
friend TranslatedValue;
516525

@@ -529,7 +538,6 @@ class TranslatedState {
529538
int frame_index, DeoptTranslationIterator* iterator,
530539
const DeoptimizationLiteralProvider& literal_array, Address fp,
531540
RegisterValues* registers, FILE* trace_file);
532-
Address DecompressIfNeeded(intptr_t value);
533541
void CreateArgumentsElementsTranslatedValues(int frame_index,
534542
Address input_frame_pointer,
535543
CreateArgumentsType type,

deps/v8/src/execution/frames.cc

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,7 +3179,109 @@ FrameSummaries OptimizedJSFrame::Summarize(bool never_allocate) const {
31793179
"Missing deoptimization information for OptimizedJSFrame::Summarize.");
31803180
}
31813181

3182-
// Prepare iteration over translation. We must not materialize values here
3182+
// Lightweight walk: iterate frame headers only, resolving just the
3183+
// function and receiver from the live frame via ResolveTaggedValue.
3184+
// This avoids the expensive TranslatedState::Init + Prepare path that
3185+
// would parse every value in every inlined frame.
3186+
3187+
Tagged<DeoptimizationLiteralArray> literal_array = data->LiteralArray();
3188+
3189+
// Lightweight walk: resolve function and receiver from live frame headers
3190+
// and build JavaScriptFrameSummary objects directly.
3191+
bool needs_full_walk = false;
3192+
{
3193+
DisallowGarbageCollection no_gc;
3194+
DeoptimizationFrameTranslation::Iterator it(
3195+
data->FrameTranslation(), data->TranslationIndex(deopt_index).value());
3196+
bool is_constructor = IsConstructor();
3197+
int remaining = it.EnterBeginOpcode().total_frame_count;
3198+
3199+
while (remaining > 0) {
3200+
TranslationOpcode opcode = it.SeekNextFrame();
3201+
remaining--;
3202+
3203+
if (opcode == TranslationOpcode::CONSTRUCT_CREATE_STUB_FRAME ||
3204+
opcode == TranslationOpcode::CONSTRUCT_INVOKE_STUB_FRAME) {
3205+
is_constructor = true;
3206+
it.SkipOperands(TranslationOpcodeOperandCount(opcode));
3207+
continue;
3208+
}
3209+
3210+
if (!IsTranslationJsFrameOpcode(opcode)) {
3211+
#if V8_ENABLE_WEBASSEMBLY
3212+
// Wasm-inlined-into-JS frames need the full TranslatedState
3213+
// machinery to produce WasmFrameSummary entries.
3214+
if (opcode == TranslationOpcode::WASM_INLINED_INTO_JS_FRAME) {
3215+
needs_full_walk = true;
3216+
break;
3217+
}
3218+
#endif
3219+
it.SkipOperands(TranslationOpcodeOperandCount(opcode));
3220+
continue;
3221+
}
3222+
3223+
bool is_builtin_cont =
3224+
(opcode == TranslationOpcode::JAVASCRIPT_BUILTIN_CONTINUATION_FRAME ||
3225+
opcode == TranslationOpcode::
3226+
JAVASCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME);
3227+
3228+
int bytecode_offset = it.NextOperand();
3229+
int sfi_id = it.NextOperand();
3230+
Tagged<SharedFunctionInfo> sfi =
3231+
Cast<SharedFunctionInfo>(literal_array->get(sfi_id));
3232+
3233+
// Skip remaining header operands to reach the values.
3234+
it.SkipOperands(TranslationOpcodeOperandCount(opcode) - 2);
3235+
3236+
// Resolve closure and receiver from the live frame. Both are always
3237+
// encoded as LITERAL or TAGGED_STACK_SLOT in the deopt translation.
3238+
Tagged<Object> function_obj =
3239+
TranslatedState::ResolveTaggedValue(&it, fp(), literal_array);
3240+
DCHECK(IsJSFunction(function_obj));
3241+
Tagged<Object> receiver_obj =
3242+
TranslatedState::ResolveTaggedValue(&it, fp(), literal_array);
3243+
3244+
Tagged<AbstractCode> abstract_code;
3245+
int code_offset;
3246+
if (is_builtin_cont) {
3247+
code_offset = 0;
3248+
abstract_code = Cast<AbstractCode>(
3249+
isolate()->builtins()->code(Builtins::GetBuiltinFromBytecodeOffset(
3250+
BytecodeOffset(bytecode_offset))));
3251+
} else {
3252+
code_offset = bytecode_offset;
3253+
abstract_code = Cast<AbstractCode>(sfi->GetBytecodeArray(isolate()));
3254+
}
3255+
3256+
DirectHandle<FixedArray> params = GetParameters(never_allocate);
3257+
FrameSummary::JavaScriptFrameSummary summary(
3258+
isolate(), receiver_obj, Cast<JSFunction>(function_obj),
3259+
abstract_code, code_offset, is_constructor, *params);
3260+
summaries.frames.push_back(summary);
3261+
is_constructor = false;
3262+
}
3263+
3264+
if (!needs_full_walk && is_constructor) {
3265+
summaries.top_frame_is_construct_call = true;
3266+
}
3267+
} // no_gc scope ends.
3268+
3269+
if (needs_full_walk) {
3270+
return SummarizeFull(data, deopt_index, never_allocate);
3271+
}
3272+
3273+
return summaries;
3274+
}
3275+
3276+
FrameSummaries OptimizedJSFrame::SummarizeFull(Tagged<DeoptimizationData> data,
3277+
int deopt_index,
3278+
bool never_allocate) const {
3279+
FrameSummaries summaries;
3280+
3281+
DCHECK_NE(deopt_index, SafepointEntry::kNoDeoptIndex);
3282+
DCHECK(!data.is_null());
3283+
3284+
// Prepare iteration over translation. We must not materialize values here
31833285
// because we do not deoptimize the function.
31843286
TranslatedState translated(this);
31853287
translated.Prepare(fp());
@@ -3204,7 +3306,21 @@ FrameSummaries OptimizedJSFrame::Summarize(bool never_allocate) const {
32043306
// Get the correct receiver in the optimized frame.
32053307
static_assert(TranslatedFrame::kReceiverIsFirstParameterInJSFrames);
32063308
CHECK(!translated_values->IsMaterializedObject());
3207-
DirectHandle<Object> receiver = translated_values->GetValue();
3309+
// Check GetRawValue() against arguments_marker() first to see whether
3310+
// calling GetValue() allocates.
3311+
Tagged<Object> receiver_obj = translated_values->GetRawValue();
3312+
DirectHandle<Object> receiver;
3313+
if (receiver_obj == ReadOnlyRoots(isolate()).arguments_marker() &&
3314+
never_allocate) {
3315+
// Calling GetValue() would definitely trigger allocation but with
3316+
// `never_allocate` allocations are not allowed. Simply pick `undefined`
3317+
// as receiver instead even though it is off. `never_allocate` is
3318+
// currently only used for OOM stacks, where we don't even emit the
3319+
// receiver but want to see as many stack frames as possible.
3320+
receiver = isolate()->factory()->undefined_value();
3321+
} else {
3322+
receiver = translated_values->GetValue();
3323+
}
32083324
translated_values++;
32093325

32103326
// Determine the underlying code object and the position within it from
@@ -3311,24 +3427,20 @@ int TurbofanJSFrame::FindReturnPCForTrampoline(Tagged<Code> code,
33113427
return safepoints.find_return_pc(trampoline_pc);
33123428
}
33133429

3314-
Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
3315-
Tagged<Code> code, int* deopt_index) const {
3316-
DCHECK(is_optimized());
3317-
3318-
Address pc = maybe_unauthenticated_pc();
3319-
3320-
DCHECK(code->contains(isolate(), pc));
3430+
// static
3431+
Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationDataForPC(
3432+
Isolate* isolate, Tagged<Code> code, Address pc, int* deopt_index) {
3433+
DCHECK(code->contains(isolate, pc));
33213434
DCHECK(CodeKindCanDeoptimize(code->kind()));
3322-
33233435
if (code->is_maglevved()) {
33243436
MaglevSafepointEntry safepoint_entry =
3325-
code->GetMaglevSafepointEntry(isolate(), pc);
3437+
code->GetMaglevSafepointEntry(isolate, pc);
33263438
if (safepoint_entry.has_deoptimization_index()) {
33273439
*deopt_index = safepoint_entry.deoptimization_index();
33283440
return code->deoptimization_data();
33293441
}
33303442
} else {
3331-
SafepointEntry safepoint_entry = code->GetSafepointEntry(isolate(), pc);
3443+
SafepointEntry safepoint_entry = code->GetSafepointEntry(isolate, pc);
33323444
if (safepoint_entry.has_deoptimization_index()) {
33333445
*deopt_index = safepoint_entry.deoptimization_index();
33343446
return code->deoptimization_data();
@@ -3338,6 +3450,14 @@ Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
33383450
return {};
33393451
}
33403452

3453+
Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
3454+
Tagged<Code> code, int* deopt_index) const {
3455+
DCHECK(is_optimized());
3456+
Address pc = maybe_unauthenticated_pc();
3457+
DCHECK(code->contains(isolate(), pc));
3458+
return GetDeoptimizationDataForPC(isolate(), code, pc, deopt_index);
3459+
}
3460+
33413461
void OptimizedJSFrame::GetFunctions(
33423462
std::vector<Tagged<SharedFunctionInfo>>* functions) const {
33433463
DCHECK(functions->empty());

deps/v8/src/execution/frames.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,11 @@ class OptimizedJSFrame : public JavaScriptFrame {
11691169
Tagged<DeoptimizationData> GetDeoptimizationData(Tagged<Code> code,
11701170
int* deopt_index) const;
11711171

1172+
// Like GetDeoptimizationData, but takes an explicit PC instead of reading
1173+
// it from the frame. Can be used without a live frame.
1174+
static Tagged<DeoptimizationData> GetDeoptimizationDataForPC(
1175+
Isolate* isolate, Tagged<Code> code, Address pc, int* deopt_index);
1176+
11721177
static int StackSlotOffsetRelativeToFp(int slot_index);
11731178

11741179
// Lookup exception handler for current {pc}, returns -1 if none found.
@@ -1178,6 +1183,13 @@ class OptimizedJSFrame : public JavaScriptFrame {
11781183
virtual int FindReturnPCForTrampoline(Tagged<Code> code,
11791184
int trampoline_pc) const = 0;
11801185

1186+
private:
1187+
// Full TranslatedState-based walk, used as fallback when the lightweight
1188+
// path in Summarize() encounters frames it cannot handle (e.g.
1189+
// wasm-inlined-into-JS frames).
1190+
FrameSummaries SummarizeFull(Tagged<DeoptimizationData> data, int deopt_index,
1191+
bool never_allocate) const;
1192+
11811193
protected:
11821194
inline explicit OptimizedJSFrame(StackFrameIteratorBase* iterator);
11831195
};

0 commit comments

Comments
 (0)