Skip to main content

binius_field/
packed.rs

1// Copyright 2023-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4//! Traits for packed field elements which support SIMD implementations.
5//!
6//! Interfaces are derived from [`plonky2`](https://github.com/mir-protocol/plonky2).
7
8use std::{
9	fmt::Debug,
10	iter,
11	ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
12};
13
14use bytemuck::Zeroable;
15
16use super::{Random, arithmetic_traits::Square};
17use crate::{BinaryField, Divisible, Maskable, WideMul, field::FieldOps};
18
19/// A packed field represents a vector of underlying field elements.
20///
21/// Arithmetic operations on packed field elements can be accelerated with SIMD CPU instructions.
22/// The vector width is a constant, `WIDTH`. This trait requires that the width must be a power of
23/// two.
24pub trait PackedField:
25	Default
26	+ Debug
27	+ Clone
28	+ Copy
29	+ Eq
30	+ Sized
31	+ FieldOps
32	+ Add<Self::Scalar, Output = Self>
33	+ Sub<Self::Scalar, Output = Self>
34	+ Mul<Self::Scalar, Output = Self>
35	+ AddAssign<Self::Scalar>
36	+ SubAssign<Self::Scalar>
37	+ MulAssign<Self::Scalar>
38	+ Send
39	+ Sync
40	+ Zeroable
41	+ Random
42	+ WideMul<Output: Debug + Send + Sync + 'static>
43	+ 'static
44	// A packed field divides into its `WIDTH` scalars. Scalar element access (`get`/`set` and
45	// their `_unchecked` variants), broadcast, and the scalar iterators are all provided by this
46	// supertrait.
47	+ Divisible<Self::Scalar>
48	// A packed field supports branchless per-lane masking over its scalars.
49	+ Maskable<Self::Scalar>
50{
51	/// Base-2 logarithm of the number of field elements packed into one packed element.
52	///
53	/// This is the number of scalars the packed field divides into, i.e. its `Divisible` log-count.
54	const LOG_WIDTH: usize = Self::LOG_N;
55
56	/// The number of field elements packed into one packed element.
57	///
58	/// WIDTH is guaranteed to equal 2^LOG_WIDTH.
59	const WIDTH: usize = 1 << Self::LOG_WIDTH;
60
61	/// Yields one scalar per lane, from the lowest lane to the highest.
62	#[inline]
63	fn into_iter(self) -> impl ExactSizeIterator<Item = Self::Scalar> + Send + Clone {
64		Divisible::value_iter(self)
65	}
66
67	/// Yields one scalar per lane, from the lowest lane to the highest.
68	#[inline]
69	fn iter(&self) -> impl ExactSizeIterator<Item = Self::Scalar> + Send + Clone + '_ {
70		Divisible::ref_iter(self)
71	}
72
73	/// Yields every scalar of the whole slice: element by element, each element's lanes in order.
74	#[inline]
75	fn iter_slice(slice: &[Self]) -> impl ExactSizeIterator<Item = Self::Scalar> + Send + Clone + '_
76	{
77		Divisible::slice_iter(slice)
78	}
79
80	/// Construct a packed field element from a function that returns scalar values by index.
81	fn from_fn(f: impl FnMut(usize) -> Self::Scalar) -> Self;
82
83	/// Construct a packed field element from a sequence of scalars.
84	///
85	/// If the number of values in the sequence is less than the packing width, the remaining
86	/// elements are set to zero. If greater than the packing width, the excess elements are
87	/// ignored.
88	#[inline]
89	fn from_scalars(values: impl IntoIterator<Item = Self::Scalar>) -> Self {
90		Divisible::from_iter(values.into_iter())
91	}
92
93	/// Returns the value to the power `exp`.
94	fn pow(self, exp: u64) -> Self {
95		let mut res = Self::one();
96		for i in (0..64).rev() {
97			res = Square::square(res);
98			if ((exp >> i) & 1) == 1 {
99				res.mul_assign(self);
100			}
101		}
102		res
103	}
104
105	/// Interleaves blocks of this packed vector with another packed vector.
106	///
107	/// The operation can be seen as stacking the two vectors, dividing them into 2x2 matrices of
108	/// blocks, where each block is 2^`log_block_width` elements, and transposing the matrices.
109	///
110	/// Consider this example, where `LOG_WIDTH` is 3 and `log_block_len` is 1:
111	///     A = [a0, a1, a2, a3, a4, a5, a6, a7]
112	///     B = [b0, b1, b2, b3, b4, b5, b6, b7]
113	///
114	/// The interleaved result is
115	///     A' = [a0, a1, b0, b1, a4, a5, b4, b5]
116	///     B' = [a2, a3, b2, b3, a6, a7, b6, b7]
117	///
118	/// ## Preconditions
119	/// * `log_block_len` must be strictly less than `LOG_WIDTH`.
120	fn interleave(self, other: Self, log_block_len: usize) -> (Self, Self);
121
122	/// Unzips interleaved blocks of this packed vector with another packed vector.
123	///
124	/// Consider this example, where `LOG_WIDTH` is 3 and `log_block_len` is 1:
125	///    A = [a0, a1, b0, b1, a2, a3, b2, b3]
126	///    B = [a4, a5, b4, b5, a6, a7, b6, b7]
127	///
128	/// The transposed result is
129	///    A' = [a0, a1, a2, a3, a4, a5, a6, a7]
130	///    B' = [b0, b1, b2, b3, b4, b5, b6, b7]
131	///
132	/// ## Preconditions
133	/// * `log_block_len` must be strictly less than `LOG_WIDTH`.
134	fn unzip(self, other: Self, log_block_len: usize) -> (Self, Self);
135
136	/// Spread takes a block of elements within a packed field and repeats them to the full packing
137	/// width.
138	///
139	/// Spread can be seen as an extension of the functionality of [`Divisible::broadcast`].
140	///
141	/// ## Examples
142	///
143	/// ```
144	/// use binius_field::{BinaryField1b, PackedField, PackedBinaryField8x1b};
145	///
146	/// let input =
147	///     PackedBinaryField8x1b::from_scalars([0, 1, 0, 1, 0, 1, 0, 1].map(BinaryField1b::from));
148	/// assert_eq!(
149	///     input.spread(0, 1),
150	///     PackedBinaryField8x1b::from_scalars([1, 1, 1, 1, 1, 1, 1, 1].map(BinaryField1b::from))
151	/// );
152	/// assert_eq!(
153	///     input.spread(1, 0),
154	///     PackedBinaryField8x1b::from_scalars([0, 0, 0, 0, 1, 1, 1, 1].map(BinaryField1b::from))
155	/// );
156	/// assert_eq!(
157	///     input.spread(2, 0),
158	///     PackedBinaryField8x1b::from_scalars([0, 0, 1, 1, 0, 0, 1, 1].map(BinaryField1b::from))
159	/// );
160	/// assert_eq!(input.spread(3, 0), input);
161	/// ```
162	///
163	/// ## Preconditions
164	///
165	/// * `log_block_len` must be less than or equal to `LOG_WIDTH`.
166	/// * `block_idx` must be less than `2^(Self::LOG_WIDTH - log_block_len)`.
167	#[inline]
168	fn spread(self, log_block_len: usize, block_idx: usize) -> Self {
169		assert!(log_block_len <= Self::LOG_WIDTH);
170		assert!(block_idx < 1 << (Self::LOG_WIDTH - log_block_len));
171
172		// Safety: is guaranteed by the preconditions.
173		unsafe { self.spread_unchecked(log_block_len, block_idx) }
174	}
175
176	/// Unsafe version of [`Self::spread`].
177	///
178	/// # Safety
179	/// The caller must ensure that `log_block_len` is less than or equal to `LOG_WIDTH` and
180	/// `block_idx` is less than `2^(Self::LOG_WIDTH - log_block_len)`.
181	#[inline]
182	unsafe fn spread_unchecked(self, log_block_len: usize, block_idx: usize) -> Self {
183		let block_len = 1 << log_block_len;
184		let repeat = 1 << (Self::LOG_WIDTH - log_block_len);
185
186		Self::from_scalars(
187			self.iter()
188				.skip(block_idx * block_len)
189				.take(block_len)
190				.flat_map(|elem| iter::repeat_n(elem, repeat)),
191		)
192	}
193}
194
195#[inline(always)]
196pub fn get_packed_slice<P: PackedField>(packed: &[P], i: usize) -> P::Scalar {
197	assert!(i >> P::LOG_WIDTH < packed.len(), "index out of bounds");
198
199	unsafe { get_packed_slice_unchecked(packed, i) }
200}
201
202/// Returns the scalar at the given index without bounds checking.
203/// # Safety
204/// The caller must ensure that `i` is less than `P::WIDTH * packed.len()`.
205#[inline(always)]
206pub unsafe fn get_packed_slice_unchecked<P: PackedField>(packed: &[P], i: usize) -> P::Scalar {
207	// Safety:
208	// - `i / P::WIDTH` is within the bounds of `packed` if `i` is less than `P::WIDTH *
209	//   packed.len()`
210	// - `i % P::WIDTH` is always less than `P::WIDTH
211	unsafe {
212		packed
213			.get_unchecked(i >> P::LOG_WIDTH)
214			.get_unchecked(i % P::WIDTH)
215	}
216}
217
218/// Sets the scalar at the given index without bounds checking.
219/// # Safety
220/// The caller must ensure that `i` is less than `P::WIDTH * packed.len()`.
221#[inline]
222pub unsafe fn set_packed_slice_unchecked<P: PackedField>(
223	packed: &mut [P],
224	i: usize,
225	scalar: P::Scalar,
226) {
227	// Safety: if `i` is less than `P::WIDTH * packed.len()`, then
228	// - `i / P::WIDTH` is within the bounds of `packed`
229	// - `i % P::WIDTH` is always less than `P::WIDTH
230	unsafe {
231		packed
232			.get_unchecked_mut(i >> P::LOG_WIDTH)
233			.set_unchecked(i % P::WIDTH, scalar);
234	}
235}
236
237/// A helper trait to make the generic bounds shorter
238pub trait PackedBinaryField: PackedField<Scalar: BinaryField> {}
239
240impl<PT> PackedBinaryField for PT where PT: PackedField<Scalar: BinaryField> {}
241
242#[cfg(test)]
243mod tests {
244	use std::iter::repeat_with;
245
246	use rand::prelude::*;
247
248	use crate::{
249		BinaryField1b, Ghash128b, PackedBinaryField1x1b, PackedBinaryField2x1b,
250		PackedBinaryField4x1b, PackedBinaryField8x1b, PackedBinaryField16x1b,
251		PackedBinaryField32x1b, PackedBinaryField64x1b, PackedBinaryField128x1b,
252		PackedBinaryField256x1b, PackedBinaryField512x1b, PackedField, PackedGhash1x128b,
253		PackedGhash2x128b, PackedGhash4x128b, PackedRijndael1x8b, PackedRijndael16x8b,
254		PackedRijndael32x8b, PackedRijndael64x8b, Rijndael8b, SlicedGhashSq1x256b,
255		SlicedGhashSq2x256b, SlicedGhashSq4x256b,
256	};
257
258	trait PackedFieldTest {
259		fn run<P: PackedField>(&self);
260	}
261
262	/// Run the test for all the packed fields defined in this crate.
263	fn run_for_all_packed_fields(test: &impl PackedFieldTest) {
264		// B1
265		test.run::<BinaryField1b>();
266		test.run::<PackedBinaryField1x1b>();
267		test.run::<PackedBinaryField2x1b>();
268		test.run::<PackedBinaryField4x1b>();
269		test.run::<PackedBinaryField8x1b>();
270		test.run::<PackedBinaryField16x1b>();
271		test.run::<PackedBinaryField32x1b>();
272		test.run::<PackedBinaryField64x1b>();
273		test.run::<PackedBinaryField128x1b>();
274		test.run::<PackedBinaryField256x1b>();
275		test.run::<PackedBinaryField512x1b>();
276
277		// AES
278		test.run::<Rijndael8b>();
279		test.run::<PackedRijndael1x8b>();
280		test.run::<PackedRijndael16x8b>();
281		test.run::<PackedRijndael32x8b>();
282		test.run::<PackedRijndael64x8b>();
283
284		// GHASH
285		test.run::<Ghash128b>();
286		test.run::<PackedGhash1x128b>();
287		test.run::<PackedGhash2x128b>();
288		test.run::<PackedGhash4x128b>();
289
290		// GHASH² in a sliced layout
291		test.run::<SlicedGhashSq1x256b>();
292		test.run::<SlicedGhashSq2x256b>();
293		test.run::<SlicedGhashSq4x256b>();
294	}
295
296	fn check_value_iteration<P: PackedField>(mut rng: impl Rng) {
297		let packed = P::random(&mut rng);
298		let mut iter = packed.iter();
299		for i in 0..P::WIDTH {
300			assert_eq!(packed.get(i), iter.next().unwrap());
301		}
302		assert!(iter.next().is_none());
303	}
304
305	fn check_ref_iteration<P: PackedField>(mut rng: impl Rng) {
306		let packed = P::random(&mut rng);
307		let mut iter = packed.into_iter();
308		for i in 0..P::WIDTH {
309			assert_eq!(packed.get(i), iter.next().unwrap());
310		}
311		assert!(iter.next().is_none());
312	}
313
314	fn check_exact_size<P: PackedField>(mut rng: impl Rng) {
315		let packed = P::random(&mut rng);
316
317		// A reported length has to match what iteration actually produces.
318		assert_eq!(packed.iter().len(), P::WIDTH);
319		assert_eq!(packed.iter().count(), P::WIDTH);
320		assert_eq!(packed.iter().size_hint(), (P::WIDTH, Some(P::WIDTH)));
321
322		assert_eq!(packed.into_iter().len(), P::WIDTH);
323		assert_eq!(packed.into_iter().count(), P::WIDTH);
324		assert_eq!(packed.into_iter().size_hint(), (P::WIDTH, Some(P::WIDTH)));
325
326		// Over a slice the length is the slice length times the width, so the empty slice is the
327		// one case that can disagree without any single scalar being wrong.
328		for len in [0, 1, 3] {
329			let slice = repeat_with(|| P::random(&mut rng))
330				.take(len)
331				.collect::<Vec<_>>();
332			let expected = len * P::WIDTH;
333
334			assert_eq!(P::iter_slice(&slice).len(), expected);
335			assert_eq!(P::iter_slice(&slice).count(), expected);
336			assert_eq!(P::iter_slice(&slice).size_hint(), (expected, Some(expected)));
337		}
338	}
339
340	fn check_iter_slice_matches_flat_map<P: PackedField>(mut rng: impl Rng) {
341		// The empty slice included, since a zero-length run is where the two sides can disagree
342		// without any single scalar being wrong.
343		for len in [0, 1, 2, 5] {
344			let slice = repeat_with(|| P::random(&mut rng))
345				.take(len)
346				.collect::<Vec<_>>();
347
348			// Reading the whole slice equals reading each element's lanes in turn.
349			let expected = slice.iter().flat_map(P::iter).collect::<Vec<_>>();
350			assert_eq!(P::iter_slice(&slice).collect::<Vec<_>>(), expected);
351		}
352	}
353
354	fn check_skipping<P: PackedField>(mut rng: impl Rng) {
355		let packed = P::random(&mut rng);
356		let scalars = packed.iter().collect::<Vec<_>>();
357
358		// Jumping ahead lands on the same scalar as reading forward to it, and the tail after a
359		// skip is the rest of the sequence.
360		for skip in 0..P::WIDTH {
361			assert_eq!(packed.iter().nth(skip), Some(scalars[skip]));
362			assert!(packed.iter().skip(skip).eq(scalars[skip..].iter().copied()));
363		}
364		assert_eq!(packed.iter().nth(P::WIDTH), None);
365
366		// Across an element boundary, so the slice iterator's index arithmetic is exercised too.
367		// The oracle reads each element in turn, so a skip is compared against independent
368		// iteration rather than against itself.
369		let slice = [packed, P::random(&mut rng)];
370		let expected = slice.iter().flat_map(P::iter).collect::<Vec<_>>();
371		for skip in [0, P::WIDTH - 1, P::WIDTH, 2 * P::WIDTH - 1] {
372			assert_eq!(P::iter_slice(&slice).nth(skip), Some(expected[skip]));
373		}
374	}
375
376	struct PackedFieldIterationTest;
377
378	impl PackedFieldTest for PackedFieldIterationTest {
379		fn run<P: PackedField>(&self) {
380			let mut rng = StdRng::seed_from_u64(0);
381
382			check_value_iteration::<P>(&mut rng);
383			check_ref_iteration::<P>(&mut rng);
384			check_exact_size::<P>(&mut rng);
385			check_iter_slice_matches_flat_map::<P>(&mut rng);
386			check_skipping::<P>(&mut rng);
387		}
388	}
389
390	#[test]
391	fn test_iteration() {
392		run_for_all_packed_fields(&PackedFieldIterationTest);
393	}
394}