[debug] Add DebugScriptScope and scope position serialization [v8/v8 : main]

0 views
Skip to first unread message

Simon Zünd (Gerrit)

unread,
Aug 13, 2026, 4:14:28 AM (2 days ago) Aug 13
to Leszek Swirski, Jakob Kummerow, v8-s...@luci-project-accounts.iam.gserviceaccount.com, devtools-...@chromium.org, v8-re...@googlegroups.com, victorgo...@chromium.org
Attention needed from Jakob Kummerow and Leszek Swirski

Simon Zünd added 5 comments

Patchset-level comments
File-level comment, Patchset 2 (Latest):
Simon Zünd . unresolved

Hey Jakob,

This CL is the beginning of go/chrome-devtools:cache-scopes-design. I discussed the idea with Leszek last week. The tl;dr is that we want to avoid repeated re-parses of scripts for every conditional breakpoint or debug evaluate in the debugger. So we plan to stash all scope info that the debugger needs on the side.

This CL only serializes the start/end position per scope, but I included the full layout thats planned as a code comment.

I just wanted to get some early, general feedback, if I'm holding the various V8 pieces correctly. Kindly see the questions inline in the code.

File src/debug/debug-scope-info.h
Line 43, Patchset 2 (Latest): DirectHandle<DebugScriptScopeInfo> info_;
Simon Zünd . unresolved

Is this ok to have as long as `DebugScriptScope` is STACK_ALLOCATED?

File src/debug/debug-scope-info.cc
Line 117, Patchset 2 (Latest): ZoneVector<Scope*> all_scopes(zone);
Simon Zünd . unresolved

Not sure if using ZoneVector here and below is necessary or desired. A normal std::vector would probably be fine? Or whats the V8 thing to use here?

Line 138, Patchset 2 (Latest): base::OwnedVector<uint8_t> buffer =
base::OwnedVector<uint8_t>::NewForOverwrite(total_size);
Simon Zünd . unresolved

I assumed we have to write into an OwnedVector first, before copying the whole thing into a ByteArray. The reason is that we (probably?) will allocate strings for variable names that get put into the `string_table` and that could move the ByteArray?

Line 142, Patchset 2 (Latest): base::WriteUnalignedValue<int32_t>(base,
static_cast<int32_t>(all_scopes.size()));
Simon Zünd . unresolved

Is `base::WriteUnalignedValue` / `base::ReadUnalignedValue` the way to go (the layout is not 4 byte aligned)?

Open in Gerrit

Related details

Attention is currently required from:
  • Jakob Kummerow
  • Leszek Swirski
Submit Requirements:
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: v8/v8
Gerrit-Branch: main
Gerrit-Change-Id: Ie809cfd4b3fd6768575b3d7214da94b3b853d6b3
Gerrit-Change-Number: 8255849
Gerrit-PatchSet: 2
Gerrit-Owner: Simon Zünd <szu...@chromium.org>
Gerrit-Reviewer: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Reviewer: Leszek Swirski <les...@chromium.org>
Gerrit-Reviewer: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Attention: Leszek Swirski <les...@chromium.org>
Gerrit-Comment-Date: Thu, 13 Aug 2026 08:13:56 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Jakob Kummerow (Gerrit)

unread,
Aug 13, 2026, 11:27:47 AM (2 days ago) Aug 13
to Simon Zünd, Leszek Swirski, Jakob Kummerow, v8-s...@luci-project-accounts.iam.gserviceaccount.com, devtools-...@chromium.org, v8-re...@googlegroups.com, victorgo...@chromium.org
Attention needed from Leszek Swirski and Simon Zünd

Jakob Kummerow added 11 comments

File src/debug/debug-scope-info.h
Line 48, Patchset 2 (Latest):static_assert(sizeof(DebugScriptScope) == sizeof(void*) + 2 * sizeof(int32_t));
Jakob Kummerow . unresolved

Why is this necessary to enforce? Just to make sure it doesn't accidentally get excessively large? Please add a brief comment; or drop the `static_assert` if you don't actually care whether this needs 16 or 64 bytes.

Line 43, Patchset 2 (Latest): DirectHandle<DebugScriptScopeInfo> info_;
Simon Zünd . unresolved

Is this ok to have as long as `DebugScriptScope` is STACK_ALLOCATED?

Jakob Kummerow

Yes.

The other prerequisite is that the `DebugScriptScope` doesn't outlive the `HandleScope` in which these handles were created, i.e. the following would be a bug:

```
std::optional<DebugScriptScope> scope;
{
HandleScope handles;
DirectHandle<DSSI> scope_info = ...;
scope = DebugScriptScope::FromIndex(scope_info, scope_index);
}
scope->info_->whatever(); // USE AFTER FREE
```

That said, for the use cases visible in this CL, you don't need a handle here at all: `Tagged` would be enough. You only need a handle if you require the `DebugScriptScope` to stay valid across a heap allocation (which could cause GC).

Line 27, Patchset 2 (Latest): // Factory: Returns std::nullopt if scope_index is out of range.
Jakob Kummerow . unresolved

As of this CL, the need for that is not obvious: the only non-test caller performs `CHECK(scope.has_value())` immediately anyway. If other callers are coming where the out-of-range case is actually expected to happen in production, that's fine; otherwise you might as well return a plain `DebugScriptScope` and either `CHECK` or `DCHECK` for invalid (out of range) inputs.

File src/debug/debug-scope-info.cc
Line 65, Patchset 2 (Latest):static_assert(ScopeRecordLayout::kFlagsOffset + sizeof(uint16_t) ==
Jakob Kummerow . unresolved

I'd say these `static_assert`s are redundant with the offset definitions above, in particular because there's no guarantee that they match the code below any better than the offsets do. For example, you could accidentally use `WriteUnalignedValue<uint32_t>(...kFlagsOffset)` below, and this `static_assert` wouldn't catch that.

I'd suggest to either just drop these entirely (based on the argument that the layout-describing comment above makes it easy enough to verify that the offsets are fine), or merge them into the offsets definitions, i.e. write `kVarCountOffset = kFlagsOffset + sizeof(uint16_t)` there. A sufficiently smart IDE will still show a tooltip saying that the constexpr evaluated to `14`.

Line 74, Patchset 2 (Latest): DCHECK_GE(bytes->length().value(), static_cast<int>(sizeof(int32_t)));
Jakob Kummerow . unresolved

shorter: `kInt32Size`

Line 79, Patchset 2 (Latest): DCHECK_GE(scope_index, 0);
DCHECK_LT(scope_index, GetScopeCount(info));
Jakob Kummerow . unresolved

If you don't expect invalid scope_index values in regular operation (i.e. no lazy generation or silent skipping of nonexistent entries), these two DCHECKs are enough to catch bugs, and you don't need lines 94-96 (and the std::optional return value).

Line 117, Patchset 2 (Latest): ZoneVector<Scope*> all_scopes(zone);
Simon Zünd . unresolved

Not sure if using ZoneVector here and below is necessary or desired. A normal std::vector would probably be fine? Or whats the V8 thing to use here?

Jakob Kummerow

Depends on your needs. A `std::vector` seems fine here.

The main benefit of a `ZoneVector` is that it's faster: it's not affected by libc++ hardening, so random accesses don't do bounds checks (in Release mode); it might also be slightly faster to allocate/grow.
The drawback of a `ZoneVector` is that it can't free any memory before the entire Zone dies, which makes it particularly wasteful when growing dynamically, because all the old backing stores it outgrew will sit around as long as the Zone lives.

Line 134, Patchset 2 (Latest): total_size += ScopeRecordLayout::kFixedBaseSize;
Jakob Kummerow . unresolved

This is going to get quite a bit more complicated with the flag-conditional optional fields, right?

Line 138, Patchset 2 (Latest): base::OwnedVector<uint8_t> buffer =
base::OwnedVector<uint8_t>::NewForOverwrite(total_size);
Simon Zünd . unresolved

I assumed we have to write into an OwnedVector first, before copying the whole thing into a ByteArray. The reason is that we (probably?) will allocate strings for variable names that get put into the `string_table` and that could move the ByteArray?

Jakob Kummerow

You don't have to: the job of a `Handle` is to refer to a `ByteArray` (or other `HeapObject`) that might move due to GC.

So the alternative would be:

  • allocate a `ByteArray` right away
  • replace `Address base` (an absolute pointer) with `uint32_t offset` (relative to the ByteArray's start)
  • replace `Address offset_ptr = base + ...` value with `... = byte_array->begin() + offset + ...`, and `Address record = ...` similarly.

So while there'd be a bit of extra cost in each iteration, that's probably not slower than needing the extra `MemCopy`, and would save (peak) memory.

File src/diagnostics/objects-debug.cc
Line 3934, Patchset 2 (Latest):#ifdef DEBUG
VerifyDebugScriptScopeInfo(this, isolate);
Jakob Kummerow . unresolved

Drop the `#ifdef`, inline the called method here. This entire file is behind `#if VERIFY_HEAP`, which is the right condition. Don't worry about manually calling this; the GC will call it for you (when running with `--verify-heap`).

File test/unittests/debug/debug-scope-info-unittest.cc
Line 135, Patchset 2 (Latest): EXPECT_FALSE(DebugScriptScope::FromIndex(info, -1).has_value());
EXPECT_FALSE(DebugScriptScope::FromIndex(info, 1).has_value());
EXPECT_FALSE(DebugScriptScope::FromIndex(info, 100).has_value());
Jakob Kummerow . unresolved

Related to my other comments: if this test is the _only_ case where we expect to try to look up nonexistent values, then it's not worth having.

Open in Gerrit

Related details

Attention is currently required from:
  • Leszek Swirski
  • Simon Zünd
Submit Requirements:
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: v8/v8
Gerrit-Branch: main
Gerrit-Change-Id: Ie809cfd4b3fd6768575b3d7214da94b3b853d6b3
Gerrit-Change-Number: 8255849
Gerrit-PatchSet: 2
Gerrit-Owner: Simon Zünd <szu...@chromium.org>
Gerrit-Reviewer: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Reviewer: Leszek Swirski <les...@chromium.org>
Gerrit-Reviewer: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Leszek Swirski <les...@chromium.org>
Gerrit-Comment-Date: Thu, 13 Aug 2026 15:27:41 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Simon Zünd <szu...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Simon Zünd (Gerrit)

unread,
Aug 14, 2026, 1:52:31 AM (yesterday) Aug 14
to Leszek Swirski, Jakob Kummerow, v8-s...@luci-project-accounts.iam.gserviceaccount.com, devtools-...@chromium.org, v8-re...@googlegroups.com, victorgo...@chromium.org
Attention needed from Jakob Kummerow and Leszek Swirski

Simon Zünd added 12 comments

Patchset-level comments
File-level comment, Patchset 4 (Latest):
Simon Zünd . resolved

Thanks for the detailed review and explanations, really appreciate it!

File src/debug/debug-scope-info.h
Line 48, Patchset 2:static_assert(sizeof(DebugScriptScope) == sizeof(void*) + 2 * sizeof(int32_t));
Jakob Kummerow . resolved

Why is this necessary to enforce? Just to make sure it doesn't accidentally get excessively large? Please add a brief comment; or drop the `static_assert` if you don't actually care whether this needs 16 or 64 bytes.

Simon Zünd

Dropped it. Missed that one on my pre-review. AGY went a bit overboard wanting to ensure that the cursor fits into 2 registers.

Line 43, Patchset 2: DirectHandle<DebugScriptScopeInfo> info_;
Simon Zünd . resolved

Is this ok to have as long as `DebugScriptScope` is STACK_ALLOCATED?

Jakob Kummerow

Yes.

The other prerequisite is that the `DebugScriptScope` doesn't outlive the `HandleScope` in which these handles were created, i.e. the following would be a bug:

```
std::optional<DebugScriptScope> scope;
{
HandleScope handles;
DirectHandle<DSSI> scope_info = ...;
scope = DebugScriptScope::FromIndex(scope_info, scope_index);
}
scope->info_->whatever(); // USE AFTER FREE
```

That said, for the use cases visible in this CL, you don't need a handle here at all: `Tagged` would be enough. You only need a handle if you require the `DebugScriptScope` to stay valid across a heap allocation (which could cause GC).

Simon Zünd

`DebugScriptScope` will be used in `debug-scopes.cc` and `debug-evaluate.cc` where it'll be alive across allocations of `DebugEvaluateContext` / ScopeInfo / context extension objects. Will keep the DirectHandle.

Line 27, Patchset 2: // Factory: Returns std::nullopt if scope_index is out of range.
Jakob Kummerow . resolved

As of this CL, the need for that is not obvious: the only non-test caller performs `CHECK(scope.has_value())` immediately anyway. If other callers are coming where the out-of-range case is actually expected to happen in production, that's fine; otherwise you might as well return a plain `DebugScriptScope` and either `CHECK` or `DCHECK` for invalid (out of range) inputs.

Simon Zünd

I was mostly worried about the empty script (which is valid) to not have any scope, but seems that the empty script also has a single declaration scope.

Since we can't pause in scripts that fail to parse, we are guaranteed to always have a root scope, and any other index should come from traversing the scope tree so removing the std::optional is fine.

File src/debug/debug-scope-info.cc
Line 65, Patchset 2:static_assert(ScopeRecordLayout::kFlagsOffset + sizeof(uint16_t) ==
Jakob Kummerow . resolved

I'd say these `static_assert`s are redundant with the offset definitions above, in particular because there's no guarantee that they match the code below any better than the offsets do. For example, you could accidentally use `WriteUnalignedValue<uint32_t>(...kFlagsOffset)` below, and this `static_assert` wouldn't catch that.

I'd suggest to either just drop these entirely (based on the argument that the layout-describing comment above makes it easy enough to verify that the offsets are fine), or merge them into the offsets definitions, i.e. write `kVarCountOffset = kFlagsOffset + sizeof(uint16_t)` there. A sufficiently smart IDE will still show a tooltip saying that the constexpr evaluated to `14`.

Simon Zünd

Removed

Line 74, Patchset 2: DCHECK_GE(bytes->length().value(), static_cast<int>(sizeof(int32_t)));
Jakob Kummerow . resolved

shorter: `kInt32Size`

Simon Zünd

Done

Line 79, Patchset 2: DCHECK_GE(scope_index, 0);
DCHECK_LT(scope_index, GetScopeCount(info));
Jakob Kummerow . resolved

If you don't expect invalid scope_index values in regular operation (i.e. no lazy generation or silent skipping of nonexistent entries), these two DCHECKs are enough to catch bugs, and you don't need lines 94-96 (and the std::optional return value).

Simon Zünd

Removed the `std::optional`, see the comment in header.

Line 117, Patchset 2: ZoneVector<Scope*> all_scopes(zone);
Simon Zünd . resolved

Not sure if using ZoneVector here and below is necessary or desired. A normal std::vector would probably be fine? Or whats the V8 thing to use here?

Jakob Kummerow

Depends on your needs. A `std::vector` seems fine here.

The main benefit of a `ZoneVector` is that it's faster: it's not affected by libc++ hardening, so random accesses don't do bounds checks (in Release mode); it might also be slightly faster to allocate/grow.
The drawback of a `ZoneVector` is that it can't free any memory before the entire Zone dies, which makes it particularly wasteful when growing dynamically, because all the old backing stores it outgrew will sit around as long as the Zone lives.

Simon Zünd

Going with `std::vector` for now.

Line 134, Patchset 2: total_size += ScopeRecordLayout::kFixedBaseSize;
Jakob Kummerow . resolved

This is going to get quite a bit more complicated with the flag-conditional optional fields, right?

Simon Zünd

Indeed. I was planning to mirror the flag conditions here to avoid re-allocating the resulting array as it grows.

Line 138, Patchset 2: base::OwnedVector<uint8_t> buffer =
base::OwnedVector<uint8_t>::NewForOverwrite(total_size);
Simon Zünd . resolved

I assumed we have to write into an OwnedVector first, before copying the whole thing into a ByteArray. The reason is that we (probably?) will allocate strings for variable names that get put into the `string_table` and that could move the ByteArray?

Jakob Kummerow

You don't have to: the job of a `Handle` is to refer to a `ByteArray` (or other `HeapObject`) that might move due to GC.

So the alternative would be:

  • allocate a `ByteArray` right away
  • replace `Address base` (an absolute pointer) with `uint32_t offset` (relative to the ByteArray's start)
  • replace `Address offset_ptr = base + ...` value with `... = byte_array->begin() + offset + ...`, and `Address record = ...` similarly.

So while there'd be a bit of extra cost in each iteration, that's probably not slower than needing the extra `MemCopy`, and would save (peak) memory.

Simon Zünd

We'll probably populate the `string_table` as we serialize the scopes since we serialize a scopes' variable directly after the basic scope info. We'd have to be careful to dereference the Handle again at the right times or build the `string_table` as a separate pass to avoid allocations. I'll give it a go.

File src/diagnostics/objects-debug.cc
Line 3934, Patchset 2:#ifdef DEBUG
VerifyDebugScriptScopeInfo(this, isolate);
Jakob Kummerow . unresolved

Drop the `#ifdef`, inline the called method here. This entire file is behind `#if VERIFY_HEAP`, which is the right condition. Don't worry about manually calling this; the GC will call it for you (when running with `--verify-heap`).

Simon Zünd

I was thinking to keep anything that needs to know the exact layout of `numeric_data` in `debug-scope-info.cc`, otherwise we'd also have to include it there. I could `#ifdef VERIFY_HEAP` the `VerifyDebugScriptScopeInfo` helper to keep it that way or inline the helper here. No strong preference from me for either solution.

File test/unittests/debug/debug-scope-info-unittest.cc
Line 135, Patchset 2: EXPECT_FALSE(DebugScriptScope::FromIndex(info, -1).has_value());

EXPECT_FALSE(DebugScriptScope::FromIndex(info, 1).has_value());
EXPECT_FALSE(DebugScriptScope::FromIndex(info, 100).has_value());
Jakob Kummerow . resolved

Related to my other comments: if this test is the _only_ case where we expect to try to look up nonexistent values, then it's not worth having.

Simon Zünd

Removed.

Open in Gerrit

Related details

Attention is currently required from:
  • Jakob Kummerow
  • Leszek Swirski
Submit Requirements:
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: v8/v8
Gerrit-Branch: main
Gerrit-Change-Id: Ie809cfd4b3fd6768575b3d7214da94b3b853d6b3
Gerrit-Change-Number: 8255849
Gerrit-PatchSet: 4
Gerrit-Owner: Simon Zünd <szu...@chromium.org>
Gerrit-Reviewer: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Reviewer: Leszek Swirski <les...@chromium.org>
Gerrit-Reviewer: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Attention: Leszek Swirski <les...@chromium.org>
Gerrit-Comment-Date: Fri, 14 Aug 2026 05:52:25 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: Simon Zünd <szu...@chromium.org>
Comment-In-Reply-To: Jakob Kummerow <jkum...@chromium.org>
satisfied_requirement
unsatisfied_requirement
open
diffy

Jakob Kummerow (Gerrit)

unread,
Aug 14, 2026, 6:43:22 AM (yesterday) Aug 14
to Simon Zünd, Leszek Swirski, Jakob Kummerow, v8-s...@luci-project-accounts.iam.gserviceaccount.com, devtools-...@chromium.org, v8-re...@googlegroups.com, victorgo...@chromium.org
Attention needed from Leszek Swirski and Simon Zünd

Jakob Kummerow added 2 comments

File src/debug/debug-scope-info.cc
Line 142, Patchset 2: base::WriteUnalignedValue<int32_t>(base,
static_cast<int32_t>(all_scopes.size()));
Simon Zünd . resolved

Is `base::WriteUnalignedValue` / `base::ReadUnalignedValue` the way to go (the layout is not 4 byte aligned)?

Jakob Kummerow

Yes.

File src/diagnostics/objects-debug.cc
Line 3934, Patchset 2:#ifdef DEBUG
VerifyDebugScriptScopeInfo(this, isolate);
Jakob Kummerow . unresolved

Drop the `#ifdef`, inline the called method here. This entire file is behind `#if VERIFY_HEAP`, which is the right condition. Don't worry about manually calling this; the GC will call it for you (when running with `--verify-heap`).

Simon Zünd

I was thinking to keep anything that needs to know the exact layout of `numeric_data` in `debug-scope-info.cc`, otherwise we'd also have to include it there. I could `#ifdef VERIFY_HEAP` the `VerifyDebugScriptScopeInfo` helper to keep it that way or inline the helper here. No strong preference from me for either solution.

Jakob Kummerow

The `#ifdef` should definitely be `VERIFY_HEAP`.

Where to put the code, I don't feel very strongly about that either. Historically, V8 has followed the convention that all object verifiers are in this file (as you can see). Keeping the layout definition local to a class-specific .cc file is a fairly convincing reason to diverge from that precedent though.

How about moving the entire `DebugScriptScopeInfoVerify` there (with a brief comment to document the reason)? Or would that lead to other difficulty?

Open in Gerrit

Related details

Attention is currently required from:
  • Leszek Swirski
  • Simon Zünd
Submit Requirements:
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: v8/v8
Gerrit-Branch: main
Gerrit-Change-Id: Ie809cfd4b3fd6768575b3d7214da94b3b853d6b3
Gerrit-Change-Number: 8255849
Gerrit-PatchSet: 4
Gerrit-Owner: Simon Zünd <szu...@chromium.org>
Gerrit-Reviewer: Jakob Kummerow <jkum...@chromium.org>
Gerrit-Reviewer: Leszek Swirski <les...@chromium.org>
Gerrit-Reviewer: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Simon Zünd <szu...@chromium.org>
Gerrit-Attention: Leszek Swirski <les...@chromium.org>
Gerrit-Comment-Date: Fri, 14 Aug 2026 10:43:17 +0000
satisfied_requirement
unsatisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages