mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
v16.3.2
755 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
228df5fd74 |
[backport] Retain fewer stale cache versions and use a TTL, plus the mtime fallback (#97304)
Backports the following to `next-16-3`, as a pair: - #96941 — "[turbopack] Retain fewer stale cache versions and use a TTL" (`ae1e53a`) - #97268 — "Add the mtime fallback back" (`52a3410`) The second fixes a bug in the first: #96941 changes the `CURRENT` file format from a bare big-endian `u32` to a JSON object, and an existing cache directory in the old format could no longer be aged. #97268 adds the mtime fallback so an unparsable `CURRENT` is aged by its mtime instead of failing. They only make sense together, hence one branch. Both cherry-picked with `-x`, no conflicts. Every touched source file is byte-identical to canary. ### Verification on this branch - `cargo check`, `cargo clippy --all-targets`, `cargo fmt --check` on `turbo-persistence` and `turbo-tasks-backend` — clean - `cargo test -p turbo-persistence -p turbo-tasks-backend` — all pass, 0 failures - includes the `db_versioning` tests, notably `test_unparsable_current_falls_back_to_mtime::case_1_old_u32_format` (the old-format compatibility case #97268 restores) - `cargo metadata --locked` — `Cargo.lock` consistent with the manifests, no lockfile update needed - `pnpm --filter=next types`, prettier, eslint — clean - `pnpm build-all` — 18/18 tasks - `test/production/next-server-nft` — 5/5 tests, 5/5 snapshots pass (covers the traced `turbopack-cache-seed.js`) ### End-to-end verification against a real old-format cache `create-next-app@16.3.0` + `next build` (with `turbopackFileSystemCacheForBuild`) on released `next@16.3.0` writes a `CURRENT` in the old pre-JSON format — a bare big-endian `u32` (`0000 0008`), 30M cache. This branch was then applied with `pnpm pack-next -p <app> -- --release` (release binary confirmed to contain `TURBO_ENGINE_VERSION_TTL_DAYS` and the new `CURRENT` field names), and two arms were run from an identical starting state — same cache, same code, only the env var differing: | Arm | Build | Cache dirs after | | --- | --- | --- | | `TURBO_ENGINE_VERSION_TTL_DAYS=0` | exit 0 | old dir **deleted**, 1 remains | | default TTL (3 days) | exit 0 | old dir **retained**, intact at 30M, 2 remain | The retention arm is the meaningful control: without #97268's fallback, `read_current_version` would error on the bare-`u32` file, the age would become `Duration::MAX`, and the directory would be evicted in *both* arms. Retention under the default TTL is direct evidence the mtime fallback works. Also confirmed: - new `CURRENT` is written as JSON: `{"max_sequence_number":8,"commit_time":"..."}` - retention cap holds: seeded 4 version dirs → build left exactly 2 (current + one most-recently-used other) - clean deletion, no `DELETION_PREFIX` leftovers in any run - `TURBO_ENGINE_VERSION_TTL_DAYS=notanumber` warns (`expected a whole number of days`) and falls back to the 3-day default rather than failing <!-- NEXT_JS_LLM --> |
||
|
|
6b3677887a |
[backport] [turbopack] Add turbopack_ecmascript and turbopack_wasm's embeded FS to internal_assets_conditions (#96655)
Backports #96600 ([turbopack] Add `turbopack_ecmascript` and `turbopack_wasm`'s embeded FS to `internal_assets_conditions`) to `next-16-3`. <!-- NEXT_JS_LLM --> Co-authored-by: Sam Poder <sampoder@berkeley.edu> |
||
|
|
8cfa551041 |
Turbopack: extract chunk list version/update into turbopack-ecmascript (#95661)
This moves modules out of `turbopack-browser` and into `turbopack-ecmascript` so it can be used in the server hmr implementation as well. Move the chunk-list version, update, and merged-update wire types out of `turbopack-browser` into a shared `turbopack-ecmascript::chunk_list` module so both the browser and node chunking contexts can build on them. Rename `EcmascriptDevChunkListVersion` to `ChunkListVersion` and make `update_chunk_list` runtime-agnostic (plain path->`VersionedContent` maps). Pure refactor: no behavior change. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3214020b2d |
Turbopack: Fix missing canonicalization of paths and always use verbatim paths internally for Windows (#95668)
I was seeing failures on https://github.com/vercel/next.js/pull/95628 and went down this rabbit hole. - We were (incorrectly) not canonicalizing the `DiskFileSystem` root dir. This happened to work out for us because pnpm also wasn't canonicalizing junction point targets, and so the root dir prefix matched. However after `#95628`, pnpm started canonicalizing junction point targets because we added `pnpm-workspace.yaml` files. This was just coincidence and indicative of much deeper problems. - `dunce` isn't a good fit for us. It picks a win32 or verbatim representation based on path length, which would mean that long paths inside of a short root path would never work because we'd never be able to strip the prefix, since we'd be looking for a win32 path prefix. Omnipath is better for what we want to do: https://docs.rs/omnipath/latest/omnipath/windows/trait.WinPathExt.html - The windows verbatim path format works with long paths (>260 characters) and behaves a lot closer to cross-platform unix paths, which is what we want. E.g. if somebody tries to import a JS module from a Windows 8.3 short path, it should fail. This will fail with verbatim paths (good!). This PR *always* uses the verbatim path format internally within `DiskFileSystem`. - `read_link` was re-inventing `try_from_sys_path` but badly. Just call `try_from_sys_path`. - `turbopack/crates/turbo-tasks-fs/src/embed/file.rs` was dead code. - `validate_path_length_inner` was incorrectly referring to verbatim paths as UNC paths. These are two different things. - Canonicalize any absolute symlink targets outside of the `DiskFileSystem` root path, resolving any symlinks they may have, normalizing case-insensitive paths, and handling Windows 8.3 short name format. ## Some Context About Windows Path Formats: - win32 paths: These are your normal user-friendly `C:\foo\blah` paths. When calling most filesystem APIs, these are limited to 260 characters unless the user has enabled long paths on the system, and avoiding the limit with these paths also requires a [`longPathAware` application manifest](https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#longPathAware). You can mix `/` and `\` in win32 paths, and Windows will normalize the path separator for you. - verbatim paths: This is a rust-specific name (used by the stdlib) for the "extended length" prefixed paths. These are paths starting with `\\?\`. These paths can be up to ~32,767 characters long. They do not support Windows 8.3 shortnames. They do not normalize between `/` and `\`, and do not support `.` or `..` components, however [`Path::join`](https://doc.rust-lang.org/std/path/struct.Path.html#method.join) will do this normalization if given a verbatim path. - Windows 8.3 shortnames: A legacy format where paths can be represented by 8 characters, followed by a tilde and a digit (or hash in some extreme cases where there are many collisions), followed by a 3 character file extension. NTFS supports this, [ReFS](https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview) (used by [Windows Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/)) does not. - UNC ("Universal Naming Convention"): This is a specific representation of paths used for remote files on network servers. - `file://` URIs: These must be formed from [win32 style paths](https://docs.rs/url/latest/url/struct.Url.html#method.from_file_path), though the normal 260 character limit typically does not apply here. - canonicalize: Corresponds to [`std::fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html). This involves actual fs operations (that we can't easily track), and recursively resolves symlinks. It will fail if the file path does not exist. On windows, this always returns a verbatim path. - wide format: Windows paths are utf-16. Generally we don't support paths that can't be encoded to utf-8, but we should try to fail as obviously as we can in these cases. We don't have good filesystem issue handling yet though. - case sensitivity: Generally paths are not case-sensitive on Windows or macOS, but are case-sensitive on most Linux filesystems. The exact definition of how case sensitivity works with unicode paths is highly platform and filesystem dependent. - junction points: The windows equivalent of symlinks (symlinks are also supported, but require developer mode to be turned on). These can contain an arbitrary path reference in any format (win32, verbatim, etc) with any case and with windows 8.3 short names. Junction points are always target absolute paths to directories. |
||
|
|
aaf2fe3d58 |
[turbopack] Optimize the implementation of AutoMap/AutoSet
(#95694) Optimize the implementation of AutoMap/AutoSet. Previously the `List` variant was backed by a `SmallVec` in inline mode, which needs at least 24 bytes of header (`len`, `cap`, `ptr` — all `usize`) even though the list never holds more than 32 elements. This replaces it with the `TinyVec` struct from turbo-tasks-backend which is now enhanced with support for an `inline` array. The length is a `NonZeroU8`, which reserves a niche the `AutoMap` enum folds its discriminant into — dropping `AutoMap`'s minimum size to 16 bytes. This in turn shrinks `TaskStorage` and the `LazyField` enum. Some inline structs in TaskStorage are increased in size so we keep the 128 byte footprint |
||
|
|
d774f7db29 |
Upgrade to swc 73 (#95731)
- [x] Waiting for a swc major bump in https://github.com/swc-project/plugins... |
||
|
|
cd24304296 |
Turbopack: add more context to persistence file errors (#95318)
### What? Adds path- and operation-level context to the filesystem errors raised by the `turbo-persistence` commit/rollback flow and the `turbo-tasks-backend` database invalidation/versioning helpers. ### Why? A user reported a bare `Access is denied. (OS error 5)` on Windows 11 that surfaced out of the cache `commit` flow with no indication of which file or which filesystem operation failed: https://github.com/vercel/next.js/discussions/95130#discussioncomment-17479429 The underlying issue was fixed in #95495 but this adds more information for future issues. Raw `io::Error`s from `?` carry an OS error code but no path, so there was no way to tell whether the failure was on `CURRENT`, a `.del` file, a per-file `fsync`, the directory `fsync`, or a cleanup `remove_file` etc. ### How? - Adopted the `fs_err` crate for all fs operations in the persistence layer - Added additional context to some flows - Unified `CURRENT` file writing and adopted an 'atomic write' pattern. <!-- NEXT_JS_LLM_PR --> |
||
|
|
3e11c728e5 |
Upgrade to swc 72 and rust-react-compiler (#95536)
Fixes #95257 Fixes #95372 |
||
|
|
1b1cf7b4b9 |
Fix #95015 by pulling a fix to scattered-collect (#95098)
Bump link-section to pick up https://github.com/mmastrac/linktime/pull/481 This fixes a bug in linktime that could trigger a miscompilation on windows, see https://github.com/mmastrac/linktime/issues/479 for a detailed description @bgw and I both confirmed that this fixed the windows crash, though we have failed to produce a reproduction. This appears to not be triggering in CI due to a different release profile. |
||
|
|
2d1d5268fb |
Upgrade to swc 70 (#94814)
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Signed commits - This repository requires verified commit signatures on protected branches. - If this pull request is blocked for unsigned commits, re-sign the commits and force-push the branch. - A `Signed-off-by` line in the commit message is not enough. ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --------- Co-authored-by: Will Binns-Smith <wbinnssmith@gmail.com> |
||
|
|
723c06200d |
switch our test benchmark runs to release-with-assertions (#94538)
In ci when testing our benchmarks use our test profile `release-with-assertions` This avoids an expensive `lto` step and ensures that our tests run with debug asserts. I noticed that our workflow for 'test cargo benches' was very slow and a lot of that time was the build presumably due to lto overheads across so many benchmark binaries. Compare: * cargo benches for this pr: https://github.com/vercel/next.js/runs/79974366863?pr=94538 - build=4m42s - test=1m46s * cargo benches before this pr: https://github.com/vercel/next.js/actions/runs/27097792020/job/79973082715#logs - build=8m23s - test=6m56s Of course doing this revealed a few things * a birthday paradox panic in one of the persistence tests * some 'top level read' errors to address in the benchmarks and in the turbopack-cli server used for some benchmarks. And finally, that the persistence tests are just extremely slow in test mode due to their initialization overhead (populating >1GB dbs), so that harness is rewritten to remove some variants and also to reduce max sizes in test mode |
||
|
|
77f02f8583 |
Turbopack: add experimental React compiler support (#94573)
- Adds experimental.rustReactCompiler config option to the next.js config to opt in. It should be used alongside `config.reactCompiler` and errors if not set. - Only runs the compiler on client code (and SSR), not RSC - When enabled, runs the compiler directly on Turbopack's swc AST with no gen+reparse. This required passing through the original source text into source transforms. |
||
|
|
f2ba54b4cf |
migrate turbo-tasks to scattered collect (#94503)
# What This uses `scatter_collect` from @mmastrac to register all values, traits and value impls of traits. now all data can be consistently accessed when constructing the VALUEs registry, eliminating a number of racy access opportunities. # Why Scatter collect uses link sections to gather registries at link time, this resolve some inherent brittleness in constructor functions since the relative execution order is **not guaranteed** and it is also not even possible to _detect_ execution that is too early (early access to an `inventory` collection will miss some entries silently) We have a single report that appears to be due to a race like this ``` web:dev: thread 'tokio-rt-worker' (41574444) panicked at turbopack/crates/turbo-tasks/src/macro_helpers.rs:274:13: web:dev: no trait impl registered for value type turbopack_core::module_graph::ModuleGraphImportTracer web:dev: note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` I do not entirely understand how this is possible since constructors should complete prior to the first napi entry point (other than `napi::module_init` which is itself a constructor). Our initialization logic is fundamentally brittle since it relies on * an `inventory` of all value types * an `inventory `of all trait types * a set of ctor functions to register trait implementations (basically (value, trait) pairs) All of these need to be initialized/invoked before the `VALUES` registry is accessed, but the above panic implies that the ctor function for `ModuleGraphImportTracer` to register its implementation of `ImportTracer` hasn't occurred yet. By moving the registies to link time we completely eliminate all startup races since the linker does all the 'gathering', now the only possible concern is that we attempt to downcast a trait prior the the `VALUES` registry completing which does not appear to be possible. # Binary size impact Measured on `next-swc.darwin-arm64.node` built with `pnpm --filter @next/swc build-native-release` (release profile), this branch vs `canary`. Stripped = `strip -x`; gzip = `gzip -9` of the stripped binary. | Metric | branch | canary | Δ | Δ% | |---|---:|---:|---:|---:| | **raw** | 122,879,888 B | 124,184,976 B | **−1,305,088 B** | **−1.051%** | | **stripped** | 83,505,264 B | 84,127,232 B | **−621,968 B** | **−0.739%** | | **gzip+stripped** | 28,800,547 B | 28,925,679 B | **−125,132 B** | **−0.433%** | The addon shrinks on every metric. Replacing the generated per-impl registration functions (and their `ctor`/`inventory` machinery) with link-time scattered collection removes real code/data — ~622 KB even after stripping — and laying the registries out contiguously at link time compresses better, so the gzip win holds up too. <!-- NEXT_JS_LLM_PR --> |
||
|
|
a683d06fbb |
[tubopack] migrate rcstr! to use scattered collect (#94498)
### What? Adopt `[scattered_collect`](https://docs.rs/scattered-collect/latest/scattered_collect/) for collecting static rcstr values. ### Why? Built by our good friend @mmastrac to solve some of our problems. Replaces inventory with a simpler linker driver approach. Basically inventory works using constructor functions to build a linked list, then iterating follows the list. `scattered_collect` on the otherhand uses a linker section for the data, so it all gets moved to a dedicated part of the binary and bingo, a slice!. This means we don't need `init` time logic. This is preparation for migrating other uses of `inventory` just to prove it out. Which was a good idea because we found and fixed a bug! See https://github.com/mmastrac/linktime/releases/tag/link-section-0.18.2 ## Wasm scatter_collect supports wasm, but requires us to register a 'section reader' function with the runtime, see https://docs.rs/link-section/latest/link_section/#wasm. The instructions are staightforward but a little tricky since we rely on `wasm-bindgen` to constrct the `Wasm.Module` instance and it is encapsulated. So to 'inject' that function we would need to patch some of the bootstrapping. Given that this registry is only an optimization for deserialization which never happens in a wasm build i just flagged it all off. |
||
|
|
409fa64d16 |
[turbopack] Use an arena for JSValues (#94297)
This PR takes the arena introduced in https://github.com/vercel/next.js/pull/94296 and uses it! It's large but shouldn't change functionality beyond the use of the arena. Some syntax related to pattern-matching had to change because `bumpalo`'s `Box` doesn't support the same pattern matching that the standard library's `Box` does but the code should be equivalent. `bumpalo`'s `Bump` type implements `Send` but not `Sync`, to allow us to pass a `Bump` into async-methods we wrap it in `ThreadLocal` (see https://docs.rs/thread_local/latest/thread_local/). `ThreadLocal` implements `Sync` by creating a new bumpalo allocator for each thread. We've tried to minimise the usage of `ThreadLocal` to just where it was needed. The downside of `ThreadLocal` is that we may find ourselves allocating memory for multiple arenas instead of just one. This is more time allocating if there are multiple threads compared to one shared allocation, however, it still appears to be a performance improvement over individually allocating memory for each `JSValue` --------- Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com> |
||
|
|
804b9a0bd4 |
[turbopack] Enable Effects to be evicted (#94173)
### What?
Enable Effects to be evicted.
Currently `EffectInstance` transitively holds a
`ReadRef<PersistentFileContent>` which means eviction cannot not free
the memory for the outputs. Because the `ReadRefs` are captured by the
task producing the `Effect` we cannot just drop them (otherwise we could
need to somehow re-execute the write to recover them). So instead we
need a new 'effect processing phase'
Here is our new state machine:
* Phase 1: `emit` effects.
- At this point we capture the content as a `ResolvedVc` and the hash as
a value.
- In principle this means the hash and content can get out of sync, but
only within an eventual consistency race which callers should be
handling.
* Phase 2: `take_effects`.
- This will call `capture()` on all the effects which will conditionally
read the content cell if the hash doesn't match the `EffectStateStorage`
* Phase 3: `Effects::apply`.
- This will apply the effects for all effects whose hashes don't match
storage.
- At this point we may discover that a hash in storage changed between
`capture` and `apply` which will trigger a special `Retry` error and
`invalidate` `Phase 2`.
To facilitate the `Retry` logic a
`read_strongly_consistent_and_apply_effects` helper function handles it.
Which allows for a simple circuit breaker and some verbose
warnings/errors when we retry too many times.
Specifically:
- Split the `Effect` trait into `Effect` (emit-side, returns a
`Captured` payload) and `CapturedEffect` (apply-side). `
- Switch `Effects` to `cell = "new"`. Every producer re-execution
allocates a fresh cell value; the prior cell (and its captured payload)
drops on overwrite, cascade-releasing upstream
`ReadRef<PersistedFileContent>` strong counts. The captured payload also
drops naturally when the cell is evicted.
- Push the per-key state machine (`Unapplied` / `InProgress` / `Applied
{ value_hash, result }`, `EventGuard` panic recovery, `Event` listener
coordination) down into `EffectStateStorage::run_apply`.
`Effects::apply` becomes a thin aggregator over `dyn_apply()` calls.
- New `ApplyOutcome<E> { Failed(E), Retry }`. `CapturedEffect::apply`
returns this; `Retry` signals "capture elided content but storage
diverged before apply" — `Effects::apply` collects across the batch,
fires the producer's invalidator once, and surfaces
`EffectsError::Retry`.
- New `EffectStateStorage::matches_applied(key, hash)`.
`WriteEffect::capture` / `WriteLinkEffect::capture` consult this and
skip the `ReadRef<PersistedFileContent>` / `ReadRef<LinkContent>`
materialization when storage already holds `Applied { matching, Ok(())
}` — avoiding the disk-read + decompression hit on producer re-runs that
don't actually change output.
- 10 integration tests in
`turbopack/crates/turbo-tasks-backend/tests/effects.rs` cover the new
lifecycle: duplicate apply, sibling stomp re-apply, repeated apply with
unchanged state dedupe, `cell = "new"` producing distinct cells per
producer run, capture-skip on storage match, and the
capture-skip-then-stomp race that exercises the `Retry` path.
### Why?
A memory audit showed `EffectInstance` cells accumulating the majority
of the ram in an eviction session
#### Capture-time storage check (the perf story)
`Effect::capture` may consult `EffectStateStorage::matches_applied` and
elide the content materialization when storage already records the
target hash. `CapturedWriteEffect.content` becomes
`Option<ReadRef<PersistedFileContent>>` — `None` means "capture observed
`Applied { matching }`; no `body` to pass to `run_apply`". The
apply-side state machine then either dedup-hits (storage still matches →
cached `Ok`) or enters the no-body branch and returns
`ApplyOutcome::Retry`.
Reading shared mutable `EffectStateStorage` from inside a turbo-tasks
task is normally suspect (the state isn't a tracked input). It is sound
here because the apply-time re-check fires the producer's invalidator on
mismatch, turning the otherwise-untracked read into an
explicitly-tracked one via the `Retry` pathway. The producer reruns,
`capture` sees the new storage state, materializes content, and the next
`apply` succeeds.
#### `ApplyOutcome::Retry` and the invalidator
`Effects::apply` collects `Retry` signals across the parallel batch via
`AtomicBool`. `Failed(e)` errors fail-fast. After the batch, if any
`Retry` fired (and no `Failed`), the producer's invalidator runs once
and `EffectsError::Retry` propagates. Callers re-read the operation; the
producer reruns; fresh `capture` either materializes content (storage
diverged) or dedup-hits cleanly.
#### `turbo-tasks-fs`
`WriteEffect::capture` / `WriteLinkEffect::capture` call
`matches_applied` first and conditionally `.await?` the content
`ResolvedVc`. `CapturedWriteEffect::apply` /
`CapturedWriteLinkEffect::apply` dispatch through `run_apply(key, hash,
body)`, where `body` is `Some(closure)` iff content is `Some`.
`apply_inner` takes the unwrapped `&ReadRef<…>` as a parameter so the
option-unwrap is at the dispatch boundary (type-enforced).
### Risk: infinite retry loop
**Failure mode.** Two sibling producers A and B contend on the same key
with different hashes (e.g. two routes that both want to write
`chunks/foo.js` with different content). Each `Retry` from A's apply
invalidates A's producer; each `Retry` from B's apply invalidates B's
producer. In the worst case, A and B perpetually re-capture, race to
apply, stomp each other's storage state, and re-trigger Retry —
invalidating themselves on each cycle.
**Why we believe it terminates in practice.** A `Retry` only fires when
(a) capture observed `Applied { matching }` and elided content, and (b)
by apply time storage was stomped to a different hash. Each `Retry`
invalidates only its own producer (no cross-producer invalidation), so
without external input churn the system reaches a fixed point —
whichever producer applies last wins, and subsequent re-runs of the
loser's producer materialize content (because storage no longer matches)
and apply cleanly. The pathological case requires a real, ongoing race
fundamentally triggered by external invalidations (file edits).
<!-- NEXT_JS_LLM_PR -->
|
||
|
|
a0dd232358 |
Simplify turbo-tasks-backend: collapse single-impl traits and remove redundant Arc layers (#93983)
### What? Simplifies `turbo-tasks-backend` by removing abstraction layers that each had a single implementation, plus several redundant `Arc` indirections that were no longer load-bearing. **Traits removed / collapsed (each had exactly one implementor):** - **`KeyValueDatabase`** — folded into inherent methods on its only impl, `TurboKeyValueDatabase`. - **`BackingStorage` + `BackingStorageSealed`** — folded into inherent methods on the renamed concrete type `TurboBackingStorage` (was the `KeyValueDatabaseBackingStorage<T>` generic + a type alias). - **`ConcurrentWriteBatch`** — folded into inherent methods on `TurboWriteBatch` (it existed to support an old backend implementation that's gone). - The **`B: BackingStorage` generic parameter** is dropped from `TurboTasksBackend`, `TurboTasksBackendInner`, `ExecuteContextImpl`, and `ChildExecuteContextImpl`. Every instantiation in the workspace already resolved to one concrete type. The only remaining trait in the crate's public role is `Backend` (defined in `turbo-tasks`), which stays — removing it would invert the `turbo-tasks` → backend dependency. **`Arc` indirections removed:** - `TurboKeyValueDatabase.db: Arc<TurboPersistence>` → `TurboPersistence` (the write batch only borrowed it; the clone in `new` was gratuitous). - The `self: &Arc<Self>` receivers and `self.clone()` calls in `run_backend_job`, `idle_start`, and `try_read_task_output` — the deferred/background work reaches the backend through the pinned `turbo_tasks` handle instead, whose `Arc` already keeps the backend alive. - With no remaining sharer, the backend's own `Arc<TurboTasksBackendInner>` and the `TurboTasksBackend` newtype were collapsed: the inner fields now live directly in `TurboTasksBackend`, stored inline in `TurboTasks` (which already shares it via its own `Arc<TurboTasks>`). **Other cleanups surfaced by the above:** - `noop_backing_storage()` is now an empty, read-only instance of the real `TurboPersistence` instead of a separate `NoopKvDb` impl — same concrete type as `turbo_backing_storage()`, so the `Either<…>` wrappers in napi and turbopack-cli go away. - `TurboTasksBackend::backing_storage()` accessor replaced with a focused `invalidate_storage(reason_code)` method, so the backend no longer leaks its storage object. - Dead code removed (`CellDependency::key`, `ExecuteContext::schedule` / `suspending_requested`, the non-stats `execute_with_stats` variant) and feature/test-only items gated behind their corresponding `cfg`. ### Why? The no-op storage work removed the last thing forcing these abstractions to be generic/dynamic, so the single-impl traits and the extra `Arc` layers were pure indirection. This is a self-contained simplification: behavior is unchanged. ### Tradeoff - One `Either`/match in every backing-storage method becomes one `is_empty` atomic load (`Relaxed` `AtomicBool`) in `should_restore` — essentially equivalent. - The backend is now stored inline in `TurboTasks` rather than behind a pointer: one fewer indirection on every backend access, at the cost of a larger `TurboTasks` struct. <!-- NEXT_JS_LLM_PR --> |
||
|
|
c97ac7e54b |
[turbopack] Add BumpVec (vector implementation that takes a Bump) (#94296)
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: https://github.com/vercel/next.js/pull/94297. |
||
|
|
87ee4fe431 |
Turbopack: refactor NFT to add Endpoint.traced_files (#94224)
Refactor the module-graph bit of NFT so that we can provide `Endpoint.traced_files()` which is needed for the bundle analyzer There should be no logic change whatsoever |
||
|
|
11c865c1d6 |
Update ctor from 0.10 to 1.0.6 (#94045)
### What?
Updates the `ctor` crate dependency from `0.10` to `1.0.6` in the
workspace and migrates the two call sites to the new API.
### Why?
We've been running into a rust-analyzer issue with `ctor` 0.10 that the
newer release looks like it should fix. While bumping, the major version
jump also brings in a number of stability and safety improvements
(post-libc-init default priority, required `unsafe` marker, declarative
re-export form, expanded platform support).
### How?
`ctor` 1.x has several breaking changes that affect us:
1. **`#[ctor]` now requires an explicit `unsafe` marker.** Both call
sites were updated:
- `turbopack/crates/turbo-tasks-macros-tests/tests/trybuild.rs`:
`#[ctor::ctor]` → `#[ctor::ctor(unsafe)]`. The function body already
wraps `std::env::remove_var` in `unsafe`, so no further change was
needed.
- `turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs`: the
proc-macro emits a vtable registration ctor for every
`#[turbo_tasks::value_impl]`. Migrated to the new declarative form (see
next bullet).
2. **`crate_path = ...` is deprecated in favor of
`ctor::declarative::ctor!`.** The ctor 1.0.6 docs explicitly recommend
the declarative macro for crates that re-export ctor (`turbo-tasks`
re-exports it via `macro_helpers::ctor` so user crates don't need a
direct dep). The emission in `value_impl_macro.rs` was rewritten from
```rust
#[turbo_tasks::macro_helpers::ctor::ctor(
crate_path = turbo_tasks::macro_helpers::ctor,
)]
#[allow(non_snake_case)]
fn #vtable_register_ident() { ... }
```
to
```rust
turbo_tasks::macro_helpers::ctor::declarative::ctor! {
#[ctor(unsafe)]
#[allow(non_snake_case)]
fn #vtable_register_ident() { ... }
}
```
3. **Default priority changed.** In 0.x the default priority was `0`
(run before C library init); in 1.x the default is `default` (≈500,
after libc init). We don't specify a priority, and the registration
ctors only push into a `Vec` guarded by a registry — they don't touch
libc — so the later timing is safe and we accept the new default. The
lazy `VTableRegistry::finalize` step (driven by the `VALUES` `LazyLock`)
is unchanged, so visible ordering semantics for downstream code are
preserved.
4. **Other 1.x breaking changes verified as non-issues for this repo:**
- `dtor` was split into its own crate — not used here.
- `priority = naked` syntax removed — not used.
- MSRV bumps: base is 1.60, only the optional `priority` feature
requires 1.85 — we don't enable `priority`.
- `used_linker` / `no_warn_on_missing_unsafe` were replaced by `--cfg
linktime_*` flags — neither was used here.
#### Verification
- `cargo check` / `cargo clippy` clean across `turbo-tasks`,
`turbo-tasks-backend`, `turbo-tasks-macros`, `turbo-tasks-macros-tests`,
and `turbopack-tests`.
- `cargo test -p turbo-tasks-backend`: all tests pass (58 in the largest
suite, 0 failures across all integration tests, 0 doc-test failures).
- `cargo test -p turbopack-tests`: 218 + 87 tests pass, 0 failures, 1
ignored.
#### Lockfile changes
`Cargo.lock` reflects the new dep graph: `ctor` jumps to `1.0.6`,
`link-section` to `0.17.2`, a new `linktime-proc-macro` is pulled in,
and the old `ctor-proc-macro` / `dtor` / `dtor-proc-macro` entries are
dropped (they were transitive deps of `ctor` 0.10).
<!-- NEXT_JS_LLM_PR -->
Co-authored-by: v-work-app[bot] <262237222+v-work-app[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
|
||
|
|
5ee7005200 |
Turbopack: use module graph for NFT (#92901)
- [x] skip traced edges properly in all graph traversals
- [x] iter_nodes and enumerate_nodes include the traced modules
- [x] Don't propagate is_self_async inside of traced references as a perf optimization
- [x] fix performance
- [x] validate that NFT JSON files are identical
- [x] ~~validate/adjust bundle analyzer output~~ will be in followup
- [x] audit `fn chunking_type()` changes
Benchmark:
```
*
|
||
|
|
1095b9ebd5 |
Fix short circuit evaluation of AND / OR in the Turbopack analyzer for string & nullish-related methods (#94159)
Here is the original description of the bug:
```
1. shortcircuit_if_known predicates are swapped for Logical(And) and
Logical(Or) (lines 2541–2548, 2573–2582, 2634–2643)
is_nullish, is_string, and is_empty_string all do this for AND/OR:
LogicalOperator::And => shortcircuit_if_known(list, JsValue::is_truthy, …),
LogicalOperator::Or => shortcircuit_if_known(list, JsValue::is_falsy, …),
shortcircuit_if_known returns item_value(item) for the first item where
use_item == Some(true). But:
- a && b returns a when a is falsy, else b → use_item should be is_falsy.
- a || b returns a when a is truthy, else b → use_item should be is_truthy.
Concretely:
- is_nullish(1 && null): code does is_truthy(1) = Some(true) → return
is_nullish(1) = Some(false). JS answer is true. Wrong.
- is_nullish(null || 2): code does is_falsy(null) = Some(true) → return
is_nullish(null) = Some(true). JS answer is false. Wrong.
```
As mentioned, in the issue `is_string`, `is_empty_string`, and
`is_nullish`'s short circuit evaluation of `&&` and `||` was reversed.
This PR reverses them so that for AND expressions, the first false-y
value is evaluated (otherwise the last value is). For OR expressions,
the first truth-y value is evaluated (otherwise the last value is).
I’ve added unit tests to this PR. To verify these unit tests with
JavaScript’s behaviour I used Claude Code to generate this node script:
```javascript
const PREDICATES = {
is_string: (v) => typeof v === 'string',
is_empty_string: (v) => v === '',
is_nullish: (v) => v == null,
is_not_nullish: (v) => v != null,
}
const cases = [
{
test: 'is_string_constant',
rust: `assert_eq!(value.is_string(), Some(true));`,
expr: `'hello'`,
predicate: 'is_string',
expected: true,
},
{
test: 'is_string_and_short_circuit',
rust: `assert_eq!(eval("'hello' && 2").is_string(), Some(false));`,
expr: `'hello' && 2`,
predicate: 'is_string',
expected: false,
},
{
test: 'is_string_and_short_circuit',
rust: `assert_eq!(eval("2 && 1 && 'hello'").is_string(), Some(true));`,
expr: `2 && 1 && 'hello'`,
predicate: 'is_string',
expected: true,
},
{
test: 'is_string_or_short_circuit',
rust: `assert_eq!(eval("'hello' || 'bye' || 2").is_string(), Some(true));`,
expr: `'hello' || 'bye' || 2`,
predicate: 'is_string',
expected: true,
},
{
test: 'is_string_or_short_circuit',
rust: `assert_eq!(eval("'hello' || 2 || 1 || 'bye'").is_string(), Some(true));`,
expr: `'hello' || 2 || 1 || 'bye'`,
predicate: 'is_string',
expected: true,
},
{
test: 'is_string_or_short_circuit',
rust: `assert_eq!(eval("2 || 1 || 'hello' || 'bye'").is_string(), Some(false));`,
expr: `2 || 1 || 'hello' || 'bye'`,
predicate: 'is_string',
expected: false,
},
{
test: 'is_empty_string_and_short_circuit',
rust: `assert_eq!(eval("'' && 'string'").is_empty_string(), Some(true));`,
expr: `'' && 'string'`,
predicate: 'is_empty_string',
expected: true,
},
{
test: 'is_empty_string_and_short_circuit',
rust: `assert_eq!(eval("false && ''").is_empty_string(), Some(false));`,
expr: `false && ''`,
predicate: 'is_empty_string',
expected: false,
},
{
test: 'is_empty_string_and_short_circuit',
rust: `assert_eq!(eval("0 && false && ''").is_empty_string(), Some(false));`,
expr: `0 && false && ''`,
predicate: 'is_empty_string',
expected: false,
},
{
test: 'is_empty_string_or_short_circuit',
rust: `assert_eq!(eval("'' || 'string'").is_empty_string(), Some(false));`,
expr: `'' || 'string'`,
predicate: 'is_empty_string',
expected: false,
},
{
test: 'is_empty_string_or_short_circuit',
rust: `assert_eq!(eval("false || ''").is_empty_string(), Some(true));`,
expr: `false || ''`,
predicate: 'is_empty_string',
expected: true,
},
{
test: 'is_empty_string_or_short_circuit',
rust: `assert_eq!(eval("0 || false || ''").is_empty_string(), Some(true));`,
expr: `0 || false || ''`,
predicate: 'is_empty_string',
expected: true,
},
{
test: 'is_nullish_and_short_circuit',
rust: `assert_eq!(eval("'' && null").is_nullish(), Some(false));`,
expr: `'' && null`,
predicate: 'is_nullish',
expected: false,
},
{
test: 'is_nullish_and_short_circuit',
rust: `assert_eq!(eval("null && ''").is_nullish(), Some(true));`,
expr: `null && ''`,
predicate: 'is_nullish',
expected: true,
},
{
test: 'is_nullish_and_short_circuit',
rust: `assert_eq!(eval("0 && null && ''").is_nullish(), Some(false));`,
expr: `0 && null && ''`,
predicate: 'is_nullish',
expected: false,
},
{
test: 'is_nullish_and_short_circuit',
rust: `assert_eq!(eval("null && 1 && 2").is_nullish(), Some(true));`,
expr: `null && 1 && 2`,
predicate: 'is_nullish',
expected: true,
},
{
test: 'is_nullish_or_short_circuit',
rust: `assert_eq!(eval("'' || null").is_nullish(), Some(true));`,
expr: `'' || null`,
predicate: 'is_nullish',
expected: true,
},
{
test: 'is_nullish_or_short_circuit',
rust: `assert_eq!(eval("null || ''").is_nullish(), Some(false));`,
expr: `null || ''`,
predicate: 'is_nullish',
expected: false,
},
{
test: 'is_nullish_or_short_circuit',
rust: `assert_eq!(eval("0 || '' || null").is_nullish(), Some(true));`,
expr: `0 || '' || null`,
predicate: 'is_nullish',
expected: true,
},
{
test: 'is_nullish_or_short_circuit',
rust: `assert_eq!(eval("null || 1 || 2").is_nullish(), Some(false));`,
expr: `null || 1 || 2`,
predicate: 'is_nullish',
expected: false,
},
{
test: 'is_not_nullish_and_short_circuit',
rust: `assert_eq!(eval("'' && null").is_not_nullish(), Some(true));`,
expr: `'' && null`,
predicate: 'is_not_nullish',
expected: true,
},
{
test: 'is_not_nullish_and_short_circuit',
rust: `assert_eq!(eval("null && ''").is_not_nullish(), Some(false));`,
expr: `null && ''`,
predicate: 'is_not_nullish',
expected: false,
},
{
test: 'is_not_nullish_and_short_circuit',
rust: `assert_eq!(eval("0 && null && ''").is_not_nullish(), Some(true));`,
expr: `0 && null && ''`,
predicate: 'is_not_nullish',
expected: true,
},
{
test: 'is_not_nullish_and_short_circuit',
rust: `assert_eq!(eval("null && 1 && 2").is_not_nullish(), Some(false));`,
expr: `null && 1 && 2`,
predicate: 'is_not_nullish',
expected: false,
},
{
test: 'is_not_nullish_or_short_circuit',
rust: `assert_eq!(eval("'' || null").is_not_nullish(), Some(false));`,
expr: `'' || null`,
predicate: 'is_not_nullish',
expected: false,
},
{
test: 'is_not_nullish_or_short_circuit',
rust: `assert_eq!(eval("null || ''").is_not_nullish(), Some(true));`,
expr: `null || ''`,
predicate: 'is_not_nullish',
expected: true,
},
{
test: 'is_not_nullish_or_short_circuit',
rust: `assert_eq!(eval("0 || '' || null").is_not_nullish(), Some(false));`,
expr: `0 || '' || null`,
predicate: 'is_not_nullish',
expected: false,
},
{
test: 'is_not_nullish_or_short_circuit',
rust: `assert_eq!(eval("null || 1 || 2").is_not_nullish(), Some(true));`,
expr: `null || 1 || 2`,
predicate: 'is_not_nullish',
expected: true,
},
]
let passed = 0
let failed = 0
let currentTest = null
for (const c of cases) {
if (c.test !== currentTest) {
console.log(`\n#[test] fn ${c.test}()`)
currentTest = c.test
}
console.log(` ${c.rust}`)
const result = (0, eval)(c.expr)
const actual = PREDICATES[c.predicate](result)
const ok = actual === c.expected
const status = ok ? 'PASS' : 'FAIL'
console.log(
` -> JS result = ${JSON.stringify(result)}, ${c.predicate} = ${actual}, expected = ${c.expected} [${status}]`
)
if (ok) passed++
else failed++
}
console.log(`\n${passed} passed, ${failed} failed`)
```
After running it on my machine:
```
28 passed, 0 failed
```
---------
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <lukesandberg@users.noreply.github.com>
|
||
|
|
21d47a456a |
Enable mimalloc in benchmarks (#93388)
this brings them closer to production |
||
|
|
a9323da293 |
Stop pinning compiled chunk source on EcmascriptBuildNodeChunkVersion.
(#93807) ## What Stop pinning compiled chunk source on `EcmascriptBuildNodeChunkVersion`. The struct previously held a `Vec<ReadRef<CodeAndIds>>` for every module in the chunk, even though the HMR update path only needs the bytes for the *changed* subset and the bytes are already on disk. That field was also forcing the upstream `CodeAndIds` / `BatchGroupCodeAndIds` tasks to stay `serialization = "skip"`, so warm restarts had to re-walk every module and re-hash its source to rebuild `entries_hashes`. This change mirrors the browser-side pattern: a new `EcmascriptBuildNodeChunkContentEntries` task lives on the chunk content and holds `ResolvedVc<Code>` + `ResolvedVc<u64>` per module. The version struct shrinks to `{ chunk_path, minify_type, entries_hashes }`, drops `serialization = "skip"`, and switches `chunk_path` from `String` to `RcStr`. `update_ecmascript_node_chunk_content` now resolves entries lazily, only when an added or modified module actually needs its code shipped. ## Why Two wins, both for dev sessions starting against a warm filesystem cache: - **Memory.** The version no longer transitively pins every module's compiled `Rope` in heap — those bytes can stay on disk until HMR actually needs them. - **Warm-restart CPU.** `entries_hashes` is sourced from the per-module `Code::source_code_hash()` task (already cached) and the version itself now round-trips through the persistent cache, so we don't re-hash anything on warm start. The HMR payload shape is unchanged. ## Perf This should speed up warm builds a bit but the major benefit is not recomputing node outputs and keeping them in ram measuring v0 after loading the main route Branch: Cold: 12.3G Warm: 7.34G Canary: Cold 12.3G Warm: 8.5G The trace file confirms the recomputations are gone and the heap measurements confirm we trimming ~1.1g of ram Using the devlow benchmarks i was able to confirm a possible small progression ``` # canary chat dev startup build=warm: root page = 23.96 s (from root page/start) chat dev startup build=warm: root page = 21.21 s (from root page/start) chat dev startup build=warm: root page = 22.70 s (from root page/start) # branch chat dev startup build=warm: root page = 20.94 s (from root page/start) chat dev startup build=warm: root page = 19.43 s (from root page/start) chat dev startup build=warm: root page = 23.49 s (from root page/start) ``` ## Tests I added a new integration test to ensure we don't accidentally regress here. Which confirms that 'clean warm builds' run nothing and 'clean warm dev sessions' just set up HMR session infra <!-- NEXT_JS_LLM_PR --> |
||
|
|
d81d5ab7df |
Proof of concept: task eviction after snapshot for turbo-tasks-backend (#91790)
> **Note:** This is a **proof of concept** implementation. It is not yet ready for production use. ## Summary Implements memory eviction for the turbo-tasks engine. After a persistence snapshot completes, tasks that are safe to remove are evicted from in-memory storage and transparently restored from disk on next access. ### Eviction levels - **Full eviction**: Entire task removed from the in-memory map (restored from disk on access). Only possible when the task has no meaningful transient state (and other state is already on disk) - **DataAndMeta eviction**: Both data and meta categories cleared, but the task stays in the map to preserve transient state (e.g. `current_session_clean`, aggregated session-clean counts). - **DataOnly eviction**: Only data-category fields cleared; meta (graph structure, output, dirty state) stays in memory. - **MetaOnly eviction**: Only meta-category fields cleared; data stays in memory. Data and meta evictability are computed independently — if one category is modified but the other is clean, the clean category can still be dropped. Eviction is gated behind `BackendOptions::evict_after_snapshot` (off by default), and can be enabled in Next.js via the `TURBO_ENGINE_EVICT_AFTER_SNAPSHOT=1` env var for testing. ## Key changes - **Orthogonal eviction decision tree** (`storage_schema.rs`): Data and meta evictability are computed independently. Full eviction additionally requires no meaningful transient state (session-clean flags, aggregated session-clean counts). Replaces the previous sequential bail-out approach which was too aggressive on full eviction (losing transient session state on leaf tasks) and not aggressive enough on partial eviction (blocking all eviction when only one category was modified). - **`drop_partial()` codegen** (`task_storage_macro.rs`): New generated methods to drop data - **`restore_from_*()` codegen changes** (`task_storage_macro.rs`): New semantics for merging persistent data from the backend with transient data stored in memory. - **`task_cache` moved into `Storage`** (`storage.rs`): The `CachedTaskType → TaskId` deduplication map was previously a separate field on `TurboTasksBackendInner`. It is now owned by `Storage` so eviction can remove entries when a task is fully evicted. Because `task_cache` is a pure performance cache (entries are re-populated by `task_by_type()` on miss once the task type is persisted to backing storage), evicting entries is safe. After bulk eviction the map is shrunk when it is less than half full. - **Parallel shard eviction** (`storage.rs`): Eviction iterates all storage shards in parallel after snapshot, applying the appropriate eviction level per task. Each shard is shrunk after bulk eviction to reclaim slack capacity. - In principle this is O(N) work to scan, but because each pass drops >98% of tasks there isn't wasted work and the logic is fast, taking <100ms for even the largest applications. ## Design notes - **SessionDependent tasks**: SessionDependent tasks can still be evicted but if `current_session_clean` is set we prevent full eviction to avoid rechecking. Within a session the file-watchers are responsible for invalidations after setting `current_session_clean`. ## Known limitations (proof of concept) - No LRU or access-frequency tracking — all eligible tasks are evicted on every snapshot cycle - No memory pressure feedback — eviction runs on a timer, not in response to actual memory pressure - Only runs after snapshotting which tends to be a high point in memory - Future work will explore interleaving this logic with snapshotting to trim the peak <!-- NEXT_JS_LLM_PR --> |
||
|
|
ee39f8f47f |
turbo-rcstr: replace rcstr! macro_rules with proc macro (#93551)
### What? Replaces the `rcstr!` macro in `turbo-rcstr` with a proc-macro implementation in a new `turbo-rcstr-macros` crate. The proc macro inspects the literal at expansion time and emits only the relevant arm — no dead `else` branch. ### Why? The previous `macro_rules!` macro emitted both an inline arm and a static + `inventory::submit!` arm at every call site, expecting `const` evaluation to discard one. `inventory::submit!` adds `#[used]` to its constructor, so the dead arm shipped in the binary even when const eval determined the live arm was the inline one. Across the workspace there are ~852 inlinable `rcstr!` literals (≤ 7 bytes); each previously emitted a wasted `PrehashedString` static, an inventory node, and a startup `ctor`. ### How? A `#[proc_macro] rcstr` in the new `turbo-rcstr-macros` crate decides at expansion time: - inlinable literal → `inline_atom(lit).unwrap()`. No static, no inventory entry, no dead branch. - non-inlinable literal → static + `inventory::submit!` + `from_static`, no inline arm. - constant identifiers / `concat!(…)` / non-literal inputs → fall back to the original both-branches expansion so const eval picks the arm. ~11 such call sites. `turbo-rcstr`'s `atom_size_64` / `atom_size_128` features forward to the proc-macro crate so the threshold is exact for every build configuration without a runtime const branch. The proc macro deliberately avoids `syn`, `quote`, and `proc-macro2`. It pattern-matches on `proc_macro::TokenTree` directly, asks the compiler for the literal's unescaped value via `Literal::str_value` (gated on the unstable `proc_macro_value` feature), and emits the chosen expansion by parsing a string template via `TokenStream::from_str`. The proc-macro crate compiles in ~0.7 s with zero third-party dependencies. ### Compile time Clean rebuild of `turbopack-core` in release mode (143 `rcstr!` call sites). Dependencies cached; only `turbopack-core` itself rebuilt each iteration. `darwin-aarch64`. | | n | min | mean | median | stdev | | ------------------ | ---:| --------:| --------:| --------:| --------:| | canary | 7 | 27.11 s | 28.00 s | 28.12 s | 0.61 s | | `rcstr_inventory` | 7 | 26.60 s | 27.99 s | 27.96 s | 0.80 s | | **delta** | | | **−0.01 s (−0.06 %)** | −0.16 s | | Within noise. The proc-macro per-invocation overhead is offset by reduced emitted-token volume (~40 % less code across all `rcstr!` sites — the 852 inlinable literals collapse from ~80 tokens to ~6 tokens each), so the compiler does materially less parsing and type-checking on the macro output. ### Binary size Release build of `next-napi-bindings` cdylib on `darwin-aarch64`: | | canary | `rcstr_inventory` | delta | | ---------------------------- | ------------------------------- | ------------------------------- | ------------------------------ | | Release `.dylib` (raw) | 123,699,312 B (117.97 MiB) | 123,349,424 B (117.64 MiB) | **−349,888 B (−0.28 %)** | | Stripped (`strip -x`) | 83,502,264 B (79.63 MiB) | 83,434,984 B (79.57 MiB) | **−67,280 B (−0.08 %)** | | Stripped + `gzip -9` | 28,967,762 B (27.63 MiB) | 28,958,198 B (27.62 MiB) | **−9,564 B (−0.03 %)** | The raw release build shrinks by ~342 KiB — the 852 dead `static PrehashedString` + inventory node + ctor entries no longer being emitted for inlinable call sites. Most of that compresses well (dead data is stripped and gzip handles the rest), so the user-facing impact in the gzipped npm tarball is ~9 KiB. <!-- NEXT_JS_LLM_PR --> |
||
|
|
dccb95c3b1 |
Turbopack: Cache effect errors, replace the write_lock Mutex with a lazily created Notify instance (#93476)
This is a mostly-by-hand refactor of some of the caching logic in https://github.com/vercel/next.js/pull/92300 - **Cache errors.** We often end up applying effects multiple times in frequent succession, and many filesystem effects already have their own backoff/retry loops, which can make them very slow when they fail, so it seems generally better to cache the failures. - **Add `EffectLastApplied` enum:** It's a matter of opinion, but I think the `EffectLastApplied` enum makes it easier to reason about the different states in the system. `Option` was difficult to reason about, and it would've been even more confiusing if it was an `Option<Result<(), Arc<dyn EffectError>>`. - `tokio::sync::Mutex<()>` is overkill here, `tokio::sync::Notify` is a simpler (and smaller) primitive that does the core of what we needed the `Mutex` for. We could also use `EventListener` here, but I didn't. (`EventListener` is really just smol's version of `Notify`) - We can lazily allocate the `Notify` instance inside of `last_applied`. We have to grab this lock anyways to check for the cached fast-path. If we're not in the fast-path, we can re-use the lock guard to create and store the `Notify` instance. - **Perf:** The old code's slow path had to always lock `last_applied` three times. Now, if there's no concurrent writer (common case), it only locks twice: Once before application, and once after. - **Perf:** Use a smallvec when computing unique indices to avoid a bunch of heap allocations. Unless there's a bug, we don't expect any of the keys to have more than one value (returns an error). - **Perf:** Store keys as `Box<[u8]>` instead of `Vec<u8>`, saving 8 bytes per key. Currently, if `effect.dyn_apply()` panics, we just do our best to mark the effect as unapplied to avoid a deadlock. This could alternatively poison the object. ## Memory Usage Memory usage can increase if we're storing errors (heap allocated, stored in `Arc`) we previously wouldn't have, but if we assume that errors are uncommon, this refactor decreases the size of the data structures involved. Before this change, eagerly allocating a `tokio::sync::Mutex` was costing 40 bytes per effect, making the entire `EffectEntryState` struct 64 bytes: <img src="https://app.graphite.com/user-attachments/assets/989ac229-9e2f-4370-8e32-b14e10e09248.png" width=500> <img src="https://app.graphite.com/user-attachments/assets/f2e14416-17c3-4961-8c00-fa7a81846fa0.png" width=500> After the changes, the entire `EffectLastApplied` enum is 40 bytes <img src="https://app.graphite.com/user-attachments/assets/18e4fc0a-e7f8-4fad-b9bc-c22ba3eefb6c.png" width=500> After wrapping the `EffectLastApplied` enum in a `parking_lot::Mutex`, it comes in at 48 bytes (after alignment), 16 bytes smaller than the old struct. |
||
|
|
8f77276c71 |
Upgrade to swc 65 (#93325)
https://github.com/swc-project/swc/compare/a0716503cc2b359ff6620d0ae5c0e8afaa18c185...b815eb24190dc54443486ccee161a419e247826b Closes https://github.com/vercel/next.js/issues/93353 |
||
|
|
26afa89200 |
Replace Lazy<FxHashMap> static lookups with phf::Map (#93096)
## What Trivial change: LazyLock<FxHashMap<...>> -> phf::Map. |
||
|
|
908717af2d |
Drop once_cell from all direct crate deps (#93095)
## What Purely mechanical change from once_cell to stdlib primitives. --------- Co-authored-by: Luke Sandberg <lukesandberg@users.noreply.github.com> |
||
|
|
6eb0126dc0 |
Turbopack: delete unused turbo-static crate (#93324)
Added in https://github.com/vercel/turborepo/pull/8037 Nobody uses this, and it's also the only crate that uses the lsp-server dependency. |
||
|
|
292a3fba32 |
Report OS memory pressure in TurboMalloc and trace samples (#93333)
### What? Adds a new `TurboMalloc::memory_pressure()` method that returns a normalized OS-level memory pressure value in the range `0..=100` as `Option<u8>`. That value is attached to every memory sample in the tracing layer (`TraceRow::MemorySample`) and propagated through the trace-server so that span queries return a `memory_pressure_samples` vector next to the existing `memory_samples`. ### Why? Our current tracing only records the in-process allocator usage (`TurboMalloc::memory_usage()`), which does not tell us when the *operating system* is actually under memory pressure. We want that signal in the trace output to: 1. Surface real OS memory pressure in trace dashboards alongside our own allocation totals. 2. Eventually use it as input to task-eviction decisions in `turbo-tasks` (see branch description). This PR lands the plumbing; the eviction heuristic is not part of this change. ### How? **`TurboMalloc::memory_pressure() -> Option<u8>`** — new, in `turbopack/crates/turbo-tasks-malloc/`. Values are normalized so that `0` = no pressure, `100` = maximum pressure. Platform-specific backends: | Platform | Source | Notes | |---|---|---| | Linux | `/proc/pressure/memory` (`some` `avg10`), fallback to `(MemTotal - MemAvailable) / MemTotal` from `/proc/meminfo` | PSI is not available on all kernels (< 4.20, without `CONFIG_PSI`, restricted containers). The meminfo fallback keeps the signal meaningful on any standard Linux system and matches the semantics of Windows' `dwMemoryLoad`. | | macOS | `kern.memorystatus_level` sysctl (% free memory) | Pressure = `100 - level`, read via `libc::sysctlbyname`. | | Windows | `MEMORYSTATUSEX::dwMemoryLoad` via `GlobalMemoryStatusEx` (`windows-sys`) | Already a 0–100 percentage of physical memory in use. | | Other / wasm | — | Returns `None`. | All runtime failures (missing file, sysctl error, failed API call, unparseable content) silently yield `None` rather than panicking. **Wiring into tracing:** - `TraceRow::MemorySample` gains a `memory_pressure: u8` field. `0` is used when `memory_pressure()` returns `None` on unsupported platforms. - `RawTraceLayer::maybe_report_memory_sample` populates it on every sample (sampling cadence unchanged). - This is a breaking change to the postcard wire format of `MemorySample`; old trace files cannot be read by the new `turbopack-trace-server`. Given the dev-only nature of this data that seemed acceptable — let me know if a migration is desired. **Wiring into the trace-server:** - `Store::memory_samples` is now `Vec<(Timestamp, u64, u8)>` and `add_memory_sample(ts, memory, memory_pressure)`. - A new `Store::memory_pressure_samples_for_range(start, end) -> Vec<u8>` mirrors `memory_samples_for_range`: same `MAX_MEMORY_SAMPLES = 200` cap and same group-and-max downsampling, so both vectors align index-by-index for a given span query. - `ServerToClientMessage::QueryResult` gains a `memory_pressure_samples: Vec<u8>` field next to `memory_samples`. **Dependencies:** `libc` (macOS only, `cfg`-gated), `windows-sys` with the `Win32_System_SystemInformation` feature (Windows only, `cfg`-gated). No new deps on Linux. ### Verification - `cargo build -p turbo-tasks-malloc -p turbopack-trace-utils -p turbopack-trace-server` - `cargo clippy -p turbo-tasks-malloc -p turbopack-trace-utils -p turbopack-trace-server --all-targets -- -D warnings` - `cargo test -p turbo-tasks-malloc` — 7 tests pass, including: - `memory_pressure_is_in_range`: asserts `Some(_)` and `≤ 100` on Linux, macOS and Windows (via `cfg`-gated `.expect()`), and allows `None` elsewhere. - Parser tests for both PSI and `/proc/meminfo` code paths (typical content, malformed input, clamping). - Runtime sanity check on the Linux sandbox: `/proc/pressure/memory` is absent (kernel 5.10 without `CONFIG_PSI`); the `/proc/meminfo` fallback returned `Some(3)` as expected. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e1bb911c14 |
turbo-tasks: precompute trait-impl vtables at compile time (#93092)
## What Remove another lazy lock from trait vtables. --------- Co-authored-by: Luke Sandberg <lukesandberg@users.noreply.github.com> |
||
|
|
99bbaa00c4 |
[rust-deps] Update tokio (#93195)
## What? Updates the tokio and tokio-util dependencies to their latest versions: - `tokio`: `~1.47.3` → `1.52.1` - `tokio-util`: `0.7.13` → `0.7.18` ## Why? Keep Rust dependencies up-to-date to benefit from bug fixes, performance improvements, and security patches. ## How? Updated version constraints in `Cargo.toml` and regenerated `Cargo.lock`. <!-- NEXT_JS_LLM_PR --> |
||
|
|
8dee7acb85 |
[turbopack-trace-server] optimize loading (#93264)
Land a few optimizations to the trace server
* Change `SpanEvent` so it is 32 bytes instead of 40 bytes by triggering a `niche` optimization
* Change `args` and `events` to be a `smallvec` with inline size 1
* for `args` it is size <=1 ~31% of the time
* for `events` it is size <=1 69% of the time
* Compute min/max timestamps in a single pass instead of 2 when inserting into the selftimetree
* Bundle dynamically computed 'total' fields behind a single OnceLock
* saves 40 bytes per span due to `Oncelock` overheads
* Inline SpanTimeData and SpanNames into Span
* We get little benefit from deferring the allocations and by inlining we save time and improve memory locality.
* post load SpanTimeData is allocated for 94% of spans, but after loading `trace.nextjs.org` it is 100%
* post load SpanNames is allocated for 0% of spans, but after loading it is 96.2% of spans
* Remove the `inner` `OnceLocks` from `SpanNames` we can just allocate these all together
Measuring with one 10gb trace file I see loading times progress from 75.7s (33G of ram) to 60.5s (19.5G of ram). With loading times hitting >200mb/s occasionally
|
||
|
|
44722b808c |
Turbopack: import.meta.glob docs + edge case support (#92729)
## What?
Adds documentation and TypeScript types for `import.meta.glob` in
Turbopack, and implements additional edge cases for Vite compatibility.
### Documentation
- New `## import.meta.glob` section in the [Turbopack reference
page](/docs/app/api-reference/turbopack) covering: lazy/eager loading,
named imports, query strings (including the object form), multiple
patterns, negation, TypeScript types, and a full options table.
- `import.meta.glob` row added to the Module Resolution feature table.
- Migration note added to the [from-Vite
guide](/docs/app/guides/migrating/from-vite) explaining that
`import.meta.glob` works out of the box with Turbopack, with
before/after for the deprecated `as` → `query` migration.
### TypeScript types
- Added `ImportMetaGlobOptions` interface and overloaded `glob()`
signatures to `ImportMeta` in `packages/next/types/global.d.ts`.
- The overloads return `Record<string, unknown>` when `eager: true` is
passed and `Record<string, () => Promise<unknown>>` otherwise.
### Implementation (edge cases)
- **`import: '*'`** (namespace import): Treated the same as omitting the
`import` option (returns the whole module namespace), matching Vite
semantics. Previously would have generated broken `module["*"]` access.
- **`query` as object literal**: `{ query: { bar: 'foo', raw: true } }`
is now supported. Keys and values are URL-encoded and joined into a
query string (`?bar=foo&raw=true`).
- **Stricter option validation**: A non-object-literal second argument
(e.g. `import.meta.glob('./*.js', 'eager')`) is now a compile-time error
instead of a warning, since the options cannot be safely defaulted.
### Tests
- Execution tests for new features: namespace `import: '*'`, query
object, combining query with negation, explicit dotfile patterns.
- New `import-meta-glob-errors` execution test with issue snapshots
covering: too many arguments, non-string pattern, and non-object options
argument.
## Why?
1. The feature shipped in #92640 but had no user-facing documentation
and no TypeScript types.
2. Several Vite-compatible edge cases were not handled: namespace
imports (`import: '*'`) and query objects. These gaps would cause subtle
behavioral differences for users migrating from Vite.
3. Option validation was too lenient — invalid second arguments fell
through to defaults silently.
## How?
### Docs & types
- `docs/01-app/03-api-reference/08-turbopack.mdx` — new section, table
row, TypeScript subsection
- `docs/01-app/02-guides/migrating/from-vite.mdx` — migration note
- `packages/next/types/global.d.ts` — `ImportMetaGlobOptions` +
`ImportMeta.glob()` overloads
### Implementation
-
`turbopack/crates/turbopack-ecmascript/src/references/import_meta_glob.rs`:
- `parse_import_meta_glob()`: `import: '*'` normalized to `None`; query
object parsing via `urlencoding::encode()`; non-object options argument
now emits an error and returns `None`.
- `turbopack/crates/turbopack-ecmascript/Cargo.toml`: added
`urlencoding` workspace dep.
Note: dotfile/dot-directory filtering is not needed in
`import.meta.glob` itself — the underlying `Glob` pattern engine and
`read_glob_internal` already handle dotfile semantics correctly
(explicit `./.foo/*.js` matches, wildcards include them).
### Tests
-
`turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob/`
— new cases for `import: '*'`, query object, query + negation, explicit
dotfile pattern, wildcard including dotfiles
-
`turbopack-tests/tests/execution/turbopack/resolving/import-meta-glob-errors/`
— new test suite for fatal parse errors with issue snapshots
### Checklist
- [x] `pnpm prettier-fix` / `cargo fmt` run
- [x] Documentation follows the [docs contribution
guide](https://nextjs.org/docs/community/contribution-guide)
- [x] Execution tests pass (`cargo test -p turbopack-tests --test
execution -- "import_meta_glob"`)
- [x] `cargo clippy` clean
- [x] `pnpm --filter=next types` passes
<!-- NEXT_JS_LLM_PR -->
---------
Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
e7bcba89dc |
[rust-deps] Update bytes, crc32fast, memchr, pin-project-lite, quote, rand, regex, ringmap, roaring (#93197)
### What? Updates Rust dependencies to their latest versions: | Crate | From | To | |-------|------|-----| | bytes | 1.1.0 | 1.11.1 | | crc32fast | 1.4.2 | 1.5.0 | | memchr | 2.7.4 | 2.8.0 | | pin-project-lite | 0.2.9 | 0.2.17 | | quote | 1.0.23 | 1.0.45 | | rand | 0.10.0 | 0.10.1 | | regex | 1.10.6 | 1.12.3 | | ringmap | 0.2.3 | 0.2.5 | | roaring | 0.10.10 | 0.11.4 | ### Why? Keep dependencies up to date with latest bug fixes, performance improvements, and security patches. ### How? Version bumps in `Cargo.toml` with corresponding `Cargo.lock` updates. <!-- NEXT_JS_LLM_PR --> |
||
|
|
5d3232fb2b |
[rust-deps] Update tracing (#93194)
### What? Updates the `tracing` crate and its related dependencies to their latest versions: - `tracing`: 0.1.37 → 0.1.44 - `tracing-attributes`: 0.1.30 → 0.1.31 - `tracing-core`: 0.1.34 → 0.1.36 ### Why? Keep Rust dependencies up to date with security patches and performance improvements. ### How? Updated the version specification in `Cargo.toml` and regenerated `Cargo.lock`. <!-- NEXT_JS_LLM_PR --> |
||
|
|
1e4782ad7b |
[rust-deps] Update serde (#93196)
### What? Updates the serde family of crates to their latest versions: - `serde`: 1.0.217 → 1.0.228 - `serde_json`: 1.0.138 → 1.0.149 - `serde_path_to_error`: 0.1.16 → 0.1.20 - `serde_qs`: 0.13.0 → 1.1.1 - `serde_with`: 3.12.0 → 3.18.0 ### Why? Keep dependencies up to date with bug fixes and performance improvements. ### How? Updated version constraints in `Cargo.toml` and ran `cargo update` to regenerate the lockfile. <!-- NEXT_JS_LLM_PR --> |
||
|
|
c916c80b2f |
turbo-tasks: drop once_cell, const-init TRAIT_METHOD, ctor WORKER_TASKS (#93090)
## What Clean up the Lazy-locks we re-export or use internally in the turbo-tasks crates, migrating to `std::sync::LazyLock` / `std::cell::LazyCell` where still needed and eliminating a couple of them where they weren't. ## Why One less dep, using standard Rust features, a smaller number of Lazy items. ## Details - **Re-exports:** drop unused `macro_helpers::OnceCell` and the now-unused `macro_helpers::phf` (since `TraitType` no longer stores a `phf::Map`). Replace `once_cell::sync::Lazy` with `std::sync::LazyLock` in `macro_helpers::TRAIT_CAST_FNS` and in the two macro-generated call sites. - **`once_cell::sync::Lazy` → `std::sync::LazyLock`** in `scope.rs`, `registry/mod.rs`, and `turbo-tasks-backend-fuzz`'s `graph.rs`. - **`once_cell::unsync::Lazy` → `std::cell::LazyCell`** in `update_cell.rs`. - **`TRAIT_METHOD` → plain const-init static:** `TraitType::methods` changes from `phf::Map<&'static str, TraitMethod>` to `&'static [TraitMethod]` (parallel to `method_names`), and `TraitType::get` becomes a `const fn` that does `index_of_name` + array indexing. The `value_trait` macro now emits a slice literal instead of `phf_map!`, and the `function` macro emits `static TRAIT_METHOD: &'static TraitMethod = TRAIT.get(stringify!(..));` — evaluated at const-eval time rather than behind a `LazyLock<&TraitMethod>`. - **Dropped `once_cell` and `phf` Cargo deps** from `turbo-tasks`, and `once_cell` from `turbo-tasks-backend` and its fuzz crate. |
||
|
|
e2e8ba9aca |
turbo-trace-server: reduce allocation overhead when loading large trace files (#92896)
### What? Reduces allocation overhead in `turbo-trace-server` when reading large trace files. Measured improvement: **114 MB/s → 156 MB/s** (~37% throughput increase) on a 16 GB trace with 46 million spans. ### Why? Profiling showed the reader thread spending significant time in heap allocation and deallocation during the initial bulk-load phase. The hot spots were: 1. `to_string()` / `into_owned()` in `process_internal_row_queue` allocating a new `String` for every span name, category, and arg key/value — even when the same string (e.g. `"turbo_tasks::function"`) appeared in millions of spans. 2. `Vec<SelfTimeEntry>::push` growing one-at-a-time, triggering many `realloc` calls. 3. `OnceLock<Box<SpanTimeData>>` — inherent to the lazy-init design; mitigated by the allocator switch. 4. `Vec<TraceRow>::push` in the deserialization loop re-growing during the initial read. 5. `process_internal_row` allocating a fresh `Vec` queue on every call (one per row). 6. `Vec<Span>::push` and hash map rehashing in `Store::add_span`. 7. `into_static()` cloning forcing borrowed `Cow` strings to heap for queued rows. ### How? **1. mimalloc as global allocator (`main.rs`, `Cargo.toml`)** Added `turbo-tasks-malloc` as a dependency with a default-on `custom_allocator` feature (matching the pattern in `turbopack-cli` and `turbopack-nft`). Set `#[global_allocator] static ALLOC: TurboMalloc = TurboMalloc`. Also wired `TurboMalloc::thread_stop()` via rayon's `exit_handler` so each worker thread returns its mimalloc thread-local heap to the global pool on exit. **2. `RcStrInterning` in `turbo-rcstr`** New `RcStrInterning` struct backed by `FxHashSet<RcStr>`. Methods: `intern(&str)`, `intern_cow(Cow<str>)`, `intern_display(&Display)`. Strings shorter than the inline threshold (7 bytes on 64-bit) are already zero-allocation inline atoms and skip the set entirely. **3. `String` → `RcStr` for span fields** Changed `Span.name`, `Span.category`, and `Span.args` key/value types from `String`/`Vec<(String, String)>` to `RcStr`/`Vec<(RcStr, RcStr)>`. Same for `SpanNames` and `SpanBottomUpBuilder`. All `to_string()` / `into_owned()` calls in `process_internal_row_queue` are replaced with `interner.intern()` / `intern_cow()` / `intern_display()` calls on a per-`TurbopackFormat` `RcStrInterning`. Repeated names like `"turbo_tasks::function"` now share a single allocation via refcount bump instead of a fresh heap allocation each time. The `into_static()` path for queued rows also benefits: cloning an already-interned `RcStr` is a refcount increment, not a string copy. **4. Capacity hints** Pre-allocated major collections to avoid repeated reallocation during the initial bulk load: - `Store::spans`: `Vec::with_capacity(131_072)` - `TurbopackFormat::id_mapping`: `with_capacity(131_072)` - `TurbopackFormat::outdated_spans`: `with_capacity(8_192)` - `TurbopackFormat::queued_rows`: `with_capacity(1_024)` - `TurbopackFormat::thread_stacks`, `thread_allocation_counters`: `with_capacity(64)` - `TurbopackFormat::self_time_started`: `with_capacity(256)` **5. Reused queue Vec (hot spot #8)** `TurbopackFormat` now holds `row_queue: Vec<InternalRow<'static>>` as a field. `process_internal_row` takes it via `take` and puts it back after the loop, retaining the allocated capacity across calls instead of allocating a new `Vec::new()` for every row processed. **6. `TraceFormat::create_reused()` hook (hot spot #4)** Added an overridable `create_reused()` method to the `TraceFormat` trait. `TurbopackFormat` overrides it to return `Vec::with_capacity(4_096)`, giving the row deserialization buffer reasonable initial capacity to avoid repeated doubling during the initial bulk read. **7. `SelfTimeTree` leaf capacity (hot spot #2)** New child nodes created at split time get `Vec::with_capacity(SPLIT_COUNT / 2)` = 64 entries, matching the expected post-split occupancy and avoiding incremental doubling after every split. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
31a1b635b4 |
turbo-tasks: Reduce allocations on cache hits (#92756)
### What? Reduce heap allocations when turbo-tasks functions get cache hits (~85% of calls). ### Why? Every turbo-tasks function call (generated by `#[turbo_tasks::function]`) was boxing its arguments into `Box<dyn MagicAny>` before looking up the task cache. This allocation is wasted on cache hits, which are the overwhelmingly common case. ### How? **Deferred boxing via `StackMagicAny` trait object:** Introduce a `StackMagicAny` trait that abstracts over a stack-resident `Option<T>`: - `as_ref(&self) -> &dyn MagicAny` — borrow the argument for hash/equality (cache lookup) - `take_box(&mut self) -> Box<dyn MagicAny>` — move the value to the heap (zero clones) - `as_any_mut(&mut self) -> &mut dyn Any` — downcast to concrete type without boxing The data flow: 1. **Callsite** (macro-generated): creates `StackMagicAnySlot::new((args...))` on the stack, calls `dynamic_call(..., &mut arg)` 2. **`dynamic_call`**: checks resolution via `arg.as_ref()`, routes to `native_call` (resolved) or boxes via `arg.take_box()` for `LocalTaskSpec` (unresolved) 3. **Backend `get_or_create_task_inner`**: does a read-only `raw_get` lookup using `hash_from_components` + `eq_components` with the borrowed `&dyn MagicAny`. On cache hit (~85%), returns immediately — **zero heap allocation**. On cache miss, re-checks under write lock using the same borrowed reference, and only calls `arg.take_box()` in the vacant entry case (true cache miss). Boxing is now deferred past all of these: - **Memory cache hit** — the common case, no allocation at all - **Backing storage hit** — found in persistent storage, no allocation needed - **Lost race under write lock** — another thread inserted while we upgraded; we use their task_id, still no allocation - **Trait method dispatch (no filtering)** — `filter_owned` is now `Option<FilterOwnedArgsFunctor>`; when `None` (the common case where all args are used), the original `&mut dyn StackMagicAny` passes straight through to `dynamic_call` without boxing **Optimized `filter_owned` for traits:** When trait methods *do* need argument filtering (unused `_`-prefixed parameters), the old path did `take_box()` → `downcast_args_owned()` → dereference → repack. This is an unnecessary heap round-trip. The new `downcast_stack_args_owned()` function uses `as_any_mut()` to downcast directly to `&mut StackMagicAnySlot<T>` and calls `take()` on the inner `Option`, skipping the intermediate `Box` entirely. **Additional changes:** - `Backend::get_or_create_*_task` now takes decomposed parameters (`native_fn`, `this`, `&mut dyn StackMagicAny`) instead of a pre-constructed `CachedTaskType` - Persistent and transient task creation merged into a shared `get_or_create_task_inner(transient: bool)` - `connect_child` uses eagerly-set `persistent_task_type` from `initialize_new_task` - `OwnedMagicAny` adapter wraps already-boxed args (from async resolution tasks) to fit the `StackMagicAny` interface - Both `dynamic_call` and `trait_call` take `&mut dyn StackMagicAny` (trait dispatch also benefits) - `CachedTaskType::hash_encode` now delegates to `hash_encode_components` (deduplicated) - Removed `try_native_call`, `native_call_if_consistent`, `try_get_or_create_*` — the deferred boxing approach subsumes these ### Binary size Binary size is neutral (linux-x86_64, `--release`, stripped + gzipped: 30.9 MB on both canary and this branch). ### Overhead benchmark (turbo-tasks-backend, median, lower is better) Measured on an isolated Firecracker microVM (linux-x86_64). Variance is nontrivial on this environment, but the direction is consistently positive across all turbo-tasks benchmarks. | Benchmark | Canary | This PR | Delta | |---|---|---|---| | `turbo-cached-same-keys/1` | 512.6 ns | 490.2 ns | -4.4% | | `turbo-cached-same-keys/10` | 493.4 ns | 483.8 ns | -2.0% | | `turbo-cached-same-keys/100` | 502.1 ns | 484.9 ns | -3.4% | | `turbo-cached-same-keys/1000` | 1.31 µs | 774.2 ns | -41.0% | | `turbo-cached-different-keys/1` | 1.02 µs | 1.00 µs | -1.4% | | `turbo-cached-different-keys/10` | 1.13 µs | 1.11 µs | -1.8% | | `turbo-cached-different-keys/100` | 1.29 µs | 1.24 µs | -3.8% | | `turbo-cached-different-keys/1000` | 2.37 µs | 2.32 µs | -2.5% | | `turbo-uncached/1` | 23.43 µs | 20.47 µs | -12.6% | | `turbo-uncached/10` | 32.23 µs | 29.71 µs | -7.8% | | `turbo-uncached/100` | 126.01 µs | 124.95 µs | -0.8% | | `turbo-uncached/1000` | 1.079 ms | 1.048 ms | -2.9% | | `turbo-uncached-parallel/1` | 6.16 µs | 5.67 µs | -8.0% | | `turbo-uncached-parallel/10` | 5.47 µs | 5.12 µs | -6.4% | | `turbo-uncached-parallel/100` | 14.65 µs | 14.55 µs | -0.7% | | `turbo-uncached-parallel/1000` | 132.09 µs | 129.13 µs | -2.2% | <!-- NEXT_JS_LLM_PR --> |
||
|
|
8a2c5e017e | Turbopack: Use FrozenMap for module export information (#92802) | ||
|
|
b9cd44892c |
Add TurboMalloc::thread_park() to flush and collect on thread park (#92804)
### What? Adds `TurboMalloc::thread_park()` — a new method on `TurboMalloc` that calls `libmimalloc_sys::mi_collect(false)` to process mimalloc's lazy free lists when a Tokio worker thread parks. Registers it as an `on_thread_park` callback in every Tokio runtime that already registers `on_thread_stop`: - `turbopack-cli/src/main.rs` - `turbopack-cli/benches/small_apps.rs` - `next-napi-bindings/src/lib.rs` - `next-build-test/src/main.rs` - `turbo-tasks-backend/fuzz/src/graph.rs` ### Why? mimalloc uses deferred freeing: memory freed by one thread is not reclaimed immediately but is queued until that thread performs N more allocations. Tokio worker threads that are parked (waiting for work) don't allocate, so deferred frees accumulate indefinitely — memory is never returned to the OS until the thread wakes up and does real work. Calling `mi_collect(false)` on park processes the lazy free lists, allowing mimalloc to reclaim that memory, while avoiding the syscalls and globally synchronized operations that `mi_collect(true)` would trigger. ### How? - `thread_park()` only calls `mi_collect(false)` — it intentionally does **not** call `flush()`. `flush()` transfers the thread-local allocation counter to the global atomic and resets it to zero, which would violate the trace server's assumption that per-thread counters are monotonically increasing. Counter sync remains exclusively in `thread_stop()`. - `thread_park()` is feature-gated behind `cfg(all(feature = "custom_allocator", not(target_family = "wasm")))` and is a no-op otherwise. - `libmimalloc-sys` is added as a direct optional dependency under `[target.'cfg(not(target_family = "wasm"))'.dependencies]` (it was already a transitive dep via `mimalloc`) to access `mi_collect` directly, since `mimalloc`'s `use ffi::*` is not a public re-export. - In each `on_thread_park` callback, `TurboMalloc::thread_park()` is called **after** the existing SWC atom store GC, so atoms freed by that GC pass are also eligible for reclamation in the same collection. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3bbbb32fa9 |
Enable wasm plugins on windows arm (#92544)
Not because we should, but because we can. Previously we disabled this due to an incompatibility between windows arm and wasmer, however we recently switched to wasmtime which appears to work. So simplify the CI configuration and the rust feature flags https://github.com/vercel/next.js/actions/runs/24271405735 is a build-and-deploy.yml run and this is the [windows arm build](https://github.com/vercel/next.js/actions/runs/24271405735/job/70877042113) |
||
|
|
efb699acbb |
chore: Bump reqwest to 0.13.2 (#92713)
Fixes #91653 Running `next dev` with the latest Turbopack fails to fetch fonts on Windows ARM machines, resulting in an `http2 feature is not enabled` error. This issue was fixed in `reqwest` v0.13.2: https://github.com/seanmonstar/reqwest/pull/2927 |
||
|
|
74cdcde87e |
feat(turbopack): add LocalPathOrProjectPath PostCSS config resolution (#91338)
### What? Adds a new `experimental.turbopackLocalPostcssConfig` option and corresponding `PostCssConfigLocation::LocalPathOrProjectPath` variant so Turbopack can resolve `postcss.config.js` starting from the CSS file's directory first, falling back to the project root. Also fixes an issue that surfaces when multiple per-directory `postcss.config.js` files are used: **Evaluate pool asset name collisions** — output file names for the Node.js evaluate pool are now derived from an xxh3 hash of the full module ident rather than the bare file name, so multiple `postcss.config.js` files in different directories no longer collide. ### Why? Currently Turbopack uses `ProjectPathOrLocalPath` which checks the project root first, then falls back to the local directory. This means per-directory `postcss.config.js` files cannot override the root config — the root config always wins. For projects that need different PostCSS transforms in different directories (e.g. a monorepo with multiple apps or style directories), the more specific config should take precedence. ### How? **Turbopack PostCSS resolution** (`turbopack/crates/turbopack-node/src/transforms/postcss.rs`): - Added `PostCssConfigLocation::LocalPathOrProjectPath` enum variant with doc comments on all variants - Refactored `find_config_in_location()` to express the search order as a `Vec<FileSystemPath>` built from a single `match`, making each variant's strategy immediately clear **Evaluate pool asset file naming** (`turbopack/crates/turbopack-node/src/evaluate.rs`): - Replaced file-name-based output path derivation with a deterministic xxh3 hash of the full module ident - Adds `turbo-tasks-hash` dependency to `turbopack-node` - Eliminates name collisions when multiple entries share the same file name (e.g. per-directory `postcss.config.js` files) **Next.js config flag** (`packages/next/src/server/config-shared.ts`, `config-schema.ts`): - Added `experimental.turbopackLocalPostcssConfig: boolean` option (opt-in, default `false`) - Wired through `crates/next-core/src/next_config.rs` → both client and server contexts (`next_client/context.rs`, `next_server/context.rs`) - When `true`, uses `LocalPathOrProjectPath`; when `false`/unset, uses the existing `ProjectPathOrLocalPath` (no behavior change by default) **E2E test** (`test/e2e/app-dir/turbopack-postcss-multiple-configs/`): - Runs in both **Turbopack dev and production modes** — skips webpack only (webpack does not support function-valued PostCSS plugins and the `turbopackLocalPostcssConfig` flag is Turbopack-specific) - 5 style directories, each with its own `postcss.config.js` passing a **unique color option** (`blue`, `purple`, `orange`, `cyan`, `magenta`) to a shared PostCSS plugin, proving that per-directory config resolution produces distinct results rather than all sharing the same transform - Root `postcss.config.js` is a no-op — if only the root config were used, CSS would remain unmodified - Test validates all 15 elements (5 dirs × 3 files) render with CSS module classes - Test collects all CSS (inline `<style>` tags for dev, linked `.css` files for production) and asserts each directory's expected color is present and `color: red` is absent **Documentation** (`docs/01-app/03-api-reference/05-config/01-next-config-js/turbopackLocalPostcssConfig.mdx`, `docs/01-app/03-api-reference/08-turbopack.mdx`): - New dedicated reference page for `experimental.turbopackLocalPostcssConfig` covering usage, behavior table, and a directory-tree example - Added to the experimental options table in the Turbopack API reference ## Test plan - [x] `cargo check -p turbo-tasks-backend` passes - [x] `cargo check -p turbopack-node` and `cargo check -p next-core` pass - [x] E2E test `turbopack-postcss-multiple-configs` validates per-directory PostCSS config resolution across 5 directories with distinct per-directory colors (Turbopack dev and production modes) - [x] Test correctly skips in webpack mode and deployment - [x] Documentation page and experimental options table updated <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> |
||
|
|
35b5582647 |
Remove a ton of turbo task functions from Issue trait items (#92623)
## What? Remove `#[turbo_tasks::function]` from all methods on the `Issue` value_trait, following the same pattern as #92593 which did the same for `ValueDebug`. The `Issue` trait previously had 8 methods decorated with `#[turbo_tasks::function]`, which caused the turbo-tasks machinery to generate `NativeFunction` statics, task registration, vtable entries, and scheduling/caching infrastructure for each method on each of the ~48 `impl Issue for` types in the codebase — most of which are trivially synchronous (returning a constant string, enum variant, or stored struct field). ## How? - Change all 8 `Issue` trait methods to plain methods. Methods that are always synchronous become direct return-type methods (`severity`, `stage`, `source`, `documentation_link`). Methods that are genuinely async on at least one impl use `Pin<Box<dyn Future<Output = Result<T>> + Send + '_>>` for object safety. - Callers that need to invoke trait methods on a `Vc<Box<dyn Issue>>` use `issue.into_trait_ref().await?` to get a `TraitRef<Box<dyn Issue>>` and call methods directly on that. - `PlainIssue::from_issue` (the sole hot caller that materializes all fields) resolves one `trait_ref` and calls all methods on it, similar to the `ValueDebug` pattern. - `ResolvingIssueWithLocation` (which wraps an inner `ResolvedVc<Box<dyn Issue>>`) eagerly copies `severity`, `stage`, `documentation_link`, and `source` at construction time rather than delegating async calls to the inner issue. - Delete `OptionIssueSource` and `OptionStyledString` wrapper types, which existed solely as `Vc<T>` return type wrappers for the old turbo-tasks methods. ## Tradeoffs **Lost per-call caching:** Previously each `Issue` method was a turbo-tasks task, so results were cached per `(Vc<Issue>, method)`. In practice this caching was useless — issues are emitted once and their fields read once by `PlainIssue::from_issue`, so there was never a second call to hit the cache. The only real cost is that `IssueFilter::matches` previously cached per `(filter, issue)` pair; that caching is preserved because `matches` itself remains a `#[turbo_tasks::function]` and now calls `into_trait_ref().await?` once at the top. **`ResolvingIssueWithLocation` eagerly reads inner fields:** Instead of lazily delegating to the inner issue, `stage` and `documentation_link` are copied at construction. This is a minor semantic change (the values are snapshotted rather than live), but in practice these fields are always constant values so it makes no difference. ## Binary size impact (darwin-arm64, compared against #92593) | Metric | #92593 baseline | This PR | Delta | |---|---|---|---| | Unstripped dylib | 124.6 MB | 124.0 MB | **-0.6 MB (-0.5%)** | | Stripped | 84.7 MB | 84.4 MB | **-0.3 MB (-0.4%)** | | Stripped + gzip (npm) | 29.2 MB | 29.1 MB | **-0.1 MB (-0.3%)** | Smaller savings than `ValueDebug` (~2.5 MB stripped) because there are ~48 `Issue` impls vs. one `ValueDebug` impl per every `#[turbo_tasks::value]` type. But real and cumulative. <!-- NEXT_JS_LLM_PR --> |
||
|
|
21933bc80a |
chore(turbopack): upgrade qfiler to v0.3.0-alpha.4 (#92558)
utoopack upgrade to crate dependencies of turbopack, this dependenices will upgrade to `0.3.0-alpha.4` and cause api build error. it seems we can just upgrade it: https://github.com/arthurprs/qfilter/pull/24/changes#diff-b1a35a68f14e696205874893c07fd24fdb88882b47c23cc0e0c80a30c7d53759 --------- Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com> |
||
|
|
0213797790 |
Bump swc to 63.1.1 (#92604)
To pull in https://github.com/swc-project/swc/pull/11777 |