implement carryless_mul

This commit is contained in:
Folkert de Vries 2026-02-04 15:55:08 +01:00
parent 5d04477ea8
commit b935f379b4
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
15 changed files with 569 additions and 5 deletions

View file

@ -218,3 +218,38 @@ macro_rules! impl_funnel_shifts {
impl_funnel_shifts! {
u8, u16, u32, u64, u128, usize
}
#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")]
pub const trait CarrylessMul: Copy + 'static {
/// See [`super::carryless_mul`]; we just need the trait indirection to handle
/// different types since calling intrinsics with generics doesn't work.
fn carryless_mul(self, rhs: Self) -> Self;
}
macro_rules! impl_carryless_mul{
($($type:ident),*) => {$(
#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")]
impl const CarrylessMul for $type {
#[inline]
fn carryless_mul(self, rhs: Self) -> Self {
let mut result = 0;
let mut i = 0;
while i < $type::BITS {
// If the i-th bit in rhs is set.
if (rhs >> i) & 1 != 0 {
// Then xor the result with `self` shifted to the left by i positions.
result ^= self << i;
}
i += 1;
}
result
}
}
)*};
}
impl_carryless_mul! {
u8, u16, u32, u64, u128, usize
}

View file

@ -2178,6 +2178,19 @@ pub const unsafe fn unchecked_funnel_shr<T: [const] fallback::FunnelShift>(
unsafe { a.unchecked_funnel_shr(b, shift) }
}
/// Carryless multiply.
///
/// Safe versions of this intrinsic are available on the integer primitives
/// via the `carryless_mul` method. For example, [`u32::carryless_mul`].
#[rustc_intrinsic]
#[rustc_nounwind]
#[rustc_const_unstable(feature = "uint_carryless_mul", issue = "152080")]
#[unstable(feature = "uint_carryless_mul", issue = "152080")]
#[miri::intrinsic_fallback_is_spec]
pub const fn carryless_mul<T: [const] fallback::CarrylessMul>(a: T, b: T) -> T {
a.carryless_mul(b)
}
/// This is an implementation detail of [`crate::ptr::read`] and should
/// not be used anywhere else. See its comments for why this exists.
///

View file

@ -162,6 +162,18 @@ pub const unsafe fn simd_funnel_shl<T>(a: T, b: T, shift: T) -> T;
#[rustc_nounwind]
pub const unsafe fn simd_funnel_shr<T>(a: T, b: T, shift: T) -> T;
/// Compute the carry-less product.
///
/// This is similar to long multiplication except that the carry is discarded.
///
/// This operation can be used to model multiplication in `GF(2)[X]`, the polynomial
/// ring over `GF(2)`.
///
/// `T` must be a vector of integers.
#[rustc_intrinsic]
#[rustc_nounwind]
pub unsafe fn simd_carryless_mul<T>(a: T, b: T) -> T;
/// "And"s vectors elementwise.
///
/// `T` must be a vector of integers.

View file

@ -170,6 +170,7 @@
#![feature(trait_alias)]
#![feature(transparent_unions)]
#![feature(try_blocks)]
#![feature(uint_carryless_mul)]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
#![feature(with_negative_coherence)]

View file

@ -244,6 +244,104 @@ macro_rules! midpoint_impl {
};
}
macro_rules! widening_carryless_mul_impl {
($SelfT:ty, $WideT:ty) => {
/// Performs a widening carry-less multiplication.
///
/// # Examples
///
/// ```
/// #![feature(uint_carryless_mul)]
///
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.widening_carryless_mul(",
stringify!($SelfT), "::MAX), ", stringify!($WideT), "::MAX / 3);")]
/// ```
#[rustc_const_unstable(feature = "uint_carryless_mul", issue = "152080")]
#[doc(alias = "clmul")]
#[unstable(feature = "uint_carryless_mul", issue = "152080")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
pub const fn widening_carryless_mul(self, rhs: $SelfT) -> $WideT {
(self as $WideT).carryless_mul(rhs as $WideT)
}
}
}
macro_rules! carrying_carryless_mul_impl {
(u128, u256) => {
carrying_carryless_mul_impl! { @internal u128 =>
pub const fn carrying_carryless_mul(self, rhs: Self, carry: Self) -> (Self, Self) {
let x0 = self as u64;
let x1 = (self >> 64) as u64;
let y0 = rhs as u64;
let y1 = (rhs >> 64) as u64;
let z0 = u64::widening_carryless_mul(x0, y0);
let z2 = u64::widening_carryless_mul(x1, y1);
// The grade school algorithm would compute:
// z1 = x0y1 ^ x1y0
// Instead, Karatsuba first computes:
let z3 = u64::widening_carryless_mul(x0 ^ x1, y0 ^ y1);
// Since it distributes over XOR,
// z3 == x0y0 ^ x0y1 ^ x1y0 ^ x1y1
// |--| |---------| |--|
// == z0 ^ z1 ^ z2
// so we can compute z1 as
let z1 = z3 ^ z0 ^ z2;
let lo = z0 ^ (z1 << 64);
let hi = z2 ^ (z1 >> 64);
(lo ^ carry, hi)
}
}
};
($SelfT:ty, $WideT:ty) => {
carrying_carryless_mul_impl! { @internal $SelfT =>
pub const fn carrying_carryless_mul(self, rhs: Self, carry: Self) -> (Self, Self) {
// Can't use widening_carryless_mul because it's not implemented for usize.
let p = (self as $WideT).carryless_mul(rhs as $WideT);
let lo = (p as $SelfT);
let hi = (p >> Self::BITS) as $SelfT;
(lo ^ carry, hi)
}
}
};
(@internal $SelfT:ty => $($fn:tt)*) => {
/// Calculates the "full carryless multiplication" without the possibility to overflow.
///
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
/// of the result as two separate values, in that order.
///
/// # Examples
///
/// Please note that this example is shared among integer types, which is why `u8` is used.
///
/// ```
/// #![feature(uint_carryless_mul)]
///
/// assert_eq!(0b1000_0000u8.carrying_carryless_mul(0b1000_0000, 0b0000), (0, 0b0100_0000));
/// assert_eq!(0b1000_0000u8.carrying_carryless_mul(0b1000_0000, 0b1111), (0b1111, 0b0100_0000));
#[doc = concat!("assert_eq!(",
stringify!($SelfT), "::MAX.carrying_carryless_mul(", stringify!($SelfT), "::MAX, ", stringify!($SelfT), "::MAX), ",
"(!(", stringify!($SelfT), "::MAX / 3), ", stringify!($SelfT), "::MAX / 3));"
)]
/// ```
#[rustc_const_unstable(feature = "uint_carryless_mul", issue = "152080")]
#[doc(alias = "clmul")]
#[unstable(feature = "uint_carryless_mul", issue = "152080")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline]
$($fn)*
}
}
impl i8 {
int_impl! {
Self = i8,
@ -458,6 +556,9 @@ impl u8 {
fsh_op = "0x36",
fshl_result = "0x8",
fshr_result = "0x8d",
clmul_lhs = "0x12",
clmul_rhs = "0x34",
clmul_result = "0x28",
swap_op = "0x12",
swapped = "0x12",
reversed = "0x48",
@ -468,6 +569,8 @@ impl u8 {
bound_condition = "",
}
midpoint_impl! { u8, u16, unsigned }
widening_carryless_mul_impl! { u8, u16 }
carrying_carryless_mul_impl! { u8, u16 }
/// Checks if the value is within the ASCII range.
///
@ -1095,6 +1198,9 @@ impl u16 {
fsh_op = "0x2de",
fshl_result = "0x30",
fshr_result = "0x302d",
clmul_lhs = "0x9012",
clmul_rhs = "0xcd34",
clmul_result = "0x928",
swap_op = "0x1234",
swapped = "0x3412",
reversed = "0x2c48",
@ -1105,6 +1211,8 @@ impl u16 {
bound_condition = "",
}
midpoint_impl! { u16, u32, unsigned }
widening_carryless_mul_impl! { u16, u32 }
carrying_carryless_mul_impl! { u16, u32 }
/// Checks if the value is a Unicode surrogate code point, which are disallowed values for [`char`].
///
@ -1145,6 +1253,9 @@ impl u32 {
fsh_op = "0x2fe78e45",
fshl_result = "0xb32f",
fshr_result = "0xb32fe78e",
clmul_lhs = "0x56789012",
clmul_rhs = "0xf52ecd34",
clmul_result = "0x9b980928",
swap_op = "0x12345678",
swapped = "0x78563412",
reversed = "0x1e6a2c48",
@ -1155,6 +1266,8 @@ impl u32 {
bound_condition = "",
}
midpoint_impl! { u32, u64, unsigned }
widening_carryless_mul_impl! { u32, u64 }
carrying_carryless_mul_impl! { u32, u64 }
}
impl u64 {
@ -1171,6 +1284,9 @@ impl u64 {
fsh_op = "0x2fe78e45983acd98",
fshl_result = "0x6e12fe",
fshr_result = "0x6e12fe78e45983ac",
clmul_lhs = "0x7890123456789012",
clmul_rhs = "0xdd358416f52ecd34",
clmul_result = "0xa6299579b980928",
swap_op = "0x1234567890123456",
swapped = "0x5634129078563412",
reversed = "0x6a2c48091e6a2c48",
@ -1181,6 +1297,8 @@ impl u64 {
bound_condition = "",
}
midpoint_impl! { u64, u128, unsigned }
widening_carryless_mul_impl! { u64, u128 }
carrying_carryless_mul_impl! { u64, u128 }
}
impl u128 {
@ -1197,6 +1315,9 @@ impl u128 {
fsh_op = "0x2fe78e45983acd98039000008736273",
fshl_result = "0x4f7602fe",
fshr_result = "0x4f7602fe78e45983acd9803900000873",
clmul_lhs = "0x12345678901234567890123456789012",
clmul_rhs = "0x4317e40ab4ddcf05dd358416f52ecd34",
clmul_result = "0xb9cf660de35d0c170a6299579b980928",
swap_op = "0x12345678901234567890123456789012",
swapped = "0x12907856341290785634129078563412",
reversed = "0x48091e6a2c48091e6a2c48091e6a2c48",
@ -1209,6 +1330,7 @@ impl u128 {
bound_condition = "",
}
midpoint_impl! { u128, unsigned }
carrying_carryless_mul_impl! { u128, u256 }
}
#[cfg(target_pointer_width = "16")]
@ -1223,9 +1345,12 @@ impl usize {
rot = 4,
rot_op = "0xa003",
rot_result = "0x3a",
fsh_op = "0x2fe78e45983acd98039000008736273",
fshl_result = "0x4f7602fe",
fshr_result = "0x4f7602fe78e45983acd9803900000873",
fsh_op = "0x2de",
fshl_result = "0x30",
fshr_result = "0x302d",
clmul_lhs = "0x9012",
clmul_rhs = "0xcd34",
clmul_result = "0x928",
swap_op = "0x1234",
swapped = "0x3412",
reversed = "0x2c48",
@ -1236,6 +1361,7 @@ impl usize {
bound_condition = " on 16-bit targets",
}
midpoint_impl! { usize, u32, unsigned }
carrying_carryless_mul_impl! { usize, u32 }
}
#[cfg(target_pointer_width = "32")]
@ -1253,6 +1379,9 @@ impl usize {
fsh_op = "0x2fe78e45",
fshl_result = "0xb32f",
fshr_result = "0xb32fe78e",
clmul_lhs = "0x56789012",
clmul_rhs = "0xf52ecd34",
clmul_result = "0x9b980928",
swap_op = "0x12345678",
swapped = "0x78563412",
reversed = "0x1e6a2c48",
@ -1263,6 +1392,7 @@ impl usize {
bound_condition = " on 32-bit targets",
}
midpoint_impl! { usize, u64, unsigned }
carrying_carryless_mul_impl! { usize, u64 }
}
#[cfg(target_pointer_width = "64")]
@ -1280,6 +1410,9 @@ impl usize {
fsh_op = "0x2fe78e45983acd98",
fshl_result = "0x6e12fe",
fshr_result = "0x6e12fe78e45983ac",
clmul_lhs = "0x7890123456789012",
clmul_rhs = "0xdd358416f52ecd34",
clmul_result = "0xa6299579b980928",
swap_op = "0x1234567890123456",
swapped = "0x5634129078563412",
reversed = "0x6a2c48091e6a2c48",
@ -1290,6 +1423,7 @@ impl usize {
bound_condition = " on 64-bit targets",
}
midpoint_impl! { usize, u128, unsigned }
carrying_carryless_mul_impl! { usize, u128 }
}
impl usize {

View file

@ -17,6 +17,9 @@ macro_rules! uint_impl {
fsh_op = $fsh_op:literal,
fshl_result = $fshl_result:literal,
fshr_result = $fshr_result:literal,
clmul_lhs = $clmul_rhs:literal,
clmul_rhs = $clmul_lhs:literal,
clmul_result = $clmul_result:literal,
swap_op = $swap_op:literal,
swapped = $swapped:literal,
reversed = $reversed:literal,
@ -482,6 +485,62 @@ macro_rules! uint_impl {
unsafe { intrinsics::unchecked_funnel_shr(self, rhs, n) }
}
/// Performs a carry-less multiplication, returning the lower bits.
///
/// This operation is similar to long multiplication, except that exclusive or is used
/// instead of addition. The implementation is equivalent to:
///
/// ```no_run
#[doc = concat!("pub fn carryless_mul(lhs: ", stringify!($SelfT), ", rhs: ", stringify!($SelfT), ") -> ", stringify!($SelfT), "{")]
/// let mut retval = 0;
#[doc = concat!(" for i in 0..", stringify!($SelfT), "::BITS {")]
/// if (rhs >> i) & 1 != 0 {
/// // long multiplication would use +=
/// retval ^= lhs << i;
/// }
/// }
/// retval
/// }
/// ```
///
/// The actual implementation is more efficient, and on some platforms lowers directly to a
/// dedicated instruction.
///
/// # Uses
///
/// Carryless multiplication can be used to turn a bitmask of quote characters into a
/// bit mask of characters surrounded by quotes:
///
/// ```no_run
/// r#"abc xxx "foobar" zzz "a"!"#; // input string
/// 0b0000000010000001000001010; // quote_mask
/// 0b0000000001111110000000100; // quote_mask.carryless_mul(!0) & !quote_mask
/// ```
///
/// Another use is in cryptography, where carryless multiplication allows for efficient
/// implementations of polynomial multiplication in `GF(2)[X]`, the polynomial ring
/// over `GF(2)`.
///
/// # Examples
///
/// ```
/// #![feature(uint_carryless_mul)]
///
#[doc = concat!("let a = ", $clmul_lhs, stringify!($SelfT), ";")]
#[doc = concat!("let b = ", $clmul_rhs, stringify!($SelfT), ";")]
///
#[doc = concat!("assert_eq!(a.carryless_mul(b), ", $clmul_result, ");")]
/// ```
#[rustc_const_unstable(feature = "uint_carryless_mul", issue = "152080")]
#[doc(alias = "clmul")]
#[unstable(feature = "uint_carryless_mul", issue = "152080")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline(always)]
pub const fn carryless_mul(self, rhs: Self) -> Self {
intrinsics::carryless_mul(self, rhs)
}
/// Reverses the byte order of the integer.
///
/// # Examples

View file

@ -116,6 +116,7 @@
#![feature(try_trait_v2)]
#![feature(type_info)]
#![feature(uint_bit_width)]
#![feature(uint_carryless_mul)]
#![feature(uint_gather_scatter_bits)]
#![feature(unsize)]
#![feature(unwrap_infallible)]

View file

@ -0,0 +1,254 @@
//! Tests the `Unsigned::{carryless_mul, widening_carryless_mul, carrying_carryless_mul}` methods.
#[test]
fn carryless_mul_u128() {
assert_eq_const_safe!(u128: <u128>::carryless_mul(0, 0), 0);
assert_eq_const_safe!(u128: <u128>::carryless_mul(1, 1), 1);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
0x0123456789ABCDEF_FEDCBA9876543210,
1u128 << 64,
),
0xFEDCBA9876543210_0000000000000000
);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
0x0123456789ABCDEF_FEDCBA9876543210,
(1u128 << 64) | 1,
),
0xFFFFFFFFFFFFFFFF_FEDCBA9876543210
);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
0x0123456789ABCDEF_FEDCBA9876543211,
1u128 << 127,
),
0x8000000000000000_0000000000000000
);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
0xAAAAAAAAAAAAAAAA_AAAAAAAAAAAAAAAA,
0x5555555555555555_5555555555555555,
),
0x2222222222222222_2222222222222222
);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
(1 << 127) | (1 << 64) | 1,
(1 << 63) | 1
),
(1 << 64) | (1 << 63) | 1
);
assert_eq_const_safe!(
u128: <u128>::carryless_mul(
0x8000000000000000_0000000000000001,
0x7FFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF,
),
0xFFFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF
);
}
#[test]
fn carryless_mul_u64() {
assert_eq_const_safe!(u64: <u64>::carryless_mul(0, 0), 0);
assert_eq_const_safe!(u64: <u64>::carryless_mul(1, 1), 1);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
0x0123_4567_89AB_CDEF,
1u64 << 32,
),
0x89AB_CDEF_0000_0000
);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
0x0123_4567_89AB_CDEF,
(1u64 << 32) | 1,
),
0x8888_8888_89AB_CDEF
);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
0x0123_4567_89AB_CDEF,
1u64 << 63,
),
0x8000_0000_0000_0000
);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
0xAAAA_AAAA_AAAA_AAAA,
0x5555_5555_5555_5555,
),
0x2222_2222_2222_2222
);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
(1u64 << 63) | (1u64 << 32) | 1,
(1u64 << 31) | 1,
),
(1u64 << 32) | (1u64 << 31) | 1
);
assert_eq_const_safe!(
u64: <u64>::carryless_mul(
0x8000_0000_0000_0001,
0x7FFF_FFFF_FFFF_FFFF,
),
0xFFFF_FFFF_FFFF_FFFF
);
}
#[test]
fn carryless_mul_u32() {
assert_eq_const_safe!(
u32: <u32>::carryless_mul(0x0123_4567, 1u32 << 16),
0x4567_0000
);
assert_eq_const_safe!(
u32: <u32>::carryless_mul(0xAAAA_AAAA, 0x5555_5555),
0x2222_2222
);
}
#[test]
fn carryless_mul_u16() {
assert_eq_const_safe!(
u16: <u16>::carryless_mul(0x0123, 1u16 << 8),
0x2300
);
assert_eq_const_safe!(
u16: <u16>::carryless_mul(0xAAAA, 0x5555),
0x2222
);
}
#[test]
fn carryless_mul_u8() {
assert_eq_const_safe!(
u8: <u8>::carryless_mul(0x01, 1u8 << 4),
0x10
);
assert_eq_const_safe!(
u8: <u8>::carryless_mul(0xAA, 0x55),
0x22
);
}
#[test]
fn widening_carryless_mul() {
assert_eq_const_safe!(
u16: <u8>::widening_carryless_mul(0xEFu8, 1u8 << 7),
0x7780u16
);
assert_eq_const_safe!(
u16: <u8>::widening_carryless_mul(0xEFu8, (1u8 << 7) | 1),
0x776Fu16
);
assert_eq_const_safe!(
u32: <u16>::widening_carryless_mul(0xBEEFu16, 1u16 << 15),
0x5F77_8000u32
);
assert_eq_const_safe!(
u32: <u16>::widening_carryless_mul(0xBEEFu16, (1u16 << 15) | 1),
0x5F77_3EEFu32
);
assert_eq_const_safe!(
u64: <u32>::widening_carryless_mul(0xDEAD_BEEFu32, 1u32 << 31),
0x6F56_DF77_8000_0000u64
);
assert_eq_const_safe!(
u64: <u32>::widening_carryless_mul(0xDEAD_BEEFu32, (1u32 << 31) | 1),
0x6F56_DF77_5EAD_BEEFu64
);
assert_eq_const_safe!(
u128: <u64>::widening_carryless_mul(0xDEAD_BEEF_FACE_FEEDu64, 1u64 << 63),
147995377545877439359040026616086396928
);
assert_eq_const_safe!(
u128: <u64>::widening_carryless_mul(0xDEAD_BEEF_FACE_FEEDu64, (1u64 << 63) | 1),
147995377545877439356638973527682121453
);
}
#[test]
fn carrying_carryless_mul() {
assert_eq_const_safe!(
(u8, u8): <u8>::carrying_carryless_mul(0xEFu8, 1u8 << 7, 0),
(0x80u8, 0x77u8)
);
assert_eq_const_safe!(
(u8, u8): <u8>::carrying_carryless_mul(0xEFu8, (1u8 << 7) | 1, 0xEF),
(0x80u8, 0x77u8)
);
assert_eq_const_safe!(
(u16, u16): <u16>::carrying_carryless_mul(0xBEEFu16, 1u16 << 15, 0),
(0x8000u16, 0x5F77u16)
);
assert_eq_const_safe!(
(u16, u16): <u16>::carrying_carryless_mul(0xBEEFu16, (1u16 << 15) | 1, 0xBEEF),
(0x8000u16, 0x5F77u16)
);
assert_eq_const_safe!(
(u32, u32): <u32>::carrying_carryless_mul(0xDEAD_BEEFu32, 1u32 << 31, 0),
(0x8000_0000u32, 0x6F56_DF77u32)
);
assert_eq_const_safe!(
(u32, u32): <u32>::carrying_carryless_mul(0xDEAD_BEEFu32, (1u32 << 31) | 1, 0xDEAD_BEEF),
(0x8000_0000u32, 0x6F56_DF77u32)
);
assert_eq_const_safe!(
(u64, u64): <u64>::carrying_carryless_mul(0xDEAD_BEEF_FACE_FEEDu64, 1u64 << 63, 0),
(9223372036854775808, 8022845492652638070)
);
assert_eq_const_safe!(
(u64, u64): <u64>::carrying_carryless_mul(
0xDEAD_BEEF_FACE_FEEDu64,
(1u64 << 63) | 1,
0xDEAD_BEEF_FACE_FEED,
),
(9223372036854775808, 8022845492652638070)
);
assert_eq_const_safe!(
(u128, u128): <u128>::carrying_carryless_mul(
0xDEAD_BEEF_FACE_FEED_0123_4567_89AB_CDEFu128,
1u128 << 127,
0,
),
(
0x8000_0000_0000_0000_0000_0000_0000_0000u128,
147995377545877439359081019380694640375,
)
);
assert_eq_const_safe!(
(u128, u128): <u128>::carrying_carryless_mul(
0xDEAD_BEEF_FACE_FEED_0123_4567_89AB_CDEFu128,
(1u128 << 127) | 1,
0xDEAD_BEEF_FACE_FEED_0123_4567_89AB_CDEF,
),
(
0x8000_0000_0000_0000_0000_0000_0000_0000u128,
147995377545877439359081019380694640375,
)
);
}

View file

@ -22,6 +22,7 @@ mod u64;
mod u8;
mod bignum;
mod carryless_mul;
mod const_from;
mod dec2flt;
mod float_iter_sum_identity;

View file

@ -117,6 +117,13 @@ macro_rules! uint_module {
assert_eq_const_safe!($T: <$T>::funnel_shr(_1, _1, 4), <$T>::rotate_right(_1, 4));
}
fn test_carryless_mul() {
assert_eq_const_safe!($T: <$T>::carryless_mul(0, 0), 0);
assert_eq_const_safe!($T: <$T>::carryless_mul(1, 1), 1);
assert_eq_const_safe!($T: <$T>::carryless_mul(0b0100, 2), 0b1000);
}
fn test_swap_bytes() {
assert_eq_const_safe!($T: A.swap_bytes().swap_bytes(), A);
assert_eq_const_safe!($T: B.swap_bytes().swap_bytes(), B);

View file

@ -315,6 +315,7 @@
#![feature(try_blocks)]
#![feature(try_trait_v2)]
#![feature(type_alias_impl_trait)]
#![feature(uint_carryless_mul)]
// tidy-alphabetical-end
//
// Library features (core):