From c760da87ada6b02e6d811e89d09482fccd01a1f1 Mon Sep 17 00:00:00 2001 From: lifning <> Date: Sat, 3 Aug 2024 21:17:38 -0700 Subject: [PATCH 1/5] generic Align4::as_slice for any Sized type with appropriate alignment --- src/lib.rs | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4263d00..97c6ba0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,15 +143,8 @@ impl Align4<[u8; N]> { /// * If the number of bytes isn't a multiple of 4 #[inline] #[must_use] - pub fn as_u32_slice(&self) -> &[u32] { - assert!(self.0.len() % 4 == 0); - // Safety: our struct is aligned to 4, so the pointer will already be - // aligned, we only need to check the length - unsafe { - let data: *const u8 = self.0.as_ptr(); - let len: usize = self.0.len(); - core::slice::from_raw_parts(data.cast::(), len / 4) - } + pub const fn as_u32_slice(&self) -> &[u32] { + self.as_slice() } /// Views these bytes as a slice of `u16` @@ -159,15 +152,26 @@ impl Align4<[u8; N]> { /// * If the number of bytes isn't a multiple of 2 #[inline] #[must_use] - pub fn as_u16_slice(&self) -> &[u16] { - assert!(self.0.len() % 2 == 0); - // Safety: our struct is aligned to 4, so the pointer will already be - // aligned, we only need to check the length - unsafe { - let data: *const u8 = self.0.as_ptr(); - let len: usize = self.0.len(); - core::slice::from_raw_parts(data.cast::(), len / 2) + pub const fn as_u16_slice(&self) -> &[u16] { + self.as_slice() + } + + /// Views these bytes as a slice of `T` + /// ## Panics + /// * If the number of bytes isn't a multiple of T + /// * If the alignment of T isn't 4, 2, or 1 + #[inline] + #[must_use] + pub const fn as_slice(&self) -> &[T] { + const { + assert!(N % size_of::() == 0); + assert!( + align_of::() == 4 || align_of::() == 2 || align_of::() == 1 + ); } + let data: *const u8 = self.0.as_ptr(); + let len = const { N / size_of::() }; + unsafe { core::slice::from_raw_parts(data.cast::(), len) } } } From 0cdc9a7a0ab0c3843496d6e6885b0d3a204c1f00 Mon Sep 17 00:00:00 2001 From: lif <> Date: Tue, 4 Feb 2025 01:10:07 -0800 Subject: [PATCH 2/5] oversight - elements of slice are aligned too --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 97c6ba0..e410b0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,7 @@ impl Align4<[u8; N]> { #[must_use] pub const fn as_slice(&self) -> &[T] { const { - assert!(N % size_of::() == 0); + assert!(N % (size_of::() + (size_of::() % align_of::())) == 0); assert!( align_of::() == 4 || align_of::() == 2 || align_of::() == 1 ); From de1410a9d515b71b2ce8b47c6a21735a214a4bac Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 21:08:16 -0800 Subject: [PATCH 3/5] unit test for generic Align4::as_slice --- src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e410b0d..60aa0ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -308,4 +308,28 @@ mod test { assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]); assert_eq!(a.as_u32_slice(), &[0x3020100_u32.to_le()]); } + + #[test_case] + fn align4_as_generic() { + // with padding + #[repr(C, align(4))] + #[derive(PartialEq, Debug)] + struct FiveByte([u8; 5]); + + assert_eq!( + Align4(*b"hello...world...").as_slice::(), + &[FiveByte(*b"hello"), FiveByte(*b"world")] + ); + + // and without + #[repr(C, align(2))] + #[derive(PartialEq, Debug)] + struct ThreeHalfWords(u16, u16, u16); + + assert_eq!( + Align4([0x11u8, 0x11u8, 0x22u8, 0x22u8, 0x33u8, 0x33u8]) + .as_slice::(), + &[ThreeHalfWords(0x1111, 0x2222, 0x3333)] + ); + } } From c545cc496c754efd56cf3fdfe60d4de5cc1a5230 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 21:12:02 -0800 Subject: [PATCH 4/5] (forgot to add a second element) --- src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 60aa0ed..5db5290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,9 +327,15 @@ mod test { struct ThreeHalfWords(u16, u16, u16); assert_eq!( - Align4([0x11u8, 0x11u8, 0x22u8, 0x22u8, 0x33u8, 0x33u8]) - .as_slice::(), - &[ThreeHalfWords(0x1111, 0x2222, 0x3333)] + Align4([ + 0x11u8, 0x11u8, 0x22u8, 0x22u8, 0x33u8, 0x33u8, 0x44u8, 0x44u8, 0x55u8, + 0x55u8, 0x66u8, 0x66u8 + ]) + .as_slice::(), + &[ + ThreeHalfWords(0x1111, 0x2222, 0x3333), + ThreeHalfWords(0x4444, 0x5555, 0x6666) + ] ); } } From 6f3bef747ecbdb56cfad22aa49c59d74dc47b197 Mon Sep 17 00:00:00 2001 From: lif <> Date: Mon, 17 Feb 2025 01:01:38 -0800 Subject: [PATCH 5/5] move test_harness to mod --- src/lib.rs | 118 ++------------------------------------------ src/test_harness.rs | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 115 deletions(-) create mode 100644 src/test_harness.rs diff --git a/src/lib.rs b/src/lib.rs index 5db5290..e79f60c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,9 @@ use prelude::{GbaCell, IrqFn}; mod macros; +#[cfg(test)] +mod test_harness; + #[cfg(feature = "on_gba")] mod asm_runtime; #[cfg(feature = "on_gba")] @@ -183,121 +186,6 @@ macro_rules! include_aligned_bytes { }}; } -#[cfg(test)] -mod test_harness { - use crate::prelude::*; - use crate::{bios, mem, mgba}; - use core::fmt::Write; - - #[panic_handler] - fn panic(info: &core::panic::PanicInfo) -> ! { - DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); - BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5)); - IE.write(IrqBits::VBLANK); - IME.write(true); - VBlankIntrWait(); - VBlankIntrWait(); - VBlankIntrWait(); - - // the Fatal one kills emulation after one line / 256 bytes - // so emit all the information as Error first - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error) - { - writeln!(log, "[failed]").ok(); - write!(log, "{}", info).ok(); - } - - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal) - { - if let Some(loc) = info.location() { - write!(log, "panic at {loc}! see mgba error log for details.").ok(); - } else { - write!(log, "panic! see mgba error log for details.").ok(); - } - } - - IE.write(IrqBits::new()); - bios::IntrWait(true, IrqBits::new()); - loop {} - } - - pub(crate) trait UnitTest { - fn run(&self); - } - - impl UnitTest for T { - fn run(&self) { - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) - { - write!(log, "{}...", core::any::type_name::()).ok(); - } - - self(); - - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) - { - writeln!(log, "[ok]").ok(); - } - } - } - - pub(crate) fn test_runner(tests: &[&dyn UnitTest]) { - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) - { - write!(log, "Running {} tests", tests.len()).ok(); - } - - for test in tests { - test.run(); - } - if let Ok(mut log) = - mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) - { - write!(log, "Tests finished successfully").ok(); - } - } - - #[no_mangle] - extern "C" fn main() { - DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0)); - BG_PALETTE.index(0).write(Color::new()); - - crate::test_main(); - - BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25)); - BG_PALETTE.index(1).write(Color::new()); - BG0CNT - .write(BackgroundControl::new().with_charblock(0).with_screenblock(31)); - DISPCNT.write( - DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true), - ); - - // some niceties for people without mgba-test-runner - let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap(); - unsafe { - mem::set_u32x80_unchecked( - tsb.into_block::<1024>().as_mut_ptr().cast(), - 0, - 12, - ); - } - Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); - - let row = tsb.get_row(9).unwrap().iter().skip(6); - for (addr, ch) in row.zip(b"all tests passed!") { - addr.write(TextEntry::new().with_tile(*ch as u16)); - } - - DISPSTAT.write(DisplayStatus::new()); - bios::IntrWait(true, IrqBits::new()); - } -} - #[cfg(test)] mod test { use super::Align4; diff --git a/src/test_harness.rs b/src/test_harness.rs new file mode 100644 index 0000000..87cb464 --- /dev/null +++ b/src/test_harness.rs @@ -0,0 +1,109 @@ +use crate::{bios, mem, mgba, prelude::*}; +use core::fmt::Write; + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); + BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5)); + IE.write(IrqBits::VBLANK); + IME.write(true); + VBlankIntrWait(); + VBlankIntrWait(); + VBlankIntrWait(); + + // the Fatal one kills emulation after one line / 256 bytes + // so emit all the information as Error first + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error) + { + writeln!(log, "[failed]").ok(); + write!(log, "{}", info).ok(); + } + + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal) + { + if let Some(loc) = info.location() { + write!(log, "panic at {loc}! see mgba error log for details.").ok(); + } else { + write!(log, "panic! see mgba error log for details.").ok(); + } + } + + IE.write(IrqBits::new()); + bios::IntrWait(true, IrqBits::new()); + loop {} +} + +pub(crate) trait UnitTest { + fn run(&self); +} + +impl UnitTest for T { + fn run(&self) { + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "{}...", core::any::type_name::()).ok(); + } + + self(); + + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + writeln!(log, "[ok]").ok(); + } + } +} + +pub(crate) fn test_runner(tests: &[&dyn UnitTest]) { + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "Running {} tests", tests.len()).ok(); + } + + for test in tests { + test.run(); + } + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "Tests finished successfully").ok(); + } +} + +#[no_mangle] +extern "C" fn main() { + DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0)); + BG_PALETTE.index(0).write(Color::new()); + + crate::test_main(); + + BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25)); + BG_PALETTE.index(1).write(Color::new()); + BG0CNT.write(BackgroundControl::new().with_charblock(0).with_screenblock(31)); + DISPCNT.write( + DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true), + ); + + // some niceties for people without mgba-test-runner + let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap(); + unsafe { + mem::set_u32x80_unchecked( + tsb.into_block::<1024>().as_mut_ptr().cast(), + 0, + 12, + ); + } + Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); + + let row = tsb.get_row(9).unwrap().iter().skip(6); + for (addr, ch) in row.zip(b"all tests passed!") { + addr.write(TextEntry::new().with_tile(*ch as u16)); + } + + DISPSTAT.write(DisplayStatus::new()); + bios::IntrWait(true, IrqBits::new()); +}