ax-memory-addr 0.6.10

Wrappers and helper functions for physical and virtual addresses
Documentation
use axtest::prelude::*;

use crate::{
    AddrRange, DynPageIter, MemoryAddr, PAGE_SIZE_2M, PAGE_SIZE_4K, PageIter, PhysAddr,
    PhysAddrRange, VirtAddr, VirtAddrRange, addr_range, pa_range, va_range,
};

#[axtest]
fn memory_addr_alignment_helpers_cover_generic_and_4k_rules() {
    ax_assert_eq!(crate::align_down(0x12345, 0x1000), 0x12000);
    ax_assert_eq!(crate::align_up(0x12345, 0x1000), 0x13000);
    ax_assert_eq!(crate::align_offset(0x12345, 0x1000), 0x345);
    ax_assert!(crate::is_aligned(0x12000, 0x1000));
    ax_assert!(!crate::is_aligned(0x12345, 0x1000));

    let vaddr = VirtAddr::from(0x12345);
    ax_assert_eq!(vaddr.align_down(0x1000usize), VirtAddr::from(0x12000));
    ax_assert_eq!(vaddr.align_up_4k(), VirtAddr::from(0x13000));
    ax_assert_eq!(vaddr.align_offset_4k(), 0x345);
    ax_assert!(!vaddr.is_aligned_4k());
    ax_assert!(VirtAddr::from(0x12000).is_aligned(PAGE_SIZE_4K));
}

#[axtest]
fn memory_addr_arithmetic_reports_wrapping_checked_and_distance_results() {
    let base = PhysAddr::from(0x4000);
    ax_assert_eq!(base.offset(0x80), PhysAddr::from(0x4080));
    ax_assert_eq!(base.offset(-0x100), PhysAddr::from(0x3f00));
    ax_assert_eq!(base.add(0x2000), PhysAddr::from(0x6000));
    ax_assert_eq!(base.sub(0x1000), PhysAddr::from(0x3000));
    ax_assert_eq!(base.offset_from(PhysAddr::from(0x3000)), 0x1000);
    ax_assert_eq!(base.sub_addr(PhysAddr::from(0x1000)), 0x3000);

    let (wrapped_add, add_overflow) = PhysAddr::from(usize::MAX).overflowing_add(1);
    ax_assert_eq!(wrapped_add, PhysAddr::from(0));
    ax_assert!(add_overflow);
    ax_assert_eq!(PhysAddr::from(usize::MAX).checked_add(1), None);

    let (wrapped_sub, sub_overflow) = PhysAddr::from(0).overflowing_sub(1);
    ax_assert_eq!(wrapped_sub, PhysAddr::from(usize::MAX));
    ax_assert!(sub_overflow);
    ax_assert_eq!(PhysAddr::from(0).checked_sub(1), None);
    ax_assert_eq!(
        PhysAddr::from(0x20).wrapping_offset(-0x40),
        PhysAddr::from(usize::MAX - 0x1f)
    );
    ax_assert_eq!(
        PhysAddr::from(usize::MAX).wrapping_add(2),
        PhysAddr::from(1)
    );
    ax_assert_eq!(
        PhysAddr::from(0).wrapping_sub(2),
        PhysAddr::from(usize::MAX - 1)
    );
    ax_assert_eq!(
        PhysAddr::from(0x10).wrapping_sub_addr(PhysAddr::from(0x20)),
        usize::MAX - 0xf
    );
    ax_assert_eq!(
        PhysAddr::from(0x10).overflowing_sub_addr(PhysAddr::from(0x20)),
        (usize::MAX - 0xf, true)
    );
    ax_assert_eq!(
        PhysAddr::from(0x20).overflowing_sub_addr(PhysAddr::from(0x10)),
        (0x10, false)
    );
    ax_assert_eq!(
        PhysAddr::from(0x10).checked_sub_addr(PhysAddr::from(0x20)),
        None
    );
    ax_assert_eq!(
        PhysAddr::from(0x20).checked_sub_addr(PhysAddr::from(0x10)),
        Some(0x10)
    );
}

#[axtest]
fn memory_addr_newtypes_format_and_pointer_helpers_hold() {
    let pa = PhysAddr::from_usize(0x1abc);
    ax_assert_eq!(pa.as_usize(), 0x1abc);
    ax_assert_eq!(alloc::format!("{pa:?}"), "PA:0x1abc");
    ax_assert_eq!(alloc::format!("{pa:x}"), "PA:0x1abc");
    ax_assert_eq!(alloc::format!("{pa:X}"), "PA:0x1ABC");

    let va = VirtAddr::from_usize(0x2abc);
    ax_assert_eq!(va.as_usize(), 0x2abc);
    ax_assert_eq!(alloc::format!("{va:?}"), "VA:0x2abc");
    ax_assert_eq!(alloc::format!("{va:x}"), "VA:0x2abc");
    ax_assert_eq!(alloc::format!("{va:X}"), "VA:0x2ABC");

    let value = 42_u64;
    let ptr = &value as *const u64;
    let addr = VirtAddr::from_ptr_of(ptr);
    ax_assert_eq!(addr.as_ptr_of::<u64>(), ptr);
    ax_assert_eq!(addr.as_ptr(), ptr.cast::<u8>());

    let mut value = 7_u32;
    let ptr = &mut value as *mut u32;
    let addr = VirtAddr::from_mut_ptr_of(ptr);
    ax_assert_eq!(addr.as_mut_ptr_of::<u32>(), ptr);
    ax_assert_eq!(addr.as_mut_ptr(), ptr.cast::<u8>());
}

#[axtest]
fn memory_addr_ranges_cover_contains_overlap_and_formatting_rules() {
    let range = va_range!(0x1000..0x3000);
    ax_assert!(!range.is_empty());
    ax_assert_eq!(range.size(), 0x2000);
    ax_assert!(range.contains(VirtAddr::from(0x1000)));
    ax_assert!(range.contains(VirtAddr::from(0x2fff)));
    ax_assert!(!range.contains(VirtAddr::from(0x3000)));

    ax_assert!(range.contains_range(va_range!(0x1800..0x2000)));
    ax_assert!(!range.contains_range(va_range!(0x0800..0x2000)));
    ax_assert!(va_range!(0x1800..0x2000).contained_in(range));
    ax_assert!(range.overlaps(va_range!(0x0800..0x1001)));
    ax_assert!(range.overlaps(va_range!(0x2fff..0x4000)));
    ax_assert!(!range.overlaps(va_range!(0x3000..0x4000)));

    let sized = VirtAddrRange::from_start_size(VirtAddr::from(0x5000), 0x800);
    ax_assert_eq!(sized.start, VirtAddr::from(0x5000));
    ax_assert_eq!(sized.end, VirtAddr::from(0x5800));
    ax_assert_eq!(
        VirtAddrRange::try_from_start_size(VirtAddr::from(usize::MAX), 2),
        None
    );
    ax_assert!(AddrRange::<usize>::try_new(0x3000, 0x1000).is_none());
    ax_assert!(AddrRange::<usize>::try_from(0x1000usize..0x2000).is_ok());

    let default_range: PhysAddrRange = Default::default();
    ax_assert!(default_range.is_empty());
    ax_assert_eq!(pa_range!(0x1000..0x2000).size(), 0x1000);
    let usize_range: AddrRange<usize> = addr_range!(0x10usize..0x20);
    ax_assert_eq!(usize_range.size(), 0x10);
}

#[axtest]
fn memory_addr_range_format_unchecked_and_boundary_rules_hold() {
    let range = va_range!(0xfec000..0xfff000usize);
    ax_assert_eq!(alloc::format!("{range:?}"), "VA:0xfec000..VA:0xfff000");
    ax_assert_eq!(alloc::format!("{range:x}"), "VA:0xfec000..VA:0xfff000");
    ax_assert_eq!(alloc::format!("{range:X}"), "VA:0xFEC000..VA:0xFFF000");

    let unchecked = unsafe { VirtAddrRange::new_unchecked(0x1000.into(), 0x1000.into()) };
    ax_assert!(unchecked.is_empty());
    ax_assert_eq!(unchecked.size(), 0);

    let sized = unsafe { VirtAddrRange::from_start_size_unchecked(usize::MAX.into(), 2) };
    ax_assert_eq!(sized.start, VirtAddr::from(usize::MAX));
    ax_assert_eq!(sized.end, VirtAddr::from(1));

    let range = va_range!(0x1000..0x2000);
    ax_assert!(!range.contains_range(va_range!(0x0fff..0x1fff)));
    ax_assert!(!range.contains_range(va_range!(0x1001..0x2001)));
    ax_assert!(range.contains_range(va_range!(0x1000..0x2000)));
    ax_assert!(range.contained_in(va_range!(0x0fff..0x2001)));
    ax_assert!(!range.overlaps(va_range!(0x0800..0x1000)));
    ax_assert!(!range.overlaps(va_range!(0x2000..0x2800)));
    ax_assert!(range.overlaps(va_range!(0x0fff..0x1001)));
    ax_assert!(range.overlaps(va_range!(0x1fff..0x2001)));

    let converted: VirtAddrRange = (VirtAddr::from(0x10)..VirtAddr::from(0x20))
        .try_into()
        .unwrap();
    ax_assert_eq!(converted, va_range!(0x10..0x20));
    ax_assert!(VirtAddrRange::try_from(VirtAddr::from(0x20)..VirtAddr::from(0x10)).is_err());
}

#[axtest]
fn memory_addr_page_iterators_accept_only_aligned_power_of_two_pages() {
    let pages = PageIter::<PAGE_SIZE_4K, usize>::new(0x1000, 0x4000)
        .unwrap()
        .collect::<alloc::vec::Vec<_>>();
    ax_assert_eq!(pages, alloc::vec![0x1000, 0x2000, 0x3000]);

    ax_assert!(PageIter::<PAGE_SIZE_4K, usize>::new(0x1001, 0x4000).is_none());
    ax_assert!(PageIter::<3, usize>::new(0x1000, 0x4000).is_none());

    let huge_pages = PageIter::<PAGE_SIZE_2M, PhysAddr>::new(
        PhysAddr::from(PAGE_SIZE_2M),
        PhysAddr::from(PAGE_SIZE_2M * 3),
    )
    .unwrap()
    .collect::<alloc::vec::Vec<_>>();
    ax_assert_eq!(
        huge_pages,
        alloc::vec![
            PhysAddr::from(PAGE_SIZE_2M),
            PhysAddr::from(PAGE_SIZE_2M * 2)
        ]
    );

    let dynamic = DynPageIter::<usize>::new(0, 0x3000, PAGE_SIZE_4K)
        .unwrap()
        .collect::<alloc::vec::Vec<_>>();
    ax_assert_eq!(dynamic, alloc::vec![0, 0x1000, 0x2000]);
    ax_assert!(DynPageIter::<usize>::new(0, 0x3001, PAGE_SIZE_4K).is_none());
    ax_assert!(DynPageIter::<usize>::new(0, 0x3000, 0x1800).is_none());
}

#[axtest]
fn memory_addr_overflowing_and_checked_ops_hold() {
    use crate::{PhysAddr, VirtAddr};

    // overflowing_sub
    let (result, overflow) = PhysAddr::from(0x100).overflowing_sub(0x50);
    ax_assert_eq!(result, PhysAddr::from(0xb0));
    ax_assert!(!overflow);
    let (result, overflow) = PhysAddr::from(0x50).overflowing_sub(0x100);
    ax_assert_eq!(result, PhysAddr::from(usize::MAX - 0xaf));
    ax_assert!(overflow);

    // checked_sub
    ax_assert_eq!(
        PhysAddr::from(0x100).checked_sub(0x50),
        Some(PhysAddr::from(0xb0))
    );
    ax_assert_eq!(PhysAddr::from(0x50).checked_sub(0x100), None);

    // sub_addr
    ax_assert_eq!(PhysAddr::from(0x200).sub_addr(PhysAddr::from(0x100)), 0x100);

    // wrapping_sub_addr
    ax_assert_eq!(
        VirtAddr::from(0x100).wrapping_sub_addr(VirtAddr::from(0x200)),
        usize::MAX - 0xff
    );

    // overflowing_sub_addr
    let (diff, ovf) = VirtAddr::from(0x300).overflowing_sub_addr(VirtAddr::from(0x100));
    ax_assert_eq!(diff, 0x200);
    ax_assert!(!ovf);
    let (diff, ovf) = VirtAddr::from(0x100).overflowing_sub_addr(VirtAddr::from(0x300));
    ax_assert_eq!(diff, usize::MAX - 0x1ff);
    ax_assert!(ovf);

    // checked_sub_addr
    ax_assert_eq!(
        VirtAddr::from(0x300).checked_sub_addr(VirtAddr::from(0x100)),
        Some(0x200)
    );
    ax_assert_eq!(
        VirtAddr::from(0x100).checked_sub_addr(VirtAddr::from(0x300)),
        None
    );
}

#[axtest]
fn memory_addr_page_size_constants_hold() {
    ax_assert!(crate::memory_addr_page_size_constants_hold());
}

#[axtest]
fn memory_addr_align_4k_helpers_hold() {
    ax_assert!(crate::memory_addr_align_4k_helpers_hold());
}

#[axtest]
fn memory_addr_align_up_down_edge_cases_hold() {
    use crate::{PAGE_SIZE_2M, PAGE_SIZE_4K, align_down, align_offset, align_up, is_aligned};

    // Edge cases for align functions
    ax_assert_eq!(align_down(0, 0x1000), 0);
    ax_assert_eq!(align_up(0, 0x1000), 0);
    ax_assert_eq!(align_offset(0, 0x1000), 0);
    ax_assert!(is_aligned(0, 0x1000));

    // Already-aligned addresses
    ax_assert_eq!(align_down(0x1000, 0x1000), 0x1000);
    ax_assert_eq!(align_up(0x1000, 0x1000), 0x1000);
    ax_assert_eq!(align_offset(0x1000, 0x1000), 0);

    // 2M page alignment
    ax_assert_eq!(align_down(0x201_000, PAGE_SIZE_2M), 0x200_000);
    ax_assert_eq!(align_up(0x201_000, PAGE_SIZE_2M), 0x400_000);
    ax_assert_eq!(align_offset(0x201_000, PAGE_SIZE_2M), 0x1000);

    // VirtAddr and PhysAddr align methods
    let pa = PhysAddr::from(0x5678);
    ax_assert_eq!(pa.align_down(PAGE_SIZE_4K), PhysAddr::from(0x5000));
    ax_assert_eq!(pa.align_up(PAGE_SIZE_4K), PhysAddr::from(0x6000));
    ax_assert_eq!(pa.align_down(PAGE_SIZE_2M), PhysAddr::from(0));
    ax_assert_eq!(pa.align_up(PAGE_SIZE_2M), PhysAddr::from(PAGE_SIZE_2M));

    let va = VirtAddr::from(0xabcd);
    ax_assert_eq!(va.align_down(PAGE_SIZE_4K), VirtAddr::from(0xa000));
    ax_assert_eq!(va.align_up(PAGE_SIZE_4K), VirtAddr::from(0xb000));
    ax_assert!(va.is_aligned(1usize));
    ax_assert!(!va.is_aligned(PAGE_SIZE_4K));
}

#[axtest]
fn memory_addr_add_sub_operators_hold() {
    use crate::{PhysAddr, VirtAddr};

    // Test Add<usize> operator
    let pa = PhysAddr::from(0x1000);
    ax_assert_eq!(pa + 0x100usize, PhysAddr::from(0x1100));

    // Test Sub<usize> operator
    ax_assert_eq!(pa - 0x100usize, PhysAddr::from(0xf00));

    // Test Sub<PhysAddr> operator (returns usize)
    let pa2 = PhysAddr::from(0x2000);
    ax_assert_eq!(pa2 - pa, 0x1000);

    // Test AddAssign
    let mut va = VirtAddr::from(0x1000);
    va += 0x200;
    ax_assert_eq!(va, VirtAddr::from(0x1200));

    // Test SubAssign
    va -= 0x100;
    ax_assert_eq!(va, VirtAddr::from(0x1100));
}

#[axtest]
fn memory_addr_wrapping_offset_and_offset_from_hold() {
    use crate::{PhysAddr, VirtAddr};

    // Test wrapping_offset with negative offset
    let pa = PhysAddr::from(0x100);
    ax_assert_eq!(pa.wrapping_offset(-0x50), PhysAddr::from(0xb0));

    // Test wrapping_offset with positive offset
    ax_assert_eq!(pa.wrapping_offset(0x50), PhysAddr::from(0x150));

    // Test offset_from
    let base = PhysAddr::from(0x1000);
    let target = PhysAddr::from(0x1100);
    ax_assert_eq!(target.offset_from(base), 0x100);

    // Test VirtAddr wrapping_offset
    let va = VirtAddr::from(0x200);
    ax_assert_eq!(va.wrapping_offset(-0x100), VirtAddr::from(0x100));
}