Skip to content

Commit 7578b8f

Browse files
authored
perf(turbo-tasks): Add a 'local' option to #[turbo_tasks::function(..)] (#75259)
Allows tasks to be marked as local using `#[turbo_tasks::function(local)]`. Local tasks are cached only for the lifetime of the nearest non-Local parent caller. Local tasks do not have a unique task id, and are not shared with the backend. Instead they use the parent task's id and store data in the backend-agnostic manager. This is useful for functions that have a low cache hit rate. Those functions could be converted to non-task functions, but that would break their function signature. This provides a mechanism for skipping caching without changing the function signature. Local tasks are already used for argument resolution, this allows other arbitrary functions to be opted-in.
1 parent 04c3f0b commit 7578b8f

File tree

12 files changed

+113
-69
lines changed

12 files changed

+113
-69
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../turbo-tasks-testing/tests/local_tasks.rs

turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: unexpected token, expected one of: "fs", "network", "operation", or "local_cells"
1+
error: unexpected token, expected one of: "fs", "network", "operation", "local", or "local_cells"
22
--> tests/function/fail_attribute_invalid_args.rs:9:25
33
|
44
9 | #[turbo_tasks::function(invalid_argument)]

turbopack/crates/turbo-tasks-macros-tests/tests/function/fail_attribute_invalid_args_inherent_impl.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: unexpected token, expected one of: "fs", "network", "operation", or "local_cells"
1+
error: unexpected token, expected one of: "fs", "network", "operation", "local", or "local_cells"
22
--> tests/function/fail_attribute_invalid_args_inherent_impl.rs:14:29
33
|
44
14 | #[turbo_tasks::function(invalid_argument)]

turbopack/crates/turbo-tasks-macros/src/func.rs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub struct TurboFn<'a> {
3131
/// Should we return `OperationVc` and require that all arguments are `NonLocalValue`s?
3232
operation: bool,
3333
/// Should this function use `TaskPersistence::LocalCells`?
34-
local_cells: bool,
34+
local: bool,
3535
}
3636

3737
#[derive(Debug)]
@@ -274,7 +274,7 @@ impl TurboFn<'_> {
274274
this,
275275
inputs,
276276
operation: args.operation.is_some(),
277-
local_cells: args.local_cells.is_some(),
277+
local: args.local.is_some(),
278278
inline_ident,
279279
})
280280
}
@@ -479,9 +479,9 @@ impl TurboFn<'_> {
479479
}
480480

481481
pub fn persistence(&self) -> impl ToTokens {
482-
if self.local_cells {
482+
if self.local {
483483
quote! {
484-
turbo_tasks::TaskPersistence::LocalCells
484+
turbo_tasks::TaskPersistence::Local
485485
}
486486
} else {
487487
quote! {
@@ -491,9 +491,9 @@ impl TurboFn<'_> {
491491
}
492492

493493
pub fn persistence_with_this(&self) -> impl ToTokens {
494-
if self.local_cells {
494+
if self.local {
495495
quote! {
496-
turbo_tasks::TaskPersistence::LocalCells
496+
turbo_tasks::TaskPersistence::Local
497497
}
498498
} else {
499499
quote! {
@@ -703,6 +703,10 @@ pub struct FunctionArguments {
703703
///
704704
/// If there is an error due to this option being set, it should be reported to this span.
705705
operation: Option<Span>,
706+
/// Does not run the function as a real task, and instead runs it inside the parent task using
707+
/// task-local state. The function call itself will not be cached, but cells will be created on
708+
/// the parent task.
709+
pub local: Option<Span>,
706710
/// Changes the behavior of `Vc::cell` to create local cells that are not cached across task
707711
/// executions. Cells can be converted to their non-local versions by calling `Vc::resolve`.
708712
///
@@ -732,23 +736,29 @@ impl Parse for FunctionArguments {
732736
("operation", Meta::Path(_)) => {
733737
parsed_args.operation = Some(meta.span());
734738
}
739+
("local", Meta::Path(_)) => {
740+
parsed_args.local = Some(meta.span());
741+
}
735742
("local_cells", Meta::Path(_)) => {
736-
let span = Some(meta.span());
737-
parsed_args.local_cells = span;
743+
parsed_args.local_cells = Some(meta.span());
738744
}
739745
(_, meta) => {
740746
return Err(syn::Error::new_spanned(
741747
meta,
742748
"unexpected token, expected one of: \"fs\", \"network\", \"operation\", \
743-
or \"local_cells\"",
749+
\"local\", or \"local_cells\"",
744750
))
745751
}
746752
}
747753
}
748-
if let (Some(_), Some(span)) = (parsed_args.local_cells, parsed_args.operation) {
754+
if let (Some(_), Some(span)) = (
755+
parsed_args.local.or(parsed_args.local_cells),
756+
parsed_args.operation,
757+
) {
749758
return Err(syn::Error::new(
750759
span,
751-
"\"operation\" is mutually exclusive with the \"local_cells\" option",
760+
"\"operation\" is mutually exclusive with the \"local\" and \"local_cells\" \
761+
options",
752762
));
753763
}
754764
Ok(parsed_args)
@@ -1023,27 +1033,14 @@ impl DefinitionContext {
10231033

10241034
#[derive(Debug)]
10251035
pub struct NativeFn {
1026-
function_path_string: String,
1027-
function_path: ExprPath,
1028-
is_method: bool,
1029-
local_cells: bool,
1036+
pub function_path_string: String,
1037+
pub function_path: ExprPath,
1038+
pub is_method: bool,
1039+
pub local: bool,
1040+
pub local_cells: bool,
10301041
}
10311042

10321043
impl NativeFn {
1033-
pub fn new(
1034-
function_path_string: &str,
1035-
function_path: &ExprPath,
1036-
is_method: bool,
1037-
local_cells: bool,
1038-
) -> NativeFn {
1039-
NativeFn {
1040-
function_path_string: function_path_string.to_owned(),
1041-
function_path: function_path.clone(),
1042-
is_method,
1043-
local_cells,
1044-
}
1045-
}
1046-
10471044
pub fn ty(&self) -> Type {
10481045
parse_quote! { turbo_tasks::NativeFunction }
10491046
}
@@ -1053,6 +1050,7 @@ impl NativeFn {
10531050
function_path_string,
10541051
function_path,
10551052
is_method,
1053+
local,
10561054
local_cells,
10571055
} = self;
10581056

@@ -1068,6 +1066,7 @@ impl NativeFn {
10681066
turbo_tasks::NativeFunction::#constructor(
10691067
#function_path_string.to_owned(),
10701068
turbo_tasks::FunctionMeta {
1069+
local: #local,
10711070
local_cells: #local_cells,
10721071
},
10731072
#function_path,

turbopack/crates/turbo-tasks-macros/src/function_macro.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub fn function(args: TokenStream, input: TokenStream) -> TokenStream {
3939
let args = syn::parse::<FunctionArguments>(args)
4040
.inspect_err(|err| errors.push(err.to_compile_error()))
4141
.unwrap_or_default();
42+
let local = args.local.is_some();
4243
let local_cells = args.local_cells.is_some();
4344

4445
let Some(turbo_fn) = TurboFn::new(&sig, DefinitionContext::NakedFn, args) else {
@@ -54,12 +55,13 @@ pub fn function(args: TokenStream, input: TokenStream) -> TokenStream {
5455
let (inline_signature, inline_block) = turbo_fn.inline_signature_and_block(&block);
5556
let inline_attrs = filter_inline_attributes(&attrs[..]);
5657

57-
let native_fn = NativeFn::new(
58-
&ident.to_string(),
59-
&parse_quote! { #inline_function_ident },
60-
turbo_fn.is_method(),
58+
let native_fn = NativeFn {
59+
function_path_string: ident.to_string(),
60+
function_path: parse_quote! { #inline_function_ident },
61+
is_method: turbo_fn.is_method(),
62+
local,
6163
local_cells,
62-
);
64+
};
6365
let native_function_ident = get_native_function_ident(ident);
6466
let native_function_ty = native_fn.ty();
6567
let native_function_def = native_fn.definition();

turbopack/crates/turbo-tasks-macros/src/value_impl_macro.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream {
122122
let func_args = func_args
123123
.inspect_err(|err| errors.push(err.to_compile_error()))
124124
.unwrap_or_default();
125+
let local = func_args.local.is_some();
125126
let local_cells = func_args.local_cells.is_some();
126127

127128
let Some(turbo_fn) =
@@ -135,12 +136,13 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream {
135136
let (inline_signature, inline_block) = turbo_fn.inline_signature_and_block(block);
136137
let inline_attrs = filter_inline_attributes(attrs.iter().copied());
137138

138-
let native_fn = NativeFn::new(
139-
&format!("{ty}::{ident}", ty = ty.to_token_stream()),
140-
&parse_quote! { <#ty>::#inline_function_ident },
141-
turbo_fn.is_method(),
139+
let native_fn = NativeFn {
140+
function_path_string: format!("{ty}::{ident}", ty = ty.to_token_stream()),
141+
function_path: parse_quote! { <#ty>::#inline_function_ident },
142+
is_method: turbo_fn.is_method(),
143+
local,
142144
local_cells,
143-
);
145+
};
144146

145147
let native_function_ident = get_inherent_impl_function_ident(ty_ident, ident);
146148
let native_function_ty = native_fn.ty();
@@ -221,6 +223,7 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream {
221223
let func_args = func_args
222224
.inspect_err(|err| errors.push(err.to_compile_error()))
223225
.unwrap_or_default();
226+
let local = func_args.local.is_some();
224227
let local_cells = func_args.local_cells.is_some();
225228

226229
let Some(turbo_fn) =
@@ -239,18 +242,19 @@ pub fn value_impl(args: TokenStream, input: TokenStream) -> TokenStream {
239242
let (inline_signature, inline_block) = turbo_fn.inline_signature_and_block(block);
240243
let inline_attrs = filter_inline_attributes(attrs.iter().copied());
241244

242-
let native_fn = NativeFn::new(
243-
&format!(
245+
let native_fn = NativeFn {
246+
function_path_string: format!(
244247
"<{ty} as {trait_path}>::{ident}",
245248
ty = ty.to_token_stream(),
246249
trait_path = trait_path.to_token_stream()
247250
),
248-
&parse_quote! {
251+
function_path: parse_quote! {
249252
<#ty as #inline_extension_trait_ident>::#inline_function_ident
250253
},
251-
turbo_fn.is_method(),
254+
is_method: turbo_fn.is_method(),
255+
local,
252256
local_cells,
253-
);
257+
};
254258

255259
let native_function_ident =
256260
get_trait_impl_function_ident(ty_ident, &trait_ident, ident);

turbopack/crates/turbo-tasks-macros/src/value_trait_macro.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,19 @@ pub fn value_trait(args: TokenStream, input: TokenStream) -> TokenStream {
114114
let (inline_signature, inline_block) = turbo_fn.inline_signature_and_block(default);
115115
let inline_attrs = filter_inline_attributes(&attrs[..]);
116116

117-
let native_function = NativeFn::new(
118-
&format!("{trait_ident}::{ident}"),
119-
&parse_quote! {
117+
let native_function = NativeFn {
118+
function_path_string: format!("{trait_ident}::{ident}"),
119+
function_path: parse_quote! {
120120
<Box<dyn #trait_ident> as #inline_extension_trait_ident>::#inline_function_ident
121121
},
122-
turbo_fn.is_method(),
123-
// `inline_cells` is currently unsupported here because:
122+
is_method: turbo_fn.is_method(),
123+
// `local` and `local_cells` are currently unsupported here because:
124124
// - The `#[turbo_tasks::function]` macro needs to be present for us to read this
125-
// argument.
125+
// argument. (This could be fixed)
126126
// - This only makes sense when a default implementation is present.
127-
false,
128-
);
127+
local: false,
128+
local_cells: false,
129+
};
129130

130131
let native_function_ident = get_trait_default_impl_function_ident(trait_ident, ident);
131132
let native_function_ty = native_function.ty();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../turbo-tasks-testing/tests/local_tasks.rs
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![feature(arbitrary_self_types)]
2+
#![feature(arbitrary_self_types_pointers)]
3+
#![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this
4+
5+
use anyhow::Result;
6+
use turbo_tasks::{test_helpers::current_task_for_testing, Vc};
7+
use turbo_tasks_testing::{register, run, Registration};
8+
9+
static REGISTRATION: Registration = register!();
10+
11+
#[tokio::test]
12+
async fn test_local_task_id() -> Result<()> {
13+
run(&REGISTRATION, || async {
14+
let local_vc = get_local_task_id();
15+
assert!(local_vc.is_local());
16+
assert_eq!(*local_vc.await.unwrap(), *current_task_for_testing());
17+
18+
let non_local_vc = get_non_local_task_id();
19+
assert!(!non_local_vc.is_local());
20+
assert_ne!(*non_local_vc.await.unwrap(), *current_task_for_testing());
21+
Ok(())
22+
})
23+
.await
24+
}
25+
26+
#[turbo_tasks::function(local)]
27+
fn get_local_task_id() -> Vc<u32> {
28+
Vc::cell(*current_task_for_testing())
29+
}
30+
31+
#[turbo_tasks::function]
32+
fn get_non_local_task_id() -> Vc<u32> {
33+
Vc::cell(*current_task_for_testing())
34+
}

turbopack/crates/turbo-tasks/src/manager.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,19 +313,15 @@ pub enum TaskPersistence {
313313
/// [`TransientInstance`][crate::value::TransientInstance].
314314
Transient,
315315

316-
/// Tasks that are persisted only for the lifetime of the nearest non-`LocalCells` parent
317-
/// caller.
316+
/// Tasks that are persisted only for the lifetime of the nearest non-`Local` parent caller.
318317
///
319318
/// This task does not have a unique task id, and is not shared with the backend. Instead it
320319
/// uses the parent task's id.
321320
///
322-
/// Cells are allocated onto a temporary arena by default. Resolved cells inside a local task
323-
/// are allocated into the parent task's cells.
324-
///
325321
/// This is useful for functions that have a low cache hit rate. Those functions could be
326322
/// converted to non-task functions, but that would break their function signature. This
327323
/// provides a mechanism for skipping caching without changing the function signature.
328-
LocalCells,
324+
Local,
329325
}
330326

331327
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -588,19 +584,21 @@ impl<B: Backend + 'static> TurboTasks<B> {
588584
arg: Box<dyn MagicAny>,
589585
persistence: TaskPersistence,
590586
) -> RawVc {
591-
let task_type = CachedTaskType { fn_type, this, arg };
592587
match persistence {
593-
TaskPersistence::LocalCells => {
594-
todo!("bgw: local tasks");
588+
TaskPersistence::Local => {
589+
let task_type = LocalTaskType::Native { fn_type, this, arg };
590+
self.schedule_local_task(task_type, persistence)
595591
}
596592
TaskPersistence::Transient => {
593+
let task_type = CachedTaskType { fn_type, this, arg };
597594
RawVc::TaskOutput(self.backend.get_or_create_transient_task(
598595
task_type,
599596
current_task("turbo_function calls"),
600597
self,
601598
))
602599
}
603600
TaskPersistence::Persistent => {
601+
let task_type = CachedTaskType { fn_type, this, arg };
604602
RawVc::TaskOutput(self.backend.get_or_create_persistent_task(
605603
task_type,
606604
current_task("turbo_function calls"),

0 commit comments

Comments
 (0)