Skip to main content

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}