From c28e66cd3e0913c5439a7d05f6c106af66ec580a Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 10:58:37 -0700 Subject: [PATCH 01/15] introduction improvements --- book/src/00-introduction/00-index.md | 13 +++++++++---- book/src/00-introduction/05-newtype.md | 22 ++++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/book/src/00-introduction/00-index.md b/book/src/00-introduction/00-index.md index 6663e2b..0567c77 100644 --- a/book/src/00-introduction/00-index.md +++ b/book/src/00-introduction/00-index.md @@ -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/05-newtype.md b/book/src/00-introduction/05-newtype.md index 214250d..02c9676 100644 --- a/book/src/00-introduction/05-newtype.md +++ b/book/src/00-introduction/05-newtype.md @@ -111,12 +111,22 @@ 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: + +* 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 then you can't use that captured type + when you declare the new wrapping type. The way you get around this is with a + proc-macro, which is a lot more powerful but which also requires that the + proc-macro be written in an entirely other crate. 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. **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 From d201ee899b9d67deb5c3ca59b37d84581f343251 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 17:01:21 -0700 Subject: [PATCH 02/15] volatile stuff --- book/src/00-introduction/05-newtype.md | 28 +++-- .../01-limitations/03-volatile_destination.md | 115 ++++++++++++++++++ 2 files changed, 133 insertions(+), 10 deletions(-) diff --git a/book/src/00-introduction/05-newtype.md b/book/src/00-introduction/05-newtype.md index 02c9676..d711917 100644 --- a/book/src/00-introduction/05-newtype.md +++ b/book/src/00-introduction/05-newtype.md @@ -120,14 +120,22 @@ add more things: 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 then you can't use that captured type - when you declare the new wrapping type. The way you get around this is with a - proc-macro, which is a lot more powerful but which also requires that the - proc-macro be written in an entirely other crate. 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. + 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 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. +**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/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index 693d129..3ce45a9 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1 +1,116 @@ # Volatile Destination + +There's a reasonable chance that you've never heard of `volatile` before, so +what's that? Well, it's a slightly overloaded term, 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 like the opposite of 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 exact 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 unknown side effect that the compiler +doesn't know about and it shouldn't try to be clever about it. 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. + +But no syntax sugar doesn't mean we can't at least do a little work for +ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`: + +```rust +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct VolatilePtr(*mut T); +``` + +Obviously we'll need some methods go with it. The basic operations are reading +and writing of course: + +```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. For this there's both +[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 for us, +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. 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_, and with +`wrapping_offset` it's not Undefined Behavior until you _use_ the out of bounds +result. + +```rust + /// Performs a `wrapping_offset`. + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.offset(count)) + } +``` + +## Volatile ASM \ No newline at end of file From bbd0617602f1ec58127d5b7bbcf8ef9681f64bea Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 21:40:41 -0700 Subject: [PATCH 03/15] volatile work --- .../01-limitations/03-volatile_destination.md | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index 3ce45a9..cbb14da 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1,8 +1,8 @@ # Volatile Destination There's a reasonable chance that you've never heard of `volatile` before, so -what's that? Well, it's a slightly overloaded term, but basically it means "get -your grubby mitts off my stuff you over-eager compiler". +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 @@ -17,7 +17,7 @@ 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 like the opposite of how normal memory works. Normally when the compiler +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 @@ -26,11 +26,11 @@ 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 exact opposite way. With volatile memory we +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 unknown side effect that the compiler -doesn't know about and it shouldn't try to be clever about it. Just do what we +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, @@ -80,12 +80,12 @@ and writing of course: ```rust impl VolatilePtr { /// Performs a `read_volatile`. - pub unsafe fn read(&self) -> T { + pub unsafe fn read(self) -> T { self.0.read_volatile() } /// Performs a `write_volatile`. - pub unsafe fn write(&self, data: T) { + pub unsafe fn write(self, data: T) { self.0.write_volatile(data); } ``` @@ -107,10 +107,46 @@ Undefined Behavior _simply to calculate the out of bounds result_, and with result. ```rust - /// Performs a `wrapping_offset`. + /// Performs a normal `offset`. pub unsafe fn offset(self, count: isize) -> Self { VolatilePtr(self.0.offset(count)) } ``` -## Volatile ASM \ No newline at end of file +Now, one thing of note is that doing the `offset` isn't `const`. If we wanted to have a `const` function for +finding the correct spot 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. In the future Rust might be +able to do it without a goofy work around, but `const` is quite limited at the moment. +It'd look something like this: + +```rust +const fn address_index(address: usize, index: usize) -> usize { + address + (index * std::mem::size_of::()) +} +``` + +We will 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 that: + +```rust + /// Performs a cast into some new pointer type. + pub fn cast(self) -> VolatilePtr { + VolatilePtr(self.0 as *mut Z) + } +``` + +TODO: iterator stuff + +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) + } +} +``` + +## Volatile ASM From 8e4299b0ad7d463fb8f12f2aa1610fd47068682a Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 11 Dec 2018 01:27:10 -0700 Subject: [PATCH 04/15] volatile complete --- .../01-limitations/03-volatile_destination.md | 239 ++++++++++++++++-- 1 file changed, 215 insertions(+), 24 deletions(-) diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index cbb14da..d635e88 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -65,17 +65,19 @@ 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. -But no syntax sugar doesn't mean we can't at least do a little work for -ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`: +### 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(*mut T); +pub struct VolatilePtr(pub *mut T); ``` -Obviously we'll need some methods go with it. The basic operations are reading -and writing of course: +Obviously we want to be able to read and write: ```rust impl VolatilePtr { @@ -91,20 +93,23 @@ impl VolatilePtr { ``` And we want a way to jump around when we do have volatile memory that's in -blocks. For this there's both +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 for us, -and the answer was that you _can_ use an `offset` in statically memory mapped +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. 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_, and with -`wrapping_offset` it's not Undefined Behavior until you _use_ the out of bounds -result. +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`. @@ -113,20 +118,25 @@ result. } ``` -Now, one thing of note is that doing the `offset` isn't `const`. If we wanted to have a `const` function for -finding the correct spot 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. In the future Rust might be -able to do it without a goofy work around, but `const` is quite limited at the moment. -It'd look something like this: +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::()) + address + (index * std::mem::size_of::()) } ``` -We will 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 that: +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. @@ -135,10 +145,116 @@ won't be able to do that with `as`, we'll have to write a method for that: } ``` -TODO: iterator stuff +### Volatile Iterating -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. +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 makes `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 { @@ -149,4 +265,79 @@ impl core::fmt::Pointer for VolatilePtr { } ``` +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". From 6a3d022d6d31b65769e87d0cca16cc1611aeeaec Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 10:58:37 -0700 Subject: [PATCH 05/15] introduction improvements --- book/src/00-introduction/00-index.md | 13 +++++++++---- book/src/00-introduction/05-newtype.md | 22 ++++++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/book/src/00-introduction/00-index.md b/book/src/00-introduction/00-index.md index 6663e2b..0567c77 100644 --- a/book/src/00-introduction/00-index.md +++ b/book/src/00-introduction/00-index.md @@ -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/05-newtype.md b/book/src/00-introduction/05-newtype.md index 214250d..02c9676 100644 --- a/book/src/00-introduction/05-newtype.md +++ b/book/src/00-introduction/05-newtype.md @@ -111,12 +111,22 @@ 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: + +* 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 then you can't use that captured type + when you declare the new wrapping type. The way you get around this is with a + proc-macro, which is a lot more powerful but which also requires that the + proc-macro be written in an entirely other crate. 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. **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 From 8b313a3edb09e9a1ae9091ef767596f84a9e0efe Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 17:01:21 -0700 Subject: [PATCH 06/15] volatile stuff --- book/src/00-introduction/05-newtype.md | 28 +++-- .../01-limitations/03-volatile_destination.md | 115 ++++++++++++++++++ 2 files changed, 133 insertions(+), 10 deletions(-) diff --git a/book/src/00-introduction/05-newtype.md b/book/src/00-introduction/05-newtype.md index 02c9676..d711917 100644 --- a/book/src/00-introduction/05-newtype.md +++ b/book/src/00-introduction/05-newtype.md @@ -120,14 +120,22 @@ add more things: 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 then you can't use that captured type - when you declare the new wrapping type. The way you get around this is with a - proc-macro, which is a lot more powerful but which also requires that the - proc-macro be written in an entirely other crate. 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. + 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 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. +**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/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index 693d129..3ce45a9 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1 +1,116 @@ # Volatile Destination + +There's a reasonable chance that you've never heard of `volatile` before, so +what's that? Well, it's a slightly overloaded term, 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 like the opposite of 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 exact 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 unknown side effect that the compiler +doesn't know about and it shouldn't try to be clever about it. 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. + +But no syntax sugar doesn't mean we can't at least do a little work for +ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`: + +```rust +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct VolatilePtr(*mut T); +``` + +Obviously we'll need some methods go with it. The basic operations are reading +and writing of course: + +```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. For this there's both +[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 for us, +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. 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_, and with +`wrapping_offset` it's not Undefined Behavior until you _use_ the out of bounds +result. + +```rust + /// Performs a `wrapping_offset`. + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.offset(count)) + } +``` + +## Volatile ASM \ No newline at end of file From 1ec9ca682c84f6397b3a02c826d7566baf09696c Mon Sep 17 00:00:00 2001 From: Lokathor Date: Mon, 10 Dec 2018 21:40:41 -0700 Subject: [PATCH 07/15] volatile work --- .../01-limitations/03-volatile_destination.md | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index 3ce45a9..cbb14da 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1,8 +1,8 @@ # Volatile Destination There's a reasonable chance that you've never heard of `volatile` before, so -what's that? Well, it's a slightly overloaded term, but basically it means "get -your grubby mitts off my stuff you over-eager compiler". +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 @@ -17,7 +17,7 @@ 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 like the opposite of how normal memory works. Normally when the compiler +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 @@ -26,11 +26,11 @@ 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 exact opposite way. With volatile memory we +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 unknown side effect that the compiler -doesn't know about and it shouldn't try to be clever about it. Just do what we +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, @@ -80,12 +80,12 @@ and writing of course: ```rust impl VolatilePtr { /// Performs a `read_volatile`. - pub unsafe fn read(&self) -> T { + pub unsafe fn read(self) -> T { self.0.read_volatile() } /// Performs a `write_volatile`. - pub unsafe fn write(&self, data: T) { + pub unsafe fn write(self, data: T) { self.0.write_volatile(data); } ``` @@ -107,10 +107,46 @@ Undefined Behavior _simply to calculate the out of bounds result_, and with result. ```rust - /// Performs a `wrapping_offset`. + /// Performs a normal `offset`. pub unsafe fn offset(self, count: isize) -> Self { VolatilePtr(self.0.offset(count)) } ``` -## Volatile ASM \ No newline at end of file +Now, one thing of note is that doing the `offset` isn't `const`. If we wanted to have a `const` function for +finding the correct spot 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. In the future Rust might be +able to do it without a goofy work around, but `const` is quite limited at the moment. +It'd look something like this: + +```rust +const fn address_index(address: usize, index: usize) -> usize { + address + (index * std::mem::size_of::()) +} +``` + +We will 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 that: + +```rust + /// Performs a cast into some new pointer type. + pub fn cast(self) -> VolatilePtr { + VolatilePtr(self.0 as *mut Z) + } +``` + +TODO: iterator stuff + +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) + } +} +``` + +## Volatile ASM From 91057a529d5c9196a23a29324d8bc8a5cd1997bb Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 11 Dec 2018 01:27:10 -0700 Subject: [PATCH 08/15] volatile complete --- .../01-limitations/03-volatile_destination.md | 239 ++++++++++++++++-- 1 file changed, 215 insertions(+), 24 deletions(-) diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index cbb14da..d635e88 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -65,17 +65,19 @@ 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. -But no syntax sugar doesn't mean we can't at least do a little work for -ourselves. Enter the `VolatilePtr` type, which is a newtype over a `*mut T`: +### 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(*mut T); +pub struct VolatilePtr(pub *mut T); ``` -Obviously we'll need some methods go with it. The basic operations are reading -and writing of course: +Obviously we want to be able to read and write: ```rust impl VolatilePtr { @@ -91,20 +93,23 @@ impl VolatilePtr { ``` And we want a way to jump around when we do have volatile memory that's in -blocks. For this there's both +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 for us, -and the answer was that you _can_ use an `offset` in statically memory mapped +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. 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_, and with -`wrapping_offset` it's not Undefined Behavior until you _use_ the out of bounds -result. +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`. @@ -113,20 +118,25 @@ result. } ``` -Now, one thing of note is that doing the `offset` isn't `const`. If we wanted to have a `const` function for -finding the correct spot 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. In the future Rust might be -able to do it without a goofy work around, but `const` is quite limited at the moment. -It'd look something like this: +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::()) + address + (index * std::mem::size_of::()) } ``` -We will 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 that: +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. @@ -135,10 +145,116 @@ won't be able to do that with `as`, we'll have to write a method for that: } ``` -TODO: iterator stuff +### Volatile Iterating -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. +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 makes `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 { @@ -149,4 +265,79 @@ impl core::fmt::Pointer for VolatilePtr { } ``` +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". From ac5353b77375602662cb780698f2abdaa276c344 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Fri, 14 Dec 2018 22:57:14 -0700 Subject: [PATCH 09/15] Introduction and Limits chapters updated --- Makefile.toml | 4 +- README.md | 18 +-- book/src/00-introduction/00-index.md | 2 +- book/src/00-introduction/04-hello-magic.md | 2 +- book/src/01-limitations/00-index.md | 9 ++ book/src/01-limitations/01-no_floats.md | 1 - book/src/01-limitations/01-no_std.md | 108 +++++++++++++++++ book/src/01-limitations/02-core_only.md | 1 - book/src/01-limitations/02-fixed_only.md | 13 +++ .../01-limitations/03-volatile_destination.md | 2 + book/src/SUMMARY.md | 4 +- examples/hello_magic.rs | 2 +- examples/mgba_panic_handler.rs | 109 ++++++++++++++++++ 13 files changed, 258 insertions(+), 17 deletions(-) delete mode 100644 book/src/01-limitations/01-no_floats.md create mode 100644 book/src/01-limitations/01-no_std.md delete mode 100644 book/src/01-limitations/02-core_only.md create mode 100644 book/src/01-limitations/02-fixed_only.md create mode 100644 examples/mgba_panic_handler.rs 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 0567c77..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. 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/01-limitations/00-index.md b/book/src/01-limitations/00-index.md index 71e12e6..3cfc7fc 100644 --- a/book/src/01-limitations/00-index.md +++ b/book/src/01-limitations/00-index.md @@ -1 +1,10 @@ # GBA Limitations + +There's a lot of seemingly normal Rust code that you can't write when compiling +for the GBA. + +I mean there's a lot of stuff that you _can_ write, but we don't wanna get +tripped up by assuming we can use something only to get stuck when it's missing. + +We'll start the book by reviewing all the major things we don't have so that we +can avoid any surprises later. 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/01-no_std.md b/book/src/01-limitations/01-no_std.md new file mode 100644 index 0000000..39a4c25 --- /dev/null +++ b/book/src/01-limitations/01-no_std.md @@ -0,0 +1,108 @@ +# 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 controls 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. +* 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). + +## 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-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/02-fixed_only.md b/book/src/01-limitations/02-fixed_only.md new file mode 100644 index 0000000..9ad5de3 --- /dev/null +++ b/book/src/01-limitations/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 pont 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-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index d635e88..14fb69d 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -1,5 +1,7 @@ # 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". diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ccd7a41..c9aca10 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,8 +9,8 @@ * [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) + * [No Std](01-limitations/01-no_std.md) + * [Fixed Only](01-limitations/02-fixed_only.md) * [Volatile Destination](01-limitations/03-volatile_destination.md) * [Concepts](02-concepts/00-index.md) * [CPU](02-concepts/01-cpu.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!"); + } +} From dd98ac89d0273f0a401b79bf9af0e5891ff56d2f Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 15 Dec 2018 10:23:36 -0700 Subject: [PATCH 10/15] Update book/src/01-limitations/03-volatile_destination.md Co-Authored-By: Lokathor --- book/src/01-limitations/03-volatile_destination.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-limitations/03-volatile_destination.md index 14fb69d..f03c62c 100644 --- a/book/src/01-limitations/03-volatile_destination.md +++ b/book/src/01-limitations/03-volatile_destination.md @@ -196,7 +196,7 @@ impl Iterator for VolatilePtrIter { 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 makes `VolatilePtr` will only be `Copy` if the `T` is `Copy`, _even +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: From e27b187bab87b310a31660a9d7ca605f6b917aa7 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sat, 15 Dec 2018 10:24:38 -0700 Subject: [PATCH 11/15] Update book/src/01-limitations/02-fixed_only.md Co-Authored-By: Lokathor --- book/src/01-limitations/02-fixed_only.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/01-limitations/02-fixed_only.md b/book/src/01-limitations/02-fixed_only.md index 9ad5de3..49e507a 100644 --- a/book/src/01-limitations/02-fixed_only.md +++ b/book/src/01-limitations/02-fixed_only.md @@ -1,7 +1,7 @@ # 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 pont math in hardware! We +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. From 12973928d78a99e434181f195bcb6e6709510f6d Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 15 Dec 2018 10:39:43 -0700 Subject: [PATCH 12/15] one too many 's's --- book/src/01-limitations/01-no_std.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/01-limitations/01-no_std.md b/book/src/01-limitations/01-no_std.md index 39a4c25..7f91ea3 100644 --- a/book/src/01-limitations/01-no_std.md +++ b/book/src/01-limitations/01-no_std.md @@ -19,7 +19,7 @@ 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 controls passes into Rust code. +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 From 4c3782426df0185da7d3a0ecfb624a72b2cba535 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 15 Dec 2018 10:41:59 -0700 Subject: [PATCH 13/15] file system possibility --- book/src/01-limitations/01-no_std.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/book/src/01-limitations/01-no_std.md b/book/src/01-limitations/01-no_std.md index 7f91ea3..4eb9a74 100644 --- a/book/src/01-limitations/01-no_std.md +++ b/book/src/01-limitations/01-no_std.md @@ -64,7 +64,11 @@ 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. + 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. From 48fa643f8c731dfa4c3402cbca9f38b6b48d6e40 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 15 Dec 2018 16:50:04 -0700 Subject: [PATCH 14/15] rename "limitations" to "quirks", less negative --- book/src/01-limitations/00-index.md | 10 ---------- book/src/01-quirks/00-index.md | 9 +++++++++ book/src/{01-limitations => 01-quirks}/01-no_std.md | 4 ++++ .../src/{01-limitations => 01-quirks}/02-fixed_only.md | 0 .../03-volatile_destination.md | 0 book/src/SUMMARY.md | 8 ++++---- 6 files changed, 17 insertions(+), 14 deletions(-) delete mode 100644 book/src/01-limitations/00-index.md create mode 100644 book/src/01-quirks/00-index.md rename book/src/{01-limitations => 01-quirks}/01-no_std.md (98%) rename book/src/{01-limitations => 01-quirks}/02-fixed_only.md (100%) rename book/src/{01-limitations => 01-quirks}/03-volatile_destination.md (100%) diff --git a/book/src/01-limitations/00-index.md b/book/src/01-limitations/00-index.md deleted file mode 100644 index 3cfc7fc..0000000 --- a/book/src/01-limitations/00-index.md +++ /dev/null @@ -1,10 +0,0 @@ -# GBA Limitations - -There's a lot of seemingly normal Rust code that you can't write when compiling -for the GBA. - -I mean there's a lot of stuff that you _can_ write, but we don't wanna get -tripped up by assuming we can use something only to get stuck when it's missing. - -We'll start the book by reviewing all the major things we don't have so that we -can avoid any surprises later. 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-limitations/01-no_std.md b/book/src/01-quirks/01-no_std.md similarity index 98% rename from book/src/01-limitations/01-no_std.md rename to book/src/01-quirks/01-no_std.md index 4eb9a74..13faa72 100644 --- a/book/src/01-limitations/01-no_std.md +++ b/book/src/01-quirks/01-no_std.md @@ -89,6 +89,10 @@ 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 diff --git a/book/src/01-limitations/02-fixed_only.md b/book/src/01-quirks/02-fixed_only.md similarity index 100% rename from book/src/01-limitations/02-fixed_only.md rename to book/src/01-quirks/02-fixed_only.md diff --git a/book/src/01-limitations/03-volatile_destination.md b/book/src/01-quirks/03-volatile_destination.md similarity index 100% rename from book/src/01-limitations/03-volatile_destination.md rename to book/src/01-quirks/03-volatile_destination.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index c9aca10..4fc9ca4 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -8,10 +8,10 @@ * [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 Std](01-limitations/01-no_std.md) - * [Fixed Only](01-limitations/02-fixed_only.md) - * [Volatile Destination](01-limitations/03-volatile_destination.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) * [Concepts](02-concepts/00-index.md) * [CPU](02-concepts/01-cpu.md) * [BIOS](02-concepts/02-bios.md) From 7cdcc02aaf4ca134c17ff147cf9baf54ff1b38e9 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Sat, 15 Dec 2018 16:53:03 -0700 Subject: [PATCH 15/15] Move "newtype" into the "quirks" --- .../{06-help_and_resources.md => 05-help_and_resources.md} | 0 .../05-newtype.md => 01-quirks/04-newtype.md} | 6 ++---- book/src/SUMMARY.md | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) rename book/src/00-introduction/{06-help_and_resources.md => 05-help_and_resources.md} (100%) rename book/src/{00-introduction/05-newtype.md => 01-quirks/04-newtype.md} (96%) 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/00-introduction/05-newtype.md b/book/src/01-quirks/04-newtype.md similarity index 96% rename from book/src/00-introduction/05-newtype.md rename to book/src/01-quirks/04-newtype.md index d711917..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 diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4fc9ca4..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) + * [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)