33use crate :: mem:: { self , ManuallyDrop , MaybeUninit } ;
44use crate :: slice:: sort:: shared:: FreezeMarker ;
55use 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.
6791pub ( 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>.
107150pub ( 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.
174237const 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+ } ) ]
542626unsafe 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+ } ) ]
580679pub 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+ } ) ]
612741pub 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