Skip to main content

binius_field/arch/
strategies.rs

1// Copyright 2024-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use std::{
5	array,
6	iter::Sum,
7	marker::PhantomData,
8	ops::{Add, AddAssign, Sub, SubAssign},
9};
10
11use bytemuck::TransparentWrapper;
12
13use crate::{
14	BinaryField,
15	arch::PackedPrimitiveType,
16	arithmetic_traits::{InvertOrZero, Square, WideMul},
17	underlier::{Divisible, UnderlierType},
18};
19
20/// Strategy that splits the underlier into `SubU`-sized lanes, applies the sub-packing
21/// `PackedPrimitiveType<SubU, F>`'s op to each lane, and recombines — a generic fallback for
22/// packings that lack a specialized full-width [`Square`], [`InvertOrZero`], or [`WideMul`]. The
23/// sub-underlier `SubU` is a `PhantomData` parameter so the packing type `T` stays last for the
24/// macro's `Divide<SubU, $name, N>` form.
25///
26/// `N` is the lane count: callers always pass `N = <U as Divisible<SubU>>::N` (or the literal it
27/// works out to). `Square`/`InvertOrZero` stream through [`Divisible`] and ignore `N`, but it is
28/// still required so every `Divide` instantiation names its lane count explicitly. `WideMul` must
29/// defer reduction, so it materializes one unreduced product per lane in an `N`-element
30/// [`LaneWideProduct`] — and an associated const can't be an array length without
31/// `generic_const_exprs`, which is why `N` is a const generic rather than read from `Divisible`.
32#[repr(transparent)]
33#[derive(TransparentWrapper)]
34#[transparent(T)]
35pub struct Divide<SubU, T, const N: usize>(T, PhantomData<SubU>);
36
37impl<U, SubU, F, const N: usize> Square for Divide<SubU, PackedPrimitiveType<U, F>, N>
38where
39	U: UnderlierType + Divisible<SubU>,
40	SubU: UnderlierType,
41	F: BinaryField,
42	PackedPrimitiveType<SubU, F>: Square,
43{
44	#[inline]
45	fn square(self) -> Self {
46		let val = Self::peel(self);
47		let squared = Divisible::<SubU>::value_iter(val.to_underlier()).map(|lane| {
48			PackedPrimitiveType::<SubU, F>::from_underlier(lane)
49				.square()
50				.to_underlier()
51		});
52		Self::wrap(PackedPrimitiveType::from_underlier(Divisible::<SubU>::from_iter(squared)))
53	}
54}
55
56impl<U, SubU, F, const N: usize> InvertOrZero for Divide<SubU, PackedPrimitiveType<U, F>, N>
57where
58	U: UnderlierType + Divisible<SubU>,
59	SubU: UnderlierType,
60	F: BinaryField,
61	PackedPrimitiveType<SubU, F>: InvertOrZero,
62{
63	#[inline]
64	fn invert_or_zero(self) -> Self {
65		let val = Self::peel(self);
66		let inverted = Divisible::<SubU>::value_iter(val.to_underlier()).map(|lane| {
67			PackedPrimitiveType::<SubU, F>::from_underlier(lane)
68				.invert_or_zero()
69				.to_underlier()
70		});
71		Self::wrap(PackedPrimitiveType::from_underlier(Divisible::<SubU>::from_iter(inverted)))
72	}
73}
74
75/// One independent deferred wide product per `SubU` lane of a [`Divide`] widening multiply. Lanes
76/// accumulate (`Add`/`Sub`/`Sum`) and reduce independently, mirroring the packing structure, so a
77/// sum of products is reduced only once per lane. `N` is the lane count.
78#[derive(Clone, Copy, Debug)]
79pub struct LaneWideProduct<O, const N: usize>(pub [O; N]);
80
81impl<O: Copy + Default, const N: usize> Default for LaneWideProduct<O, N> {
82	#[inline]
83	fn default() -> Self {
84		Self([O::default(); N])
85	}
86}
87
88impl<O: Copy + Add<Output = O>, const N: usize> Add for LaneWideProduct<O, N> {
89	type Output = Self;
90
91	#[inline]
92	fn add(self, rhs: Self) -> Self {
93		Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
94	}
95}
96
97impl<O: Copy + Add<Output = O>, const N: usize> AddAssign for LaneWideProduct<O, N> {
98	#[inline]
99	fn add_assign(&mut self, rhs: Self) {
100		*self = *self + rhs;
101	}
102}
103
104impl<O: Copy + Sub<Output = O>, const N: usize> Sub for LaneWideProduct<O, N> {
105	type Output = Self;
106
107	#[inline]
108	fn sub(self, rhs: Self) -> Self {
109		Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
110	}
111}
112
113impl<O: Copy + Sub<Output = O>, const N: usize> SubAssign for LaneWideProduct<O, N> {
114	#[inline]
115	fn sub_assign(&mut self, rhs: Self) {
116		*self = *self - rhs;
117	}
118}
119
120impl<O: Copy + Default + Add<Output = O>, const N: usize> Sum for LaneWideProduct<O, N> {
121	#[inline]
122	fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
123		iter.fold(Self::default(), |acc, x| acc + x)
124	}
125}
126
127impl<U, SubU, F, const N: usize> WideMul for Divide<SubU, PackedPrimitiveType<U, F>, N>
128where
129	U: UnderlierType + Divisible<SubU>,
130	SubU: UnderlierType,
131	F: BinaryField,
132	PackedPrimitiveType<SubU, F>: WideMul,
133	<PackedPrimitiveType<SubU, F> as WideMul>::Output: Copy + Default,
134{
135	type Output = LaneWideProduct<<PackedPrimitiveType<SubU, F> as WideMul>::Output, N>;
136
137	#[inline]
138	fn wide_mul(a: Self, b: Self) -> Self::Output {
139		debug_assert_eq!(N, <U as Divisible<SubU>>::N, "N must equal Divisible<SubU>::N");
140
141		let a = Self::peel(a).to_underlier();
142		let b = Self::peel(b).to_underlier();
143
144		let mut lanes = [<PackedPrimitiveType<SubU, F> as WideMul>::Output::default(); N];
145		for (slot, (lhs, rhs)) in lanes
146			.iter_mut()
147			.zip(Divisible::<SubU>::value_iter(a).zip(Divisible::<SubU>::value_iter(b)))
148		{
149			*slot = <PackedPrimitiveType<SubU, F> as WideMul>::wide_mul(
150				PackedPrimitiveType::from_underlier(lhs),
151				PackedPrimitiveType::from_underlier(rhs),
152			);
153		}
154		LaneWideProduct(lanes)
155	}
156
157	#[inline]
158	fn reduce(wide: Self::Output) -> Self {
159		let lanes = wide.0.into_iter().map(|product| {
160			<PackedPrimitiveType<SubU, F> as WideMul>::reduce(product).to_underlier()
161		});
162		Self::wrap(PackedPrimitiveType::from_underlier(Divisible::<SubU>::from_iter(lanes)))
163	}
164}
165
166/// Wrapper that defines multiplication as `reduce(wide_mul(a, b))`, deferring to the type's own
167/// [`WideMul`] impl, making the widening multiply the single source of truth for both `Mul` and
168/// `WideMul`. Used by every GHASH and AES packing.
169#[repr(transparent)]
170#[derive(TransparentWrapper)]
171pub struct MulFromWideMul<T>(T);
172
173impl<P: WideMul> std::ops::Mul for MulFromWideMul<P> {
174	type Output = Self;
175
176	#[inline]
177	fn mul(self, rhs: Self) -> Self {
178		Self::wrap(P::reduce(P::wide_mul(Self::peel(self), Self::peel(rhs))))
179	}
180}