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