Skip to content

Commit 1e55d65

Browse files
committed
WIP(will be squashed)
1 parent 1a38674 commit 1e55d65

File tree

1 file changed

+263
-1
lines changed

1 file changed

+263
-1
lines changed

library/core/src/slice/sort/shared/smallsort.rs

+263-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
use crate::mem::{self, ManuallyDrop, MaybeUninit};
44
use crate::slice::sort::shared::FreezeMarker;
55
use crate::{intrinsics, ptr, slice};
6+
use safety::{ensures, requires};
7+
8+
#[cfg(kani)]
9+
use crate::{kani, ub_checks};
610

711
// It's important to differentiate between SMALL_SORT_THRESHOLD performance for
812
// small slices and small-sort performance sorting small sub-slices as part of
@@ -62,6 +66,26 @@ impl<T: FreezeMarker> StableSmallSortTypeImpl for T {
6266
}
6367
}
6468

69+
// This wrapper contract function exists because;
70+
// - kani contract attribute macros doesnt't work with `default fn`
71+
// - we cannot specify the trait member function with `proof_for_contract`
72+
#[cfg(kani)]
73+
#[kani::modifies(v)]
74+
#[ensures(|_| {
75+
let mut is_less = is_less.clone();
76+
v.is_sorted_by(|a, b| !is_less(b, a))
77+
})]
78+
pub(crate) fn _stable_small_sort_type_impl_small_sort<T, F>(
79+
v: &mut [T],
80+
scratch: &mut [MaybeUninit<T>],
81+
is_less: &mut F,
82+
)
83+
where
84+
F: FnMut(&T, &T) -> bool + Clone,
85+
{
86+
<T as StableSmallSortTypeImpl>::small_sort(v, scratch, is_less);
87+
}
88+
6589
/// Using a trait allows us to specialize on `Freeze` which in turn allows us to make safe
6690
/// abstractions.
6791
pub(crate) trait UnstableSmallSortTypeImpl: Sized {
@@ -102,6 +126,25 @@ impl<T: FreezeMarker> UnstableSmallSortTypeImpl for T {
102126
}
103127
}
104128

129+
// This wrapper contract function exists because;
130+
// - kani contract attribute macros doesnt't work with `default fn`
131+
// - we cannot specify the trait member function with `proof_for_contract`
132+
#[cfg(kani)]
133+
#[kani::modifies(v)]
134+
#[ensures(|_| {
135+
let mut is_less = is_less.clone();
136+
v.is_sorted_by(|a, b| !is_less(b, a))
137+
})]
138+
pub(crate) fn _unstable_small_sort_type_impl_small_sort<T, F>(
139+
v: &mut [T],
140+
is_less: &mut F,
141+
)
142+
where
143+
F: FnMut(&T, &T) -> bool + Clone,
144+
{
145+
<T as UnstableSmallSortTypeImpl>::small_sort(v, is_less);
146+
}
147+
105148
/// FIXME(const_trait_impl) use original ipnsort approach with choose_unstable_small_sort,
106149
/// as found here <https://github.com/Voultapher/sort-research-rs/blob/438fad5d0495f65d4b72aa87f0b62fc96611dff3/ipnsort/src/smallsort.rs#L83C10-L83C36>.
107150
pub(crate) trait UnstableSmallSortFreezeTypeImpl: Sized + FreezeMarker {
@@ -170,6 +213,26 @@ impl<T: FreezeMarker + CopyMarker> UnstableSmallSortFreezeTypeImpl for T {
170213
}
171214
}
172215

216+
// This wrapper contract function exists because;
217+
// - kani contract attribute macros doesnt't work with `default fn`
218+
// - we cannot specify the trait member function with `proof_for_contract`
219+
#[cfg(kani)]
220+
#[kani::modifies(v)]
221+
#[ensures(|_| {
222+
let mut is_less = is_less.clone();
223+
v.is_sorted_by(|a, b| !is_less(b, a))
224+
})]
225+
pub(crate) fn _unstable_small_sort_freeze_type_impl_small_sort<T, F>(
226+
v: &mut [T],
227+
is_less: &mut F,
228+
)
229+
where
230+
T: FreezeMarker + CopyMarker,
231+
F: FnMut(&T, &T) -> bool + Clone,
232+
{
233+
<T as UnstableSmallSortFreezeTypeImpl>::small_sort(v, is_less);
234+
}
235+
173236
/// Optimal number of comparisons, and good perf.
174237
const SMALL_SORT_FALLBACK_THRESHOLD: usize = 16;
175238

@@ -539,6 +602,27 @@ where
539602
///
540603
/// # Safety
541604
/// begin < tail and p must be valid and initialized for all begin <= p <= tail.
605+
#[cfg_attr(
606+
kani,
607+
kani::modifies(
608+
ptr::slice_from_raw_parts_mut(begin, tail.addr().saturating_sub(tail.addr())))
609+
)
610+
]
611+
#[requires(begin.addr() < tail.addr() && {
612+
let len = tail.addr() - begin.addr();
613+
let is_less: &mut F = unsafe { mem::transmute(&is_less) };
614+
(0..=len).into_iter().all(|i| {
615+
let p = begin.add(i);
616+
ub_checks::can_dereference(p as *const _) &&
617+
ub_checks::can_write(p) &&
618+
ub_checks::same_allocation(begin as *const _, p as *const _)
619+
}) && (0..(len - 1)).into_iter().all(|i| !is_less(&*begin.add(i + 1), &*begin.add(i)))
620+
})]
621+
#[ensures(|_| {
622+
let len = tail.addr() - begin.addr();
623+
let is_less: &mut F = unsafe { mem::transmute(&is_less) };
624+
(0..len).into_iter().all(|i| !is_less(&*begin.add(i + 1), &*begin.add(i)))
625+
})]
542626
unsafe fn insert_tail<T, F: FnMut(&T, &T) -> bool>(begin: *mut T, tail: *mut T, is_less: &mut F) {
543627
// SAFETY: see individual comments.
544628
unsafe {
@@ -556,7 +640,13 @@ unsafe fn insert_tail<T, F: FnMut(&T, &T) -> bool>(begin: *mut T, tail: *mut T,
556640
let tmp = ManuallyDrop::new(tail.read());
557641
let mut gap_guard = CopyOnDrop { src: &*tmp, dst: tail, len: 1 };
558642

559-
loop {
643+
#[safety::loop_invariant(
644+
sift.addr() >= begin.addr() && sift.addr() < tail.addr()
645+
)]
646+
// FIXME: This was `loop` but kani's loop contract doesn't support `loop`.
647+
// Once it is supported, replace `while true` with the original `loop`
648+
#[allow(while_true)]
649+
while true {
560650
// SAFETY: we move sift into the gap (which is valid), and point the
561651
// gap guard destination at sift, ensuring that if a panic occurs the
562652
// gap is once again filled.
@@ -577,6 +667,15 @@ unsafe fn insert_tail<T, F: FnMut(&T, &T) -> bool>(begin: *mut T, tail: *mut T,
577667
}
578668

579669
/// Sort `v` assuming `v[..offset]` is already sorted.
670+
#[cfg_attr(kani, kani::modifies(v))]
671+
#[requires(offset != 0 && offset <= v.len() && {
672+
let is_less: &mut F = unsafe { mem::transmute(&is_less) };
673+
v[..offset].is_sorted_by(|a, b| !is_less(b, a))
674+
})]
675+
#[ensures(|_| {
676+
let is_less: &mut F = unsafe { mem::transmute(&is_less) };
677+
v.is_sorted_by(|a, b| !is_less(b, a))
678+
})]
580679
pub fn insertion_sort_shift_left<T, F: FnMut(&T, &T) -> bool>(
581680
v: &mut [T],
582681
offset: usize,
@@ -597,6 +696,14 @@ pub fn insertion_sort_shift_left<T, F: FnMut(&T, &T) -> bool>(
597696
let v_end = v_base.add(len);
598697
let mut tail = v_base.add(offset);
599698
while tail != v_end {
699+
// FIXME: This should be loop contract but sadly, making this into
700+
// loop invariant causes OOM
701+
#[cfg(kani)]
702+
kani::assert(
703+
tail.addr() > v_base.addr() && tail.addr() <= v_end.addr(),
704+
"loop invariants",
705+
);
706+
600707
// SAFETY: v_base and tail are both valid pointers to elements, and
601708
// v_base < tail since we checked offset != 0.
602709
insert_tail(v_base, tail, is_less);
@@ -609,6 +716,28 @@ pub fn insertion_sort_shift_left<T, F: FnMut(&T, &T) -> bool>(
609716

610717
/// SAFETY: The caller MUST guarantee that `v_base` is valid for 4 reads and
611718
/// `dst` is valid for 4 writes. The result will be stored in `dst[0..4]`.
719+
#[cfg_attr(kani, kani::modifies(dst, dst.add(1), dst.add(2), dst.add(3)))]
720+
#[requires(
721+
(0..4).into_iter().all(|i| {
722+
let p = v_base.add(i);
723+
ub_checks::can_dereference(p) &&
724+
ub_checks::same_allocation(v_base, p)
725+
})
726+
)]
727+
#[requires(
728+
(0..4).into_iter().all(|i| {
729+
let p = dst.add(i);
730+
ub_checks::can_write(p) &&
731+
ub_checks::same_allocation(dst, p)
732+
})
733+
)]
734+
#[ensures(|_| {
735+
let is_less: &mut F = unsafe { mem::transmute(&is_less) };
736+
(0..3).into_iter().all(|i| !is_less(
737+
&*dst.add(i + 1),
738+
&*dst.add(i),
739+
))
740+
})]
612741
pub unsafe fn sort4_stable<T, F: FnMut(&T, &T) -> bool>(
613742
v_base: *const T,
614743
dst: *mut T,
@@ -870,3 +999,136 @@ pub(crate) const fn has_efficient_in_place_swap<T>() -> bool {
870999
// Heuristic that holds true on all tested 64-bit capable architectures.
8711000
mem::size_of::<T>() <= 8 // mem::size_of::<u64>()
8721001
}
1002+
1003+
#[cfg(kani)]
1004+
#[unstable(feature = "kani", issue = "none")]
1005+
mod verify {
1006+
use super::*;
1007+
1008+
// The maximum length of the slice that `insertion_sort_shift_left`
1009+
// is called upon.
1010+
// The value is from the following line;
1011+
// https://github.com/model-checking/verify-rust-std/blob/1a38674ad6753e3a78e0181d1fe613f3b25ebacd/library/core/src/slice/sort/shared/smallsort.rs#L330
1012+
const INSERTION_SORT_MAX_LEN: usize = 17;
1013+
1014+
#[kani::proof]
1015+
pub fn check_swap_if_less() {
1016+
let mut array: [u8; SMALL_SORT_GENERAL_THRESHOLD] = kani::any();
1017+
let a_pos = kani::any_where(|x: &usize| *x < SMALL_SORT_GENERAL_THRESHOLD);
1018+
let b_pos = kani::any_where(|x: &usize| *x < SMALL_SORT_GENERAL_THRESHOLD);
1019+
let mut is_less = |x: &u8, y: &u8| x < y;
1020+
let expected = {
1021+
let mut array = array.clone();
1022+
let a: u8 = array[a_pos];
1023+
let b: u8 = array[b_pos];
1024+
if is_less(&b, &a) {
1025+
array[a_pos] = b;
1026+
array[b_pos] = a;
1027+
}
1028+
array
1029+
};
1030+
unsafe {
1031+
swap_if_less(array.as_mut_ptr(), a_pos, b_pos, &mut is_less);
1032+
}
1033+
kani::assert(
1034+
array == expected,
1035+
"swapped slice is different from the expectation"
1036+
);
1037+
}
1038+
1039+
#[kani::proof_for_contract(insert_tail)]
1040+
#[kani::unwind(17)]
1041+
pub fn check_insert_tail() {
1042+
let mut array: [u8; INSERTION_SORT_MAX_LEN] = kani::any();
1043+
let tail = kani::any_where(|x: &usize| *x < INSERTION_SORT_MAX_LEN);
1044+
let mut is_less = |x: &u8, y: &u8| x < y;
1045+
unsafe {
1046+
let begin = array.as_mut_ptr();
1047+
let tail = begin.add(tail);
1048+
insert_tail(begin, tail, &mut is_less);
1049+
}
1050+
}
1051+
1052+
// FIXME: Ideally, this should be `proof_for_contract(insertion_sort_shift_left)`,
1053+
// but this failes with OOM due to `proof_for_contract`'s perf issue.
1054+
//
1055+
// See https://github.com/model-checking/kani/issues/3797
1056+
#[kani::proof]
1057+
#[kani::stub_verified(insert_tail)]
1058+
#[kani::unwind(17)]
1059+
pub fn check_insertion_sort_shift_left() {
1060+
let slice_len = kani::any_where(|x: &usize| {
1061+
*x != 0 && *x <= INSERTION_SORT_MAX_LEN
1062+
});
1063+
let offset = kani::any_where(|x: &usize| *x != 0 && *x <= slice_len);
1064+
let mut is_less = |x: &u8, y: &u8| x < y;
1065+
let mut array = kani::any_where(|x: &[u8; INSERTION_SORT_MAX_LEN]| {
1066+
x[..offset].is_sorted_by(|a, b| !is_less(b, a))
1067+
});
1068+
insertion_sort_shift_left(&mut array[..slice_len], offset, &mut is_less);
1069+
kani::assert(
1070+
array[..slice_len].is_sorted_by(|a, b| !is_less(b, a)),
1071+
"slice is not sorted",
1072+
);
1073+
}
1074+
1075+
#[kani::proof_for_contract(sort4_stable)]
1076+
pub fn check_sort4_stable() {
1077+
let src: [u8; 4] = kani::any();
1078+
let mut dst = MaybeUninit::<[u8; 4]>::uninit();
1079+
let mut is_less = |x: &u8, y: &u8| x < y;
1080+
unsafe {
1081+
sort4_stable(src.as_ptr(), dst.as_mut_ptr() as *mut _, &mut is_less);
1082+
}
1083+
}
1084+
1085+
#[kani::proof]
1086+
pub fn check_sort4_stable_stability() {
1087+
let src: [(u8, u8); 4] = [
1088+
(kani::any(), 0),
1089+
(kani::any(), 1),
1090+
(kani::any(), 2),
1091+
(kani::any(), 3),
1092+
];
1093+
let mut dst = MaybeUninit::<[(u8, u8); 4]>::uninit();
1094+
let mut is_less = |x: &(u8, u8), y: &(u8, u8)| x.0 < y.0;
1095+
unsafe {
1096+
sort4_stable(src.as_ptr(), dst.as_mut_ptr() as *mut _, &mut is_less);
1097+
}
1098+
let dst = unsafe { dst.assume_init() };
1099+
let mut is_stably_less = |x: &(u8, u8), y: &(u8, u8)| {
1100+
if x.0 == y.0 {
1101+
x.1 < y.1
1102+
} else {
1103+
x.0 < y.0
1104+
}
1105+
};
1106+
kani::assert(
1107+
dst.is_sorted_by(|a, b| !is_stably_less(b, a)),
1108+
"slice is not stably sorted",
1109+
);
1110+
}
1111+
1112+
#[kani::proof]
1113+
pub fn check_has_efficient_in_place_swap() {
1114+
// There aren't much to verify as the function just calls `mem::size_of`.
1115+
// So, just brought some tests written by the author of smallsort,
1116+
// from Voultapher/sort-research-rs
1117+
//
1118+
// https://github.com/Voultapher/sort-research-rs/blob/c0cb46214a8d6e10ae5f4e0c363c2dbcbf71966f/ipnsort/src/smallsort.rs#L758-L771
1119+
assert!(has_efficient_in_place_swap::<i32>());
1120+
assert!(has_efficient_in_place_swap::<u64>());
1121+
assert!(!has_efficient_in_place_swap::<u128>());
1122+
}
1123+
1124+
#[kani::proof_for_contract(_stable_small_sort_type_impl_small_sort)]
1125+
#[kani::stub_verified(insertion_sort_shift_left)]
1126+
pub fn check_stable_small_sort_type_impl_small_sort() {
1127+
let mut array: [u8; SMALL_SORT_FALLBACK_THRESHOLD] = kani::any();
1128+
let len = kani::any_where(|x: &usize| *x <= SMALL_SORT_FALLBACK_THRESHOLD);
1129+
let mut scratch: [MaybeUninit<u8>; SMALL_SORT_GENERAL_SCRATCH_LEN]
1130+
= [const { MaybeUninit::uninit() }; SMALL_SORT_GENERAL_SCRATCH_LEN];
1131+
let mut is_less = |x: &u8, y: &u8| x < y;
1132+
_stable_small_sort_type_impl_small_sort(&mut array[..len], &mut scratch, &mut is_less);
1133+
}
1134+
}

0 commit comments

Comments
 (0)