binius_field/
packed_extension.rs

1// Copyright 2023-2025 Irreducible Inc.
2
3use crate::{
4	as_packed_field::PackScalar,
5	underlier::{Divisible, WithUnderlier},
6	ExtensionField, Field, PackedField,
7};
8
9/// A [`PackedField`] that can be safely cast to indexable slices of scalars.
10///
11/// Not all packed fields can index individual scalar elements. Notably, packed fields of
12/// $\mathbb{F}_2$ elements can pack multiple scalars into a single byte.
13///
14///
15/// # Safety
16///
17/// In order for the above relation to be guaranteed, the memory representation of a slice of
18/// `PackedExtensionIndexable` elements must be the same as a slice of the underlying scalar
19/// elements, differing only in the slice lengths.
20pub unsafe trait PackedFieldIndexable: PackedField {
21	fn unpack_scalars(packed: &[Self]) -> &[Self::Scalar];
22	fn unpack_scalars_mut(packed: &mut [Self]) -> &mut [Self::Scalar];
23}
24
25unsafe impl<S, P> PackedFieldIndexable for P
26where
27	S: Field,
28	P: PackedDivisible<S, Scalar = S>,
29{
30	fn unpack_scalars(packed: &[Self]) -> &[Self::Scalar] {
31		P::divide(packed)
32	}
33
34	fn unpack_scalars_mut(packed: &mut [Self]) -> &mut [Self::Scalar] {
35		P::divide_mut(packed)
36	}
37}
38
39/// Check if `P` implements `PackedFieldIndexable`.
40/// This functions gets optimized out by the compiler so if it is used in a generic context
41/// as an `if` condition, the non-meaningful branch will be optimized out.
42#[inline(always)]
43#[allow(clippy::redundant_clone)]
44pub fn is_packed_field_indexable<P: PackedField>() -> bool {
45	// Use a hack that array of copyable elements won't call clone when the array is cloned.
46
47	struct X<T> {
48		cloned: bool,
49		_pd: std::marker::PhantomData<T>,
50	}
51
52	impl<T> Clone for X<T> {
53		fn clone(&self) -> Self {
54			Self {
55				cloned: true,
56				_pd: std::marker::PhantomData,
57			}
58		}
59	}
60
61	impl<T: PackedFieldIndexable> Copy for X<T> {}
62
63	let arr = [X::<P> {
64		cloned: false,
65		_pd: std::marker::PhantomData,
66	}];
67	let cloned = arr.clone();
68
69	!cloned[0].cloned
70}
71
72#[inline(always)]
73pub fn unpack_if_possible<P: PackedField, R>(
74	slice: &[P],
75	unpacked_fn: impl FnOnce(&[P::Scalar]) -> R,
76	fallback_fn: impl FnOnce(&[P]) -> R,
77) -> R {
78	if is_packed_field_indexable::<P>() {
79		let unpacked = unsafe {
80			std::slice::from_raw_parts(
81				slice.as_ptr() as *const P::Scalar,
82				slice.len() << P::LOG_WIDTH,
83			)
84		};
85		unpacked_fn(unpacked)
86	} else {
87		fallback_fn(slice)
88	}
89}
90
91#[inline(always)]
92pub fn unpack_if_possible_mut<P: PackedField, R>(
93	slice: &mut [P],
94	unpacked_fn: impl FnOnce(&mut [P::Scalar]) -> R,
95	fallback_fn: impl FnOnce(&mut [P]) -> R,
96) -> R {
97	if is_packed_field_indexable::<P>() {
98		let unpacked = unsafe {
99			std::slice::from_raw_parts_mut(
100				slice.as_mut_ptr() as *mut P::Scalar,
101				slice.len() << P::LOG_WIDTH,
102			)
103		};
104		unpacked_fn(unpacked)
105	} else {
106		fallback_fn(slice)
107	}
108}
109
110/// Trait represents a relationship between a packed struct of field elements and a packed struct
111/// of elements from an extension field.
112///
113/// This trait guarantees that one packed type has the same
114/// memory representation as the other, differing only in the scalar type and preserving the order
115/// of smaller elements.
116///
117/// This trait relation guarantees that the following iterators yield the same sequence of scalar
118/// elements:
119///
120/// ```
121/// use binius_field::{ExtensionField, PackedExtension, PackedField, Field};
122///
123/// fn ext_then_bases<'a, F, PE>(packed: &'a PE) -> impl Iterator<Item=F> + 'a
124///     where
125///         PE: PackedField<Scalar: ExtensionField<F>>,
126///         F: Field,
127/// {
128///     packed.iter().flat_map(|ext| ext.into_iter_bases())
129/// }
130///
131/// fn cast_then_iter<'a, F, PE>(packed: &'a PE) -> impl Iterator<Item=F> + 'a
132///     where
133///         PE: PackedExtension<F>,
134///         F: Field,
135/// {
136///     PE::cast_base_ref(packed).into_iter()
137/// }
138/// ```
139///
140/// # Safety
141///
142/// In order for the above relation to be guaranteed, the memory representation of
143/// `PackedExtensionField` element must be the same as a slice of the underlying `PackedField`
144/// element.
145pub trait PackedExtension<FS: Field>: PackedField<Scalar: ExtensionField<FS>> {
146	type PackedSubfield: PackedField<Scalar = FS>;
147
148	fn cast_bases(packed: &[Self]) -> &[Self::PackedSubfield];
149	fn cast_bases_mut(packed: &mut [Self]) -> &mut [Self::PackedSubfield];
150
151	fn cast_exts(packed: &[Self::PackedSubfield]) -> &[Self];
152	fn cast_exts_mut(packed: &mut [Self::PackedSubfield]) -> &mut [Self];
153
154	fn cast_base(self) -> Self::PackedSubfield;
155	fn cast_base_ref(&self) -> &Self::PackedSubfield;
156	fn cast_base_mut(&mut self) -> &mut Self::PackedSubfield;
157
158	fn cast_ext(base: Self::PackedSubfield) -> Self;
159	fn cast_ext_ref(base: &Self::PackedSubfield) -> &Self;
160	fn cast_ext_mut(base: &mut Self::PackedSubfield) -> &mut Self;
161
162	#[inline(always)]
163	fn cast_base_arr<const N: usize>(packed: [Self; N]) -> [Self::PackedSubfield; N] {
164		packed.map(Self::cast_base)
165	}
166
167	#[inline(always)]
168	fn cast_base_arr_ref<const N: usize>(packed: &[Self; N]) -> &[Self::PackedSubfield; N] {
169		Self::cast_bases(packed)
170			.try_into()
171			.expect("array has size N")
172	}
173
174	#[inline(always)]
175	fn cast_base_arr_mut<const N: usize>(packed: &mut [Self; N]) -> &mut [Self::PackedSubfield; N] {
176		Self::cast_bases_mut(packed)
177			.try_into()
178			.expect("array has size N")
179	}
180
181	#[inline(always)]
182	fn cast_ext_arr<const N: usize>(packed: [Self::PackedSubfield; N]) -> [Self; N] {
183		packed.map(Self::cast_ext)
184	}
185
186	#[inline(always)]
187	fn cast_ext_arr_ref<const N: usize>(packed: &[Self::PackedSubfield; N]) -> &[Self; N] {
188		Self::cast_exts(packed)
189			.try_into()
190			.expect("array has size N")
191	}
192
193	#[inline(always)]
194	fn cast_ext_arr_mut<const N: usize>(packed: &mut [Self::PackedSubfield; N]) -> &mut [Self; N] {
195		Self::cast_exts_mut(packed)
196			.try_into()
197			.expect("array has size N")
198	}
199}
200
201impl<PT, FS> PackedExtension<FS> for PT
202where
203	FS: Field,
204	PT: PackedField<Scalar: ExtensionField<FS>> + WithUnderlier<Underlier: PackScalar<FS>>,
205{
206	type PackedSubfield = <PT::Underlier as PackScalar<FS>>::Packed;
207
208	fn cast_bases(packed: &[Self]) -> &[Self::PackedSubfield] {
209		Self::PackedSubfield::from_underliers_ref(Self::to_underliers_ref(packed))
210	}
211
212	fn cast_bases_mut(packed: &mut [Self]) -> &mut [Self::PackedSubfield] {
213		Self::PackedSubfield::from_underliers_ref_mut(Self::to_underliers_ref_mut(packed))
214	}
215
216	fn cast_exts(base: &[Self::PackedSubfield]) -> &[Self] {
217		Self::from_underliers_ref(Self::PackedSubfield::to_underliers_ref(base))
218	}
219
220	fn cast_exts_mut(base: &mut [Self::PackedSubfield]) -> &mut [Self] {
221		Self::from_underliers_ref_mut(Self::PackedSubfield::to_underliers_ref_mut(base))
222	}
223
224	fn cast_base(self) -> Self::PackedSubfield {
225		Self::PackedSubfield::from_underlier(self.to_underlier())
226	}
227
228	fn cast_base_ref(&self) -> &Self::PackedSubfield {
229		Self::PackedSubfield::from_underlier_ref(self.to_underlier_ref())
230	}
231
232	fn cast_base_mut(&mut self) -> &mut Self::PackedSubfield {
233		Self::PackedSubfield::from_underlier_ref_mut(self.to_underlier_ref_mut())
234	}
235
236	fn cast_ext(base: Self::PackedSubfield) -> Self {
237		Self::from_underlier(base.to_underlier())
238	}
239
240	fn cast_ext_ref(base: &Self::PackedSubfield) -> &Self {
241		Self::from_underlier_ref(base.to_underlier_ref())
242	}
243
244	fn cast_ext_mut(base: &mut Self::PackedSubfield) -> &mut Self {
245		Self::from_underlier_ref_mut(base.to_underlier_ref_mut())
246	}
247}
248
249/// Convenient type alias that returns the packed field type for the scalar field `F` and packed
250/// extension `P`.
251pub type PackedSubfield<P, F> = <P as PackedExtension<F>>::PackedSubfield;
252
253/// Recast a packed field from one subfield of a packed extension to another.
254pub fn recast_packed<P, FSub1, FSub2>(elem: PackedSubfield<P, FSub1>) -> PackedSubfield<P, FSub2>
255where
256	P: PackedField + PackedExtension<FSub1> + PackedExtension<FSub2>,
257	P::Scalar: ExtensionField<FSub1> + ExtensionField<FSub2>,
258	FSub1: Field,
259	FSub2: Field,
260{
261	<P as PackedExtension<FSub2>>::cast_base(<P as PackedExtension<FSub1>>::cast_ext(elem))
262}
263
264/// Recast a slice of packed field elements from one subfield of a packed extension to another.
265pub fn recast_packed_slice<P, FSub1, FSub2>(
266	elems: &[PackedSubfield<P, FSub1>],
267) -> &[PackedSubfield<P, FSub2>]
268where
269	P: PackedField + PackedExtension<FSub1> + PackedExtension<FSub2>,
270	P::Scalar: ExtensionField<FSub1> + ExtensionField<FSub2>,
271	FSub1: Field,
272	FSub2: Field,
273{
274	<P as PackedExtension<FSub2>>::cast_bases(<P as PackedExtension<FSub1>>::cast_exts(elems))
275}
276
277/// Recast a mutable slice of packed field elements from one subfield of a packed extension to
278/// another.
279pub fn recast_packed_mut<P, FSub1, FSub2>(
280	elems: &mut [PackedSubfield<P, FSub1>],
281) -> &mut [PackedSubfield<P, FSub2>]
282where
283	P: PackedField + PackedExtension<FSub1> + PackedExtension<FSub2>,
284	P::Scalar: ExtensionField<FSub1> + ExtensionField<FSub2>,
285	FSub1: Field,
286	FSub2: Field,
287{
288	<P as PackedExtension<FSub2>>::cast_bases_mut(<P as PackedExtension<FSub1>>::cast_exts_mut(
289		elems,
290	))
291}
292
293/// This trait is a shorthand for the case `PackedExtension<P::Scalar, PackedSubfield = P>` which is a
294/// quite common case in our codebase.
295pub trait RepackedExtension<P: PackedField>:
296	PackedField<Scalar: ExtensionField<P::Scalar>> + PackedExtension<P::Scalar, PackedSubfield = P>
297{
298}
299
300impl<PT1, PT2> RepackedExtension<PT1> for PT2
301where
302	PT1: PackedField,
303	PT2: PackedExtension<PT1::Scalar, PackedSubfield = PT1, Scalar: ExtensionField<PT1::Scalar>>,
304{
305}
306
307/// This trait adds shortcut methods for the case `PackedExtension<F, PackedSubfield: PackedFieldIndexable>` which is a
308/// quite common case in our codebase.
309pub trait PackedExtensionIndexable<F: Field>:
310	PackedExtension<F, PackedSubfield: PackedFieldIndexable> + PackedField<Scalar: ExtensionField<F>>
311{
312	fn unpack_base_scalars(packed: &[Self]) -> &[F] {
313		Self::PackedSubfield::unpack_scalars(Self::cast_bases(packed))
314	}
315
316	fn unpack_base_scalars_mut(packed: &mut [Self]) -> &mut [F] {
317		Self::PackedSubfield::unpack_scalars_mut(Self::cast_bases_mut(packed))
318	}
319}
320
321impl<F, PT> PackedExtensionIndexable<F> for PT
322where
323	F: Field,
324	PT: PackedExtension<F, PackedSubfield: PackedFieldIndexable>,
325{
326}
327
328/// Trait represents a relationship between a packed struct of field elements and a smaller packed
329/// struct the same field elements.
330///
331/// This trait can be used to safely cast memory slices from larger packed fields to smaller ones.
332///
333/// # Safety
334///
335/// In order for the above relation to be guaranteed, the memory representation of a slice of
336/// `PackedDivisible` elements must be the same as a slice of the underlying `PackedField`
337/// elements, differing only in the slice lengths.
338pub unsafe trait PackedDivisible<P>: PackedField
339where
340	P: PackedField<Scalar = Self::Scalar>,
341{
342	fn divide(packed: &[Self]) -> &[P];
343	fn divide_mut(packed: &mut [Self]) -> &mut [P];
344}
345
346unsafe impl<PT1, PT2> PackedDivisible<PT2> for PT1
347where
348	PT2: PackedField + WithUnderlier,
349	PT1: PackedField<Scalar = PT2::Scalar> + WithUnderlier<Underlier: Divisible<PT2::Underlier>>,
350{
351	fn divide(packed: &[Self]) -> &[PT2] {
352		let underliers = PT1::to_underliers_ref(packed);
353		let underliers: &[PT2::Underlier] = PT1::Underlier::split_slice(underliers);
354		PT2::from_underliers_ref(underliers)
355	}
356
357	fn divide_mut(packed: &mut [Self]) -> &mut [PT2] {
358		let underliers = PT1::to_underliers_ref_mut(packed);
359		let underliers: &mut [PT2::Underlier] = PT1::Underlier::split_slice_mut(underliers);
360		PT2::from_underliers_ref_mut(underliers)
361	}
362}
363
364#[cfg(test)]
365mod tests {
366	use super::*;
367	use crate::{PackedBinaryField2x8b, PackedBinaryField8x2b};
368
369	#[test]
370	fn test_unpack_if_possible() {
371		let slice = [PackedBinaryField2x8b::zero(); 4];
372
373		let len = unpack_if_possible(&slice, |slice| slice.len(), |slice| slice.len());
374		assert_eq!(len, 8);
375
376		let slice = [PackedBinaryField8x2b::zero(); 4];
377		let len = unpack_if_possible(&slice, |slice| slice.len(), |slice| slice.len());
378		assert_eq!(len, 4);
379	}
380
381	#[test]
382	fn test_unpack_if_possible_mut() {
383		let mut slice = [PackedBinaryField2x8b::zero(); 4];
384
385		let len = unpack_if_possible_mut(&mut slice, |slice| slice.len(), |slice| slice.len());
386		assert_eq!(len, 8);
387
388		let mut slice = [PackedBinaryField8x2b::zero(); 4];
389		let len = unpack_if_possible_mut(&mut slice, |slice| slice.len(), |slice| slice.len());
390		assert_eq!(len, 4);
391	}
392}