Skip to main content

vyre_driver/
ordering.rs

1//! Backend-neutral monotonic ordering helpers for staging hot paths.
2
3/// Return whether an iterator's keys are already nondecreasing.
4pub fn iter_is_monotonic_by_key<I, K, F>(items: I, mut key: F) -> bool
5where
6    I: IntoIterator,
7    K: Ord,
8    F: FnMut(I::Item) -> K,
9{
10    let mut previous = None;
11    for item in items {
12        let current = key(item);
13        if let Some(previous) = previous {
14            if current < previous {
15                return false;
16            }
17        }
18        previous = Some(current);
19    }
20    true
21}
22
23/// Sort only when `items` are not already nondecreasing by `key`.
24pub fn sort_by_key_if_needed<T, K, F>(items: &mut [T], mut key: F)
25where
26    K: Ord,
27    F: FnMut(&T) -> K,
28{
29    let mut previous = None;
30    for index in 0..items.len() {
31        let current = key(&items[index]);
32        if let Some(previous) = previous {
33            if current < previous {
34                items.sort_by_key(key);
35                return;
36            }
37        }
38        previous = Some(current);
39    }
40}
41
42/// Unstable-sort only when `items` are not already nondecreasing by `key`.
43pub fn sort_unstable_by_key_if_needed<T, K, F>(items: &mut [T], mut key: F)
44where
45    K: Ord,
46    F: FnMut(&T) -> K,
47{
48    let mut previous = None;
49    for index in 0..items.len() {
50        let current = key(&items[index]);
51        if let Some(previous) = previous {
52            if current < previous {
53                items.sort_unstable_by_key(key);
54                return;
55            }
56        }
57        previous = Some(current);
58    }
59}
60
61/// Unstable-sort only when `items` are not already nondecreasing.
62pub fn sort_unstable_if_needed<T>(items: &mut [T])
63where
64    T: Ord,
65{
66    for index in 1..items.len() {
67        if items[index] < items[index - 1] {
68            items.sort_unstable();
69            return;
70        }
71    }
72}
73
74/// The first way a sorted index slice fails to be a dense permutation of
75/// `0..expected_len`. Distinguishing these lets callers emit a remediation that
76/// names the actual defect (a duplicate aliases two descriptors onto one logical
77/// slot; a sparse map skips one; a length mismatch has the wrong cardinality)
78/// instead of a generic "not dense".
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum DensePermutationDefect {
81    /// After sorting, `index` sits at `slot` with `index < slot`: a value
82    /// repeated earlier, so two descriptors alias one logical slot.
83    Duplicate {
84        /// The repeated value found below its sorted slot position.
85        index: usize,
86        /// The sorted slot position at which the duplicate surfaced.
87        slot: usize,
88    },
89    /// After sorting, `index` sits at `slot` with `index > slot`: a gap, so a
90    /// logical slot in `0..expected_len` is never mapped.
91    Sparse {
92        /// The value found above its sorted slot position.
93        index: usize,
94        /// The sorted slot position whose dense value (`slot`) is missing.
95        slot: usize,
96    },
97    /// Every present index was dense but the cardinality is wrong (the map is
98    /// truncated or over-long relative to `expected_len`).
99    LengthMismatch {
100        /// The number of indices actually present.
101        resolved: usize,
102        /// The dense cardinality the map was required to cover.
103        expected: usize,
104    },
105}
106
107/// Classify whether `sorted_indices` is a dense permutation of `0..expected_len`
108/// — each value in `0..expected_len` present exactly once.
109///
110/// Callers MUST pass indices already sorted ascending (e.g. via
111/// [`sort_unstable_if_needed`]); the classification is defined on sorted slot
112/// position. This is the single source of the dense-index-map invariant shared
113/// by every resident/graph descriptor→logical-slot map; format the returned
114/// [`DensePermutationDefect`] into a context-specific message at the call site.
115pub fn classify_dense_permutation(
116    sorted_indices: &[usize],
117    expected_len: usize,
118) -> Result<(), DensePermutationDefect> {
119    for (slot, &index) in sorted_indices.iter().enumerate() {
120        if index != slot {
121            return Err(if index < slot {
122                DensePermutationDefect::Duplicate { index, slot }
123            } else {
124                DensePermutationDefect::Sparse { index, slot }
125            });
126        }
127    }
128    if sorted_indices.len() != expected_len {
129        return Err(DensePermutationDefect::LengthMismatch {
130            resolved: sorted_indices.len(),
131            expected: expected_len,
132        });
133    }
134    Ok(())
135}
136
137#[cfg(test)]
138mod tests {
139    use std::cell::Cell;
140
141    use super::{
142        classify_dense_permutation, iter_is_monotonic_by_key, sort_by_key_if_needed,
143        sort_unstable_by_key_if_needed, sort_unstable_if_needed, DensePermutationDefect,
144    };
145
146    #[test]
147    fn iter_monotonic_by_key_detects_ordered_and_unordered_streams() {
148        assert!(iter_is_monotonic_by_key([0, 1, 1, 3], |value| value));
149        assert!(!iter_is_monotonic_by_key([0, 2, 1, 3], |value| value));
150    }
151
152    #[test]
153    fn stable_sort_by_key_skips_already_monotonic_slices() {
154        let calls = Cell::new(0usize);
155        let mut items = [(0usize, "a"), (1, "b"), (1, "c"), (3, "d")];
156
157        sort_by_key_if_needed(&mut items, |(key, _)| {
158            calls.set(calls.get() + 1);
159            *key
160        });
161
162        assert_eq!(items, [(0, "a"), (1, "b"), (1, "c"), (3, "d")]);
163        assert_eq!(
164            calls.get(),
165            items.len(),
166            "Fix: monotonic ordering paths must not invoke the fallback sort."
167        );
168    }
169
170    #[test]
171    fn stable_sort_by_key_sorts_unordered_slices() {
172        let mut items = [(2usize, "c"), (0, "a"), (3, "d"), (1, "b")];
173
174        sort_by_key_if_needed(&mut items, |(key, _)| *key);
175
176        assert_eq!(items, [(0, "a"), (1, "b"), (2, "c"), (3, "d")]);
177    }
178
179    #[test]
180    fn unstable_sort_by_key_skips_already_monotonic_slices() {
181        let calls = Cell::new(0usize);
182        let mut items = [(0usize, "a"), (1, "b"), (3, "c")];
183
184        sort_unstable_by_key_if_needed(&mut items, |(key, _)| {
185            calls.set(calls.get() + 1);
186            *key
187        });
188
189        assert_eq!(items, [(0, "a"), (1, "b"), (3, "c")]);
190        assert_eq!(
191            calls.get(),
192            items.len(),
193            "Fix: monotonic unstable-ordering paths must not invoke the fallback sort."
194        );
195    }
196
197    #[test]
198    fn unstable_sort_by_key_sorts_unordered_slices() {
199        let mut items = [(2usize, "c"), (0, "a"), (1, "b")];
200
201        sort_unstable_by_key_if_needed(&mut items, |(key, _)| *key);
202
203        assert_eq!(items, [(0, "a"), (1, "b"), (2, "c")]);
204    }
205
206    #[test]
207    fn unstable_sort_skips_already_monotonic_slices() {
208        let mut items = [0usize, 1, 1, 3];
209
210        sort_unstable_if_needed(&mut items);
211
212        assert_eq!(items, [0, 1, 1, 3]);
213    }
214
215    #[test]
216    fn unstable_sort_sorts_unordered_slices() {
217        let mut items = [2usize, 0, 1];
218
219        sort_unstable_if_needed(&mut items);
220
221        assert_eq!(items, [0, 1, 2]);
222    }
223
224    #[test]
225    fn classify_dense_permutation_distinguishes_dense_duplicate_sparse_and_length() {
226        assert_eq!(classify_dense_permutation(&[0, 1, 2], 3), Ok(()));
227        assert_eq!(classify_dense_permutation(&[], 0), Ok(()));
228        assert_eq!(
229            classify_dense_permutation(&[0, 0, 2], 3),
230            Err(DensePermutationDefect::Duplicate { index: 0, slot: 1 }),
231            "Fix: a repeated value at a later sorted slot is a duplicate, not a generic non-dense map."
232        );
233        assert_eq!(
234            classify_dense_permutation(&[0, 2, 3], 3),
235            Err(DensePermutationDefect::Sparse { index: 2, slot: 1 }),
236            "Fix: a value above its sorted slot is a sparse gap, not a duplicate."
237        );
238        assert_eq!(
239            classify_dense_permutation(&[0, 1], 3),
240            Err(DensePermutationDefect::LengthMismatch {
241                resolved: 2,
242                expected: 3
243            }),
244            "Fix: a dense-but-short map is a length mismatch."
245        );
246        assert_eq!(
247            classify_dense_permutation(&[0, 1, 2, 3], 3),
248            Err(DensePermutationDefect::LengthMismatch {
249                resolved: 4,
250                expected: 3
251            }),
252            "Fix: a dense-but-long map is a length mismatch."
253        );
254    }
255
256    #[test]
257    fn classify_dense_permutation_matches_sorted_reference_over_generated_maps() {
258        // For every permutation-with-defect we can synthesize, the classifier's
259        // verdict must agree with an independent set-based reference oracle.
260        for len in 0usize..=24 {
261            let dense: Vec<usize> = (0..len).collect();
262            assert_eq!(classify_dense_permutation(&dense, len), Ok(()));
263
264            for collide in 0..len {
265                // Replace one slot's value with a duplicate of slot 0's value (0),
266                // then re-sort: this guarantees a duplicate, never a sparse gap.
267                let mut indices = dense.clone();
268                indices[collide] = 0;
269                sort_unstable_if_needed(&mut indices);
270                let verdict = classify_dense_permutation(&indices, len);
271                let distinct: std::collections::BTreeSet<usize> =
272                    indices.iter().copied().collect();
273                let reference_is_dense =
274                    distinct.len() == len && indices.len() == len && *distinct.iter().max().unwrap_or(&0) < len.max(1);
275                if collide == 0 {
276                    // collide==0 leaves the map unchanged: still dense.
277                    assert_eq!(verdict, Ok(()));
278                } else {
279                    assert!(verdict.is_err(), "len={len} collide={collide} must be a defect");
280                    assert!(!reference_is_dense || verdict.is_ok());
281                    assert!(matches!(
282                        verdict,
283                        Err(DensePermutationDefect::Duplicate { .. })
284                            | Err(DensePermutationDefect::Sparse { .. })
285                            | Err(DensePermutationDefect::LengthMismatch { .. })
286                    ));
287                }
288            }
289        }
290    }
291
292    #[test]
293    fn generated_ordering_matrix_matches_full_sort_contract() {
294        for len in 0..=128 {
295            let ordered: Vec<usize> = (0..len).collect();
296            let mut reversed: Vec<usize> = (0..len).rev().collect();
297            let mut expected = reversed.clone();
298            expected.sort_unstable();
299
300            assert!(iter_is_monotonic_by_key(ordered.iter().copied(), |value| {
301                value
302            }));
303            if len > 1 {
304                assert!(!iter_is_monotonic_by_key(
305                    reversed.iter().copied(),
306                    |value| value
307                ));
308            }
309
310            sort_unstable_if_needed(&mut reversed);
311            assert_eq!(reversed, expected);
312
313            let mut keyed: Vec<(usize, usize)> = (0..len).rev().map(|value| (value, len)).collect();
314            sort_unstable_by_key_if_needed(&mut keyed, |(key, _)| *key);
315            for (expected_key, actual) in keyed.iter().enumerate() {
316                assert_eq!(actual.0, expected_key);
317                assert_eq!(actual.1, len);
318            }
319        }
320    }
321}