Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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>
  • Loading branch information
schuay and aduh95 committed Oct 11, 2026
commit 23fa04697bcc0d2da9d34b81fe5d1d8c8350ec72
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.59',
'v8_embedder_string': '-node.60',

##### V8 defaults for Node.js #####

Expand Down
23 changes: 22 additions & 1 deletion deps/v8/src/deoptimizer/translated-state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1875,12 +1875,33 @@ Address TranslatedState::DecompressIfNeeded(intptr_t value) {
static_cast<uintptr_t>(value) <= std::numeric_limits<uint32_t>::max()) {
#endif
return V8HeapCompressionScheme::DecompressTagged(
isolate(), static_cast<uint32_t>(value));
V8HeapCompressionScheme::base(), static_cast<uint32_t>(value));
} else {
return value;
}
}

// static
Tagged<Object> TranslatedState::ResolveTaggedValue(
DeoptTranslationIterator* it, Address fp,
Tagged<DeoptimizationLiteralArray> literals) {
TranslationOpcode opcode = it->NextOpcode();
switch (opcode) {
case TranslationOpcode::LITERAL: {
int literal_index = it->NextOperand();
return literals->get(literal_index);
}
case TranslationOpcode::TAGGED_STACK_SLOT: {
int slot_offset =
OptimizedJSFrame::StackSlotOffsetRelativeToFp(it->NextOperand());
intptr_t value = *reinterpret_cast<intptr_t*>(fp + slot_offset);
return Tagged<Object>(DecompressIfNeeded(value));
}
default:
UNREACHABLE();
}
}

TranslatedState::TranslatedState(const JavaScriptFrame* frame)
: purpose_(kFrameInspection) {
int deopt_index = SafepointEntry::kNoDeoptIndex;
Expand Down
10 changes: 9 additions & 1 deletion deps/v8/src/deoptimizer/translated-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,15 @@ class TranslatedState {
void VerifyMaterializedObjects();
bool DoUpdateFeedback();

// Resolves one deopt translation value opcode to a raw Tagged<Object>,
// reading from the live frame if needed. Only LITERAL and
// TAGGED_STACK_SLOT are expected; other opcodes are UNREACHABLE.
static Tagged<Object> ResolveTaggedValue(
DeoptTranslationIterator* it, Address fp,
Tagged<DeoptimizationLiteralArray> literals);

static Address DecompressIfNeeded(intptr_t value);

private:
friend TranslatedValue;

Expand All @@ -513,7 +522,6 @@ class TranslatedState {
int frame_index, DeoptTranslationIterator* iterator,
const DeoptimizationLiteralProvider& literal_array, Address fp,
RegisterValues* registers, FILE* trace_file);
Address DecompressIfNeeded(intptr_t value);
void CreateArgumentsElementsTranslatedValues(int frame_index,
Address input_frame_pointer,
CreateArgumentsType type,
Expand Down
127 changes: 116 additions & 11 deletions deps/v8/src/execution/frames.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3121,7 +3121,108 @@ FrameSummaries OptimizedJSFrame::Summarize() const {
"Missing deoptimization information for OptimizedJSFrame::Summarize.");
}

// Prepare iteration over translation. We must not materialize values here
// Lightweight walk: iterate frame headers only, resolving just the
// function and receiver from the live frame via ResolveTaggedValue.
// This avoids the expensive TranslatedState::Init + Prepare path that
// would parse every value in every inlined frame.

Tagged<DeoptimizationLiteralArray> literal_array = data->LiteralArray();

// Lightweight walk: resolve function and receiver from live frame headers
// and build JavaScriptFrameSummary objects directly.
bool needs_full_walk = false;
{
DisallowGarbageCollection no_gc;
DeoptimizationFrameTranslation::Iterator it(
data->FrameTranslation(), data->TranslationIndex(deopt_index).value());
bool is_constructor = IsConstructor();
int remaining = it.EnterBeginOpcode().total_frame_count;

while (remaining > 0) {
TranslationOpcode opcode = it.SeekNextFrame();
remaining--;

if (opcode == TranslationOpcode::CONSTRUCT_CREATE_STUB_FRAME ||
opcode == TranslationOpcode::CONSTRUCT_INVOKE_STUB_FRAME) {
is_constructor = true;
it.SkipOperands(TranslationOpcodeOperandCount(opcode));
continue;
}

if (!IsTranslationJsFrameOpcode(opcode)) {
#if V8_ENABLE_WEBASSEMBLY
// Wasm-inlined-into-JS frames need the full TranslatedState
// machinery to produce WasmFrameSummary entries.
if (opcode == TranslationOpcode::WASM_INLINED_INTO_JS_FRAME) {
needs_full_walk = true;
break;
}
#endif
it.SkipOperands(TranslationOpcodeOperandCount(opcode));
continue;
}

bool is_builtin_cont =
(opcode == TranslationOpcode::JAVASCRIPT_BUILTIN_CONTINUATION_FRAME ||
opcode == TranslationOpcode::
JAVASCRIPT_BUILTIN_CONTINUATION_WITH_CATCH_FRAME);

int bytecode_offset = it.NextOperand();
int sfi_id = it.NextOperand();
Tagged<SharedFunctionInfo> sfi =
Cast<SharedFunctionInfo>(literal_array->get(sfi_id));

// Skip remaining header operands to reach the values.
it.SkipOperands(TranslationOpcodeOperandCount(opcode) - 2);

// Resolve closure and receiver from the live frame. Both are always
// encoded as LITERAL or TAGGED_STACK_SLOT in the deopt translation.
Tagged<Object> function_obj =
TranslatedState::ResolveTaggedValue(&it, fp(), literal_array);
DCHECK(IsJSFunction(function_obj));
Tagged<Object> receiver_obj =
TranslatedState::ResolveTaggedValue(&it, fp(), literal_array);

Tagged<AbstractCode> abstract_code;
int code_offset;
if (is_builtin_cont) {
code_offset = 0;
abstract_code = Cast<AbstractCode>(
isolate()->builtins()->code(Builtins::GetBuiltinFromBytecodeOffset(
BytecodeOffset(bytecode_offset))));
} else {
code_offset = bytecode_offset;
abstract_code = Cast<AbstractCode>(sfi->GetBytecodeArray(isolate()));
}

DirectHandle<FixedArray> params = GetParameters();
FrameSummary::JavaScriptFrameSummary summary(
isolate(), receiver_obj, Cast<JSFunction>(function_obj),
abstract_code, code_offset, is_constructor, *params);
summaries.frames.push_back(summary);
is_constructor = false;
}

if (!needs_full_walk && is_constructor) {
summaries.top_frame_is_construct_call = true;
}
} // no_gc scope ends.

if (needs_full_walk) {
return SummarizeFull(data, deopt_index);
}

return summaries;
}

FrameSummaries OptimizedJSFrame::SummarizeFull(Tagged<DeoptimizationData> data,
int deopt_index) const {
FrameSummaries summaries;

DCHECK_NE(deopt_index, SafepointEntry::kNoDeoptIndex);
DCHECK(!data.is_null());

// Prepare iteration over translation. We must not materialize values here
// because we do not deoptimize the function.
TranslatedState translated(this);
translated.Prepare(fp());
Expand Down Expand Up @@ -3257,24 +3358,20 @@ int TurbofanJSFrame::FindReturnPCForTrampoline(Tagged<Code> code,
return safepoints.find_return_pc(trampoline_pc);
}

Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
Tagged<Code> code, int* deopt_index) const {
DCHECK(is_optimized());

Address pc = maybe_unauthenticated_pc();

DCHECK(code->contains(isolate(), pc));
// static
Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationDataForPC(
Isolate* isolate, Tagged<Code> code, Address pc, int* deopt_index) {
DCHECK(code->contains(isolate, pc));
DCHECK(CodeKindCanDeoptimize(code->kind()));

if (code->is_maglevved()) {
MaglevSafepointEntry safepoint_entry =
code->GetMaglevSafepointEntry(isolate(), pc);
code->GetMaglevSafepointEntry(isolate, pc);
if (safepoint_entry.has_deoptimization_index()) {
*deopt_index = safepoint_entry.deoptimization_index();
return Cast<DeoptimizationData>(code->deoptimization_data());
}
} else {
SafepointEntry safepoint_entry = code->GetSafepointEntry(isolate(), pc);
SafepointEntry safepoint_entry = code->GetSafepointEntry(isolate, pc);
if (safepoint_entry.has_deoptimization_index()) {
*deopt_index = safepoint_entry.deoptimization_index();
return Cast<DeoptimizationData>(code->deoptimization_data());
Expand All @@ -3284,6 +3381,14 @@ Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
return {};
}

Tagged<DeoptimizationData> OptimizedJSFrame::GetDeoptimizationData(
Tagged<Code> code, int* deopt_index) const {
DCHECK(is_optimized());
Address pc = maybe_unauthenticated_pc();
DCHECK(code->contains(isolate(), pc));
return GetDeoptimizationDataForPC(isolate(), code, pc, deopt_index);
}

void OptimizedJSFrame::GetFunctions(
std::vector<Tagged<SharedFunctionInfo>>* functions) const {
DCHECK(functions->empty());
Expand Down
12 changes: 12 additions & 0 deletions deps/v8/src/execution/frames.h
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,11 @@ class OptimizedJSFrame : public JavaScriptFrame {
Tagged<DeoptimizationData> GetDeoptimizationData(Tagged<Code> code,
int* deopt_index) const;

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

static int StackSlotOffsetRelativeToFp(int slot_index);

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

private:
// Full TranslatedState-based walk, used as fallback when the lightweight
// path in Summarize() encounters frames it cannot handle (e.g.
// wasm-inlined-into-JS frames).
FrameSummaries SummarizeFull(Tagged<DeoptimizationData> data,
int deopt_index) const;

protected:
inline explicit OptimizedJSFrame(StackFrameIteratorBase* iterator);
};
Expand Down
Loading