gba/docs/01-quirks/03-volatile_destination.html
2018-12-24 20:24:29 +00:00

522 lines
27 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Volatile Destination - Rust GBA Guide</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">var path_to_root = "../";</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<ol class="chapter"><li><a href="../00-introduction/00-index.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><ol class="section"><li><a href="../00-introduction/01-requirements.html"><strong aria-hidden="true">1.1.</strong> Reader Requirements</a></li><li><a href="../00-introduction/02-goals_and_style.html"><strong aria-hidden="true">1.2.</strong> Book Goals and Style</a></li><li><a href="../00-introduction/03-development-setup.html"><strong aria-hidden="true">1.3.</strong> Development Setup</a></li><li><a href="../00-introduction/04-hello-magic.html"><strong aria-hidden="true">1.4.</strong> Hello, Magic</a></li><li><a href="../00-introduction/05-help_and_resources.html"><strong aria-hidden="true">1.5.</strong> Help and Resources</a></li></ol></li><li><a href="../01-quirks/00-index.html"><strong aria-hidden="true">2.</strong> Quirks</a></li><li><ol class="section"><li><a href="../01-quirks/01-no_std.html"><strong aria-hidden="true">2.1.</strong> No Std</a></li><li><a href="../01-quirks/02-fixed_only.html"><strong aria-hidden="true">2.2.</strong> Fixed Only</a></li><li><a href="../01-quirks/03-volatile_destination.html" class="active"><strong aria-hidden="true">2.3.</strong> Volatile Destination</a></li><li><a href="../01-quirks/04-newtype.html"><strong aria-hidden="true">2.4.</strong> Newtype</a></li><li><a href="../01-quirks/05-const_asserts.html"><strong aria-hidden="true">2.5.</strong> Const Asserts</a></li></ol></li><li><a href="../02-concepts/00-index.html"><strong aria-hidden="true">3.</strong> Concepts</a></li><li><ol class="section"><li><a href="../02-concepts/01-cpu.html"><strong aria-hidden="true">3.1.</strong> CPU</a></li><li><a href="../02-concepts/02-bios.html"><strong aria-hidden="true">3.2.</strong> BIOS</a></li><li><a href="../02-concepts/03-wram.html"><strong aria-hidden="true">3.3.</strong> Work RAM</a></li><li><a href="../02-concepts/04-io-registers.html"><strong aria-hidden="true">3.4.</strong> IO Registers</a></li><li><a href="../02-concepts/05-palram.html"><strong aria-hidden="true">3.5.</strong> Palette RAM</a></li><li><a href="../02-concepts/06-vram.html"><strong aria-hidden="true">3.6.</strong> Video RAM</a></li><li><a href="../02-concepts/07-oam.html"><strong aria-hidden="true">3.7.</strong> Object Attribute Memory</a></li><li><a href="../02-concepts/08-rom.html"><strong aria-hidden="true">3.8.</strong> Game Pak ROM / Flash ROM</a></li><li><a href="../02-concepts/09-sram.html"><strong aria-hidden="true">3.9.</strong> Save RAM</a></li></ol></li><li><a href="../03-video/00-index.html"><strong aria-hidden="true">4.</strong> Video</a></li><li><ol class="section"><li><a href="../03-video/01-rgb15.html"><strong aria-hidden="true">4.1.</strong> RBG15 Color</a></li><li><a href="../03-video/TODO.html"><strong aria-hidden="true">4.2.</strong> TODO</a></li></ol></li><li><a href="../04-non-video/00-index.html"><strong aria-hidden="true">5.</strong> Non-Video</a></li><li><ol class="section"><li><a href="../04-non-video/01-buttons.html"><strong aria-hidden="true">5.1.</strong> Buttons</a></li><li><a href="../04-non-video/02-timers.html"><strong aria-hidden="true">5.2.</strong> Timers</a></li><li><a href="../04-non-video/03-dma.html"><strong aria-hidden="true">5.3.</strong> Direct Memory Access</a></li><li><a href="../04-non-video/04-sound.html"><strong aria-hidden="true">5.4.</strong> Sound</a></li><li><a href="../04-non-video/05-interrupts.html"><strong aria-hidden="true">5.5.</strong> Interrupts</a></li><li><a href="../04-non-video/06-link_cable.html"><strong aria-hidden="true">5.6.</strong> Link Cable</a></li><li><a href="../04-non-video/07-game_pak.html"><strong aria-hidden="true">5.7.</strong> Game Pak</a></li></ol></li><li><a href="../05-examples/00-index.html"><strong aria-hidden="true">6.</strong> Examples</a></li></ol>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust GBA Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<a class="header" href="#volatile-destination" id="volatile-destination"><h1>Volatile Destination</h1></a>
<p>TODO: update this when we can make more stuff <code>const</code></p>
<a class="header" href="#volatile-memory" id="volatile-memory"><h2>Volatile Memory</h2></a>
<p>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</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let mut x = 5;
x = 7;
#}</code></pre></pre>
<p>The compiler won't actually ever put 5 into <code>x</code>. It'll skip straight to putting
7 in <code>x</code>, because we never read from <code>x</code> 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.</p>
<p>However, what if we had a bit of hardware where we wanted to do a write and that
did something <em>other than</em> keeping the value for us to look at later? As you saw
in the <code>hello_magic</code> example, we have to use a <code>write_volatile</code> operation.
Volatile means &quot;just do it anyway&quot;. 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
<code>write_volatile</code> instead of <code>write</code>.</p>
<p>This is kinda error prone though, right? Because it's just a raw pointer, so we
might forget to use <code>write_volatile</code> at some point.</p>
<p>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.</p>
<a class="header" href="#constructing-the-voladdress-type" id="constructing-the-voladdress-type"><h3>Constructing The VolAddress Type</h3></a>
<p>First, we want a type that stores a location within the address space. This can
be a pointer, or a <code>usize</code>, and we'll use a <code>usize</code> because that's easier to
work with in a <code>const</code> context (and we want to have <code>const</code> when we can get it).
We'll also have our type use <code>NonZeroUsize</code> instead of just <code>usize</code> so that
<code>Option&lt;VolAddress&lt;T&gt;&gt;</code> 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 <em>just</em> an address number then we wouldn't be able to
track what type the address is for. We need some
<a href="https://doc.rust-lang.org/core/marker/struct.PhantomData.html">PhantomData</a>,
and specifically we need the phantom data to be for <code>*mut T</code>:</p>
<ul>
<li>If we used <code>*const T</code> that'd have the wrong
<a href="https://doc.rust-lang.org/nomicon/subtyping.html">variance</a>.</li>
<li>If we used <code>&amp;mut T</code> then that's fusing in the ideas of <em>lifetime</em> and
<em>exclusive access</em> to our type. That's potentially important, but that's also
an abstraction we'll build <em>on top of</em> this <code>VolAddress</code> type if we need it.</li>
</ul>
<p>One abstraction layer at a time, so we start with just a phantom pointer. This gives us a type that looks like this:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
#[derive(Debug)]
#[repr(transparent)]
pub struct VolAddress&lt;T&gt; {
address: NonZeroUsize,
marker: PhantomData&lt;*mut T&gt;,
}
#}</code></pre></pre>
<p>Now, because of how <code>derive</code> is specified, it derives traits <em>if the generic
parameter</em> 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.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
impl&lt;T&gt; Clone for VolAddress&lt;T&gt; {
fn clone(&amp;self) -&gt; Self {
*self
}
}
impl&lt;T&gt; Copy for VolAddress&lt;T&gt; {}
impl&lt;T&gt; PartialEq for VolAddress&lt;T&gt; {
fn eq(&amp;self, other: &amp;Self) -&gt; bool {
self.address == other.address
}
}
impl&lt;T&gt; Eq for VolAddress&lt;T&gt; {}
impl&lt;T&gt; PartialOrd for VolAddress&lt;T&gt; {
fn partial_cmp(&amp;self, other: &amp;Self) -&gt; Option&lt;Ordering&gt; {
Some(self.address.cmp(&amp;other.address))
}
}
impl&lt;T&gt; Ord for VolAddress&lt;T&gt; {
fn cmp(&amp;self, other: &amp;Self) -&gt; Ordering {
self.address.cmp(&amp;other.address)
}
}
#}</code></pre></pre>
<p>Boilerplate junk, not interesting. There's a reason that you derive those traits
99% of the time in Rust.</p>
<a class="header" href="#constructing-a-voladdress-value" id="constructing-a-voladdress-value"><h3>Constructing A VolAddress Value</h3></a>
<p>Okay so here's the next core concept: If we unsafely <em>construct</em> a
<code>VolAddress&lt;T&gt;</code>, then we can safely <em>use</em> the value once it's been properly
created.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![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&lt;T&gt; VolAddress&lt;T&gt; {
pub const unsafe fn new_unchecked(address: usize) -&gt; Self {
VolAddress {
address: NonZeroUsize::new_unchecked(address),
marker: PhantomData,
}
}
pub const unsafe fn cast&lt;Z&gt;(self) -&gt; VolAddress&lt;Z&gt; {
VolAddress {
address: self.address,
marker: PhantomData,
}
}
pub unsafe fn offset(self, offset: isize) -&gt; Self {
VolAddress {
address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::&lt;T&gt;())),
marker: PhantomData,
}
}
}
#}</code></pre></pre>
<p>So what are the unsafety rules here?</p>
<ul>
<li>Non-null, obviously.</li>
<li>Must be aligned for <code>T</code></li>
<li>Must always produce valid bit patterns for <code>T</code></li>
<li>Must not be part of the address space that Rust's stack or allocator will ever
uses.</li>
</ul>
<p>So, again using the <code>hello_magic</code> example, we had</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
(0x400_0000 as *mut u16).write_volatile(0x0403);
#}</code></pre></pre>
<p>And instead we could declare</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
const MAGIC_LOCATION: VolAddress&lt;u16&gt; = unsafe { VolAddress::new_unchecked(0x400_0000) };
#}</code></pre></pre>
<a class="header" href="#using-a-voladdress-value" id="using-a-voladdress-value"><h3>Using A VolAddress Value</h3></a>
<p>Now that we've named the magic location, we want to write to it.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
impl&lt;T&gt; VolAddress&lt;T&gt; {
pub fn read(self) -&gt; T
where
T: Copy,
{
unsafe { (self.address.get() as *mut T).read_volatile() }
}
pub unsafe fn read_non_copy(self) -&gt; 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) }
}
}
#}</code></pre></pre>
<p>So if the type is <code>Copy</code> we can <code>read</code> it as much as we want. If, somehow, the
type isn't <code>Copy</code>, then it might be <code>Drop</code>, and that means if we read out a
value over and over we could cause the <code>drop</code> method to trigger UB. Since the
end user might really know what they're doing, we provide an unsafe backup
<code>read_non_copy</code>.</p>
<p>On the other hand, we can <code>write</code> to the location as much as we want. Even if
the type isn't <code>Copy</code>, <em>not running <code>Drop</code> is safe</em>, so a <code>write</code> is always
safe.</p>
<p>Now we can write to our magical location.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
MAGIC_LOCATION.write(0x0403);
#}</code></pre></pre>
<a class="header" href="#voladdress-iteration" id="voladdress-iteration"><h3>VolAddress Iteration</h3></a>
<p>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
<em>all the locations</em>. That's not particularly hard.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
impl&lt;T&gt; VolAddress&lt;T&gt; {
pub const unsafe fn iter_slots(self, slots: usize) -&gt; VolAddressIter&lt;T&gt; {
VolAddressIter { vol_address: self, slots }
}
}
#[derive(Debug)]
pub struct VolAddressIter&lt;T&gt; {
vol_address: VolAddress&lt;T&gt;,
slots: usize,
}
impl&lt;T&gt; Clone for VolAddressIter&lt;T&gt; {
fn clone(&amp;self) -&gt; Self {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl&lt;T&gt; PartialEq for VolAddressIter&lt;T&gt; {
fn eq(&amp;self, other: &amp;Self) -&gt; bool {
self.vol_address == other.vol_address &amp;&amp; self.slots == other.slots
}
}
impl&lt;T&gt; Eq for VolAddressIter&lt;T&gt; {}
impl&lt;T&gt; Iterator for VolAddressIter&lt;T&gt; {
type Item = VolAddress&lt;T&gt;;
fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt; {
if self.slots &gt; 0 {
let out = self.vol_address;
unsafe {
self.slots -= 1;
self.vol_address = self.vol_address.offset(1);
}
Some(out)
} else {
None
}
}
}
impl&lt;T&gt; FusedIterator for VolAddressIter&lt;T&gt; {}
#}</code></pre></pre>
<a class="header" href="#voladdressblock" id="voladdressblock"><h3>VolAddressBlock</h3></a>
<p>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 &quot;block&quot; because
&quot;array&quot; and &quot;slice&quot; are already things in Rust.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
#[derive(Debug)]
pub struct VolAddressBlock&lt;T&gt; {
vol_address: VolAddress&lt;T&gt;,
slots: usize,
}
impl&lt;T&gt; Clone for VolAddressBlock&lt;T&gt; {
fn clone(&amp;self) -&gt; Self {
VolAddressBlock {
vol_address: self.vol_address,
slots: self.slots,
}
}
}
impl&lt;T&gt; PartialEq for VolAddressBlock&lt;T&gt; {
fn eq(&amp;self, other: &amp;Self) -&gt; bool {
self.vol_address == other.vol_address &amp;&amp; self.slots == other.slots
}
}
impl&lt;T&gt; Eq for VolAddressBlock&lt;T&gt; {}
impl&lt;T&gt; VolAddressBlock&lt;T&gt; {
pub const unsafe fn new_unchecked(vol_address: VolAddress&lt;T&gt;, slots: usize) -&gt; Self {
VolAddressBlock { vol_address, slots }
}
pub const fn iter(self) -&gt; VolAddressIter&lt;T&gt; {
VolAddressIter {
vol_address: self.vol_address,
slots: self.slots,
}
}
pub unsafe fn index_unchecked(self, slot: usize) -&gt; VolAddress&lt;T&gt; {
self.vol_address.offset(slot as isize)
}
pub fn index(self, slot: usize) -&gt; VolAddress&lt;T&gt; {
if slot &lt; self.slots {
unsafe { self.vol_address.offset(slot as isize) }
} else {
panic!(&quot;Index Requested: {} &gt;= Bound: {}&quot;, slot, self.slots)
}
}
pub fn get(self, slot: usize) -&gt; Option&lt;VolAddress&lt;T&gt;&gt; {
if slot &lt; self.slots {
unsafe { Some(self.vol_address.offset(slot as isize)) }
} else {
None
}
}
}
#}</code></pre></pre>
<p>Now we can have something like:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
const OTHER_MAGIC: VolAddressBlock&lt;u16&gt; = 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);
#}</code></pre></pre>
<a class="header" href="#docs" id="docs"><h3>Docs?</h3></a>
<p>If you wanna see these types and methods with a full docs write up you should
check the GBA crate's source.</p>
<a class="header" href="#volatile-asm" id="volatile-asm"><h2>Volatile ASM</h2></a>
<p>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, &quot;hey
just do what I'm telling you, don't get smart about it&quot;.</p>
<p>Normally when you have some <code>asm!</code> 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, <code>asm!</code> 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.</p>
<p>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 <code>asm!</code> 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 &quot;do it anyway&quot;.</p>
<p>Note that if you use a linker script to include any ASM with your Rust program
(eg: the <code>crt0.s</code> file that we setup in the &quot;Development Setup&quot; section), all of
that ASM is &quot;volatile&quot; for these purposes. Volatile isn't actually a <em>hardware</em>
concept, it's just an LLVM concept, and the linker script runs after LLVM has
done its work.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../01-quirks/02-fixed_only.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../01-quirks/04-newtype.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="../01-quirks/02-fixed_only.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="../01-quirks/04-newtype.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>