gb_ram_fn/lib.rs
1//! Functions whose machine code can be copied into RAM (or HRAM) and run there.
2//!
3//! [`ram_fn`] defines a function and a handle implementing [`RamFn`]. The handle
4//! places the function between two name-sorted markers, so its exact length is
5//! known; [`RamFn::install`] copies the bytes into a fixed-size RAM buffer and
6//! returns a callable pointer.
7//!
8//! The function must be **position independent**: it may reference statics and
9//! other functions by absolute address (those do not move), but it must not
10//! branch to its own code by an absolute target. Small functions qualify (the
11//! SM83 backend uses relative `jr` for short branches); a long function whose
12//! branches become absolute `jp` does not, and copying it would run the wrong
13//! code; `cargo-gb` rejects such a function at build time (see below).
14//! Parameters and return values are fine: they travel in registers and on the
15//! stack, which copying does not affect.
16//!
17//! # Examples
18//!
19//! Define a function, run the ROM copy in place, then copy it into a RAM buffer
20//! and run that. Bring [`RamFn`] into scope for the handle's methods.
21//!
22//! ```ignore
23//! use gb_ram_fn::{RamFn, ram_fn};
24//!
25//! static mut COUNTER: u16 = 0;
26//!
27//! #[ram_fn(max = 16)]
28//! fn inc() {
29//! unsafe {
30//! let c = core::ptr::read_volatile(&raw const COUNTER);
31//! core::ptr::write_volatile(&raw mut COUNTER, c.wrapping_add(1));
32//! }
33//! }
34//!
35//! static mut BUF: [u8; 16] = [0; 16];
36//!
37//! fn demo() {
38//! inc.rom()(); // run in place, in ROM
39//! let ram_inc = unsafe { inc.install(&raw mut BUF) };
40//! ram_inc(); // run the RAM copy
41//! }
42//! ```
43//!
44//! Parameters and return values work; the installed pointer keeps the signature.
45//! The buffer must be at least `max` bytes, checked at compile time:
46//!
47//! ```ignore
48//! use gb_ram_fn::{RamFn, ram_fn};
49//!
50//! #[ram_fn(max = 8)]
51//! fn add(a: u8, b: u8) -> u8 {
52//! a.wrapping_add(b)
53//! }
54//!
55//! static mut BUF: [u8; 8] = [0; 8];
56//!
57//! fn demo() -> u8 {
58//! let added = unsafe { add.install(&raw mut BUF) };
59//! added(2, 3) // 5, computed from the RAM copy
60//! }
61//! ```
62//!
63//! # Build-time verification
64//!
65//! Two things keep [`install`](RamFn::install) safe: the function fits its
66//! declared `max` (a compile-time-sized buffer then always holds it), and it is
67//! position independent (the copy runs correctly at its new address). The
68//! compiler cannot confirm either, so `cargo-gb` checks them over the linked ROM:
69//! `END - run <= max`, and that the code holds no absolute self-references.
70//!
71//! These are therefore guarantees of a `cargo-gb` build, not of `#[ram_fn]`
72//! itself. A build path that skips those checks does not provide them: `install`
73//! may overflow its buffer, or copy code that breaks when run relocated.
74
75#![no_std]
76
77pub use gb_ram_fn_macros::ram_fn;
78
79/// Shared interface for functions defined with [`ram_fn`].
80///
81/// Each `ram_fn` produces a zero-sized handle that implements this trait; the
82/// associated [`Fn`](RamFn::Fn) type carries the function's own signature.
83pub trait RamFn {
84 /// Function-pointer type carrying the defined function's signature.
85 type Fn;
86
87 /// The declared maximum compiled size, in bytes (from `#[ram_fn(max = N)]`).
88 const MAX: usize;
89
90 /// Address of the function's machine code in ROM.
91 fn src(&self) -> *const u8;
92
93 /// Length of the machine code, in bytes.
94 fn len(&self) -> usize;
95
96 /// The ROM-resident copy as a callable pointer.
97 fn rom(&self) -> Self::Fn;
98
99 /// Copy the code into `dst` and return the RAM copy as a callable pointer.
100 ///
101 /// `dst` is a fixed-size buffer; `N >= MAX` is checked at compile time, so the
102 /// buffer is large enough for any function within its declared `max`. No
103 /// runtime length check is needed.
104 ///
105 /// # Safety
106 ///
107 /// `dst` must point to executable RAM that stays live and unchanged for as
108 /// long as the returned pointer is called.
109 ///
110 /// `install` also assumes the function fits `MAX` and is position independent.
111 /// Both are verified only by `cargo-gb` over the linked ROM (see the crate
112 /// docs); built another way, `install` may overflow `dst` or return a pointer
113 /// to code that does not run correctly when relocated.
114 unsafe fn install<const N: usize>(&self, dst: *mut [u8; N]) -> Self::Fn;
115}