gb_bank_safe/lib.rs
1//! The [`BankSafe`] marker.
2//!
3//! It lives in its own crate so that `gb-bank`'s own dependencies can bound their
4//! APIs on it. Use it through `gb_bank::BankSafe`, which re-exports it.
5
6#![no_std]
7#![feature(auto_traits, negative_impls)]
8
9/// A value safe to carry across a bank switch: it embeds no pointer to banked code
10/// that the switch would unmap.
11///
12/// This is the bank-switching analog of the standard library's `std::panic::UnwindSafe`,
13/// and like it an *auto trait*: a type is `BankSafe` unless it contains something that
14/// is not, so the property propagates through structs, tuples, and enums. A generic
15/// type parameter that reaches a switch therefore needs an explicit `BankSafe` bound,
16/// the same way crossing a thread boundary needs `Send`.
17///
18/// # What is not `BankSafe`
19///
20/// A bare `fn` pointer and a `dyn` trait object, because calling either performs no
21/// bank switch: if it targets banked code, the caller runs it with that bank unmapped.
22/// `Far` and `DynFar` are the sanctioned carriers and are exempt, because calling
23/// one switches banks first (and costs nothing when the target is bank 0).
24///
25/// `Anchor` is excluded for a different reason: it witnesses that the current code
26/// is in bank 0, which reaching a banked callee would falsify.
27///
28/// # Where it is enforced
29///
30/// Every path that crosses a switch requires it on the values that cross:
31/// `Warp` and `FarCall` on both their arguments and their output, `scope` and
32/// `FarWith::there` on what the closure returns.
33///
34/// # Examples
35///
36/// Handing a banked function pointer back to the caller is rejected:
37///
38/// ```compile_fail
39/// #[bank]
40/// pub fn pick() -> fn() {
41/// fn helper() { /* lives in this bank */ }
42/// helper // ERROR: `fn()` is not `BankSafe`
43/// }
44/// ```
45///
46/// So is passing one into another bank, where it would no longer be mapped:
47///
48/// ```compile_fail
49/// #[bank]
50/// pub fn register(tick: fn()) { // ERROR: `fn()` is not `BankSafe`
51/// tick()
52/// }
53/// ```
54///
55/// Carry the function as a `Far` instead; calling it maps its bank first:
56///
57/// ```ignore
58/// #[bank]
59/// fn pick() -> Far<fn(), Sound> {
60/// far!(sound::helper)
61/// }
62/// ```
63///
64/// A generic banked item needs the bound on any parameter that crosses:
65///
66/// ```ignore
67/// #[bank]
68/// impl<A: Copy + BankSafe> Summary for Pair<A> {
69/// fn summarize(&self) -> u8 { /* `&Pair<A>` crosses as the receiver */ }
70/// }
71/// ```
72///
73/// # Globals are not covered
74///
75/// The bound only reaches values that pass through an API. A banked function may
76/// still park a pointer to its own code in a global and leave it there after the
77/// switch, with no `unsafe` anywhere:
78///
79/// ```ignore
80/// static CB: Mutex<Cell<Option<fn()>>> = Mutex::new(Cell::new(None));
81///
82/// #[bank]
83/// pub fn install(cs: CriticalSection) {
84/// fn helper() { /* lives in this bank */ }
85/// CB.borrow(cs).set(Some(helper)); // accepted
86/// }
87///
88/// // elsewhere, once this bank is no longer mapped
89/// critical_section::with(|cs| CB.borrow(cs).get().unwrap()()); // undefined behaviour
90/// ```
91///
92/// Calling such a pointer runs whatever bytes the currently mapped bank holds at
93/// that address. Unlike `UnwindSafe`, whose violations only expose inconsistent
94/// state, a violation here can execute arbitrary code.
95///
96/// A crate that targets the Game Boy and hands out a place to keep global state
97/// should bound what goes in it on this trait. General-purpose wrappers cannot, for
98/// example `critical_section::Mutex`, so a banked code pointer still reaches a
99/// global through any of them.
100#[diagnostic::on_unimplemented(
101 message = "`{Self}` cannot be carried across a bank switch",
102 label = "cannot cross a bank switch",
103 note = "a bare `fn` pointer, a `dyn` trait object, or a value containing one cannot \
104 cross a bank switch: it would be called with its bank unmapped",
105 note = "carry banked functions as `Far` / `DynFar` (built by `far!`) instead, \
106 whose call switches banks first"
107)]
108pub unsafe auto trait BankSafe {}
109
110// A bare `fn` pointer is *not* `BankSafe`: calling it performs no bank switch, so if
111// it targets banked code the caller runs it with that bank unmapped. One impl per
112// arity and safety/ABI combination, as the standard library does for the `Fn` family.
113macro_rules! not_bank_safe_fn {
114 ($($arg:ident),*) => {
115 impl<Ret, $($arg),*> !BankSafe for fn($($arg),*) -> Ret {}
116 impl<Ret, $($arg),*> !BankSafe for unsafe fn($($arg),*) -> Ret {}
117 impl<Ret, $($arg),*> !BankSafe for extern "C" fn($($arg),*) -> Ret {}
118 impl<Ret, $($arg),*> !BankSafe for unsafe extern "C" fn($($arg),*) -> Ret {}
119 };
120}
121not_bank_safe_fn!();
122not_bank_safe_fn!(A);
123not_bank_safe_fn!(A, B);
124not_bank_safe_fn!(A, B, C);
125not_bank_safe_fn!(A, B, C, D);
126not_bank_safe_fn!(A, B, C, D, E);
127not_bank_safe_fn!(A, B, C, D, E, F);
128not_bank_safe_fn!(A, B, C, D, E, F, G);
129not_bank_safe_fn!(A, B, C, D, E, F, G, H);
130not_bank_safe_fn!(A, B, C, D, E, F, G, H, I);
131not_bank_safe_fn!(A, B, C, D, E, F, G, H, I, J);
132not_bank_safe_fn!(A, B, C, D, E, F, G, H, I, J, K);
133not_bank_safe_fn!(A, B, C, D, E, F, G, H, I, J, K, L);