Files
Luke SandbergandGitHub dc856d6c33 [turbopack] Optimize TaskInput implementations (#94202)
# What

Replace the `derive` macros for TaskInput with a proc macro
`[turbo_tasks::task_input]` (and analogous support inside of the
`turbo_tasks::value` macro.

Introduce a hand written `CloneResolved` future type for TaskInput
implementations that are always resolved.

# Why

This allows us to take advantage of the `NonLocalValue` marker trait. If
a type is `NonLocalValue` then `TaskInput` can have trivial
`is_resolved` and `resolve_input` implementations. This applies to the
vast majority of TaskInputs but the current `derive` implementation
always produces a recursive `async` `resolve_input` implementation but
it is rarely needed and rustc/llvm do a bad job of optimizing it.

## Code-size impact

`next-swc.darwin-arm64.node` — release profile, `pnpm swc-build-native
--release`, macOS aarch64.

| Metric | canary | this branch | Δ bytes | Δ % |
| --- | ---: | ---: | ---: | ---: |
| Raw `.node` | 124,494,592 | 124,217,280 | −277,312 | −0.22% |
| Stripped (`strip -x`) | 84,294,000 | 84,013,136 | −280,864 | −0.33% |
| Stripped + gzip -9 | 29,123,538 | 28,898,920 | −224,618 | −0.77% |

## Performance

A similar measurement of performance was neutral

| Metric | canary | temporary | Δ | % |
|---|---|---|---|---|
| wall time (s) | 42.659 ± 1.851 | 42.501 ± 2.028 | −0.158 | −0.4% |
| user time (s) | 294.322 ± 1.834 | 293.207 ± 2.070 | −1.115 | −0.4% |
| sys time (s) | 74.158 ± 4.197 | 73.543 ± 5.725 | −0.615 | −0.8% |
| MaxRSS (MB) | 13650.1 ± 66.0 | 13610.1 ± 57.5 | −40.0 | −0.3% |

All deltas are smaller than one standard deviation — statistically
indistinguishable from noise on this benchmark, as expected for a small
optimization.
2026-06-03 23:26:17 +00:00

169 lines
4.5 KiB
Rust

#![feature(arbitrary_self_types)]
#![feature(arbitrary_self_types_pointers)]
#![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this
use std::sync::Arc;
use anyhow::Result;
use bincode::{Decode, Encode};
use turbo_tasks::{State, Vc, trace::TraceRawVcs};
use turbo_tasks_testing::{Registration, register, run_once};
static REGISTRATION: Registration = register!();
#[turbo_tasks::task_input]
#[derive(Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
pub struct TaskReferenceSpec {
task: u16,
chain: u8,
read: bool,
read_strongly_consistent: bool,
}
#[turbo_tasks::task_input]
#[derive(Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
pub struct TaskSpec {
references: Vec<TaskReferenceSpec>,
children: u8,
change: Option<Box<TaskSpec>>,
}
#[turbo_tasks::value(transparent)]
struct Iteration(State<usize>);
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_graph_bug() {
let mut nonce = 0;
run_once(&REGISTRATION, move || {
// pass a nonce to re-run the test body on every turbo-tasks restart
nonce += 1;
async move {
test_graph_bug_operation(nonce)
.read_strongly_consistent()
.await
}
})
.await
.unwrap()
}
#[turbo_tasks::function(operation, root)]
async fn test_graph_bug_operation(nonce: u32) -> Result<Vc<()>> {
let _ = nonce; // ensure the nonce is part of our cache key
let spec = vec![
TaskSpec {
references: vec![TaskReferenceSpec {
task: 1,
chain: 0,
read: false,
read_strongly_consistent: false,
}],
children: 0,
change: Some(Box::new(TaskSpec {
references: vec![TaskReferenceSpec {
task: 1,
chain: 254,
read: false,
read_strongly_consistent: false,
}],
children: 0,
change: None,
})),
},
TaskSpec {
references: vec![],
children: 0,
change: None,
},
];
let it = *create_iteration().to_resolved().await?;
it.await?.set(0);
println!("🚀 Initial");
let task = run_task(Arc::new(spec), it, 0);
task.strongly_consistent().await?;
println!("🚀 Set iteration to 1");
it.await?.set(1);
task.strongly_consistent().await?;
println!("🚀 Finished strongly consistent wait");
Ok(Vc::cell(()))
}
#[turbo_tasks::function]
fn create_iteration() -> Vc<Iteration> {
Vc::cell(State::new(0))
}
#[turbo_tasks::function(root)]
async fn run_task_chain(
spec: Arc<Vec<TaskSpec>>,
iteration: Vc<Iteration>,
from: u16,
ref_index: usize,
to: u16,
chain: u8,
) -> Result<Vc<()>> {
println!("run_task_chain(from: {from}, ref_index: {ref_index}, to: {to}, chain: {chain})");
if chain > 0 {
run_task_chain(spec, iteration, from, ref_index, to, chain - 1).await?;
} else {
run_task(spec, iteration, to).await?;
}
Ok(Vc::cell(()))
}
#[turbo_tasks::function(root)]
async fn run_task(
spec: Arc<Vec<TaskSpec>>,
iteration: Vc<Iteration>,
task_index: u16,
) -> Result<Vc<()>> {
println!("run_task(task_index: {task_index})");
let mut task = &spec[task_index as usize];
if task.change.is_some() {
let iteration = iteration.await?;
let it = *iteration.get();
for _ in 0..it {
task = if let Some(change) = &task.change {
change
} else {
task
};
}
}
for i in 0..task.children {
run_task_child(task_index, i).await?;
}
for (i, reference) in task.references.iter().enumerate() {
let call = if reference.chain > 0 {
run_task_chain(
spec.clone(),
iteration,
task_index,
i,
reference.task,
reference.chain,
)
} else {
run_task(spec.clone(), iteration, reference.task)
};
if reference.read {
call.await?;
}
if reference.read_strongly_consistent {
call.strongly_consistent().await?;
}
}
Ok(Vc::cell(()))
}
#[turbo_tasks::function]
async fn run_task_child(from: u16, i: u8) -> Result<Vc<()>> {
println!("run_task_child(from: {from}, i: {i})");
let _ = from;
let _ = i;
Ok(Vc::cell(()))
}