diff --git a/Makefile.toml b/Makefile.toml index 66d47ec..e01af30 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -43,10 +43,10 @@ fn main() -> std::io::Result<()> { std::process::Command::new("arm-none-eabi-objcopy").args( &["-O", "binary", &format!("target/thumbv4-none-agb/release/examples/{}",name), - &format!("target/example-{}.gba",name)]) + &format!("target/{}.gba",name)]) .output().expect("failed to objcopy!"); std::process::Command::new("gbafix").args( - &[&format!("target/example-{}.gba",name)]) + &[&format!("target/{}.gba",name)]) .output().expect("failed to gbafix!"); } } diff --git a/README.md b/README.md index fdc60f9..0233d8f 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,19 @@ # gba -A crate that helps you make GameBoy Advance (GBA) games +This repository is both a [Tutorial Book](https://rust-console.github.io/gba/) +that teaches you what you need to know to write Rust games for the GameBoy +Advance (GBA), and also a [crate](https://crates.io/crates/gba) that you can +use to do the same. -# First Time Setup +## First Time Setup -This crate requires a fair amount of special setup. All of the steps are -detailed for you [in the 0th chapter of the -book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html) that goes with this -crate. +Writing a Rust program for the GBA requires a fair amount of special setup. All +of the steps are detailed for you [in the Introduction chapter of the +book](https://rust-console.github.io/gba/00-introduction/03-development-setup.html). -If you've done the global setup once before and just want to get a new project -started quickly we got you covered: +If you've done the described global setup once before and just want to get a new +project started quickly we got you covered: ```sh curl https://raw.githubusercontent.com/rust-console/gba/master/init.sh -sSf | bash -s APP_NAME diff --git a/book/src/00-introduction/00-index.md b/book/src/00-introduction/00-index.md index 6663e2b..4a75dba 100644 --- a/book/src/00-introduction/00-index.md +++ b/book/src/00-introduction/00-index.md @@ -1,6 +1,6 @@ # Introduction -This is the book for learning how to write GBA games in Rust. +This is the book for learning how to write GameBoy Advance (GBA) games in Rust. I'm **Lokathor**, the main author of the book. There's also **Ketsuban** who provides the technical advisement, reviews the PRs, and keeps my crazy in check. @@ -10,7 +10,12 @@ of the pages listed in the Table Of Contents. ## Feedback -It's also often hard to tell when you've explained something properly to someone -who doesn't understand the concept yet. Please, if things don't make sense then -[file an issue](https://github.com/rust-console/gba/issues) about it so I know -where things need to improve. +It's very often hard to tell when you've explained something properly. In the +same way that your brain will read over small misspellings and correct things +into the right word, if an explanation for something you already understand +accidentally skips over some small detail then your brain can fill in the gaps +without you realizing it. + +**Please**, if things don't make sense then [file an +issue](https://github.com/rust-console/gba/issues) about it so I know where +things need to improve. diff --git a/book/src/00-introduction/04-hello-magic.md b/book/src/00-introduction/04-hello-magic.md index dd36821..5d8a679 100644 --- a/book/src/00-introduction/04-hello-magic.md +++ b/book/src/00-introduction/04-hello-magic.md @@ -21,8 +21,8 @@ goes: `hello_magic.rs`: ```rust -#![feature(start)] #![no_std] +#![feature(start)] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/book/src/00-introduction/06-help_and_resources.md b/book/src/00-introduction/05-help_and_resources.md similarity index 100% rename from book/src/00-introduction/06-help_and_resources.md rename to book/src/00-introduction/05-help_and_resources.md diff --git a/book/src/01-limitations/00-index.md b/book/src/01-limitations/00-index.md deleted file mode 100644 index 71e12e6..0000000 --- a/book/src/01-limitations/00-index.md +++ /dev/null @@ -1 +0,0 @@ -# GBA Limitations diff --git a/book/src/01-limitations/01-no_floats.md b/book/src/01-limitations/01-no_floats.md deleted file mode 100644 index e27e28b..0000000 --- a/book/src/01-limitations/01-no_floats.md +++ /dev/null @@ -1 +0,0 @@ -# No Floats diff --git a/book/src/01-limitations/02-core_only.md b/book/src/01-limitations/02-core_only.md deleted file mode 100644 index c213ee4..0000000 --- a/book/src/01-limitations/02-core_only.md +++ /dev/null @@ -1 +0,0 @@ -# Core Only diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md deleted file mode 100644 index 693d129..0000000 --- a/book/src/01-limitations/03-volatile_destination.md +++ /dev/null @@ -1 +0,0 @@ -# Volatile Destination diff --git a/book/src/01-quirks/00-index.md b/book/src/01-quirks/00-index.md new file mode 100644 index 0000000..8df76e7 --- /dev/null +++ b/book/src/01-quirks/00-index.md @@ -0,0 +1,9 @@ +# Quirks + +The GBA supports a lot of totally normal Rust code exactly like you'd think. + +However, it also is missing a lot of what you might expect, and sometimes we +have to do things in slightly weird ways. + +We start the book by covering the quirks our code will have, just to avoid too +many surprises later. diff --git a/book/src/01-quirks/01-no_std.md b/book/src/01-quirks/01-no_std.md new file mode 100644 index 0000000..13faa72 --- /dev/null +++ b/book/src/01-quirks/01-no_std.md @@ -0,0 +1,116 @@ +# No Std + +First up, as you already saw in the `hello_magic` code, we have to use the +`#![no_std]` outer attribute on our program when we target the GBA. You can find +some info about `no_std` in two official sources: + +* [unstable + book section](https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib) +* [embedded + book section](https://rust-embedded.github.io/book/intro/no-std.html?highlight=no_std#a--no_std--rust-environment) + +The unstable book is borderline useless here because it's describing too many +things in too many words. The embedded book is much better, but still fairly +terse. + +## Bare Metal + +The GBA falls under what the Embedded Book calls "Bare Metal Environments". +Basically, the machine powers on and immediately begins executing some ASM code. +Our ASM startup was provided by `Ketsuban` (check the `crt0.s` file). We'll go +over _how_ it works much later on, for now it's enough to know that it does +work, and eventually control passes into Rust code. + +On the rust code side of things, we determine our starting point with the +`#[start]` attribute on our `main` function. The `main` function also has a +specific type signature that's different from the usual `main` that you'd see in +Rust. I'd tell you to read the unstable-book entry on `#[start]` but they +[literally](https://doc.rust-lang.org/unstable-book/language-features/start.html) +just tell you to look at the [tracking issue for +it](https://github.com/rust-lang/rust/issues/29633) instead, and that's not very +helpful either. Basically it just _has_ to be declared the way it is, even +though there's nothing passing in the arguments and there's no place that the +return value will go. The compiler won't accept it any other way. + +## No Standard Library + +The Embedded Book tells us that we can't use the standard library, but we get +access to something called "libcore", which sounds kinda funny. What they're +talking about is just [the core +crate](https://doc.rust-lang.org/core/index.html), which is called `libcore` +within the rust repository for historical reasons. + +The `core` crate is actually still a really big portion of Rust. The standard +library doesn't actually hold too much code (relatively speaking), instead it +just takes code form other crates and then re-exports it in an organized way. So +with just `core` instead of `std`, what are we missing? + +In no particular order: + +* Allocation +* Clock +* Network +* File System + +The allocation system and all the types that you can use if you have a global +allocator are neatly packaged up in the +[alloc](https://doc.rust-lang.org/alloc/index.html) crate. The rest isn't as +nicely organized. + +It's _possible_ to implement a fair portion of the entire standard library +within a GBA context and make the rest just panic if you try to use it. However, +do you really need all that? Eh... probably not? + +* We don't need a file system, because all of our data is just sitting there in + the ROM for us to use. When programming we can organize our `const` data into + modules and such to keep it organized, but once the game is compiled it's just + one huge flat address space. TODO: Parasyte says that a FS can be handy even + if it's all just ReadOnly, so we'll eventually talk about how you might set up + such a thing I guess, since we'll already be talking about replacements for + three of the other four things we "lost". Maybe we'll make Parasyte write that + section. +* Networking, well, the GBA has a Link Cable you can use to communicate with + another GBA, but it's not really like a unix socket with TCP, so the standard + Rust networking isn't a very good match. +* Clock is actually two different things at once. One is the ability to store + the time long term, which is a bit of hardware that some gamepaks have in them + (eg: pokemon ruby/sapphire/emerald). The GBA itself can't keep time while + power is off. However, the second part is just tracking time moment to moment, + which the GBA can totally do. We'll see how to access the timers soon enough. + +Which just leaves us with allocation. Do we need an allocator? Depends on your +game. For demos and small games you probably don't need one. For bigger games +you'll maybe want to get an allocator going eventually. It's in some sense a +crutch, but it's a very useful one. + +So I promise that at some point we'll cover how to get an allocator going. +Either a Rust Global Allocator (if practical), which would allow for a lot of +the standard library types to be used "for free" once it was set up, or just a +custom allocator that's GBA specific if Rust's global allocator style isn't a +good fit for the GBA (I honestly haven't looked into it). + +## LLVM Intrinsics + +TODO: explain that we'll occasionally have to provide some intrinsics. + +## Bare Metal Panic + +TODO: expand this + +* Write `0xC0DE` to `0x4fff780` (`u16`) to enable mGBA logging. Write any other + value to disable it. +* Read `0x4fff780` (`u16`) to check mGBA logging status. + * You get `0x1DEA` if debugging is active. + * Otherwise you get standard open bus nonsense values. +* Write your message into the virtual `[u8; 255]` array starting at `0x4fff600`. + mGBA will interpret these bytes as a CString value. +* Write `0x100` PLUS the message level to `0x4fff700` (`u16`) when you're ready + to send a message line: + * 0: Fatal (halts execution with a popup) + * 1: Error + * 2: Warning + * 3: Info + * 4: Debug +* Sending the message also automatically zeroes the output buffer. +* View the output within the "Tools" menu, "View Logs...". Note that the Fatal + message, if any doesn't get logged. diff --git a/book/src/01-quirks/02-fixed_only.md b/book/src/01-quirks/02-fixed_only.md new file mode 100644 index 0000000..49e507a --- /dev/null +++ b/book/src/01-quirks/02-fixed_only.md @@ -0,0 +1,13 @@ +# Fixed Only + +In addition to not having the standard library available, we don't even have a +floating point unit available! We can't do floating point math in hardware! We +could still do floating point math as software computations if we wanted, but +that's a slow, slow thing to do. + +Instead let's learn about another way to have fractional values called "Fixed +Point" + +## Fixed Point + +TODO: describe fixed point, make some types, do the impls, all that. diff --git a/book/src/01-quirks/03-volatile_destination.md b/book/src/01-quirks/03-volatile_destination.md new file mode 100644 index 0000000..f03c62c --- /dev/null +++ b/book/src/01-quirks/03-volatile_destination.md @@ -0,0 +1,345 @@ +# Volatile Destination + +TODO: replace all this one "the rant" is finalized + +There's a reasonable chance that you've never heard of `volatile` before, so +what's that? Well, it's a term that can be used in more than one context, but +basically it means "get your grubby mitts off my stuff you over-eager compiler". + +## Volatile Memory + +The first, and most common, form of volatile thing is volatile memory. Volatile +memory can change without your program changing it, usually because it's not a +location in RAM, but instead some special location that represents an actual +hardware device, or part of a hardware device perhaps. The compiler doesn't know +what's going on in this situation, but when the program is actually run and the +CPU gets an instruction to read or write from that location, instead of just +accessing some place in RAM like with normal memory, it accesses whatever bit of +hardware and does _something_. The details of that something depend on the +hardware, but what's important is that we need to actually, definitely execute +that read or write instruction. + +This is not how normal memory works. Normally when the compiler +sees us write values into variables and read values from variables, it's free to +optimize those expressions and eliminate some of the reads and writes if it can, +and generally try to save us time. Maybe it even knows some stuff about the data +dependencies in our expressions and so it does some of the reads or writes out +of order from what the source says, because the compiler knows that it won't +actually make a difference to the operation of the program. A good and helpful +friend, that compiler. + +Volatile memory works almost the opposite way. With volatile memory we +need the compiler to _definitely_ emit an instruction to do a read or write and +they need to happen _exactly_ in the order that we say to do it. Each volatile +read or write might have any sort of side effect that the compiler +doesn't know about, and it shouldn't try to be clever about the optimization. Just do what we +say, please. + +In Rust, we don't mark volatile things as being a separate type of thing, +instead we use normal raw pointers and then call the +[read_volatile](https://doc.rust-lang.org/core/ptr/fn.read_volatile.html) and +[write_volatile](https://doc.rust-lang.org/core/ptr/fn.write_volatile.html) +functions (also available as methods, if you like), which then delegate to the +LLVM +[volatile_load](https://doc.rust-lang.org/core/intrinsics/fn.volatile_load.html) +and +[volatile_store](https://doc.rust-lang.org/core/intrinsics/fn.volatile_store.html) +intrinsics. In C and C++ you can tag a pointer as being volatile and then any +normal read and write with it becomes the volatile version, but in Rust we have +to remember to use the correct alternate function instead. + +I'm told by the experts that this makes for a cleaner and saner design from a +_language design_ perspective, but it really kinda screws us when doing low +level code. References, both mutable and shared, aren't volatile, so they +compile into normal reads and writes. This means we can't do anything we'd +normally do in Rust that utilizes references of any kind. Volatile blocks of +memory can't use normal `.iter()` or `.iter_mut()` based iteration (which give +`&T` or `&mut T`), and they also can't use normal `Index` and `IndexMut` sugar +like `a + x[i]` or `x[i] = 7`. + +Unlike with normal raw pointers, this pain point never goes away. There's no way +to abstract over the difference with Rust as it exists now, you'd need to +actually adjust the core language by adding an additional pointer type (`*vol +T`) and possibly a reference type to go with it (`&vol T`) to get the right +semantics. And then you'd need an `IndexVol` trait, and you'd need +`.iter_vol()`, and so on for every other little thing. It would be a lot of +work, and the Rust developers just aren't interested in doing all that for such +a limited portion of their user population. We'll just have to deal with not +having any syntax sugar. + +### VolatilePtr + +No syntax sugar doesn't mean we can't at least make things a little easier for +ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`. +One of those "manual" newtypes I mentioned where we can't use our nice macro. + +```rust +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct VolatilePtr(pub *mut T); +``` + +Obviously we want to be able to read and write: + +```rust +impl VolatilePtr { + /// Performs a `read_volatile`. + pub unsafe fn read(self) -> T { + self.0.read_volatile() + } + + /// Performs a `write_volatile`. + pub unsafe fn write(self, data: T) { + self.0.write_volatile(data); + } +``` + +And we want a way to jump around when we do have volatile memory that's in +blocks. This is where we can get ourselves into some trouble if we're not +careful. We have to decide between +[offset](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset) and +[wrapping_offset](https://doc.rust-lang.org/std/primitive.pointer.html#method.wrapping_offset). +The difference is that `offset` optimizes better, but also it can be Undefined +Behavior if the result is not "in bounds or one byte past the end of the same +allocated object". I asked [ubsan](https://github.com/ubsan) (who is the expert +that you should always listen to on matters like this) what that means exactly +when memory mapped hardware is involved (since we never allocated anything), and +the answer was that you _can_ use an `offset` in statically memory mapped +situations like this as long as you don't use it to jump to the address of +something that Rust itself allocated at some point. Cool, we all like being able +to use the one that optimizes better. Unfortunately, the downside to using +`offset` instead of `wrapping_offset` is that with `offset`, it's Undefined +Behavior _simply to calculate the out of bounds result_ (with `wrapping_offset` +it's not Undefined Behavior until you _use_ the out of bounds result). We'll +have to be quite careful when we're using `offset`. + +```rust + /// Performs a normal `offset`. + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.offset(count)) + } +``` + +Now, one thing of note is that doing the `offset` isn't `const`. The math for it +is something that's possible to do in a `const` way of course, but Rust +basically doesn't allow you to fiddle raw pointers much during `const` right +now. Maybe in the future that will improve. + +If we did want to have a `const` function for finding the correct address within +a volatile block of memory we'd have to do all the math using `usize` values, +and then cast that value into being a pointer once we were done. It'd look +something like this: + +```rust +const fn address_index(address: usize, index: usize) -> usize { + address + (index * std::mem::size_of::()) +} +``` + +But, back to methods for `VolatilePtr`, well we sometimes want to be able to +cast a `VolatilePtr` between pointer types. Since we won't be able to do that +with `as`, we'll have to write a method for it: + +```rust + /// Performs a cast into some new pointer type. + pub fn cast(self) -> VolatilePtr { + VolatilePtr(self.0 as *mut Z) + } +``` + +### Volatile Iterating + +How about that `Iterator` stuff I said we'd be missing? We can actually make +_an_ Iterator available, it's just not the normal "iterate by shared reference +or unique reference" Iterator. Instead, it's more like a "throw out a series of +`VolatilePtr` values" style Iterator. Other than that small difference it's +totally normal, and we'll be able to use map and skip and take and all those +neat methods. + +So how do we make this thing we need? First we check out the [Implementing +Iterator](https://doc.rust-lang.org/core/iter/index.html#implementing-iterator) +section in the core documentation. It says we need a struct for holding the +iterator state. Right-o, probably something like this: + +```rust +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct VolatilePtrIter { + vol_ptr: VolatilePtr, + slots: usize, +} +``` + +And then we just implement +[core::iter::Iterator](https://doc.rust-lang.org/core/iter/trait.Iterator.html) +on that struct. Wow, that's quite the trait though! Don't worry, we only need to +implement two small things and then the rest of it comes free as a bunch of +default methods. + +So, the code that we _want_ to write looks like this: + +```rust +impl Iterator for VolatilePtrIter { + type Item = VolatilePtr; + + fn next(&mut self) -> Option> { + if self.slots > 0 { + let out = Some(self.vol_ptr); + self.slots -= 1; + self.vol_ptr = unsafe { self.vol_ptr.offset(1) }; + out + } else { + None + } + } +} +``` + +Except we _can't_ write that code. What? The problem is that we used +`derive(Clone, Copy` on `VolatilePtr`. Because of a quirk in how `derive` works, +this means `VolatilePtr` will only be `Copy` if the `T` is `Copy`, _even +though the pointer itself is always `Copy` regardless of what it points to_. +Ugh, terrible. We've got three basic ways to handle this: + +* Make the `Iterator` implementation be for ``, and then hope that we + always have types that are `Clone`. +* Hand implement every trait we want `VolatilePtr` (and `VolatilePtrIter`) to + have so that we can override the fact that `derive` is basically broken in + this case. +* Make `VolatilePtr` store a `usize` value instead of a pointer, and then cast + it to `*mut T` when we actually need to read and write. This would require us + to also store a `PhantomData` so that the type of the address is tracked + properly, which would make it a lot more verbose to construct a `VolatilePtr` + value. + +None of those options are particularly appealing. I guess we'll do the first one +because it's the least amount of up front trouble, and I don't _think_ we'll +need to be iterating non-Clone values. All we do to pick that option is add the +bound to the very start of the `impl` block, where we introduce the `T`: + +```rust +impl Iterator for VolatilePtrIter { + type Item = VolatilePtr; + + fn next(&mut self) -> Option> { + if self.slots > 0 { + let out = Some(self.vol_ptr.clone()); + self.slots -= 1; + self.vol_ptr = unsafe { self.vol_ptr.clone().offset(1) }; + out + } else { + None + } + } +} +``` + +What's going on here? Okay so our iterator has a number of slots that it'll go +over, and then when it's out of slots it starts producing `None` forever. That's +actually pretty simple. We're also masking some unsafety too. In this case, +we'll rely on the person who made the `VolatilePtrIter` to have selected the +correct number of slots. This gives us a new method for `VolatilePtr`: + +```rust + pub unsafe fn iter_slots(self, slots: usize) -> VolatilePtrIter { + VolatilePtrIter { + vol_ptr: self, + slots, + } + } +``` + +With this design, making the `VolatilePtrIter` at the start is `unsafe` (we have +to trust the caller that the right number of slots exists), and then using it +after that is totally safe (if the right number of slots was given we'll never +screw up our end of it). + +### VolatilePtr Formatting + +Also, just as a little bonus that we probably won't use, we could enable our new +pointer type to be formatted as a pointer value. + +```rust +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) + } +} +``` + +Neat! + +### VolatilePtr Complete + +That was a lot of small code blocks, let's look at it all put together: + +```rust +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct VolatilePtr(pub *mut T); +impl VolatilePtr { + pub unsafe fn read(self) -> T { + self.0.read_volatile() + } + pub unsafe fn write(self, data: T) { + self.0.write_volatile(data); + } + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.offset(count)) + } + pub fn cast(self) -> VolatilePtr { + VolatilePtr(self.0 as *mut Z) + } + pub unsafe fn iter_slots(self, slots: usize) -> VolatilePtrIter { + VolatilePtrIter { + vol_ptr: self, + slots, + } + } +} +impl core::fmt::Pointer for VolatilePtr { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{:p}", self.0) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct VolatilePtrIter { + vol_ptr: VolatilePtr, + slots: usize, +} +impl Iterator for VolatilePtrIter { + type Item = VolatilePtr; + fn next(&mut self) -> Option> { + if self.slots > 0 { + let out = Some(self.vol_ptr.clone()); + self.slots -= 1; + self.vol_ptr = unsafe { self.vol_ptr.clone().offset(1) }; + out + } else { + None + } + } +} +``` + +## Volatile ASM + +In addition to some memory locations being volatile, it's also possible for +inline assembly to be declared volatile. This is basically the same idea, "hey +just do what I'm telling you, don't get smart about it". + +Normally when you have some `asm!` it's basically treated like a function, +there's inputs and outputs and the compiler will try to optimize it so that if +you don't actually use the outputs it won't bother with doing those +instructions. However, `asm!` is basically a pure black box, so the compiler +doesn't know what's happening inside at all, and it can't see if there's any +important side effects going on. + +An example of an important side effect that doesn't have output values would be +putting the CPU into a low power state while we want for the next VBlank. This +lets us save quite a bit of battery power. It requires some setup to be done +safely (otherwise the GBA won't ever actually wake back up from the low power +state), but the `asm!` you use once you're ready is just a single instruction +with no return value. The compiler can't tell what's going on, so you just have +to say "do it anyway". diff --git a/book/src/00-introduction/05-newtype.md b/book/src/01-quirks/04-newtype.md similarity index 61% rename from book/src/00-introduction/05-newtype.md rename to book/src/01-quirks/04-newtype.md index 214250d..07244c9 100644 --- a/book/src/00-introduction/05-newtype.md +++ b/book/src/01-quirks/04-newtype.md @@ -1,9 +1,7 @@ # Newtype -There's one thing I want to get out of the way near the start of the book and it -didn't really have a good fit anywhere else in the book so it goes right here. - -We're talking about the "Newtype Pattern"! +There's a great Zero Cost abstraction that we'll be using a lot that you might +not already be familiar with: we're talking about the "Newtype Pattern"! Now, I told you to read the Rust Book before you read this book, and I'm sure you're all good students who wouldn't sneak into this book without doing the @@ -111,13 +109,31 @@ macro_rules! newtype { ``` That seems like enough for all of our examples, so we'll stop there. We could -add more things, such as making the `From` impl optional (because what if you -shouldn't unwrap it for some weird reason?), allowing for more precise -visibility controls (on both the newtype overall and the inner field), and maybe -even other things I can't think of right now. We won't really need those in our -example code for this book, so it's probably nicer to just keep the macro -simpler and quit while we're ahead. +add more things: -**As a reminder:** remember that macros have to appear _before_ they're invoked in -your source, so the `newtype` macro will always have to be at the very top of -your file, or in a module that's declared before other modules and code. +* Making the `From` impl being optional. We'd have to make the newtype + invocation be more complicated somehow, the user puts ", no-unwrap" after the + inner type declaration or something, or something like that. +* Allowing for more precise visibility controls on the wrapping type and on the + inner field. This would add a lot of line noise, so we'll just always have our + newtypes be `pub`. +* Allowing for generic newtypes, which might sound silly but that we'll actually + see an example of soon enough. To do this you might _think_ that we can change + the `:ident` declarations to `:ty`, but since we're declaring a fresh type not + using an existing type we have to accept it as an `:ident`. The way you get + around this is with a proc-macro, which is a lot more powerful but which also + requires that you write the proc-macro in an entirely other crate that gets + compiled first. We don't need that much power, so for our examples we'll go + with the macro_rules version and just do it by hand in the few cases where we + need a generic newtype. +* Allowing for `Deref` and `DerefMut`, which usually defeats the point of doing + the newtype, but maybe sometimes it's the right thing, so if you were going + for the full industrial strength version with a proc-macro and all you might + want to make that part of your optional add-ons as well the same way you might + want optional `From`. You'd probably want `From` to be "on by default" and + `Deref`/`DerefMut` to be "off by default", but whatever. + +**As a reminder:** remember that `macro_rules` macros have to appear _before_ +they're invoked in your source, so the `newtype` macro will always have to be at +the very top of your file, or if you put it in a module within your project +you'll need to declare the module before anything that uses it. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ccd7a41..6de4af7 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -6,12 +6,12 @@ * [Book Goals and Style](00-introduction/02-goals_and_style.md) * [Development Setup](00-introduction/03-development-setup.md) * [Hello, Magic](00-introduction/04-hello-magic.md) - * [Newtype](00-introduction/05-newtype.md) - * [Help and Resources](00-introduction/06-help_and_resources.md) -* [Limitations](01-limitations/00-index.md) - * [No Floats](01-limitations/01-no_floats.md) - * [Core Only](01-limitations/02-core_only.md) - * [Volatile Destination](01-limitations/03-volatile_destination.md) + * [Help and Resources](00-introduction/05-help_and_resources.md) +* [Quirks](01-quirks/00-index.md) + * [No Std](01-quirks/01-no_std.md) + * [Fixed Only](01-quirks/02-fixed_only.md) + * [Volatile Destination](01-quirks/03-volatile_destination.md) + * [Newtype](01-quirks/04-newtype.md) * [Concepts](02-concepts/00-index.md) * [CPU](02-concepts/01-cpu.md) * [BIOS](02-concepts/02-bios.md) diff --git a/examples/hello_magic.rs b/examples/hello_magic.rs index 247ba9b..75eb6bc 100644 --- a/examples/hello_magic.rs +++ b/examples/hello_magic.rs @@ -1,5 +1,5 @@ -#![feature(start)] #![no_std] +#![feature(start)] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/examples/mgba_panic_handler.rs b/examples/mgba_panic_handler.rs new file mode 100644 index 0000000..8620d26 --- /dev/null +++ b/examples/mgba_panic_handler.rs @@ -0,0 +1,109 @@ +#![no_std] +#![feature(start)] + +#[no_mangle] +pub unsafe 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; + } + y = x >> 8; + if y != 0 { + n = n - 8; + x = y; + } + y = x >> 4; + if y != 0 { + n = n - 4; + x = y; + } + y = x >> 2; + if y != 0 { + n = n - 2; + x = y; + } + y = x >> 1; + if y != 0 { + n - 2 + } else { + n - x + } +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + unsafe { + const DEBUG_ENABLE_MGBA: *mut u16 = 0x4fff780 as *mut u16; + const DEBUG_OUTPUT_BASE: *mut u8 = 0x4fff600 as *mut u8; + const DEBUG_SEND_MGBA: *mut u16 = 0x4fff700 as *mut u16; + const DEBUG_SEND_FLAG: u16 = 0x100; + const DEBUG_FATAL: u16 = 0; + const DEBUG_ERROR: u16 = 1; + DEBUG_ENABLE_MGBA.write_volatile(0xC0DE); + if DEBUG_ENABLE_MGBA.read_volatile() == 0x1DEA { + // Give the location + if let Some(location) = info.location() { + let mut out_ptr = DEBUG_OUTPUT_BASE; + let line = location.line(); + let mut line_bytes = [ + (line / 10000) as u8, + ((line / 1000) % 10) as u8, + ((line / 1000) % 10) as u8, + ((line / 10) % 10) as u8, + (line % 10) as u8, + ]; + for line_bytes_mut in line_bytes.iter_mut() { + *line_bytes_mut += b'0'; + } + for b in "Panic: " + .bytes() + .chain(location.file().bytes()) + .chain(", Line ".bytes()) + .chain(line_bytes.iter().cloned()) + .take(255) + { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } else { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in "Panic with no location info:".bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); + // Give the payload + if let Some(payload) = info.payload().downcast_ref::<&str>() { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in payload.bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } else { + let mut out_ptr = DEBUG_OUTPUT_BASE; + for b in "no payload".bytes().take(255) { + out_ptr.write_volatile(b); + out_ptr = out_ptr.offset(1); + } + } + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_ERROR); + DEBUG_SEND_MGBA.write_volatile(DEBUG_SEND_FLAG + DEBUG_FATAL); + } + } + loop {} +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + (0x400_0000 as *mut u16).write_volatile(0x0403); + (0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); + (0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); + (0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); + panic!("fumoffu!"); + } +}