playdate_allocator/
lib.rs

1#![no_std]
2#![cfg_attr(feature = "allocator-api", feature(allocator_api, slice_ptr_get))]
3#![cfg_attr(feature = "global-error-handler",
4            feature(alloc_error_handler, core_intrinsics),
5            allow(internal_features))]
6#![cfg_attr(any(test, debug_assertions, not(feature = "static-link")),
7            feature(fn_ptr_trait))]
8
9
10extern crate alloc;
11
12
13#[cfg(feature = "allocator-api")]
14pub(crate) mod local;
15pub(crate) mod global;
16
17
18/// PlaydateOs system allocator.
19///
20/// Allocator uses system `realloc` c-fn,
21/// so user have to call [`init`] if `static-link` feature is disabled.
22///
23/// Otherwise if `static-link` is on, it's statically linked and it's nesessary to call [`init`].
24pub struct System;
25
26
27type Realloc = unsafe extern "C" fn(ptr: *mut c_void, size: usize) -> *mut c_void;
28
29
30use core::ffi::c_void;
31
32/// Fn-pointer to the OS's realloc function.
33#[cfg(not(feature = "static-link"))]
34static mut REALLOC: Realloc = fake;
35
36
37/// Fake no-op realloc function, used as initial value (instead of any kinds of null) of [`REALLOC`].
38/// Using this function will cause allocation failures.
39#[cold]
40#[cfg(not(feature = "static-link"))]
41unsafe extern "C" fn fake(_: *mut c_void, _: usize) -> *mut c_void { core::ptr::null_mut() }
42
43
44unsafe extern "C" {
45	/// Statically linked OS's realloc function.
46	#[cfg(feature = "static-link")]
47	fn pdrealloc(ptr: *mut c_void, size: usize) -> *mut c_void;
48}
49
50
51#[inline(always)]
52#[cfg(debug_assertions)]
53pub fn init(realloc: Realloc) {
54	use core::marker::FnPtr;
55
56	debug_assert!(!realloc.addr().is_null());
57	init_realloc(realloc)
58}
59
60
61#[inline(always)]
62#[cfg(not(debug_assertions))]
63pub const fn init(realloc: Realloc) { init_realloc(realloc) }
64
65
66#[inline(always)]
67#[cfg_attr(feature = "static-link",
68           doc = "\n no-op because [`realloc`] is linked statically")]
69const fn init_realloc(#[cfg_attr(feature = "static-link", allow(unused_variables))] realloc: Realloc) {
70	#[cfg(not(feature = "static-link"))]
71	unsafe {
72		REALLOC = realloc
73	}
74}
75
76#[inline(always)]
77#[cfg(feature = "static-link")]
78pub const fn is_inited() -> bool { true }
79
80
81#[cfg(not(feature = "static-link"))]
82pub fn is_inited() -> bool {
83	use core::ptr::fn_addr_eq;
84	unsafe { !fn_addr_eq(REALLOC, fake as Realloc) }
85}
86
87
88#[inline(always)]
89#[cfg(debug_assertions)]
90fn get() -> Realloc {
91	let realloc = get_unchecked();
92
93	#[cfg(not(feature = "static-link"))]
94	debug_assert!(!core::marker::FnPtr::addr(realloc).is_null(), "missed realloc");
95
96	realloc
97}
98
99#[inline(always)]
100#[cfg(not(debug_assertions))]
101const fn get() -> Realloc { get_unchecked() }
102
103
104#[inline(always)]
105const fn get_unchecked() -> Realloc {
106	#[cfg(feature = "static-link")]
107	{
108		pdrealloc
109	}
110	#[cfg(not(feature = "static-link"))]
111	{
112		unsafe { REALLOC }
113	}
114}
115
116
117#[cfg(test)]
118#[cfg(not(feature = "global"))]
119mod tests {
120	#![allow(unexpected_cfgs)] // for `fake_alloc`.
121	// It could be properly registered by adding to build-script `println!("cargo::rustc-check-cfg=cfg(fake_alloc)")`,
122	// but it's only needed for tests and should not used in production,
123	// so could be great if compiler warn about it in places other than tests.
124
125	use core::ptr::null_mut;
126	use super::*;
127
128
129	#[test]
130	#[cfg_attr(feature = "static-link", ignore = "for static-mut only")]
131	fn not_inited() {
132		#[cfg(not(feature = "static-link"))]
133		unsafe {
134			REALLOC = fake
135		}
136		assert!(!is_inited());
137	}
138
139	#[test]
140	fn inited() {
141		#[cfg(not(feature = "static-link"))]
142		{
143			unsafe { REALLOC = fake }
144			assert!(!is_inited());
145		}
146
147		init_fake();
148
149		assert!(is_inited());
150
151		#[cfg(feature = "static-link")]
152		assert!(!core::marker::FnPtr::addr(pdrealloc as Realloc).is_null());
153	}
154
155
156	#[test]
157	#[cfg_attr(not(fake_alloc), ignore = "set RUSTFLAGS='--cfg=fake_alloc' to enable.")]
158	fn get_alloc_fake() {
159		init_fake();
160
161		let realloc = get();
162		let p = unsafe { realloc(null_mut(), 64) };
163
164		assert!(p.is_null());
165	}
166
167
168	pub(crate) fn init_fake() {
169		#[cfg(not(feature = "static-link"))]
170		{
171			// another fake, mem-location is different from crate::fake
172			unsafe extern "C" fn fake(_: *mut c_void, _: usize) -> *mut c_void { null_mut() }
173			unsafe { REALLOC = fake }
174		}
175	}
176
177
178	#[no_mangle]
179	#[cfg(fake_alloc)]
180	#[cfg(feature = "static-link")]
181	extern "C" fn pdrealloc(_: *mut c_void, _: usize) -> *mut c_void { null_mut() }
182}