Skip to main content

turbo_tasks/
dyn_task_inputs.rs

1use std::{
2    any::{Any, type_name},
3    fmt::Debug,
4    hash::Hash,
5};
6
7use turbo_dyn_eq_hash::{
8    DynEq, DynHash, impl_eq_for_dyn, impl_hash_for_dyn, impl_partial_eq_for_dyn,
9};
10
11pub trait DynTaskInputs: Debug + DynEq + DynHash + Send + Sync + 'static {
12    #[cfg(debug_assertions)]
13    fn dyn_type_name(&self) -> &'static str;
14}
15
16impl<T> DynTaskInputs for T
17where
18    T: Debug + Eq + Hash + Send + Sync + 'static,
19{
20    #[cfg(debug_assertions)]
21    fn dyn_type_name(&self) -> &'static str {
22        std::any::type_name::<T>()
23    }
24}
25
26impl_partial_eq_for_dyn!(dyn DynTaskInputs);
27impl_eq_for_dyn!(dyn DynTaskInputs);
28impl_hash_for_dyn!(dyn DynTaskInputs);
29
30pub fn any_as_encode<T: Any>(this: &dyn Any) -> &T {
31    if let Some(enc) = this.downcast_ref::<T>() {
32        return enc;
33    }
34    unreachable!(
35        "any_as_encode::<{}> called with invalid type",
36        type_name::<T>()
37    );
38}
39
40/// A trait for task arguments that may reside on the stack.
41///
42/// This enables deferred boxing: on the cache-hit path (~85%), we only borrow
43/// the argument via [`as_ref`](DynTaskInputsStorage::as_ref) for hash/equality lookups,
44/// avoiding any heap allocation. On cache miss, [`take_box`](DynTaskInputsStorage::take_box)
45/// moves the value into a `Box` with zero clones.
46pub trait DynTaskInputsStorage {
47    /// Borrow the argument as a type-erased reference (for cache lookup).
48    fn as_ref(&self) -> &dyn DynTaskInputs;
49    /// Move the argument out into a heap-allocated Box (panics if already taken).
50    fn take_box(&mut self) -> Box<dyn DynTaskInputs>;
51    /// Downcast to `&mut dyn Any` for concrete type recovery without boxing.
52    fn as_any_mut(&mut self) -> &mut dyn Any;
53}
54
55/// Stack-resident slot wrapping a concrete typed value.
56///
57/// Created by macro-generated callsites. The value starts in `Some` on the
58/// stack; [`take_box`](DynTaskInputsStorage::take_box) moves it to the heap on cache miss.
59#[repr(transparent)]
60pub struct StackDynTaskInputsStorage<T> {
61    slot: Option<T>,
62}
63
64impl<T> StackDynTaskInputsStorage<T> {
65    #[inline]
66    pub fn new(value: T) -> Self {
67        Self { slot: Some(value) }
68    }
69
70    #[inline]
71    pub fn take(&mut self) -> T {
72        self.slot
73            .take()
74            .expect("StackDynTaskInputsStorage::take called after value was already taken")
75    }
76}
77
78impl<T: DynTaskInputs> DynTaskInputsStorage for StackDynTaskInputsStorage<T> {
79    #[inline]
80    fn as_ref(&self) -> &dyn DynTaskInputs {
81        self.slot
82            .as_ref()
83            .expect("StackDynTaskInputsStorage::as_ref called after take_box")
84    }
85
86    #[inline]
87    fn take_box(&mut self) -> Box<dyn DynTaskInputs> {
88        Box::new(
89            self.slot
90                .take()
91                .expect("StackDynTaskInputsStorage::take_box called twice"),
92        )
93    }
94
95    #[inline]
96    fn as_any_mut(&mut self) -> &mut dyn Any {
97        self
98    }
99}
100
101/// Adapter for an already-boxed value (e.g., from async resolution tasks).
102pub struct HeapDynTaskInputsStorage {
103    slot: Option<Box<dyn DynTaskInputs>>,
104}
105
106impl HeapDynTaskInputsStorage {
107    #[inline]
108    pub fn new(value: Box<dyn DynTaskInputs>) -> Self {
109        Self { slot: Some(value) }
110    }
111}
112
113impl DynTaskInputsStorage for HeapDynTaskInputsStorage {
114    #[inline]
115    fn as_ref(&self) -> &dyn DynTaskInputs {
116        &**self
117            .slot
118            .as_ref()
119            .expect("HeapDynTaskInputsStorage::as_ref called after take_box")
120    }
121
122    #[inline]
123    fn take_box(&mut self) -> Box<dyn DynTaskInputs> {
124        self.slot
125            .take()
126            .expect("HeapDynTaskInputsStorage::take_box called twice")
127    }
128
129    #[inline]
130    fn as_any_mut(&mut self) -> &mut dyn Any {
131        self
132    }
133}