Skip to main content

rustymath_core/reduce/
scans.rs

1use super::*;
2
3/// Appends the running folds by `R` along the middle axis of `data`, an array of shape `[outer, len, inner]` held row by row, to `out`, in the same layout. Where `seed` is given, each lane's first fold combines `seed`'s item for its column, or its only item. Gives whether every fold fits. Lanes of consecutive items scan by [`Reduction::short_lanes`] where the reduction takes them, else by `scan`. Along any other axis, each row's folds step the row before's by `checked`.
4pub fn scan_along<T: Copy, R: Reduction<T>>(data: &[T], dims: [usize; 3], seed: Option<&[R::Total]>, out: &mut Vec<R::Total>) -> bool {
5    let start = |i| seed.map(|s| s[if s.len() == 1 { 0 } else { i }]);
6    if let [_, len, 1] = dims && let Some(fits) = R::short_lanes(data, len, start(0), out) { return fits; }
7    rows_scan(data, dims, out, |lane, o| R::scan(lane, start(0), o), |i, x| start(i).map_or_else(|| R::one(x), |s| R::checked(s, x)), R::checked)
8}
9
10/// Appends the ordered left scan of each lane of `[outer, len, inner]` in `data` to `out`, in the same layout: along the last axis by `lane`, one call for every lane, stepping from `seed`'s item; along any other axis by `block`, one call for each row, stepping the row before it, or `seed`'s row, by the row's items. Without a seed, a lane's first item or row is kept as it is. Gives the kind of the first step that failed.
11///
12/// # Safety
13/// `block` must initialize every output slot before returning, including when it returns `Err`.
14pub unsafe fn ordered_scan<T: Copy>(
15    data: &[T], [_, len, inner]: [usize; 3], seed: Option<&[T]>, out: &mut Vec<T>, lane: &dyn Fn(Option<T>, &[T], usize, &mut Vec<T>) -> Option<Kind>,
16    block: &dyn Fn(&[T], &[T], &mut [std::mem::MaybeUninit<T>]) -> Result<(), Kind>) -> Option<Kind> {
17    if inner == 1 { return lane(seed.map(|s| s[0]), data, len, out); }
18    unsafe {
19        appended(out, data.len(), |slots| {
20            let mut failure = None;
21            for (items, outs) in data.chunks(len * inner).zip(slots.chunks_mut(len * inner)) {
22                match seed {
23                    Some(s) => failure = failure.or(block(s, &items[..inner], &mut outs[..inner]).err()),
24                    None => { for (slot, &x) in outs.iter_mut().zip(&items[..inner]) { slot.write(x); } }
25                }
26                for r in 1..len {
27                    let (done, next) = outs.split_at_mut(r * inner);
28                    failure = failure.or(block(written(&mut done[(r - 1) * inner..]), &items[r * inner..(r + 1) * inner], &mut next[..inner]).err());
29                }
30            }
31            failure
32        })
33    }
34}
35
36/// Appends the running results along each lane of `len` consecutive items in `data` to `out`, and gives whether every one fits: `first` gives each lane's first result, and `step` each later one from the one before. Lanes shorter than [`SHORT_LANE`] run [`LANES`] side by side (`rows_scan`).
37pub fn lanes_scan<T: Copy>(data: &[T], len: usize, out: &mut Vec<T>, first: impl Fn(T) -> (T, bool) + Copy, step: impl Fn(T, T) -> (T, bool) + Copy) -> bool {
38    rows_scan(data, [0, len, 1], out, |lane, out| {
39        let (total, ok) = first(lane[0]);
40        out.push(total);
41        checked_scan(&lane[1..], total, out, step) & ok
42    }, move |_, x| first(x), step)
43}
44
45/// Appends the running results along the middle axis of `data`, an array of shape `[outer, len, inner]` held row by row, to `out`, in the same layout, and gives whether every one fits. A lane of at least [`SHORT_LANE`] consecutive items runs through `lane`. Shorter lanes run [`LANES`] side by side, and lanes along any other axis run a row at a time: `first` gives each lane's first item or row, and `step` steps each from the one before. `first` and `step` go to `side_by_side` by value, because a build of many codegen units can leave core's `<&F as Fn>::call` for a reference out of line, at a call for each item.
46#[inline(always)]
47fn rows_scan<T: Copy, S: Copy>(
48    data: &[T], [_, len, inner]: [usize; 3], out: &mut Vec<S>, lane: impl Fn(&[T], &mut Vec<S>) -> bool, first: impl Fn(usize, T) -> (S, bool) + Copy,
49    step: impl Fn(S, T) -> (S, bool) + Copy) -> bool {
50    if data.is_empty() { return true; }
51    if inner == 1 && len >= SHORT_LANE { return data.chunks(len).fold(true, |fits, l| lane(l, out) & fits); }
52    if inner == 1 {
53        return unsafe {
54            appended(out, data.len(), |slots| {
55                let whole = data.len() / (LANES * len) * (LANES * len);
56                let (groups, rest) = slots.split_at_mut(whole);
57                // SAFETY: `side_by_side` gives each position of the data it runs on, which holds as many items as the slots written here.
58                side_by_side::<LANES, _, _>(&data[..whole], len, |x| first(0, x), step, |at, s| {
59                    groups.get_unchecked_mut(at).write(s);
60                }, |_, _| {}) & side_by_side::<1, _, _>(&data[whole..], len, |x| first(0, x), step, |at, s| {
61                    rest.get_unchecked_mut(at).write(s);
62                }, |_, _| {})
63            })
64        };
65    }
66    // Each row steps from the row before it, which `out` already holds.
67    unsafe {
68        appended(out, data.len(), |slots| {
69            let mut fits = true;
70            let mut at = 0;
71            while at < data.len() {
72                let (row, outs) = (&data[at..at + inner], &mut slots[at..at + inner]);
73                for k in 0..inner {
74                    let (t, f) = first(k, row[k]);
75                    outs[k].write(t);
76                    fits &= f;
77                }
78                let mut r = at + inner;
79                while r < at + len * inner {
80                    let (done, rest) = slots.split_at_mut(r);
81                    let (before, row, outs) = (written(&mut done[r - inner..]), &data[r..r + inner], &mut rest[..inner]);
82                    for k in 0..inner {
83                        let (t, f) = step(before[k], row[k]);
84                        outs[k].write(t);
85                        fits &= f;
86                    }
87                    r += inner;
88                }
89                at += len * inner;
90            }
91            fits
92        })
93    }
94}
95
96/// A function of two Booleans, held as its truth table: `Logical(t)` gives `t[r][x]` for the running result `r` and the next item `x`. Its scans run any such function.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct Logical(pub [[bool; 2]; 2]);
99
100impl Logical {
101    /// `r f x`, with no branch.
102    #[inline(always)]
103    fn apply(self, r: bool, x: bool) -> bool { let [[f00, f01], [f10, f11]] = self.0; (r & ((x & f11) | (!x & f10))) | (!r & ((x & f01) | (!x & f00))) }
104
105    /// Appends the running results of `x` to `out`: the first item, or `start` combined with it where `start` is given, then each result combined with the next item.
106    ///
107    /// For each value of the next item, the function maps the running result to itself, to its negation, or to a constant. Where one value maps it to itself and the other to a constant, as for `and` and `or`, the results change at most once. `first_where` finds that place, and two runs fill the rest. Otherwise each map is `r ↦ (s ∧ r) ≠ t` for two bits `s` and `t`, and the maps compose as `r ↦ (s₂ ∧ s₁ ∧ r) ≠ ((s₂ ∧ t₁) ≠ t₂)`. Each block of 64 items packs into a word of bits, whose maps compose in six steps of shifts, with no branch and no step for each item.
108    pub fn scan(self, x: &[bool], start: Option<bool>, out: &mut Vec<bool>) {
109        let Some((&head, rest)) = x.split_first() else { return };
110        let first = self.first(start, head);
111        out.push(first);
112        // `maps[v]` gives the results of an item `v` for a running result of false and of true.
113        let [[f00, f01], [f10, f11]] = self.0;
114        let maps = [[f00, f10], [f01, f11]];
115        for v in [false, true] {
116            let [lo, hi] = maps[usize::from(!v)];
117            if maps[usize::from(v)] == [false, true] && lo == hi {
118                let at = first_where(rest, |x| x != v);
119                out.extend(std::iter::repeat_n(first, at));
120                out.extend(std::iter::repeat_n(lo, rest.len() - at));
121                return;
122            }
123        }
124        let bit_maps = self.bit_maps();
125        mapped(rest.len(), first, |start| bit_maps(packed(rest, start)), out);
126    }
127
128    /// A lane's first result: its first item `x`, combined with `start` where it's given.
129    #[inline(always)]
130    fn first(self, start: Option<bool>, x: bool) -> bool { start.map_or(x, |s| self.apply(s, x)) }
131
132    /// The maps of 64 items given as the bits of a word, as [`mapped`] takes them: item `k` maps `r` to `(s ∧ r) ≠ t` for bit `k` of each word of `(s, t)`.
133    #[inline(always)]
134    fn bit_maps(self) -> impl Fn(u64) -> (u64, u64) + Copy {
135        let [[f00, f01], [f10, f11]] = self.0;
136        let word = |b: bool| if b { u64::MAX } else { 0 };
137        // An item `v` maps a result of false to `f0v` and a result of true to `f1v`.
138        let (s0, t0, s1, t1) = (word(f00 != f10), word(f00), word(f01 != f11), word(f01));
139        move |bits| ((bits & s1) | (!bits & s0), (bits & t1) | (!bits & t0))
140    }
141
142    /// Appends the running results of `data`'s lanes of `len` consecutive items, 1 to 64, each lane from `start`, to `out` in one [`mapped`] call. The Boolean reductions' [`Reduction::short_lanes`] runs through it. Each word restarts the lanes that start in it with constant maps, at places that shift by `64 % len` from one word to the next. A lane's first result depends only on its first item, so it comes from the same bits.
143    pub(super) fn short_lanes(self, data: &[bool], len: usize, start: Option<bool>, out: &mut Vec<bool>) {
144        let (bit_maps, word) = (self.bit_maps(), |b: bool| if b { u64::MAX } else { 0 });
145        let (yes, no) = (word(self.first(start, true)), word(self.first(start, false)));
146        let starts = (0..64).step_by(len).fold(0u64, |p, k| p | 1 << k);
147        let (shift, at) = (64 % len, std::cell::Cell::new(0));
148        mapped(data.len(), false, |from| {
149            let (bits, k) = (packed(data, from), at.get());
150            at.set(if k >= shift { k - shift } else { k + len - shift });
151            let (places, (s, t)) = (starts << k, bit_maps(bits));
152            (s & !places, (t & !places) | ((bits & yes) | (!bits & no)) & places)
153        }, out);
154    }
155}
156
157/// The 64 Booleans of `block` as the bits of a word, the first in the lowest bit. Each 8 Booleans, one byte each, gather into a byte through one multiplication.
158#[inline(always)]
159fn pack(block: &[bool; 64]) -> u64 {
160    let mut bits = 0;
161    for (k, b) in block.as_chunks::<8>().0.iter().enumerate() {
162        let bytes = (0..8).fold(0u64, |w, i| w | u64::from(b[i]) << (8 * i));
163        bits |= (bytes.wrapping_mul(0x0102_0408_1020_4080) >> 56) << (8 * k);
164    }
165    bits
166}
167
168/// Expands the 64 bits of `r` into Boolean bytes, the lowest bit first, then converts that block into `out`. Each byte of `r` becomes 8 bytes of 0 or 1, bit `j` in byte `j`, through one multiplication, two masks, an add and a shift that's the same for every byte. LLVM compiles a test of bit `j` in byte `j` to a different shift for each byte, which wasm's SIMD can only emulate.
169#[inline(always)]
170fn unpack<S>(r: u64, out: &mut [std::mem::MaybeUninit<S>; 64]) where bool: Source<S> {
171    let mut block = [false; 64];
172    for (k, lanes) in block.as_chunks_mut::<8>().0.iter_mut().enumerate() {
173        let bits = (r >> (8 * k) & 0xff).wrapping_mul(0x0101_0101_0101_0101) & 0x8040_2010_0804_0201;
174        let bytes = ((bits + 0x7f7f_7f7f_7f7f_7f7f) >> 7 & 0x0101_0101_0101_0101).to_le_bytes();
175        *lanes = std::array::from_fn(|j| bytes[j] != 0);
176    }
177    crate::slice::pairs_into(Arg::Many(&block), Arg::One(()), 64, out, |b, ()| (b.read(), true));
178}
179
180/// The results of 64 items, bit by bit, from the running result `carry`, where item `k`'s map is `r ↦ (s ∧ r) ≠ t` for bit `k` of `s` and `t`. Each step composes each bit's map with the map it holds from the bits before it. A bit with none before it takes the identity map, whose `s` is 1 and whose `t` is 0.
181#[inline(always)]
182fn composed(mut s: u64, mut t: u64, carry: bool) -> u64 {
183    for k in [1, 2, 4, 8, 16, 32] {
184        t ^= s & (t << k);
185        s &= (s << k) | ((1u64 << k) - 1);
186    }
187    (s & if carry { u64::MAX } else { 0 }) ^ t
188}
189
190/// The Booleans of `x` from `start`, at most 64, as the bits of a word, the first in the lowest bit. Bits past the end of `x` are 0.
191#[inline(always)]
192fn packed(x: &[bool], start: usize) -> u64 {
193    match x[start..].first_chunk::<64>() {
194        Some(block) => pack(block),
195        None => {
196            let mut block = [false; 64];
197            block[..x.len() - start].copy_from_slice(&x[start..]);
198            pack(&block)
199        }
200    }
201}
202
203/// Appends the running results of `n` items to `out`, from the running result `carry` before the first. `maps(start)` gives the maps of the 64 items from `start` as two words of bits `(s, t)`: item `start + k` maps `r` to `(s ∧ r) ≠ t` for bit `k` of each. Bits past the last item are ignored.
204#[inline(always)]
205fn mapped<S: Copy>(n: usize, mut carry: bool, maps: impl Fn(usize) -> (u64, u64), out: &mut Vec<S>) where bool: Source<S> {
206    unsafe {
207        appended(out, n, |slots| {
208            let (outs, last) = slots.as_chunks_mut::<64>();
209            for (i, o) in outs.iter_mut().enumerate() {
210                let (s, t) = maps(64 * i);
211                let r = composed(s, t, carry);
212                unpack(r, o);
213                carry = r >> 63 != 0;
214            }
215            if !last.is_empty() {
216                let (s, t) = maps(n - last.len());
217                let r = composed(s, t, carry);
218                for (k, slot) in last.iter_mut().enumerate() { slot.write(Source::<S>::read(r >> k & 1 != 0)); }
219            }
220        })
221    };
222}
223
224/// Appends a Boolean-valued scan of long consecutive lanes, keeping an unseeded lane's first item as it is. Comparisons at carries 0 and 1 make independent two-state maps, whose packed words compose through `mapped`. A failed speculative comparison leaves `out` unchanged and declines, so the ordered runner checks only steps the scan actually takes.
225pub fn comparison_scan<T: Element + PartialEq, S: Element>(
226    data: &[T], len: usize, start: Option<T>, step: impl Fn(T, T) -> (T, Option<Kind>) + Copy, read: impl Fn(T) -> S + Copy,
227    bulk: impl Fn(T, &[T], &mut Vec<bool>) -> Option<bool> + Copy, out: &mut Vec<S>) -> bool where bool: Source<T> + Source<S> {
228    if len < SHORT_LANE { return false; }
229    let (zero, one, before) = (false.read(), true.read(), out.len());
230    let (mut low, mut high) = (Vec::new(), Vec::new());
231    for lane in data.chunks(len) {
232        let (first, used) = match start { Some(seed) => (step(seed, lane[0]), 1), None => { out.push(read(lane[0])); (step(lane[0], lane[1]), 2) } };
233        let (first, failed) = first;
234        if failed.is_some() { out.truncate(before); return false; }
235        out.push(read(first));
236        let rest = &lane[used..];
237        low.clear();
238        high.clear();
239        let transitions = |carry, dest: &mut Vec<bool>| {
240            if let Some(valid) = bulk(carry, rest, dest) { return valid; }
241            unsafe {
242                appended(dest, rest.len(), |slots| {
243                    let mut valid = true;
244                    for (&x, slot) in rest.iter().zip(slots) {
245                        let (value, failed) = step(carry, x);
246                        slot.write(value != zero);
247                        valid &= failed.is_none();
248                    }
249                    valid
250                })
251            }
252        };
253        if !(transitions(zero, &mut low) & transitions(one, &mut high)) { out.truncate(before); return false; }
254        mapped(rest.len(), first != zero, |at| { let (l, h) = (packed(&low, at), packed(&high, at)); (l ^ h, l) }, out);
255    }
256    true
257}
258
259/// Appends each cell's left difference scan without materializing its negated items. The first item is unchanged without a seed; every later item is subtracted from the running total.
260pub(super) fn negated_cells<T: Element, R: Reduction<T, Total = T>>(
261    items: &[T], dims: [usize; 3], seed: Option<&[T]>, out: &mut Vec<T>, neg: impl Fn(T) -> T + Copy) {
262    let start = |i| seed.map(|s| s[if s.len() == 1 { 0 } else { i }]);
263    let lane = |lane: &[T], out: &mut Vec<T>| {
264        let (total, rest) = match start(0) { Some(s) => (s, lane), None => { out.push(lane[0]); (lane[0], &lane[1..]) } };
265        grouped(rest, Some(total), out, neg, R::step, |x| x, T::DTYPE.is_integral());
266        true
267    };
268    rows_scan(items, dims, out, lane, |i, x| (start(i).map_or(x, |s| R::step(s, neg(x))), true), |t, x| (R::step(t, neg(x)), true));
269}