Skip to content

Commit 0d3dfbc

Browse files
committed
Use a registry abstraction to reduce duplication
1 parent 4425d64 commit 0d3dfbc

File tree

2 files changed

+130
-125
lines changed

2 files changed

+130
-125
lines changed

turbopack/crates/turbo-tasks/src/native_function.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -273,18 +273,16 @@ impl NativeFunction {
273273
tracing::trace_span!("turbo_tasks::resolve_call", name = self.name, flags = flags)
274274
}
275275
}
276-
277-
impl PartialEq for &'static NativeFunction {
276+
impl PartialEq for NativeFunction {
278277
fn eq(&self, other: &Self) -> bool {
279-
std::ptr::eq(*self, *other)
278+
std::ptr::eq(self, other)
280279
}
281280
}
282281

283-
impl Eq for &'static NativeFunction {}
284-
285-
impl Hash for &'static NativeFunction {
282+
impl Eq for NativeFunction {}
283+
impl Hash for NativeFunction {
286284
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
287-
Hash::hash(&(*self as *const NativeFunction), state);
285+
(self as *const NativeFunction).hash(state);
288286
}
289287
}
290288

turbopack/crates/turbo-tasks/src/registry.rs

Lines changed: 125 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,168 +12,175 @@ use crate::{
1212
value_type::{CollectableTrait, CollectableValueType},
1313
};
1414

15-
struct Functions {
16-
id_to_value: Box<[&'static NativeFunction]>,
17-
value_to_id: FxHashMap<&'static NativeFunction, FunctionId>,
15+
/// A trait for types that can be registered in a registry.
16+
///
17+
/// This allows the generic registry to work with different types
18+
/// while maintaining their specific requirements.
19+
trait RegistryItem: 'static + Eq + std::hash::Hash {
20+
/// The ID type used for this registry item
21+
type Id: Copy + From<NonZeroU16> + std::ops::Deref<Target = u16> + std::fmt::Display;
22+
const TYPE_NAME: &'static str;
23+
24+
/// Get the global name used for sorting and uniqueness validation
25+
fn global_name(&self) -> &'static str;
1826
}
19-
static FUNCTIONS: Lazy<Functions> = Lazy::new(|| {
20-
let mut functions = inventory::iter::<CollectableFunction>
21-
.into_iter()
22-
.map(|c| &**c.0)
23-
.collect::<Vec<_>>();
24-
functions.sort_unstable_by_key(|f| f.global_name);
25-
let mut value_to_id = FxHashMap::with_capacity_and_hasher(functions.len(), Default::default());
26-
let mut names = FxHashSet::with_capacity_and_hasher(functions.len(), Default::default());
27-
28-
let mut id = NonZeroU16::MIN;
29-
for &native_function in functions.iter() {
30-
value_to_id.insert(native_function, id.into());
31-
let global_name = native_function.global_name;
32-
assert!(
33-
names.insert(global_name),
34-
"multiple functions registered with name: {global_name}!"
35-
);
36-
id = id.checked_add(1).expect("overflowing function ids");
27+
28+
impl RegistryItem for NativeFunction {
29+
type Id = FunctionId;
30+
const TYPE_NAME: &'static str = "Function";
31+
32+
fn global_name(&self) -> &'static str {
33+
self.global_name
34+
}
35+
}
36+
37+
impl RegistryItem for ValueType {
38+
type Id = ValueTypeId;
39+
const TYPE_NAME: &'static str = "Value";
40+
41+
fn global_name(&self) -> &'static str {
42+
self.global_name
43+
}
44+
}
45+
46+
impl RegistryItem for TraitType {
47+
type Id = TraitTypeId;
48+
const TYPE_NAME: &'static str = "Trait";
49+
fn global_name(&self) -> &'static str {
50+
self.global_name
51+
}
52+
}
53+
54+
/// A generic registry that maps between IDs and static references to items.
55+
///
56+
/// This eliminates the code duplication between Functions, Values, and Traits registries.
57+
struct Registry<T: RegistryItem> {
58+
id_to_item: Box<[&'static T]>,
59+
item_to_id: FxHashMap<&'static T, T::Id>,
60+
}
61+
62+
impl<T: RegistryItem> Registry<T> {
63+
/// Create a new registry from a collection of items.
64+
///
65+
/// Items are sorted by global_name to ensure stable ID assignment.
66+
fn new_from_items(mut items: Vec<&'static T>) -> Self {
67+
// Sort by global name to get stable order
68+
items.sort_unstable_by_key(|item| item.global_name());
69+
70+
let mut item_to_id = FxHashMap::with_capacity_and_hasher(items.len(), Default::default());
71+
let mut names = FxHashSet::with_capacity_and_hasher(items.len(), Default::default());
72+
73+
let mut id = NonZeroU16::MIN;
74+
for &item in items.iter() {
75+
item_to_id.insert(item, id.into());
76+
let global_name = item.global_name();
77+
assert!(
78+
names.insert(global_name),
79+
"multiple {ty} items registered with name: {global_name}!",
80+
ty = T::TYPE_NAME
81+
);
82+
id = id.checked_add(1).expect("overflowing item ids");
83+
}
84+
85+
Self {
86+
id_to_item: items.into_boxed_slice(),
87+
item_to_id,
88+
}
89+
}
90+
91+
/// Get an item by its ID
92+
fn get_item(&self, id: T::Id) -> &'static T {
93+
self.id_to_item[*id as usize - 1]
94+
}
95+
96+
/// Get the ID for an item
97+
fn get_id(&self, item: &'static T) -> T::Id {
98+
match self.item_to_id.get(&item) {
99+
Some(id) => *id,
100+
None => panic!(
101+
"{ty} isn't registered: {item}",
102+
ty = T::TYPE_NAME,
103+
item = item.global_name()
104+
),
105+
}
37106
}
38107

39-
Functions {
40-
id_to_value: functions.into_boxed_slice(),
41-
value_to_id,
108+
/// Validate that an ID is within the valid range
109+
fn validate_id(&self, id: T::Id) -> Option<Error> {
110+
let len = self.id_to_item.len();
111+
if *id as usize <= len {
112+
None
113+
} else {
114+
Some(anyhow::anyhow!(
115+
"Invalid {ty} id, {id} expected a value <= {len}",
116+
ty = T::TYPE_NAME
117+
))
118+
}
42119
}
120+
}
121+
122+
static FUNCTIONS: Lazy<Registry<NativeFunction>> = Lazy::new(|| {
123+
let functions = inventory::iter::<CollectableFunction>
124+
.into_iter()
125+
.map(|c| &**c.0)
126+
.collect::<Vec<_>>();
127+
Registry::new_from_items(functions)
43128
});
44129

45130
pub fn get_native_function(id: FunctionId) -> &'static NativeFunction {
46-
FUNCTIONS.id_to_value[*id as usize - 1]
131+
FUNCTIONS.get_item(id)
47132
}
48133

49134
pub fn get_function_id(func: &'static NativeFunction) -> FunctionId {
50-
*FUNCTIONS
51-
.value_to_id
52-
.get(&func)
53-
.expect("function isn't registered")
135+
FUNCTIONS.get_id(func)
54136
}
55137

56138
pub fn validate_function_id(id: FunctionId) -> Option<Error> {
57-
let len = FUNCTIONS.id_to_value.len();
58-
if *id as usize <= len {
59-
None
60-
} else {
61-
Some(anyhow::anyhow!(
62-
"Invalid function type id, {id} expected a value <= {len}"
63-
))
64-
}
65-
}
66-
67-
struct Values {
68-
id_to_value: Box<[&'static ValueType]>,
69-
value_to_id: FxHashMap<&'static ValueType, ValueTypeId>,
139+
FUNCTIONS.validate_id(id)
70140
}
71141

72-
static VALUES: Lazy<Values> = Lazy::new(|| {
142+
static VALUES: Lazy<Registry<ValueType>> = Lazy::new(|| {
73143
// Inventory does not guarantee an order. So we sort by the global name to get a stable order
74144
// This ensures that assigned ids are also stable.
75145
// We don't currently take advantage of this but we could in the future. The remaining issue is
76146
// ensuring the set of values is the same across runs.
77-
let mut all_values = inventory::iter::<CollectableValueType>
147+
let all_values = inventory::iter::<CollectableValueType>
78148
.into_iter()
79149
.map(|t| &**t.0)
80150
.collect::<Vec<_>>();
81-
all_values.sort_unstable_by_key(|t| t.global_name);
82-
83-
let mut value_to_id = FxHashMap::with_capacity_and_hasher(all_values.len(), Default::default());
84-
// Our sort above is non-sensical if names are not unique
85-
let mut names = FxHashSet::with_capacity_and_hasher(all_values.len(), Default::default());
86-
87-
let mut id = NonZeroU16::MIN;
88-
for &value_type in all_values.iter() {
89-
value_to_id.insert(value_type, id.into());
90-
let global_name = value_type.global_name;
91-
assert!(
92-
names.insert(global_name),
93-
"two values registered with the same name: {global_name}"
94-
);
95-
id = id.checked_add(1).expect("overflowing value type ids");
96-
}
97-
98-
Values {
99-
value_to_id,
100-
id_to_value: all_values.into_boxed_slice(),
101-
}
151+
Registry::new_from_items(all_values)
102152
});
103153

104154
pub fn get_value_type_id(value: &'static ValueType) -> ValueTypeId {
105-
match VALUES.value_to_id.get(value) {
106-
Some(id) => *id,
107-
None => panic!("Use of unregistered trait {value:?}"),
108-
}
155+
VALUES.get_id(value)
109156
}
110157

111158
pub fn get_value_type(id: ValueTypeId) -> &'static ValueType {
112-
VALUES.id_to_value[*id as usize - 1]
159+
VALUES.get_item(id)
113160
}
114161

115162
pub fn validate_value_type_id(id: ValueTypeId) -> Option<Error> {
116-
let len = VALUES.id_to_value.len();
117-
if *id as usize <= len {
118-
None
119-
} else {
120-
Some(anyhow::anyhow!(
121-
"Invalid value type id, {id} expected a value <= {len}"
122-
))
123-
}
124-
}
125-
126-
struct Traits {
127-
id_to_trait: Box<[&'static TraitType]>,
128-
trait_to_id: FxHashMap<&'static TraitType, TraitTypeId>,
163+
VALUES.validate_id(id)
129164
}
130165

131-
static TRAITS: Lazy<Traits> = Lazy::new(|| {
166+
static TRAITS: Lazy<Registry<TraitType>> = Lazy::new(|| {
132167
// Inventory does not guarantee an order. So we sort by the global name to get a stable order
133168
// This ensures that assigned ids are also stable.
134-
let mut all_traits = inventory::iter::<CollectableTrait>
169+
let all_traits = inventory::iter::<CollectableTrait>
135170
.into_iter()
136171
.map(|t| &**t.0)
137172
.collect::<Vec<_>>();
138-
all_traits.sort_unstable_by_key(|t| t.global_name);
139-
140-
let mut trait_to_id = FxHashMap::with_capacity_and_hasher(all_traits.len(), Default::default());
141-
// Our sort above is non-sensical if names are not unique
142-
let mut names = FxHashSet::with_capacity_and_hasher(all_traits.len(), Default::default());
143-
let mut id = NonZeroU16::MIN;
144-
for &trait_type in all_traits.iter() {
145-
trait_to_id.insert(trait_type, id.into());
146-
147-
let global_name = trait_type.global_name;
148-
assert!(
149-
names.insert(global_name),
150-
"two traits registered with the same name: {global_name}"
151-
);
152-
id = id.checked_add(1).expect("overflowing trait type ids");
153-
}
154-
Traits {
155-
trait_to_id,
156-
id_to_trait: all_traits.into_boxed_slice(),
157-
}
173+
Registry::new_from_items(all_traits)
158174
});
159175

160176
pub fn get_trait_type_id(trait_type: &'static TraitType) -> TraitTypeId {
161-
match TRAITS.trait_to_id.get(trait_type) {
162-
Some(id) => *id,
163-
None => panic!("Use of unregistered trait {trait_type:?}"),
164-
}
177+
TRAITS.get_id(trait_type)
165178
}
166179

167180
pub fn get_trait(id: TraitTypeId) -> &'static TraitType {
168-
TRAITS.id_to_trait[*id as usize - 1]
181+
TRAITS.get_item(id)
169182
}
183+
170184
pub fn validate_trait_type_id(id: TraitTypeId) -> Option<Error> {
171-
let len = TRAITS.id_to_trait.len();
172-
if *id as usize <= len {
173-
None
174-
} else {
175-
Some(anyhow::anyhow!(
176-
"Invalid trait type id, {id} expected a value <= {len}"
177-
))
178-
}
185+
TRAITS.validate_id(id)
179186
}

0 commit comments

Comments
 (0)