Skip to main content

turbo_tasks/
raw_vc.rs

1use std::{
2    fmt::{Debug, Display},
3    future::Future,
4    pin::Pin,
5    sync::Arc,
6    task::{Poll, ready},
7};
8
9use anyhow::Result;
10use auto_hash_map::AutoSet;
11use bincode::{Decode, Encode};
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    CollectiblesSource, ReadCellOptions, ReadConsistency, ReadOutputOptions, ResolvedVc, TaskId,
16    TaskPersistence, TraitTypeId, ValueTypeId, VcValueTrait,
17    backend::TypedCellContent,
18    event::EventListener,
19    id::{ExecutionId, LocalTaskId},
20    manager::{
21        ReadCellTracking, ReadTracking, SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK,
22        TurboTasksApi, read_local_output, with_turbo_tasks,
23    },
24    registry::get_value_type,
25    turbo_tasks,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
29pub struct CellId {
30    pub type_id: ValueTypeId,
31    pub index: u32,
32}
33
34impl Display for CellId {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}#{}", get_value_type(self.type_id).ty.name, self.index)
37    }
38}
39
40/// A type-erased representation of [`Vc`].
41///
42/// Type erasure reduces the [monomorphization] (and therefore binary size and compilation time)
43/// required to support [`Vc`].
44///
45/// This type is heavily used within the [`Backend`][crate::backend::Backend] trait, but should
46/// otherwise be treated as an internal implementation detail of `turbo-tasks`.
47///
48/// [`Vc`]: crate::Vc
49/// [monomorphization]: https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics
50#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
51pub enum RawVc {
52    /// The synchronous return value of a task (after argument resolution). This is the
53    /// representation used by [`OperationVc`][crate::OperationVc].
54    TaskOutput(TaskId),
55    /// A pointer to a specific [`Vc::cell`][crate::Vc::cell] or `.cell()` call within a task. This
56    /// is the representation used by [`ResolvedVc`].
57    ///
58    /// [`CellId`] contains the [`ValueTypeId`], which can be useful for efficient downcasting.
59    TaskCell(TaskId, CellId),
60    /// The synchronous return value of a local task. This is created when a function is called
61    /// with unresolved arguments or more explicitly with
62    /// [`#[turbo_tasks::function(local)]`][crate::function].
63    ///
64    /// Local outputs are only valid within the context of their parent "non-local" task. Turbo
65    /// Task's APIs are designed to prevent escapes of local [`Vc`]s, but [`ExecutionId`] is used
66    /// for a fallback runtime assertion.
67    ///
68    /// [`Vc`]: crate::Vc
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::to_resolved`].
151    pub(crate) fn resolve(self) -> ResolveRawVcFuture {
152        ResolveRawVcFuture::new(self)
153    }
154
155    /// Convert a potentially local `RawVc` into a non-local `RawVc`. This is a subset of resolution
156    /// resolution, because the returned `RawVc` can be a `TaskOutput`.
157    pub(crate) async fn to_non_local(self) -> Result<RawVc> {
158        Ok(match self {
159            RawVc::LocalOutput(execution_id, local_task_id, ..) => {
160                let tt = turbo_tasks();
161                let local_output = read_local_output(&*tt, execution_id, local_task_id).await?;
162                debug_assert!(
163                    !matches!(local_output, RawVc::LocalOutput(_, _, _)),
164                    "a LocalOutput cannot point at other LocalOutputs"
165                );
166                local_output
167            }
168            non_local => non_local,
169        })
170    }
171
172    pub(crate) fn connect(&self) {
173        let RawVc::TaskOutput(task_id) = self else {
174            panic!("RawVc::connect() must only be called on a RawVc::TaskOutput");
175        };
176        let tt = turbo_tasks();
177        tt.connect_task(*task_id);
178    }
179
180    pub fn try_get_task_id(&self) -> Option<TaskId> {
181        match self {
182            RawVc::TaskOutput(t) | RawVc::TaskCell(t, ..) => Some(*t),
183            RawVc::LocalOutput(..) => None,
184        }
185    }
186
187    pub fn try_get_type_id(&self) -> Option<ValueTypeId> {
188        match self {
189            RawVc::TaskCell(_, CellId { type_id, .. }) => Some(*type_id),
190            RawVc::TaskOutput(..) | RawVc::LocalOutput(..) => None,
191        }
192    }
193
194    /// For a cell that's already resolved, synchronously check if it implements a trait using the
195    /// type information in `RawVc::TaskCell` (we don't actually need to read the cell!).
196    pub(crate) fn resolved_has_trait(&self, trait_id: TraitTypeId) -> bool {
197        match self {
198            RawVc::TaskCell(_task_id, cell_id) => {
199                get_value_type(cell_id.type_id).has_trait(&trait_id)
200            }
201            _ => unreachable!("resolved_has_trait must be called with a RawVc::TaskCell"),
202        }
203    }
204
205    /// For a cell that's already resolved, synchronously check if it is a given type using the type
206    /// information in `RawVc::TaskCell` (we don't actually need to read the cell!).
207    pub(crate) fn resolved_is_type(&self, type_id: ValueTypeId) -> bool {
208        match self {
209            RawVc::TaskCell(_task_id, cell_id) => cell_id.type_id == type_id,
210            _ => unreachable!("resolved_is_type must be called with a RawVc::TaskCell"),
211        }
212    }
213}
214
215/// This implementation of `CollectiblesSource` assumes that `self` is a `RawVc::TaskOutput`.
216impl CollectiblesSource for RawVc {
217    fn peek_collectibles<T: VcValueTrait + ?Sized>(self) -> AutoSet<ResolvedVc<T>> {
218        let RawVc::TaskOutput(task_id) = self else {
219            panic!(
220                "<RawVc as CollectiblesSource>::peek_collectibles() must only be called on a \
221                 RawVc::TaskOutput"
222            );
223        };
224        let tt = turbo_tasks();
225        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
226        map.into_iter()
227            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
228            .collect()
229    }
230
231    fn take_collectibles<T: VcValueTrait + ?Sized>(self) -> AutoSet<ResolvedVc<T>> {
232        let RawVc::TaskOutput(task_id) = self else {
233            panic!(
234                "<RawVc as CollectiblesSource>::take_collectibles() must only be called on a \
235                 RawVc::TaskOutput"
236            );
237        };
238        let tt = turbo_tasks();
239        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
240        tt.unemit_collectibles(T::get_trait_type_id(), &map);
241        map.into_iter()
242            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
243            .collect()
244    }
245
246    fn drop_collectibles<T: VcValueTrait + ?Sized>(self) {
247        let RawVc::TaskOutput(task_id) = self else {
248            panic!(
249                "<RawVc as CollectiblesSource>::drop_collectibles() must only be called on a \
250                 RawVc::TaskOutput"
251            );
252        };
253        let tt = turbo_tasks();
254        let map = tt.read_task_collectibles(task_id, T::get_trait_type_id());
255        tt.unemit_collectibles(T::get_trait_type_id(), &map);
256    }
257}
258
259/// Polls a pending [`EventListener`] slot. Returns [`Poll::Pending`] if the event has not yet
260/// fired. On [`Poll::Ready`], clears the slot so it is not polled again.
261fn poll_listener(
262    listener: &mut Option<EventListener>,
263    cx: &mut std::task::Context<'_>,
264) -> Poll<()> {
265    if let Some(l) = listener {
266        ready!(Pin::new(l).poll(cx));
267        *listener = None;
268    }
269    Poll::Ready(())
270}
271
272/// Wraps `f` in a scope that suppresses the eventual-consistency top-level task assertion,
273/// but only when `strongly_consistent` is `true` and debug assertions are enabled.
274///
275/// This is needed because a strongly-consistent read of a `TaskOutput` is not a single atomic
276/// operation — inner reads switch to eventual consistency after the first output is resolved —
277/// which would otherwise trigger the assertion in top-level tasks.
278fn suppress_top_level_task_check<R>(strongly_consistent: bool, f: impl FnOnce() -> R) -> R {
279    if cfg!(debug_assertions) && strongly_consistent {
280        // Temporarily suppress the top-level task check
281        SUPPRESS_EVENTUAL_CONSISTENCY_TOP_LEVEL_TASK_CHECK.sync_scope(true, f)
282    } else {
283        f()
284    }
285}
286
287#[must_use]
288pub struct ResolveRawVcFuture {
289    current: RawVc,
290    read_output_options: ReadOutputOptions,
291    /// This flag is redundant with `read_output_options`, but `read_output_options` is mutated
292    /// during the resolve. This flag indicates that the initial read was strongly consistent.
293    strongly_consistent: bool,
294    listener: Option<EventListener>,
295}
296
297impl ResolveRawVcFuture {
298    fn new(vc: RawVc) -> Self {
299        ResolveRawVcFuture {
300            current: vc,
301            read_output_options: ReadOutputOptions::default(),
302            strongly_consistent: false,
303            listener: None,
304        }
305    }
306
307    pub fn strongly_consistent(mut self) -> Self {
308        self.strongly_consistent = true;
309        self.read_output_options.consistency = ReadConsistency::Strong;
310        self
311    }
312
313    /// Track task output reads with a specific key (forwarded from
314    /// [`ReadRawVcFuture::track_with_key`]).
315    pub(crate) fn track_with_key(mut self) -> Self {
316        self.read_output_options.tracking = ReadTracking::Tracked;
317        self
318    }
319
320    /// Do not track task output reads as dependencies (forwarded from
321    /// [`ReadRawVcFuture::untracked`]).
322    pub(crate) fn untracked(mut self) -> Self {
323        self.read_output_options.tracking = ReadTracking::TrackOnlyError;
324        self
325    }
326}
327
328impl Future for ResolveRawVcFuture {
329    type Output = Result<RawVc>;
330
331    #[inline(never)]
332    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
333        // SAFETY: we are not moving self
334        let this = unsafe { self.get_unchecked_mut() };
335
336        let poll_fn = |tt: &Arc<dyn TurboTasksApi>| -> Poll<Self::Output> {
337            'outer: loop {
338                ready!(poll_listener(&mut this.listener, cx));
339                let listener = match this.current {
340                    RawVc::TaskOutput(task) => {
341                        let read_result = tt.try_read_task_output(task, this.read_output_options);
342                        match read_result {
343                            Ok(Ok(vc)) => {
344                                // turbo-tasks-backend doesn't currently have any sort of
345                                // "transaction" or global lock mechanism to group together chains
346                                // of `TaskOutput`/`TaskCell` reads.
347                                //
348                                // If we ignore the theoretical TOCTOU issues, we no longer need to
349                                // read strongly consistent, as any Vc returned from the first task
350                                // will be inside of the scope of the first task. So it's already
351                                // strongly consistent.
352                                this.read_output_options.consistency = ReadConsistency::Eventual;
353                                this.current = vc;
354                                continue 'outer;
355                            }
356                            Ok(Err(listener)) => listener,
357                            Err(err) => return Poll::Ready(Err(err)),
358                        }
359                    }
360                    RawVc::TaskCell(_, _) => return Poll::Ready(Ok(this.current)),
361                    RawVc::LocalOutput(execution_id, local_task_id, ..) => {
362                        debug_assert_eq!(
363                            this.read_output_options.consistency,
364                            ReadConsistency::Eventual
365                        );
366                        let read_result = tt.try_read_local_output(execution_id, local_task_id);
367                        match read_result {
368                            Ok(Ok(vc)) => {
369                                this.current = vc;
370                                continue 'outer;
371                            }
372                            Ok(Err(listener)) => listener,
373                            Err(err) => return Poll::Ready(Err(err)),
374                        }
375                    }
376                };
377                this.listener = Some(listener);
378            }
379        };
380
381        // HACK: Temporarily suppress top-level task check if doing strongly consistent read.
382        //
383        // This masks a bug: There's an unlikely TOCTOU race condition in `poll_fn`. Because the
384        // strongly consistent read isn't a single atomic operation, any inner `TaskOutput` or
385        // `TaskCell` could get mutated after the strongly consistent read of the outer
386        // `TaskOutput`.
387        suppress_top_level_task_check(this.strongly_consistent, || with_turbo_tasks(poll_fn))
388    }
389}
390
391impl Unpin for ResolveRawVcFuture {}
392
393#[must_use]
394pub struct ReadRawVcFuture {
395    /// Phase 1: resolves the [`RawVc`] pointer chain to a [`RawVc::TaskCell`].
396    resolve: ResolveRawVcFuture,
397    /// Phase 2: options for the cell read once we have a [`RawVc::TaskCell`].
398    read_cell_options: ReadCellOptions,
399    /// If `true`, the `is_serializable_cell_content` flag in `read_cell_options` is unknown at
400    /// construction time and must be determined lazily from the type registry once we reach the
401    /// [`RawVc::TaskCell`].
402    is_serializable_cell_content_unknown: bool,
403    /// Phase 2: the resolved task and cell identity, set when phase 1 completes.
404    resolved: Option<(TaskId, CellId)>,
405    /// Phase 2: listener for the cell read wait.
406    listener: Option<EventListener>,
407}
408
409impl ReadRawVcFuture {
410    pub(crate) fn new(vc: RawVc, is_serializable_cell_content: Option<bool>) -> Self {
411        ReadRawVcFuture {
412            resolve: ResolveRawVcFuture::new(vc),
413            read_cell_options: ReadCellOptions {
414                is_serializable_cell_content: is_serializable_cell_content.unwrap_or(false),
415                ..Default::default()
416            },
417            is_serializable_cell_content_unknown: is_serializable_cell_content.is_none(),
418            resolved: None,
419            listener: None,
420        }
421    }
422
423    /// Make reads strongly consistent.
424    pub fn strongly_consistent(mut self) -> Self {
425        self.resolve = self.resolve.strongly_consistent();
426        self
427    }
428
429    /// Track the value as a dependency with an key.
430    pub fn track_with_key(mut self, key: u64) -> Self {
431        self.resolve = self.resolve.track_with_key();
432        self.read_cell_options.tracking = ReadCellTracking::Tracked { key: Some(key) };
433        self
434    }
435
436    /// This will not track the value as dependency, but will still track the error as dependency,
437    /// if there is an error.
438    ///
439    /// INVALIDATION: Be careful with this, it will not track dependencies, so
440    /// using it could break cache invalidation.
441    pub fn untracked(mut self) -> Self {
442        self.resolve = self.resolve.untracked();
443        self.read_cell_options.tracking = ReadCellTracking::TrackOnlyError;
444        self
445    }
446
447    /// Hint that this is the final read of the cell content.
448    pub fn final_read_hint(mut self) -> Self {
449        self.read_cell_options.final_read_hint = true;
450        self
451    }
452}
453
454impl Future for ReadRawVcFuture {
455    type Output = Result<TypedCellContent>;
456
457    #[inline(never)]
458    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
459        // SAFETY: we are not moving self
460        let this = unsafe { self.get_unchecked_mut() };
461
462        // --- Phase 1: resolve the RawVc pointer chain to a TaskCell ---
463        //
464        // `ResolveRawVcFuture` is `Unpin`, so `Pin::new` is safe.
465        // It handles `with_turbo_tasks` and `suppress_top_level_task_check` internally.
466        if this.resolved.is_none() {
467            match ready!(Pin::new(&mut this.resolve).poll(cx)) {
468                Err(err) => return Poll::Ready(Err(err)),
469                Ok(RawVc::TaskCell(task, index)) => {
470                    this.resolved = Some((task, index));
471                }
472                Ok(_) => unreachable!("ResolveRawVcFuture always resolves to a TaskCell"),
473            }
474        }
475
476        // --- Phase 2: read the cell content ---
477        //
478        // At this point `this.resolved` is `Some((task, index))`.
479        let (task, index) = this.resolved.unwrap();
480
481        // Lazily resolve `is_serializable_cell_content` from the type registry on the first
482        // entry into phase 2, then clear the flag so subsequent polls skip this lookup.
483        if this.is_serializable_cell_content_unknown {
484            this.read_cell_options.is_serializable_cell_content =
485                get_value_type(index.type_id).bincode.is_some();
486            this.is_serializable_cell_content_unknown = false;
487        }
488
489        let poll_fn = |tt: &Arc<dyn TurboTasksApi>| -> Poll<Self::Output> {
490            loop {
491                ready!(poll_listener(&mut this.listener, cx));
492                let listener = match tt.try_read_task_cell(task, index, this.read_cell_options) {
493                    Ok(Ok(content)) => return Poll::Ready(Ok(content)),
494                    Ok(Err(listener)) => listener,
495                    Err(err) => return Poll::Ready(Err(err)),
496                };
497                this.listener = Some(listener);
498            }
499        };
500
501        // Phase 2 must also suppress the top-level task check when phase 1 was
502        // strongly-consistent. The suppression from `ResolveRawVcFuture::poll` only lasts for
503        // the duration of that individual `poll` call and does not carry over to subsequent calls
504        // or to this phase.
505        suppress_top_level_task_check(this.resolve.strongly_consistent, || {
506            with_turbo_tasks(poll_fn)
507        })
508    }
509}
510
511impl Unpin for ReadRawVcFuture {}