use std::cmp::{Eq, Ord, PartialEq, PartialOrd};
use std::fmt::Debug;
use std::ops::{Add, BitAnd, BitOr, Not, Sub};
pub trait AddressValue {
type V: Copy
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Not<Output = Self::V>
+ Add<Output = Self::V>
+ Sub<Output = Self::V>
+ BitAnd<Output = Self::V>
+ BitOr<Output = Self::V>
+ Debug
+ From<u8>;
fn zero() -> Self::V {
0u8.into()
}
fn one() -> Self::V {
1u8.into()
}
}
pub trait Address:
AddressValue
+ Sized
+ Default
+ Copy
+ Eq
+ PartialEq
+ Ord
+ PartialOrd
+ BitAnd<<Self as AddressValue>::V, Output = Self>
+ BitOr<<Self as AddressValue>::V, Output = Self>
{
fn new(addr: Self::V) -> Self;
fn raw_value(&self) -> Self::V;
fn mask(&self, mask: Self::V) -> Self::V {
self.raw_value() & mask
}
fn checked_offset_from(&self, base: Self) -> Option<Self::V>;
fn unchecked_offset_from(&self, base: Self) -> Self::V {
self.raw_value() - base.raw_value()
}
fn checked_align_up(&self, power_of_two: Self::V) -> Option<Self> {
let mask = power_of_two - Self::one();
assert_ne!(power_of_two, Self::zero());
assert_eq!(power_of_two & mask, Self::zero());
self.checked_add(mask).map(|x| x & !mask)
}
fn unchecked_align_up(&self, power_of_two: Self::V) -> Self {
let mask = power_of_two - Self::one();
self.unchecked_add(mask) & !mask
}
fn checked_add(&self, other: Self::V) -> Option<Self>;
fn overflowing_add(&self, other: Self::V) -> (Self, bool);
fn unchecked_add(&self, offset: Self::V) -> Self;
fn checked_sub(&self, other: Self::V) -> Option<Self>;
fn overflowing_sub(&self, other: Self::V) -> (Self, bool);
fn unchecked_sub(&self, other: Self::V) -> Self;
}
macro_rules! impl_address_ops {
($T:ident, $V:ty) => {
impl AddressValue for $T {
type V = $V;
}
impl Address for $T {
fn new(value: $V) -> $T {
$T(value)
}
fn raw_value(&self) -> $V {
self.0
}
fn checked_offset_from(&self, base: $T) -> Option<$V> {
self.0.checked_sub(base.0)
}
fn checked_add(&self, other: $V) -> Option<$T> {
self.0.checked_add(other).map($T)
}
fn overflowing_add(&self, other: $V) -> ($T, bool) {
let (t, ovf) = self.0.overflowing_add(other);
($T(t), ovf)
}
fn unchecked_add(&self, offset: $V) -> $T {
$T(self.0 + offset)
}
fn checked_sub(&self, other: $V) -> Option<$T> {
self.0.checked_sub(other).map($T)
}
fn overflowing_sub(&self, other: $V) -> ($T, bool) {
let (t, ovf) = self.0.overflowing_sub(other);
($T(t), ovf)
}
fn unchecked_sub(&self, other: $V) -> $T {
$T(self.0 - other)
}
}
impl Default for $T {
fn default() -> $T {
Self::new(0 as $V)
}
}
impl BitAnd<$V> for $T {
type Output = $T;
fn bitand(self, other: $V) -> $T {
$T(self.0 & other)
}
}
impl BitOr<$V> for $T {
type Output = $T;
fn bitor(self, other: $V) -> $T {
$T(self.0 | other)
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
struct MockAddress(pub u64);
impl_address_ops!(MockAddress, u64);
#[test]
fn test_new() {
assert_eq!(MockAddress::new(0), MockAddress(0));
assert_eq!(MockAddress::new(std::u64::MAX), MockAddress(std::u64::MAX));
}
#[test]
fn test_offset_from() {
let base = MockAddress(0x100);
let addr = MockAddress(0x150);
assert_eq!(addr.unchecked_offset_from(base), 0x50u64);
assert_eq!(addr.checked_offset_from(base), Some(0x50u64));
assert_eq!(base.checked_offset_from(addr), None);
}
#[test]
fn test_equals() {
let a = MockAddress(0x300);
let b = MockAddress(0x300);
let c = MockAddress(0x301);
assert_eq!(a, MockAddress(a.raw_value()));
assert_eq!(a, b);
assert_eq!(b, a);
assert_ne!(a, c);
assert_ne!(c, a);
}
#[test]
fn test_cmp() {
let a = MockAddress(0x300);
let b = MockAddress(0x301);
assert!(a < b);
}
#[test]
fn test_checked_align_up() {
assert_eq!(
MockAddress::new(0x128).checked_align_up(8),
Some(MockAddress(0x128))
);
assert_eq!(
MockAddress::new(0x128).checked_align_up(16),
Some(MockAddress(0x130))
);
assert_eq!(
MockAddress::new(std::u64::MAX - 0x3fff).checked_align_up(0x10000),
None
);
}
#[test]
#[should_panic]
fn test_checked_align_up_invalid() {
let _ = MockAddress::new(0x128).checked_align_up(12);
}
#[test]
fn test_unchecked_align_up() {
assert_eq!(
MockAddress::new(0x128).unchecked_align_up(8),
MockAddress(0x128)
);
assert_eq!(
MockAddress::new(0x128).unchecked_align_up(16),
MockAddress(0x130)
);
}
#[test]
fn test_mask() {
let a = MockAddress(0x5050);
assert_eq!(MockAddress(0x5000), a & 0xff00u64);
assert_eq!(0x5000, a.mask(0xff00u64));
assert_eq!(MockAddress(0x5055), a | 0x0005u64);
}
fn check_add(a: u64, b: u64, expected_overflow: bool, expected_result: u64) {
assert_eq!(
(MockAddress(expected_result), expected_overflow),
MockAddress(a).overflowing_add(b)
);
if expected_overflow {
assert!(MockAddress(a).checked_add(b).is_none());
#[cfg(debug_assertions)]
assert!(std::panic::catch_unwind(|| MockAddress(a).unchecked_add(b)).is_err());
} else {
assert_eq!(
Some(MockAddress(expected_result)),
MockAddress(a).checked_add(b)
);
assert_eq!(
MockAddress(expected_result),
MockAddress(a).unchecked_add(b)
);
}
}
#[test]
fn test_add() {
check_add(10, 10, false, 20);
check_add(std::u64::MAX - 1, 1, false, std::u64::MAX);
check_add(std::u64::MAX, 1, true, 0);
}
fn check_sub(a: u64, b: u64, expected_overflow: bool, expected_result: u64) {
assert_eq!(
(MockAddress(expected_result), expected_overflow),
MockAddress(a).overflowing_sub(b)
);
if expected_overflow {
assert!(MockAddress(a).checked_sub(b).is_none());
assert!(MockAddress(a).checked_offset_from(MockAddress(b)).is_none());
#[cfg(debug_assertions)]
assert!(std::panic::catch_unwind(|| MockAddress(a).unchecked_sub(b)).is_err());
} else {
assert_eq!(
Some(MockAddress(expected_result)),
MockAddress(a).checked_sub(b)
);
assert_eq!(
Some(expected_result),
MockAddress(a).checked_offset_from(MockAddress(b))
);
assert_eq!(
MockAddress(expected_result),
MockAddress(a).unchecked_sub(b)
);
}
}
#[test]
fn test_sub() {
check_sub(20, 10, false, 10);
check_sub(1, 1, false, 0);
check_sub(0, 1, true, std::u64::MAX);
}
#[test]
fn test_default() {
assert_eq!(MockAddress::default(), MockAddress(0));
}
#[test]
fn test_bit_and() {
let a = MockAddress(0x0ff0);
assert_eq!(a & 0xf00f, MockAddress(0));
}
#[test]
fn test_bit_or() {
let a = MockAddress(0x0ff0);
assert_eq!(a | 0xf00f, MockAddress(0xffff));
}
}