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