Skip to main content

binius_field/underlier/
maskable.rs

1// Copyright 2026 The Binius Developers
2
3use bytemuck::Zeroable;
4
5use super::divisible::Divisible;
6
7/// Branchless conditional lane selection for fields and packed fields.
8///
9/// `Maskable<T>` keeps a chosen subset of a value's `T`-lanes and zeroes the rest, against a
10/// precomputed [`Self::Mask`]. It is the high-level, underlier-free primitive behind the branchless
11/// masked accumulation in the shift protocol: a mask is built once with [`make_mask`] and reused
12/// across many [`select`] calls, so the per-call cost is a single bitwise AND on a binary field.
13///
14/// It is a parent trait of [`Field`](crate::Field) (`Maskable<Self>`, a single lane) and
15/// [`PackedField`](crate::PackedField) (`Maskable<Self::Scalar>`, one entry per packed lane),
16/// mirroring how [`Divisible`] is a parent trait of both.
17///
18/// [`make_mask`]: Maskable::make_mask
19/// [`select`]: Maskable::select
20pub trait Maskable<T>: Divisible<T> + Copy + Zeroable {
21	/// A precomputed mask selecting which `T`-lanes [`select`](Maskable::select) keeps.
22	///
23	/// `Send + Sync` so a precomputed mask table can be shared across threads (e.g. a parallel
24	/// fold over a precomputed `Vec<Self::Mask>`).
25	type Mask: Send + Sync;
26
27	/// Builds a mask from per-lane boolean selectors, in LSB-to-MSB lane order (the same ordering
28	/// as [`Divisible`]). Consumes at most [`Divisible::N`] selectors; any lane past the end of the
29	/// iterator, or whose selector is `false`, is not selected.
30	fn make_mask(selectors: impl Iterator<Item = bool>) -> Self::Mask;
31
32	/// Returns a value keeping the lanes selected when `mask` was built and zeroing the rest.
33	///
34	/// For a `mask` built by `Self::make_mask(selectors)`, this equals
35	///
36	/// ```text
37	/// Self::from_iter(zip(self.ref_iter(), selectors)
38	///     .map(|(val, selected)| if selected { val } else { <zero> }))
39	/// ```
40	///
41	/// but branchless: on a binary field each selected lane of the mask is all-ones and each
42	/// unselected lane all-zeros, so this is a single bitwise AND.
43	fn select(&self, mask: &Self::Mask) -> Self;
44}