Skip to main content

binius_field/arch/portable/
m128.rs

1// Copyright 2026 The Binius Developers
2
3use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, Shr};
4
5use binius_utils::{
6	DeserializeBytes, SerializationError, SerializeBytes,
7	bytes::{Buf, BufMut},
8	serialization::{assert_enough_data_for, assert_enough_space_for},
9};
10use bytemuck::{Pod, Zeroable};
11use derive_more::{From, Into};
12use rand::{
13	distr::{Distribution, StandardUniform},
14	prelude::*,
15};
16
17use crate::{
18	BinaryField,
19	arch::portable::packed::PackedPrimitiveType,
20	underlier::{
21		Divisible, NumCast, SmallU, UnderlierType, impl_divisible_bitmask, impl_divisible_memcast,
22		impl_divisible_self,
23	},
24};
25
26/// 128-bit underlier for the portable build — a transparent wrapper over `u128`.
27///
28/// On x86_64/aarch64 `M128` is a SIMD register and on wasm32 (with `simd128`) a `v128`; here it is
29/// a plain `u128` newtype. Wrapping rather than aliasing `u128` keeps `M128` a distinct type on
30/// every target, so the `M128 <-> u128` conversions never collide with `u128`'s own reflexive
31/// impls and the architecture-gated `BinaryField128bGhash` conversions need no cfg gate.
32#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, From, Into)]
33#[repr(transparent)]
34pub struct M128(u128);
35
36impl M128 {
37	#[inline(always)]
38	pub const fn from_u128(value: u128) -> Self {
39		Self(value)
40	}
41}
42
43impl From<u64> for M128 {
44	#[inline(always)]
45	fn from(value: u64) -> Self {
46		Self(value as u128)
47	}
48}
49impl From<u32> for M128 {
50	#[inline(always)]
51	fn from(value: u32) -> Self {
52		Self(value as u128)
53	}
54}
55impl From<u16> for M128 {
56	#[inline(always)]
57	fn from(value: u16) -> Self {
58		Self(value as u128)
59	}
60}
61impl From<u8> for M128 {
62	#[inline(always)]
63	fn from(value: u8) -> Self {
64		Self(value as u128)
65	}
66}
67
68impl<const N: usize> From<SmallU<N>> for M128 {
69	#[inline(always)]
70	fn from(value: SmallU<N>) -> Self {
71		Self(value.val() as u128)
72	}
73}
74
75impl<U: NumCast<u128>> NumCast<M128> for U {
76	#[inline(always)]
77	fn num_cast_from(val: M128) -> Self {
78		Self::num_cast_from(val.0)
79	}
80}
81
82impl SerializeBytes for M128 {
83	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
84		assert_enough_space_for(&write_buf, std::mem::size_of::<Self>())?;
85		write_buf.put_u128_le(self.0);
86		Ok(())
87	}
88}
89
90impl DeserializeBytes for M128 {
91	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
92	where
93		Self: Sized,
94	{
95		assert_enough_data_for(&read_buf, std::mem::size_of::<Self>())?;
96		Ok(Self(read_buf.get_u128_le()))
97	}
98}
99
100unsafe impl Zeroable for M128 {}
101
102unsafe impl Pod for M128 {}
103
104impl_divisible_memcast!(M128, u128, u64, u32, u16, u8);
105impl_divisible_bitmask!(M128, 1, 2, 4);
106impl_divisible_self!(M128);
107
108impl BitAnd for M128 {
109	type Output = Self;
110
111	#[inline(always)]
112	fn bitand(self, rhs: Self) -> Self::Output {
113		Self(self.0 & rhs.0)
114	}
115}
116
117impl BitAndAssign for M128 {
118	#[inline(always)]
119	fn bitand_assign(&mut self, rhs: Self) {
120		self.0 &= rhs.0;
121	}
122}
123
124impl BitOr for M128 {
125	type Output = Self;
126
127	#[inline(always)]
128	fn bitor(self, rhs: Self) -> Self::Output {
129		Self(self.0 | rhs.0)
130	}
131}
132
133impl BitOrAssign for M128 {
134	#[inline(always)]
135	fn bitor_assign(&mut self, rhs: Self) {
136		self.0 |= rhs.0;
137	}
138}
139
140impl BitXor for M128 {
141	type Output = Self;
142
143	#[inline(always)]
144	fn bitxor(self, rhs: Self) -> Self::Output {
145		Self(self.0 ^ rhs.0)
146	}
147}
148
149impl BitXorAssign for M128 {
150	#[inline(always)]
151	fn bitxor_assign(&mut self, rhs: Self) {
152		self.0 ^= rhs.0;
153	}
154}
155
156impl Not for M128 {
157	type Output = Self;
158
159	#[inline(always)]
160	fn not(self) -> Self::Output {
161		Self(!self.0)
162	}
163}
164
165impl Shl<usize> for M128 {
166	type Output = Self;
167
168	#[inline(always)]
169	fn shl(self, rhs: usize) -> Self::Output {
170		Self(self.0 << rhs)
171	}
172}
173
174impl Shr<usize> for M128 {
175	type Output = Self;
176
177	#[inline(always)]
178	fn shr(self, rhs: usize) -> Self::Output {
179		Self(self.0 >> rhs)
180	}
181}
182
183impl Distribution<M128> for StandardUniform {
184	#[inline]
185	fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> M128 {
186		M128(rng.random())
187	}
188}
189
190impl std::fmt::Display for M128 {
191	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192		write!(f, "{:032X}", self.0)
193	}
194}
195
196impl std::fmt::Debug for M128 {
197	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198		write!(f, "M128({self})")
199	}
200}
201
202impl std::fmt::LowerHex for M128 {
203	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204		std::fmt::LowerHex::fmt(&self.0, f)
205	}
206}
207
208impl UnderlierType for M128 {
209	const LOG_BITS: usize = 7;
210	const ZERO: Self = Self(0);
211	const ONE: Self = Self(1);
212	const ONES: Self = Self(u128::MAX);
213
214	#[inline(always)]
215	fn interleave(self, other: Self, log_block_len: usize) -> (Self, Self) {
216		let (a, b) = self.0.interleave(other.0, log_block_len);
217		(Self(a), Self(b))
218	}
219}
220
221impl<Scalar: BinaryField> From<u128> for PackedPrimitiveType<M128, Scalar> {
222	#[inline]
223	fn from(value: u128) -> Self {
224		Self::from(M128::from(value))
225	}
226}
227
228impl<Scalar: BinaryField> From<PackedPrimitiveType<M128, Scalar>> for u128 {
229	#[inline]
230	fn from(value: PackedPrimitiveType<M128, Scalar>) -> Self {
231		value.to_underlier().into()
232	}
233}