Skip to main content

turbo_tasks/vc/
operation.rs

1use std::{
2    fmt::Debug,
3    future::Future,
4    hash::Hash,
5    marker::PhantomData,
6    pin::Pin,
7    task::{Context, Poll},
8};
9
10use anyhow::Result;
11use auto_hash_map::AutoSet;
12use bincode::{Decode, Encode};
13use serde::{Deserialize, Serialize};
14pub use turbo_tasks_macros::OperationValue;
15
16use crate::{
17    CollectiblesSource, RawVc, ReadVcFuture, ResolvedVc, TaskId, TaskInput, UpcastStrict, Vc,
18    VcValueTrait, VcValueTraitCast, VcValueType, marker_trait::impl_auto_marker_trait,
19    trace::TraceRawVcs, turbo_tasks,
20};
21
22/// A future returned by [`OperationVc::resolve`] that connects an [`OperationVc<T>`] and resolves
23/// it to a [`ResolvedVc<T>`].
24///
25/// Use [`.strongly_consistent()`][Self::strongly_consistent] to opt into strong consistency.
26#[must_use]
27pub struct ResolveOperationVcFuture<T>
28where
29    T: ?Sized,
30{
31    inner: super::ResolveVcFuture<T>,
32}
33
34impl<T: ?Sized> ResolveOperationVcFuture<T> {
35    /// Make the resolution strongly consistent.
36    pub fn strongly_consistent(mut self) -> Self {
37        self.inner.inner = self.inner.inner.strongly_consistent();
38        self
39    }
40}
41
42impl<T: ?Sized> Future for ResolveOperationVcFuture<T> {
43    type Output = anyhow::Result<ResolvedVc<T>>;
44
45    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
46        // SAFETY: we are not moving self
47        let this = unsafe { self.get_unchecked_mut() };
48        // ResolveVcFuture: Unpin, so Pin::new is safe
49        Pin::new(&mut this.inner)
50            .poll(cx)
51            .map(|r| r.map(|node| ResolvedVc { node }))
52    }
53}
54
55impl<T: ?Sized> Unpin for ResolveOperationVcFuture<T> {}
56
57/// A "subtype" (can be converted via [`.connect()`]) of [`Vc`] that
58/// represents a specific call (with arguments) to [a task][macro@crate::function].
59///
60/// Unlike [`Vc`], `OperationVc`:
61///
62/// - Does not potentially refer to task-local information, meaning that it implements
63///   [`NonLocalValue`], and can be used in any [`#[turbo_tasks::value]`][macro@crate::value].
64///
65/// - Has only one potential internal representation, meaning that it has a saner equality
66///   definition.
67///
68/// - Can be [reconnected][OperationVc::connect] to the strongly-consistent compilation graph after
69///   being placed inside of a [`State`].
70///
71/// - Makes sense with [collectibles][`CollectiblesSource`], as it represents a function call, and
72///   only function calls can have issues or side-effects.
73///
74///
75/// ## Equality & Hashing
76///
77/// Equality between two `OperationVc`s means that both have an identical in-memory representation
78/// and point to the same task function call. The implementation of [`Hash`] has similar behavior.
79///
80/// If [connected] and then `.await`ed at the same time, both would likely resolve to the same
81/// [`ReadRef`], though it is possible that they may not if the task or cell is invalidated between
82/// `.await`s.
83///
84/// Because equality is a synchronous operation that cannot read the cell contents, even if the
85/// `OperationVc`s are not equal, it is possible that if `.await`ed, both `OperationVc`s could point
86/// to the same or equal values.
87///
88/// [`.connect()`]: OperationVc::connect
89/// [reconnected]: OperationVc::connect
90/// [connected]: OperationVc::connect
91/// [`NonLocalValue`]: crate::NonLocalValue
92/// [`State`]: crate::State
93/// [`ReadRef`]: crate::ReadRef
94#[must_use]
95#[derive(Serialize, Deserialize, Encode, Decode)]
96#[bincode(bounds = "T: ?Sized")]
97pub struct OperationVc<T>
98where
99    T: ?Sized,
100{
101    task: TaskId,
102
103    _t: PhantomData<T>,
104}
105
106impl<T: ?Sized> OperationVc<T> {
107    /// Called by the `#[turbo_tasks::function]` macro.
108    ///
109    /// The macro ensures that the `Vc` is not a local task and it points to a single operation.
110    #[doc(hidden)]
111    #[deprecated = "This is an internal function. Use #[turbo_tasks::function(operation)] instead."]
112    pub fn cell_private(node: Vc<T>) -> Self {
113        let task = node.node.as_task_output().expect(
114            "OperationVc::cell_private must be called on the immediate return value of a task \
115             function",
116        );
117
118        Self {
119            task,
120            _t: PhantomData,
121        }
122    }
123
124    /// Marks this operation's underlying function call as a child of the current task, and returns
125    /// a [`Vc`] that can be [resolved][Vc::to_resolved] or read with `.await?`.
126    ///
127    /// By marking this function call as a child of the current task, turbo-tasks will re-run tasks
128    /// as-needed to achieve strong consistency at the root of the function call tree. This explicit
129    /// operation is needed as `OperationVc` types can be stored outside of the call graph as part
130    /// of [`State`][crate::State]s.
131    pub fn connect(self) -> Vc<T> {
132        let tt = turbo_tasks();
133        tt.connect_task(self.task);
134        Self::into_raw(self).into()
135    }
136
137    /// Returns the `RawVc` corresponding to this `OperationVc`.
138    /// inverse of [`RawVc::as_task_output`]
139    fn into_raw(vc: Self) -> RawVc {
140        RawVc::task_output(vc.task)
141    }
142
143    /// Upcasts the given `OperationVc<T>` to a `OperationVc<Box<dyn K>>`.
144    ///
145    /// This is also available as an `Into`/`From` conversion.
146    #[inline(always)]
147    pub fn upcast<K>(vc: Self) -> OperationVc<K>
148    where
149        T: UpcastStrict<K>,
150        K: VcValueTrait + ?Sized,
151    {
152        OperationVc {
153            task: vc.task,
154            _t: PhantomData,
155        }
156    }
157
158    /// [Connects the `OperationVc`][Self::connect] and resolves the reference until it points to a
159    /// cell directly.
160    ///
161    /// Resolving will wait for task execution to be finished, so that the returned [`ResolvedVc`]
162    /// points to a cell that stores a value.
163    ///
164    /// Resolving is necessary to compare identities of [`Vc`]s.
165    ///
166    /// Use [`.strongly_consistent()`][ResolveOperationVcFuture::strongly_consistent] to opt into
167    /// strong consistency.
168    pub fn resolve(self) -> ResolveOperationVcFuture<T> {
169        ResolveOperationVcFuture {
170            inner: self.connect().resolve(),
171        }
172    }
173
174    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
175    /// consistent][crate::ReadConsistency::Strong] read of the value.
176    ///
177    /// This ensures that all internal tasks are finished before the read is returned.
178    pub fn read_strongly_consistent(self) -> ReadVcFuture<T>
179    where
180        T: VcValueType,
181    {
182        self.connect().node.into_read().strongly_consistent().into()
183    }
184
185    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
186    /// consistent][crate::ReadConsistency::Strong] read of the value.
187    ///
188    /// This ensures that all internal tasks are finished before the read is returned.
189    pub fn read_trait_strongly_consistent(self) -> ReadVcFuture<T, VcValueTraitCast<T>>
190    where
191        T: VcValueTrait,
192    {
193        self.connect().into_trait_ref().strongly_consistent()
194    }
195
196    pub fn task_id(self) -> TaskId {
197        self.task
198    }
199}
200
201impl<T> Copy for OperationVc<T> where T: ?Sized {}
202
203impl<T> Clone for OperationVc<T>
204where
205    T: ?Sized,
206{
207    fn clone(&self) -> Self {
208        *self
209    }
210}
211
212impl<T> Hash for OperationVc<T>
213where
214    T: ?Sized,
215{
216    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
217        self.task.hash(state);
218    }
219}
220
221impl<T> PartialEq<OperationVc<T>> for OperationVc<T>
222where
223    T: ?Sized,
224{
225    fn eq(&self, other: &Self) -> bool {
226        self.task == other.task
227    }
228}
229
230impl<T> Eq for OperationVc<T> where T: ?Sized {}
231
232impl<T> Debug for OperationVc<T>
233where
234    T: ?Sized,
235{
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        f.debug_struct("OperationVc")
238            .field("task", &self.task)
239            .finish()
240    }
241}
242
243// NOTE: This uses the default implementation of `is_resolved` which returns `true` because we don't
244// want `OperationVc` arguments to get resolved when passed to a `#[turbo_tasks::function]`.
245impl<T> TaskInput for OperationVc<T>
246where
247    T: ?Sized + Send + Sync,
248{
249    fn is_transient(&self) -> bool {
250        self.task.is_transient()
251    }
252}
253
254impl<T> TryFrom<RawVc> for OperationVc<T>
255where
256    T: ?Sized,
257{
258    type Error = anyhow::Error;
259
260    fn try_from(raw: RawVc) -> Result<Self> {
261        let Some(task) = raw.as_task_output() else {
262            anyhow::bail!("Given RawVc {raw:?} is not a TaskOutput");
263        };
264        Ok(Self {
265            task,
266            _t: PhantomData,
267        })
268    }
269}
270
271impl<T> TraceRawVcs for OperationVc<T>
272where
273    T: ?Sized,
274{
275    fn trace_raw_vcs(&self, trace_context: &mut crate::trace::TraceRawVcsContext) {
276        Self::into_raw(*self).trace_raw_vcs(trace_context);
277    }
278}
279
280impl<T> CollectiblesSource for OperationVc<T>
281where
282    T: ?Sized,
283{
284    fn drop_collectibles<Vt: VcValueTrait>(self) {
285        let tt = turbo_tasks();
286        let map = tt.read_task_collectibles(self.task, Vt::get_trait_type_id());
287        tt.unemit_collectibles(Vt::get_trait_type_id(), &map);
288    }
289
290    fn take_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
291        let tt = turbo_tasks();
292        let map = tt.read_task_collectibles(self.task, Vt::get_trait_type_id());
293        tt.unemit_collectibles(Vt::get_trait_type_id(), &map);
294        map.into_iter()
295            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
296            .collect()
297    }
298
299    fn peek_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
300        let tt = turbo_tasks();
301        let map = tt.read_task_collectibles(self.task, Vt::get_trait_type_id());
302        map.into_iter()
303            .filter_map(|(raw, count)| (count > 0).then_some(raw.try_into().unwrap()))
304            .collect()
305    }
306}
307
308/// Indicates that a type does not contain any instances of [`Vc`] or [`ResolvedVc`]. It may contain
309/// [`OperationVc`].
310///
311/// # Safety
312///
313/// This trait is marked as unsafe. You should not derive it yourself, but instead you should rely
314/// on [`#[derive(OperationValue)]`][macro@OperationValue] to do it for you.
315pub unsafe trait OperationValue {}
316
317unsafe impl<T: ?Sized + Send> OperationValue for OperationVc<T> {}
318
319impl_auto_marker_trait!(OperationValue);