p3_field/packed/packed_traits.rs
1use core::iter::{Product, Sum};
2use core::mem::MaybeUninit;
3use core::ops::{Div, DivAssign};
4use core::{array, slice};
5
6use crate::field::Field;
7use crate::{Algebra, BasedVectorSpace, ExtensionField, Powers, PrimeCharacteristicRing};
8
9/// A trait to constrain types that can be packed into a packed value.
10///
11/// The `Packable` trait allows us to specify implementations for potentially conflicting types.
12pub trait Packable: 'static + Default + Copy + Send + Sync + PartialEq + Eq {}
13
14/// A trait for array-like structs made up of multiple scalar elements.
15///
16/// # Safety
17/// - If `P` implements `PackedField` then `P` must be castable to/from `[P::Value; P::WIDTH]`
18/// without UB.
19pub unsafe trait PackedValue: 'static + Copy + Send + Sync {
20 /// The scalar type that is packed into this value.
21 type Value: Packable;
22
23 /// Number of scalar values packed together.
24 const WIDTH: usize;
25
26 /// Constructs a packed value using a function to generate each element.
27 ///
28 /// Similar to [`core::array::from_fn`].
29 #[must_use]
30 fn from_fn<F>(f: F) -> Self
31 where
32 F: FnMut(usize) -> Self::Value;
33
34 /// Create a packed value with all lanes set to the same scalar value.
35 #[inline]
36 #[must_use]
37 fn broadcast(value: Self::Value) -> Self {
38 Self::from_fn(|_| value)
39 }
40
41 /// Interprets a slice of scalar values as a packed value reference.
42 ///
43 /// # Panics:
44 /// This function will panic if `slice.len() != Self::WIDTH`
45 #[must_use]
46 fn from_slice(slice: &[Self::Value]) -> &Self;
47
48 /// Interprets a mutable slice of scalar values as a mutable packed value.
49 ///
50 /// # Panics:
51 /// This function will panic if `slice.len() != Self::WIDTH`
52 #[must_use]
53 fn from_slice_mut(slice: &mut [Self::Value]) -> &mut Self;
54
55 /// Returns the underlying scalar values as an immutable slice.
56 #[must_use]
57 fn as_slice(&self) -> &[Self::Value];
58
59 /// Returns the underlying scalar values as a mutable slice.
60 #[must_use]
61 fn as_slice_mut(&mut self) -> &mut [Self::Value];
62
63 /// Extract the scalar value at the given SIMD lane.
64 ///
65 /// This is equivalent to `self.as_slice()[lane]` but more explicit about the
66 /// SIMD extraction semantics.
67 #[inline]
68 #[must_use]
69 fn extract(&self, lane: usize) -> Self::Value {
70 self.as_slice()[lane]
71 }
72
73 /// Packs a slice of scalar values into a slice of packed values.
74 ///
75 /// # Panics
76 /// Panics if the slice length is not divisible by `WIDTH`.
77 #[inline]
78 #[must_use]
79 fn pack_slice(buf: &[Self::Value]) -> &[Self] {
80 // Sources vary, but this should be true on all platforms we care about.
81 const {
82 assert!(align_of::<Self>() <= align_of::<Self::Value>());
83 }
84 assert!(
85 buf.len().is_multiple_of(Self::WIDTH),
86 "Slice length (got {}) must be a multiple of packed field width ({}).",
87 buf.len(),
88 Self::WIDTH
89 );
90 let buf_ptr = buf.as_ptr().cast::<Self>();
91 let n = buf.len() / Self::WIDTH;
92 unsafe { slice::from_raw_parts(buf_ptr, n) }
93 }
94
95 /// Converts a mutable slice of scalar values into a mutable slice of packed values.
96 ///
97 /// # Panics
98 /// Panics if the slice length is not divisible by `WIDTH`.
99 #[inline]
100 #[must_use]
101 fn pack_slice_mut(buf: &mut [Self::Value]) -> &mut [Self] {
102 const {
103 assert!(align_of::<Self>() <= align_of::<Self::Value>());
104 }
105 assert!(
106 buf.len().is_multiple_of(Self::WIDTH),
107 "Slice length (got {}) must be a multiple of packed field width ({}).",
108 buf.len(),
109 Self::WIDTH
110 );
111 let buf_ptr = buf.as_mut_ptr().cast::<Self>();
112 let n = buf.len() / Self::WIDTH;
113 unsafe { slice::from_raw_parts_mut(buf_ptr, n) }
114 }
115
116 /// Converts a mutable slice of possibly uninitialized scalar values into
117 /// a mutable slice of possibly uninitialized packed values.
118 ///
119 /// # Panics
120 /// Panics if the slice length is not divisible by `WIDTH`.
121 #[inline]
122 #[must_use]
123 fn pack_maybe_uninit_slice_mut(
124 buf: &mut [MaybeUninit<Self::Value>],
125 ) -> &mut [MaybeUninit<Self>] {
126 const {
127 assert!(align_of::<Self>() <= align_of::<Self::Value>());
128 }
129 assert!(
130 buf.len().is_multiple_of(Self::WIDTH),
131 "Slice length (got {}) must be a multiple of packed field width ({}).",
132 buf.len(),
133 Self::WIDTH
134 );
135 let buf_ptr = buf.as_mut_ptr().cast::<MaybeUninit<Self>>();
136 let n = buf.len() / Self::WIDTH;
137 unsafe { slice::from_raw_parts_mut(buf_ptr, n) }
138 }
139
140 /// Packs a slice into packed values and returns the packed portion and any remaining suffix.
141 #[inline]
142 #[must_use]
143 fn pack_slice_with_suffix(buf: &[Self::Value]) -> (&[Self], &[Self::Value]) {
144 let (packed, suffix) = buf.split_at(buf.len() - buf.len() % Self::WIDTH);
145 (Self::pack_slice(packed), suffix)
146 }
147
148 /// Converts a mutable slice of scalar values into a pair:
149 /// - a slice of packed values covering the largest aligned portion,
150 /// - and a remainder slice of scalar values that couldn't be packed.
151 #[inline]
152 #[must_use]
153 fn pack_slice_with_suffix_mut(buf: &mut [Self::Value]) -> (&mut [Self], &mut [Self::Value]) {
154 let (packed, suffix) = buf.split_at_mut(buf.len() - buf.len() % Self::WIDTH);
155 (Self::pack_slice_mut(packed), suffix)
156 }
157
158 /// Converts a mutable slice of possibly uninitialized scalar values into a pair:
159 /// - a slice of possibly uninitialized packed values covering the largest aligned portion,
160 /// - and a remainder slice of possibly uninitialized scalar values that couldn't be packed.
161 #[inline]
162 #[must_use]
163 fn pack_maybe_uninit_slice_with_suffix_mut(
164 buf: &mut [MaybeUninit<Self::Value>],
165 ) -> (&mut [MaybeUninit<Self>], &mut [MaybeUninit<Self::Value>]) {
166 let (packed, suffix) = buf.split_at_mut(buf.len() - buf.len() % Self::WIDTH);
167 (Self::pack_maybe_uninit_slice_mut(packed), suffix)
168 }
169
170 /// Reinterprets a slice of packed values as a flat slice of scalar values.
171 ///
172 /// Each packed value contains `Self::WIDTH` scalar values, which are laid out
173 /// contiguously in memory. This function allows direct access to those scalars.
174 #[inline]
175 #[must_use]
176 fn unpack_slice(buf: &[Self]) -> &[Self::Value] {
177 const {
178 assert!(align_of::<Self>() >= align_of::<Self::Value>());
179 }
180 let buf_ptr = buf.as_ptr().cast::<Self::Value>();
181 let n = buf.len() * Self::WIDTH;
182 unsafe { slice::from_raw_parts(buf_ptr, n) }
183 }
184
185 /// Pack columns from `WIDTH` rows of scalar values into `N` packed values.
186 ///
187 /// Given `WIDTH` rows of `N` scalar values, extract each column and pack it
188 /// into a single packed value. This is the inverse of `unpack_into`.
189 ///
190 /// ## Panics
191 /// Panics if `rows.len() != WIDTH`.
192 #[inline]
193 #[must_use]
194 fn pack_columns<const N: usize>(rows: &[[Self::Value; N]]) -> [Self; N] {
195 assert_eq!(rows.len(), Self::WIDTH);
196 array::from_fn(|col| Self::from_fn(|lane| rows[lane][col]))
197 }
198
199 /// Pack columns using a closure that provides each row's data.
200 ///
201 /// Calls `row_fn(lane)` for each lane `0..WIDTH` to get `[Self::Value; N]`,
202 /// then transposes columns into packed values. Useful when rows aren't
203 /// contiguous in memory (e.g., strided access).
204 #[inline]
205 #[must_use]
206 fn pack_columns_fn<const N: usize>(row_fn: impl Fn(usize) -> [Self::Value; N]) -> [Self; N] {
207 array::from_fn(|col| Self::from_fn(|lane| row_fn(lane)[col]))
208 }
209
210 /// Unpack `N` packed values into `WIDTH` rows of `N` scalars.
211 ///
212 /// ## Inputs
213 /// - `packed`: An array of `N` packed values.
214 /// - `rows`: A mutable slice of exactly `WIDTH` arrays to write the unpacked values.
215 ///
216 /// ## Panics
217 /// Panics if `rows.len() != WIDTH`.
218 #[inline]
219 fn unpack_into<const N: usize>(packed: &[Self; N], rows: &mut [[Self::Value; N]]) {
220 assert_eq!(rows.len(), Self::WIDTH);
221 for (lane, row) in rows.iter_mut().enumerate() {
222 *row = array::from_fn(|col| packed[col].extract(lane));
223 }
224 }
225
226 /// Unpack `N` packed values into an iterator of `WIDTH` rows.
227 ///
228 /// This is the iterator equivalent of `unpack_into`, yielding each row
229 /// without requiring a pre-allocated buffer.
230 #[inline]
231 fn unpack_iter<const N: usize>(packed: [Self; N]) -> impl Iterator<Item = [Self::Value; N]> {
232 (0..Self::WIDTH).map(move |lane| array::from_fn(|col| packed[col].extract(lane)))
233 }
234}
235
236unsafe impl<T: Packable, const WIDTH: usize> PackedValue for [T; WIDTH] {
237 type Value = T;
238 const WIDTH: usize = WIDTH;
239
240 #[inline]
241 fn from_slice(slice: &[Self::Value]) -> &Self {
242 assert_eq!(slice.len(), Self::WIDTH);
243 unsafe { &*slice.as_ptr().cast() }
244 }
245
246 #[inline]
247 fn from_slice_mut(slice: &mut [Self::Value]) -> &mut Self {
248 assert_eq!(slice.len(), Self::WIDTH);
249 unsafe { &mut *slice.as_mut_ptr().cast() }
250 }
251
252 #[inline]
253 fn from_fn<Fn>(f: Fn) -> Self
254 where
255 Fn: FnMut(usize) -> Self::Value,
256 {
257 core::array::from_fn(f)
258 }
259
260 #[inline]
261 fn as_slice(&self) -> &[Self::Value] {
262 self
263 }
264
265 #[inline]
266 fn as_slice_mut(&mut self) -> &mut [Self::Value] {
267 self
268 }
269}
270
271/// An array of field elements which can be packed into a vector for SIMD operations.
272///
273/// # Safety
274/// - See `PackedValue` above.
275pub unsafe trait PackedField:
276 Algebra<Self::Scalar>
277 + PackedValue<Value = Self::Scalar>
278 + Div<Self, Output = Self>
279 + Div<Self::Scalar, Output = Self>
280 + DivAssign<Self>
281 + DivAssign<Self::Scalar>
282 + Sum<Self::Scalar>
283 + Product<Self::Scalar>
284{
285 type Scalar: Field;
286
287 /// Construct an iterator which returns powers of `base` packed into packed field elements.
288 ///
289 /// E.g. if `Self::WIDTH = 4`, returns: `[base^0, base^1, base^2, base^3], [base^4, base^5, base^6, base^7], ...`.
290 #[must_use]
291 fn packed_powers(base: Self::Scalar) -> Powers<Self> {
292 Self::packed_shifted_powers(base, Self::Scalar::ONE)
293 }
294
295 /// Construct an iterator which returns powers of `base` multiplied by `start` and packed into packed field elements.
296 ///
297 /// E.g. if `Self::WIDTH = 4`, returns: `[start, start*base, start*base^2, start*base^3], [start*base^4, start*base^5, start*base^6, start*base^7], ...`.
298 #[must_use]
299 fn packed_shifted_powers(base: Self::Scalar, start: Self::Scalar) -> Powers<Self> {
300 let mut current: Self = start.into();
301 let slice = current.as_slice_mut();
302 for i in 1..Self::WIDTH {
303 slice[i] = slice[i - 1] * base;
304 }
305
306 Powers {
307 base: base.exp_u64(Self::WIDTH as u64).into(),
308 current,
309 }
310 }
311
312 /// Accumulate the products of `d` coefficient streams against a shared stream of
313 /// packed base values.
314 ///
315 /// For each `(coeffs, base)` pair produced by the iterator, performs
316 /// `acc[k] += coeffs[k] * base` for all `k < d`, and returns the accumulators
317 /// (entries at `d..` are zero). Coefficient slices shorter than `d` only
318 /// contribute their available entries.
319 ///
320 /// This is the inner kernel of mixed base-times-extension dot products, where each
321 /// extension element contributes `d` base-field coefficient words. Implementations
322 /// may override it to defer modular reductions across iterations.
323 ///
324 /// # Panics
325 /// Debug builds panic if `d > 8`.
326 #[must_use]
327 fn coeffwise_dot_product<'a, I>(d: usize, pairs: I) -> [Self; 8]
328 where
329 Self: 'a,
330 I: Iterator<Item = (&'a [Self], Self)>,
331 {
332 debug_assert!(d <= 8, "Extension degree > 8 not supported");
333 let mut acc = [Self::ZERO; 8];
334 for (coeffs, base) in pairs {
335 for (acc_k, &coeff) in acc[..d].iter_mut().zip(coeffs) {
336 *acc_k += coeff * base;
337 }
338 }
339 acc
340 }
341}
342
343/// # Safety
344/// - `WIDTH` is assumed to be a power of 2.
345pub unsafe trait PackedFieldPow2: PackedField {
346 /// Take interpret two vectors as chunks of `block_len` elements. Unpack and interleave those
347 /// chunks. This is best seen with an example. If we have:
348 /// ```text
349 /// A = [x0, y0, x1, y1]
350 /// B = [x2, y2, x3, y3]
351 /// ```
352 ///
353 /// then
354 ///
355 /// ```text
356 /// interleave(A, B, 1) = ([x0, x2, x1, x3], [y0, y2, y1, y3])
357 /// ```
358 ///
359 /// Pairs that were adjacent in the input are at corresponding positions in the output.
360 ///
361 /// `r` lets us set the size of chunks we're interleaving. If we set `block_len = 2`, then for
362 ///
363 /// ```text
364 /// A = [x0, x1, y0, y1]
365 /// B = [x2, x3, y2, y3]
366 /// ```
367 ///
368 /// we obtain
369 ///
370 /// ```text
371 /// interleave(A, B, block_len) = ([x0, x1, x2, x3], [y0, y1, y2, y3])
372 /// ```
373 ///
374 /// We can also think about this as stacking the vectors, dividing them into 2x2 matrices, and
375 /// transposing those matrices.
376 ///
377 /// When `block_len = WIDTH`, this operation is a no-op.
378 ///
379 /// # Panics
380 /// This may panic if `block_len` does not divide `WIDTH`. Since `WIDTH` is specified to be a power of 2,
381 /// `block_len` must also be a power of 2. It cannot be 0 and it cannot exceed `WIDTH`.
382 #[must_use]
383 fn interleave(&self, other: Self, block_len: usize) -> (Self, Self);
384}
385
386/// Fix a field `F` a packing width `W` and an extension field `EF` of `F`.
387///
388/// By choosing a basis `B`, `EF` can be transformed into an array `[F; D]`.
389///
390/// A type should implement PackedFieldExtension if it can be transformed into `[F::Packing; D] ~ [[F; W]; D]`
391///
392/// This is interpreted by taking a transpose to get `[[F; D]; W]` which can then be reinterpreted
393/// as `[EF; W]` by making use of the chosen basis `B` again.
394pub trait PackedFieldExtension<
395 BaseField: Field,
396 ExtField: ExtensionField<BaseField, ExtensionPacking = Self>,
397>: Algebra<ExtField> + Algebra<BaseField::Packing> + BasedVectorSpace<BaseField::Packing>
398{
399 /// Construct a packed extension by applying `f` to each lane.
400 ///
401 /// This is the extension-field analog of [`PackedValue::from_fn`] and the canonical
402 /// primitive constructor for packed extensions: every other constructor in this
403 /// trait (`from_ext_slice`, `pack_ext_columns`, etc.) routes through it.
404 ///
405 /// `f` is called once per `(basis_coefficient, lane)` pair (`D * W` calls total),
406 /// hence the [`Fn`] bound — closures with side effects are unsuitable.
407 ///
408 /// The default impl uses only the [`BasedVectorSpace`] machinery the trait already
409 /// requires. Concrete impls should override when the extension struct exposes its
410 /// base packings directly, e.g. `Self::new(F::Packing::pack_columns_fn(|l| f(l).value))`.
411 #[inline]
412 #[must_use]
413 fn from_ext_fn(f: impl Fn(usize) -> ExtField) -> Self {
414 Self::from_basis_coefficients_fn(|d| {
415 BaseField::Packing::from_fn(|lane| f(lane).as_basis_coefficients_slice()[d])
416 })
417 }
418
419 /// Pack a length-`WIDTH` slice of extension field elements into one packed extension.
420 ///
421 /// ## Panics
422 /// Panics if `slice.len() != BaseField::Packing::WIDTH`.
423 #[inline]
424 #[must_use]
425 fn from_ext_slice(slice: &[ExtField]) -> Self {
426 assert_eq!(slice.len(), BaseField::Packing::WIDTH);
427 Self::from_ext_fn(|lane| slice[lane])
428 }
429
430 /// Pack `N` columns from `W` rows of extension field elements into `N` packed extensions.
431 ///
432 /// This is the extension-field analog of [`PackedValue::pack_columns`]: given `W` rows
433 /// of `N` extension elements, lane `lane` of output column `col` is `rows[lane][col]`.
434 ///
435 /// ## Panics
436 /// Panics if `rows.len() != BaseField::Packing::WIDTH`.
437 #[inline]
438 #[must_use]
439 fn pack_ext_columns<const N: usize>(rows: &[[ExtField; N]]) -> [Self; N] {
440 assert_eq!(rows.len(), BaseField::Packing::WIDTH);
441 array::from_fn(|col| Self::from_ext_fn(|lane| rows[lane][col]))
442 }
443
444 /// Pack `N` columns using a closure that produces each row.
445 ///
446 /// Analog of [`PackedValue::pack_columns_fn`].
447 #[inline]
448 #[must_use]
449 fn pack_ext_columns_fn<const N: usize>(row_fn: impl Fn(usize) -> [ExtField; N]) -> [Self; N] {
450 array::from_fn(|col| Self::from_ext_fn(|lane| row_fn(lane)[col]))
451 }
452
453 /// Extract the extension field element at the given SIMD lane.
454 #[inline]
455 #[must_use]
456 fn extract(&self, lane: usize) -> ExtField {
457 ExtField::from_basis_coefficients_fn(|d| {
458 self.as_basis_coefficients_slice()[d].as_slice()[lane]
459 })
460 }
461
462 /// Write all `W` lanes into the given slice.
463 ///
464 /// This is the extension-field analog of [`PackedValue::as_slice`], but the lanes of
465 /// a packed extension are not contiguous in memory (the layout is `[[F; W]; D]`,
466 /// indexed first by basis coefficient), so the lanes must be copied rather than
467 /// borrowed.
468 ///
469 /// ## Panics
470 /// Panics if `out.len() != BaseField::Packing::WIDTH`.
471 #[inline]
472 fn to_ext_slice(&self, out: &mut [ExtField]) {
473 assert_eq!(out.len(), BaseField::Packing::WIDTH);
474 for (lane, slot) in out.iter_mut().enumerate() {
475 *slot = self.extract(lane);
476 }
477 }
478
479 /// Unpack `N` packed extensions into `W` rows of `N` extension elements.
480 ///
481 /// Inverse of [`PackedFieldExtension::pack_ext_columns`]. Lane `lane` of input
482 /// column `col` is written to `rows[lane][col]`.
483 ///
484 /// ## Panics
485 /// Panics if `rows.len() != BaseField::Packing::WIDTH`.
486 #[inline]
487 fn unpack_ext_into<const N: usize>(packed: &[Self; N], rows: &mut [[ExtField; N]]) {
488 assert_eq!(rows.len(), BaseField::Packing::WIDTH);
489 for (lane, row) in rows.iter_mut().enumerate() {
490 *row = array::from_fn(|col| {
491 ExtField::from_basis_coefficients_fn(|d| {
492 packed[col].as_basis_coefficients_slice()[d].as_slice()[lane]
493 })
494 });
495 }
496 }
497
498 /// Iterator equivalent of [`PackedFieldExtension::unpack_ext_into`].
499 ///
500 /// Yields `WIDTH` rows of `N` extension elements without requiring a pre-allocated
501 /// buffer. Analog of [`PackedValue::unpack_iter`].
502 #[inline]
503 fn unpack_ext_iter<const N: usize>(packed: [Self; N]) -> impl Iterator<Item = [ExtField; N]> {
504 (0..BaseField::Packing::WIDTH).map(move |lane| {
505 array::from_fn(|col| {
506 ExtField::from_basis_coefficients_fn(|d| {
507 packed[col].as_basis_coefficients_slice()[d].as_slice()[lane]
508 })
509 })
510 })
511 }
512
513 /// Convert an iterator of packed extension field elements to an iterator of
514 /// extension field elements (flat — one `ExtField` per lane per packed value).
515 #[inline]
516 #[must_use]
517 fn to_ext_iter(iter: impl IntoIterator<Item = Self>) -> impl Iterator<Item = ExtField> {
518 iter.into_iter()
519 .flat_map(|x| (0..BaseField::Packing::WIDTH).map(move |lane| x.extract(lane)))
520 }
521
522 /// Similar to `packed_powers`, construct an iterator which returns
523 /// powers of `base` packed into `PackedFieldExtension` elements.
524 #[must_use]
525 fn packed_ext_powers(base: ExtField) -> Powers<Self>;
526
527 /// Similar to `packed_ext_powers` but only returns `unpacked_len` powers of `base`.
528 ///
529 /// Note that the length of the returned iterator will be `unpacked_len / WIDTH` and
530 /// not `len` as the iterator is over packed extension field elements. If `unpacked_len`
531 /// is not divisible by `WIDTH`, `unpacked_len` will be rounded up to the next multiple of `WIDTH`.
532 #[must_use]
533 fn packed_ext_powers_capped(base: ExtField, unpacked_len: usize) -> impl Iterator<Item = Self> {
534 Self::packed_ext_powers(base).take(unpacked_len.div_ceil(BaseField::Packing::WIDTH))
535 }
536}
537
538unsafe impl<T: Packable> PackedValue for T {
539 type Value = Self;
540
541 const WIDTH: usize = 1;
542
543 #[inline]
544 fn from_slice(slice: &[Self::Value]) -> &Self {
545 assert_eq!(slice.len(), Self::WIDTH);
546 &slice[0]
547 }
548
549 #[inline]
550 fn from_slice_mut(slice: &mut [Self::Value]) -> &mut Self {
551 assert_eq!(slice.len(), Self::WIDTH);
552 &mut slice[0]
553 }
554
555 #[inline]
556 fn from_fn<Fn>(mut f: Fn) -> Self
557 where
558 Fn: FnMut(usize) -> Self::Value,
559 {
560 f(0)
561 }
562
563 #[inline]
564 fn as_slice(&self) -> &[Self::Value] {
565 slice::from_ref(self)
566 }
567
568 #[inline]
569 fn as_slice_mut(&mut self) -> &mut [Self::Value] {
570 slice::from_mut(self)
571 }
572}
573
574unsafe impl<F: Field> PackedField for F {
575 type Scalar = Self;
576}
577
578unsafe impl<F: Field> PackedFieldPow2 for F {
579 #[inline]
580 fn interleave(&self, other: Self, block_len: usize) -> (Self, Self) {
581 match block_len {
582 1 => (*self, other),
583 _ => panic!("unsupported block length"),
584 }
585 }
586}
587
588impl<F: Field> PackedFieldExtension<F, F> for F::Packing {
589 #[inline]
590 fn from_ext_fn(f: impl Fn(usize) -> F) -> Self {
591 F::Packing::from_fn(f)
592 }
593
594 #[inline]
595 fn from_ext_slice(slice: &[F]) -> Self {
596 *F::Packing::from_slice(slice)
597 }
598
599 #[inline]
600 fn packed_ext_powers(base: F) -> Powers<Self> {
601 F::Packing::packed_powers(base)
602 }
603}
604
605impl Packable for u8 {}
606
607impl Packable for u16 {}
608
609impl Packable for u32 {}
610
611impl Packable for u64 {}
612
613impl Packable for u128 {}