Skip to main content

turbo_tasks/registry/
mod.rs

1use std::{cell::SyncUnsafeCell, num::NonZeroU16, sync::LazyLock};
2
3use anyhow::Error;
4use scattered_collect::slice::ScatteredSlice;
5
6use crate::{
7    TraitType, ValueType,
8    id::{FunctionId, TraitTypeId, ValueTypeId},
9    native_function::NativeFunction,
10};
11
12mod registry_type;
13
14pub use registry_type::RegistryType;
15
16/// Generates pointer-based `Eq`, `PartialEq`, `Hash`, `Ord`, `PartialOrd` impls.
17macro_rules! impl_ptr_identity {
18    ($ty:ty) => {
19        impl ::core::cmp::Eq for $ty {}
20        impl ::core::cmp::PartialEq for $ty {
21            fn eq(&self, other: &$ty) -> bool {
22                ::core::ptr::eq(self, other)
23            }
24        }
25        impl ::core::hash::Hash for $ty {
26            fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
27                ::core::ptr::hash(self, state)
28            }
29        }
30        impl ::core::cmp::Ord for $ty {
31            fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
32                (self as *const Self).cmp(&(other as *const Self))
33            }
34        }
35        impl ::core::cmp::PartialOrd for $ty {
36            fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
37                Some(self.cmp(other))
38            }
39        }
40    };
41}
42
43pub(crate) use impl_ptr_identity;
44
45// Link-time gather slices, one per registry.
46#[doc(hidden)]
47#[scattered_collect::gather]
48pub static FUNCTIONS_SLICE: ScatteredSlice<&'static NativeFunction>;
49
50#[doc(hidden)]
51#[scattered_collect::gather]
52pub static VALUES_SLICE: ScatteredSlice<&'static ValueType>;
53
54#[doc(hidden)]
55#[scattered_collect::gather]
56pub static TRAITS_SLICE: ScatteredSlice<&'static TraitType>;
57
58/// Register a [`NativeFunction`] definition into the link-time [`FUNCTIONS_SLICE`].
59#[macro_export]
60#[doc(hidden)]
61macro_rules! register_function {
62    ($name:ident = $value:expr) => {
63        static $name: $crate::macro_helpers::NativeFunction = $value;
64        $crate::macro_helpers::scattered_collect::declarative::scatter! {
65            #[scatter($crate::registry::FUNCTIONS_SLICE)]
66            const _: &'static $crate::macro_helpers::NativeFunction = &$name;
67        }
68    };
69}
70
71/// Register a [`ValueType`] definition into the link-time [`VALUES_SLICE`], and provide its
72/// `RegistryDef` so the impl macros can recover the `&'static ValueType` for a Vc type.
73#[macro_export]
74#[doc(hidden)]
75macro_rules! register_value {
76    ($reg:ty => $name:ident = $value:expr) => {
77        static $name: $crate::ValueType = $value;
78        $crate::macro_helpers::scattered_collect::declarative::scatter! {
79            #[scatter($crate::registry::VALUES_SLICE)]
80            const _: &'static $crate::ValueType = &$name;
81        }
82
83        impl $crate::macro_helpers::RegistryDef<$crate::ValueType> for $reg {
84            const DEF: &'static $crate::ValueType = &$name;
85        }
86    };
87}
88
89/// Register a [`TraitType`] definition into the link-time [`TRAITS_SLICE`], and provide its
90/// `RegistryDef` so the impl macros can recover the `&'static TraitType` for a `Box<dyn Trait>`.
91#[macro_export]
92#[doc(hidden)]
93macro_rules! register_trait {
94    ($reg:ty => $name:ident = $value:expr) => {
95        static $name: $crate::TraitType = $value;
96        $crate::macro_helpers::scattered_collect::declarative::scatter! {
97            #[scatter($crate::registry::TRAITS_SLICE)]
98            const _: &'static $crate::TraitType = &$name;
99        }
100
101        impl $crate::macro_helpers::RegistryDef<$crate::TraitType> for $reg {
102            const DEF: &'static $crate::TraitType = &$name;
103        }
104    };
105}
106
107#[doc(hidden)]
108pub trait RegistryDef<T: 'static> {
109    const DEF: &'static T;
110}
111
112/// A trait for types that can be registered in a registry.
113///
114/// This allows the generic registry to work with different types
115/// while maintaining their specific requirements.
116trait Registerable: 'static + Eq + std::hash::Hash {
117    /// The ID type used for this registry item
118    type Id: Copy + From<NonZeroU16> + std::ops::Deref<Target = u16> + std::fmt::Display;
119    const TYPE_NAME: &'static str;
120
121    /// The largest id that may be assigned to this registry item.
122    const MAX_ID: u16 = u16::MAX;
123
124    /// Get the global registry type used for sorting and uniqueness validation
125    fn ty(&self) -> &RegistryType;
126}
127
128impl Registerable for NativeFunction {
129    type Id = FunctionId;
130    const TYPE_NAME: &'static str = "Function";
131
132    fn ty(&self) -> &RegistryType {
133        &self.ty
134    }
135}
136
137impl Registerable for ValueType {
138    type Id = ValueTypeId;
139    const TYPE_NAME: &'static str = "Value";
140    const MAX_ID: u16 = ValueTypeId::MAX.to_primitive();
141    fn ty(&self) -> &RegistryType {
142        &self.ty
143    }
144}
145
146impl Registerable for TraitType {
147    type Id = TraitTypeId;
148    const TYPE_NAME: &'static str = "Trait";
149    fn ty(&self) -> &RegistryType {
150        &self.ty
151    }
152}
153
154/// Assign IDs to items and call post_init. Shared logic for all registry types.
155fn init_registry<T: Registerable>(mut items: Vec<&'static T>) -> Box<[&'static T]> {
156    // Sort by global name for stable, deterministic ID assignment
157    items.sort_unstable_by_key(|item| item.ty().global_name);
158
159    let mut id = NonZeroU16::MIN;
160    let mut prev_name: Option<&str> = None;
161    for item in items.iter() {
162        let global_name = item.ty().global_name;
163        if let Some(prev) = prev_name {
164            assert!(
165                prev != global_name,
166                "multiple {ty} items registered with name: {global_name}!",
167                ty = T::TYPE_NAME
168            );
169        }
170        prev_name = Some(global_name);
171        assert!(
172            u16::from(id) <= T::MAX_ID,
173            "too many {ty} items registered: id {id} exceeds the cap of {max}",
174            ty = T::TYPE_NAME,
175            max = T::MAX_ID,
176        );
177        // SAFETY: Single-threaded during Lazy init; no concurrent readers yet.
178        unsafe { std::ptr::write(SyncUnsafeCell::raw_get(&item.ty().id), u16::from(id)) };
179        id = id.checked_add(1).expect("overflowing item ids");
180    }
181
182    items.into_boxed_slice()
183}
184
185/// Get an item by its ID from a registry slice
186#[inline]
187fn get_item<T: Registerable>(registry: &LazyLock<Box<[&'static T]>>, id: T::Id) -> &'static T {
188    registry[*id as usize - 1]
189}
190
191/// Read an item's assigned id directly, without touching `LazyLock`.
192///
193/// # Safety
194///
195/// Caller must guarantee that `init_registry` has already written the id for this `item`.
196#[inline]
197unsafe fn get_id_unchecked<T: Registerable>(item: &'static T) -> T::Id {
198    // SAFETY: caller guarantees the id has been written. The write and this read are both
199    // inside the registry's single-threaded lazy init.
200    let n = unsafe { std::ptr::read(item.ty().id.get()) };
201    let Some(id) = NonZeroU16::new(n) else {
202        panic!(
203            "{ty} isn't registered: {item}",
204            ty = T::TYPE_NAME,
205            item = item.ty().global_name
206        );
207    };
208    T::Id::from(id)
209}
210
211/// Get the ID for a registered item. Forces registry init if needed, which
212/// assigns IDs to all items as a side effect.
213#[inline]
214fn get_id<T: Registerable>(registry: &LazyLock<Box<[&'static T]>>, item: &'static T) -> T::Id {
215    LazyLock::force(registry);
216    // SAFETY: The ID write happens-before this read thanks to the fence inside of LazyLock
217    unsafe { get_id_unchecked(item) }
218}
219
220/// Validate that an ID is within the valid range
221fn validate_id<T: Registerable>(
222    registry: &LazyLock<Box<[&'static T]>>,
223    id: T::Id,
224) -> Option<Error> {
225    let len = registry.len();
226    if *id as usize <= len {
227        None
228    } else {
229        Some(anyhow::anyhow!(
230            "Invalid {ty} id, {id} expected a value <= {len}",
231            ty = T::TYPE_NAME
232        ))
233    }
234}
235
236static FUNCTIONS: LazyLock<Box<[&'static NativeFunction]>> =
237    LazyLock::new(|| init_registry(FUNCTIONS_SLICE.iter().copied().collect()));
238
239#[inline]
240pub fn get_native_function(id: FunctionId) -> &'static NativeFunction {
241    get_item(&FUNCTIONS, id)
242}
243
244#[inline]
245pub fn get_function_id(func: &'static NativeFunction) -> FunctionId {
246    get_id(&FUNCTIONS, func)
247}
248
249pub fn validate_function_id(id: FunctionId) -> Option<Error> {
250    validate_id(&FUNCTIONS, id)
251}
252
253static VALUES: LazyLock<Box<[&'static ValueType]>> = LazyLock::new(|| {
254    let items = init_registry(VALUES_SLICE.iter().copied().collect());
255    crate::value_type::register_all_trait_methods();
256    items
257});
258
259#[inline]
260pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId {
261    get_id(&VALUES, value)
262}
263
264/// Read a `ValueType`'s assigned id directly, without touching `LazyLock`. See
265/// [`get_id_unchecked`] for the safety contract.
266///
267/// # Safety
268///
269/// The only legitimate caller is [`crate::value_type::register_all_trait_methods`],
270/// which runs inside the `VALUES` `LazyLock` initializer, after `init_registry` has assigned ids.
271/// Calling `get_value_type_id` from there would re-enter `LazyLock::force` and deadlock.
272#[inline]
273pub(crate) unsafe fn get_value_type_id_unchecked(value: &'static ValueType) -> ValueTypeId {
274    unsafe { get_id_unchecked(value) }
275}
276
277#[inline]
278pub fn get_value_type(id: ValueTypeId) -> &'static ValueType {
279    get_item(&VALUES, id)
280}
281
282pub fn validate_value_type_id(id: ValueTypeId) -> Option<Error> {
283    validate_id(&VALUES, id)
284}
285
286/// Number of registered trait types. Forces TRAITS init.
287#[inline]
288pub(crate) fn trait_type_count() -> usize {
289    TRAITS.len()
290}
291
292static TRAITS: LazyLock<Box<[&'static TraitType]>> =
293    LazyLock::new(|| init_registry(TRAITS_SLICE.iter().copied().collect()));
294
295#[inline]
296pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId {
297    get_id(&TRAITS, trait_type)
298}
299
300#[inline]
301pub fn get_trait(id: TraitTypeId) -> &'static TraitType {
302    get_item(&TRAITS, id)
303}
304
305pub fn validate_trait_type_id(id: TraitTypeId) -> Option<Error> {
306    validate_id(&TRAITS, id)
307}