core/slice/rotate.rs
1use crate::mem::{MaybeUninit, SizedTypeProperties};
2use crate::ptr;
3
4type BufType = [usize; 32];
5
6/// Rotates the range `[mid-left, mid+right)` such that the element at `mid` becomes the first
7/// element. Equivalently, rotates the range `left` elements to the left or `right` elements to the
8/// right.
9///
10/// # Safety
11///
12/// The specified range must be valid for reading and writing.
13#[inline]
14pub(super) const unsafe fn ptr_rotate<T>(left: usize, mid: *mut T, right: usize) {
15 if T::IS_ZST {
16 return;
17 }
18 // abort early if the rotate is a no-op
19 if (left == 0) || (right == 0) {
20 return;
21 }
22
23 // For huge slices, GCD has poor locality because it jumps by large element-sized
24 // strides and carries a large temporary. Since rotation only permutes raw storage,
25 // a large element whose size/alignment permits it can instead be viewed as multiple
26 // `MaybeUninit<usize>` words. This makes dispatch choose the contiguous swap algorithm,
27 // which has much better cache/TLB locality.
28 if !cfg!(feature = "optimize_for_size")
29 && size_of::<T>() > size_of::<[usize; 4]>()
30 && size_of::<T>() % size_of::<usize>() == 0
31 && align_of::<T>() >= align_of::<usize>()
32 {
33 let ratio = size_of::<T>() / size_of::<usize>();
34
35 // SAFETY: this covers exactly the same bytes as `[mid-left, mid+right)` because
36 // `ratio * size_of::<usize>() == size_of::<T>()`. The check above guarantees
37 // `mid` is aligned for `usize`, and preserving the total byte length introduces
38 // no new overflow.
39 unsafe { ptr_rotate_dispatch(left * ratio, mid as *mut MaybeUninit<usize>, right * ratio) }
40 } else {
41 // SAFETY: guaranteed by the caller
42 unsafe { ptr_rotate_dispatch(left, mid, right) }
43 }
44}
45
46/// Chooses between the three rotation algorithms. See the algorithm docs below.
47///
48/// # Safety
49///
50/// The specified range must be valid for reading and writing.
51#[inline]
52const unsafe fn ptr_rotate_dispatch<T>(left: usize, mid: *mut T, right: usize) {
53 // `T` is not a zero-sized type, so it's okay to divide by its size.
54 if !cfg!(feature = "optimize_for_size")
55 // FIXME(const-hack): Use cmp::min when available in const
56 && const_min(left, right) <= size_of::<BufType>() / size_of::<T>()
57 {
58 // SAFETY: guaranteed by the caller
59 unsafe { ptr_rotate_memmove(left, mid, right) };
60 } else if !cfg!(feature = "optimize_for_size")
61 && ((left + right < 24) || (size_of::<T>() > size_of::<[usize; 4]>()))
62 {
63 // SAFETY: guaranteed by the caller
64 unsafe { ptr_rotate_gcd(left, mid, right) }
65 } else {
66 // SAFETY: guaranteed by the caller
67 unsafe { ptr_rotate_swap(left, mid, right) }
68 }
69}
70
71/// Algorithm 1 is used if `min(left, right)` is small enough to fit onto a stack buffer. The
72/// `min(left, right)` elements are copied onto the buffer, `memmove` is applied to the others, and
73/// the ones on the buffer are moved back into the hole on the opposite side of where they
74/// originated.
75///
76/// # Safety
77///
78/// The specified range must be valid for reading and writing.
79#[inline]
80const unsafe fn ptr_rotate_memmove<T>(left: usize, mid: *mut T, right: usize) {
81 // The `[T; 0]` here is to ensure this is appropriately aligned for T
82 let mut rawarray = MaybeUninit::<(BufType, [T; 0])>::uninit();
83 let buf = rawarray.as_mut_ptr() as *mut T;
84 // SAFETY: `mid-left <= mid-left+right < mid+right`
85 let dim = unsafe { mid.sub(left).add(right) };
86 if left <= right {
87 // SAFETY:
88 //
89 // 1) The `if` condition about the sizes ensures `[mid-left; left]` will fit in
90 // `buf` without overflow and `buf` was created just above and so cannot be
91 // overlapped with any value of `[mid-left; left]`
92 // 2) [mid-left, mid+right) are all valid for reading and writing and we don't care
93 // about overlaps here.
94 // 3) The `if` condition about `left <= right` ensures writing `left` elements to
95 // `dim = mid-left+right` is valid because:
96 // - `buf` is valid and `left` elements were written in it in 1)
97 // - `dim+left = mid-left+right+left = mid+right` and we write `[dim, dim+left)`
98 unsafe {
99 // 1)
100 ptr::copy_nonoverlapping(mid.sub(left), buf, left);
101 // 2)
102 ptr::copy(mid, mid.sub(left), right);
103 // 3)
104 ptr::copy_nonoverlapping(buf, dim, left);
105 }
106 } else {
107 // SAFETY: same reasoning as above but with `left` and `right` reversed
108 unsafe {
109 ptr::copy_nonoverlapping(mid, buf, right);
110 ptr::copy(mid.sub(left), dim, left);
111 ptr::copy_nonoverlapping(buf, mid.sub(left), right);
112 }
113 }
114}
115
116/// Algorithm 2 is used for small values of `left + right` or for large `T`. The elements
117/// are moved into their final positions one at a time starting at `mid - left` and advancing by
118/// `right` steps modulo `left + right`, such that only one temporary is needed. Eventually, we
119/// arrive back at `mid - left`. However, if `gcd(left + right, right)` is not 1, the above steps
120/// skipped over elements. For example:
121/// ```text
122/// left = 10, right = 6
123/// the `^` indicates an element in its final place
124/// 6 7 8 9 10 11 12 13 14 15 . 0 1 2 3 4 5
125/// after using one step of the above algorithm (The X will be overwritten at the end of the round,
126/// and 12 is stored in a temporary):
127/// X 7 8 9 10 11 6 13 14 15 . 0 1 2 3 4 5
128/// ^
129/// after using another step (now 2 is in the temporary):
130/// X 7 8 9 10 11 6 13 14 15 . 0 1 12 3 4 5
131/// ^ ^
132/// after the third step (the steps wrap around, and 8 is in the temporary):
133/// X 7 2 9 10 11 6 13 14 15 . 0 1 12 3 4 5
134/// ^ ^ ^
135/// after 7 more steps, the round ends with the temporary 0 getting put in the X:
136/// 0 7 2 9 4 11 6 13 8 15 . 10 1 12 3 14 5
137/// ^ ^ ^ ^ ^ ^ ^ ^
138/// ```
139/// Fortunately, the number of skipped over elements between finalized elements is always equal, so
140/// we can just offset our starting position and do more rounds (the total number of rounds is the
141/// `gcd(left + right, right)` value). The end result is that all elements are finalized once and
142/// only once.
143///
144/// Algorithm 2 can be vectorized by chunking and performing many rounds at once, but there are too
145/// few rounds on average until `left + right` is enormous, and the worst case of a single
146/// round is always there.
147///
148/// # Safety
149///
150/// The specified range must be valid for reading and writing.
151#[inline]
152const unsafe fn ptr_rotate_gcd<T>(left: usize, mid: *mut T, right: usize) {
153 // Algorithm 2
154 // Microbenchmarks indicate that the average performance for random shifts is better all
155 // the way until about `left + right == 32`, but the worst case performance breaks even
156 // around 16. 24 was chosen as middle ground. If the size of `T` is larger than 4
157 // `usize`s, this algorithm also outperforms other algorithms.
158 // SAFETY: callers must ensure `mid - left` is valid for reading and writing.
159 let x = unsafe { mid.sub(left) };
160 // beginning of first round
161 // SAFETY: see previous comment.
162 let mut tmp: T = unsafe { x.read() };
163 let mut i = right;
164 // `gcd` can be found before hand by calculating `gcd(left + right, right)`,
165 // but it is faster to do one loop which calculates the gcd as a side effect, then
166 // doing the rest of the chunk
167 let mut gcd = right;
168 // benchmarks reveal that it is faster to swap temporaries all the way through instead
169 // of reading one temporary once, copying backwards, and then writing that temporary at
170 // the very end. This is possibly due to the fact that swapping or replacing temporaries
171 // uses only one memory address in the loop instead of needing to manage two.
172 loop {
173 // [long-safety-expl]
174 // SAFETY: callers must ensure `[left, left+mid+right)` are all valid for reading and
175 // writing.
176 //
177 // - `i` start with `right` so `mid-left <= x+i = x+right = mid-left+right < mid+right`
178 // - `i <= left+right-1` is always true
179 // - if `i < left`, `right` is added so `i < left+right` and on the next
180 // iteration `left` is removed from `i` so it doesn't go further
181 // - if `i >= left`, `left` is removed immediately and so it doesn't go further.
182 // - overflows cannot happen for `i` since the function's safety contract ask for
183 // `mid+right-1 = x+left+right` to be valid for writing
184 // - underflows cannot happen because `i` must be bigger or equal to `left` for
185 // a subtraction of `left` to happen.
186 //
187 // So `x+i` is valid for reading and writing if the caller respected the contract
188 tmp = unsafe { x.add(i).replace(tmp) };
189 // instead of incrementing `i` and then checking if it is outside the bounds, we
190 // check if `i` will go outside the bounds on the next increment. This prevents
191 // any wrapping of pointers or `usize`.
192 if i >= left {
193 i -= left;
194 if i == 0 {
195 // end of first round
196 // SAFETY: tmp has been read from a valid source and x is valid for writing
197 // according to the caller.
198 unsafe { x.write(tmp) };
199 break;
200 }
201 // this conditional must be here if `left + right >= 15`
202 if i < gcd {
203 gcd = i;
204 }
205 } else {
206 i += right;
207 }
208 }
209 // finish the chunk with more rounds
210 // FIXME(const-hack): Use `for start in 1..gcd` when available in const
211 let mut start = 1;
212 while start < gcd {
213 // SAFETY: `gcd` is at most equal to `right` so all values in `1..gcd` are valid for
214 // reading and writing as per the function's safety contract, see [long-safety-expl]
215 // above
216 tmp = unsafe { x.add(start).read() };
217 // [safety-expl-addition]
218 //
219 // Here `start < gcd` so `start < right` so `i < right+right`: `right` being the
220 // greatest common divisor of `(left+right, right)` means that `left = right` so
221 // `i < left+right` so `x+i = mid-left+i` is always valid for reading and writing
222 // according to the function's safety contract.
223 i = start + right;
224 loop {
225 // SAFETY: see [long-safety-expl] and [safety-expl-addition]
226 tmp = unsafe { x.add(i).replace(tmp) };
227 if i >= left {
228 i -= left;
229 if i == start {
230 // SAFETY: see [long-safety-expl] and [safety-expl-addition]
231 unsafe { x.add(start).write(tmp) };
232 break;
233 }
234 } else {
235 i += right;
236 }
237 }
238
239 start += 1;
240 }
241}
242
243/// Algorithm 3 utilizes repeated swapping of `min(left, right)` elements.
244///
245/// ///
246/// ```text
247/// left = 11, right = 4
248/// [4 5 6 7 8 9 10 11 12 13 14 . 0 1 2 3]
249/// ^ ^ ^ ^ ^ ^ ^ ^ swapping the right most elements with elements to the left
250/// [4 5 6 7 8 9 10 . 0 1 2 3] 11 12 13 14
251/// ^ ^ ^ ^ ^ ^ ^ ^ swapping these
252/// [4 5 6 . 0 1 2 3] 7 8 9 10 11 12 13 14
253/// we cannot swap any more, but a smaller rotation problem is left to solve
254/// ```
255/// when `left < right` the swapping happens from the left instead.
256///
257/// # Safety
258///
259/// The specified range must be valid for reading and writing.
260#[inline]
261const unsafe fn ptr_rotate_swap<T>(mut left: usize, mut mid: *mut T, mut right: usize) {
262 loop {
263 if left >= right {
264 // Algorithm 3
265 // There is an alternate way of swapping that involves finding where the last swap
266 // of this algorithm would be, and swapping using that last chunk instead of swapping
267 // adjacent chunks like this algorithm is doing, but this way is still faster.
268 loop {
269 // SAFETY:
270 // `left >= right` so `[mid-right, mid+right)` is valid for reading and writing
271 // Subtracting `right` from `mid` each turn is counterbalanced by the addition and
272 // check after it.
273 unsafe {
274 ptr::swap_nonoverlapping(mid.sub(right), mid, right);
275 mid = mid.sub(right);
276 }
277 left -= right;
278 if left < right {
279 break;
280 }
281 }
282 } else {
283 // Algorithm 3, `left < right`
284 loop {
285 // SAFETY: `[mid-left, mid+left)` is valid for reading and writing because
286 // `left < right` so `mid+left < mid+right`.
287 // Adding `left` to `mid` each turn is counterbalanced by the subtraction and check
288 // after it.
289 unsafe {
290 ptr::swap_nonoverlapping(mid.sub(left), mid, left);
291 mid = mid.add(left);
292 }
293 right -= left;
294 if right < left {
295 break;
296 }
297 }
298 }
299 if (right == 0) || (left == 0) {
300 return;
301 }
302
303 // Switch to the buffered algorithm once the smaller side fits in the stack
304 // buffer. The swap algorithm is efficient for reducing large blocks, but its
305 // tail can degenerate into many small swaps when one side becomes tiny.
306 if !cfg!(feature = "optimize_for_size")
307 && const_min(left, right) <= size_of::<BufType>() / size_of::<T>()
308 {
309 // SAFETY: the remaining range `[mid-left, mid+right)` is a subrange of the
310 // original input, so it is valid for reading and writing.
311 unsafe { ptr_rotate_memmove(left, mid, right) };
312 return;
313 }
314 }
315}
316
317// FIXME(const-hack): Use cmp::min when available in const
318const fn const_min(left: usize, right: usize) -> usize {
319 if right < left { right } else { left }
320}