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

219 lines
6.1 KiB
Rust

#![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this
#![feature(arbitrary_self_types)]
#![feature(arbitrary_self_types_pointers)]
use anyhow::{Result, bail};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{ResolvedVc, ValueToString, Vc};
use turbo_tasks_testing::{Registration, register, run_once};
static REGISTRATION: Registration = register!();
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_all_in_one() {
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_all_in_one_operation(nonce)
.read_strongly_consistent()
.await
}
})
.await
.unwrap()
}
#[turbo_tasks::function(operation, root)]
async fn test_all_in_one_operation(nonce: u32) -> Result<Vc<()>> {
let _ = nonce; // ensure the nonce is part of our cache key
let a: Vc<u32> = Vc::cell(4242);
assert_eq!(*a.await?, 4242);
let a: Vc<MyTransparentValue> = Vc::cell(4242);
assert_eq!(*a.await?, 4242);
let b = MyEnumValue::cell(MyEnumValue::More(MyEnumValue::Yeah(42).resolved_cell()));
assert_eq!(*b.to_string().await?, "42");
let c = MyStructValue {
value: 42,
next: Some(MyStructValue::new(a).to_resolved().await?),
}
.cell();
let result = my_function(a, b.get_last(), c, MyEnumValue::Yeah(42));
assert_eq!(*result.my_trait_function().await?, "42");
assert_eq!(*result.my_trait_function2().await?, "42");
assert_eq!(*result.my_trait_function3().await?, "4242");
assert_eq!(*result.to_string().await?, "42");
// Testing Vc<Self> in traits
let a: Vc<Number> = Vc::cell(32);
let b: Vc<Number> = Vc::cell(10);
let c: Vc<Number> = a.add(Vc::upcast(b));
assert_eq!(*c.await?, 42);
let a_erased: Vc<Box<dyn Add>> = Vc::upcast(a);
let b_erased: Vc<Box<dyn Add>> = Vc::upcast(b);
let c_erased: Vc<Box<dyn Add>> = a_erased.add(b_erased);
assert_eq!(
*ResolvedVc::try_downcast_type::<Number>(c_erased.to_resolved().await?)
.unwrap()
.await?,
42
);
let b_erased_other: Vc<Box<dyn Add>> = Vc::upcast(Vc::<NumberB>::cell(10));
let c_erased_invalid: Vc<Box<dyn Add>> = a_erased.add(b_erased_other);
assert!(c_erased_invalid.to_resolved().await.is_err());
Ok(Vc::cell(()))
}
#[turbo_tasks::value(transparent)]
#[derive(Debug, Clone, Hash)]
struct MyTransparentValue(u32);
#[turbo_tasks::value(shared, task_input)]
#[derive(Debug, Clone, Hash)]
enum MyEnumValue {
Yeah(u32),
Nah,
More(ResolvedVc<MyEnumValue>),
}
#[turbo_tasks::value_impl]
impl MyEnumValue {
#[turbo_tasks::function]
pub async fn get_last(self: Vc<Self>) -> Result<Vc<Self>> {
let mut current = self;
while let MyEnumValue::More(more) = &*current.await? {
current = **more;
}
Ok(current)
}
}
#[turbo_tasks::value_impl]
impl ValueToString for MyEnumValue {
#[turbo_tasks::function]
fn to_string(&self) -> Vc<RcStr> {
match self {
MyEnumValue::Yeah(value) => Vc::cell(value.to_string().into()),
MyEnumValue::Nah => Vc::cell(rcstr!("nah")),
MyEnumValue::More(more) => more.to_string(),
}
}
}
#[turbo_tasks::value(shared)]
struct MyStructValue {
value: u32,
next: Option<ResolvedVc<MyStructValue>>,
}
#[turbo_tasks::value_impl]
impl MyStructValue {
#[turbo_tasks::function]
pub async fn new(value: Vc<MyTransparentValue>) -> Result<Vc<Self>> {
Ok(Self::cell(MyStructValue {
value: *value.await?,
next: None,
}))
}
}
#[turbo_tasks::value_impl]
impl ValueToString for MyStructValue {
#[turbo_tasks::function]
fn to_string(&self) -> Vc<RcStr> {
Vc::cell(self.value.to_string().into())
}
}
#[turbo_tasks::value_impl]
impl MyTrait for MyStructValue {
#[turbo_tasks::function]
fn my_trait_function2(self: Vc<Self>) -> Vc<RcStr> {
self.to_string()
}
#[turbo_tasks::function]
fn my_trait_function3(&self) -> Result<Vc<RcStr>> {
if let Some(next) = self.next {
return Ok(next.my_trait_function3());
}
Ok(Vc::cell(self.value.to_string().into()))
}
}
#[turbo_tasks::value_trait]
trait MyTrait: ValueToString {
#[turbo_tasks::function]
async fn my_trait_function(self: Vc<Self>) -> Result<Vc<RcStr>> {
if *self.to_string().await? != "42" {
bail!("my_trait_function must only be called with 42 as value")
}
// Calling a function twice
Ok(self.to_string())
}
#[turbo_tasks::function]
fn my_trait_function2(self: Vc<Self>) -> Vc<RcStr>;
#[turbo_tasks::function]
fn my_trait_function3(self: Vc<Self>) -> Vc<RcStr>;
}
#[turbo_tasks::function]
async fn my_function(
a: Vc<MyTransparentValue>,
b: Vc<MyEnumValue>,
c: Vc<MyStructValue>,
d: MyEnumValue,
) -> Result<Vc<MyStructValue>> {
assert_eq!(*a.await?, 4242);
assert_eq!(*b.await?, MyEnumValue::Yeah(42));
assert_eq!(c.await?.value, 42);
assert_eq!(d, MyEnumValue::Yeah(42));
Ok(c)
}
#[turbo_tasks::value_trait]
trait Add {
#[turbo_tasks::function]
fn add(self: Vc<Self>, other: Vc<Box<dyn Add>>) -> Vc<Self>;
}
#[turbo_tasks::value(transparent)]
struct Number(u32);
#[turbo_tasks::value_impl]
impl Add for Number {
#[turbo_tasks::function]
async fn add(self: Vc<Self>, other: ResolvedVc<Box<dyn Add>>) -> Result<Vc<Self>> {
let Some(other) = ResolvedVc::try_downcast_type::<Number>(other) else {
bail!("Expected Number");
};
Ok(Vc::cell(*self.await? + *other.await?))
}
}
#[turbo_tasks::value(transparent)]
struct NumberB(u32);
#[turbo_tasks::value_impl]
impl Add for NumberB {
#[turbo_tasks::function]
async fn add(self: Vc<Self>, other: ResolvedVc<Box<dyn Add>>) -> Result<Vc<Self>> {
let Some(other) = ResolvedVc::try_downcast_type::<NumberB>(other) else {
bail!("Expected NumberB");
};
Ok(Vc::cell(*self.await? + *other.await?))
}
}