Skip to main content

turbo_tasks/registry/
mod.rs

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