mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
# 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.
219 lines
6.1 KiB
Rust
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(®ISTRATION, 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?))
|
|
}
|
|
}
|