Skip to content

[turbopack] Add BumpVec (vector implementation that takes a Bump)#94296

Open
sampoder wants to merge 3 commits into
canaryfrom
sp/turbopack/analyzer-arena-infra
Open

[turbopack] Add BumpVec (vector implementation that takes a Bump)#94296
sampoder wants to merge 3 commits into
canaryfrom
sp/turbopack/analyzer-arena-infra

Conversation

@sampoder
Copy link
Copy Markdown
Member

@sampoder sampoder commented Jun 1, 2026

This PR implements BumpVec this is a custom vector implementation that takes in a Bump as a type (from bumpalo) so that we get Send/Sync on our JSValue vectors when we make that switch in: #94297.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Failing test suites

Commit: 01e3dd5 | About building and testing Next.js

pnpm test-start test/production/app-dir/server-action-period-hash/server-action-period-hash-custom-key.test.ts (job)

  • app-dir - server-action-period-hash-custom-key > should have a different manifest if the encryption key from process env is changed (DD)
  • app-dir - server-action-period-hash-custom-key > should have the same manifest if the encryption key from process env is same (DD)
Expand output

● app-dir - server-action-period-hash-custom-key › should have a different manifest if the encryption key from process env is changed

can not run export while server is running, use next.stop() first

  253 |   ) {
  254 |     if (this.childProcess) {
> 255 |       throw new Error(
      |             ^
  256 |         `can not run export while server is running, use next.stop() first`
  257 |       )
  258 |     }

  at NextStartInstance.build (lib/next-modes/next-start.ts:255:13)
  at Object.build (production/app-dir/server-action-period-hash/server-action-period-hash-custom-key.test.ts:18:16)

● app-dir - server-action-period-hash-custom-key › should have the same manifest if the encryption key from process env is same

can not run export while server is running, use next.stop() first

  253 |   ) {
  254 |     if (this.childProcess) {
> 255 |       throw new Error(
      |             ^
  256 |         `can not run export while server is running, use next.stop() first`
  257 |       )
  258 |     }

  at NextStartInstance.build (lib/next-modes/next-start.ts:255:13)
  at Object.build (production/app-dir/server-action-period-hash/server-action-period-hash-custom-key.test.ts:32:16)

pnpm test-dev-turbo test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts (turbopack) (job)

  • interception-dynamic-segment > should work when interception route is paired with a dynamic segment (DD)
  • interception-dynamic-segment > should intercept consistently with back/forward navigation (DD)
  • interception-dynamic-segment > should intercept multiple times from root (DD)
Expand output

● interception-dynamic-segment › should work when interception route is paired with a dynamic segment

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
catch-all"

  69 |
  70 |     await retry(async () => {
> 71 |       expect(await browser.elementById('modal').text()).toContain('intercepted')
     |                                                         ^
  72 |     })
  73 |
  74 |     await browser.refresh()

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:71:57)
  at retry (lib/next-test-utils.ts:867:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:70:5)

● interception-dynamic-segment › should intercept consistently with back/forward navigation

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
catch-all"

  94 |
  95 |     await retry(async () => {
> 96 |       expect(await browser.elementById('modal').text()).toContain('intercepted')
     |                                                         ^
  97 |     })
  98 |
  99 |     // Go back to root

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:96:57)
  at retry (lib/next-test-utils.ts:867:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:95:5)

● interception-dynamic-segment › should intercept multiple times from root

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
catch-all"

  124 |
  125 |       await retry(async () => {
> 126 |         expect(await browser.elementById('modal').text()).toContain(
      |                                                           ^
  127 |           'intercepted'
  128 |         )
  129 |       })

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:126:59)
  at retry (lib/next-test-utils.ts:867:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:125:7)

Other failing CI jobs

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Stats skipped

Commit: 01e3dd5
View workflow run

Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/arena.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/arena.rs Outdated
@sampoder sampoder requested review from bgw and lukesandberg June 1, 2026 18:27
@sampoder sampoder marked this pull request as ready for review June 1, 2026 18:27
Introduces `BumpVec<'a, T>`, a minimal growable vector for bump-allocated
data. It stores only an arena-allocated buffer and a length, so it is
`Send`/`Sync` (a `&'a mut [T]` is, when `T` is) with no `unsafe impl` — the
only `unsafe` is the localized `MaybeUninit` bookkeeping. Growth methods take
the `&'a Bump` to allocate from; `Drop` drops the live elements (the arena only
frees memory). Includes unit tests covering growth, `split_off`, the three
iterators, and drop/double-free behavior.

This is the first of a two-PR stack; a follow-up uses it to arena-allocate the
analyzer's `JsValue`s.
@sampoder sampoder force-pushed the sp/turbopack/analyzer-arena-infra branch from a98d014 to e21492c Compare June 1, 2026 23:29
@sampoder sampoder changed the title [turbopack] Add bumpalo-based arena for storing JsValues [turbopack] Add BumpVec (vector implementation that takes a Bump) Jun 1, 2026
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment on lines +80 to +84
pub fn extend(&mut self, bump: &'a Bump, iter: impl IntoIterator<Item = T>) {
for value in iter {
self.push(bump, value);
}
}
Copy link
Copy Markdown
Member

@bgw bgw Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can, we should implement extend_from_slice instead, and use memcopy to implement that.

This is also not ideal as-implemented because it could lead to multiple reallocs, when there should only be one.

Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs Outdated
Comment thread turbopack/crates/turbopack-ecmascript/src/analyzer/bump_vec.rs
Comment on lines +126 to +131
impl<T> Drop for BumpVec<'_, T> {
fn drop(&mut self) {
// SAFETY: `0..len` is initialized; drop each element (the arena only frees memory).
unsafe { ptr::drop_in_place(&mut **self) }
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential follow up: See if we can eliminate reference-counted types in JsValue and develop a unit test that checks that std::mem::needs_drop::<JsValue>() == false.

@sampoder sampoder requested a review from bgw June 3, 2026 08:42
Comment on lines +34 to +35
/// Binds the arena lifetime and signals ownership/variance over `T` to the compiler.
_marker: PhantomData<&'a mut [T]>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Binds the arena lifetime and signals ownership/variance over `T` to the compiler.
_marker: PhantomData<&'a mut [T]>,
/// Binds the arena lifetime and signals ownership/variance over `T` to the compiler.
_marker: PhantomData<&'a ()>,

/// Collect `iter` into a growable [`BumpVec`].
pub fn from_iter_in(bump: &'a Bump, iter: impl IntoIterator<Item = T>) -> Self {
let iter = iter.into_iter();
let mut vec = Self::with_capacity_in(bump, iter.size_hint().0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe prefer the upper bound if it exists

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants