dbsp/utils/sort.rs
1//! This module contains a dynamically typed re-implementation of the stable
2//! sorting algorithm from the Rust standard library. DBSP stores data in sorted vectors.
3//! Using the `std` implementation of sorting, we ended up compiling specialized
4//! implementations of the sorting algorithm for hundreds of concrete types, which slowed down
5//! compilation significantly. The dynamic implementation in this module works for all typed by
6//! taking the comparison function as `&dyn Fn`, along with the size and alignment of a vector
7//! element.
8
9use std::ptr;
10use std::{
11 alloc, cmp,
12 cmp::Ordering,
13 fmt::Debug,
14 mem::{MaybeUninit, align_of, size_of},
15 ops::Range,
16};
17
18fn vec_reverse(v: &mut [u8], val_size: usize) {
19 let len = v.len() / val_size;
20
21 let half_len = len / 2;
22 let Range { start, end } = v.as_mut_ptr_range();
23
24 let mut i = 0;
25 while i < half_len {
26 unsafe {
27 ptr::swap_nonoverlapping(
28 start.add(i * val_size),
29 end.sub((1 + i) * val_size),
30 val_size,
31 );
32 }
33
34 i += 1;
35 }
36}
37
38// When dropped, copies from `src` into `dest`.
39struct InsertionHole {
40 src: *const u8,
41 dest: *mut u8,
42 val_size: usize,
43}
44
45impl Drop for InsertionHole {
46 fn drop(&mut self) {
47 // SAFETY: This is a helper class. Please refer to its usage for correctness.
48 // Namely, one must be sure that `src` and `dst` does not overlap as
49 // required by `ptr::copy_nonoverlapping` and are both valid for writes.
50 unsafe {
51 ptr::copy_nonoverlapping(self.src, self.dest, self.val_size);
52 }
53 }
54}
55
56/// Inserts `v[v.len() - 1]` into pre-sorted sequence `v[..v.len() - 1]` so that
57/// whole `v[..]` becomes sorted.
58unsafe fn insert_tail(
59 v: &mut [u8],
60 val_size: usize,
61 is_less: &dyn Fn(*const u8, *const u8) -> bool,
62 scratch: *mut u8,
63) {
64 debug_assert!(v.len() / val_size >= 2);
65
66 let arr_ptr = v.as_mut_ptr();
67 let i = v.len() / val_size - 1;
68
69 // SAFETY: caller must ensure v is at least len 2.
70 unsafe {
71 // See insert_head which talks about why this approach is beneficial.
72 let i_ptr = arr_ptr.add(i * val_size);
73
74 // It's important that we use i_ptr here. If this check is positive and we
75 // continue, We want to make sure that no other copy of the value was
76 // seen by is_less. Otherwise we would have to copy it back.
77 if is_less(i_ptr, i_ptr.sub(val_size)) {
78 // It's important, that we use tmp for comparison from now on. As it is the
79 // value that will be copied back. And notionally we could have
80 // created a divergence if we copy back the wrong value.
81 ptr::copy_nonoverlapping(i_ptr, scratch, val_size);
82 //let tmp = mem::ManuallyDrop::new(ptr::read(i_ptr));
83
84 // Intermediate state of the insertion process is always tracked by `hole`,
85 // which serves two purposes:
86 // 1. Protects integrity of `v` from panics in `is_less`.
87 // 2. Fills the remaining hole in `v` in the end.
88 //
89 // Panic safety:
90 //
91 // If `is_less` panics at any point during the process, `hole` will get dropped
92 // and fill the hole in `v` with `tmp`, thus ensuring that `v` still
93 // holds every object it initially held exactly once.
94 let mut hole = InsertionHole {
95 src: scratch,
96 dest: i_ptr.sub(val_size),
97 val_size,
98 };
99 ptr::copy_nonoverlapping(hole.dest, i_ptr, val_size);
100
101 // SAFETY: We know i is at least 1.
102 for j in (0..(i - 1)).rev() {
103 let j_ptr = arr_ptr.add(j * val_size);
104 if !is_less(scratch, j_ptr) {
105 break;
106 }
107
108 ptr::copy_nonoverlapping(j_ptr, hole.dest, val_size);
109 hole.dest = j_ptr;
110 }
111 // `hole` gets dropped and thus copies `tmp` into the remaining hole
112 // in `v`.
113 }
114 }
115}
116
117/// Sort `v` assuming `v[..offset]` is already sorted.
118///
119/// Never inline this function to avoid code bloat. It still optimizes nicely
120/// and has practically no performance impact. Even improving performance in
121/// some cases.
122#[inline(never)]
123pub(super) fn insertion_sort_shift_left(
124 v: &mut [u8],
125 val_size: usize,
126 offset: usize,
127 is_less: &dyn Fn(*const u8, *const u8) -> bool,
128 scratch: *mut u8,
129) {
130 let len = v.len() / val_size;
131
132 // Using assert here improves performance.
133 assert!(offset != 0 && offset <= len);
134
135 // Shift each element of the unsorted region v[i..] as far left as is needed to
136 // make v sorted.
137 for i in offset..len {
138 // SAFETY: we tested that `offset` must be at least 1, so this loop is only
139 // entered if len >= 2. The range is exclusive and we know `i` must be
140 // at least 1 so this slice has at >least len 2.
141 unsafe {
142 insert_tail(
143 &mut v[..=(i + 1) * val_size - 1],
144 val_size,
145 is_less,
146 scratch,
147 );
148 }
149 }
150}
151
152/// Merges non-decreasing runs `v[..mid]` and `v[mid..]` using `buf` as
153/// temporary storage, and stores the result into `v[..]`.
154///
155/// # Safety
156///
157/// The two slices must be non-empty and `mid` must be in bounds. Buffer `buf`
158/// must be long enough to hold a copy of the shorter slice. Also, `T` must not
159/// be a zero-sized type.
160unsafe fn merge(
161 v: &mut [u8],
162 val_size: usize,
163 mid: usize,
164 buf: *mut u8,
165 is_less: &dyn Fn(*const u8, *const u8) -> bool,
166) {
167 let len = v.len() / val_size;
168 let v = v.as_mut_ptr();
169
170 // SAFETY: mid and len must be in-bounds of v.
171 let (v_mid, v_end) = unsafe { (v.add(mid * val_size), v.add(len * val_size)) };
172
173 // The merge process first copies the shorter run into `buf`. Then it traces the
174 // newly copied run and the longer run forwards (or backwards), comparing
175 // their next unconsumed elements and copying the lesser (or greater) one
176 // into `v`.
177 //
178 // As soon as the shorter run is fully consumed, the process is done. If the
179 // longer run gets consumed first, then we must copy whatever is left of the
180 // shorter run into the remaining hole in `v`.
181 //
182 // Intermediate state of the process is always tracked by `hole`, which serves
183 // two purposes:
184 // 1. Protects integrity of `v` from panics in `is_less`.
185 // 2. Fills the remaining hole in `v` if the longer run gets consumed first.
186 //
187 // Panic safety:
188 //
189 // If `is_less` panics at any point during the process, `hole` will get dropped
190 // and fill the hole in `v` with the unconsumed range in `buf`, thus
191 // ensuring that `v` still holds every object it initially held exactly
192 // once.
193 let mut hole;
194
195 if mid <= len - mid {
196 // The left run is shorter.
197
198 // SAFETY: buf must have enough capacity for `v[..mid]`.
199 unsafe {
200 ptr::copy_nonoverlapping(v, buf, mid * val_size);
201 hole = MergeHole {
202 start: buf,
203 end: buf.add(mid * val_size),
204 dest: v,
205 };
206 }
207
208 // Initially, these pointers point to the beginnings of their arrays.
209 let left = &mut hole.start;
210 let mut right = v_mid;
211 let out = &mut hole.dest;
212
213 while *left < hole.end && right < v_end {
214 // Consume the lesser side.
215 // If equal, prefer the left run to maintain stability.
216
217 // SAFETY: left and right must be valid and part of v same for out.
218 unsafe {
219 let is_l = is_less(right, *left);
220 let to_copy = if is_l { right } else { *left };
221 ptr::copy_nonoverlapping(to_copy, *out, val_size);
222 *out = out.add(val_size);
223 right = right.add((is_l as usize) * val_size);
224 *left = left.add((!is_l as usize) * val_size);
225 }
226 }
227 } else {
228 // The right run is shorter.
229
230 // SAFETY: buf must have enough capacity for `v[mid..]`.
231 unsafe {
232 ptr::copy_nonoverlapping(v_mid, buf, (len - mid) * val_size);
233 hole = MergeHole {
234 start: buf,
235 end: buf.add((len - mid) * val_size),
236 dest: v_mid,
237 };
238 }
239
240 // Initially, these pointers point past the ends of their arrays.
241 let left = &mut hole.dest;
242 let right = &mut hole.end;
243 let mut out = v_end;
244
245 while v < *left && buf < *right {
246 // Consume the greater side.
247 // If equal, prefer the right run to maintain stability.
248
249 // SAFETY: left and right must be valid and part of v same for out.
250 unsafe {
251 let is_l = is_less(&*right.sub(val_size), &*left.sub(val_size));
252 *left = left.sub((is_l as usize) * val_size);
253 *right = right.sub((!is_l as usize) * val_size);
254 let to_copy = if is_l { *left } else { *right };
255 out = out.sub(val_size);
256 ptr::copy_nonoverlapping(to_copy, out, val_size);
257 }
258 }
259 }
260 // Finally, `hole` gets dropped. If the shorter run was not fully consumed,
261 // whatever remains of it will now be copied into the hole in `v`.
262
263 // When dropped, copies the range `start..end` into `dest..`.
264 struct MergeHole {
265 start: *mut u8,
266 end: *mut u8,
267 dest: *mut u8,
268 }
269
270 impl Drop for MergeHole {
271 fn drop(&mut self) {
272 // SAFETY: `T` is not a zero-sized type, and these are pointers into a slice's
273 // elements.
274 unsafe {
275 let len = self.end as usize - self.start as usize;
276 ptr::copy_nonoverlapping(self.start, self.dest, len);
277 }
278 }
279 }
280}
281
282/// This merge sort borrows some (but not all) ideas from TimSort, which used to
283/// be described in detail [here](https://github.com/python/cpython/blob/main/Objects/listsort.txt). However Python
284/// has switched to a Powersort based implementation.
285///
286/// The algorithm identifies strictly descending and non-descending
287/// subsequences, which are called natural runs. There is a stack of pending
288/// runs yet to be merged. Each newly found run is pushed onto the stack, and
289/// then some pairs of adjacent runs are merged until these two invariants are
290/// satisfied:
291///
292/// 1. for every `i` in `1..runs.len()`: `runs[i - 1].len > runs[i].len`
293/// 2. for every `i` in `2..runs.len()`: `runs[i - 2].len > runs[i - 1].len +
294/// runs[i].len`
295///
296/// The invariants ensure that the total running time is *O*(*n* \* log(*n*))
297/// worst-case.
298pub fn merge_sort(
299 v: &mut [u8],
300 val_size: usize,
301 align: usize,
302 is_less: &dyn Fn(*const u8, *const u8) -> bool,
303 scratch: *mut u8,
304) {
305 // Slices of up to this length get sorted using insertion sort.
306 const MAX_INSERTION: usize = 20;
307
308 if val_size == 0 {
309 // Sorting has no meaningful behavior on zero-sized types. Do nothing.
310 return;
311 }
312
313 debug_assert_eq!(v.len() % val_size, 0);
314
315 let len = v.len() / val_size;
316
317 // Short arrays get sorted in-place via insertion sort to avoid allocations.
318 if len <= MAX_INSERTION {
319 if len >= 2 {
320 insertion_sort_shift_left(v, val_size, 1, is_less, scratch);
321 }
322 return;
323 }
324
325 // Allocate a buffer to use as scratch memory. We keep the length 0 so we can
326 // keep in it shallow copies of the contents of `v` without risking the
327 // dtors running on copies if `is_less` panics. When merging two sorted
328 // runs, this buffer holds a copy of the shorter run, which will always have
329 // length at most `len / 2`.
330 let buf = BufGuard::new(len / 2, val_size, align);
331 let buf_ptr = buf.buf_ptr.as_ptr();
332
333 let mut runs = RunVec::new();
334
335 let mut end = 0;
336 let mut start = 0;
337
338 // Scan forward. Memory pre-fetching prefers forward scanning vs backwards
339 // scanning, and the code-gen is usually better. For the most sensitive
340 // types such as integers, these are merged bidirectionally at once. So
341 // there is no benefit in scanning backwards.
342 while end < len {
343 let (streak_end, was_reversed) = find_streak(&v[start * val_size..], val_size, is_less);
344 end += streak_end;
345 if was_reversed {
346 vec_reverse(&mut v[start * val_size..end * val_size], val_size);
347 }
348
349 // Insert some more elements into the run if it's too short. Insertion sort is
350 // faster than merge sort on short sequences, so this significantly
351 // improves performance.
352 end = provide_sorted_batch(v, val_size, start, end, is_less, scratch);
353
354 // Push this run onto the stack.
355 runs.push(TimSortRun {
356 start,
357 len: end - start,
358 });
359 start = end;
360
361 // Merge some pairs of adjacent runs to satisfy the invariants.
362 while let Some(r) = collapse(runs.as_slice(), len) {
363 let left = runs[r];
364 let right = runs[r + 1];
365 let merge_slice = &mut v[left.start * val_size..(right.start + right.len) * val_size];
366 // SAFETY: `buf_ptr` must hold enough capacity for the shorter of the two sides,
367 // and neither side may be on length 0.
368 unsafe {
369 merge(merge_slice, val_size, left.len, buf_ptr, is_less);
370 }
371 runs[r + 1] = TimSortRun {
372 start: left.start,
373 len: left.len + right.len,
374 };
375 runs.remove(r);
376 }
377 }
378
379 // Finally, exactly one run must remain in the stack.
380 debug_assert!(runs.len() == 1 && runs[0].start == 0 && runs[0].len == len);
381
382 // Examines the stack of runs and identifies the next pair of runs to merge.
383 // More specifically, if `Some(r)` is returned, that means `runs[r]` and
384 // `runs[r + 1]` must be merged next. If the algorithm should continue
385 // building a new run instead, `None` is returned.
386 //
387 // TimSort is infamous for its buggy implementations, as described here:
388 // http://envisage-project.eu/timsort-specification-and-verification/
389 //
390 // The gist of the story is: we must enforce the invariants on the top four runs
391 // on the stack. Enforcing them on just top three is not sufficient to
392 // ensure that the invariants will still hold for *all* runs in the stack.
393 //
394 // This function correctly checks invariants for the top four runs.
395 // Additionally, if the top run starts at index 0, it will always demand a
396 // merge operation until the stack is fully collapsed, in order to complete
397 // the sort.
398 #[inline]
399 fn collapse(runs: &[TimSortRun], stop: usize) -> Option<usize> {
400 let n = runs.len();
401 if n >= 2
402 && (runs[n - 1].start + runs[n - 1].len == stop
403 || runs[n - 2].len <= runs[n - 1].len
404 || (n >= 3 && runs[n - 3].len <= runs[n - 2].len + runs[n - 1].len)
405 || (n >= 4 && runs[n - 4].len <= runs[n - 3].len + runs[n - 2].len))
406 {
407 if n >= 3 && runs[n - 3].len < runs[n - 1].len {
408 Some(n - 3)
409 } else {
410 Some(n - 2)
411 }
412 } else {
413 None
414 }
415 }
416
417 // Extremely basic versions of Vec.
418 // Their use is super limited and by having the code here, it allows reuse
419 // between the sort implementations.
420 struct BufGuard {
421 buf_ptr: ptr::NonNull<u8>,
422 capacity: usize,
423 val_size: usize,
424 align: usize,
425 }
426
427 impl BufGuard {
428 fn new(len: usize, val_size: usize, align: usize) -> Self {
429 let buf_ptr = unsafe {
430 alloc::alloc(alloc::Layout::from_size_align_unchecked(
431 val_size * len,
432 align,
433 ))
434 };
435
436 Self {
437 buf_ptr: ptr::NonNull::new(buf_ptr).unwrap(),
438 capacity: len,
439 val_size,
440 align,
441 }
442 }
443 }
444
445 impl Drop for BufGuard {
446 fn drop(&mut self) {
447 unsafe {
448 alloc::dealloc(
449 self.buf_ptr.as_ptr(),
450 alloc::Layout::from_size_align_unchecked(
451 self.val_size * self.capacity,
452 self.align,
453 ),
454 )
455 }
456 }
457 }
458
459 struct RunVec {
460 buf_ptr: ptr::NonNull<TimSortRun>,
461 capacity: usize,
462 len: usize,
463 }
464
465 impl RunVec {
466 fn new() -> Self {
467 // Most slices can be sorted with at most 16 runs in-flight.
468 const START_RUN_CAPACITY: usize = 16;
469
470 Self {
471 buf_ptr: ptr::NonNull::new(run_alloc(START_RUN_CAPACITY)).unwrap(),
472 capacity: START_RUN_CAPACITY,
473 len: 0,
474 }
475 }
476
477 fn push(&mut self, val: TimSortRun) {
478 if self.len == self.capacity {
479 let old_capacity = self.capacity;
480 let old_buf_ptr = self.buf_ptr.as_ptr();
481
482 self.capacity *= 2;
483 self.buf_ptr = ptr::NonNull::new(run_alloc(self.capacity)).unwrap();
484
485 // SAFETY: buf_ptr new and old were correctly allocated and old_buf_ptr has
486 // old_capacity valid elements.
487 unsafe {
488 ptr::copy_nonoverlapping(old_buf_ptr, self.buf_ptr.as_ptr(), old_capacity);
489 }
490
491 run_dealloc(old_buf_ptr, old_capacity);
492 }
493
494 // SAFETY: The invariant was just checked.
495 unsafe {
496 self.buf_ptr.as_ptr().add(self.len).write(val);
497 }
498 self.len += 1;
499 }
500
501 fn remove(&mut self, index: usize) {
502 if index >= self.len {
503 panic!("Index out of bounds");
504 }
505
506 // SAFETY: buf_ptr needs to be valid and len invariant upheld.
507 unsafe {
508 // the place we are taking from.
509 let ptr = self.buf_ptr.as_ptr().add(index);
510
511 // Shift everything down to fill in that spot.
512 ptr::copy(ptr.add(1), ptr, self.len - index - 1);
513 }
514 self.len -= 1;
515 }
516
517 fn as_slice(&self) -> &[TimSortRun] {
518 // SAFETY: Safe as long as buf_ptr is valid and len invariant was upheld.
519 unsafe { &*ptr::slice_from_raw_parts(self.buf_ptr.as_ptr(), self.len) }
520 }
521
522 fn len(&self) -> usize {
523 self.len
524 }
525 }
526
527 impl core::ops::Index<usize> for RunVec {
528 type Output = TimSortRun;
529
530 fn index(&self, index: usize) -> &Self::Output {
531 if index < self.len {
532 // SAFETY: buf_ptr and len invariant must be upheld.
533 unsafe {
534 return &*(self.buf_ptr.as_ptr().add(index));
535 }
536 }
537
538 panic!("Index out of bounds");
539 }
540 }
541
542 impl core::ops::IndexMut<usize> for RunVec {
543 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
544 if index < self.len {
545 // SAFETY: buf_ptr and len invariant must be upheld.
546 unsafe {
547 return &mut *(self.buf_ptr.as_ptr().add(index));
548 }
549 }
550
551 panic!("Index out of bounds");
552 }
553 }
554
555 impl Drop for RunVec {
556 fn drop(&mut self) {
557 // As long as TimSortRun is Copy we don't need to drop them individually but
558 // just the whole allocation.
559 run_dealloc(self.buf_ptr.as_ptr(), self.capacity);
560 }
561 }
562}
563
564/// Internal type used by merge_sort.
565#[derive(Clone, Copy, Debug)]
566pub struct TimSortRun {
567 len: usize,
568 start: usize,
569}
570
571/// Takes a range as denoted by start and end, that is already sorted and
572/// extends it to the right if necessary with sorts optimized for smaller ranges
573/// such as insertion sort.
574fn provide_sorted_batch(
575 v: &mut [u8],
576 val_size: usize,
577 start: usize,
578 mut end: usize,
579 is_less: &dyn Fn(*const u8, *const u8) -> bool,
580 scratch: *mut u8,
581) -> usize {
582 debug_assert_eq!(v.len() % val_size, 0);
583
584 let len = v.len() / val_size;
585 assert!(end >= start && end <= len);
586
587 // This value is a balance between least comparisons and best performance, as
588 // influenced by for example cache locality.
589 const MIN_INSERTION_RUN: usize = 10;
590
591 // Insert some more elements into the run if it's too short. Insertion sort is
592 // faster than merge sort on short sequences, so this significantly improves
593 // performance.
594 let start_end_diff = end - start;
595
596 if start_end_diff < MIN_INSERTION_RUN && end < len {
597 // v[start_found..end] are elements that are already sorted in the input. We
598 // want to extend the sorted region to the left, so we push up
599 // MIN_INSERTION_RUN - 1 to the right. Which is more efficient that
600 // trying to push those already sorted elements to the left.
601 end = cmp::min(start + MIN_INSERTION_RUN, len);
602 let presorted_start = cmp::max(start_end_diff, 1);
603
604 insertion_sort_shift_left(
605 &mut v[start * val_size..end * val_size],
606 val_size,
607 presorted_start,
608 is_less,
609 scratch,
610 );
611 }
612
613 end
614}
615
616/// Finds a streak of presorted elements starting at the beginning of the slice.
617/// Returns the first value that is not part of said streak, and a bool denoting
618/// whether the streak was reversed. Streaks can be increasing or decreasing.
619fn find_streak(
620 v: &[u8],
621 val_size: usize,
622 is_less: &dyn Fn(*const u8, *const u8) -> bool,
623) -> (usize, bool) {
624 debug_assert_eq!(v.len() % val_size, 0);
625
626 let len = v.len() / val_size;
627
628 if len < 2 {
629 return (len, false);
630 }
631
632 let v = v.as_ptr();
633
634 let mut end = 2;
635
636 // SAFETY: See below specific.
637 unsafe {
638 // SAFETY: We checked that len >= 2, so 0 and 1 are valid indices.
639 let assume_reverse = is_less(v.add(val_size), v);
640
641 // SAFETY: We know end >= 2 and check end < len.
642 // From that follows that accessing v at end and end - 1 is safe.
643 if assume_reverse {
644 while end < len && is_less(v.add(end * val_size), v.add((end - 1) * val_size)) {
645 end += 1;
646 }
647
648 (end, true)
649 } else {
650 while end < len && !is_less(v.add(end * val_size), v.add((end - 1) * val_size)) {
651 end += 1;
652 }
653 (end, false)
654 }
655 }
656}
657
658fn run_alloc(len: usize) -> *mut TimSortRun {
659 // SAFETY: Creating the layout is safe as long as merge_sort never calls this
660 // with an obscene length or 0.
661 unsafe {
662 alloc::alloc(alloc::Layout::array::<TimSortRun>(len).unwrap_unchecked()) as *mut TimSortRun
663 }
664}
665
666fn run_dealloc(buf_ptr: *mut TimSortRun, len: usize) {
667 // SAFETY: The caller must ensure that buf_ptr was created by elem_alloc_fn with
668 // the same len.
669 unsafe {
670 alloc::dealloc(
671 buf_ptr as *mut u8,
672 alloc::Layout::array::<TimSortRun>(len).unwrap_unchecked(),
673 );
674 }
675}
676
677pub fn stable_sort<T: Ord>(slice: &mut [T]) {
678 stable_sort_by(slice, |x, y| x.cmp(y))
679}
680
681pub fn stable_sort_by<T, F>(slice: &mut [T], cmp: F)
682where
683 F: Fn(&T, &T) -> Ordering,
684{
685 let byte_slice = unsafe {
686 std::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, std::mem::size_of_val(slice))
687 };
688 let is_less = (&|x: *const u8, y: *const u8| {
689 let x = unsafe { &*(x as *const T) };
690 let y = unsafe { &*(y as *const T) };
691 cmp(x, y) == Ordering::Less
692 }) as &dyn Fn(*const u8, *const u8) -> bool;
693
694 merge_sort(
695 byte_slice,
696 size_of::<T>(),
697 align_of::<T>(),
698 is_less,
699 <MaybeUninit<T>>::uninit().as_mut_ptr() as *mut u8,
700 );
701
702 // println!("sorted: {slice:?}");
703}
704
705#[cfg(test)]
706mod test {
707 use super::stable_sort;
708 use proptest::{collection::vec, prelude::*};
709
710 #[test]
711 fn test_stable_sort() {
712 let long_sorted: Vec<i32> = (0..1000).collect();
713 let long_reversed: Vec<i32> = (0..1000).rev().collect();
714 let corpus: Vec<Vec<i32>> = vec![
715 vec![],
716 vec![10],
717 vec![5, 4],
718 vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
719 vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 3, 4, 1, 2],
720 vec![
721 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1560281088, 234560113, 0, 0, 0, 0, 0, 0, 0, 0, 0,
722 ],
723 long_sorted,
724 long_reversed,
725 ];
726
727 for mut vec in corpus.into_iter() {
728 let mut expected = vec.clone();
729 expected.sort();
730
731 stable_sort(&mut vec);
732
733 assert_eq!(vec, expected);
734 }
735 }
736
737 prop_compose! {
738 fn short_vec()(batch in vec(any::<u32>(), 0..30)) -> Vec<u32> {
739 batch
740 }
741 }
742
743 prop_compose! {
744 fn long_vec()(batch in vec(any::<u32>(), 0..300)) -> Vec<u32> {
745 batch
746 }
747 }
748
749 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
750 struct TestStruct {
751 f1: u16,
752 f2: String,
753 f3: i64,
754 f4: i8,
755 f5: String,
756 f6: u8,
757 f7: Vec<u8>,
758 }
759
760 fn test_struct() -> impl Strategy<Value = TestStruct> {
761 (
762 any::<u16>(),
763 any::<String>(),
764 any::<i64>(),
765 any::<i8>(),
766 any::<String>(),
767 any::<u8>(),
768 vec(any::<u8>(), 0..20),
769 )
770 .prop_map(|(f1, f2, f3, f4, f5, f6, f7)| TestStruct {
771 f1,
772 f2,
773 f3,
774 f4,
775 f5,
776 f6,
777 f7,
778 })
779 }
780
781 proptest! {
782 #![proptest_config(ProptestConfig::with_cases(10000))]
783
784 #[test]
785 fn stable_sort_small_proptest(mut v in short_vec()) {
786 let mut expected = v.clone();
787 expected.sort();
788
789 stable_sort(&mut v);
790 assert_eq!(v, expected);
791 }
792 }
793
794 proptest! {
795 #![proptest_config(ProptestConfig::with_cases(1000))]
796 #[test]
797 fn stable_sort_small_structs_proptest(mut v in vec(test_struct(), 0..25)) {
798 let mut expected = v.clone();
799 expected.sort();
800
801 stable_sort(&mut v);
802 assert_eq!(v, expected);
803 }
804 }
805
806 proptest! {
807 #![proptest_config(ProptestConfig::with_cases(1000))]
808 #[test]
809 fn stable_sort_large_proptest(mut v in long_vec()) {
810 let mut expected = v.clone();
811 expected.sort();
812
813 stable_sort(&mut v);
814 assert_eq!(v, expected);
815 }
816 }
817}