From 70b752f82b2fe77d4f80d5a7b3f316c8914b7256 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Wed, 2 Jan 2019 20:27:15 -0700 Subject: [PATCH] make docs be hosted in a separate branch --- .travis.yml | 4 +- Cargo.toml | 2 +- book/book.toml | 2 +- docs/01-quirks/01-no_std.html | 339 ---------- docs/01-quirks/02-fixed_only.html | 698 -------------------- docs/01-quirks/03-volatile_destination.html | 521 --------------- 6 files changed, 4 insertions(+), 1562 deletions(-) delete mode 100644 docs/01-quirks/01-no_std.html delete mode 100644 docs/01-quirks/02-fixed_only.html delete mode 100644 docs/01-quirks/03-volatile_destination.html diff --git a/.travis.yml b/.travis.yml index 71c74d4..eea3dd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,10 +44,10 @@ script: deploy: provider: pages + local-dir: target/book-output skip-cleanup: true github-token: $GITHUB_TOKEN - target-branch: master - keep-history: true + keep-history: false name: DocsBot verbose: true on: diff --git a/Cargo.toml b/Cargo.toml index d099971..2c50455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["gba"] edition = "2018" license = "Apache-2.0" -#publish = false +publish = false [dependencies] typenum = "1.10" diff --git a/book/book.toml b/book/book.toml index 4088ba4..69085ec 100644 --- a/book/book.toml +++ b/book/book.toml @@ -3,5 +3,5 @@ title = "Rust GBA Guide" authors = ["Lokathor"] [build] -build-dir = "../docs" +build-dir = "../target/book-output" create-missing = true diff --git a/docs/01-quirks/01-no_std.html b/docs/01-quirks/01-no_std.html deleted file mode 100644 index 6a2d3f8..0000000 --- a/docs/01-quirks/01-no_std.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - No Std - Rust GBA Guide - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

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:

- -

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 -just tell you to look at the tracking issue for -it 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, 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 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).

-

Bare Metal Panic

-

If our code panics, we usually want to see that panic message. Unfortunately, -without a way to access something like stdout or stderr we've gotta do -something a little weirder.

-

If our program is running within the mGBA emulator, version 0.7 or later, we -can access a special set of addresses that allow us to send out CString -values, which then appear within a message log that you can check.

-

We can capture this behavior by making an MGBADebug type, and then implement -core::fmt::Write for that type. Once done, the write! macro will let us -target the mGBA debug output channel.

-

When used, it looks like this:

-

-# #![allow(unused_variables)]
-#fn main() {
-#[panic_handler]
-fn panic(info: &core::panic::PanicInfo) -> ! {
-  use core::fmt::Write;
-  use gba::mgba::{MGBADebug, MGBADebugLevel};
-
-  if let Some(mut mgba) = MGBADebug::new() {
-    let _ = write!(mgba, "{}", info);
-    mgba.send(MGBADebugLevel::Fatal);
-  }
-  loop {}
-}
-#}
-

If you want to follow the particulars you can check the MGBADebug source in -the gba crate. Basically, there's one address you can use to try and activate -the debug output, and if it works you write your message into the "array" at -another address, and then finally write a send value to a third address. You'll -need to have read the volatile section for the -details to make sense.

-

LLVM Intrinsics

-

The above code will make your program fail to build in debug mode, saying that -__clzsi2 can't be found. This is a special builtin function that LLVM attempts -to use when there's no hardware version of an operation it wants to do (in this -case, counting the leading zeros). It's not actually necessary in this case, -which is why you only need it in debug mode. The higher optimization level of -release mode makes LLVM pre-compute more and fold more constants or whatever and -then it stops trying to call __clzsi2.

-

Unfortunately, sometimes a build will fail with a missing intrinsic even in -release mode.

-

If LLVM wants core to have that intrinsic then you're in -trouble, you'll have to send a PR to the -compiler-builtins -repository and hope to get it into rust itself.

-

If LLVM wants your code to have the intrinsic then you're in less trouble. You -can look up the details and then implement it yourself. It can go anywhere in -your program, as long as it has the right ABI and name. In the case of -__clzsi2 it takes a usize and returns a usize, so you'd write something -like:

-

-# #![allow(unused_variables)]
-#fn main() {
-#[no_mangle]
-pub extern "C" fn __clzsi2(mut x: usize) -> usize {
-  //
-}
-#}
-

And so on for whatever other missing intrinsic.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/01-quirks/02-fixed_only.html b/docs/01-quirks/02-fixed_only.html deleted file mode 100644 index 35dfbc5..0000000 --- a/docs/01-quirks/02-fixed_only.html +++ /dev/null @@ -1,698 +0,0 @@ - - - - - - Fixed Only - Rust GBA Guide - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Fixed Only

-

In addition to not having much of 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 pure software computations -if we wanted, but that's a slow, slow thing to do.

-

Are there faster ways? It's the same answer as always: "Yes, but not without a -tradeoff."

-

The faster way is to represent fractional values using a system called a Fixed -Point Representation. -What do we trade away? Numeric range.

-
    -
  • Floating point math stores bits for base value and for exponent all according -to a single well defined standard -for how such a complicated thing works.
  • -
  • Fixed point math takes a normal integer (either signed or unsigned) and then -just "mentally associates" it (so to speak) with a fractional value for its -"units". If you have 3 and it's in units of 1/2, then you have 3/2, or 1.5 -using decimal notation. If your number is 256 and it's in units of 1/256th -then the value is 1.0 in decimal notation.
  • -
-

Floating point math requires dedicated hardware to perform quickly, but it can -"trade" precision when it needs to represent extremely large or small values.

-

Fixed point math is just integral math, which our GBA is reasonably good at, but -because your number is associated with a fixed fraction your results can get out -of range very easily.

-

Representing A Fixed Point Value

-

So we want to associate our numbers with a mental note of what units they're in:

-
    -
  • PhantomData -is a type that tells the compiler "please remember this extra type info" when -you add it as a field to a struct. It goes away at compile time, so it's -perfect for us to use as space for a note to ourselves without causing runtime -overhead.
  • -
  • The typenum crate is the best way to -represent a number within a type in Rust. Since our values on the GBA are -always specified as a number of fractional bits to count the number as, we can -put typenum types such as U8 or U14 into our PhantomData to keep track -of what's going on.
  • -
-

Now, those of you who know me, or perhaps just know my reputation, will of -course immediately question what happened to the real Lokathor. I do not care -for most crates, and I particularly don't care for using a crate in teaching -situations. However, typenum has a number of factors on its side that let me -suggest it in this situation:

-
    -
  • It's version 1.10 with a total of 21 versions and nearly 700k downloads, so we -can expect that the major troubles have been shaken out and that it will remain -fairly stable for quite some time to come.
  • -
  • It has no further dependencies that it's going to drag into the compilation.
  • -
  • It happens all at compile time, so it's not clogging up our actual game with -any nonsense.
  • -
  • The (interesting) subject of "how do you do math inside Rust's trait system?" is -totally separate from the concern that we're trying to focus on here.
  • -
-

Therefore, we will consider it acceptable to use this crate.

-

Now the typenum crate defines a whole lot, but we'll focus down to just a -single type at the moment: -UInt is a -type-level unsigned value. It's like u8 or u16, but while they're types that -then have values, each UInt construction statically equates to a specific -value. Like how the () type only has one value, which is also called (). In -this case, you wrap up UInt around smaller UInt values and a B1 or B0 -value to build up the binary number that you want at the type level.

-

In other words, instead of writing

-

-# #![allow(unused_variables)]
-#fn main() {
-let six = 0b110;
-#}
-

We write

-

-# #![allow(unused_variables)]
-#fn main() {
-type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>;
-#}
-

Wild, I know. If you look into the typenum crate you can do math and stuff -with these type level numbers, and we will a little bit below, but to start off -we just need to store one in some PhantomData.

-

A struct For Fixed Point

-

Our actual type for a fixed point value looks like this:

-

-# #![allow(unused_variables)]
-#fn main() {
-use core::marker::PhantomData;
-use typenum::marker_traits::Unsigned;
-
-/// Fixed point `T` value with `F` fractional bits.
-#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
-#[repr(transparent)]
-pub struct Fx<T, F: Unsigned> {
-  bits: T,
-  _phantom: PhantomData<F>,
-}
-#}
-

This says that Fx<T,F> is a generic type that holds some base number type T -and a F type that's marking off how many fractional bits we're using. We only -want people giving unsigned type-level values for the PhantomData type, so we -use the trait bound F: Unsigned.

-

We use -repr(transparent) -here to ensure that Fx will always be treated just like the base type in the -final program (in terms of bit pattern and ABI).

-

If you go and check, this is basically how the existing general purpose crates -for fixed point math represent their numbers. They're a little fancier about it -because they have to cover every case, and we only have to cover our GBA case.

-

That's quite a bit to type though. We probably want to make a few type aliases -for things to be easier to look at. Unfortunately there's no standard -notation for how -you write a fixed point type. We also have to limit ourselves to what's valid -for use in a Rust type too. I like the fx thing, so we'll use that for signed -and then fxu if we need an unsigned value.

-

-# #![allow(unused_variables)]
-#fn main() {
-/// Alias for an `i16` fixed point value with 8 fractional bits.
-pub type fx8_8 = Fx<i16,U8>;
-#}
-

Rust will complain about having non_camel_case_types, and you can shut that -warning up by putting an #[allow(non_camel_case_types)] attribute on the type -alias directly, or you can use #![allow(non_camel_case_types)] at the very top -of the module to shut up that warning for the whole module (which is what I -did).

-

Constructing A Fixed Point Value

-

So how do we actually make one of these values? Well, we can always just wrap or unwrap any value in our Fx type:

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T, F: Unsigned> Fx<T, F> {
-  /// Uses the provided value directly.
-  pub fn from_raw(r: T) -> Self {
-    Fx {
-      num: r,
-      phantom: PhantomData,
-    }
-  }
-  /// Unwraps the inner value.
-  pub fn into_raw(self) -> T {
-    self.num
-  }
-}
-#}
-

I'd like to use the From trait of course, but it was giving me some trouble, i -think because of the orphan rule. Oh well.

-

If we want to be particular to the fact that these are supposed to be -numbers... that gets tricky. Rust is actually quite bad at being generic about -number types. You can use the num crate, or you -can just use a macro and invoke it once per type. Guess what we're gonna do.

-

-# #![allow(unused_variables)]
-#fn main() {
-macro_rules! fixed_point_methods {
-  ($t:ident) => {
-    impl<F: Unsigned> Fx<$t, F> {
-      /// Gives the smallest positive non-zero value.
-      pub fn precision() -> Self {
-        Fx {
-          num: 1,
-          phantom: PhantomData,
-        }
-      }
-
-      /// Makes a value with the integer part shifted into place.
-      pub fn from_int_part(i: $t) -> Self {
-        Fx {
-          num: i << F::U8,
-          phantom: PhantomData,
-        }
-      }
-    }
-  };
-}
-
-fixed_point_methods! {u8}
-fixed_point_methods! {i8}
-fixed_point_methods! {i16}
-fixed_point_methods! {u16}
-fixed_point_methods! {i32}
-fixed_point_methods! {u32}
-#}
-

Now you'd think that those can be const, but at the moment you can't have a -const function with a bound on any trait other than Sized, so they have to -be normal functions.

-

Also, we're doing something a little interesting there with from_int_part. We -can take our F type and get its constant value. There's other associated -constants if we want it in other types, and also non-const methods if you wanted -that for some reason (maybe passing it as a closure function? dunno).

-

Casting Base Values

-

Next, once we have a value in one base type we will need to be able to move it -into another base type. Unfortunately this means we gotta use the as operator, -which requires a concrete source type and a concrete destination type. There's -no easy way for us to make it generic here.

-

We could let the user use into_raw, cast, and then do from_raw, but that's -error prone because they might change the fractional bit count accidentally. -This means that we have to write a function that does the casting while -perfectly preserving the fractional bit quantity. If we wrote one function for -each conversion it'd be like 30 different possible casts (6 base types that we -support, and then 5 possible target types). Instead, we'll write it just once in -a way that takes a closure, and let the user pass a closure that does the cast. -The compiler should merge it all together quite nicely for us once optimizations -kick in.

-

This code goes outside the macro. I want to avoid too much code in the macro if -we can, it's a little easier to cope with I think.

-

-# #![allow(unused_variables)]
-#fn main() {
-  /// Casts the base type, keeping the fractional bit quantity the same.
-  pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
-    Fx {
-      num: op(self.num),
-      phantom: PhantomData,
-    }
-  }
-#}
-

It's horrible and ugly, but Rust is just bad at numbers sometimes.

-

Adjusting Fractional Part

-

In addition to the base value we might want to change our fractional bit -quantity. This is actually easier that it sounds, but it also requires us to be -tricky with the generics. We can actually use some typenum type level operators -here.

-

This code goes inside the macro: we need to be able to use the left shift and -right shift, which is easiest when we just use the macro's $t as our type. We -could alternately put a similar function outside the macro and be generic on T -having the left and right shift operators by using a where clause. As much as -I'd like to avoid too much code being generated by macro, I'd even more like -to avoid generic code with huge and complicated trait bounds. It comes down to -style, and you gotta decide for yourself.

-

-# #![allow(unused_variables)]
-#fn main() {
-      /// Changes the fractional bit quantity, keeping the base type the same.
-      pub fn adjust_fractional_bits<Y: Unsigned + IsEqual<F, Output = False>>(self) -> Fx<$t, Y> {
-        let leftward_movement: i32 = Y::to_i32() - F::to_i32();
-        Fx {
-          num: if leftward_movement > 0 {
-            self.num << leftward_movement
-          } else {
-            self.num >> (-leftward_movement)
-          },
-          phantom: PhantomData,
-        }
-      }
-#}
-

There's a few things at work. First, we introduce Y as the target number of -fractional bits, and we also limit it that the target bits quantity can't be -the same as we already have using a type-level operator. If it's the same as we -started with, why are you doing the cast at all?

-

Now, once we're sure that the current bits and target bits aren't the same, we -compute target - start, and call this our "leftward movement". Example: if -we're targeting 8 bits and we're at 4 bits, we do 8-4 and get +4 as our leftward -movement. If the leftward_movement is positive we naturally shift our current -value to the left. If it's not positive then it must be negative because we -eliminated 0 as a possibility using the type-level operator, so we shift to the -right by the negative value.

-

Addition, Subtraction, Shifting, Negative, Comparisons

-

From here on we're getting help from this blog -post by Job -Vranish, so thank them if you -learn something.

-

I might have given away the game a bit with those derive traits on our fixed -point type. For a fair number of operations you can use the normal form of the -op on the inner bits as long as the fractional parts have the same quantity. -This includes equality and ordering (which we derived) as well as addition, -subtraction, and bit shifting (which we need to do ourselves).

-

This code can go outside the macro, with sufficient trait bounds.

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
-  type Output = Self;
-  fn add(self, rhs: Fx<T, F>) -> Self::Output {
-    Fx {
-      num: self.num + rhs.num,
-      phantom: PhantomData,
-    }
-  }
-}
-#}
-

The bound on T makes it so that Fx<T, F> can be added any time that T can -be added to its own type with itself as the output. We can use the exact same -pattern for Sub, Shl, Shr, and Neg. With enough trait bounds, we can do -anything!

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
-  type Output = Self;
-  fn sub(self, rhs: Fx<T, F>) -> Self::Output {
-    Fx {
-      num: self.num - rhs.num,
-      phantom: PhantomData,
-    }
-  }
-}
-
-impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
-  type Output = Self;
-  fn shl(self, rhs: u32) -> Self::Output {
-    Fx {
-      num: self.num << rhs,
-      phantom: PhantomData,
-    }
-  }
-}
-
-impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
-  type Output = Self;
-  fn shr(self, rhs: u32) -> Self::Output {
-    Fx {
-      num: self.num >> rhs,
-      phantom: PhantomData,
-    }
-  }
-}
-
-impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
-  type Output = Self;
-  fn neg(self) -> Self::Output {
-    Fx {
-      num: -self.num,
-      phantom: PhantomData,
-    }
-  }
-}
-#}
-

Unfortunately, for Shl and Shr to have as much coverage on our type as it -does on the base type (allowing just about any right hand side) we'd have to do -another macro, but I think just u32 is fine. We can always add more later if -we need.

-

We could also implement BitAnd, BitOr, BitXor, and Not, but they don't -seem relevent to our fixed point math use, and this section is getting long -already. Just use the same general patterns if you want to add it in your own -programs. Shockingly, Rem also works directly if you want it, though I don't -forsee us needing floating point remainder. Also, the GBA can't do hardware -division or remainder, and we'll have to work around that below when we -implement Div (which maybe we don't need, but it's complex enough I should -show it instead of letting people guess).

-

Note: In addition to the various Op traits, there's also OpAssign -variants. Each OpAssign is the same as Op, but takes &mut self instead of -self and then modifies in place instead of producing a fresh value. In other -words, if you want both + and += you'll need to do the AddAssign trait -too. It's not the worst thing to just write a = a+b, so I won't bother with -showing all that here. It's pretty easy to figure out for yourself if you want.

-

Multiplication

-

This is where things get more interesting. When we have two numbers A and B -they really stand for (a*f) and (b*f). If we write A*B then we're really -writing (a*f)*(b*f), which can be rewritten as (a*b)*2f, and now it's -obvious that we have one more f than we wanted to have. We have to do the -multiply of the inner value and then divide out the f. We divide by 1 << bit_count, so if we have 8 fractional bits we'll divide by 256.

-

The catch is that, when we do the multiply we're extremely likely to overflow -our base type with that multiplication step. Then we do that divide, and now our -result is basically nonsense. We can avoid this to some extent by casting up to -a higher bit type, doing the multiplication and division at higher precision, -and then casting back down. We want as much precision as possible without being -too inefficient, so we'll always cast up to 32-bit (on a 64-bit machine you'd -cast up to 64-bit instead).

-

Naturally, any signed value has to be cast up to i32 and any unsigned value -has to be cast up to u32, so we'll have to handle those separately.

-

Also, instead of doing an actual divide we can right-shift by the correct -number of bits to achieve the same effect. Except when we have a signed value -that's negative, because actual division truncates towards zero and -right-shifting truncates towards negative infinity. We can get around this by -flipping the sign, doing the shift, and flipping the sign again (which sounds -silly but it's so much faster than doing an actual division).

-

Also, again signed values can be annoying, because if the value just happens -to be i32::MIN then when you negate it you'll have... still a negative -value. I'm not 100% on this, but I think the correct thing to do at that point -is to give $t::MIN as the output num value.

-

Did you get all that? Good, because this involves casting, so we will need to -implement it three times, which calls for another macro.

-

-# #![allow(unused_variables)]
-#fn main() {
-macro_rules! fixed_point_signed_multiply {
-  ($t:ident) => {
-    impl<F: Unsigned> Mul for Fx<$t, F> {
-      type Output = Self;
-      fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
-        let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
-        if pre_shift < 0 {
-          if pre_shift == core::i32::MIN {
-            Fx {
-              num: core::$t::MIN,
-              phantom: PhantomData,
-            }
-          } else {
-            Fx {
-              num: (-((-pre_shift) >> F::U8)) as $t,
-              phantom: PhantomData,
-            }
-          }
-        } else {
-          Fx {
-            num: (pre_shift >> F::U8) as $t,
-            phantom: PhantomData,
-          }
-        }
-      }
-    }
-  };
-}
-
-fixed_point_signed_multiply! {i8}
-fixed_point_signed_multiply! {i16}
-fixed_point_signed_multiply! {i32}
-
-macro_rules! fixed_point_unsigned_multiply {
-  ($t:ident) => {
-    impl<F: Unsigned> Mul for Fx<$t, F> {
-      type Output = Self;
-      fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
-        Fx {
-          num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t,
-          phantom: PhantomData,
-        }
-      }
-    }
-  };
-}
-
-fixed_point_unsigned_multiply! {u8}
-fixed_point_unsigned_multiply! {u16}
-fixed_point_unsigned_multiply! {u32}
-#}
-

Division

-

Division is similar to multiplication, but reversed. Which makes sense. This -time A/B gives (a*f)/(b*f) which is a/b, one less f than we were -after.

-

As with the multiplication version of things, we have to up-cast our inner value -as much a we can before doing the math, to allow for the most precision -possible.

-

The snag here is that the GBA has no division or remainder. Instead, the GBA has -a BIOS function you can call to do i32/i32 division.

-

This is a potential problem for us though. If we have some unsigned value, we -need it to fit within the positive space of an i32 after the multiply so -that we can cast it to i32, call the BIOS function that only works on i32 -values, and cast it back to its actual type.

-
    -
  • If you have a u8 you're always okay, even with 8 floating bits.
  • -
  • If you have a u16 you're okay even with a maximum value up to 15 floating -bits, but having a maximum value and 16 floating bits makes it break.
  • -
  • If you have a u32 you're probably going to be in trouble all the time.
  • -
-

So... ugh, there's not much we can do about this. For now we'll just have to -suffer some.

-

// TODO: find a numerics book that tells us how to do u32/u32 divisions.

-

-# #![allow(unused_variables)]
-#fn main() {
-macro_rules! fixed_point_signed_division {
-  ($t:ident) => {
-    impl<F: Unsigned> Div for Fx<$t, F> {
-      type Output = Self;
-      fn div(self, rhs: Fx<$t, F>) -> Self::Output {
-        let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
-        let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
-        Fx {
-          num: divide_result as $t,
-          phantom: PhantomData,
-        }
-      }
-    }
-  };
-}
-
-fixed_point_signed_division! {i8}
-fixed_point_signed_division! {i16}
-fixed_point_signed_division! {i32}
-
-macro_rules! fixed_point_unsigned_division {
-  ($t:ident) => {
-    impl<F: Unsigned> Div for Fx<$t, F> {
-      type Output = Self;
-      fn div(self, rhs: Fx<$t, F>) -> Self::Output {
-        let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
-        let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
-        Fx {
-          num: divide_result as $t,
-          phantom: PhantomData,
-        }
-      }
-    }
-  };
-}
-
-fixed_point_unsigned_division! {u8}
-fixed_point_unsigned_division! {u16}
-fixed_point_unsigned_division! {u32}
-#}
-

Trigonometry

-

TODO: look up tables! arcbits!

-

Just Using A Crate

-

If, after seeing all that, and seeing that I still didn't even cover every -possible trait impl that you might want for all the possible types... if after -all that you feel too intimidated, then I'll cave a bit on your behalf and -suggest to you that the fixed crate seems to -be the best crate available for fixed point math.

-

I have not tested its use on the GBA myself.

-

It's just my recommendation from looking at the docs of the various options -available, if you really wanted to just have a crate for it.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/01-quirks/03-volatile_destination.html b/docs/01-quirks/03-volatile_destination.html deleted file mode 100644 index b9a3886..0000000 --- a/docs/01-quirks/03-volatile_destination.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - Volatile Destination - Rust GBA Guide - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - -
-
-

Volatile Destination

-

TODO: update this when we can make more stuff const

-

Volatile Memory

-

The compiler is an eager friend, so when it sees a read or a write that won't -have an effect, it eliminates that read or write. For example, if we write

-

-# #![allow(unused_variables)]
-#fn main() {
-let mut x = 5;
-x = 7;
-#}
-

The compiler won't actually ever put 5 into x. It'll skip straight to putting -7 in x, because we never read from x when it's 5, so that's a safe change to -make. Normally, values are stored in RAM, which has no side effects when you -read and write from it. RAM is purely for keeping notes about values you'll need -later on.

-

However, what if we had a bit of hardware where we wanted to do a write and that -did something other than keeping the value for us to look at later? As you saw -in the hello_magic example, we have to use a write_volatile operation. -Volatile means "just do it anyway". The compiler thinks that it's pointless, but -we know better, so we can force it to really do exactly what we say by using -write_volatile instead of write.

-

This is kinda error prone though, right? Because it's just a raw pointer, so we -might forget to use write_volatile at some point.

-

Instead, we want a type that's always going to use volatile reads and writes. -Also, we want a pointer type that lets our reads and writes to be as safe as -possible once we've unsafely constructed the initial value.

-

Constructing The VolAddress Type

-

First, we want a type that stores a location within the address space. This can -be a pointer, or a usize, and we'll use a usize because that's easier to -work with in a const context (and we want to have const when we can get it). -We'll also have our type use NonZeroUsize instead of just usize so that -Option<VolAddress<T>> stays as a single machine word. This helps quite a bit -when we want to iterate over the addresses of a block of memory (such as -locations within the palette memory). Hardware is never at the null address -anyway. Also, if we had just an address number then we wouldn't be able to -track what type the address is for. We need some -PhantomData, -and specifically we need the phantom data to be for *mut T:

-
    -
  • If we used *const T that'd have the wrong -variance.
  • -
  • If we used &mut T then that's fusing in the ideas of lifetime and -exclusive access to our type. That's potentially important, but that's also -an abstraction we'll build on top of this VolAddress type if we need it.
  • -
-

One abstraction layer at a time, so we start with just a phantom pointer. This gives us a type that looks like this:

-

-# #![allow(unused_variables)]
-#fn main() {
-#[derive(Debug)]
-#[repr(transparent)]
-pub struct VolAddress<T> {
-  address: NonZeroUsize,
-  marker: PhantomData<*mut T>,
-}
-#}
-

Now, because of how derive is specified, it derives traits if the generic -parameter supports those traits. Since our type is like a pointer, the traits -it supports are distinct from whatever traits the target type supports. So we'll -provide those implementations manually.

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T> Clone for VolAddress<T> {
-  fn clone(&self) -> Self {
-    *self
-  }
-}
-impl<T> Copy for VolAddress<T> {}
-impl<T> PartialEq for VolAddress<T> {
-  fn eq(&self, other: &Self) -> bool {
-    self.address == other.address
-  }
-}
-impl<T> Eq for VolAddress<T> {}
-impl<T> PartialOrd for VolAddress<T> {
-  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-    Some(self.address.cmp(&other.address))
-  }
-}
-impl<T> Ord for VolAddress<T> {
-  fn cmp(&self, other: &Self) -> Ordering {
-    self.address.cmp(&other.address)
-  }
-}
-#}
-

Boilerplate junk, not interesting. There's a reason that you derive those traits -99% of the time in Rust.

-

Constructing A VolAddress Value

-

Okay so here's the next core concept: If we unsafely construct a -VolAddress<T>, then we can safely use the value once it's been properly -created.

-

-# #![allow(unused_variables)]
-#fn main() {
-// you'll need these features enabled and a recent nightly
-#![feature(const_int_wrapping)]
-#![feature(min_const_unsafe_fn)]
-
-impl<T> VolAddress<T> {
-  pub const unsafe fn new_unchecked(address: usize) -> Self {
-    VolAddress {
-      address: NonZeroUsize::new_unchecked(address),
-      marker: PhantomData,
-    }
-  }
-  pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
-    VolAddress {
-      address: self.address,
-      marker: PhantomData,
-    }
-  }
-  pub unsafe fn offset(self, offset: isize) -> Self {
-    VolAddress {
-      address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::<T>())),
-      marker: PhantomData,
-    }
-  }
-}
-#}
-

So what are the unsafety rules here?

-
    -
  • Non-null, obviously.
  • -
  • Must be aligned for T
  • -
  • Must always produce valid bit patterns for T
  • -
  • Must not be part of the address space that Rust's stack or allocator will ever -uses.
  • -
-

So, again using the hello_magic example, we had

-

-# #![allow(unused_variables)]
-#fn main() {
-(0x400_0000 as *mut u16).write_volatile(0x0403);
-#}
-

And instead we could declare

-

-# #![allow(unused_variables)]
-#fn main() {
-const MAGIC_LOCATION: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0000) };
-#}
-

Using A VolAddress Value

-

Now that we've named the magic location, we want to write to it.

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T> VolAddress<T> {
-  pub fn read(self) -> T
-  where
-    T: Copy,
-  {
-    unsafe { (self.address.get() as *mut T).read_volatile() }
-  }
-  pub unsafe fn read_non_copy(self) -> T {
-    (self.address.get() as *mut T).read_volatile()
-  }
-  pub fn write(self, val: T) {
-    unsafe { (self.address.get() as *mut T).write_volatile(val) }
-  }
-}
-#}
-

So if the type is Copy we can read it as much as we want. If, somehow, the -type isn't Copy, then it might be Drop, and that means if we read out a -value over and over we could cause the drop method to trigger UB. Since the -end user might really know what they're doing, we provide an unsafe backup -read_non_copy.

-

On the other hand, we can write to the location as much as we want. Even if -the type isn't Copy, not running Drop is safe, so a write is always -safe.

-

Now we can write to our magical location.

-

-# #![allow(unused_variables)]
-#fn main() {
-MAGIC_LOCATION.write(0x0403);
-#}
-

VolAddress Iteration

-

We've already seen that sometimes we want to have a base address of some sort -and then offset from that location to another. What if we wanted to iterate over -all the locations. That's not particularly hard.

-

-# #![allow(unused_variables)]
-#fn main() {
-impl<T> VolAddress<T> {
-  pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
-    VolAddressIter { vol_address: self, slots }
-  }
-}
-
-#[derive(Debug)]
-pub struct VolAddressIter<T> {
-  vol_address: VolAddress<T>,
-  slots: usize,
-}
-impl<T> Clone for VolAddressIter<T> {
-  fn clone(&self) -> Self {
-    VolAddressIter {
-      vol_address: self.vol_address,
-      slots: self.slots,
-    }
-  }
-}
-impl<T> PartialEq for VolAddressIter<T> {
-  fn eq(&self, other: &Self) -> bool {
-    self.vol_address == other.vol_address && self.slots == other.slots
-  }
-}
-impl<T> Eq for VolAddressIter<T> {}
-impl<T> Iterator for VolAddressIter<T> {
-  type Item = VolAddress<T>;
-
-  fn next(&mut self) -> Option<Self::Item> {
-    if self.slots > 0 {
-      let out = self.vol_address;
-      unsafe {
-        self.slots -= 1;
-        self.vol_address = self.vol_address.offset(1);
-      }
-      Some(out)
-    } else {
-      None
-    }
-  }
-}
-impl<T> FusedIterator for VolAddressIter<T> {}
-#}
-

VolAddressBlock

-

Obviously, having a base address and a length exist separately is error prone. -There's a good reason for slices to keep their pointer and their length -together. We want something like that, which we'll call a "block" because -"array" and "slice" are already things in Rust.

-

-# #![allow(unused_variables)]
-#fn main() {
-#[derive(Debug)]
-pub struct VolAddressBlock<T> {
-  vol_address: VolAddress<T>,
-  slots: usize,
-}
-impl<T> Clone for VolAddressBlock<T> {
-  fn clone(&self) -> Self {
-    VolAddressBlock {
-      vol_address: self.vol_address,
-      slots: self.slots,
-    }
-  }
-}
-impl<T> PartialEq for VolAddressBlock<T> {
-  fn eq(&self, other: &Self) -> bool {
-    self.vol_address == other.vol_address && self.slots == other.slots
-  }
-}
-impl<T> Eq for VolAddressBlock<T> {}
-
-impl<T> VolAddressBlock<T> {
-  pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
-    VolAddressBlock { vol_address, slots }
-  }
-  pub const fn iter(self) -> VolAddressIter<T> {
-    VolAddressIter {
-      vol_address: self.vol_address,
-      slots: self.slots,
-    }
-  }
-  pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
-    self.vol_address.offset(slot as isize)
-  }
-  pub fn index(self, slot: usize) -> VolAddress<T> {
-    if slot < self.slots {
-      unsafe { self.vol_address.offset(slot as isize) }
-    } else {
-      panic!("Index Requested: {} >= Bound: {}", slot, self.slots)
-    }
-  }
-  pub fn get(self, slot: usize) -> Option<VolAddress<T>> {
-    if slot < self.slots {
-      unsafe { Some(self.vol_address.offset(slot as isize)) }
-    } else {
-      None
-    }
-  }
-}
-#}
-

Now we can have something like:

-

-# #![allow(unused_variables)]
-#fn main() {
-const OTHER_MAGIC: VolAddressBlock<u16> = unsafe {
-  VolAddressBlock::new_unchecked(
-    VolAddress::new_unchecked(0x600_0000),
-    240 * 160
-  )
-};
-
-OTHER_MAGIC.index(120 + 80 * 240).write_volatile(0x001F);
-OTHER_MAGIC.index(136 + 80 * 240).write_volatile(0x03E0);
-OTHER_MAGIC.index(120 + 96 * 240).write_volatile(0x7C00);
-#}
-

Docs?

-

If you wanna see these types and methods with a full docs write up you should -check the GBA crate's source.

-

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".

-

Note that if you use a linker script to include any ASM with your Rust program -(eg: the crt0.s file that we setup in the "Development Setup" section), all of -that ASM is "volatile" for these purposes. Volatile isn't actually a hardware -concept, it's just an LLVM concept, and the linker script runs after LLVM has -done its work.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - -