Skip to main content

turbo_tasks/
macro_helpers.rs

1//! Runtime helpers for [turbo-tasks-macro].
2
3use std::{
4    cell::SyncUnsafeCell,
5    ptr::{DynMetadata, Pointee},
6};
7
8pub use async_trait::async_trait;
9pub use bincode;
10pub use ctor;
11pub use inventory;
12use rustc_hash::FxHashMap;
13pub use shrink_to_fit;
14pub use tracing;
15
16#[cfg(debug_assertions)]
17use crate::debug::ValueDebugFormatString;
18use crate::{NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitType, ValueType, ValueTypeId};
19pub use crate::{
20    dyn_task_inputs::DynTaskInputs,
21    global_name_for_method, global_name_for_scope, global_name_for_trait_method,
22    global_name_for_trait_method_impl, global_name_for_type, inventory_submit,
23    manager::{find_cell_by_id, find_cell_by_type, spawn_detached_for_testing},
24    native_function::{
25        ArgMeta, NativeFunction, VTABLE_DEFAULT, downcast_args_owned, downcast_args_ref,
26        downcast_stack_args_owned,
27    },
28    registry::RegistryDef,
29    task::function::{into_task_fn, into_task_fn_with_this},
30    turbo_register,
31    value_type::{TraitVtablePrototype, build_trait_vtable, index_of_method_name},
32};
33
34#[cfg(debug_assertions)]
35#[inline(never)]
36pub async fn value_debug_format_field(value: ValueDebugFormatString<'_>) -> String {
37    match value.try_to_string().await {
38        Ok(result) => result,
39        Err(err) => format!("{err:?}"),
40    }
41}
42
43pub fn get_persistence_from_inputs(inputs: &impl TaskInput) -> TaskPersistence {
44    if inputs.is_transient() {
45        TaskPersistence::Transient
46    } else {
47        TaskPersistence::Persistent
48    }
49}
50
51pub fn get_persistence_from_inputs_and_this(
52    this: RawVc,
53    inputs: &impl TaskInput,
54) -> TaskPersistence {
55    if this.is_transient() || inputs.is_transient() {
56        TaskPersistence::Transient
57    } else {
58        TaskPersistence::Persistent
59    }
60}
61
62pub fn assert_argument_is_non_local_value<Argument: NonLocalValue>() {}
63
64#[macro_export]
65macro_rules! stringify_path {
66    ($path:path) => {
67        stringify!($path)
68    };
69}
70
71/// Rexport std::ptr::metadata so not every crate needs to enable the feature when they use our
72/// macros.
73#[inline(always)]
74pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata {
75    // Ideally we would just `pub use std::ptr::metadata;` but this doesn't seem to work.
76    std::ptr::metadata(ptr)
77}
78
79/// Const wrapper around `std::any::type_name` so downstream crates don't need to enable the
80/// unstable `const_type_name` feature.
81#[doc(hidden)]
82pub const fn const_type_name<T: ?Sized>() -> &'static str {
83    std::any::type_name::<T>()
84}
85
86/// Compute the total byte length of all string slices.
87#[doc(hidden)]
88pub const fn const_concat_len(slices: &[&str]) -> usize {
89    let mut total = 0;
90    let mut i = 0;
91    while i < slices.len() {
92        total += slices[i].len();
93        i += 1;
94    }
95    total
96}
97
98/// Copy all string slices into a fixed-size byte array at compile time.
99#[doc(hidden)]
100pub const fn const_concat_into<const N: usize>(slices: &[&str]) -> [u8; N] {
101    let mut buf = [0u8; N];
102    let mut pos = 0;
103    let mut i = 0;
104    while i < slices.len() {
105        let bytes = slices[i].as_bytes();
106        let (_, rest) = buf.split_at_mut(pos);
107        let (dst, _) = rest.split_at_mut(bytes.len());
108        dst.copy_from_slice(bytes);
109        pos += bytes.len();
110        i += 1;
111    }
112    assert!(pos == N, "const_concat: length mismatch");
113    buf
114}
115
116/// Concatenate a const slice of `&str` into a single `&'static str` at compile time.
117///
118/// This is a macro only because const generics require the length to be a const expression
119/// computed from the input. The call sites look like normal function calls:
120///
121/// ```ignore
122/// const_concat!(&[type_name, "::", method_name])
123/// ```
124#[doc(hidden)]
125#[macro_export]
126macro_rules! const_concat {
127    ($slices:expr) => {{
128        const SLICES: &[&str] = $slices;
129        const LEN: usize = $crate::macro_helpers::const_concat_len(SLICES);
130        const BYTES: [u8; LEN] = $crate::macro_helpers::const_concat_into(SLICES);
131        // SAFETY: all inputs are valid UTF-8 strings, concatenation preserves UTF-8
132        const STR: &str = unsafe { ::std::str::from_utf8_unchecked(&BYTES) };
133        STR
134    }};
135}
136
137/// Const fn that strips `count` trailing `::component` segments from a string.
138/// Used by `global_name_for_scope!` to extract the module path from a `type_name`.
139#[doc(hidden)]
140pub const fn strip_trailing_segments(s: &str, count: usize) -> &str {
141    let mut remaining = s;
142    let mut i = 0;
143    while i < count {
144        let bytes = remaining.as_bytes();
145        if bytes.len() < 2 {
146            return s;
147        }
148        let mut pos = bytes.len();
149        loop {
150            if pos < 2 {
151                return s;
152            }
153            pos -= 1;
154            if bytes[pos] == b':' && bytes[pos - 1] == b':' {
155                (remaining, _) = remaining.split_at(pos - 1);
156                break;
157            }
158        }
159        i += 1;
160    }
161    remaining
162}
163
164/// A registry of all the impl vtables for a given VcValue trait.
165///
166/// `const`-constructed as a plain `static`. Populated in two phases:
167///
168/// 1. **ctor phase** (before `main`): `value_impl`-emitted `#[ctor::ctor]` functions push
169///    `(value_type, DynMetadata)` pairs into `pending`. `ValueType` ids are not yet assigned here —
170///    we just accumulate.
171/// 2. **`finalize` phase** (inside the `VALUES` `LazyLock` initializer, after ids are assigned):
172///    `pending` is drained into `inner`, keyed by `ValueTypeId`. Idempotent.
173///
174/// After finalize, the cast path is a single hashmap `.get()` keyed by `u16` — no `LazyLock`
175/// check, no `get_value_type(id)` indirection. The vtable pointer for each
176/// `impl Trait for Concrete` is materialized at compile time, so there's no runtime `transmute`
177/// or indirect fn-pointer call either.
178pub struct VTableRegistry<T>
179where
180    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
181{
182    /// Accumulator written from ctors. `Some` until `finalize` runs, then `take`n and left
183    /// `None` so any post-finalize call to `register` panics rather than silently dropping
184    /// the registration. The `None` state also makes subsequent `finalize` calls cheap no-ops.
185    #[allow(clippy::type_complexity)]
186    pending: SyncUnsafeCell<Option<Vec<(&'static ValueType, DynMetadata<T>)>>>,
187    /// Built once by `finalize`, read-only thereafter. `None` until finalize runs.
188    inner: SyncUnsafeCell<Option<FxHashMap<ValueTypeId, DynMetadata<T>>>>,
189}
190
191// SAFETY: writes to `pending` happen only from `#[ctor::ctor]` functions, which run serially on
192// the main thread before `main`. Writes to `inner` happen only from `finalize`, which runs once
193// inside the `VALUES` `LazyLock` initializer (synchronized by the `LazyLock`). Reads of `inner`
194// from `cast` are published by that same `LazyLock` — see the `cast` safety comment.
195unsafe impl<T> Sync for VTableRegistry<T> where T: Pointee<Metadata = DynMetadata<T>> + ?Sized {}
196
197impl<T> VTableRegistry<T>
198where
199    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
200{
201    pub const fn new() -> Self {
202        Self {
203            pending: SyncUnsafeCell::new(Some(Vec::new())),
204            inner: SyncUnsafeCell::new(None),
205        }
206    }
207
208    /// Queue an `impl Trait for Concrete` registration. Called from a `#[ctor::ctor]` function
209    /// at program load, before `ValueType` ids are assigned. The actual map is built later by
210    /// [`Self::finalize`].
211    ///
212    /// Panics if called after `finalize`. Since all `value_impl` ctors run before `main` and
213    /// `finalize` runs lazily after `main`, this should be unreachable in normal use; the
214    /// panic guards against future changes that might violate that ordering.
215    pub fn register(&'static self, value_type: &'static ValueType, fat_ptr: *const T) {
216        // SAFETY: ctors run single-threaded at load; no concurrent readers or writers.
217        let pending = unsafe { &mut *self.pending.get() };
218        let Some(pending) = pending.as_mut() else {
219            panic!("VTableRegistry::register called after finalize for {value_type}");
220        };
221        pending.push((value_type, std::ptr::metadata(fat_ptr)));
222    }
223
224    /// Take `pending` into `inner`, resolving each `&'static ValueType` to its assigned
225    /// `ValueTypeId`. Must be called after `VALUES` ids are assigned (i.e. from inside the
226    /// `VALUES` `LazyLock` initializer). Idempotent: a second call is a no-op, which lets
227    /// `register_all_trait_methods` invoke `finalize_vtable_registry` once per impl without
228    /// having to dedup.
229    pub fn finalize(&'static self) {
230        // SAFETY: invoked from inside the `VALUES` `LazyLock` initializer (single-threaded
231        // section synchronized by the `LazyLock`). All ctors completed before `main`.
232        let pending = unsafe { &mut *self.pending.get() };
233        // `take` makes any later `register` call (which would otherwise be silently dropped)
234        // hit the `None` branch and panic; subsequent `finalize` calls also short-circuit here.
235        let Some(pending) = pending.take() else {
236            return;
237        };
238        let inner = unsafe { &mut *self.inner.get() };
239        debug_assert!(inner.is_none(), "inner already populated");
240        let mut map = FxHashMap::with_capacity_and_hasher(pending.len(), Default::default());
241        for (value_type, metadata) in pending {
242            // SAFETY: we're called from inside the `VALUES` `LazyLock` initializer, after
243            // `init_registry` has assigned ids. Calling `get_value_type_id` here would re-enter
244            // `LazyLock::force` and deadlock; read the id cell directly instead.
245            let id = unsafe { crate::registry::get_value_type_id_unchecked(value_type) };
246            let prev = map.insert(id, metadata);
247            debug_assert!(
248                prev.is_none(),
249                "multiple trait impls registered for {value_type}"
250            );
251        }
252        *inner = Some(map);
253    }
254
255    pub(crate) fn cast(&self, id: ValueTypeId, raw: *const ()) -> *const T {
256        // SAFETY: any caller in possession of a `ValueTypeId` must have already forced the
257        // `VALUES` `LazyLock` (that's the only way to obtain one). `finalize` ran inside that
258        // initializer, so its write to `inner` happens-before this read via the `LazyLock`'s
259        // acquire fence.
260        let inner = unsafe { &*self.inner.get() };
261        let Some(metadata) = inner.as_ref().and_then(|map| map.get(&id)) else {
262            panic!(
263                "no trait impl registered for value type {}",
264                crate::registry::get_value_type(id)
265            )
266        };
267        std::ptr::from_raw_parts(raw, *metadata)
268    }
269}
270
271impl<T> Default for VTableRegistry<T>
272where
273    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
274{
275    fn default() -> Self {
276        Self::new()
277    }
278}
279
280pub struct CollectableTraitMethods {
281    pub value_type: &'static ValueType,
282    pub trait_type: &'static TraitType,
283    pub methods: &'static [&'static NativeFunction],
284    /// Calls `<Box<dyn Trait>>::IMPL_VTABLES.finalize()` for the trait this entry corresponds
285    /// to. Invoked from `register_all_trait_methods` after `VALUES` ids are assigned. The same
286    /// trait's registry will be finalized once per impl, but `VTableRegistry::finalize` is
287    /// idempotent.
288    pub finalize_vtable_registry: fn(),
289}
290inventory::collect! {CollectableTraitMethods}
291
292/// Submit an item to the inventory.
293///
294/// This macro is a wrapper around `inventory::submit` that adds a `#[not(cfg(rust_analyzer))]`
295/// attribute to the item. This is to avoid warnings about unused items when using Rust Analyzer.
296#[doc(hidden)]
297#[macro_export]
298macro_rules! inventory_submit {
299    ($($item:tt)*) => {
300        #[cfg(not(rust_analyzer))]
301        $crate::macro_helpers::inventory_submit_inner! { $($item)* }
302    }
303}
304
305/// Exported so the above macro can reference it.
306#[doc(hidden)]
307pub use inventory::submit as inventory_submit_inner;
308
309/// Use `type_name` to get globally unique identifier that's stable across multiple executions of
310/// the same Turbopack version, potentially allowing cache sharing across platforms/architectures.
311///
312/// The stdlib docs explicitly recommend against using type_name to get a unique identifier, but the
313/// way we're using it here seems unlikely to break. We've got runtime logic to panic if it breaks.
314#[doc(hidden)]
315#[macro_export]
316macro_rules! global_name_for_type {
317    ($item:ty) => {
318        $crate::macro_helpers::const_type_name::<$item>()
319    };
320}
321
322#[doc(hidden)]
323#[macro_export]
324macro_rules! global_name_for_method {
325    ($ty:ty, $method:ident) => {
326        $crate::const_concat!(&[
327            $crate::macro_helpers::const_type_name::<$ty>(),
328            "::",
329            ::std::stringify!($method),
330        ])
331    };
332}
333
334#[doc(hidden)]
335#[macro_export]
336macro_rules! global_name_for_trait_method {
337    ($trait:path, $method:ident) => {
338        $crate::const_concat!(&[
339            "<",
340            $crate::macro_helpers::const_type_name::<dyn $trait>(),
341            ">::",
342            ::std::stringify!($method),
343        ])
344    };
345}
346
347#[doc(hidden)]
348#[macro_export]
349macro_rules! global_name_for_trait_method_impl {
350    ($ty:ty, $trait:path, $method:ident) => {
351        $crate::const_concat!(&[
352            "<",
353            $crate::macro_helpers::const_type_name::<$ty>(),
354            " as ",
355            $crate::macro_helpers::const_type_name::<dyn $trait>(),
356            ">::",
357            ::std::stringify!($method),
358        ])
359    };
360}
361
362/// Get a globally unique name for an identifier in a current or parent scope.
363#[doc(hidden)]
364#[macro_export]
365macro_rules! global_name_for_scope {
366    ($depth:literal, $($item:tt)+) => {{
367        struct PlaceholderMarkerType;
368        $crate::const_concat!(&[
369            $crate::macro_helpers::strip_trailing_segments(
370                $crate::macro_helpers::const_type_name::<PlaceholderMarkerType>(),
371                $depth + 1,  // add one for the placeholder
372            ),
373            "::",
374            ::std::stringify!($($item)+),
375        ])
376    }}
377}