Skip to main content

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}