Skip to main content

binius_field/packed_fields/
primitive.rs

1// Copyright 2024-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4//! A primitive or SIMD integer reinterpreted as a vector of packed field elements.
5
6// This is because derive(bytemuck::TransparentWrapper) adds some type constraints to
7// PackedPrimitiveType in addition to the type constraints we define. Even more, annoying, the
8// allow attribute has to be added to the module, it doesn't work to add it to the struct
9// definition.
10#![allow(clippy::multiple_bound_locations)]
11
12use std::{
13	fmt::Debug,
14	iter::{Product, Sum},
15	marker::PhantomData,
16	ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
17};
18
19use binius_utils::{
20	DeserializeBytes, FixedSizeSerializeBytes, SerializationError, SerializeBytes,
21	bytes::{Buf, BufMut},
22	checked_arithmetics::checked_int_div,
23	iter::IterExtensions,
24};
25use bytemuck::{Pod, TransparentWrapper, Zeroable};
26use rand::{
27	Rng,
28	distr::{Distribution, StandardUniform},
29};
30
31use crate::{
32	BinaryField, Divisible, ExtensionField, Field, Maskable, PackedField, WideMul,
33	arithmetic_traits::{InvertOrZero, Square},
34	field::FieldOps,
35	underlier::{U1, Underlier, UnderlierView},
36};
37
38#[derive(PartialEq, Eq, Clone, Copy, Default, bytemuck::TransparentWrapper)]
39#[repr(transparent)]
40#[transparent(U)]
41pub struct PackedPrimitiveType<U: Underlier, Scalar: BinaryField>(pub U, pub PhantomData<Scalar>);
42
43impl<U: Underlier, Scalar: BinaryField> PackedPrimitiveType<U, Scalar> {
44	pub const WIDTH: usize = {
45		assert!(U::BITS.is_multiple_of(Scalar::N_BITS));
46
47		U::BITS / Scalar::N_BITS
48	};
49
50	pub const LOG_WIDTH: usize = {
51		let result = Self::WIDTH.ilog2();
52
53		assert!(2usize.pow(result) == Self::WIDTH);
54
55		result as usize
56	};
57
58	#[inline]
59	pub const fn from_underlier(val: U) -> Self {
60		Self(val, PhantomData)
61	}
62
63	#[inline]
64	pub const fn to_underlier(self) -> U {
65		self.0
66	}
67}
68
69impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField>
70	PackedPrimitiveType<U, Scalar>
71{
72	#[inline]
73	pub fn broadcast(scalar: Scalar) -> Self {
74		<U as Divisible<_>>::broadcast(scalar.to_underlier()).into()
75	}
76}
77
78unsafe impl<U: Underlier, Scalar: BinaryField> UnderlierView for PackedPrimitiveType<U, Scalar> {
79	type Underlier = U;
80
81	#[inline(always)]
82	fn to_underlier(self) -> Self::Underlier {
83		TransparentWrapper::peel(self)
84	}
85
86	#[inline(always)]
87	fn to_underlier_ref(&self) -> &Self::Underlier {
88		TransparentWrapper::peel_ref(self)
89	}
90
91	#[inline(always)]
92	fn to_underlier_ref_mut(&mut self) -> &mut Self::Underlier {
93		TransparentWrapper::peel_mut(self)
94	}
95
96	#[inline(always)]
97	fn to_underliers_ref(val: &[Self]) -> &[Self::Underlier] {
98		TransparentWrapper::peel_slice(val)
99	}
100
101	#[inline(always)]
102	fn to_underliers_ref_mut(val: &mut [Self]) -> &mut [Self::Underlier] {
103		TransparentWrapper::peel_slice_mut(val)
104	}
105
106	#[inline(always)]
107	fn from_underlier(val: Self::Underlier) -> Self {
108		TransparentWrapper::wrap(val)
109	}
110
111	#[inline(always)]
112	fn from_underlier_ref(val: &Self::Underlier) -> &Self {
113		TransparentWrapper::wrap_ref(val)
114	}
115
116	#[inline(always)]
117	fn from_underlier_ref_mut(val: &mut Self::Underlier) -> &mut Self {
118		TransparentWrapper::wrap_mut(val)
119	}
120
121	#[inline(always)]
122	fn from_underliers_ref(val: &[Self::Underlier]) -> &[Self] {
123		TransparentWrapper::wrap_slice(val)
124	}
125
126	#[inline(always)]
127	fn from_underliers_ref_mut(val: &mut [Self::Underlier]) -> &mut [Self] {
128		TransparentWrapper::wrap_slice_mut(val)
129	}
130}
131
132// Note: this bound is deliberately phrased in terms of `Divisible` rather than `Self:
133// PackedField`. `PackedField` now has `WideMul<Output: Debug>` as a parent trait, and the trivial
134// `WideMul` impl sets `Output = Self`, so a `Self: PackedField` bound here would make `Debug`
135// depend on itself and overflow trait resolution.
136impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Debug
137	for PackedPrimitiveType<U, Scalar>
138{
139	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140		let width = checked_int_div(U::BITS, Scalar::N_BITS);
141		let values_str = (0..width)
142			// Safety: `i` ranges over `0..width`, the number of scalars packed in `U`.
143			.map(|i| {
144				Scalar::from_underlier(unsafe {
145					Divisible::<Scalar::Underlier>::get_unchecked(&self.0, i)
146				})
147			})
148			.map(|value| format!("{value}"))
149			.collect::<Vec<_>>()
150			.join(",");
151
152		write!(f, "Packed{}x{}([{}])", width, Scalar::N_BITS, values_str)
153	}
154}
155
156impl<U: Underlier, Scalar: BinaryField> From<U> for PackedPrimitiveType<U, Scalar> {
157	#[inline]
158	fn from(val: U) -> Self {
159		Self(val, PhantomData)
160	}
161}
162
163// Serialization forwards to the underlier, which is a transparent wrapper of `Self`. These are
164// available whenever the underlier implements the corresponding trait, so a single generic impl
165// covers every `PackedPrimitiveType` rather than a per-type macro expansion.
166impl<U: Underlier + SerializeBytes, Scalar: BinaryField> SerializeBytes
167	for PackedPrimitiveType<U, Scalar>
168{
169	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
170		self.0.serialize(write_buf)
171	}
172}
173
174impl<U: Underlier + DeserializeBytes, Scalar: BinaryField> DeserializeBytes
175	for PackedPrimitiveType<U, Scalar>
176{
177	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError> {
178		Ok(Self(U::deserialize(read_buf)?, PhantomData))
179	}
180}
181
182impl<U: Underlier + FixedSizeSerializeBytes, Scalar: BinaryField> FixedSizeSerializeBytes
183	for PackedPrimitiveType<U, Scalar>
184{
185	const BYTE_SIZE: usize = U::BYTE_SIZE;
186}
187
188impl<U: Underlier, Scalar: BinaryField> Neg for PackedPrimitiveType<U, Scalar> {
189	type Output = Self;
190
191	#[inline]
192	fn neg(self) -> Self::Output {
193		self
194	}
195}
196
197impl<U: Underlier, Scalar: BinaryField> Add for PackedPrimitiveType<U, Scalar> {
198	type Output = Self;
199
200	#[inline]
201	#[allow(clippy::suspicious_arithmetic_impl)]
202	fn add(self, rhs: Self) -> Self::Output {
203		(self.0 ^ rhs.0).into()
204	}
205}
206
207impl<U: Underlier, Scalar: BinaryField> Add<&Self> for PackedPrimitiveType<U, Scalar> {
208	type Output = Self;
209
210	#[inline]
211	#[allow(clippy::suspicious_arithmetic_impl)]
212	fn add(self, rhs: &Self) -> Self::Output {
213		(self.0 ^ rhs.0).into()
214	}
215}
216
217impl<U: Underlier, Scalar: BinaryField> Sub for PackedPrimitiveType<U, Scalar> {
218	type Output = Self;
219
220	#[inline]
221	#[allow(clippy::suspicious_arithmetic_impl)]
222	fn sub(self, rhs: Self) -> Self::Output {
223		(self.0 ^ rhs.0).into()
224	}
225}
226
227impl<U: Underlier, Scalar: BinaryField> Sub<&Self> for PackedPrimitiveType<U, Scalar> {
228	type Output = Self;
229
230	#[inline]
231	#[allow(clippy::suspicious_arithmetic_impl)]
232	fn sub(self, rhs: &Self) -> Self::Output {
233		(self.0 ^ rhs.0).into()
234	}
235}
236
237impl<U: Underlier, Scalar: BinaryField> AddAssign for PackedPrimitiveType<U, Scalar>
238where
239	Self: Add<Output = Self>,
240{
241	fn add_assign(&mut self, rhs: Self) {
242		*self = *self + rhs;
243	}
244}
245
246impl<U: Underlier, Scalar: BinaryField> AddAssign<&Self> for PackedPrimitiveType<U, Scalar>
247where
248	Self: for<'a> Add<&'a Self, Output = Self>,
249{
250	fn add_assign(&mut self, rhs: &Self) {
251		*self = *self + rhs;
252	}
253}
254
255impl<U: Underlier, Scalar: BinaryField> SubAssign for PackedPrimitiveType<U, Scalar>
256where
257	Self: Sub<Output = Self>,
258{
259	fn sub_assign(&mut self, rhs: Self) {
260		*self = *self - rhs;
261	}
262}
263
264impl<U: Underlier, Scalar: BinaryField> SubAssign<&Self> for PackedPrimitiveType<U, Scalar>
265where
266	Self: for<'a> Sub<&'a Self, Output = Self>,
267{
268	fn sub_assign(&mut self, rhs: &Self) {
269		*self = *self - rhs;
270	}
271}
272
273impl<U: Underlier, Scalar: BinaryField> MulAssign for PackedPrimitiveType<U, Scalar>
274where
275	Self: Mul<Output = Self>,
276{
277	fn mul_assign(&mut self, rhs: Self) {
278		*self = *self * rhs;
279	}
280}
281
282impl<U: Underlier, Scalar: BinaryField> MulAssign<&Self> for PackedPrimitiveType<U, Scalar>
283where
284	Self: for<'a> Mul<&'a Self, Output = Self>,
285{
286	fn mul_assign(&mut self, rhs: &Self) {
287		*self = *self * rhs;
288	}
289}
290
291impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Add<Scalar>
292	for PackedPrimitiveType<U, Scalar>
293{
294	type Output = Self;
295
296	fn add(self, rhs: Scalar) -> Self::Output {
297		self + Self::broadcast(rhs)
298	}
299}
300
301impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Sub<Scalar>
302	for PackedPrimitiveType<U, Scalar>
303{
304	type Output = Self;
305
306	fn sub(self, rhs: Scalar) -> Self::Output {
307		self - Self::broadcast(rhs)
308	}
309}
310
311impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Mul<Scalar>
312	for PackedPrimitiveType<U, Scalar>
313where
314	Self: Mul<Output = Self>,
315{
316	type Output = Self;
317
318	fn mul(self, rhs: Scalar) -> Self::Output {
319		self * Self::broadcast(rhs)
320	}
321}
322
323impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> AddAssign<Scalar>
324	for PackedPrimitiveType<U, Scalar>
325{
326	fn add_assign(&mut self, rhs: Scalar) {
327		*self += Self::broadcast(rhs);
328	}
329}
330
331impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> SubAssign<Scalar>
332	for PackedPrimitiveType<U, Scalar>
333{
334	fn sub_assign(&mut self, rhs: Scalar) {
335		*self -= Self::broadcast(rhs);
336	}
337}
338
339impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> MulAssign<Scalar>
340	for PackedPrimitiveType<U, Scalar>
341where
342	Self: MulAssign<Self>,
343{
344	fn mul_assign(&mut self, rhs: Scalar) {
345		*self *= Self::broadcast(rhs);
346	}
347}
348
349impl<U: Underlier, Scalar: BinaryField> Sum for PackedPrimitiveType<U, Scalar>
350where
351	Self: Add<Output = Self>,
352{
353	fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
354		iter.fold(Self::from(U::default()), |result, next| result + next)
355	}
356}
357
358impl<'a, U: Underlier, Scalar: BinaryField> Sum<&'a Self> for PackedPrimitiveType<U, Scalar>
359where
360	Self: Add<Output = Self>,
361{
362	fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
363		iter.fold(Self::from(U::default()), |result, next| result + *next)
364	}
365}
366
367impl<U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Product
368	for PackedPrimitiveType<U, Scalar>
369where
370	Self: Mul<Output = Self>,
371{
372	fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
373		iter.fold(Self::broadcast(Scalar::ONE), |result, next| result * next)
374	}
375}
376
377impl<'a, U: Underlier + Divisible<Scalar::Underlier>, Scalar: BinaryField> Product<&'a Self>
378	for PackedPrimitiveType<U, Scalar>
379where
380	Self: Mul<Output = Self>,
381{
382	fn product<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
383		iter.fold(Self::broadcast(Scalar::ONE), |result, next| result * *next)
384	}
385}
386
387impl<U: Underlier, Scalar: BinaryField> Mul<&Self> for PackedPrimitiveType<U, Scalar>
388where
389	Self: Mul<Output = Self>,
390{
391	type Output = Self;
392
393	#[inline]
394	fn mul(self, rhs: &Self) -> Self::Output {
395		self * *rhs
396	}
397}
398
399unsafe impl<U: Underlier + Zeroable, Scalar: BinaryField> Zeroable
400	for PackedPrimitiveType<U, Scalar>
401{
402}
403
404unsafe impl<U: Underlier + Pod, Scalar: BinaryField> Pod for PackedPrimitiveType<U, Scalar> {}
405
406impl<U, Scalar> FieldOps for PackedPrimitiveType<U, Scalar>
407where
408	Self: Square + InvertOrZero + Mul<Output = Self>,
409	U: Underlier + Divisible<Scalar::Underlier>,
410	Scalar: BinaryField,
411{
412	type Scalar = Scalar;
413
414	#[inline]
415	fn zero() -> Self {
416		Self::from_underlier(U::ZERO)
417	}
418
419	#[inline]
420	fn one() -> Self {
421		Self::broadcast(Scalar::ONE)
422	}
423
424	fn square_transpose<FSub: Field>(elems: &mut [Self])
425	where
426		Scalar: ExtensionField<FSub>,
427	{
428		let log_degree = <Scalar as ExtensionField<FSub>>::LOG_DEGREE;
429		let degree = <Scalar as ExtensionField<FSub>>::DEGREE;
430		assert_eq!(elems.len(), degree);
431
432		let log_sub_bits = Scalar::N_BITS.ilog2() as usize - log_degree;
433
434		// See Hacker's Delight, Section 7-3.
435		for i in 0..log_degree {
436			for j in 0..1 << (log_degree - i - 1) {
437				for k in 0..1 << i {
438					let idx0 = (j << (i + 1)) | k;
439					let idx1 = idx0 | (1 << i);
440					let (u0, u1) = elems[idx0].0.interleave(elems[idx1].0, i + log_sub_bits);
441					elems[idx0] = u0.into();
442					elems[idx1] = u1.into();
443				}
444			}
445		}
446	}
447}
448
449// A packed field divides into its scalars, mirroring how its underlier divides into the scalar's
450// underlier. This is the supertrait obligation behind `PackedField: Divisible<Self::Scalar>`.
451impl<U, Scalar> Divisible<Scalar> for PackedPrimitiveType<U, Scalar>
452where
453	U: Underlier + Divisible<Scalar::Underlier>,
454	Scalar: BinaryField,
455{
456	const LOG_N: usize = (U::BITS / Scalar::N_BITS).ilog2() as usize;
457
458	// Skipping ahead must not convert the lanes it jumps over.
459	#[inline]
460	fn value_iter(value: Self) -> impl ExactSizeIterator<Item = Scalar> + Send + Clone {
461		Divisible::<Scalar::Underlier>::value_iter(value.0).map_skippable(Scalar::from_underlier)
462	}
463
464	#[inline]
465	fn ref_iter(value: &Self) -> impl ExactSizeIterator<Item = Scalar> + Send + Clone + '_ {
466		Divisible::<Scalar::Underlier>::ref_iter(&value.0).map_skippable(Scalar::from_underlier)
467	}
468
469	#[inline]
470	fn slice_iter(slice: &[Self]) -> impl ExactSizeIterator<Item = Scalar> + Send + Clone + '_ {
471		Divisible::<Scalar::Underlier>::slice_iter(Self::to_underliers_ref(slice))
472			.map_skippable(Scalar::from_underlier)
473	}
474
475	#[inline]
476	unsafe fn get_unchecked(&self, index: usize) -> Scalar {
477		// Safety: `index < Self::N` by the caller's contract.
478		Scalar::from_underlier(unsafe {
479			Divisible::<Scalar::Underlier>::get_unchecked(&self.0, index)
480		})
481	}
482
483	#[inline]
484	unsafe fn set_unchecked(&mut self, index: usize, val: Scalar) {
485		// Safety: `index < Self::N` by the caller's contract.
486		unsafe { U::set_unchecked(&mut self.0, index, val.to_underlier()) };
487	}
488
489	#[inline]
490	fn broadcast(val: Scalar) -> Self {
491		U::broadcast(val.to_underlier()).into()
492	}
493
494	#[inline]
495	fn from_iter(iter: impl Iterator<Item = Scalar>) -> Self {
496		U::from_iter(iter.map(Scalar::to_underlier)).into()
497	}
498}
499
500// Lane masking lowers to a single bitwise AND against an all-ones/all-zeros per-lane mask, the same
501// operation the shift protocol previously performed by reaching into the underlier directly.
502impl<U, Scalar> Maskable<Scalar> for PackedPrimitiveType<U, Scalar>
503where
504	U: Underlier + Divisible<Scalar::Underlier>,
505	Scalar: BinaryField,
506{
507	type Mask = U;
508
509	#[inline]
510	fn make_mask(selectors: impl Iterator<Item = bool>) -> U {
511		// Build a per-lane all-ones/all-zeros sub-underlier for each scalar slot and pack into U.
512		U::from_iter(
513			selectors
514				.take(Self::N)
515				.map(|selected| Scalar::Underlier::broadcast(U1::from(selected))),
516		)
517	}
518
519	#[inline]
520	fn select(&self, mask: &U) -> Self {
521		Self::from_underlier(self.to_underlier() & *mask)
522	}
523}
524
525impl<U, Scalar> PackedField for PackedPrimitiveType<U, Scalar>
526where
527	Self:
528		Square + InvertOrZero + Mul<Output = Self> + WideMul<Output: Debug + Send + Sync + 'static>,
529	U: Underlier + Divisible<Scalar::Underlier>,
530	Scalar: BinaryField,
531{
532	#[inline]
533	fn interleave(self, other: Self, log_block_len: usize) -> (Self, Self) {
534		assert!(log_block_len < Self::LOG_WIDTH);
535		let log_bit_len = Self::Scalar::N_BITS.ilog2() as usize;
536		let (c, d) = self.0.interleave(other.0, log_block_len + log_bit_len);
537		(c.into(), d.into())
538	}
539
540	#[inline]
541	fn unzip(self, other: Self, log_block_len: usize) -> (Self, Self) {
542		assert!(log_block_len < Self::LOG_WIDTH);
543		let log_bit_len = Self::Scalar::N_BITS.ilog2() as usize;
544		let (c, d) = self.0.transpose(other.0, log_block_len + log_bit_len);
545		(c.into(), d.into())
546	}
547
548	#[inline]
549	fn from_fn(mut f: impl FnMut(usize) -> Self::Scalar) -> Self {
550		U::from_fn(move |i| f(i).to_underlier()).into()
551	}
552}
553
554impl<U: Underlier, Scalar: BinaryField> Distribution<PackedPrimitiveType<U, Scalar>>
555	for StandardUniform
556{
557	fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PackedPrimitiveType<U, Scalar> {
558		PackedPrimitiveType::from_underlier(U::random(rng))
559	}
560}