gb_bank/model/far.rs
1//! Far pointers and the operations on them.
2//!
3//! A [`Far<T, G>`] is a pointer into a *statically known* bank (group `G`); an
4//! [`DynFar<T>`] is the same thing with the group erased to a runtime number, for
5//! heterogeneous dynamic dispatch. The [`FarCall`] and [`FarWith`] traits unify
6//! the two so that calling a function or borrowing data reads the same whichever
7//! you hold.
8
9use core::marker::{PhantomData, Tuple};
10
11use super::{
12 scope, switch_bank, switch_run, switch_run_far, Anchor, Bank, BankNumber, BankSafe, Group,
13 GroupZero,
14};
15
16// ===== Far: static-bank far pointer =====
17
18/// A pointer to a `T` living in group `G`'s bank.
19///
20/// `Far` is itself a plain value (just an address plus the group brand), so
21/// it is [`Copy`] and may be stored and passed around freely. What it points at,
22/// however, is only addressable when `G`'s bank is mapped, so it cannot be
23/// dereferenced without a [`Bank<G>`] token. `T` may be data (e.g. `[u8; N]`) or
24/// a function pointer (`fn(..) -> R`).
25///
26/// Use [`local`](Far::local) / [`there`](FarWith::there) for data and
27/// [`invoke`](FarCall::invoke) for functions, or [`erase`](Far::erase) to drop the
28/// static group for dynamic dispatch.
29///
30/// # Examples
31///
32/// ```ignore
33/// // Generated by `#[bank] static TILES: [u8; 256] = ..;`
34/// // const TILES: Far<[u8; 256], Sprites> = ..;
35///
36/// fn first_tile(anchor: Anchor, bank: &mut Bank<GroupZero>, tiles: &Far<[u8; 256], Sprites>) -> u8 {
37/// tiles.there(anchor, bank, |t| t[0]) // switch into Sprites, read, switch back
38/// }
39/// ```
40pub struct Far<T: ?Sized, G: Group> {
41 pub(crate) ptr: *const T,
42 _g: PhantomData<G>,
43}
44
45unsafe impl<T: ?Sized, G: Group> Sync for Far<T, G> {}
46
47impl<T: ?Sized, G: Group> Far<T, G> {
48 /// Construct a far pointer. Normally emitted by the `#[bank]` macro.
49 ///
50 /// # Safety
51 ///
52 /// For data, `ptr` must be the address of the `T` in group `G`'s bank window.
53 /// For a function `T = fn(..)`, `ptr` is the function's own address (e.g.
54 /// `real_fn as *const _`), which [`invoke`](FarCall::invoke) reinterprets as the
55 /// fn pointer.
56 #[doc(hidden)]
57 #[inline(always)]
58 pub const unsafe fn new(ptr: *const T) -> Self {
59 Far {
60 ptr,
61 _g: PhantomData,
62 }
63 }
64
65 /// Erase the static group into a runtime bank number, yielding an [`DynFar`].
66 ///
67 /// Use this to store far pointers of *different* groups together (a dispatch
68 /// table) or to pass one where the group is not known at compile time.
69 ///
70 /// # Examples
71 ///
72 /// ```ignore
73 /// let table: [DynFar<fn(u8)>; 2] = [a.erase(), b.erase()];
74 /// ```
75 #[inline]
76 pub fn erase(self) -> DynFar<T> {
77 unsafe { DynFar::new(self.ptr, G::bank()) }
78 }
79
80 /// Read the data in place, given a token already in its bank: no switch.
81 ///
82 /// The same-bank read. `here` proves `G` is mapped, so the data is addressable;
83 /// the reference borrows `here`, and a switch needs the token `&mut`, so it
84 /// cannot be held across a switch away from the bank. (The cross-bank borrow is
85 /// [`there`](FarWith::there).) A token of the wrong group is a type error:
86 ///
87 /// ```compile_fail
88 /// # use gb_bank::*;
89 /// # struct G1; struct G2;
90 /// # impl Group for G1 {
91 /// # const FIXED: bool = false;
92 /// # fn bank() -> BankNumber { BankNumber::new(1) }
93 /// # }
94 /// # impl Group for G2 {
95 /// # const FIXED: bool = false;
96 /// # fn bank() -> BankNumber { BankNumber::new(2) }
97 /// # }
98 /// fn wrong(b: &Bank<G1>, far: &Far<u8, G2>) -> u8 {
99 /// *far.local(b) // ERROR: a G2 far needs a G2 token, not G1
100 /// }
101 /// ```
102 #[inline(always)]
103 pub fn local<'a>(&self, _here: &'a Bank<G>) -> &'a T {
104 unsafe { &*self.ptr }
105 }
106}
107
108impl<T: ?Sized> Far<T, GroupZero> {
109 /// A far pointer to bank 0 data, built *safely*.
110 ///
111 /// Safe because [`GroupZero`] is always mapped: the pointer is always valid,
112 /// so there is no bank-mapping precondition to uphold. Use it to hand bank-0
113 /// data to code written in terms of [`Far`] / [`DynFar`], e.g. a dispatch table
114 /// mixing data from several banks.
115 ///
116 /// `data` must genuinely live in bank 0: an ordinary
117 /// `static` or `const`, not a `#[bank]` static. Plain statics always qualify
118 /// (the linker keeps them in bank 0); only data deliberately placed in a
119 /// switchable bank would not, and the safe API gives no `&'static` reference to
120 /// such data anyway.
121 ///
122 /// # Examples
123 ///
124 /// ```ignore
125 /// static PALETTE: [u8; 4] = [0, 1, 2, 3];
126 /// let far: Far<[u8; 4], GroupZero> = Far::resident(&PALETTE);
127 /// ```
128 #[inline(always)]
129 pub const fn resident(data: &'static T) -> Self {
130 Far { ptr: data, _g: PhantomData }
131 }
132}
133
134impl<T: ?Sized, G: Group> Copy for Far<T, G> {}
135impl<T: ?Sized, G: Group> Clone for Far<T, G> {
136 #[inline(always)]
137 fn clone(&self) -> Self {
138 *self
139 }
140}
141
142// ===== DynFar: runtime-bank (erased) far pointer =====
143
144/// A far pointer whose bank is known only at runtime: a group-erased [`Far`].
145///
146/// `DynFar` is the no-alloc, vtable-free type erasure of [`Far`]: the group type
147/// becomes a [`BankNumber`] stored inline, so far pointers of *different* banks
148/// share one type and can live in the same collection. Every access switches to the
149/// stored bank (there is no compile-time group to elide it against).
150///
151/// Obtain one with [`Far::erase`].
152///
153/// # Examples
154///
155/// ```ignore
156/// fn run_each(bank: &mut Bank<GroupZero>, behaviours: &[DynFar<fn(u8)>], state: u8) {
157/// for b in behaviours {
158/// b.invoke(bank, (state,)); // switch to each one's bank, run, restore
159/// }
160/// }
161/// ```
162pub struct DynFar<T: ?Sized> {
163 ptr: *const T,
164 bank: BankNumber,
165}
166
167unsafe impl<T: ?Sized> Sync for DynFar<T> {}
168
169impl<T: ?Sized> DynFar<T> {
170 /// Construct an erased far pointer from a raw address and bank number.
171 ///
172 /// # Safety
173 ///
174 /// `ptr` must be valid when `bank` is mapped. Prefer [`Far::erase`], which
175 /// fills in the bank from the group.
176 #[doc(hidden)]
177 #[inline(always)]
178 pub const unsafe fn new(ptr: *const T, bank: BankNumber) -> Self {
179 DynFar { ptr, bank }
180 }
181}
182
183impl<T: ?Sized> Copy for DynFar<T> {}
184impl<T: ?Sized> Clone for DynFar<T> {
185 #[inline(always)]
186 fn clone(&self) -> Self {
187 *self
188 }
189}
190
191/// A far pointer that can be *called* across a bank boundary.
192///
193/// Implemented by [`Far`] and [`DynFar`] over function pointers, this is the
194/// unified call interface: [`invoke`](FarCall::invoke) switches into the target
195/// bank, invokes the function with `args`, and restores the caller's bank `C`.
196/// The `#[bank]` macro rewrites `recv.invoke(args)` onto it.
197pub trait FarCall<Args: Tuple + BankSafe> {
198 /// The function's return type.
199 type Output: BankSafe;
200
201 /// Switch into the far function's bank, call it with `args`, restore `C`.
202 fn invoke<C: Group>(&self, outer: &mut Bank<C>, args: Args) -> Self::Output;
203}
204
205impl<F: Copy + Fn<Args>, G: Group, Args: Tuple + BankSafe> FarCall<Args> for Far<F, G>
206where
207 F::Output: BankSafe,
208{
209 type Output = F::Output;
210 // A resident caller inlines the switch; a banked one, whose code leaves the
211 // window during the call, takes the bank-0 trampoline instead.
212 #[inline]
213 fn invoke<C: Group>(&self, outer: &mut Bank<C>, args: Args) -> F::Output {
214 let run = |_b: &mut Bank<G>| {
215 // `ptr` is the function's address; reinterpret it as the fn pointer.
216 let f: F = unsafe { core::mem::transmute_copy(&self.ptr) };
217 f.call(args)
218 };
219 if C::FIXED {
220 switch_run(outer, run)
221 } else {
222 switch_run_far(outer, run)
223 }
224 }
225}
226
227impl<F: Copy + Fn<Args>, Args: Tuple + BankSafe> FarCall<Args> for DynFar<F>
228where
229 F::Output: BankSafe,
230{
231 type Output = F::Output;
232 // See `Far`'s impl above. The target bank is a runtime value, so this cannot go
233 // through `switch_run` and carries its own trampoline.
234 #[inline]
235 fn invoke<C: Group>(&self, _outer: &mut Bank<C>, args: Args) -> F::Output {
236 let run = || {
237 let f: F = unsafe { core::mem::transmute_copy(&self.ptr) };
238 f.call(args)
239 };
240 if C::FIXED {
241 unsafe { switch_bank(self.bank) };
242 run()
243 } else {
244 dyn_invoke_far::<C, _>(self.bank, run)
245 }
246 }
247}
248
249/// [`DynFar::invoke`](FarCall::invoke) for a banked caller. `C` is never
250/// [`FIXED`](Group::FIXED) here, so the restore is unconditional.
251#[inline(never)]
252fn dyn_invoke_far<C: Group, R>(bank: BankNumber, f: impl FnOnce() -> R) -> R {
253 unsafe { switch_bank(bank) };
254 let r = f();
255 unsafe { switch_bank(C::bank()) };
256 r
257}
258
259/// Borrowing a far pointer's *data* across a bank boundary.
260///
261/// Switches into the data's bank, lends `&T` to `f`, then restores the caller `C`:
262/// the cross-bank counterpart of [`Far::local`]. Implemented by [`Far`] (switch via
263/// [`scope`], then [`local`](Far::local)) and [`DynFar`] (switch to its runtime bank).
264pub trait FarWith<T: ?Sized> {
265 /// Switch into the data's bank, lend `&T` to `f`, restore `C`.
266 ///
267 /// `anchor` proves the caller is in bank 0: `f` runs across a bank switch, so its
268 /// code must stay mapped. A `#[bank]` function holds no [`Anchor`] and so cannot
269 /// call this (use [`local`](Far::local) for same-bank data instead).
270 fn there<C: Group, R: BankSafe>(&self, anchor: Anchor, outer: &mut Bank<C>, f: impl FnOnce(&T) -> R) -> R;
271}
272
273impl<T: ?Sized, G: Group> FarWith<T> for Far<T, G> {
274 #[inline]
275 fn there<C: Group, R: BankSafe>(&self, anchor: Anchor, outer: &mut Bank<C>, f: impl FnOnce(&T) -> R) -> R {
276 scope(anchor, outer, |b: &mut Bank<G>| f(self.local(b)))
277 }
278}
279
280impl<T: ?Sized> FarWith<T> for DynFar<T> {
281 #[inline]
282 fn there<C: Group, R: BankSafe>(&self, _anchor: Anchor, _outer: &mut Bank<C>, f: impl FnOnce(&T) -> R) -> R {
283 unsafe { switch_bank(self.bank) };
284 let r = f(unsafe { &*self.ptr });
285 if !C::FIXED {
286 unsafe { switch_bank(C::bank()) };
287 }
288 r
289 }
290}