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, 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 SerializeBytes for M128 {
76	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
77		assert_enough_space_for(&write_buf, std::mem::size_of::<Self>())?;
78		write_buf.put_u128_le(self.0);
79		Ok(())
80	}
81}
82
83impl DeserializeBytes for M128 {
84	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
85	where
86		Self: Sized,
87	{
88		assert_enough_data_for(&read_buf, std::mem::size_of::<Self>())?;
89		Ok(Self(read_buf.get_u128_le()))
90	}
91}
92
93unsafe impl Zeroable for M128 {}
94
95unsafe impl Pod for M128 {}
96
97impl_divisible_memcast!(M128, u128, u64, u32, u16, u8);
98impl_divisible_bitmask!(M128, 1, 2, 4);
99impl_divisible_self!(M128);
100
101impl BitAnd for M128 {
102	type Output = Self;
103
104	#[inline(always)]
105	fn bitand(self, rhs: Self) -> Self::Output {
106		Self(self.0 & rhs.0)
107	}
108}
109
110impl BitAndAssign for M128 {
111	#[inline(always)]
112	fn bitand_assign(&mut self, rhs: Self) {
113		self.0 &= rhs.0;
114	}
115}
116
117impl BitOr for M128 {
118	type Output = Self;
119
120	#[inline(always)]
121	fn bitor(self, rhs: Self) -> Self::Output {
122		Self(self.0 | rhs.0)
123	}
124}
125
126impl BitOrAssign for M128 {
127	#[inline(always)]
128	fn bitor_assign(&mut self, rhs: Self) {
129		self.0 |= rhs.0;
130	}
131}
132
133impl BitXor for M128 {
134	type Output = Self;
135
136	#[inline(always)]
137	fn bitxor(self, rhs: Self) -> Self::Output {
138		Self(self.0 ^ rhs.0)
139	}
140}
141
142impl BitXorAssign for M128 {
143	#[inline(always)]
144	fn bitxor_assign(&mut self, rhs: Self) {
145		self.0 ^= rhs.0;
146	}
147}
148
149impl Not for M128 {
150	type Output = Self;
151
152	#[inline(always)]
153	fn not(self) -> Self::Output {
154		Self(!self.0)
155	}
156}
157
158impl Shl<usize> for M128 {
159	type Output = Self;
160
161	#[inline(always)]
162	fn shl(self, rhs: usize) -> Self::Output {
163		Self(self.0 << rhs)
164	}
165}
166
167impl Shr<usize> for M128 {
168	type Output = Self;
169
170	#[inline(always)]
171	fn shr(self, rhs: usize) -> Self::Output {
172		Self(self.0 >> rhs)
173	}
174}
175
176impl Distribution<M128> for StandardUniform {
177	#[inline]
178	fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> M128 {
179		M128(rng.random())
180	}
181}
182
183impl std::fmt::Display for M128 {
184	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185		write!(f, "{:032X}", self.0)
186	}
187}
188
189impl std::fmt::Debug for M128 {
190	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191		write!(f, "M128({self})")
192	}
193}
194
195impl std::fmt::LowerHex for M128 {
196	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197		std::fmt::LowerHex::fmt(&self.0, f)
198	}
199}
200
201impl UnderlierType for M128 {
202	const LOG_BITS: usize = 7;
203	const ZERO: Self = Self(0);
204	const ONE: Self = Self(1);
205	const ONES: Self = Self(u128::MAX);
206
207	#[inline(always)]
208	fn interleave(self, other: Self, log_block_len: usize) -> (Self, Self) {
209		let (a, b) = self.0.interleave(other.0, log_block_len);
210		(Self(a), Self(b))
211	}
212}
213
214impl<Scalar: BinaryField> From<u128> for PackedPrimitiveType<M128, Scalar> {
215	#[inline]
216	fn from(value: u128) -> Self {
217		Self::from(M128::from(value))
218	}
219}
220
221impl<Scalar: BinaryField> From<PackedPrimitiveType<M128, Scalar>> for u128 {
222	#[inline]
223	fn from(value: PackedPrimitiveType<M128, Scalar>) -> Self {
224		value.to_underlier().into()
225	}
226}