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