Skip to main content

rustymath_core/
slice.rs

1//! Slice forms of rustymath's functions. Each function object, such as [`Pow`], has a scalar form `one` and a slice form `many`. Both run code that rustymath compiles once for each item type, inside its own crate. A caller's build settings can't change that code, and rustymath's speed test times what callers run. An edit to a function's body rebuilds only rustymath.
2use crate::float::Float;
3use num_complex::Complex;
4use std::mem::MaybeUninit;
5
6/// How many items a caller handles at a time where it converts or expands arguments into buffers for a slice form. Buffers of this many items stay in cache.
7pub const CHUNK: usize = 4096;
8
9/// How many independent running values a loop keeps, each of `size` bytes: a fold's partial results, or a block of items under Horner's rule. 32 measured faster on the Mac than 8 or 16 for items of up to 8 bytes. A 16-byte item, such as a `Complex<f64>`, keeps 16, because 32 fill every vector register, and a complex sum then took 1.7 times as long. In wasm, LLVM leaves 32 values of 8 bytes, or 16 of 16 bytes, in linear memory, where each step loads and stores them. It keeps half as many in locals.
10pub const fn running(size: usize) -> usize {
11    let wasm = cfg!(target_arch = "wasm32");
12    if size >= 16 { if wasm { 8 } else { 16 } } else if wasm && size == 8 { 16 } else { 32 }
13}
14
15/// One argument of a slice function: one item that every result pairs with, or one item for each result.
16#[derive(Clone, Copy, Debug)]
17pub enum Arg<'a, T> { One(T), Many(&'a [T]) }
18
19impl<'a, T: Copy> Arg<'a, T> {
20    /// The argument that `items` gives: its only item for every result when it holds one, otherwise one item for each result.
21    pub fn of(items: &'a [T]) -> Self { if let [x] = *items { Arg::One(x) } else { Arg::Many(items) } }
22    /// Item `i` of this argument, or its one item.
23    #[inline(always)]
24    pub fn nth(self, i: usize) -> T { match self { Arg::One(v) => v, Arg::Many(s) => s[i] } }
25    /// The one item of an argument that holds one for every result.
26    #[inline(always)]
27    pub fn one(self) -> Option<T> { match self { Arg::One(v) => Some(v), Arg::Many(_) => None } }
28    /// Items `start..start + n` of this argument, each converted by `f`. A `Many` argument converts into `buf`.
29    #[inline]
30    pub fn converted<'b, A>(self, start: usize, n: usize, buf: &'b mut Vec<A>, f: impl Fn(T) -> A) -> Arg<'b, A> {
31        match self.part(start, n) {
32            Arg::One(v) => Arg::One(f(v)),
33            Arg::Many(items) => {
34                buf.clear();
35                buf.extend(items.iter().map(|&v| f(v)));
36                Arg::Many(buf)
37            }
38        }
39    }
40
41    /// Items `start..start + n` of this argument. A `One` argument stays as it is.
42    #[inline(always)]
43    pub fn part(self, start: usize, n: usize) -> Self { match self { Arg::One(v) => Arg::One(v), Arg::Many(s) => Arg::Many(&s[start..start + n]) } }
44}
45
46/// A type that function objects take or give: a float, a complex number, an integer, a Boolean, a character or an ordering. Pieces on several threads read and write items.
47pub trait KernelItem: Copy + Send + Sync {
48    /// Whether the type has NaN, as floats and complex numbers do.
49    const NAN: bool;
50    /// Whether this item is NaN, or for a complex number has a NaN part. An integer or Boolean never is.
51    fn has_nan(self) -> bool;
52}
53
54macro_rules! nan_items { ($($t:ty),+) => {$( impl KernelItem for $t { const NAN: bool = true; #[inline(always)] fn has_nan(self) -> bool { self.is_nan() } } )+} }
55nan_items!(f32, f64, half::f16, Complex<f32>, Complex<f64>);
56macro_rules! exact_items { ($($t:ty),+) => {$( impl KernelItem for $t { const NAN: bool = false; #[inline(always)] fn has_nan(self) -> bool { false } } )+} }
57exact_items!(u8, i16, i32, i64, bool, char, std::cmp::Ordering);
58
59/// The results of a function object of `N` items of type `T`: their type and whether any can fail. [`Unary`], [`Binary`] and [`Ternary`] each require it, so code that takes a function object of any arity reads these through one bound.
60pub trait Results<T, const N: usize> {
61    /// The type of each result.
62    type Out: KernelItem;
63    /// Whether no result fails: `checked` always gives `Some`, and `many` and `many_into` always give true. A total float function gives NaN from arguments that aren't NaN only where NaN is its value, as [`Subtract`](crate::Subtract) gives for ∞ - ∞ and [`Sin`](crate::Sin) for ±∞.
64    const TOTAL: bool;
65}
66
67/// Native scalar and slice interfaces. The slice body initializes every output slot even when an item fails.
68macro_rules! arities {
69    ($($(#[$doc:meta])* $name:ident<$n:literal>($($x:ident),+);)+) => {$(
70        $(#[$doc])*
71        ///
72        /// # Safety
73        /// The slice body initializes every slot in its output before returning.
74        pub unsafe trait $name<T>: Results<T, $n> + Sync {
75            fn one(&self, $($x: T),+) -> Self::Out;
76            fn judged(&self, $($x: T),+) -> (Self::Out, bool);
77            fn slice(&self, args: [Arg<'_, T>; $n], n: usize, out: &mut [MaybeUninit<Self::Out>]) -> bool;
78            #[inline]
79            fn checked(&self, $($x: T),+) -> Option<Self::Out> { let (r, ok) = self.judged($($x),+); ok.then_some(r) }
80            fn many(&self, $($x: Arg<'_, T>,)+ n: usize, out: &mut Vec<Self::Out>) -> bool where T: Copy + Sync {
81                unsafe { appended(out, n, |slots| crate::parallel::split(slots, CHUNK, &|start, slots| {
82                    crate::parallel::in_chunks(start, slots, |at, part| self.slice([$($x.part(at, part.len())),+], part.len(), part))
83                })) }
84            }
85            fn many_into(&self, $($x: Arg<'_, T>,)+ n: usize, out: &mut crate::parallel::Slots<'_, Self::Out>) -> bool where T: Copy {
86                // SAFETY: the unsafe object trait promises that `slice` initializes every output.
87                unsafe { out.fill(n, |slots| crate::parallel::in_chunks(0, slots, |at, part| self.slice([$($x.part(at, part.len())),+], part.len(), part))) }
88            }
89        }
90    )+};
91}
92
93arities! {
94    /// A function of one item as a value, with a scalar form and a slice form. rustymath's function objects, such as [`Exp`](crate::Exp), implement it at each item type they take: every [`Float`] type for a float function, and complex numbers, integers or Booleans for others. `Out` is the type of each result, which is the item type unless the function gives another, as [`Abs`](crate::Abs) gives the real magnitude of a complex number. Its methods take `&self`, so a function object can hold settings.
95    Unary<1>(x);
96    /// A function of two items as a value, as [`Unary`] is for one.
97    Binary<2>(x, y);
98    /// A function of three items as a value, as [`Unary`] is for one.
99    Ternary<3>(x, y, z);
100}
101
102/// `K` with its two arguments in the other order, as a [`Binary`] at each item type where `K` is one: `Flipped(k).one(x, y)` is `k.one(y, x)`, and likewise for `many` and `checked`.
103#[derive(Clone, Copy, Debug)]
104pub struct Flipped<K>(pub K);
105
106impl<T, K: Results<T, 2>> Results<T, 2> for Flipped<K> {
107    type Out = K::Out;
108    const TOTAL: bool = K::TOTAL;
109}
110unsafe impl<T, K: Binary<T>> Binary<T> for Flipped<K> {
111    fn one(&self, x: T, y: T) -> K::Out { self.0.one(y, x) }
112    fn judged(&self, x: T, y: T) -> (K::Out, bool) { self.0.judged(y, x) }
113    fn slice(&self, [x, y]: [Arg<'_, T>; 2], n: usize, out: &mut [MaybeUninit<K::Out>]) -> bool { self.0.slice([y, x], n, out) }
114}
115
116/// `K` with no result failing. Each result is `K`'s, wrapped to its type, as static kinds and NumPy keep integer results. LLVM removes `K`'s test of whether a result fits, because the loop never reads it. The adapter suits an object whose scalar form is its whole computation, as integer arithmetic's is.
117#[derive(Clone, Copy, Debug)]
118pub struct Wrapped<K>(pub K);
119
120impl<T, K: Results<T, N>, const N: usize> Results<T, N> for Wrapped<K> {
121    type Out = K::Out;
122    const TOTAL: bool = true;
123}
124macro_rules! wrapped {
125    ($arity:ident, $n:literal, $($x:ident),+) => {
126        unsafe impl<T: KernelItem, K: $arity<T>> $arity<T> for Wrapped<K> {
127            fn one(&self, $($x: T),+) -> K::Out { self.0.one($($x),+) }
128            fn judged(&self, $($x: T),+) -> (K::Out, bool) { (self.0.one($($x),+), true) }
129            fn slice(&self, x: [Arg<'_, T>; $n], n: usize, out: &mut [MaybeUninit<K::Out>]) -> bool {
130                each(x, n, out, #[inline(always)] |[$($x),+]| Total(self.0.one($($x),+)))
131            }
132        }
133    };
134}
135wrapped!(Unary, 1, x);
136wrapped!(Binary, 2, x, y);
137wrapped!(Ternary, 3, x, y, z);
138
139/// Whether `r`, the result for arguments `args`, keeps the rule that a NaN result needs a NaN argument.
140#[inline(always)]
141pub(crate) fn kept<O: KernelItem, E: KernelItem, const N: usize>(r: O, args: [E; N]) -> bool { !r.has_nan() | args.iter().any(|a| a.has_nan()) }
142
143/// What a function object's item body gives: an output, which fails where it is NaN and no argument is, an `Option` of one, which fails where it is `None`, a wrapped output paired with whether it's exact, which fails where it isn't, or a [`Total`] output, which never fails. An integer power, whose result can overflow, gives a wrapped output. An integer function with no wrapped result to give, such as a binomial coefficient, gives an `Option`. An output that is never NaN, such as `bool`, fails only through `None` or an inexact pair.
144pub(crate) trait Outcome<O: KernelItem>: Sized {
145    /// Whether an output that `split` fails can still succeed, as a NaN output does where an argument is NaN. Only a plain output can.
146    const RESCUE: bool;
147    /// Whether no output fails: a [`Total`] output, or a plain output of a type without NaN.
148    const TOTAL: bool;
149    /// The output, as a scalar form gives it. `None` panics, because a scalar form has no other way to report it.
150    fn value(self) -> O;
151    /// The output, and whether it succeeded, judged by the output alone, so that a NaN output fails. That costs one test for each item, where testing each argument too would cost one more for each argument.
152    fn split(self) -> (O, bool);
153    /// The output, and whether it succeeded for arguments `args`.
154    #[inline(always)]
155    fn judged<E: KernelItem, const N: usize>(self, args: [E; N]) -> (O, bool) { let (r, ok) = self.split(); (r, ok || (Self::RESCUE && kept(r, args))) }
156}
157
158impl<O: KernelItem> Outcome<O> for O {
159    const RESCUE: bool = true;
160    const TOTAL: bool = !O::NAN;
161    #[inline(always)]
162    fn value(self) -> O { self }
163    #[inline(always)]
164    fn split(self) -> (O, bool) { (self, !self.has_nan()) }
165}
166
167impl<O: KernelItem + Default> Outcome<O> for Option<O> {
168    const RESCUE: bool = false;
169    const TOTAL: bool = false;
170    #[inline(always)]
171    fn value(self) -> O { self.expect("the result doesn't fit the output type") }
172    #[inline(always)]
173    fn split(self) -> (O, bool) { (self.unwrap_or_default(), self.is_some()) }
174}
175
176/// An output wrapped to its type, and whether it's the exact result, as an integer power gives it. A result that isn't exact fails, and its slot holds the wrapped output. That output is NumPy's result for fixed-width integers.
177impl<O: KernelItem> Outcome<O> for (O, bool) {
178    const RESCUE: bool = false;
179    const TOTAL: bool = false;
180    #[inline(always)]
181    fn value(self) -> O { self.0 }
182    #[inline(always)]
183    fn split(self) -> (O, bool) { self }
184}
185
186/// The output of an item body that never fails: one that never gives NaN from arguments that aren't NaN, such as `exp`'s, or one whose NaN is the function's value, such as that of `∞ - ∞`. The slice loop tests none of them.
187#[derive(Clone, Copy)]
188pub(crate) struct Total<O>(pub O);
189
190impl<O: KernelItem> Outcome<O> for Total<O> {
191    const RESCUE: bool = false;
192    const TOTAL: bool = true;
193    #[inline(always)]
194    fn value(self) -> O { self.0 }
195    #[inline(always)]
196    fn split(self) -> (O, bool) { (self.0, true) }
197}
198
199/// Whether an item body never fails, as its outcome type `R` says. `forms!` passes the body as a closure, which names that type without running it.
200pub(crate) const fn total<K, E, O: KernelItem, R: Outcome<O>, const N: usize>(_: fn(&K, [E; N]) -> R) -> bool { R::TOTAL }
201
202/// The items of `x` that result `i` takes.
203#[inline(always)]
204fn items<E: Copy, const N: usize>(x: &[Arg<'_, E>; N], i: usize) -> [E; N] { std::array::from_fn(|k| x[k].nth(i)) }
205
206/// Writes `f` for each of `n` results into `out`, for arguments `x`, and gives whether `f` said `true` for every result. One and two arguments run through [`pairs_into`], and three through `triples_into`, whose loops vectorize.
207#[inline(always)]
208pub fn mapped<E: Copy, O, const N: usize>(x: &[Arg<'_, E>; N], n: usize, out: &mut [MaybeUninit<O>], f: impl Fn([E; N]) -> (O, bool)) -> bool {
209    if const { N == 1 } { pairs_into(x[0], Arg::One(()), n, out, #[inline(always)] |a, ()| f([a; N])) }
210    else if const { N == 2 } {
211        pairs_into(x[0], x[1], n, out, #[inline(always)] |a, b| f(std::array::from_fn(|k| if k == 0 { a } else { b })))
212    } else if const { N == 3 } {
213        triples_into(x[0], x[1], x[2], n, out, #[inline(always)] |a, b, c| {
214            f(std::array::from_fn(|k| match k { 0 => a, 1 => b, _ => c }))
215        })
216    } else { unreachable!("function objects take one to three arguments") }
217}
218
219/// Writes `f` for each of `n` results into `out`, for arguments `x`, and gives whether every result succeeded, as [`Outcome`] says. Slice forms call it through [`each!`]. The loop judges each output alone, and a [`Total`] output not at all. Only where some output is NaN does a second pass look at the arguments of the NaN outputs, which is rare.
220#[inline(always)]
221pub(crate) fn each<E: KernelItem, O: KernelItem, R: Outcome<O>, const N: usize>(
222    x: [Arg<'_, E>; N], n: usize, out: &mut [MaybeUninit<O>], f: impl Fn([E; N]) -> R) -> bool {
223    mapped(
224        &x, n, out, #[inline(always)] |a| f(a).split()
225    ) || (R::RESCUE && unsafe { written(&mut out[..n]) }.iter().enumerate().all(|(i, &r)| kept(r, items(&x, i))))
226}
227
228/// A rare form for `each_usual` that no block takes, for a function with no rare form.
229pub(crate) fn none<E, O>() -> (impl Fn(E) -> bool, impl Fn(E) -> O) { (|_| false, |_| unreachable!("`none` accepts no item")) }
230
231/// Writes a form of each item of `xs` into `out`, a block of `BLOCK` items at a time, and gives whether `mixed` said `true` for every block it wrote. `uniform` tests a whole block and gives whether every item of it takes `first`'s form, then whether every item takes `second`'s. A block that neither covers, and the last part block, goes to `mixed`. A test for each block keeps rare items, such as a function's special values, out of the loop over a uniform block. Each caller's test suits its forms: `each_usual` folds two tests over the items, and `erfc` folds a minimum and a maximum of their magnitudes.
232#[inline(always)]
233pub(crate) fn blocks<E: Copy, O, const BLOCK: usize>(
234    xs: &[E], out: &mut [MaybeUninit<O>], uniform: impl Fn(&[E; BLOCK]) -> (bool, bool), first: impl Fn(E) -> O, second: impl Fn(E) -> O,
235    mut mixed: impl FnMut(&[E], &mut [MaybeUninit<O>]) -> bool) -> bool {
236    let (blocks, rest) = xs.as_chunks::<BLOCK>();
237    let (slots, last) = out[..xs.len()].split_at_mut(blocks.len() * BLOCK);
238    let mut ok = true;
239    for (b, o) in blocks.iter().zip(slots.as_chunks_mut::<BLOCK>().0) {
240        let (one, two) = uniform(b);
241        if one { for k in 0..BLOCK { o[k].write(first(b[k])); } } else if two { for k in 0..BLOCK { o[k].write(second(b[k])); } } else { ok &= mixed(b, o) }
242    }
243    ok & mixed(rest, last)
244}
245
246/// [`each`] for one argument, through [`blocks`] of 64 items. A block whose every item `usual`'s test accepts writes `usual`'s form of each item. Failing that, a block whose every item `rare`'s test accepts writes `rare`'s form, as `exp` writes 0 for a block below its underflow bound. Any other block writes `body` of each item, judged as `each` judges it. The two forms never fail.
247#[inline(always)]
248pub(crate) fn each_usual<E: KernelItem, O: KernelItem, R: Outcome<O>>(
249    x: Arg<'_, E>, n: usize, out: &mut [MaybeUninit<O>], usual: (impl Fn(E) -> bool, impl Fn(E) -> O), rare: (impl Fn(E) -> bool, impl Fn(E) -> O),
250    body: impl Fn(E) -> R) -> bool {
251    let Arg::Many(xs) = x else { return each([x], n, out, #[inline(always)] |[x]| body(x)); };
252    // With blocks of 16, f64's test and the fast form's constants outgrew AArch64's vector registers, and the loop rebuilt the constants for each block.
253    blocks::<_, _, 64>(
254        &xs[..n], out, #[inline(always)] |b| b.iter().fold((true, true), |(one, two), &v| (one & (usual.0)(v), two & (rare.0)(v))), usual.1, rare.1,
255        #[inline(always)] |b, o| {
256            each([Arg::Many(b)], b.len(), o, #[inline(always)] |[x]| body(x))
257        })
258}
259
260/// Declares a function object of one argument whose slice form runs `each_usual`. `$usual` says whether an item needs none of the function's special values, `$fast` computes such an item, and `$name` is the full function. An optional `$rare => $value` gives the form for a block whose items all take one rare value, as `exp` gives 0 below its underflow bound. A trailing `; total` wraps the full function's results in [`Total`].
261macro_rules! usual_object {
262    ($(#[doc = $doc:literal])* $object:ident: $name:ident, $usual:expr => $fast:expr $(, $rare:expr => $value:expr)? $(; $outcome:ident)?) => {
263        $(#[doc = $doc])*
264        #[derive(Clone, Copy, Debug)]
265        pub struct $object;
266        forms!($object at real: |_, [x]| usual_object!(@outcome [$($outcome)?], $name::<_, { crate::FUSED }>(x)),
267            many |_, [x], n, out| crate::slice::each_usual(x, n, out, ($usual, $fast), usual_object!(@rare $($rare, $value)?),
268                #[inline(always)] |x| usual_object!(@outcome [$($outcome)?], $name::<_, { crate::FUSED }>(x))));
269    };
270    (@rare) => { crate::slice::none() };
271    (@rare $rare:expr, $value:expr) => { ($rare, $value) };
272    (@outcome [], $e:expr) => { $e };
273    (@outcome [total], $e:expr) => { crate::slice::Total($e) };
274}
275
276/// [`each`] with the item body marked `#[inline(always)]`. Every slice form runs its loop this way: `forms!`'s default slice form, and a custom slice form once it has read its settings. LLVM leaves a large unmarked body out of line, and its call stops the loop vectorizing.
277macro_rules! each {
278    ($x:expr, $n:expr, $out:expr, |$args:pat_param| $body:expr $(,)?) => {
279        crate::slice::each(
280            $x, $n, $out, #[inline(always)] |$args| $body,
281        )
282    };
283}
284
285/// Writes a function object's forms, compiled inside rustymath. The list names the item types, such as `Complex<f32>` or `i64`. `real` stands for `f32` and `f64`, and also writes an `f16` form, which runs the `f32` form and narrows its outputs. `=> T` makes the output type `T` in place of the item type: for each listed type, or after `real` for every float type. The object is the first argument of the item body, and its items the bracketed ones. The body gives an output, an `Option` of one, or a [`Total`] output, as [`Outcome`] says. A `many` body replaces the default slice form, which runs the item body through [`each!`]. Its arguments are the object, the bracketed `Arg`s, the number of results, and the slots it writes them into. Attributes before the object, such as `#[inline]`, go on its scalar forms, `one` and `judged`, so that a caller in another crate can inline a small form into its own loop over items.
286///
287/// A function object is a concrete type, never a generic one, so that its forms compile here. Settings that choose a loop, such as whether a tolerance is 0, are read once per call inside `many`, which then runs `each!`.
288///
289/// ```text
290/// forms!(Choose at real: |_, [x, y, m]| Total(if m != 0.0 { x } else { y }));
291/// forms!(Choose at bool: |_, [x, y, m]| (m & x) | (!m & y));
292/// forms!(Gcd at i64: |_, [x, y]| gcd(x, y));
293/// forms!(Less at real => bool: |k, [x, y]| k.0.less(x, y),
294///     many |k, [x, y], n, out| with_tolerance!(k.0, |t| each!([x, y], n, out, |[x, y]| t.less(x, y))));
295/// ```
296macro_rules! forms {
297    ($(#[$($m:tt)+])* $object:ident at real $(=> $o:ty)? : |$k:pat_param, $args:tt| $body:expr $(, many |$mk:pat_param, $margs:tt, $n:ident, $out:ident| $many:expr)?) => {
298        forms!(@types [$(#[$($m)+])*], $object, $k, $args, $body, [$((|$mk, $margs, $n, $out| $many))?], [f32 $(=> $o)?, f64 $(=> $o)?]);
299        forms!(@half [$(#[$($m)+])*], $object, forms!(@out half::f16 $(, $o)?), forms!(@narrow $($o)?), $args);
300    };
301    ($(#[$($m:tt)+])* $object:ident at $($e:ty $(=> $o:ty)?),+ : |$k:pat_param, $args:tt| $body:expr $(, many |$mk:pat_param, $margs:tt, $n:ident, $out:ident| $many:expr)?) => {
302        forms!(@types [$(#[$($m)+])*], $object, $k, $args, $body, [$((|$mk, $margs, $n, $out| $many))?], [$($e $(=> $o)?),+]);
303    };
304    // A generic type, such as `Indicator<Less>`, goes in brackets, because a type can't be followed by `at`.
305    ($(#[$($m:tt)+])* [$object:ty] at $($e:ty $(=> $o:ty)?),+ : |$k:pat_param, $args:tt| $body:expr $(, many |$mk:pat_param, $margs:tt, $n:ident, $out:ident| $many:expr)?) => {
306        forms!(@types [$(#[$($m)+])*], $object, $k, $args, $body, [$((|$mk, $margs, $n, $out| $many))?], [$($e $(=> $o)?),+]);
307    };
308    // One type at a time, so that the optional `many` group, which repeats on its own, needn't sit inside the types' repetition.
309    (@types $attrs:tt, $object:ty, $k:pat_param, $args:tt, $body:expr, $many:tt, [$e:ty $(=> $o:ty)? $(, $($rest:tt)+)?]) => {
310        forms!(@concrete $attrs, $object, $e, forms!(@out $e $(, $o)?), $k, $args, $body, $many);
311        $(forms!(@types $attrs, $object, $k, $args, $body, $many, [$($rest)+]);)?
312    };
313    (@concrete $attrs:tt, $object:ty, $e:ty, $o:ty, $k:pat_param, [$x:ident], $body:expr, $many:tt) => {
314        forms!(@impl $attrs, $object, Unary, 1, $e, $o, $k, [$x], $body, $many);
315    };
316    (@concrete $attrs:tt, $object:ty, $e:ty, $o:ty, $k:pat_param, [$x:ident, $y:ident], $body:expr, $many:tt) => {
317        forms!(@impl $attrs, $object, Binary, 2, $e, $o, $k, [$x, $y], $body, $many);
318    };
319    (@concrete $attrs:tt, $object:ty, $e:ty, $o:ty, $k:pat_param, [$x:ident, $y:ident, $z:ident], $body:expr, $many:tt) => {
320        forms!(@impl $attrs, $object, Ternary, 3, $e, $o, $k, [$x, $y, $z], $body, $many);
321    };
322    (@impl [$(#[$($m:tt)+])*], $object:ty, $arity:ident, $arity_n:literal, $e:ty, $o:ty, $k:pat_param, [$($x:ident),+], $body:expr, $many:tt) => {
323        impl crate::slice::Results<$e, $arity_n> for $object {
324            type Out = $o;
325            const TOTAL: bool = crate::slice::total::<Self, $e, $o, _, $arity_n>(|$k, [$($x),+]| $body);
326        }
327        unsafe impl crate::slice::$arity<$e> for $object {
328            $(#[$($m)+])*
329            fn one(&self, $($x: $e),+) -> $o {
330                let $k = self;
331                crate::slice::Outcome::<$o>::value($body)
332            }
333            $(#[$($m)+])*
334            fn judged(&self, $($x: $e),+) -> ($o, bool) {
335                let $k = self;
336                crate::slice::Outcome::<$o>::judged($body, [$($x),+])
337            }
338            forms!(@slice $e => $o, $k, [$($x),+], $body, $many);
339        }
340    };
341    (@slice $e:ty => $o:ty, $k:pat_param, [$($x:ident),+], $body:expr, []) => {
342        fn slice(&self, args: [crate::Arg<'_, $e>; forms!(@n $($x),+)], n: usize, out: &mut [std::mem::MaybeUninit<$o>]) -> bool {
343            let $k = self;
344            each!(args, n, out, |[$($x),+]| $body)
345        }
346    };
347    (@slice $e:ty => $o:ty, $k:pat_param, $args:tt, $body:expr, [(|$mk:pat_param, [$($mx:ident),+], $n:ident, $out:ident| $many:expr)]) => {
348        fn slice(&self, [$($mx),+]: [crate::Arg<'_, $e>; forms!(@n $($mx),+)], $n: usize, $out: &mut [std::mem::MaybeUninit<$o>]) -> bool {
349            let $mk = self;
350            $many
351        }
352    };
353    (@half $attrs:tt, $object:ty, $o:ty, $narrow:expr, [$x:ident]) => { forms!(@half_impl $attrs, $object, Unary, 1, $o, $narrow, [$x]); };
354    (@half $attrs:tt, $object:ty, $o:ty, $narrow:expr, [$x:ident, $y:ident]) => { forms!(@half_impl $attrs, $object, Binary, 2, $o, $narrow, [$x, $y]); };
355    (@half $attrs:tt, $object:ty, $o:ty, $narrow:expr, [$x:ident, $y:ident, $z:ident]) => { forms!(@half_impl $attrs, $object, Ternary, 3, $o, $narrow, [$x, $y, $z]); };
356    (@half_impl [$(#[$($m:tt)+])*], $object:ty, $arity:ident, $arity_n:literal, $o:ty, $narrow:expr, [$($x:ident),+]) => {
357        impl crate::slice::Results<half::f16, $arity_n> for $object {
358            type Out = $o;
359            const TOTAL: bool = <Self as crate::slice::Results<f32, $arity_n>>::TOTAL;
360        }
361        unsafe impl crate::slice::$arity<half::f16> for $object {
362            $(#[$($m)+])*
363            fn one(&self, $($x: half::f16),+) -> $o { ($narrow)(crate::slice::$arity::<f32>::one(self, $($x.to_f32()),+)) }
364            $(#[$($m)+])*
365            fn judged(&self, $($x: half::f16),+) -> ($o, bool) {
366                let (r, ok) = crate::slice::$arity::<f32>::judged(self, $($x.to_f32()),+);
367                (($narrow)(r), ok)
368            }
369            fn slice(&self, x: [crate::Arg<'_, half::f16>; $arity_n], n: usize, out: &mut [std::mem::MaybeUninit<$o>]) -> bool {
370                crate::slice::working(x, n, out, |x, m, o| crate::slice::$arity::<f32>::slice(self, x, m, o), $narrow)
371            }
372        }
373    };
374    (@out $e:ty) => { $e };
375    (@out $e:ty, $o:ty) => { $o };
376    (@narrow) => { half::f16::from_f32 };
377    (@narrow $o:ty) => { std::convert::identity };
378    (@n $($x:ident),+) => { [$(stringify!($x)),+].len() };
379}
380
381/// Runs `$body` for each item `$i` of a result of `$n` items, with `$a` and `$b` the items of `$x` and `$y` that pair with it. Each pairing of `One` and `Many` has a loop of its own. The loops index slices of the result's length with no iterator adapters, and vectorize at any build settings.
382macro_rules! each_pair {
383    ($x:expr, $y:expr, $n:expr, |$i:ident, $a:ident, $b:ident| $body:expr) => {{
384        let n = $n;
385        match ($x, $y) {
386            (Arg::One($a), Arg::One($b)) => {
387                for $i in 0..n {
388                    $body
389                }
390            }
391            (Arg::One($a), Arg::Many(ys)) => {
392                let ys = &ys[..n];
393                for $i in 0..n {
394                    let $b = ys[$i];
395                    $body
396                }
397            }
398            (Arg::Many(xs), Arg::One($b)) => {
399                let xs = &xs[..n];
400                for $i in 0..n {
401                    let $a = xs[$i];
402                    $body
403                }
404            }
405            (Arg::Many(xs), Arg::Many(ys)) => {
406                let (xs, ys) = (&xs[..n], &ys[..n]);
407                for $i in 0..n {
408                    let ($a, $b) = (xs[$i], ys[$i]);
409                    $body
410                }
411            }
412        }
413    }};
414}
415
416/// Runs `$body` for each item `$i` of a result of `$n` items, with `$a`, `$b` and `$c` the items of `$x`, `$y` and `$z` that go with it, as [`each_pair!`] does for two arguments. Each pairing of `One` and `Many` has a loop of its own.
417macro_rules! each_triple {
418    ($x:expr, $y:expr, $z:expr, $n:expr, |$i:ident, $a:ident, $b:ident, $c:ident| $body:expr) => {{
419        let n = $n;
420        match $x {
421            Arg::One($a) => each_pair!($y, $z, n, |$i, $b, $c| $body),
422            Arg::Many(firsts) => {
423                let firsts = &firsts[..n];
424                each_pair!($y, $z, n, |$i, $b, $c| {
425                    let $a = firsts[$i];
426                    $body
427                })
428            }
429        }
430    }};
431}
432
433/// Appends `f` on each of `n` pairs of items to `out`, and gives whether `f` said `true` for every pair. An `Arg::One` pairs with every item of the other argument. The loops write straight into `out`'s spare capacity, which is never zeroed first. This is the loop that rustymath's slice forms run, and a caller's own kernels can run it too, as basedpl's arithmetic does. A long call splits across threads, as [`parallel`](crate::parallel) describes.
434#[inline(never)]
435pub fn pairs<X: Copy + Sync, Y: Copy + Sync, B: Send>(x: Arg<'_, X>, y: Arg<'_, Y>, n: usize, out: &mut Vec<B>, f: impl Fn(X, Y) -> (B, bool) + Sync) -> bool {
436    unsafe {
437        appended(out, n, |slots| {
438            crate::parallel::split(slots, CHUNK, &|start, slots| {
439                pairs_into(x.part(start, slots.len()), y.part(start, slots.len()), slots.len(), slots, |a, b| f(a, b))
440            })
441        })
442    }
443}
444
445/// [`pairs`], writing into the `n` slots of `out` in place of appending to a vector. It runs [`triples_into`]'s loops with a unit first argument, which go straight to [`each_pair!`]'s.
446#[inline]
447pub(crate) fn pairs_into<X: Copy, Y: Copy, B>(x: Arg<'_, X>, y: Arg<'_, Y>, n: usize, out: &mut [MaybeUninit<B>], f: impl Fn(X, Y) -> (B, bool)) -> bool {
448    triples_into(Arg::One(()), x, y, n, out, #[inline(always)] |(), a, b| f(a, b))
449}
450
451/// [`pairs_into`] for three arguments, which `mapped` runs for a function object of three arguments.
452#[inline]
453pub(crate) fn triples_into<X: Copy, Y: Copy, Z: Copy, B>(
454    x: Arg<'_, X>, y: Arg<'_, Y>, z: Arg<'_, Z>, n: usize, out: &mut [MaybeUninit<B>], f: impl Fn(X, Y, Z) -> (B, bool)) -> bool {
455    let (out, mut ok) = (&mut out[..n], true);
456    each_triple!(x, y, z, n, |i, a, b, c| {
457        let (r, good) = f(a, b, c);
458        out[i].write(r);
459        ok &= good;
460    });
461    ok
462}
463
464/// Whether `valid` holds for every result in `out` with the items of `x` that gave it, paired as [`mapped`] pairs them. These loops only judge: a loop that also computes each result stops vectorizing for some bodies, such as `Ln`'s. Failures gather as bits, with no branch.
465#[inline(always)]
466pub fn judged<E: Copy, O: Copy, const N: usize>(x: &[Arg<'_, E>; N], out: &[O], valid: impl Fn([E; N], O) -> bool) -> bool {
467    let (n, mut bad) = (out.len(), 0u8);
468    if const { N == 1 } { each_pair!(x[0], Arg::Many(out), n, |_i, a, r| bad |= u8::from(!valid([a; N], r))); }
469    else if const { N == 2 } {
470        each_triple!(x[0], x[1], Arg::Many(out), n, |_i, a, b, r| bad |= u8::from(!valid(std::array::from_fn(|k| if k == 0 { a } else { b }), r)));
471    } else { for i in 0..n { bad |= u8::from(!valid(items(x, i), out[i])); } }
472    bad == 0
473}
474
475/// Appends `n` items to `out`, which `write` writes into its spare capacity, never zeroed first, and gives `write`'s result. `write` must write every slot it gets. Each caller's loop does, by its construction.
476///
477/// # Safety
478/// `write` must initialize every slot it receives before returning.
479#[inline(always)]
480pub unsafe fn appended<B, T>(out: &mut Vec<B>, n: usize, write: impl FnOnce(&mut [MaybeUninit<B>]) -> T) -> T {
481    out.reserve(n);
482    let result = write(&mut out.spare_capacity_mut()[..n]);
483    // SAFETY: `write` wrote all `n` slots.
484    unsafe { out.set_len(out.len() + n) }
485    result
486}
487
488/// `out`'s slots as items. Each caller passes only slots that its loop has just written.
489///
490/// # Safety
491/// Every slot in `out` must hold an initialized `B`.
492#[inline(always)]
493pub unsafe fn written<B>(out: &mut [MaybeUninit<B>]) -> &mut [B] {
494    // SAFETY: the caller has written every slot.
495    unsafe { &mut *(std::ptr::from_mut(out) as *mut [B]) }
496}
497
498/// Writes `f` for each of `n` results into `out`, for arguments `x`, for a function `f` with a fast form that holds for all but rare items. `fast` computes every item with no branch, and gives `None` for the rare items. The items run in two passes. The first writes NaN for each rare item and judges each result alone, as [`each`] does. The second runs only if a result is NaN. It recomputes each NaN item with `f` and judges it by its arguments too. A rare item costs one more scan of at most one `CHUNK`, the most that `parallel::in_chunks` hands a slice form. Slice forms call it through [`two_pass!`].
499#[inline(always)]
500pub(crate) fn two_pass<R, O, const N: usize>(
501    x: [Arg<'_, R>; N], n: usize, out: &mut [MaybeUninit<O>], fast: impl Fn([R; N]) -> Option<R>, f: impl Fn([R; N]) -> R) -> bool where
502    R: crate::Float + crate::buffer::Source<O>, O: crate::Float {
503    let out = &mut out[..n];
504    let first = mapped(&x, n, out, #[inline(always)] |a| {
505        let r = <R as crate::buffer::Source<O>>::read(fast(a).unwrap_or(R::nan()));
506        (r, !r.is_nan())
507    });
508    if first { return true; }
509    let mut ok = true;
510    for (i, r) in unsafe { written(out) }.iter_mut().enumerate().filter(|(_, r)| r.is_nan()) {
511        let a = items(&x, i);
512        *r = <R as crate::buffer::Source<O>>::read(f(a));
513        ok &= kept(*r, a);
514    }
515    ok
516}
517
518/// `f`, a slice form at `F`'s working type, for narrower floats, as `forms!`'s `f16` forms run it: the items convert to the working type and run, and `narrow` turns each output into the output at `F`.
519pub(crate) fn working<F: Float, O: Copy, P, const N: usize>(
520    x: [Arg<'_, F>; N], n: usize, out: &mut [MaybeUninit<P>], f: impl Fn([Arg<'_, F::Working>; N], usize, &mut [MaybeUninit<O>]) -> bool,
521    narrow: impl Fn(O) -> P) -> bool {
522    let mut bufs = [(); N].map(|()| Vec::new());
523    let mut next = bufs.each_mut().into_iter();
524    unsafe { buffered(n, out, |o| f(x.map(|a| a.converted(0, n, next.next().unwrap(), F::working)), n, o), narrow) }
525}
526
527/// `f`, a slice form writing `n` outputs of type `O`, run into a buffer, with `each` turning each output into the `P` that `out` takes. It gives `f`'s result.
528///
529/// # Safety
530/// `f` must initialize every output slot it receives before returning.
531pub unsafe fn buffered<O: Copy, P>(n: usize, out: &mut [MaybeUninit<P>], f: impl FnOnce(&mut [MaybeUninit<O>]) -> bool, each: impl Fn(O) -> P) -> bool {
532    let mut rs = Vec::new();
533    let ok = unsafe { appended(&mut rs, n, f) };
534    pairs_into(Arg::Many(&rs), Arg::One(()), n, &mut out[..n], |r, ()| (each(r), true));
535    ok
536}