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().node.into_read().strongly_consistent().into()
175 }
176
177 /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
178 /// consistent][crate::ReadConsistency::Strong] read of the value.
179 ///
180 /// This ensures that all internal tasks are finished before the read is returned.
181 pub fn read_trait_strongly_consistent(self) -> ReadVcFuture<T, VcValueTraitCast<T>>
182 where
183 T: VcValueTrait,
184 {
185 self.connect().into_trait_ref().strongly_consistent()
186 }
187}
188
189impl<T> Copy for OperationVc<T> where T: ?Sized {}
190
191impl<T> Clone for OperationVc<T>
192where
193 T: ?Sized,
194{
195 fn clone(&self) -> Self {
196 *self
197 }
198}
199
200impl<T> Hash for OperationVc<T>
201where
202 T: ?Sized,
203{
204 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
205 self.node.hash(state);
206 }
207}
208
209impl<T> PartialEq<OperationVc<T>> for OperationVc<T>
210where
211 T: ?Sized,
212{
213 fn eq(&self, other: &Self) -> bool {
214 self.node == other.node
215 }
216}
217
218impl<T> Eq for OperationVc<T> where T: ?Sized {}
219
220impl<T> Debug for OperationVc<T>
221where
222 T: ?Sized,
223{
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 f.debug_struct("OperationVc")
226 .field("node", &self.node.node)
227 .finish()
228 }
229}
230
231// NOTE: This uses the default implementation of `is_resolved` which returns `true` because we don't
232// want `OperationVc` arguments to get resolved when passed to a `#[turbo_tasks::function]`.
233impl<T> TaskInput for OperationVc<T>
234where
235 T: ?Sized + Send + Sync,
236{
237 fn is_transient(&self) -> bool {
238 self.node.is_transient()
239 }
240}
241
242impl<T> TryFrom<RawVc> for OperationVc<T>
243where
244 T: ?Sized,
245{
246 type Error = anyhow::Error;
247
248 fn try_from(raw: RawVc) -> Result<Self> {
249 if !matches!(raw, RawVc::TaskOutput(..)) {
250 anyhow::bail!("Given RawVc {raw:?} is not a TaskOutput");
251 }
252 Ok(Self {
253 node: Vc::from(raw),
254 })
255 }
256}
257
258impl<T> TraceRawVcs for OperationVc<T>
259where
260 T: ?Sized,
261{
262 fn trace_raw_vcs(&self, trace_context: &mut crate::trace::TraceRawVcsContext) {
263 self.node.trace_raw_vcs(trace_context);
264 }
265}
266
267impl<T> CollectiblesSource for OperationVc<T>
268where
269 T: ?Sized,
270{
271 fn drop_collectibles<Vt: VcValueTrait>(self) {
272 self.node.node.drop_collectibles::<Vt>();
273 }
274
275 fn take_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
276 self.node.node.take_collectibles()
277 }
278
279 fn peek_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
280 self.node.node.peek_collectibles()
281 }
282}
283
284/// Indicates that a type does not contain any instances of [`Vc`] or [`ResolvedVc`]. It may contain
285/// [`OperationVc`].
286///
287/// # Safety
288///
289/// This trait is marked as unsafe. You should not derive it yourself, but instead you should rely
290/// on [`#[derive(OperationValue)]`][macro@OperationValue] to do it for you.
291pub unsafe trait OperationValue {}
292
293unsafe impl<T: ?Sized + Send> OperationValue for OperationVc<T> {}
294
295impl_auto_marker_trait!(OperationValue);