Skip to content

Commit 6a3fcc1

Browse files
authored
generalize Align4::as_slice for any Sized type with appropriate alignment (#207)
* generic Align4::as_slice for any Sized type with appropriate alignment * oversight - elements of slice are aligned too * unit test for generic Align4::as_slice * (forgot to add a second element) * move test_harness to mod --------- Co-authored-by: lifning <>
1 parent 5a93b05 commit 6a3fcc1

File tree

2 files changed

+163
-132
lines changed

2 files changed

+163
-132
lines changed

src/lib.rs

Lines changed: 54 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ use prelude::{GbaCell, IrqFn};
9898

9999
mod macros;
100100

101+
#[cfg(test)]
102+
mod test_harness;
103+
101104
#[cfg(feature = "on_gba")]
102105
mod asm_runtime;
103106
#[cfg(feature = "on_gba")]
@@ -143,31 +146,35 @@ impl<const N: usize> Align4<[u8; N]> {
143146
/// * If the number of bytes isn't a multiple of 4
144147
#[inline]
145148
#[must_use]
146-
pub fn as_u32_slice(&self) -> &[u32] {
147-
assert!(self.0.len() % 4 == 0);
148-
// Safety: our struct is aligned to 4, so the pointer will already be
149-
// aligned, we only need to check the length
150-
unsafe {
151-
let data: *const u8 = self.0.as_ptr();
152-
let len: usize = self.0.len();
153-
core::slice::from_raw_parts(data.cast::<u32>(), len / 4)
154-
}
149+
pub const fn as_u32_slice(&self) -> &[u32] {
150+
self.as_slice()
155151
}
156152

157153
/// Views these bytes as a slice of `u16`
158154
/// ## Panics
159155
/// * If the number of bytes isn't a multiple of 2
160156
#[inline]
161157
#[must_use]
162-
pub fn as_u16_slice(&self) -> &[u16] {
163-
assert!(self.0.len() % 2 == 0);
164-
// Safety: our struct is aligned to 4, so the pointer will already be
165-
// aligned, we only need to check the length
166-
unsafe {
167-
let data: *const u8 = self.0.as_ptr();
168-
let len: usize = self.0.len();
169-
core::slice::from_raw_parts(data.cast::<u16>(), len / 2)
158+
pub const fn as_u16_slice(&self) -> &[u16] {
159+
self.as_slice()
160+
}
161+
162+
/// Views these bytes as a slice of `T`
163+
/// ## Panics
164+
/// * If the number of bytes isn't a multiple of T
165+
/// * If the alignment of T isn't 4, 2, or 1
166+
#[inline]
167+
#[must_use]
168+
pub const fn as_slice<T: Sized>(&self) -> &[T] {
169+
const {
170+
assert!(N % (size_of::<T>() + (size_of::<T>() % align_of::<T>())) == 0);
171+
assert!(
172+
align_of::<T>() == 4 || align_of::<T>() == 2 || align_of::<T>() == 1
173+
);
170174
}
175+
let data: *const u8 = self.0.as_ptr();
176+
let len = const { N / size_of::<T>() };
177+
unsafe { core::slice::from_raw_parts(data.cast::<T>(), len) }
171178
}
172179
}
173180

@@ -179,121 +186,6 @@ macro_rules! include_aligned_bytes {
179186
}};
180187
}
181188

182-
#[cfg(test)]
183-
mod test_harness {
184-
use crate::prelude::*;
185-
use crate::{bios, mem, mgba};
186-
use core::fmt::Write;
187-
188-
#[panic_handler]
189-
fn panic(info: &core::panic::PanicInfo) -> ! {
190-
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
191-
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
192-
IE.write(IrqBits::VBLANK);
193-
IME.write(true);
194-
VBlankIntrWait();
195-
VBlankIntrWait();
196-
VBlankIntrWait();
197-
198-
// the Fatal one kills emulation after one line / 256 bytes
199-
// so emit all the information as Error first
200-
if let Ok(mut log) =
201-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error)
202-
{
203-
writeln!(log, "[failed]").ok();
204-
write!(log, "{}", info).ok();
205-
}
206-
207-
if let Ok(mut log) =
208-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal)
209-
{
210-
if let Some(loc) = info.location() {
211-
write!(log, "panic at {loc}! see mgba error log for details.").ok();
212-
} else {
213-
write!(log, "panic! see mgba error log for details.").ok();
214-
}
215-
}
216-
217-
IE.write(IrqBits::new());
218-
bios::IntrWait(true, IrqBits::new());
219-
loop {}
220-
}
221-
222-
pub(crate) trait UnitTest {
223-
fn run(&self);
224-
}
225-
226-
impl<T: Fn()> UnitTest for T {
227-
fn run(&self) {
228-
if let Ok(mut log) =
229-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
230-
{
231-
write!(log, "{}...", core::any::type_name::<T>()).ok();
232-
}
233-
234-
self();
235-
236-
if let Ok(mut log) =
237-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
238-
{
239-
writeln!(log, "[ok]").ok();
240-
}
241-
}
242-
}
243-
244-
pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
245-
if let Ok(mut log) =
246-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
247-
{
248-
write!(log, "Running {} tests", tests.len()).ok();
249-
}
250-
251-
for test in tests {
252-
test.run();
253-
}
254-
if let Ok(mut log) =
255-
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
256-
{
257-
write!(log, "Tests finished successfully").ok();
258-
}
259-
}
260-
261-
#[no_mangle]
262-
extern "C" fn main() {
263-
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
264-
BG_PALETTE.index(0).write(Color::new());
265-
266-
crate::test_main();
267-
268-
BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
269-
BG_PALETTE.index(1).write(Color::new());
270-
BG0CNT
271-
.write(BackgroundControl::new().with_charblock(0).with_screenblock(31));
272-
DISPCNT.write(
273-
DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true),
274-
);
275-
276-
// some niceties for people without mgba-test-runner
277-
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
278-
unsafe {
279-
mem::set_u32x80_unchecked(
280-
tsb.into_block::<1024>().as_mut_ptr().cast(),
281-
0,
282-
12,
283-
);
284-
}
285-
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);
286-
287-
let row = tsb.get_row(9).unwrap().iter().skip(6);
288-
for (addr, ch) in row.zip(b"all tests passed!") {
289-
addr.write(TextEntry::new().with_tile(*ch as u16));
290-
}
291-
292-
DISPSTAT.write(DisplayStatus::new());
293-
bios::IntrWait(true, IrqBits::new());
294-
}
295-
}
296-
297189
#[cfg(test)]
298190
mod test {
299191
use super::Align4;
@@ -304,4 +196,34 @@ mod test {
304196
assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]);
305197
assert_eq!(a.as_u32_slice(), &[0x3020100_u32.to_le()]);
306198
}
199+
200+
#[test_case]
201+
fn align4_as_generic() {
202+
// with padding
203+
#[repr(C, align(4))]
204+
#[derive(PartialEq, Debug)]
205+
struct FiveByte([u8; 5]);
206+
207+
assert_eq!(
208+
Align4(*b"hello...world...").as_slice::<FiveByte>(),
209+
&[FiveByte(*b"hello"), FiveByte(*b"world")]
210+
);
211+
212+
// and without
213+
#[repr(C, align(2))]
214+
#[derive(PartialEq, Debug)]
215+
struct ThreeHalfWords(u16, u16, u16);
216+
217+
assert_eq!(
218+
Align4([
219+
0x11u8, 0x11u8, 0x22u8, 0x22u8, 0x33u8, 0x33u8, 0x44u8, 0x44u8, 0x55u8,
220+
0x55u8, 0x66u8, 0x66u8
221+
])
222+
.as_slice::<ThreeHalfWords>(),
223+
&[
224+
ThreeHalfWords(0x1111, 0x2222, 0x3333),
225+
ThreeHalfWords(0x4444, 0x5555, 0x6666)
226+
]
227+
);
228+
}
307229
}

src/test_harness.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use crate::{bios, mem, mgba, prelude::*};
2+
use core::fmt::Write;
3+
4+
#[panic_handler]
5+
fn panic(info: &core::panic::PanicInfo) -> ! {
6+
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
7+
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
8+
IE.write(IrqBits::VBLANK);
9+
IME.write(true);
10+
VBlankIntrWait();
11+
VBlankIntrWait();
12+
VBlankIntrWait();
13+
14+
// the Fatal one kills emulation after one line / 256 bytes
15+
// so emit all the information as Error first
16+
if let Ok(mut log) =
17+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error)
18+
{
19+
writeln!(log, "[failed]").ok();
20+
write!(log, "{}", info).ok();
21+
}
22+
23+
if let Ok(mut log) =
24+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal)
25+
{
26+
if let Some(loc) = info.location() {
27+
write!(log, "panic at {loc}! see mgba error log for details.").ok();
28+
} else {
29+
write!(log, "panic! see mgba error log for details.").ok();
30+
}
31+
}
32+
33+
IE.write(IrqBits::new());
34+
bios::IntrWait(true, IrqBits::new());
35+
loop {}
36+
}
37+
38+
pub(crate) trait UnitTest {
39+
fn run(&self);
40+
}
41+
42+
impl<T: Fn()> UnitTest for T {
43+
fn run(&self) {
44+
if let Ok(mut log) =
45+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
46+
{
47+
write!(log, "{}...", core::any::type_name::<T>()).ok();
48+
}
49+
50+
self();
51+
52+
if let Ok(mut log) =
53+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
54+
{
55+
writeln!(log, "[ok]").ok();
56+
}
57+
}
58+
}
59+
60+
pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
61+
if let Ok(mut log) =
62+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
63+
{
64+
write!(log, "Running {} tests", tests.len()).ok();
65+
}
66+
67+
for test in tests {
68+
test.run();
69+
}
70+
if let Ok(mut log) =
71+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
72+
{
73+
write!(log, "Tests finished successfully").ok();
74+
}
75+
}
76+
77+
#[no_mangle]
78+
extern "C" fn main() {
79+
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
80+
BG_PALETTE.index(0).write(Color::new());
81+
82+
crate::test_main();
83+
84+
BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
85+
BG_PALETTE.index(1).write(Color::new());
86+
BG0CNT.write(BackgroundControl::new().with_charblock(0).with_screenblock(31));
87+
DISPCNT.write(
88+
DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true),
89+
);
90+
91+
// some niceties for people without mgba-test-runner
92+
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
93+
unsafe {
94+
mem::set_u32x80_unchecked(
95+
tsb.into_block::<1024>().as_mut_ptr().cast(),
96+
0,
97+
12,
98+
);
99+
}
100+
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);
101+
102+
let row = tsb.get_row(9).unwrap().iter().skip(6);
103+
for (addr, ch) in row.zip(b"all tests passed!") {
104+
addr.write(TextEntry::new().with_tile(*ch as u16));
105+
}
106+
107+
DISPSTAT.write(DisplayStatus::new());
108+
bios::IntrWait(true, IrqBits::new());
109+
}

0 commit comments

Comments
 (0)