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;
10use rustc_hash::FxHashMap;
11pub use scattered_collect;
12use scattered_collect::slice::ScatteredSlice;
13pub use shrink_to_fit;
14pub use tracing;
15
16#[cfg(debug_assertions)]
17use crate::debug::ValueDebugFormatString;
18use crate::{
19    InputResolution, NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitType, ValueType,
20    ValueTypeId,
21};
22pub use crate::{
23    dyn_task_inputs::DynTaskInputs,
24    global_name_for_method, global_name_for_scope, global_name_for_trait_method,
25    global_name_for_trait_method_impl, global_name_for_type,
26    manager::{find_cell_by_id, find_cell_by_type, spawn_detached_for_testing},
27    native_function::{
28        ArgMeta, NativeFunction, VTABLE_DEFAULT, downcast_args_owned, downcast_args_ref,
29        downcast_stack_args_owned,
30    },
31    register_function, register_trait, register_value,
32    registry::RegistryDef,
33    task::function::{into_task_fn, into_task_fn_with_this},
34    value_type::{TraitVtablePrototype, build_trait_vtable, index_of_method_name},
35};
36
37#[cfg(debug_assertions)]
38#[inline(never)]
39pub async fn value_debug_format_field(value: ValueDebugFormatString<'_>) -> String {
40    match value.try_to_string().await {
41        Ok(result) => result,
42        Err(err) => format!("{err:?}"),
43    }
44}
45
46pub fn get_persistence_from_inputs(inputs: &impl TaskInput) -> TaskPersistence {
47    if inputs.is_transient() {
48        TaskPersistence::Transient
49    } else {
50        TaskPersistence::Persistent
51    }
52}
53
54/// Computes `TaskInput::is_resolved` for the call's inputs at the macro-generated callsite, on
55/// the fully concrete tuple type, returning it as an [`InputResolution`].  Computing it here keeps
56/// `is_resolved()` inlinable/const-foldable and avoids the macro gencode needing to import the
57/// type.
58#[inline(always)]
59pub fn input_resolution(inputs: &impl TaskInput) -> InputResolution {
60    InputResolution::from_is_resolved(inputs.is_resolved())
61}
62
63pub fn get_persistence_from_inputs_and_this(
64    this: RawVc,
65    inputs: &impl TaskInput,
66) -> TaskPersistence {
67    if this.is_transient() || inputs.is_transient() {
68        TaskPersistence::Transient
69    } else {
70        TaskPersistence::Persistent
71    }
72}
73
74pub fn assert_argument_is_non_local_value<Argument: NonLocalValue>() {}
75
76#[macro_export]
77macro_rules! stringify_path {
78    ($path:path) => {
79        stringify!($path)
80    };
81}
82
83/// Rexport std::ptr::metadata so not every crate needs to enable the feature when they use our
84/// macros.
85#[inline(always)]
86pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata {
87    // Ideally we would just `pub use std::ptr::metadata;` but this doesn't seem to work.
88    std::ptr::metadata(ptr)
89}
90
91/// Const wrapper around `std::any::type_name` so downstream crates don't need to enable the
92/// unstable `const_type_name` feature.
93#[doc(hidden)]
94pub const fn const_type_name<T: ?Sized>() -> &'static str {
95    std::any::type_name::<T>()
96}
97
98/// Compute the total byte length of all string slices.
99#[doc(hidden)]
100pub const fn const_concat_len(slices: &[&str]) -> usize {
101    let mut total = 0;
102    let mut i = 0;
103    while i < slices.len() {
104        total += slices[i].len();
105        i += 1;
106    }
107    total
108}
109
110/// Copy all string slices into a fixed-size byte array at compile time.
111#[doc(hidden)]
112pub const fn const_concat_into<const N: usize>(slices: &[&str]) -> [u8; N] {
113    let mut buf = [0u8; N];
114    let mut pos = 0;
115    let mut i = 0;
116    while i < slices.len() {
117        let bytes = slices[i].as_bytes();
118        let (_, rest) = buf.split_at_mut(pos);
119        let (dst, _) = rest.split_at_mut(bytes.len());
120        dst.copy_from_slice(bytes);
121        pos += bytes.len();
122        i += 1;
123    }
124    assert!(pos == N, "const_concat: length mismatch");
125    buf
126}
127
128/// Concatenate a const slice of `&str` into a single `&'static str` at compile time.
129///
130/// This is a macro only because const generics require the length to be a const expression
131/// computed from the input. The call sites look like normal function calls:
132///
133/// ```ignore
134/// const_concat!(&[type_name, "::", method_name])
135/// ```
136#[doc(hidden)]
137#[macro_export]
138macro_rules! const_concat {
139    ($slices:expr) => {{
140        const SLICES: &[&str] = $slices;
141        const LEN: usize = $crate::macro_helpers::const_concat_len(SLICES);
142        const BYTES: [u8; LEN] = $crate::macro_helpers::const_concat_into(SLICES);
143        // SAFETY: all inputs are valid UTF-8 strings, concatenation preserves UTF-8
144        const STR: &str = unsafe { ::std::str::from_utf8_unchecked(&BYTES) };
145        STR
146    }};
147}
148
149/// Const fn that strips `count` trailing `::component` segments from a string.
150/// Used by `global_name_for_scope!` to extract the module path from a `type_name`.
151#[doc(hidden)]
152pub const fn strip_trailing_segments(s: &str, count: usize) -> &str {
153    let mut remaining = s;
154    let mut i = 0;
155    while i < count {
156        let bytes = remaining.as_bytes();
157        if bytes.len() < 2 {
158            return s;
159        }
160        let mut pos = bytes.len();
161        loop {
162            if pos < 2 {
163                return s;
164            }
165            pos -= 1;
166            if bytes[pos] == b':' && bytes[pos - 1] == b':' {
167                (remaining, _) = remaining.split_at(pos - 1);
168                break;
169            }
170        }
171        i += 1;
172    }
173    remaining
174}
175
176/// A registry of all the impl vtables for a given VcValue trait.
177pub struct VTableRegistry<T>
178where
179    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
180{
181    /// Built once during `register_all_trait_methods`, read-only thereafter. `None` until that
182    /// runs.
183    inner: SyncUnsafeCell<Option<FxHashMap<ValueTypeId, DynMetadata<T>>>>,
184}
185
186// SAFETY: writes to `inner` happen only from `insert`, which is called only from
187// `register_all_trait_methods` (inside the `VALUES` `LazyLock` initializer, single-threaded and
188// synchronized by the `LazyLock`). Reads of `inner` from `cast` are published by that same
189// `LazyLock` — see the `cast` safety comment.
190unsafe impl<T> Sync for VTableRegistry<T> where T: Pointee<Metadata = DynMetadata<T>> + ?Sized {}
191
192impl<T> VTableRegistry<T>
193where
194    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
195{
196    pub const fn new() -> Self {
197        Self {
198            inner: SyncUnsafeCell::new(None),
199        }
200    }
201
202    /// Insert one `impl Trait for Concrete`'s vtable metadata, keyed by `ValueTypeId`. Called from
203    /// a [`TraitImplRecord::install_vtable`] thunk during `register_all_trait_methods`, after
204    /// `init_registry` has assigned ids.
205    ///
206    /// The `DynMetadata` is produced by the caller via [`metadata`] (the null-fat-ptr trick) so
207    /// downstream crates that invoke `value_impl` don't need `#![feature(ptr_metadata)]` — they
208    /// pass the value through without ever naming the `DynMetadata` type.
209    pub fn insert(&'static self, id: ValueTypeId, metadata: DynMetadata<T>) {
210        // SAFETY: called only from `register_all_trait_methods` inside the `VALUES` `LazyLock`
211        // initializer — single-threaded, no concurrent readers or writers.
212        let inner = unsafe { &mut *self.inner.get() };
213        let map = inner.get_or_insert_with(FxHashMap::default);
214        let prev = map.insert(id, metadata);
215        debug_assert!(
216            prev.is_none(),
217            "multiple trait impls registered for value type id {id}"
218        );
219    }
220
221    pub(crate) fn cast(&self, id: ValueTypeId, raw: *const ()) -> *const T {
222        // SAFETY: any caller in possession of a `ValueTypeId` must have already forced the
223        // `VALUES` `LazyLock` (that's the only way to obtain one). `register_all_trait_methods`
224        // ran inside that initializer, so its writes to `inner` happen-before this read via the
225        // `LazyLock`'s acquire fence.
226        let inner = unsafe { &*self.inner.get() };
227        let Some(metadata) = inner.as_ref().and_then(|map| map.get(&id)) else {
228            panic!(
229                "no trait impl registered for value type {}",
230                crate::registry::get_value_type(id)
231            )
232        };
233        std::ptr::from_raw_parts(raw, *metadata)
234    }
235}
236
237impl<T> Default for VTableRegistry<T>
238where
239    T: Pointee<Metadata = DynMetadata<T>> + ?Sized,
240{
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246/// One `impl Trait for ConcreteType` registration, gathered at link time into
247/// [`TRAIT_IMPLS_SLICE`].
248pub struct TraitImplRecord {
249    pub value_type: &'static ValueType,
250    pub trait_type: &'static TraitType,
251    pub methods: &'static [&'static NativeFunction],
252    /// Installs this impl's Rust vtable `DynMetadata` into its trait's [`VTableRegistry`] (via
253    /// [`VTableRegistry::insert`]).
254    pub install_vtable: fn(ValueTypeId),
255}
256
257// Link-time collection of every `impl Trait for Concrete`. Like the definition slices in
258// `registry`, this is populated by the linker — complete at process start, no constructors, no
259// ordering. It is iterated exactly once, in `register_all_trait_methods`.
260//
261// `pub` so the `value_impl`-emitted scatter (which expands in downstream crates) can name it as
262// `$crate::macro_helpers::TRAIT_IMPLS_SLICE`; the data is `#[doc(hidden)]`.
263#[doc(hidden)]
264#[scattered_collect::gather]
265pub static TRAIT_IMPLS_SLICE: ScatteredSlice<TraitImplRecord>;
266
267/// Use `type_name` to get globally unique identifier that's stable across multiple executions of
268/// the same Turbopack version, potentially allowing cache sharing across platforms/architectures.
269///
270/// The stdlib docs explicitly recommend against using type_name to get a unique identifier, but the
271/// way we're using it here seems unlikely to break. We've got runtime logic to panic if it breaks.
272#[doc(hidden)]
273#[macro_export]
274macro_rules! global_name_for_type {
275    ($item:ty) => {
276        $crate::macro_helpers::const_type_name::<$item>()
277    };
278}
279
280#[doc(hidden)]
281#[macro_export]
282macro_rules! global_name_for_method {
283    ($ty:ty, $method:ident) => {
284        $crate::const_concat!(&[
285            $crate::macro_helpers::const_type_name::<$ty>(),
286            "::",
287            ::std::stringify!($method),
288        ])
289    };
290}
291
292#[doc(hidden)]
293#[macro_export]
294macro_rules! global_name_for_trait_method {
295    ($trait:path, $method:ident) => {
296        $crate::const_concat!(&[
297            "<",
298            $crate::macro_helpers::const_type_name::<dyn $trait>(),
299            ">::",
300            ::std::stringify!($method),
301        ])
302    };
303}
304
305#[doc(hidden)]
306#[macro_export]
307macro_rules! global_name_for_trait_method_impl {
308    ($ty:ty, $trait:path, $method:ident) => {
309        $crate::const_concat!(&[
310            "<",
311            $crate::macro_helpers::const_type_name::<$ty>(),
312            " as ",
313            $crate::macro_helpers::const_type_name::<dyn $trait>(),
314            ">::",
315            ::std::stringify!($method),
316        ])
317    };
318}
319
320/// Get a globally unique name for an identifier in a current or parent scope.
321#[doc(hidden)]
322#[macro_export]
323macro_rules! global_name_for_scope {
324    ($depth:literal, $($item:tt)+) => {{
325        struct PlaceholderMarkerType;
326        $crate::const_concat!(&[
327            $crate::macro_helpers::strip_trailing_segments(
328                $crate::macro_helpers::const_type_name::<PlaceholderMarkerType>(),
329                $depth + 1,  // add one for the placeholder
330            ),
331            "::",
332            ::std::stringify!($($item)+),
333        ])
334    }}
335}