|
@@ -0,0 +1,245 @@
|
|
|
+use platform::types::*;
|
|
|
+
|
|
|
+pub fn introsort(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ let maxdepth = 2 * log2(nel);
|
|
|
+ introsort_helper(base, nel, width, maxdepth, comp);
|
|
|
+}
|
|
|
+
|
|
|
+// NOTE: if num is 0, the result should be considered undefined
|
|
|
+fn log2(num: size_t) -> size_t {
|
|
|
+ const IS_32_BIT: bool = size_t::max_value() as u32 as size_t == size_t::max_value();
|
|
|
+
|
|
|
+ let max_bits = if IS_32_BIT {
|
|
|
+ 31
|
|
|
+ } else {
|
|
|
+ // assuming we are 64-bit (this may or may not need to be updated in the future)
|
|
|
+ 63
|
|
|
+ };
|
|
|
+
|
|
|
+ max_bits - num.to_le().leading_zeros() as size_t
|
|
|
+}
|
|
|
+
|
|
|
+fn introsort_helper(
|
|
|
+ mut base: *mut c_char,
|
|
|
+ mut nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ mut maxdepth: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ const THRESHOLD: size_t = 8;
|
|
|
+
|
|
|
+ // this loop is a trick to save stack space because TCO is not a thing in Rustland
|
|
|
+ // basically, we just change the arguments and loop rather than recursing for the second call
|
|
|
+ // to introsort_helper()
|
|
|
+ loop {
|
|
|
+ if nel < THRESHOLD {
|
|
|
+ insertion_sort(base, nel, width, comp);
|
|
|
+ break;
|
|
|
+ } else if nel > 1 {
|
|
|
+ if maxdepth == 0 {
|
|
|
+ heapsort(base, nel, width, comp);
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ let (left, right) = partition(base, nel, width, comp);
|
|
|
+ let right_base = unsafe { base.add((right + 1) * width) };
|
|
|
+ let right_nel = nel - (right + 1);
|
|
|
+ maxdepth -= 1;
|
|
|
+ if left < nel - right {
|
|
|
+ introsort_helper(base, left, width, maxdepth, comp);
|
|
|
+ base = right_base;
|
|
|
+ nel = right_nel;
|
|
|
+ } else {
|
|
|
+ introsort_helper(right_base, right_nel, width, maxdepth, comp);
|
|
|
+ nel = left;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn insertion_sort(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ for i in 0..nel {
|
|
|
+ for j in (0..i).rev() {
|
|
|
+ let current = unsafe { base.add(j * width) };
|
|
|
+ let prev = unsafe { base.add((j + 1) * width) };
|
|
|
+ if comp(current as *const c_void, prev as *const c_void) > 0 {
|
|
|
+ swap(current, prev, width);
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn heapsort(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ heapify(base, nel, width, comp);
|
|
|
+
|
|
|
+ let mut end = nel - 1;
|
|
|
+ while end > 0 {
|
|
|
+ let end_ptr = unsafe { base.add(end * width) };
|
|
|
+ swap(end_ptr, base, width);
|
|
|
+ end -= 1;
|
|
|
+ heap_sift_down(base, 0, end, width, comp);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn heapify(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ // we start at the last parent in the heap (the parent of the last child)
|
|
|
+ let last_parent = (nel - 2) / 2;
|
|
|
+
|
|
|
+ for start in (0..last_parent + 1).rev() {
|
|
|
+ heap_sift_down(base, start, nel - 1, width, comp);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn heap_sift_down(
|
|
|
+ base: *mut c_char,
|
|
|
+ start: size_t,
|
|
|
+ end: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) {
|
|
|
+ // get the left child of the node at the given index
|
|
|
+ let left_child = |idx| 2 * idx + 1;
|
|
|
+
|
|
|
+ let mut root = start;
|
|
|
+
|
|
|
+ while left_child(root) <= end {
|
|
|
+ let child = left_child(root);
|
|
|
+ let mut swap_idx = root;
|
|
|
+
|
|
|
+ let root_ptr = unsafe { base.add(root * width) };
|
|
|
+ let mut swap_ptr = unsafe { base.add(swap_idx * width) };
|
|
|
+ let first_child_ptr = unsafe { base.add(child * width) };
|
|
|
+ let second_child_ptr = unsafe { base.add((child + 1) * width) };
|
|
|
+
|
|
|
+ if comp(swap_ptr as *const c_void, first_child_ptr as *const c_void) < 0 {
|
|
|
+ swap_idx = child;
|
|
|
+ swap_ptr = first_child_ptr;
|
|
|
+ }
|
|
|
+ if child + 1 <= end
|
|
|
+ && comp(swap_ptr as *const c_void, second_child_ptr as *const c_void) < 0
|
|
|
+ {
|
|
|
+ swap_idx = child + 1;
|
|
|
+ swap_ptr = second_child_ptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ if swap_idx == root {
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ swap(root_ptr, swap_ptr, width);
|
|
|
+ root = swap_idx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[inline]
|
|
|
+fn partition(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) -> (size_t, size_t) {
|
|
|
+ // calculate the median of the first, middle, and last elements and use it as the pivot
|
|
|
+ // to do fewer comparisons, also swap the elements into their correct positions
|
|
|
+ let mut pivot = median_of_three(base, nel, width, comp);
|
|
|
+
|
|
|
+ let mut i = 1;
|
|
|
+ let mut j = 1;
|
|
|
+ let mut n = nel - 2;
|
|
|
+
|
|
|
+ // use this to deal with the Dutch national flag problem
|
|
|
+ while j <= n {
|
|
|
+ let i_ptr = unsafe { base.add(i * width) };
|
|
|
+ let j_ptr = unsafe { base.add(j * width) };
|
|
|
+ let n_ptr = unsafe { base.add(n * width) };
|
|
|
+ let pivot_ptr = unsafe { base.add(pivot * width) };
|
|
|
+
|
|
|
+ let comparison = comp(j_ptr as *const c_void, pivot_ptr as *const c_void);
|
|
|
+ if comparison < 0 {
|
|
|
+ swap(i_ptr, j_ptr, width);
|
|
|
+ if i == pivot {
|
|
|
+ pivot = j;
|
|
|
+ }
|
|
|
+ i += 1;
|
|
|
+ j += 1;
|
|
|
+ } else if comparison > 0 {
|
|
|
+ swap(j_ptr, n_ptr, width);
|
|
|
+ if n == pivot {
|
|
|
+ pivot = j;
|
|
|
+ }
|
|
|
+ n -= 1;
|
|
|
+ } else {
|
|
|
+ j += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ (i, n)
|
|
|
+}
|
|
|
+
|
|
|
+fn median_of_three(
|
|
|
+ base: *mut c_char,
|
|
|
+ nel: size_t,
|
|
|
+ width: size_t,
|
|
|
+ comp: extern "C" fn(*const c_void, *const c_void) -> c_int,
|
|
|
+) -> size_t {
|
|
|
+ let pivot = nel / 2;
|
|
|
+
|
|
|
+ let mid = unsafe { base.add(pivot * width) };
|
|
|
+ let last = unsafe { base.add((nel - 1) * width) };
|
|
|
+ if comp(mid as *const c_void, base as *const c_void) < 0 {
|
|
|
+ swap(mid, base, width);
|
|
|
+ }
|
|
|
+ if comp(last as *const c_void, mid as *const c_void) < 0 {
|
|
|
+ swap(mid, last, width);
|
|
|
+ if comp(mid as *const c_void, base as *const c_void) < 0 {
|
|
|
+ swap(mid, base, width);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pivot
|
|
|
+}
|
|
|
+
|
|
|
+#[inline]
|
|
|
+fn swap(mut ptr1: *mut c_char, mut ptr2: *mut c_char, mut width: size_t) {
|
|
|
+ use core::mem;
|
|
|
+
|
|
|
+ const BUFSIZE: usize = 128;
|
|
|
+
|
|
|
+ let mut buffer: [c_char; BUFSIZE] = unsafe { mem::uninitialized() };
|
|
|
+ while width > 0 {
|
|
|
+ let copy_size = BUFSIZE.min(width as usize);
|
|
|
+
|
|
|
+ unsafe {
|
|
|
+ buffer
|
|
|
+ .as_mut_ptr()
|
|
|
+ .copy_from_nonoverlapping(ptr1, copy_size);
|
|
|
+ ptr1.copy_from_nonoverlapping(ptr2, copy_size);
|
|
|
+ ptr2.copy_from_nonoverlapping(buffer.as_ptr(), copy_size);
|
|
|
+
|
|
|
+ ptr1 = ptr1.add(copy_size);
|
|
|
+ ptr2 = ptr2.add(copy_size);
|
|
|
+ }
|
|
|
+ width -= copy_size as size_t;
|
|
|
+ }
|
|
|
+}
|