Skip to main content

turbo_tasks/
raw_vc.rs

1use std::{
2    fmt::{Debug, Display},
3    future::Future,
4    pin::Pin,
5    task::Poll,
6};
7
8use anyhow::Result;
9use auto_hash_map::AutoSet;
10use bincode::{Decode, Encode};
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    CollectiblesSource, ReadCellOptions, ReadConsistency, ReadOutputOptions, ResolvedVc, TaskId,
15    TaskPersistence, TraitTypeId, ValueTypeId, VcValueTrait,
16    backend::TypedCellContent,
17    event::EventListener,
18    id::{ExecutionId, LocalTaskId},
19    manager::{
20        ReadCellTracking, ReadTracking, read_local_output, read_task_output, with_turbo_tasks,
21    },
22    registry::{self, get_value_type},
23    turbo_tasks,
24};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
27pub struct CellId {
28    pub type_id: ValueTypeId,
29    pub index: u32,
30}
31
32impl Display for CellId {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(
35            f,
36            "{}#{}",
37            registry::get_value_type(self.type_id).name,
38            self.index
39        )
40    }
41}
42
43/// A type-erased representation of [`Vc`][crate::Vc].
44///
45/// Type erasure reduces the [monomorphization] (and therefore binary size and compilation time)
46/// required to support [`Vc`][crate::Vc].
47///
48/// This type is heavily used within the [`Backend`][crate::backend::Backend] trait, but should
49/// otherwise be treated as an internal implementation detail of `turbo-tasks`.
50///
51/// [monomorphization]: https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics
52#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
53pub enum RawVc {
54    /// The synchronous return value of a task (after argument resolution). This is the
55    /// representation used by [`OperationVc`][crate::OperationVc].
56    TaskOutput(TaskId),
57    /// A pointer to a specific [`Vc::cell`][crate::Vc::cell] or `.cell()` call within a task. This
58    /// is the representation used by [`ResolvedVc`].
59    ///
60    /// [`CellId`] contains the [`ValueTypeId`], which can be useful for efficient downcasting.
61    TaskCell(TaskId, CellId),
62    /// The synchronous return value of a local task. This is created when a function is called
63    /// with unresolved arguments or more explicitly with
64    /// [`#[turbo_tasks::function(local)]`][crate::function].
65    ///
66    /// Local outputs are only valid within the context of their parent "non-local" task. Turbo
67    /// Task's APIs are designed to prevent escapes of local [`Vc`]s, but [`ExecutionId`] is used
68    /// for a fallback runtime assertion.
69    LocalOutput(ExecutionId, LocalTaskId, TaskPersistence),
70}
71
72impl Debug for RawVc {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self {
75            RawVc::TaskOutput(task_id) => f
76                .debug_tuple("RawVc::TaskOutput")
77                .field(&**task_id)
78                .finish(),
79            RawVc::TaskCell(task_id, cell_id) => f
80                .debug_tuple("RawVc::TaskCell")
81                .field(&**task_id)
82                .field(&cell_id.to_string())
83                .finish(),
84            RawVc::LocalOutput(execution_id, local_task_id, task_persistence) => f
85                .debug_tuple("RawVc::LocalOutput")
86                .field(&**execution_id)
87                .field(&**local_task_id)
88                .field(task_persistence)
89                .finish(),
90        }
91    }
92}
93
94impl Display for RawVc {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        match self {
97            RawVc::TaskOutput(task_id) => write!(f, "output of task {}", **task_id),
98            RawVc::TaskCell(task_id, cell_id) => {
99                write!(f, "{} of task {}", cell_id, **task_id)
100            }
101            RawVc::LocalOutput(execution_id, local_task_id, task_persistence) => write!(
102                f,
103                "output of local task {} ({}, {})",
104                **local_task_id, **execution_id, task_persistence
105            ),
106        }
107    }
108}
109
110impl RawVc {
111    pub fn is_resolved(&self) -> bool {
112        match self {
113            RawVc::TaskOutput(..) => false,
114            RawVc::TaskCell(..) => true,
115            RawVc::LocalOutput(..) => false,
116        }
117    }
118
119    pub fn is_local(&self) -> bool {
120        match self {
121            RawVc::TaskOutput(..) => false,
122            RawVc::TaskCell(..) => false,
123            RawVc::LocalOutput(..) => true,
124        }
125    }
126
127    /// Returns `true` if the task this `RawVc` reads from cannot be serialized and will not be
128    /// stored in the filesystem cache.
129    ///
130    /// See [`TaskPersistence`] for more details.
131    pub fn is_transient(&self) -> bool {
132        match self {
133            RawVc::TaskOutput(task) | RawVc::TaskCell(task, ..) => task.is_transient(),
134            RawVc::LocalOutput(_, _, persistence) => *persistence == TaskPersistence::Transient,
135        }
136    }
137
138    pub(crate) fn into_read(self, is_serializable_cell_content: bool) -> ReadRawVcFuture {
139        // returns a custom future to have something concrete and sized
140        // this avoids boxing in IntoFuture
141        ReadRawVcFuture::new(self, Some(is_serializable_cell_content))
142    }
143
144    pub(crate) fn into_read_with_unknown_is_serializable_cell_content(self) -> ReadRawVcFuture {
145        // returns a custom future to have something concrete and sized
146        // this avoids boxing in IntoFuture
147        ReadRawVcFuture::new(self, None)
148    }
149
150    /// See [`crate::Vc::resolve`].
151    pub(crate) async fn resolve(self) -> Result<RawVc> {
152        self.resolve_inner(ReadOutputOptions {
153            consistency: ReadConsistency::Eventual,
154            ..Default::default()
155        })
156        .await
157    }
158
159    /// See [`crate::Vc::resolve_strongly_consistent`].
160    pub(crate) async fn resolve_strongly_consistent(self) -> Result<RawVc> {
161        self.resolve_inner(ReadOutputOptions {
162            consistency: ReadConsistency::Strong,
163            ..Default::default()
164        })
165        .await
166    }
167
168    async fn resolve_inner(self, mut options: ReadOutputOptions) -> Result<RawVc> {
169        let tt = turbo_tasks();
170        let mut current = self;
171        loop {
172            match current {
173                RawVc::TaskOutput(task) => {
174                    current = read_task_output(&*tt, task, options).await?;
175                    // We no longer need to read strongly consistent, as any Vc returned
176                    // from the first task will be inside of the scope of the first
177                    // task. So it's already strongly consistent.
178                    options.consistency = ReadConsistency::Eventual;
179                }
180                RawVc::TaskCell(_, _) => return Ok(current),
181                RawVc::LocalOutput(execution_id, local_task_id, ..) => {
182                    debug_assert_eq!(options.consistency, ReadConsistency::Eventual);
183                    current = read_local_output(&*tt, execution_id, local_task_id).await?;
184                }
185            }
186        }
187    }
188
189    /// Convert a potentially local `RawVc` into a non-local `RawVc`. This is a subset of resolution
190    /// resolution, because the returned `RawVc` can be a `TaskOutput`.
191    pub(crate) async fn to_non_local(self) -> Result<RawVc> {
192        Ok(match self {
193            RawVc::LocalOutput(execution_id, local_task_id, ..) => {
194                let tt = turbo_tasks();
195                let local_output = read_local_output(&*tt, execution_id, local_task_id).await?;
196                debug_assert!(
197                    !matches!(local_output, RawVc::LocalOutput(_, _, _)),
198                    "a LocalOutput cannot point at other LocalOutputs"
199                );
200                local_output
201            }
202            non_local => non_local,
203        })
204    }
205
206    pub(crate) fn connect(&self) {
207        let RawVc::TaskOutput(task_id) = self else {
208            panic!("RawVc::connect() must only be called on a RawVc::TaskOutput");
209        };
210        let tt = turbo_tasks();
211        tt.connect_task(*task_id);
212    }
213
214    pub fn try_get_task_id(&self) -> Option<TaskId> {
215        match self {
216            RawVc::TaskOutput(t) | RawVc::TaskCell(t, ..) => Some(*t),
217            RawVc::LocalOutput(..) => None,
218        }
219    }
220
221    pub fn try_get_type_id(&self) -> Option<ValueTypeId> {
222        match self {
223            RawVc::TaskCell(_, CellId { type_id, .. }) => Some(*type_id),
224            RawVc::TaskOutput(..) | RawVc::LocalOutput(..) => None,
225        }
226    }
227
228    /// For a cell that's already resolved, synchronously check if it implements a trait using the
229    /// type information in `RawVc::TaskCell` (we don't actually need to read the cell!).
230    pub(crate) fn resolved_has_trait(&self, trait_id: TraitTypeId) -> bool {
231        match self {
232            RawVc::TaskCell(_task_id, cell_id) => {
233                get_value_type(cell_id.type_id).has_trait(&trait_id)
234            }
235            _ => unreachable!("resolved_has_trait must be called with a RawVc::TaskCell"),
236        }
237    }
238
239    /// For a cell that's already resolved, synchronously check if it is a given type using the type
240    /// information in `RawVc::TaskCell` (we don't actually need to read the cell!).
241    pub(crate) fn resolved_is_type(&self, type_id: ValueTypeId) -> bool {
242        match self {
243            RawVc::TaskCell(_task_id, cell_id) => cell_id.type_id == type_id,
244            _ => unreachable!("resolved_is_type must be called with a RawVc::TaskCell"),
245        }
246    }
247}
248
249/// This implementation of `CollectiblesSource` assumes that `self` is a `RawVc::TaskOutput`.
250impl CollectiblesSource for RawVc {
251    fn peek_collectibles<T: VcValueTrait + ?Sized>(self) -> AutoSet<ResolvedVc<T>> {
252        let RawVc::TaskOutput(task_id) = self else {
253            panic!(
254                "<RawVc as CollectiblesSource>::peek_collectibles() must only be called on a \
255                 RawVc::TaskOutput"
256            );
257        };
258        let tt = turbo_tasks();
259        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
260        map.into_iter()
261            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
262            .collect()
263    }
264
265    fn take_collectibles<T: VcValueTrait + ?Sized>(self) -> AutoSet<ResolvedVc<T>> {
266        let RawVc::TaskOutput(task_id) = self else {
267            panic!(
268                "<RawVc as CollectiblesSource>::take_collectibles() must only be called on a \
269                 RawVc::TaskOutput"
270            );
271        };
272        let tt = turbo_tasks();
273        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
274        tt.unemit_collectibles(T::get_trait_type_id(), &map);
275        map.into_iter()
276            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
277            .collect()
278    }
279
280    fn drop_collectibles<T: VcValueTrait + ?Sized>(self) {
281        let RawVc::TaskOutput(task_id) = self else {
282            panic!(
283                "<RawVc as CollectiblesSource>::drop_collectibles() must only be called on a \
284                 RawVc::TaskOutput"
285            );
286        };
287        let tt = turbo_tasks();
288        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
289        tt.unemit_collectibles(T::get_trait_type_id(), &map);
290    }
291}
292
293pub struct ReadRawVcFuture {
294    current: RawVc,
295    read_output_options: ReadOutputOptions,
296    read_cell_options: ReadCellOptions,
297    is_serializable_cell_content_unknown: bool,
298    listener: Option<EventListener>,
299}
300
301impl ReadRawVcFuture {
302    pub(crate) fn new(vc: RawVc, is_serializable_cell_content: Option<bool>) -> Self {
303        ReadRawVcFuture {
304            current: vc,
305            read_output_options: ReadOutputOptions::default(),
306            read_cell_options: ReadCellOptions {
307                is_serializable_cell_content: is_serializable_cell_content.unwrap_or(false),
308                ..Default::default()
309            },
310            is_serializable_cell_content_unknown: is_serializable_cell_content.is_none(),
311            listener: None,
312        }
313    }
314
315    /// Make reads strongly consistent.
316    pub fn strongly_consistent(mut self) -> Self {
317        self.read_output_options.consistency = ReadConsistency::Strong;
318        self
319    }
320
321    /// Track the value as a dependency with an key.
322    pub fn track_with_key(mut self, key: u64) -> Self {
323        self.read_output_options.tracking = ReadTracking::Tracked;
324        self.read_cell_options.tracking = ReadCellTracking::Tracked { key: Some(key) };
325        self
326    }
327
328    /// This will not track the value as dependency, but will still track the error as dependency,
329    /// if there is an error.
330    ///
331    /// INVALIDATION: Be careful with this, it will not track dependencies, so
332    /// using it could break cache invalidation.
333    pub fn untracked(mut self) -> Self {
334        self.read_output_options.tracking = ReadTracking::TrackOnlyError;
335        self.read_cell_options.tracking = ReadCellTracking::TrackOnlyError;
336        self
337    }
338
339    /// Hint that this is the final read of the cell content.
340    pub fn final_read_hint(mut self) -> Self {
341        self.read_cell_options.final_read_hint = true;
342        self
343    }
344}
345
346impl Future for ReadRawVcFuture {
347    type Output = Result<TypedCellContent>;
348
349    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
350        with_turbo_tasks(|tt| {
351            // SAFETY: we are not moving this
352            let this = unsafe { self.get_unchecked_mut() };
353            'outer: loop {
354                if let Some(listener) = &mut this.listener {
355                    // SAFETY: listener is from previous pinned this
356                    let listener = unsafe { Pin::new_unchecked(listener) };
357                    if listener.poll(cx).is_pending() {
358                        return Poll::Pending;
359                    }
360                    this.listener = None;
361                }
362                let mut listener = match this.current {
363                    RawVc::TaskOutput(task) => {
364                        let read_result = tt.try_read_task_output(task, this.read_output_options);
365                        match read_result {
366                            Ok(Ok(vc)) => {
367                                // We no longer need to read strongly consistent, as any Vc returned
368                                // from the first task will be inside of the scope of the first
369                                // task. So it's already strongly consistent.
370                                this.read_output_options.consistency = ReadConsistency::Eventual;
371                                this.current = vc;
372                                continue 'outer;
373                            }
374                            Ok(Err(listener)) => listener,
375                            Err(err) => return Poll::Ready(Err(err)),
376                        }
377                    }
378                    RawVc::TaskCell(task, index) => {
379                        if this.is_serializable_cell_content_unknown {
380                            let value_type = registry::get_value_type(index.type_id);
381                            this.read_cell_options.is_serializable_cell_content =
382                                value_type.bincode.is_some();
383                        }
384                        let read_result =
385                            tt.try_read_task_cell(task, index, this.read_cell_options);
386                        match read_result {
387                            Ok(Ok(content)) => {
388                                // SAFETY: Constructor ensures that T and U are binary identical
389                                return Poll::Ready(Ok(content));
390                            }
391                            Ok(Err(listener)) => listener,
392                            Err(err) => return Poll::Ready(Err(err)),
393                        }
394                    }
395                    RawVc::LocalOutput(execution_id, local_output_id, ..) => {
396                        debug_assert_eq!(
397                            this.read_output_options.consistency,
398                            ReadConsistency::Eventual
399                        );
400                        let read_result = tt.try_read_local_output(execution_id, local_output_id);
401                        match read_result {
402                            Ok(Ok(vc)) => {
403                                this.current = vc;
404                                continue 'outer;
405                            }
406                            Ok(Err(listener)) => listener,
407                            Err(err) => return Poll::Ready(Err(err)),
408                        }
409                    }
410                };
411                // SAFETY: listener is from previous pinned this
412                match unsafe { Pin::new_unchecked(&mut listener) }.poll(cx) {
413                    Poll::Ready(_) => continue,
414                    Poll::Pending => {
415                        this.listener = Some(listener);
416                        return Poll::Pending;
417                    }
418                };
419            }
420        })
421    }
422}
423
424impl Unpin for ReadRawVcFuture {}