1use std::{
2 borrow::{Borrow, Cow},
3 fmt::Debug,
4};
5
6pub use crate::{
7 benchmark::{BenchArgs, BenchOptions},
8 entry::{
9 BenchEntry, BenchEntryRunner, EntryConst, EntryList, EntryLocation, EntryMeta, EntryType,
10 GenericBenchEntry, GroupEntry, BENCH_ENTRIES, GROUP_ENTRIES,
11 },
12 time::IntoDuration,
13};
14
15/// Helper to convert values to strings via `ToString` or fallback to `Debug`.
16///
17/// This works by having a `Debug`-based `ToString::to_string` method that will
18/// be chosen if the wrapped type implements `Debug` *but not* `ToString`. If
19/// the wrapped type implements `ToString`, then the inherent
20/// `ToStringHelper::to_string` method will be chosen instead.
21pub struct ToStringHelper<'a, T: 'static>(pub &'a T);
22
23#[allow(clippy::to_string_trait_impl)]
24impl<T: Debug> ToString for ToStringHelper<'_, T> {
25 #[inline]
26 fn to_string(&self) -> String {
27 format!("{:?}", self.0)
28 }
29}
30
31impl<T: ToString> ToStringHelper<'_, T> {
32 #[allow(clippy::inherent_to_string)]
33 #[inline]
34 pub fn to_string(&self) -> String {
35 self.0.to_string()
36 }
37}
38
39/// Used by `#[divan::bench(args = ...)]` to enable polymorphism.
40pub trait Arg<T> {
41 fn get(this: Self) -> T;
42}
43
44impl<T> Arg<T> for T {
45 #[inline]
46 fn get(this: Self) -> T {
47 this
48 }
49}
50
51impl<'a, T: ?Sized> Arg<&'a T> for &'a Cow<'a, T>
52where
53 T: ToOwned,
54{
55 #[inline]
56 fn get(this: Self) -> &'a T {
57 this
58 }
59}
60
61impl<'a> Arg<&'a str> for &'a String {
62 #[inline]
63 fn get(this: Self) -> &'a str {
64 this
65 }
66}
67
68impl<T: Copy> Arg<T> for &T {
69 #[inline]
70 fn get(this: Self) -> T {
71 *this
72 }
73}
74
75impl<T: Copy> Arg<T> for &&T {
76 #[inline]
77 fn get(this: Self) -> T {
78 **this
79 }
80}
81
82impl<T: Copy> Arg<T> for &&&T {
83 #[inline]
84 fn get(this: Self) -> T {
85 ***this
86 }
87}
88
89/// Used by `#[divan::bench(threads = ...)]` to leak thread counts for easy
90/// global usage in [`BenchOptions::threads`].
91///
92/// This enables the `threads` option to be polymorphic over:
93/// - `usize`
94/// - `bool`
95/// - `true` is 0
96/// - `false` is 1
97/// - Iterators:
98/// - `[usize; N]`
99/// - `&[usize; N]`
100/// - `&[usize]`
101///
102/// # Orphan Rules Hack
103///
104/// Normally we can't implement a trait over both `usize` and `I: IntoIterator`
105/// because the compiler has no guarantee that `usize` will never implement
106/// `IntoIterator`. Ideally we would handle this with specialization, but that's
107/// not stable.
108///
109/// The solution here is to make `IntoThreads` generic to implement technically
110/// different traits for `usize` and `IntoIterator` because of different `IMP`
111/// values. We then call verbatim `IntoThreads::into_threads(val)` and have the
112/// compiler infer the generic parameter for the single `IntoThreads`
113/// implementation.
114///
115/// It's fair to assume that scalar primitives will never implement
116/// `IntoIterator`, so this hack shouldn't break in the future 🤠.
117pub trait IntoThreads<const IMP: u32> {
118 fn into_threads(self) -> Cow<'static, [usize]>;
119}
120
121impl IntoThreads<0> for usize {
122 #[inline]
123 fn into_threads(self) -> Cow<'static, [usize]> {
124 let counts: &[usize; 1] = match self {
125 0 => &[0],
126 1 => &[1],
127 2 => &[2],
128 _ => return Cow::Owned(vec![self]),
129 };
130 Cow::Borrowed(counts)
131 }
132}
133
134impl IntoThreads<0> for bool {
135 #[inline]
136 fn into_threads(self) -> Cow<'static, [usize]> {
137 let counts: &[usize; 1] = if self {
138 // Available parallelism.
139 &[0]
140 } else {
141 // No parallelism.
142 &[1]
143 };
144 Cow::Borrowed(counts)
145 }
146}
147
148impl<I> IntoThreads<1> for I
149where
150 I: IntoIterator,
151 I::Item: Borrow<usize>,
152{
153 #[inline]
154 fn into_threads(self) -> Cow<'static, [usize]> {
155 let mut options: Vec<usize> = self.into_iter().map(|i: impl Borrow| *i.borrow()).collect();
156 options.sort_unstable();
157 options.dedup();
158 Cow::Owned(options)
159 }
160}
161
162/// Used by `#[divan::bench(counters = [...])]`.
163#[inline]
164pub fn new_counter_set() -> crate::counter::CounterSet {
165 Default::default()
166}
167
168/// Used by `#[divan::bench]` to truncate arrays for generic `const` benchmarks.
169pub const fn shrink_array<T, const IN: usize, const OUT: usize>(
170 array: [T; IN],
171) -> Option<[T; OUT]> {
172 use std::mem::ManuallyDrop;
173
174 #[repr(C)]
175 union Transmute<F, I> {
176 from: ManuallyDrop<F>,
177 into: ManuallyDrop<I>,
178 }
179
180 let from: ManuallyDrop<[T; IN]> = ManuallyDrop::new(array);
181
182 if OUT <= IN {
183 Some(unsafe { ManuallyDrop::into_inner(slot:Transmute { from }.into) })
184 } else {
185 None
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn into_threads() {
195 macro_rules! test {
196 ($value:expr, $expected:expr) => {
197 assert_eq!(IntoThreads::into_threads($value).as_ref(), $expected);
198 };
199 }
200
201 test!(true, &[0]);
202 test!(false, &[1]);
203
204 test!(0, &[0]);
205 test!(1, &[1]);
206 test!(42, &[42]);
207
208 test!([0; 0], &[]);
209 test!([0], &[0]);
210 test!([0, 0], &[0]);
211
212 test!([0, 2, 3, 1], &[0, 1, 2, 3]);
213 test!([0, 0, 2, 3, 2, 1, 3], &[0, 1, 2, 3]);
214 }
215
216 #[test]
217 fn shrink_array() {
218 let values = [1, 2, 3, 4, 5];
219
220 let equal: Option<[i32; 5]> = super::shrink_array(values);
221 assert_eq!(equal, Some(values));
222
223 let smaller: Option<[i32; 3]> = super::shrink_array(values);
224 assert_eq!(smaller, Some([1, 2, 3]));
225
226 let larger: Option<[i32; 100]> = super::shrink_array(values);
227 assert_eq!(larger, None);
228 }
229}
230