Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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