turbo_tasks/
macro_helpers.rs

1//! Runtime helpers for [turbo-tasks-macro].
2
3pub use async_trait::async_trait;
4pub use once_cell::sync::{Lazy, OnceCell};
5use rustc_hash::FxHashMap;
6pub use serde;
7pub use shrink_to_fit;
8pub use tracing;
9
10use crate::{
11    FxDashMap, NonLocalValue, RawVc, TaskInput, TaskPersistence, TraitTypeId, ValueType,
12    ValueTypeId, Vc, debug::ValueDebugFormatString, task::TaskOutput,
13};
14pub use crate::{
15    global_name, inventory_submit,
16    magic_any::MagicAny,
17    manager::{find_cell_by_type, spawn_detached_for_testing},
18    native_function::{
19        CollectableFunction, NativeFunction, downcast_args_owned, downcast_args_ref,
20    },
21    value_type::{CollectableTrait, CollectableValueType},
22};
23
24#[inline(never)]
25pub async fn value_debug_format_field(value: ValueDebugFormatString<'_>) -> String {
26    match value.try_to_value_debug_string().await {
27        Ok(result) => match result.await {
28            Ok(result) => result.to_string(),
29            Err(err) => format!("{err:?}"),
30        },
31        Err(err) => format!("{err:?}"),
32    }
33}
34
35pub fn get_persistence_from_inputs(inputs: &impl TaskInput) -> TaskPersistence {
36    if inputs.is_transient() {
37        TaskPersistence::Transient
38    } else {
39        TaskPersistence::Persistent
40    }
41}
42
43pub fn get_persistence_from_inputs_and_this(
44    this: RawVc,
45    inputs: &impl TaskInput,
46) -> TaskPersistence {
47    if this.is_transient() || inputs.is_transient() {
48        TaskPersistence::Transient
49    } else {
50        TaskPersistence::Persistent
51    }
52}
53
54pub fn assert_returns_non_local_value<ReturnType, Rv>()
55where
56    ReturnType: TaskOutput<Return = Vc<Rv>>,
57    Rv: NonLocalValue + Send,
58{
59}
60
61pub fn assert_argument_is_non_local_value<Argument: NonLocalValue>() {}
62
63#[macro_export]
64macro_rules! stringify_path {
65    ($path:path) => {
66        stringify!($path)
67    };
68}
69
70/// Rexport std::ptr::metadata so not every crate needs to enable the feature when they use our
71/// macros.
72#[inline(always)]
73pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as std::ptr::Pointee>::Metadata {
74    // Ideally we would just `pub use std::ptr::metadata;` but this doesn't seem to work.
75    std::ptr::metadata(ptr)
76}
77
78/// A registry of all the impl vtables for a given VcValue trait
79/// This is constructed in the macro gencode and populated by the registry.
80#[derive(Default)]
81pub struct VTableRegistry<T: ?Sized> {
82    map: FxHashMap<ValueTypeId, <T as std::ptr::Pointee>::Metadata>,
83}
84
85impl<T: ?Sized> VTableRegistry<T> {
86    pub fn new(id: TraitTypeId) -> Self {
87        let mut map = FxHashMap::default();
88        match TRAIT_CAST_FNS.remove(&id) {
89            Some((_, impls)) => {
90                for (value_type_id, RawPtr(raw_fn)) in impls {
91                    // SAFETY: These are generated by the macro gencode in value_impl with this
92                    // signature.
93                    let cast_fn: fn(*const ()) -> *const T = unsafe { std::mem::transmute(raw_fn) };
94                    // Cast a null pointer to a fat pointer using the cast_fn, this allows us to
95                    // capture a vtable Alternatively we could just store the
96                    // cast functions but it will be faster to call 'from_raw_parts' instead of an
97                    // indirect function call.
98                    let ptr = cast_fn(std::ptr::null::<()>());
99                    let metadata = std::ptr::metadata(ptr);
100                    let prev = map.insert(value_type_id, metadata);
101                    debug_assert!(
102                        prev.is_none(),
103                        "multiple cast functions registered for {value_type_id}"
104                    )
105                }
106            }
107            None => {
108                // A trait doesn't have to have any implementations.
109            }
110        }
111
112        Self { map }
113    }
114
115    pub(crate) fn cast(&self, id: ValueTypeId, raw: *const ()) -> *const T {
116        let metadata = self.map.get(&id).unwrap();
117        std::ptr::from_raw_parts(raw, *metadata)
118    }
119}
120
121struct RawPtr(*const ());
122// SAFETY: We only store function pointers in here which are safe to send/sync
123unsafe impl Sync for RawPtr {}
124unsafe impl Send for RawPtr {}
125
126// Accumulate all trait impls by trait id
127static TRAIT_CAST_FNS: Lazy<FxDashMap<TraitTypeId, Vec<(ValueTypeId, RawPtr)>>> = Lazy::new(|| {
128    let map: FxDashMap<TraitTypeId, Vec<(ValueTypeId, RawPtr)>> = FxDashMap::default();
129    for CollectableTraitCastFunctions(trait_id_fn, value_id_fn, cast_fn) in
130        inventory::iter::<CollectableTraitCastFunctions>
131    {
132        map.entry(trait_id_fn())
133            .or_default()
134            .value_mut()
135            .push((value_id_fn(), RawPtr(*cast_fn)));
136    }
137    map
138});
139
140// Holds a raw pointer to a function that can perform a fat pointer cast
141pub struct CollectableTraitCastFunctions(
142    pub fn() -> TraitTypeId,
143    pub fn() -> ValueTypeId,
144    pub *const (),
145);
146// SAFETY: We only store function pointers in here.
147unsafe impl Sync for CollectableTraitCastFunctions {}
148inventory::collect! {CollectableTraitCastFunctions}
149
150#[allow(clippy::type_complexity)]
151pub struct CollectableTraitMethods(
152    // A value type name
153    pub &'static str,
154    pub fn() -> (TraitTypeId, Vec<(&'static str, &'static NativeFunction)>),
155);
156inventory::collect! {CollectableTraitMethods}
157
158// Called when initializing ValueTypes by value_impl
159pub fn register_trait_methods(value: &mut ValueType) {
160    #[allow(clippy::type_complexity)]
161    static TRAIT_METHODS_BY_VALUE: Lazy<
162        FxDashMap<&'static str, Vec<(TraitTypeId, Vec<(&'static str, &'static NativeFunction)>)>>,
163    > = Lazy::new(|| {
164        let map: FxDashMap<&'static str, Vec<_>> = FxDashMap::default();
165        for CollectableTraitMethods(value_name, thunk) in inventory::iter::<CollectableTraitMethods>
166        {
167            map.entry(*value_name).or_default().push(thunk());
168        }
169        map
170    });
171    match TRAIT_METHODS_BY_VALUE.remove(value.global_name) {
172        Some((_, traits)) => {
173            for (trait_type_id, methods) in traits {
174                let trait_type = crate::registry::get_trait(trait_type_id);
175                value.register_trait(trait_type_id);
176                for (name, method) in methods {
177                    value.register_trait_method(trait_type.get(name), method);
178                }
179            }
180        }
181        None => {
182            // do nothing, values don't have to implement any traits
183        }
184    }
185}
186
187/// Submit an item to the inventory.
188///
189/// This macro is a wrapper around `inventory::submit` that adds a `#[not(cfg(rust_analyzer))]`
190/// attribute to the item. This is to avoid warnings about unused items when using Rust Analyzer.
191#[macro_export]
192macro_rules! inventory_submit {
193    ($($item:tt)*) => {
194        #[cfg(not(rust_analyzer))]
195        $crate::macro_helpers::inventory_submit_inner! { $($item)* }
196    }
197}
198
199/// Exported so the above macro can reference it.
200#[doc(hidden)]
201pub use inventory::submit as inventory_submit_inner;
202
203/// Define a global name for a turbo-tasks value.
204#[cfg(not(rust_analyzer))] // ignore-rust-analyzer due to https://github.com/rust-lang/rust-analyzer/issues/19993
205#[macro_export]
206macro_rules! global_name {
207    ($($item:tt)*) => {
208
209        ::std::concat!(::std::env!("CARGO_PKG_NAME"), "@", ::std::module_path!(), "::", $($item)*)
210    }
211}
212/// Define a global name for a turbo-tasks value.
213/// This has a dummy implementation for Rust Analyzer to avoid https://github.com/rust-lang/rust-analyzer/issues/19993
214#[cfg(rust_analyzer)]
215#[macro_export]
216macro_rules! global_name {
217    ($($item:tt)*) => {
218        $($item)*
219    }
220}