Skip to content

Commit

Permalink
[byteorder] Implement additional ops for native RHSs (#1741)
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn authored Sep 24, 2024
1 parent b24dfe6 commit 6b7a012
Showing 1 changed file with 238 additions and 66 deletions.
304 changes: 238 additions & 66 deletions src/byteorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ macro_rules! impl_ops_traits {
self.get().cmp(&other.get())
}
}

impl<O: ByteOrder> PartialOrd<$native> for $name<O> {
#[inline(always)]
fn partial_cmp(&self, other: &$native) -> Option<Ordering> {
self.get().partial_cmp(other)
}
}
};
($name:ident, $native:ident, @signed_integer_floating_point) => {
impl<O: ByteOrder> core::ops::Neg for $name<O> {
Expand All @@ -262,7 +269,7 @@ macro_rules! impl_ops_traits {
impl_ops_traits!(@with_byteorder_swap $name, $native, Sub, sub, SubAssign, sub_assign);
};
(@with_byteorder_swap $name:ident, $native:ident, $trait:ident, $method:ident, $trait_assign:ident, $method_assign:ident) => {
impl<O: ByteOrder> core::ops::$trait for $name<O> {
impl<O: ByteOrder> core::ops::$trait<$name<O>> for $name<O> {
type Output = $name<O>;

#[inline(always)]
Expand All @@ -274,18 +281,58 @@ macro_rules! impl_ops_traits {
}
}

impl<O: ByteOrder> core::ops::$trait_assign for $name<O> {
impl<O: ByteOrder> core::ops::$trait<$name<O>> for $native {
type Output = $name<O>;

#[inline(always)]
fn $method(self, rhs: $name<O>) -> $name<O> {
let rhs_native: $native = rhs.get();
let result_native = core::ops::$trait::$method(self, rhs_native);
$name::<O>::new(result_native)
}
}

impl<O: ByteOrder> core::ops::$trait<$native> for $name<O> {
type Output = $name<O>;

#[inline(always)]
fn $method(self, rhs: $native) -> $name<O> {
let self_native: $native = self.get();
let result_native = core::ops::$trait::$method(self_native, rhs);
$name::<O>::new(result_native)
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$name<O>> for $name<O> {
#[inline(always)]
fn $method_assign(&mut self, rhs: $name<O>) {
*self = core::ops::$trait::$method(*self, rhs);
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$name<O>> for $native {
#[inline(always)]
fn $method_assign(&mut self, rhs: $name<O>) {
let rhs_native: $native = rhs.get();
*self = core::ops::$trait::$method(*self, rhs_native);
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$native> for $name<O> {
#[inline(always)]
fn $method_assign(&mut self, rhs: $native) {
*self = core::ops::$trait::$method(*self, rhs);
}
}
};
// Implement traits in terms of the same trait on the native type, but
// without performing a byte order swap. This only works for bitwise
// operations like `&`, `|`, etc.
// without performing a byte order swap when both operands are byteorder
// types. This only works for bitwise operations like `&`, `|`, etc.
//
// When only one operand is a byteorder type, we still need to perform a
// byteorder swap.
(@without_byteorder_swap $name:ident, $native:ident, $trait:ident, $method:ident, $trait_assign:ident, $method_assign:ident) => {
impl<O: ByteOrder> core::ops::$trait for $name<O> {
impl<O: ByteOrder> core::ops::$trait<$name<O>> for $name<O> {
type Output = $name<O>;

#[inline(always)]
Expand All @@ -297,12 +344,65 @@ macro_rules! impl_ops_traits {
}
}

impl<O: ByteOrder> core::ops::$trait_assign for $name<O> {
impl<O: ByteOrder> core::ops::$trait<$name<O>> for $native {
type Output = $name<O>;

#[inline(always)]
fn $method(self, rhs: $name<O>) -> $name<O> {
// No runtime cost - just byte packing
let rhs_native = $native::from_ne_bytes(rhs.0);
// (Maybe) runtime cost - byte order swap
let slf_byteorder = $name::<O>::new(self);
// No runtime cost - just byte packing
let slf_native = $native::from_ne_bytes(slf_byteorder.0);
// Runtime cost - perform the operation
let result_native = core::ops::$trait::$method(slf_native, rhs_native);
// No runtime cost - just byte unpacking
$name(result_native.to_ne_bytes(), PhantomData)
}
}

impl<O: ByteOrder> core::ops::$trait<$native> for $name<O> {
type Output = $name<O>;

#[inline(always)]
fn $method(self, rhs: $native) -> $name<O> {
// (Maybe) runtime cost - byte order swap
let rhs_byteorder = $name::<O>::new(rhs);
// No runtime cost - just byte packing
let rhs_native = $native::from_ne_bytes(rhs_byteorder.0);
// No runtime cost - just byte packing
let slf_native = $native::from_ne_bytes(self.0);
// Runtime cost - perform the operation
let result_native = core::ops::$trait::$method(slf_native, rhs_native);
// No runtime cost - just byte unpacking
$name(result_native.to_ne_bytes(), PhantomData)
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$name<O>> for $name<O> {
#[inline(always)]
fn $method_assign(&mut self, rhs: $name<O>) {
*self = core::ops::$trait::$method(*self, rhs);
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$name<O>> for $native {
#[inline(always)]
fn $method_assign(&mut self, rhs: $name<O>) {
// (Maybe) runtime cost - byte order swap
let rhs_native = rhs.get();
// Runtime cost - perform the operation
*self = core::ops::$trait::$method(*self, rhs_native);
}
}

impl<O: ByteOrder> core::ops::$trait_assign<$native> for $name<O> {
#[inline(always)]
fn $method_assign(&mut self, rhs: $native) {
*self = core::ops::$trait::$method(*self, rhs);
}
}
};
}

Expand Down Expand Up @@ -576,6 +676,13 @@ example of how it can be used for parsing UDP packets.
}
}

impl<O: ByteOrder> PartialEq<$native> for $name<O> {
#[inline(always)]
fn eq(&self, other: &$native) -> bool {
self.get().eq(other)
}
}

impl_fmt_traits!($name, $native, $number_kind);
impl_ops_traits!($name, $native, $number_kind);

Expand Down Expand Up @@ -962,7 +1069,9 @@ mod tests {
fn invert(self) -> Self;
}

trait ByteOrderType: FromBytes + IntoBytes + Unaligned + Copy + Eq + Debug {
trait ByteOrderType:
FromBytes + IntoBytes + Unaligned + Copy + Eq + Debug + From<Self::Native>
{
type Native: Native;
type ByteArray: ByteArray;

Expand Down Expand Up @@ -1246,12 +1355,24 @@ mod tests {
// regardless of byte order). These are important to test, and while
// we're testing those anyway, it's trivial to test all of the impls.

fn test<T, F, G, H>(op: F, op_native: G, op_native_checked: Option<H>)
where
fn test<T, FTT, FTN, FNT, FNN, FNNChecked, FATT, FATN, FANT>(
op_t_t: FTT,
op_t_n: FTN,
op_n_t: FNT,
op_n_n: FNN,
op_n_n_checked: Option<FNNChecked>,
op_assign: Option<(FATT, FATN, FANT)>,
) where
T: ByteOrderType,
F: Fn(T, T) -> T,
G: Fn(T::Native, T::Native) -> T::Native,
H: Fn(T::Native, T::Native) -> Option<T::Native>,
FTT: Fn(T, T) -> T,
FTN: Fn(T, T::Native) -> T,
FNT: Fn(T::Native, T) -> T,
FNN: Fn(T::Native, T::Native) -> T::Native,
FNNChecked: Fn(T::Native, T::Native) -> Option<T::Native>,

FATT: Fn(&mut T, T),
FATN: Fn(&mut T, T::Native),
FANT: Fn(&mut T::Native, T),
{
let mut r = SmallRng::seed_from_u64(RNG_SEED);
for _ in 0..RAND_ITERS {
Expand All @@ -1262,64 +1383,115 @@ mod tests {

// If this operation would overflow/underflow, skip it rather
// than attempt to catch and recover from panics.
if matches!(&op_native_checked, Some(checked) if checked(n0, n1).is_none()) {
if matches!(&op_n_n_checked, Some(checked) if checked(n0, n1).is_none()) {
continue;
}

let n_res = op_native(n0, n1);
let t_res = op(t0, t1);
let t_t_res = op_t_t(t0, t1);
let t_n_res = op_t_n(t0, n1);
let n_t_res = op_n_t(n0, t1);
let n_n_res = op_n_n(n0, n1);

// For `f32` and `f64`, NaN values are not considered equal to
// themselves. We store `Option<f32>`/`Option<f64>` and store
// NaN as `None` so they can still be compared.
let n_res = (!T::Native::is_nan(n_res)).then(|| n_res);
let t_res = (!T::Native::is_nan(t_res.get())).then(|| t_res.get());
assert_eq!(n_res, t_res);
let val_or_none = |t: T| (!T::Native::is_nan(t.get())).then(|| t.get());
let t_t_res = val_or_none(t_t_res);
let t_n_res = val_or_none(t_n_res);
let n_t_res = val_or_none(n_t_res);
let n_n_res = (!T::Native::is_nan(n_n_res)).then(|| n_n_res);
assert_eq!(t_t_res, n_n_res);
assert_eq!(t_n_res, n_n_res);
assert_eq!(n_t_res, n_n_res);

if let Some((op_assign_t_t, op_assign_t_n, op_assign_n_t)) = &op_assign {
let mut t_t_res = t0;
op_assign_t_t(&mut t_t_res, t1);
let mut t_n_res = t0;
op_assign_t_n(&mut t_n_res, n1);
let mut n_t_res = n0;
op_assign_n_t(&mut n_t_res, t1);

// For `f32` and `f64`, NaN values are not considered equal to
// themselves. We store `Option<f32>`/`Option<f64>` and store
// NaN as `None` so they can still be compared.
let t_t_res = val_or_none(t_t_res);
let t_n_res = val_or_none(t_n_res);
let n_t_res = (!T::Native::is_nan(n_t_res)).then(|| n_t_res);
assert_eq!(t_t_res, n_n_res);
assert_eq!(t_n_res, n_n_res);
assert_eq!(n_t_res, n_n_res);
}
}
}

macro_rules! test {
(@binary $trait:ident, $method:ident $([$checked_method:ident])?, $($call_for_macros:ident),*) => {{
test!(
@inner $trait,
core::ops::$trait::$method,
core::ops::$trait::$method,
{
#[allow(unused_mut, unused_assignments)]
let mut op_native_checked = None::<fn(T::Native, T::Native) -> Option<T::Native>>;
$(
op_native_checked = Some(T::Native::$checked_method);
)?
op_native_checked
},
$($call_for_macros),*
);
}};
(@unary $trait:ident, $method:ident $([$checked_method:ident])?, $($call_for_macros:ident),*) => {{
test!(
@inner $trait,
|slf, _rhs| core::ops::$trait::$method(slf),
|slf, _rhs| core::ops::$trait::$method(slf),
{
#[allow(unused_mut, unused_assignments)]
let mut op_native_checked = None::<fn(T::Native, T::Native) -> Option<T::Native>>;
$(
op_native_checked = Some(|slf, _rhs| T::Native::$checked_method(slf));
)?
op_native_checked
},
$($call_for_macros),*
);
(
@binary
$trait:ident,
$method:ident $([$checked_method:ident])?,
$trait_assign:ident,
$method_assign:ident,
$($call_for_macros:ident),*
) => {{
fn t<T>()
where
T: ByteOrderType,
T: core::ops::$trait<T, Output = T>,
T: core::ops::$trait<T::Native, Output = T>,
T::Native: core::ops::$trait<T, Output = T>,
T::Native: core::ops::$trait<T::Native, Output = T::Native>,

T: core::ops::$trait_assign<T>,
T: core::ops::$trait_assign<T::Native>,
T::Native: core::ops::$trait_assign<T>,
T::Native: core::ops::$trait_assign<T::Native>,
{
test::<T, _, _, _, _, _, _, _, _>(
core::ops::$trait::$method,
core::ops::$trait::$method,
core::ops::$trait::$method,
core::ops::$trait::$method,
{
#[allow(unused_mut, unused_assignments)]
let mut op_native_checked = None::<fn(T::Native, T::Native) -> Option<T::Native>>;
$(
op_native_checked = Some(T::Native::$checked_method);
)?
op_native_checked
},
Some((
<T as core::ops::$trait_assign<T>>::$method_assign,
<T as core::ops::$trait_assign::<T::Native>>::$method_assign,
<T::Native as core::ops::$trait_assign::<T>>::$method_assign
)),
);
}

$(
$call_for_macros!(t, NativeEndian);
$call_for_macros!(t, NonNativeEndian);
)*
}};
(@inner $trait:ident, $op:expr, $op_native:expr, $op_native_checked:expr, $($call_for_macros:ident),*) => {{
fn t<T: ByteOrderType + core::ops::$trait<Output = T>>()
(
@unary
$trait:ident,
$method:ident,
$($call_for_macros:ident),*
) => {{
fn t<T>()
where
T: ByteOrderType,
T: core::ops::$trait<Output = T>,
T::Native: core::ops::$trait<Output = T::Native>,
{
test::<T, _, _, _>(
$op,
$op_native,
$op_native_checked,
test::<T, _, _, _, _, _, _, _, _>(
|slf, _rhs| core::ops::$trait::$method(slf),
|slf, _rhs| core::ops::$trait::$method(slf),
|slf, _rhs| core::ops::$trait::$method(slf).into(),
|slf, _rhs| core::ops::$trait::$method(slf),
None::<fn(T::Native, T::Native) -> Option<T::Native>>,
None::<(fn(&mut T, T), fn(&mut T, T::Native), fn(&mut T::Native, T))>,
);
}

Expand All @@ -1330,17 +1502,17 @@ mod tests {
}};
}

test!(@binary Add, add[checked_add], call_for_all_types);
test!(@binary Div, div[checked_div], call_for_all_types);
test!(@binary Mul, mul[checked_mul], call_for_all_types);
test!(@binary Rem, rem[checked_rem], call_for_all_types);
test!(@binary Sub, sub[checked_sub], call_for_all_types);

test!(@binary BitAnd, bitand, call_for_unsigned_types, call_for_signed_types);
test!(@binary BitOr, bitor, call_for_unsigned_types, call_for_signed_types);
test!(@binary BitXor, bitxor, call_for_unsigned_types, call_for_signed_types);
test!(@binary Shl, shl[checked_shl], call_for_unsigned_types, call_for_signed_types);
test!(@binary Shr, shr[checked_shr], call_for_unsigned_types, call_for_signed_types);
test!(@binary Add, add[checked_add], AddAssign, add_assign, call_for_all_types);
test!(@binary Div, div[checked_div], DivAssign, div_assign, call_for_all_types);
test!(@binary Mul, mul[checked_mul], MulAssign, mul_assign, call_for_all_types);
test!(@binary Rem, rem[checked_rem], RemAssign, rem_assign, call_for_all_types);
test!(@binary Sub, sub[checked_sub], SubAssign, sub_assign, call_for_all_types);

test!(@binary BitAnd, bitand, BitAndAssign, bitand_assign, call_for_unsigned_types, call_for_signed_types);
test!(@binary BitOr, bitor, BitOrAssign, bitor_assign, call_for_unsigned_types, call_for_signed_types);
test!(@binary BitXor, bitxor, BitXorAssign, bitxor_assign, call_for_unsigned_types, call_for_signed_types);
test!(@binary Shl, shl[checked_shl], ShlAssign, shl_assign, call_for_unsigned_types, call_for_signed_types);
test!(@binary Shr, shr[checked_shr], ShrAssign, shr_assign, call_for_unsigned_types, call_for_signed_types);

test!(@unary Not, not, call_for_signed_types, call_for_unsigned_types);
test!(@unary Neg, neg, call_for_signed_types, call_for_float_types);
Expand Down

0 comments on commit 6b7a012

Please sign in to comment.