1use std::{
2 any::{Any, TypeId},
3 cmp::Ordering,
4 mem::ManuallyDrop,
5 sync::OnceLock,
6};
7
8use crate::{
9 entry::{BenchEntryRunner, GroupEntry},
10 util::sort::natural_cmp,
11};
12
13/// Compile-time entry for a generic benchmark function, generated by
14/// `#[divan::bench]`.
15///
16/// Unlike `BenchEntry`, this is for a specific generic type or `const`.
17///
18/// Although this type contains trivially-`Copy` data, it *should not* implement
19/// `Clone` because the memory address of each instance is used to determine the
20/// relative order in `GroupEntry.generic_benches` when sorting benchmarks by
21/// location.
22pub struct GenericBenchEntry {
23 /// The associated group, for entry metadata.
24 pub group: &'static GroupEntry,
25
26 /// The benchmarking function.
27 pub bench: BenchEntryRunner,
28
29 /// A generic type.
30 pub ty: Option<EntryType>,
31
32 /// A `const` value and associated data.
33 pub const_value: Option<EntryConst>,
34}
35
36impl GenericBenchEntry {
37 pub(crate) fn raw_name(&self) -> &str {
38 match (&self.ty, &self.const_value) {
39 (_, Some(const_value)) => const_value.name(),
40 (Some(ty), None) => ty.raw_name(),
41 (None, None) => unreachable!(),
42 }
43 }
44
45 pub(crate) fn display_name(&self) -> &str {
46 match (&self.ty, &self.const_value) {
47 (_, Some(const_value)) => const_value.name(),
48 (Some(ty), None) => ty.display_name(),
49 (None, None) => unreachable!(),
50 }
51 }
52
53 pub(crate) fn path_components(&self) -> impl Iterator<Item = &str> {
54 let module_path = self.group.meta.module_path_components();
55
56 // Generic benchmarks consider their group's raw name to be the path
57 // component after the module path.
58 let group_component = self.group.meta.raw_name;
59
60 // If this is a generic const benchmark with generic types, the generic
61 // types are considered to be the parent of the const values.
62 let type_component = if self.const_value.is_some() {
63 // FIXME: Switch back to `raw_name` once we have a way to insert
64 // this `display_name` into `EntryTree::Parent`. The current
65 // approach allows different types with the same name to become the
66 // same `EntryTree::Parent`.
67 self.ty.as_ref().map(|ty| ty.display_name())
68 } else {
69 None
70 };
71
72 module_path.chain(Some(group_component)).chain(type_component)
73 }
74}
75
76/// Generic type instantiation.
77pub struct EntryType {
78 /// [`std::any::type_name`].
79 get_type_name: fn() -> &'static str,
80
81 /// [`std::any::TypeId::of`].
82 #[allow(dead_code)]
83 get_type_id: fn() -> TypeId,
84}
85
86impl EntryType {
87 /// Creates an instance for the given type.
88 pub const fn new<T: Any>() -> Self {
89 Self { get_type_name: std::any::type_name::<T>, get_type_id: TypeId::of::<T> }
90 }
91
92 pub(crate) fn raw_name(&self) -> &'static str {
93 (self.get_type_name)()
94 }
95
96 pub(crate) fn display_name(&self) -> &'static str {
97 let mut type_name = self.raw_name();
98
99 // Remove module components in type name.
100 while let Some((prev, next)) = type_name.split_once("::") {
101 // Do not go past generic type boundary.
102 if prev.contains('<') {
103 break;
104 }
105 type_name = next;
106 }
107
108 type_name
109 }
110}
111
112/// A reference to a `const` as a `&'static T`.
113pub struct EntryConst {
114 /// `&'static T`.
115 value: *const (),
116
117 /// [`PartialOrd::partial_cmp`].
118 partial_cmp: unsafe fn(*const (), *const ()) -> Option<Ordering>,
119
120 /// [`ToString::to_string`].
121 to_string: unsafe fn(*const ()) -> String,
122
123 /// Cached `to_string` result.
124 cached_string: ManuallyDrop<OnceLock<&'static str>>,
125}
126
127// SAFETY: `T: Send + Sync`.
128unsafe impl Send for EntryConst {}
129unsafe impl Sync for EntryConst {}
130
131impl EntryConst {
132 /// Creates entry data for a `const` values.
133 pub const fn new<T>(value: &'static T) -> Self
134 where
135 T: PartialOrd + ToString + Send + Sync,
136 {
137 unsafe fn partial_cmp<T: PartialOrd>(a: *const (), b: *const ()) -> Option<Ordering> {
138 T::partial_cmp(&*a.cast(), &*b.cast())
139 }
140
141 unsafe fn to_string<T: ToString>(value: *const ()) -> String {
142 T::to_string(&*value.cast())
143 }
144
145 Self {
146 value: value as *const T as *const (),
147 partial_cmp: partial_cmp::<T>,
148 to_string: to_string::<T>,
149 cached_string: ManuallyDrop::new(OnceLock::new()),
150 }
151 }
152
153 /// Returns [`PartialOrd::partial_cmp`] ordering if `<` or `>, falling back
154 /// to comparing [`ToString::to_string`] otherwise.
155 pub(crate) fn cmp_name(&self, other: &Self) -> Ordering {
156 // SAFETY: If both constants have the same `partial_cmp` function
157 // pointer, they are safely comparable. In the context of how this
158 // method is used, it is because the constants are of the same type.
159 //
160 // We don't need a type ID check because constants that are compared to
161 // each other all come from the same code generation unit, so their
162 // `partial_cmp` function pointers will never differ.
163 #[allow(unpredictable_function_pointer_comparisons)]
164 if self.partial_cmp == other.partial_cmp {
165 if let Some(ordering) = unsafe { (self.partial_cmp)(self.value, other.value) } {
166 if !ordering.is_eq() {
167 return ordering;
168 }
169 }
170 }
171
172 // Fallback to name comparison.
173 natural_cmp(self.name(), other.name())
174 }
175
176 /// [`ToString::to_string`].
177 #[inline]
178 pub(crate) fn name(&self) -> &str {
179 self.cached_string.get_or_init(|| {
180 // SAFETY: The function is guaranteed to call `T::to_string`.
181 let string = unsafe { (self.to_string)(self.value) };
182
183 Box::leak(string.into_boxed_str())
184 })
185 }
186}
187