gb_bank/model/mod.rs
1//! Compile-time-safe ROM bank switching for the Game Boy.
2//!
3//! Game Boy cartridges larger than 32 KiB can only map one 16 KiB **ROM bank**
4//! at a time into the `0x4000..0x8000` window; code or data in any other bank is
5//! simply not addressable until that bank is switched in. Getting this wrong
6//! (holding a pointer into a bank that is no longer mapped) is a classic source
7//! of corruption. This crate moves the check to compile time.
8//!
9//! The model is borrowed from [GhostCell]: the *permission* is separated from
10//! the *data*, and a brand type ties them together.
11//!
12//! - A [`Bank<G>`] is a zero-sized token: a witness that group `G`'s bank is
13//! currently mapped.
14//! - A [`Far<T, G>`] is a pointer to a `T` living in group `G`'s bank. It cannot
15//! be read without a [`Bank<G>`](Bank).
16//! - [`scope`] switches into a bank, lends you the token for the duration of a
17//! closure, then restores the previous bank. Because the token is borrowed
18//! `&mut` across the switch, a live reference into a bank can never outlive a
19//! switch away from it; the borrow checker rejects it.
20//!
21//! For dynamic dispatch across banks chosen at runtime, [`DynFar<T>`] erases the
22//! group to a runtime bank number, and [`Warp`] is a deferred banked call: the
23//! analog of a [`Future`], produced by a banked function and executed with
24//! [`Warp::drive`].
25//!
26//! [GhostCell]: https://plv.mpi-sws.org/rustbelt/ghostcell/
27//!
28//! # Examples
29//!
30//! ```ignore
31//! use gb_bank::*;
32//!
33//! // A bank group. (Normally generated by the `#[bank]` macro; shown raw here.)
34//! struct Sound;
35//! impl Group for Sound {
36//! const FIXED: bool = false;
37//! fn bank() -> BankNumber { BankNumber::new(2) }
38//! }
39//!
40//! fn main_loop(anchor: Anchor, bank: &mut Bank<GroupZero>) {
41//! // Enter Sound's bank for a block; `b` witnesses that it is mapped.
42//! scope(anchor, bank, |b: &mut Bank<Sound>| {
43//! let tempo = *b.local(&TEMPO); // read banked data, no extra switch
44//! // ... use `tempo` while still inside Sound's bank ...
45//! });
46//! // Sound's bank is restored to the caller's on the way out.
47//! }
48//! ```
49//!
50//! # Panics, unwinding, and interrupts
51//!
52//! The bank restore in [`scope`] (and the [`Far`] call / borrow paths) runs *after*
53//! the closure returns, so an unwinding panic would skip it and leave the wrong
54//! bank mapped. This is sound on the Game Boy because the target aborts on panic
55//! (there is no unwinder), so a panic never resumes into a stale-bank token. These
56//! types are not designed to be unwind-safe on a hosted, unwinding target.
57//!
58//! The safety model also assumes that nothing changes the mapped bank *behind the
59//! token's back*. An interrupt handler that switches banks (e.g. to read banked
60//! data) must save [`current_bank`] on entry and restore it before returning, so the
61//! interrupted code resumes with the bank its live token still claims is mapped. An
62//! ISR that is not bank-transparent breaks the invariant, just like a raw
63//! [`switch_bank`] that is not paired with a matching restore. Under
64//! `gb_wide_bank="mbc5"` a handler must not switch banks at all; see
65//! [`switch_bank`].
66
67
68use core::{any::TypeId, marker::PhantomData};
69
70mod far;
71pub(crate) mod warp;
72
73pub use far::{DynFar, Far, FarCall, FarWith};
74pub use warp::{Warp, BankSafe};
75
76// ===== Low-level bank switch =====
77
78/// The integer a [`BankNumber`] is stored in: one byte, or two under
79/// `gb_wide_bank="mbc5"`. Public only so the `bank::module!` marker can name it.
80#[doc(hidden)]
81#[cfg(not(gb_wide_bank = "mbc5"))]
82pub type BankRepr = u8;
83#[doc(hidden)]
84#[cfg(gb_wide_bank = "mbc5")]
85pub type BankRepr = u16;
86
87/// A ROM bank number, held as a `u8` or, under `gb_wide_bank="mbc5"`, a `u16`.
88///
89/// It is a type rather than a plain integer so that the storage width stays out of
90/// every signature: how wide a bank number is, and how selecting one reaches the
91/// cartridge, is a property of the build. `cargo-gb` derives it from `wide_banks`
92/// in `header.toml` and passes `--cfg gb_wide_bank`; [`MAX`](BankNumber::MAX) is
93/// all that changes between modes.
94///
95/// | `gb_wide_bank` | [`MAX`](BankNumber::MAX) | Bank register writes |
96/// |---|---|---|
97/// | unset | 255 | `0x2000` |
98/// | `"mbc1"` | 127 | `0x2000` (5 bits), `0x4000` (2 bits) |
99/// | `"mbc5"` | 511 | `0x3000` (bit 8), `0x2000` (low byte) |
100///
101/// This is the widest number the *build* can carry. The cartridge's own limit is
102/// tighter and `gb-bank-pack` enforces it at link time.
103///
104/// # Wide banking and GBDK
105///
106/// GBDK reaches the bank register through its own one-byte shadow, exported here
107/// as `_current_bank`, and writes only `0x2000`. `"mbc1"` keeps both, so linking
108/// `gbdk-sys` still works; the parts of GBDK that read the shadow (its far
109/// pointers, banked-call trampoline, and crash handler) would misread a bank above
110/// 31, and none of them are reachable from Rust. `"mbc5"` has no one-byte shadow to
111/// export, so a program linking `gbdk-sys` fails to link, and no interrupt handler
112/// may switch banks (see [`switch_bank`]).
113#[repr(transparent)]
114#[derive(Clone, Copy, PartialEq, Eq, Debug)]
115pub struct BankNumber(BankRepr);
116
117impl BankNumber {
118 /// The highest bank number this build can represent.
119 #[cfg(not(any(gb_wide_bank = "mbc1", gb_wide_bank = "mbc5")))]
120 pub const MAX: u16 = 255;
121 /// The highest bank number this build can represent.
122 #[cfg(gb_wide_bank = "mbc1")]
123 pub const MAX: u16 = 127;
124 /// The highest bank number this build can represent.
125 #[cfg(gb_wide_bank = "mbc5")]
126 pub const MAX: u16 = 511;
127
128 /// Name a bank, rejecting one this build cannot represent.
129 ///
130 /// In a `const` context an out-of-range number is a compile error.
131 #[inline(always)]
132 pub const fn new(n: u16) -> Self {
133 assert!(n <= Self::MAX, "bank number is wider than this build carries");
134 BankNumber(n as BankRepr)
135 }
136
137 /// Name a bank without the range check.
138 ///
139 /// # Safety
140 ///
141 /// `n` must be no greater than [`MAX`](BankNumber::MAX); a wider number is
142 /// silently truncated and selects the wrong bank.
143 #[doc(hidden)]
144 #[inline(always)]
145 pub const unsafe fn new_unchecked(n: u16) -> Self {
146 BankNumber(n as BankRepr)
147 }
148
149 /// The bank number, for a [`Mapper`] writing it to the cartridge.
150 #[inline(always)]
151 pub const fn get(self) -> u16 {
152 self.0 as u16
153 }
154}
155
156// The software bank shadow. The MBC bank register is write-only, so the mapped
157// bank is tracked here: High RAM cells read and written with the immediate `ldh`
158// form through gb-hram. The runtime zero-initialises HRAM at reset (as gb-hram
159// requires), so it starts at 0.
160#[cfg(not(gb_wide_bank = "mbc5"))]
161mod shadow {
162 use super::BankNumber;
163 use gb_hram::prelude::*;
164
165 // `as "_current_bank"` exports the storage as the `__current_bank` symbol that
166 // GBDK's C runtime also references (the target adds the leading underscore).
167 gb_hram::hram! {
168 static CURRENT_BANK as "_current_bank": HramAtomicCell<u8>;
169 }
170
171 #[inline(always)]
172 pub fn set(bank: BankNumber) {
173 CURRENT_BANK.set(bank.get() as u8);
174 }
175
176 #[inline(always)]
177 pub fn get() -> BankNumber {
178 unsafe { BankNumber::new_unchecked(CURRENT_BANK.get() as u16) }
179 }
180}
181
182#[cfg(gb_wide_bank = "mbc5")]
183mod shadow {
184 use super::BankNumber;
185 use gb_hram::prelude::*;
186
187 // Two one-byte cells rather than one two-byte cell: each access stays a single
188 // `ldh`, and no interrupt handler may switch banks in this mode, so nothing
189 // observes the pair half updated. There is no `_current_bank` export because a
190 // one-byte symbol cannot describe a nine-bit bank.
191 gb_hram::hram! {
192 static CURRENT_BANK_LO: HramAtomicCell<u8>;
193 static CURRENT_BANK_HI: HramAtomicCell<u8>;
194 }
195
196 #[inline(always)]
197 pub fn set(bank: BankNumber) {
198 CURRENT_BANK_LO.set(bank.get() as u8);
199 CURRENT_BANK_HI.set((bank.get() >> 8) as u8);
200 }
201
202 #[inline(always)]
203 pub fn get() -> BankNumber {
204 let n = CURRENT_BANK_LO.get() as u16 | (CURRENT_BANK_HI.get() as u16) << 8;
205 unsafe { BankNumber::new_unchecked(n) }
206 }
207}
208
209/// How a cartridge maps a bank into `0x4000..0x8000`.
210///
211/// A program on a stock cartridge never touches this: `header.toml` picks the
212/// built-in that matches.
213///
214/// The controllers differ only in which registers a bank number is spread across,
215/// so that is all this covers. A custom cartridge implements it and names the
216/// implementation with [`set_mapper!`](crate::set_mapper).
217///
218/// # Safety
219///
220/// [`select`](Mapper::select) must leave bank `bank` mapped at `0x4000` and change
221/// nothing else the program can observe.
222pub unsafe trait Mapper {
223 /// Map `bank` into `0x4000..0x8000`.
224 ///
225 /// # Safety
226 ///
227 /// The cartridge must have that bank.
228 unsafe fn select(bank: BankNumber);
229}
230
231/// One register at `0x2000` takes the whole number: MBC1, MBC2 and MBC3, and MBC5
232/// below bank 256.
233#[doc(hidden)]
234pub struct MbcN;
235
236unsafe impl Mapper for MbcN {
237 #[inline(always)]
238 unsafe fn select(bank: BankNumber) {
239 unsafe { core::ptr::write_volatile(0x2000 as *mut u8, bank.get() as u8) };
240 }
241}
242
243/// MBC1 above bank 31: five bits at `0x2000` and two more at `0x4000`.
244///
245/// Those two extend the ROM bank only in banking mode 0, which is the power-on
246/// default, so a program that selects mode 1 for RAM banking cannot reach bank 32
247/// and up.
248#[doc(hidden)]
249pub struct Mbc1Wide;
250
251unsafe impl Mapper for Mbc1Wide {
252 #[inline(always)]
253 unsafe fn select(bank: BankNumber) {
254 let n = bank.get();
255 unsafe {
256 core::ptr::write_volatile(0x4000 as *mut u8, ((n >> 5) & 0x03) as u8);
257 core::ptr::write_volatile(0x2000 as *mut u8, (n & 0x1F) as u8);
258 }
259 }
260}
261
262/// MBC5 above bank 255: the ninth bit lives in its own register at `0x3000`.
263#[doc(hidden)]
264pub struct Mbc5Wide;
265
266unsafe impl Mapper for Mbc5Wide {
267 #[inline(always)]
268 unsafe fn select(bank: BankNumber) {
269 let n = bank.get();
270 unsafe {
271 core::ptr::write_volatile(0x3000 as *mut u8, (n >> 8) as u8);
272 core::ptr::write_volatile(0x2000 as *mut u8, n as u8);
273 }
274 }
275}
276
277/// Name the [`Mapper`] this program's cartridge uses.
278///
279/// Only a custom cartridge needs this; a stock one is picked from `header.toml`.
280///
281/// Write it once, anywhere in the program, and build with `--cfg gb_custom_mapper`
282/// so that `gb-bank` does not also name one.
283///
284/// ```ignore
285/// struct MyCart;
286///
287/// unsafe impl gb_bank::Mapper for MyCart {
288/// unsafe fn select(bank: gb_bank::BankNumber) {
289/// unsafe { core::ptr::write_volatile(0x2100 as *mut u8, bank.get() as u8) };
290/// }
291/// }
292///
293/// gb_bank::set_mapper!(MyCart);
294/// ```
295#[macro_export]
296macro_rules! set_mapper {
297 ($mapper:ty) => {
298 const _: () = {
299 #[unsafe(no_mangle)]
300 unsafe extern "Rust" fn __gb_bank_select(bank: $crate::BankNumber) {
301 unsafe { <$mapper as $crate::Mapper>::select(bank) }
302 }
303 };
304 };
305}
306
307unsafe extern "Rust" {
308 fn __gb_bank_select(bank: BankNumber);
309}
310
311// A cartridge with its own hardware sets `gb_custom_mapper` and names the
312// implementation itself; without it the built-in one for the cartridge is used.
313#[cfg(not(gb_custom_mapper))]
314mod builtin {
315 #[cfg(not(any(gb_wide_bank = "mbc1", gb_wide_bank = "mbc5")))]
316 crate::set_mapper!(super::MbcN);
317 #[cfg(gb_wide_bank = "mbc1")]
318 crate::set_mapper!(super::Mbc1Wide);
319 #[cfg(gb_wide_bank = "mbc5")]
320 crate::set_mapper!(super::Mbc5Wide);
321}
322
323/// Switch the active ROM bank.
324///
325/// Writes the cartridge's bank register (see [`BankNumber`] for which, and how many)
326/// and the software bank shadow that interrupt handlers rely on to save and
327/// restore the mapped bank.
328///
329/// Prefer the safe [`scope`] / [`Far`] API; this is the raw primitive for hand
330/// rolled control. After calling it, use [`Bank::assume`] to mint a matching
331/// token.
332///
333/// A cartridge with no switchable window has nothing to write, so this compiles
334/// to nothing.
335///
336/// # Safety
337///
338/// `bank` must be a valid bank for the cartridge's MBC, and the caller is
339/// responsible for restoring the previous bank and for any pointers that become
340/// invalid across the switch.
341///
342/// MBC1, MBC2, and MBC3 read a written `0` as `1`, so `switch_bank(BankNumber::new(0))`
343/// maps bank 1 there while the shadow records 0, and an interrupt would restore the
344/// wrong bank. Only MBC5 maps bank 0 into the window.
345///
346/// Under `gb_wide_bank="mbc5"` the shadow spans two cells, so an interrupt landing
347/// between the two writes would afterwards read a bank that was never mapped. An
348/// interrupt handler must not call this in that mode, which also rules out `far!`
349/// and [`DynFar`] there, since those restore through [`current_bank`].
350// With nothing left in the body on a flat ROM, the call would be all that remains.
351#[cfg_attr(not(gb_flat_rom), inline(never))]
352#[cfg_attr(gb_flat_rom, inline(always))]
353pub unsafe fn switch_bank(bank: BankNumber) {
354 if !FLAT_ROM {
355 shadow::set(bank);
356 unsafe { __gb_bank_select(bank) };
357 }
358}
359
360/// Read the currently mapped bank from the software shadow.
361///
362/// The MBC register is write-only, so the active bank cannot be read from
363/// hardware; this returns the shadow maintained by [`switch_bank`]. Intended for
364/// interrupt save/restore; the normal call path restores via the caller's group
365/// type and never needs it.
366///
367/// On a cartridge with no switchable window this is always bank 0, and no shadow
368/// is kept.
369#[inline]
370pub fn current_bank() -> BankNumber {
371 if FLAT_ROM {
372 BankNumber::new(0)
373 } else {
374 shadow::get()
375 }
376}
377
378// ===== Group brands =====
379
380/// The compile-time identity of a bank group.
381///
382/// The implementing type *is* the brand: two values share a group iff they share
383/// the type parameter `G`, checked entirely at compile time. The runtime
384/// [`bank()`](Group::bank) number is only consulted for the actual hardware
385/// switch.
386///
387/// Groups are normally generated by the `#[bank]` macro, one per banked module.
388pub trait Group: 'static {
389 /// Whether this group is permanently mapped, so switching to it is a no-op.
390 ///
391 /// [`GroupZero`] always is, and so is every group on a cartridge with no
392 /// switchable window.
393 const FIXED: bool;
394
395 /// The physical bank number, resolved at link time by gb-bank-pack.
396 fn bank() -> BankNumber;
397}
398
399/// Whether the cartridge has no switchable window, so every group sits in bank 0.
400///
401/// `cargo-gb` sets this from `cartridge_type` in `header.toml`. `bank::module!`
402/// reads it through this const rather than the cfg directly, so a crate using the
403/// macro does not have to declare the cfg itself.
404#[doc(hidden)]
405pub const FLAT_ROM: bool = cfg!(gb_flat_rom);
406
407/// Bank 0: the always-mapped region.
408///
409/// Switching to or from a [`FIXED`](Group::FIXED) group is elided, so calls and
410/// data accesses anchored at `GroupZero` never touch the MBC register.
411pub struct GroupZero;
412
413impl Group for GroupZero {
414 const FIXED: bool = true;
415 #[inline(always)]
416 fn bank() -> BankNumber {
417 BankNumber::new(0)
418 }
419}
420
421// ===== Bank token + scope =====
422
423/// A witness that group `G`'s bank is currently mapped.
424///
425/// This is the GhostCell-style permission token: a zero-sized value whose type
426/// `G` is the brand. Borrowing it shared ([`local`](Far::local)) yields references
427/// into the bank; borrowing it `&mut` (a switch via [`scope`]) is what changes
428/// the mapping. Because you cannot do both at once, a reference obtained from
429/// `local` can never be live across a switch; the conflict is a borrow error.
430///
431/// The token is `!Send` and only mintable through [`assume`](Bank::assume) (or,
432/// safely, the closure argument of [`scope`]).
433///
434/// # Examples
435///
436/// ```ignore
437/// fn play(b: &mut Bank<Sound>) {
438/// let note = *b.local(&MELODY); // `b` proves Sound is mapped
439/// // ...
440/// }
441/// ```
442///
443/// The token is `!Send` and `!Sync`: a "this bank is mapped" proof cannot cross
444/// threads, nor be parked in a `static` and read back later.
445///
446/// ```compile_fail
447/// # use gb_bank::*;
448/// fn assert_send<T: Send>() {}
449/// assert_send::<Bank<GroupZero>>(); // ERROR: Bank is !Send
450/// ```
451///
452/// ```compile_fail
453/// # use gb_bank::*;
454/// fn assert_sync<T: Sync>() {}
455/// assert_sync::<Bank<GroupZero>>(); // ERROR: Bank is !Sync
456/// ```
457pub struct Bank<G: Group> {
458 _bank: PhantomData<G>,
459}
460
461impl<G: Group> !Send for Bank<G> {}
462impl<G: Group> !Sync for Bank<G> {}
463
464impl<G: Group> Bank<G> {
465 /// Mint a token, asserting that group `G`'s bank is already mapped.
466 ///
467 /// This is the escape hatch for hand-rolled control: after a raw
468 /// [`switch_bank`] or inline asm, call `assume` to re-enter the safe
469 /// [`local`](Far::local) / [`scope`] API.
470 ///
471 /// # Safety
472 ///
473 /// Group `G`'s bank must actually be mapped at the call site; otherwise every
474 /// subsequent `local`/call through this token reads the wrong bank.
475 #[inline(always)]
476 pub const unsafe fn assume() -> Self {
477 Bank { _bank: PhantomData }
478 }
479}
480
481/// Proof that the holder runs in bank 0 (the always-mapped region), and
482/// so may run a closure across a bank switch via [`scope`] / [`there`](FarWith::there).
483///
484/// Those operations lend a closure across a switch; the closure's *code* must stay
485/// mapped while it runs, which only holds for code in bank 0. An `Anchor` is that
486/// guarantee, as a zero-sized [`Copy`] capability. The `#[bank::main]` and
487/// `#[bank::zero]` macros mint one at the top of every body (their code is
488/// in bank 0); a `#[bank]` function gets none, so it cannot reach `scope` / `there`,
489/// which would otherwise run a banked closure the switch unmaps.
490///
491/// Being `Copy`, it threads into nested scopes without borrow friction.
492#[derive(Clone, Copy)]
493pub struct Anchor(());
494
495// An `Anchor` witnesses that the *current* code is in bank 0, so carrying one into a
496// banked call would falsify it: the callee would hold a bank-0 capability while
497// running from a switchable bank.
498impl !BankSafe for Anchor {}
499
500impl Anchor {
501 /// Assert that the calling code runs in bank 0.
502 ///
503 /// # Safety
504 ///
505 /// The caller must physically reside in bank 0 (the always-mapped region): a
506 /// `#[bank::main]` / `#[bank::zero]` body, or a plain `fn` the linker keeps
507 /// in bank 0. A `#[bank]` function is *not* in bank 0 and must never mint one.
508 ///
509 /// An `Anchor` must also not be moved into `#[bank]` code: it witnesses that the
510 /// *current* code is in bank 0, which carrying it into a banked function would
511 /// falsify. (Safe code cannot do this: the macro-minted `Anchor` is hygienic and
512 /// there is no other safe constructor.)
513 #[inline(always)]
514 pub const unsafe fn assume() -> Self {
515 Anchor(())
516 }
517}
518
519/// Enter group `G`'s bank for the duration of `f`, then restore the caller (`C`).
520///
521/// This is the safe heart of the crate. It switches to `G`, hands `f` a fresh
522/// [`Bank<G>`] token, runs it, and switches back to `C`. Threading the caller's
523/// `&mut Bank<C>` is what makes the cross-bank footgun a compile error: a
524/// reference borrowed from one token cannot survive a nested [`scope`] that needs
525/// the same token `&mut`.
526///
527/// The switch is elided entirely when `G` is [`FIXED`](Group::FIXED) or `C == G`
528/// (same group, folded at compile time), so same-bank work costs nothing.
529///
530/// # Examples
531///
532/// ```ignore
533/// fn run(anchor: Anchor, bank: &mut Bank<GroupZero>) {
534/// let first = scope(anchor, bank, |b: &mut Bank<Sound>| *b.local(&MELODY).first().unwrap());
535/// // back in the caller's bank here
536/// }
537/// ```
538///
539/// The borrow checker stops a banked reference from escaping the switch:
540///
541/// ```compile_fail
542/// # use gb_bank::*;
543/// # struct G;
544/// # impl Group for G {
545/// # const FIXED: bool = false;
546/// # fn bank() -> BankNumber { BankNumber::new(1) }
547/// # }
548/// fn leak<'a>(anchor: Anchor, bank: &mut Bank<GroupZero>, data: &'a Far<u8, G>) -> &'a u8 {
549/// scope(anchor, bank, |g| g.local(data)) // ERROR: the ref borrows the inner token
550/// }
551/// ```
552#[inline]
553pub fn scope<C: Group, G: Group, R: BankSafe>(
554 _anchor: Anchor,
555 outer: &mut Bank<C>,
556 f: impl FnOnce(&mut Bank<G>) -> R,
557) -> R {
558 switch_run(outer, f)
559}
560
561/// The bank-switch primitive behind [`scope`] and the one-shot [`Warp::drive`] /
562/// [`FarCall::invoke`]: switch to `G`, run `f`, restore `C`, eliding when `G` is
563/// [`FIXED`](Group::FIXED) or `C == G`.
564///
565/// Unlike [`scope`] it takes no [`Anchor`], so it stays crate-internal: its callers
566/// either thread an `Anchor` through (`scope` / [`there`](FarWith::there), whose `f`
567/// is a user closure) or run no user closure at all (`drive` / `invoke` call the
568/// banked function themselves, taking [`switch_run_far`] when the caller is banked).
569#[inline]
570pub(crate) fn switch_run<C: Group, G: Group, R>(
571 _outer: &mut Bank<C>,
572 f: impl FnOnce(&mut Bank<G>) -> R,
573) -> R {
574 if G::FIXED || const { TypeId::of::<C>() == TypeId::of::<G>() } {
575 let mut b = unsafe { Bank::<G>::assume() };
576 f(&mut b)
577 } else {
578 unsafe { switch_bank(G::bank()) };
579 let mut b = unsafe { Bank::<G>::assume() };
580 let r = f(&mut b);
581 if !C::FIXED {
582 unsafe { switch_bank(C::bank()) };
583 }
584 r
585 }
586}
587
588/// [`switch_run`] for a caller that is itself banked, whose code leaves the window
589/// during the call: `#[inline(never)]` keeps the switch in a bank-0 trampoline.
590#[inline(never)]
591pub(crate) fn switch_run_far<C: Group, G: Group, R>(
592 outer: &mut Bank<C>,
593 f: impl FnOnce(&mut Bank<G>) -> R,
594) -> R {
595 switch_run(outer, f)
596}