turbopack: Add NEEDS_RESOLVE const to reduce binary size from monomorphization#92686
Draft
lukesandberg wants to merge 3 commits intocanaryfrom
Draft
turbopack: Add NEEDS_RESOLVE const to reduce binary size from monomorphization#92686lukesandberg wants to merge 3 commits intocanaryfrom
lukesandberg wants to merge 3 commits intocanaryfrom
Conversation
Analysis of the next-swc native binary (153MB unstripped, 103MB stripped, 34MB gzipped) using cargo-bloat, nm symbol analysis, and dependency tree analysis. Identifies turbo-tasks monomorphization (21.6 MiB), the Wasmer/Cranelift WASM engine (9.5 MiB), and LightningCSS (8 MiB) as the top size contributors with actionable reduction recommendations. Co-Authored-By: Claude <noreply@anthropic.com>
Deep-dive into per-function and per-value-type code generation overhead from #[turbo_tasks::function] and #[turbo_tasks::value] macros. Identifies ~8.4 MiB of infrastructure glue (12-15% of stripped binary) with concrete refactoring proposals for ~4.5-6.5 MiB savings. Co-Authored-By: Claude <noreply@anthropic.com>
Introduces `const NEEDS_RESOLVE: bool` on the `TaskInput` trait, defaulting to `false`. Only `Vc<T>` sets it to `true`; compound types (tuples, Vec, Option, etc.) OR their elements' values. The `#[derive(TaskInput)]` macro computes this at expansion time via syntactic `Vc<…>` detection to avoid const-eval cycles with recursive types. `ArgMeta` now branches on `T::NEEDS_RESOLVE` to select between the full `resolve_functor_impl<T>` (async Vc resolution state machine) and a new `trivial_clone_resolve<T>` (simple clone). For the ~60% of turbo-tasks functions whose argument tuples contain no `Vc`, this avoids monomorphizing the expensive async resolver, estimated to save ~200-350 KiB of .text. Co-Authored-By: Claude <noreply@anthropic.com>
lukesandberg
commented
Apr 12, 2026
| /// | ||
| /// The default is `false`. Only [`Vc<T>`][Vc] overrides this to `true`. Compound types (e.g. | ||
| /// `Vec<T>`, tuples, derived types) should OR their fields' `NEEDS_RESOLVE` values. | ||
| const NEEDS_RESOLVE: bool = false; |
Contributor
Author
There was a problem hiding this comment.
is false the right default?
Comment on lines
+200
to
+203
| let needs_resolve_impl = if any_field_needs_resolve { | ||
| quote! { true } | ||
| } else { | ||
| quote! { false } |
Contributor
There was a problem hiding this comment.
Suggested change
| let needs_resolve_impl = if any_field_needs_resolve { | |
| quote! { true } | |
| } else { | |
| quote! { false } | |
| // Collect the idents of generic type parameters so we can OR in their NEEDS_RESOLVE. | |
| // This is safe from const-eval cycles because `T::NEEDS_RESOLVE` refers to the concrete | |
| // substitution at monomorphization time, not `Self`. | |
| let generic_param_idents: Vec<_> = generic_params.iter().map(|p| &p.ident).collect(); | |
| let needs_resolve_impl = if any_field_needs_resolve { | |
| quote! { true } | |
| } else { | |
| quote! { false #(|| <#generic_param_idents as turbo_tasks::TaskInput>::NEEDS_RESOLVE)* } |
The TaskInput derive macro always emits NEEDS_RESOLVE = false for generic types, ignoring that generic type parameters could be Vc<T> at monomorphization time.
Merging this PR will not alter performance
Comparing Footnotes
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TaskInput::NEEDS_RESOLVEconst — a compile-time constant on theTaskInputtrait that indicates whether a type may contain unresolvedVcs. OnlyVc<T>sets it totrue; compound types OR their elements' values.trivial_clone_resolve— a simpler resolve functor used whenNEEDS_RESOLVEisfalse, replacing the full asyncresolve_functor_implstate machine for ~60% of turbo-tasks functions whose argument tuples contain noVcvalues.Key findings from analysis
This PR addresses
resolve_functor_impl monomorphization: ~527 of 877 instances contain no Vc arguments and compile to trivially-different async state machines. By branching on the new NEEDS_RESOLVE const at compile time, these instances use trivial_clone_resolve instead — a simpler function body that the compiler can better deduplicate via ICF. Estimated savings: ~200-350 KiB of .text.
Design notes
Future opportunities identified in the reports
Test plan