diff --git a/.travis.yml b/.travis.yml index 45e0d04..e22f99c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,12 @@ script: - export PATH="$PATH:/opt/devkitpro/devkitARM/bin" - export PATH="$PATH:/opt/devkitpro/tools/bin" - cd .. - # Test the lib and then compile all examples with `cargo make` - - cargo test --lib && cargo test --lib --release + # Run all tests, both modes + - cargo test --no-fail-fast --lib + - cargo test --no-fail-fast --lib --release + - cargo test --no-fail-fast --tests + - cargo test --no-fail-fast --tests --release + # cargo make defaults to both debug and release builds of all examples - cargo make # Test build the book so that a failed book build kills this run - cd book && mdbook build diff --git a/Cargo.toml b/Cargo.toml index 401f558..2e65f0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ publish = false typenum = "1.10" gba-proc-macro = "0.2.1" +[dev-dependencies] +quickcheck="0.7" + [profile.release] lto = true panic = "abort" diff --git a/Makefile.toml b/Makefile.toml index e01af30..5371b95 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -58,6 +58,9 @@ fn main() -> std::io::Result<()> { [tasks.build] dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"] +[tasks.justrelease] +dependencies = ["build-examples-release", "pack-roms"] + [tasks.test] command = "cargo" args = ["test", "--lib"] diff --git a/book/src/02-concepts/02-bios.md b/book/src/02-concepts/02-bios.md index dadcf2b..4ab245d 100644 --- a/book/src/02-concepts/02-bios.md +++ b/book/src/02-concepts/02-bios.md @@ -237,3 +237,5 @@ them that enumerating them all here wouldn't serve much purpose. Which is not to say that we'll never cover any BIOS functions in this book! Instead, we'll simply mention them when whenever they're relevent to the task at hand (such as controlling sound or waiting for vblank). + +//TODO: list/name all BIOS functions as well as what they relate to elsewhere. diff --git a/src/builtins.rs b/src/builtins.rs index 048b5bf..bcda21f 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -5,14 +5,40 @@ //! You shouldn't need to call anything in here yourself, it just has to be in //! the translation unit and LLVM will find it. +//TODO: make 64 bit too #[no_mangle] -pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize { +#[cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))] +pub extern "C" fn __clzsi2(mut x: usize) -> usize { let mut y: usize; - let mut n: usize = 32; - y = x >> 16; - if y != 0 { - n = n - 16; - x = y; + let mut n: usize = { + #[cfg(target_pointer_width = "64")] + { + 64 + } + #[cfg(target_pointer_width = "32")] + { + 32 + } + #[cfg(target_pointer_width = "16")] + { + 16 + } + }; + #[cfg(target_pointer_width = "64")] + { + y = x >> 32; + if y != 0 { + n = n - 32; + x = y; + } + } + #[cfg(target_pointer_width = "32")] + { + y = x >> 16; + if y != 0 { + n = n - 16; + x = y; + } } y = x >> 8; if y != 0 { @@ -36,3 +62,16 @@ pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize { n - x } } + +#[test] +fn __clzsi2_test() { + let mut i = 1 << 63; + while i > 0 { + assert_eq!(__clzsi2(i), i.leading_zeros() as usize); + i >>= 1; + } +} + +// TODO: add some shims +// #[no_mangle] extern "aapcs" fn __aeabi_uidiv(num: u32: denom: u32) -> u32 +// #[no_mangle] extern "aapcs" fn __aeabi_idiv(num: i32: denom: i32) -> u32 diff --git a/src/core_extras.rs b/src/core_extras.rs index 38eaba4..1e8bd96 100644 --- a/src/core_extras.rs +++ b/src/core_extras.rs @@ -1,41 +1,301 @@ //! Things that I wish were in core, but aren't. -/// A simple wrapper for any `*mut T` to adjust the basic operations. +//TODO(Lokathor): reorganize as gba::core::fixed and gba::core::volatile ? + +use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; + +/// Abstracts the use of a volatile hardware address. /// -/// Read and Write are made to be volatile. Offset is made to be -/// wrapping_offset. This makes it much easier to correctly work with IO -/// Registers and all display related memory on the GBA. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +/// If you're trying to do anything other than abstract a volatile hardware +/// device then you _do not want to use this type_. Use one of the many other +/// smart pointer types. +/// +/// A volatile address doesn't store a value in the normal way: It maps to some +/// real hardware _other than_ RAM, and that hardware might have any sort of +/// strange rules. The specifics of reading and writing depend on the hardware +/// being mapped. For example, a particular address might be read only (ignoring +/// writes), write only (returning some arbitrary value if you read it), +/// "normal" read write (where you read back what you wrote), or some complex +/// read-write situation where writes have an effect but you _don't_ read back +/// what you wrote. +/// +/// As you imagine it can be very unsafe. The design of this type is set up so +/// that _creation_ is unsafe, and _use_ is safe. This gives an optimal +/// experience, since you'll use memory locations a lot more often than you try +/// to name them, on average. +/// +/// `VolAddress` is _not_ a thread safe type. If your device is multi-threaded +/// then you must arrange for synchronization in some other way. A `VolAddress` +/// _can_ be used to share data between an interrupt running on a core and a +/// thread running on that core as long as all access of that location is +/// volatile (if you're using the `asm!` macro add the "volatile" option, if +/// you're linking in ASM with the linker that's effectively volatile since the +/// compiler doesn't get a chance to mess with it). +/// +/// # Safety +/// +/// In order for values of this type to operate correctly they must follow quite +/// a few safety limits: +/// +/// * The declared address must be non-null (it uses the `NonNull` optimization +/// for better iteration results). This shouldn't be a big problem, since +/// hardware can't really live at the null address. +/// * The declared address must be aligned for the declared type of `T`. +/// * The declared address must _always_ read as something that's a valid bit +/// pattern for `T`. Don't pick any enums or things like that if your hardware +/// doesn't back it up. If there's _any_ doubt at all, you must instead read +/// or write an unsigned int of the correct bit size and then parse the bits +/// by hand. +/// * The declared address must be a part of the address space that Rust's +/// allocator and/or stack frames will never use. If you're not sure, please +/// re-read the hardware specs of your device and its memory map until you +/// know. +/// +/// The exact points of UB are if the address is ever 0, or if you ever `read` +/// or `write` with the invalid pointer. For example, if you offset to some +/// crazy (non-zero) value and then never use it that won't be an immediate +/// trigger of UB. +#[derive(Debug)] #[repr(transparent)] -pub struct VolatilePtr(pub *mut T); - -impl core::fmt::Pointer for VolatilePtr { - /// Formats exactly like the inner `*mut T`. - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{:p}", self.0) +pub struct VolAddress { + address: NonZeroUsize, + marker: PhantomData<*mut T>, +} +// Note(Lokathor): We have to hand implement all these traits because if we use +// `derive` then they only get derived if the inner `T` has the trait. However, +// since we're acting like a pointer to `T`, the capability we offer isn't +// affected by whatever type `T` ends up being. +impl Clone for VolAddress { + fn clone(&self) -> Self { + *self + } +} +impl Copy for VolAddress {} +impl PartialEq for VolAddress { + fn eq(&self, other: &Self) -> bool { + self.address == other.address + } +} +impl Eq for VolAddress {} +impl PartialOrd for VolAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } +} +impl Ord for VolAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.address.cmp(&other.address) } } -impl VolatilePtr { - /// Performs a `read_volatile`. - pub unsafe fn read(&self) -> T { - self.0.read_volatile() +impl VolAddress { + /// Constructs a new address. + /// + /// # Safety + /// + /// You must follow the standard safety rules as outlined in the type docs. + pub const unsafe fn new_unchecked(address: usize) -> Self { + VolAddress { + address: NonZeroUsize::new_unchecked(address), + marker: PhantomData, + } } - /// Performs a `write_volatile`. - pub unsafe fn write(&self, data: T) { - self.0.write_volatile(data); + /// Casts the type of `T` into type `Z`. + /// + /// # Safety + /// + /// You must follow the standard safety rules as outlined in the type docs. + pub const unsafe fn cast(self) -> VolAddress { + VolAddress { + address: self.address, + marker: PhantomData, + } } - /// Performs a `wrapping_offset`. - pub fn offset(self, count: isize) -> Self { - VolatilePtr(self.0.wrapping_offset(count)) + /// Offsets the address by `offset` slots (like `pointer::wrapping_offset`). + /// + /// # Safety + /// + /// You must follow the standard safety rules as outlined in the type docs. + pub unsafe fn offset(self, offset: isize) -> Self { + // TODO: const this + VolAddress { + address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::())), + marker: PhantomData, + } } - /// Performs a cast into some new pointer type. - pub fn cast(self) -> VolatilePtr { - VolatilePtr(self.0 as *mut Z) + /// Checks that the current target type of this address is aligned at this + /// address value. + /// + /// Technically it's a safety violation to even make a `VolAddress` that isn't + /// aligned. However, I know you're gonna try doing the bad thing, and it's + /// better to give you a chance to call `is_aligned` and potentially back off + /// from the operation or throw a `debug_assert!` or something instead of + /// triggering UB. Eventually this will be `const fn`, which will potentially + /// let you spot errors without even having to run your program. + pub fn is_aligned(self) -> bool { + // TODO: const this + self.address.get() % core::mem::align_of::() == 0 + } + + /// Makes an iterator starting here across the given number of slots. + /// + /// # Safety + /// + /// The normal safety rules must be correct for each address iterated over. + pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter { + VolAddressIter { vol_address: self, slots } + } + + // non-const and never can be. + + /// Reads a `Copy` value out of the address. + /// + /// The `Copy` bound is actually supposed to be `!Drop`, but rust doesn't + /// allow negative trait bounds. If your type isn't `Copy` you can use the + /// `read_non_copy` fallback to do an unsafe read. + /// + /// That said, I don't think that you legitimately have hardware that maps to + /// a Rust type with a `Drop` impl. If you do please tell me, I'm interested + /// to hear about it. + pub fn read(self) -> T + where + T: Copy, + { + unsafe { (self.address.get() as *mut T).read_volatile() } + } + + /// Reads a value out of the address with no trait bound. + /// + /// # Safety + /// + /// This is _not_ a move, it forms a bit duplicate of the current address + /// value. If `T` has a `Drop` trait that does anything it is up to you to + /// ensure that repeated drops do not cause UB (such as a double free). + pub unsafe fn read_non_copy(self) -> T { + (self.address.get() as *mut T).read_volatile() + } + + /// Writes a value to the address. + /// + /// Semantically, the value is moved into the `VolAddress` and then forgotten, + /// so if `T` has a `Drop` impl then that will never get executed. This is + /// "safe" under Rust's safety rules, but could cause something unintended + /// (eg: a memory leak). + pub fn write(self, val: T) { + unsafe { (self.address.get() as *mut T).write_volatile(val) } } } -// TODO: kill all this with fire +/// An iterator that produces a series of `VolAddress` values. +#[derive(Debug)] +pub struct VolAddressIter { + vol_address: VolAddress, + slots: usize, +} +impl Clone for VolAddressIter { + fn clone(&self) -> Self { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } +} +impl PartialEq for VolAddressIter { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } +} +impl Eq for VolAddressIter {} +impl Iterator for VolAddressIter { + type Item = VolAddress; + + fn next(&mut self) -> Option { + if self.slots > 0 { + let out = self.vol_address; + unsafe { + self.slots -= 1; + self.vol_address = self.vol_address.offset(1); + } + Some(out) + } else { + None + } + } +} +impl FusedIterator for VolAddressIter {} + +/// This type is like `VolAddress`, but for when you have a block of values all +/// in a row. +/// +/// This is similar to the idea of an array or a slice, but called a "block" +/// because you could _also_ construct a `[VolAddress]`, and we want to avoid +/// any accidental confusion. +#[derive(Debug)] +pub struct VolAddressBlock { + vol_address: VolAddress, + slots: usize, +} +impl Clone for VolAddressBlock { + fn clone(&self) -> Self { + VolAddressBlock { + vol_address: self.vol_address, + slots: self.slots, + } + } +} +impl PartialEq for VolAddressBlock { + fn eq(&self, other: &Self) -> bool { + self.vol_address == other.vol_address && self.slots == other.slots + } +} +impl Eq for VolAddressBlock {} + +impl VolAddressBlock { + /// Constructs a new `VolAddressBlock`. + /// + /// # Safety + /// + /// The given `VolAddress` must be valid when offset by each of `0 .. slots` + pub const unsafe fn new_unchecked(vol_address: VolAddress, slots: usize) -> Self { + VolAddressBlock { vol_address, slots } + } + + /// Checked "indexing" style access of the block, giving either a `VolAddress` or a panic. + pub fn index(self, slot: usize) -> VolAddress { + if slot < self.slots { + unsafe { self.vol_address.offset(slot as isize) } + } else { + panic!("Index Requested: {} >= Bound: {}", slot, self.slots) + } + } + + /// Unchecked indexing into the block. + /// + /// # Safety + /// + /// The slot given must be in bounds. + pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress { + // TODO: const this + self.vol_address.offset(slot as isize) + } + + /// Checked "getting" style access of the block, giving an Option value. + pub fn get(self, slot: usize) -> Option> { + if slot < self.slots { + unsafe { Some(self.vol_address.offset(slot as isize)) } + } else { + None + } + } + + /// Gives an iterator over the block's slots. + pub const fn iter(self) -> VolAddressIter { + VolAddressIter { + vol_address: self.vol_address, + slots: self.slots, + } + } +} diff --git a/src/io_registers.rs b/src/io_registers.rs index 3439b87..9bdc7fb 100644 --- a/src/io_registers.rs +++ b/src/io_registers.rs @@ -22,7 +22,7 @@ use super::*; /// LCD Control. Read/Write. /// /// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) -pub const DISPCNT: VolatilePtr = VolatilePtr(0x400_0000 as *mut u16); +pub const DISPCNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0000) }; newtype!( /// A newtype over the various display control options that you have on a GBA. @@ -98,24 +98,19 @@ pub enum DisplayControlMode { /// Assigns the given display control setting. pub fn set_display_control(setting: DisplayControlSetting) { - unsafe { - DISPCNT.write(setting.0); - } + DISPCNT.write(setting); } /// Obtains the current display control setting. pub fn display_control() -> DisplayControlSetting { - unsafe { DisplayControlSetting(DISPCNT.read()) } + DISPCNT.read() } -/// General LCD Status (STAT,LYC) -pub const DISPSTAT: VolatilePtr = VolatilePtr(0x400_0004 as *mut u16); - /// Vertical Counter (LY) -pub const VCOUNT: VolatilePtr = VolatilePtr(0x400_0006 as *mut u16); +pub const VCOUNT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0006) }; /// Obtains the current VCount value. pub fn vcount() -> u16 { - unsafe { VCOUNT.read() } + VCOUNT.read() } /// Performs a busy loop until VBlank starts. @@ -130,278 +125,8 @@ pub fn wait_until_vdraw() { while vcount() >= SCREEN_HEIGHT as u16 {} } -/// BG0 Control -pub const BG0CNT: VolatilePtr = VolatilePtr(0x400_0008 as *mut u16); - -/// BG1 Control -pub const BG1CNT: VolatilePtr = VolatilePtr(0x400_000A as *mut u16); - -/// BG2 Control -pub const BG2CNT: VolatilePtr = VolatilePtr(0x400_000C as *mut u16); - -/// BG3 Control -pub const BG3CNT: VolatilePtr = VolatilePtr(0x400_000E as *mut u16); - -/// BG0 X-Offset -pub const BG0HOFS: VolatilePtr = VolatilePtr(0x400_0010 as *mut u16); - -/// BG0 Y-Offset -pub const BG0VOFS: VolatilePtr = VolatilePtr(0x400_0012 as *mut u16); - -/// BG1 X-Offset -pub const BG1HOFS: VolatilePtr = VolatilePtr(0x400_0014 as *mut u16); - -/// BG1 Y-Offset -pub const BG1VOFS: VolatilePtr = VolatilePtr(0x400_0016 as *mut u16); - -/// BG2 X-Offset -pub const BG2HOFS: VolatilePtr = VolatilePtr(0x400_0018 as *mut u16); - -/// BG2 Y-Offset -pub const BG2VOFS: VolatilePtr = VolatilePtr(0x400_001A as *mut u16); - -/// BG3 X-Offset -pub const BG3HOFS: VolatilePtr = VolatilePtr(0x400_001C as *mut u16); - -/// BG3 Y-Offset -pub const BG3VOFS: VolatilePtr = VolatilePtr(0x400_001E as *mut u16); - -/// BG2 Rotation/Scaling Parameter A (dx) -pub const BG2PA: VolatilePtr = VolatilePtr(0x400_0020 as *mut u16); - -/// BG2 Rotation/Scaling Parameter B (dmx) -pub const BG2PB: VolatilePtr = VolatilePtr(0x400_0022 as *mut u16); - -/// BG2 Rotation/Scaling Parameter C (dy) -pub const BG2PC: VolatilePtr = VolatilePtr(0x400_0024 as *mut u16); - -/// BG2 Rotation/Scaling Parameter D (dmy) -pub const BG2PD: VolatilePtr = VolatilePtr(0x400_0026 as *mut u16); - -/// BG2 Reference Point X-Coordinate -pub const BG2X: VolatilePtr = VolatilePtr(0x400_0028 as *mut u32); - -/// BG2 Reference Point Y-Coordinate -pub const BG2Y: VolatilePtr = VolatilePtr(0x400_002C as *mut u32); - -/// BG3 Rotation/Scaling Parameter A (dx) -pub const BG3PA: VolatilePtr = VolatilePtr(0x400_0030 as *mut u16); - -/// BG3 Rotation/Scaling Parameter B (dmx) -pub const BG3PB: VolatilePtr = VolatilePtr(0x400_0032 as *mut u16); - -/// BG3 Rotation/Scaling Parameter C (dy) -pub const BG3PC: VolatilePtr = VolatilePtr(0x400_0034 as *mut u16); - -/// BG3 Rotation/Scaling Parameter D (dmy) -pub const BG3PD: VolatilePtr = VolatilePtr(0x400_0036 as *mut u16); - -/// BG3 Reference Point X-Coordinate -pub const BG3X: VolatilePtr = VolatilePtr(0x400_0038 as *mut u32); - -/// BG3 Reference Point Y-Coordinate -pub const BG3Y: VolatilePtr = VolatilePtr(0x400_003C as *mut u32); - -/// Window 0 Horizontal Dimensions -pub const WIN0H: VolatilePtr = VolatilePtr(0x400_0040 as *mut u16); - -/// Window 1 Horizontal Dimensions -pub const WIN1H: VolatilePtr = VolatilePtr(0x400_0042 as *mut u16); - -/// Window 0 Vertical Dimensions -pub const WIN0V: VolatilePtr = VolatilePtr(0x400_0044 as *mut u16); - -/// Window 1 Vertical Dimensions -pub const WIN1V: VolatilePtr = VolatilePtr(0x400_0046 as *mut u16); - -/// Inside of Window 0 and 1 -pub const WININ: VolatilePtr = VolatilePtr(0x400_0048 as *mut u16); - -/// Inside of OBJ Window & Outside of Windows -pub const WINOUT: VolatilePtr = VolatilePtr(0x400_004A as *mut u16); - -/// Mosaic Size -pub const MOSAIC: VolatilePtr = VolatilePtr(0x400_004C as *mut u16); - -/// Color Special Effects Selection -pub const BLDCNT: VolatilePtr = VolatilePtr(0x400_0050 as *mut u16); - -/// Alpha Blending Coefficients -pub const BLDALPHA: VolatilePtr = VolatilePtr(0x400_0052 as *mut u16); - -/// Brightness (Fade-In/Out) Coefficient -pub const BLDY: VolatilePtr = VolatilePtr(0x400_0054 as *mut u16); - -/// Channel 1 Sweep register (NR10) -pub const UND1CNT_L: VolatilePtr = VolatilePtr(0x400_0060 as *mut u16); - -/// Channel 1 Duty/Length/Envelope (NR11, NR12) -pub const UND1CNT_H: VolatilePtr = VolatilePtr(0x400_0062 as *mut u16); - -/// Channel 1 Frequency/Control (NR13, NR14) -pub const UND1CNT_X: VolatilePtr = VolatilePtr(0x400_0064 as *mut u16); - -/// Channel 2 Duty/Length/Envelope (NR21, NR22) -pub const UND2CNT_L: VolatilePtr = VolatilePtr(0x400_0068 as *mut u16); - -/// Channel 2 Frequency/Control (NR23, NR24) -pub const UND2CNT_H: VolatilePtr = VolatilePtr(0x400_006C as *mut u16); - -/// Channel 3 Stop/Wave RAM select (NR30) -pub const UND3CNT_L: VolatilePtr = VolatilePtr(0x400_0070 as *mut u16); - -/// Channel 3 Length/Volume (NR31, NR32) -pub const UND3CNT_H: VolatilePtr = VolatilePtr(0x400_0072 as *mut u16); - -/// Channel 3 Frequency/Control (NR33, NR34) -pub const UND3CNT_X: VolatilePtr = VolatilePtr(0x400_0074 as *mut u16); - -/// Channel 4 Length/Envelope (NR41, NR42) -pub const UND4CNT_L: VolatilePtr = VolatilePtr(0x400_0078 as *mut u16); - -/// Channel 4 Frequency/Control (NR43, NR44) -pub const UND4CNT_H: VolatilePtr = VolatilePtr(0x400_007C as *mut u16); - -/// Control Stereo/Volume/Enable (NR50, NR51) -pub const UNDCNT_L: VolatilePtr = VolatilePtr(0x400_0080 as *mut u16); - -/// Control Mixing/DMA Control -pub const UNDCNT_H: VolatilePtr = VolatilePtr(0x400_0082 as *mut u16); - -/// Control Sound on/off (NR52) -pub const UNDCNT_X: VolatilePtr = VolatilePtr(0x400_0084 as *mut u16); - -/// Sound PWM Control -pub const UNDBIAS: VolatilePtr = VolatilePtr(0x400_0088 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM0_L: VolatilePtr = VolatilePtr(0x400_0090 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM0_H: VolatilePtr = VolatilePtr(0x400_0092 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM1_L: VolatilePtr = VolatilePtr(0x400_0094 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM1_H: VolatilePtr = VolatilePtr(0x400_0096 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM2_L: VolatilePtr = VolatilePtr(0x400_0098 as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM2_H: VolatilePtr = VolatilePtr(0x400_009A as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM3_L: VolatilePtr = VolatilePtr(0x400_009C as *mut u16); - -/// Channel 3 Wave Pattern RAM (W/R) -pub const WAVE_RAM3_H: VolatilePtr = VolatilePtr(0x400_009E as *mut u16); - -/// Channel A FIFO, Data 0-3 -pub const FIFO_A: VolatilePtr = VolatilePtr(0x400_00A0 as *mut u32); - -/// Channel B FIFO, Data 0-3 -pub const FIFO_B: VolatilePtr = VolatilePtr(0x400_00A4 as *mut u32); - -/// DMA 0 Source Address -pub const DMA0SAD: VolatilePtr = VolatilePtr(0x400_00B0 as *mut u32); - -/// DMA 0 Destination Address -pub const DMA0DAD: VolatilePtr = VolatilePtr(0x400_00B4 as *mut u32); - -/// DMA 0 Word Count -pub const DMA0CNT_L: VolatilePtr = VolatilePtr(0x400_00B8 as *mut u16); - -/// DMA 0 Control -pub const DMA0CNT_H: VolatilePtr = VolatilePtr(0x400_00BA as *mut u16); - -/// DMA 1 Source Address -pub const DMA1SAD: VolatilePtr = VolatilePtr(0x400_00BC as *mut u32); - -/// DMA 1 Destination Address -pub const DMA1DAD: VolatilePtr = VolatilePtr(0x400_00C0 as *mut u32); - -/// DMA 1 Word Count -pub const DMA1CNT_L: VolatilePtr = VolatilePtr(0x400_00C4 as *mut u16); - -/// DMA 1 Control -pub const DMA1CNT_H: VolatilePtr = VolatilePtr(0x400_00C6 as *mut u16); - -/// DMA 2 Source Address -pub const DMA2SAD: VolatilePtr = VolatilePtr(0x400_00C8 as *mut u32); - -/// DMA 2 Destination Address -pub const DMA2DAD: VolatilePtr = VolatilePtr(0x400_00CC as *mut u32); - -/// DMA 2 Word Count -pub const DMA2CNT_L: VolatilePtr = VolatilePtr(0x400_00D0 as *mut u16); - -/// DMA 2 Control -pub const DMA2CNT_H: VolatilePtr = VolatilePtr(0x400_00D2 as *mut u16); - -/// DMA 3 Source Address -pub const DMA3SAD: VolatilePtr = VolatilePtr(0x400_00D4 as *mut u32); - -/// DMA 3 Destination Address -pub const DMA3DAD: VolatilePtr = VolatilePtr(0x400_00D8 as *mut u32); - -/// DMA 3 Word Count -pub const DMA3CNT_L: VolatilePtr = VolatilePtr(0x400_00DC as *mut u16); - -/// DMA 3 Control -pub const DMA3CNT_H: VolatilePtr = VolatilePtr(0x400_00DE as *mut u16); - -/// Timer 0 Counter/Reload -pub const TM0D: VolatilePtr = VolatilePtr(0x400_0100 as *mut u16); - -/// Timer 0 Control -pub const TM0CNT: VolatilePtr = VolatilePtr(0x400_0102 as *mut u16); - -/// Timer 1 Counter/Reload -pub const TM1D: VolatilePtr = VolatilePtr(0x400_0104 as *mut u16); - -/// Timer 1 Control -pub const TM1CNT: VolatilePtr = VolatilePtr(0x400_0106 as *mut u16); - -/// Timer 2 Counter/Reload -pub const TM2D: VolatilePtr = VolatilePtr(0x400_0108 as *mut u16); - -/// Timer 2 Control -pub const TM2CNT: VolatilePtr = VolatilePtr(0x400_010A as *mut u16); - -/// Timer 3 Counter/Reload -pub const TM3D: VolatilePtr = VolatilePtr(0x400_010C as *mut u16); - -/// Timer 3 Control -pub const TM3CNT: VolatilePtr = VolatilePtr(0x400_010E as *mut u16); - -/// SIO Data (Normal-32bit Mode; shared with below) -pub const SIODATA32: VolatilePtr = VolatilePtr(0x400_0120 as *mut u32); - -/// SIO Data 0 (Parent) (Multi-Player Mode) -pub const SIOMULTI0: VolatilePtr = VolatilePtr(0x400_0120 as *mut u16); - -/// SIO Data 1 (1st Child) (Multi-Player Mode) -pub const SIOMULTI1: VolatilePtr = VolatilePtr(0x400_0122 as *mut u16); - -/// SIO Data 2 (2nd Child) (Multi-Player Mode) -pub const SIOMULTI2: VolatilePtr = VolatilePtr(0x400_0124 as *mut u16); - -/// SIO Data 3 (3rd Child) (Multi-Player Mode) -pub const SIOMULTI3: VolatilePtr = VolatilePtr(0x400_0126 as *mut u16); - -/// SIO Control Register -pub const SIOCNT: VolatilePtr = VolatilePtr(0x400_0128 as *mut u16); - -/// D SIO Data (Local of MultiPlayer; shared below) -pub const SIOMLT_SEN: VolatilePtr = VolatilePtr(0x400_012A as *mut u16); - -/// SIO Data (Normal-8bit and UART Mode) -pub const SIODATA8: VolatilePtr = VolatilePtr(0x400_012A as *mut u16); - /// Key Status -pub const KEYINPUT: VolatilePtr = VolatilePtr(0x400_0130 as *mut u16); +const KEYINPUT: VolAddress = unsafe { VolAddress::new_unchecked(0x400_0130) }; /// A "tribool" value helps us interpret the arrow pad. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -467,35 +192,5 @@ pub fn key_input() -> KeyInput { // Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6 // unused bits are always low, so we XOR with this mask to get a result where // the only active bits are currently pressed keys. - unsafe { KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) } + KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) } - -/// Key Interrupt Control -pub const KEYCNT: VolatilePtr = VolatilePtr(0x400_0132 as *mut u16); - -/// SIO Mode Select/General Purpose Data -pub const RCNT: VolatilePtr = VolatilePtr(0x400_0134 as *mut u16); - -/// SIO JOY Bus Control -pub const JOYCNT: VolatilePtr = VolatilePtr(0x400_0140 as *mut u16); - -/// SIO JOY Bus Receive Data -pub const JOY_RECV: VolatilePtr = VolatilePtr(0x400_0150 as *mut u32); - -/// SIO JOY Bus Transmit Data -pub const JOY_TRANS: VolatilePtr = VolatilePtr(0x400_0154 as *mut u32); - -/// SIO JOY Bus Receive Status -pub const JOYSTAT: VolatilePtr = VolatilePtr(0x400_0158 as *mut u16); - -/// Interrupt Enable Register -pub const IE: VolatilePtr = VolatilePtr(0x400_0200 as *mut u16); - -/// Interrupt Request Flags / IRQ Acknowledge -pub const IF: VolatilePtr = VolatilePtr(0x400_0202 as *mut u16); - -/// Game Pak Waitstate Control -pub const WAITCNT: VolatilePtr = VolatilePtr(0x400_0204 as *mut u16); - -/// Interrupt Master Enable Register -pub const IME: VolatilePtr = VolatilePtr(0x400_0208 as *mut u16); diff --git a/src/lib.rs b/src/lib.rs index c172301..1ddf7d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![feature(asm)] +#![feature(const_int_wrapping)] +#![feature(min_const_unsafe_fn)] #![warn(missing_docs)] #![allow(clippy::cast_lossless)] #![deny(clippy::float_arithmetic)] @@ -68,3 +70,159 @@ pub mod io_registers; pub mod video_ram; pub(crate) use crate::video_ram::*; + +/// Performs unsigned divide and remainder, gives None if dividing by 0. +pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> { + if denom == 0 { + None + } else { + Some(unsafe { divrem_u32_unchecked(numer, denom) }) + } +} + +/// Performs divide and remainder, no check for 0 division. +/// +/// # Safety +/// +/// If you call this with a denominator of 0 the result is implementation +/// defined (not literal UB) including but not limited to: an infinite loop, +/// panic on overflow, or incorrect output. +pub unsafe fn divrem_u32_unchecked(numer: u32, denom: u32) -> (u32, u32) { + if (numer >> 5) < denom { + divrem_u32_simple(numer, denom) + } else { + divrem_u32_non_restoring(numer, denom) + } +} + +/// The simplest form of division. If N is too much larger than D this will be +/// extremely slow. If N is close enough to D then it will likely be faster than +/// the non_restoring form. +fn divrem_u32_simple(mut numer: u32, denom: u32) -> (u32, u32) { + let mut quot = 0; + while numer >= denom { + numer -= denom; + quot += 1; + } + (quot, numer) +} + +/// Takes a fixed quantity of time based on the bit width of the number (in this +/// case 32). +fn divrem_u32_non_restoring(numer: u32, denom: u32) -> (u32, u32) { + let mut r: i64 = numer as i64; + let d: i64 = (denom as i64) << 32; + let mut q: u32 = 0; + let mut i = 1 << 31; + while i > 0 { + if r >= 0 { + q |= i; + r = 2 * r - d; + } else { + r = 2 * r + d; + } + i >>= 1; + } + q = q - !q; + if r < 0 { + q = q - 1; + r = r + d; + } + r = r >> 32; + debug_assert!(r >= 0); + debug_assert!(r <= core::u32::MAX as i64); + (q, r as u32) +} + +/// Performs signed divide and remainder, gives None if dividing by 0 or +/// computing `MIN/-1` +pub fn divrem_i32(numer: i32, denom: i32) -> Option<(i32, i32)> { + if denom == 0 || (numer == core::i32::MIN && denom == -1) { + None + } else { + Some(unsafe { divrem_i32_unchecked(numer, denom) }) + } +} + +/// Performs signed divide and remainder, no check for 0 division or `MIN/-1`. +/// +/// # Safety +/// +/// * If you call this with a denominator of 0 the result is implementation +/// defined (not literal UB) including but not limited to: an infinite loop, +/// panic on overflow, or incorrect output. +/// * If you call this with `MIN/-1` you'll get a panic in debug or just `MIN` +/// in release (which is incorrect), because of how twos-compliment works. +pub unsafe fn divrem_i32_unchecked(numer: i32, denom: i32) -> (i32, i32) { + let unsigned_numer = numer.abs() as u32; + let unsigned_denom = denom.abs() as u32; + let opposite_sign = (numer ^ denom) < 0; + let (udiv, urem) = if (numer >> 5) < denom { + divrem_u32_simple(unsigned_numer, unsigned_denom) + } else { + divrem_u32_non_restoring(unsigned_numer, unsigned_denom) + }; + if opposite_sign { + if numer < 0 { + (-(udiv as i32), -(urem as i32)) + } else { + (-(udiv as i32), urem as i32) + } + } else { + if numer < 0 { + (udiv as i32, -(urem as i32)) + } else { + (udiv as i32, urem as i32) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::quickcheck; + + // We have an explicit property on the non_restoring division + quickcheck! { + fn divrem_u32_non_restoring_prop(num: u32, denom: u32) -> bool { + if denom > 0 { + divrem_u32_non_restoring(num, denom) == (num / denom, num % denom) + } else { + true + } + } + } + + // We have an explicit property on the simple division + quickcheck! { + fn divrem_u32_simple_prop(num: u32, denom: u32) -> bool { + if denom > 0 { + divrem_u32_simple(num, denom) == (num / denom, num % denom) + } else { + true + } + } + } + + // Test the u32 wrapper + quickcheck! { + fn divrem_u32_prop(num: u32, denom: u32) -> bool { + if denom > 0 { + divrem_u32(num, denom).unwrap() == (num / denom, num % denom) + } else { + divrem_u32(num, denom).is_none() + } + } + } + + // test the i32 wrapper + quickcheck! { + fn divrem_i32_prop(num: i32, denom: i32) -> bool { + if denom == 0 || num == core::i32::MIN && denom == -1 { + divrem_i32(num, denom).is_none() + } else { + divrem_i32(num, denom).unwrap() == (num / denom, num % denom) + } + } + } +} diff --git a/src/video_ram.rs b/src/video_ram.rs index fd47355..232249f 100644 --- a/src/video_ram.rs +++ b/src/video_ram.rs @@ -30,6 +30,8 @@ pub const SCREEN_HEIGHT: isize = 160; /// value as just being a `usize`. pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000; +const MODE3_VRAM: VolAddress = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) }; + /// Draws a pixel to the screen while in Display Mode 3, with bounds checks. /// /// # Panics @@ -53,7 +55,7 @@ pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) { /// * `col` must be in `0..SCREEN_WIDTH` /// * `row` must be in `0..SCREEN_HEIGHT` pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) { - VolatilePtr(VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); + MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color); } /// Reads the given pixel of video memory according to Mode 3 placement. @@ -63,7 +65,7 @@ pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) { /// If the location is out of bounds you get `None`. pub fn mode3_read_pixel(col: isize, row: isize) -> Option { if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT { - unsafe { Some(VolatilePtr(VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH).read()) } + unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) } } else { None } @@ -74,9 +76,8 @@ pub unsafe fn mode3_clear_screen(color: u16) { // TODO: use DMA? let color = color as u32; let bulk_color = color << 16 | color; - let mut ptr = VolatilePtr(VRAM_BASE_ADDRESS as *mut u32); - for _ in 0..(SCREEN_HEIGHT * SCREEN_WIDTH / 2) { - ptr.write(bulk_color); - ptr = ptr.offset(1); + let block: VolAddressBlock = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize); + for b in block.iter() { + b.write(bulk_color); } }