diff --git a/src/byteorder.rs b/src/byteorder.rs index adcfb3a607..7b88049881 100644 --- a/src/byteorder.rs +++ b/src/byteorder.rs @@ -241,6 +241,13 @@ macro_rules! impl_ops_traits { self.get().cmp(&other.get()) } } + + impl PartialOrd<$native> for $name { + #[inline(always)] + fn partial_cmp(&self, other: &$native) -> Option { + self.get().partial_cmp(other) + } + } }; ($name:ident, $native:ident, @signed_integer_floating_point) => { impl core::ops::Neg for $name { @@ -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 core::ops::$trait for $name { + impl core::ops::$trait<$name> for $name { type Output = $name; #[inline(always)] @@ -274,18 +281,58 @@ macro_rules! impl_ops_traits { } } - impl core::ops::$trait_assign for $name { + impl core::ops::$trait<$name> for $native { + type Output = $name; + + #[inline(always)] + fn $method(self, rhs: $name) -> $name { + let rhs_native: $native = rhs.get(); + let result_native = core::ops::$trait::$method(self, rhs_native); + $name::::new(result_native) + } + } + + impl core::ops::$trait<$native> for $name { + type Output = $name; + + #[inline(always)] + fn $method(self, rhs: $native) -> $name { + let self_native: $native = self.get(); + let result_native = core::ops::$trait::$method(self_native, rhs); + $name::::new(result_native) + } + } + + impl core::ops::$trait_assign<$name> for $name { #[inline(always)] fn $method_assign(&mut self, rhs: $name) { *self = core::ops::$trait::$method(*self, rhs); } } + + impl core::ops::$trait_assign<$name> for $native { + #[inline(always)] + fn $method_assign(&mut self, rhs: $name) { + let rhs_native: $native = rhs.get(); + *self = core::ops::$trait::$method(*self, rhs_native); + } + } + + impl core::ops::$trait_assign<$native> for $name { + #[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 core::ops::$trait for $name { + impl core::ops::$trait<$name> for $name { type Output = $name; #[inline(always)] @@ -297,12 +344,65 @@ macro_rules! impl_ops_traits { } } - impl core::ops::$trait_assign for $name { + impl core::ops::$trait<$name> for $native { + type Output = $name; + + #[inline(always)] + fn $method(self, rhs: $name) -> $name { + // 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::::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 core::ops::$trait<$native> for $name { + type Output = $name; + + #[inline(always)] + fn $method(self, rhs: $native) -> $name { + // (Maybe) runtime cost - byte order swap + let rhs_byteorder = $name::::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 core::ops::$trait_assign<$name> for $name { #[inline(always)] fn $method_assign(&mut self, rhs: $name) { *self = core::ops::$trait::$method(*self, rhs); } } + + impl core::ops::$trait_assign<$name> for $native { + #[inline(always)] + fn $method_assign(&mut self, rhs: $name) { + // (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 core::ops::$trait_assign<$native> for $name { + #[inline(always)] + fn $method_assign(&mut self, rhs: $native) { + *self = core::ops::$trait::$method(*self, rhs); + } + } }; } @@ -576,6 +676,13 @@ example of how it can be used for parsing UDP packets. } } + impl PartialEq<$native> for $name { + #[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); @@ -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 + { type Native: Native; type ByteArray: ByteArray; @@ -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(op: F, op_native: G, op_native_checked: Option) - where + fn test( + op_t_t: FTT, + op_t_n: FTN, + op_n_t: FNT, + op_n_n: FNN, + op_n_n_checked: Option, + 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, + 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, + + 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 { @@ -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`/`Option` 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`/`Option` 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:: Option>; - $( - 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:: Option>; - $( - 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() + where + T: ByteOrderType, + T: core::ops::$trait, + T: core::ops::$trait, + T::Native: core::ops::$trait, + T::Native: core::ops::$trait, + + T: core::ops::$trait_assign, + T: core::ops::$trait_assign, + T::Native: core::ops::$trait_assign, + T::Native: core::ops::$trait_assign, + { + test::( + 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:: Option>; + $( + op_native_checked = Some(T::Native::$checked_method); + )? + op_native_checked + }, + Some(( + >::$method_assign, + >::$method_assign, + >::$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>() + ( + @unary + $trait:ident, + $method:ident, + $($call_for_macros:ident),* + ) => {{ + fn t() where + T: ByteOrderType, + T: core::ops::$trait, T::Native: core::ops::$trait, { - test::( - $op, - $op_native, - $op_native_checked, + test::( + |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:: Option>, + None::<(fn(&mut T, T), fn(&mut T, T::Native), fn(&mut T::Native, T))>, ); } @@ -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);