solana_allocator/lib.rs
1// solana-allocator — custom global allocator for Solana programs.
2// © 2024 by Composable Foundation
3// © 2025 by Michał Nazarewicz <mina86@mina86.com>
4//
5// This program is free software; you can redistribute it and/or modify it under
6// the terms of the GNU General Public License as published by the Free Software
7// Foundation; either version 2 of the License, or (at your option) any later
8// version.
9//
10// This program is distributed in the hope that it will be useful, but WITHOUT
11// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13// details.
14//
15// You should have received a copy of the GNU General Public License along with
16// this program; if not, see <https://www.gnu.org/licenses/>.
17
18// Rust doesn’t recognise ‘solana’ as a target_os unless building via cargo
19// build-sbf. Silence the warning.
20#![cfg_attr(not(target_os = "solana"), allow(unexpected_cfgs))]
21#![allow(private_bounds)]
22
23//! Custom global allocator which doesn’t assume 32 KiB heap size.
24//!
25//! Default Solana allocator assumes there’s only 32 KiB of available heap
26//! space. Since heap size can be changed per-transaction, this assumption is
27//! not always accurate. This module defines a global allocator which doesn’t
28//! assume size of available space.
29
30extern crate alloc;
31
32#[cfg(any(test, target_os = "solana"))]
33mod imp;
34#[cfg(any(test, target_os = "solana"))]
35mod ptr;
36
37#[cfg(any(test, target_os = "solana"))]
38pub use imp::BumpAllocator;
39
40
41/// On Solana, defines `BumpAllocator` as the global allocator.
42///
43/// When compiling for Solana, defines a new instance of `BumpAllocator` and
44/// declares it as a the global allocator. When compiling for other platforms,
45/// does nothing.
46///
47/// Note that, as always when defining custom global allocator on Solana, if
48/// using `solana_program::entrypoint` or `anchor::program` macros, the smart
49/// contract must define and enable `custom-heap` feature. Otherwise, global
50/// allocator defined by this macro and in `solana_program` will clash.
51///
52/// # Example
53///
54/// ```ignore
55/// #[cfg(not(feature = "cpi"))]
56/// solana_allocator::custom_heap!();
57/// ```
58#[macro_export]
59macro_rules! custom_heap {
60 () => {
61 #[cfg(target_os = "solana")]
62 #[global_allocator]
63 // SAFETY: We’re compiling for Solana and declaring this as a global
64 // allocator which can exist only one.
65 static A: $crate::BumpAllocator<()> = unsafe {
66 $crate::BumpAllocator::new();
67 };
68 };
69}
70
71
72/// On Solana, defines `BumpAllocator` as the global allocator with given
73/// global state.
74///
75/// When compiling for Solana, defines a new global allocator and a function
76/// which returns static object living in mutable memory. The name of the
77/// function and type of the global object depend on the invocation.
78///
79/// See also caveats in [`custom_heap`] macro.
80///
81/// # Existing type
82///
83/// ```ignore
84/// custom_global!($visibility fn $name() -> $Global);
85/// custom_global!($visibility type $Global);
86/// ```
87///
88/// Defines function `$name` with specified visibility which returns a reference
89/// to a `'static` value of type `$Global`. In the second invocation, the name
90/// of the function is `global`.
91///
92/// `$Global` must be a [`Sync`](`core::marker::Sync`) and
93/// [`bytemuck::Zeroable`] type. Furthermore, the `$name` function returns
94/// a shared reference to the static objects which doesn’t allow modification
95/// unless the type has internal mutability. This can be achieved by
96/// [`Cell`](`core::cell::Cell`).
97///
98/// # Global struct definition
99///
100/// ```ignore
101/// custom_global!($visibility fn $name() -> struct $Global { ... });
102/// custom_global!($visibility struct $Global { ... });
103/// ```
104///
105/// Defines a struct `$Global` and uses that as the global object. Note that
106/// all fields of the struct must be [`bytemuck::Zeroable`] *however* they do
107/// not need to be [`Sync`](`core::marker::Sync`). When building on Solana, the
108/// macro will unsafely declare `$Global` as `Sync` based on the observation
109/// that Solana is single-threaded thus passing data between threads is not
110/// a concern.
111///
112/// # Non-Solana target
113///
114/// When not building for Solana (i.e. for `not(target_os = "solana")`
115/// configuration), the macro doesn’t set the global allocator nor defines the
116/// `$name` function returning the global state. (Note that the invocation with
117/// `struct $Global` definition defines the struct regardless of the target).
118///
119/// Caller is responsible for using appropriate conditional compilation to
120/// provide all the necessary global state. One approach is to provide wrapper
121/// functions which use the global object when building on Solana and use static
122/// atomic type or locked variables when building on other platforms.
123///
124/// # Example
125///
126/// ```ignore
127/// pub(crate) mod global {
128/// #[cfg(not(feature = "cpi"))]
129/// solana_allocator::custom_global!(struct GlobalData {
130/// counter: Cell<usize>,
131/// });
132///
133/// #[cfg(all(target_os = "solana", not(feature = "cpi")))]
134/// pub fn unique() -> usize {
135/// let counter = &global().counter;
136/// let value = counter.get();
137/// counter.set(value + 1);
138/// value
139/// }
140///
141/// #[cfg(not(target_os = "solana"))]
142/// pub fn unique() -> usize {
143/// use std::sync::atomic::{AtomicUsize, Ordering};
144/// static COUNTER: AtomicUsize = AtomicUsize::new(0);
145/// COUNTER.fetch_add(1, Ordering::SeqCst)
146/// }
147/// }
148/// ```
149#[macro_export]
150macro_rules! custom_global {
151 ($visibility:vis fn $name:ident() -> $G:ty) => {
152 #[cfg(target_os = "solana")]
153 $visibility fn $name() -> &'static $G {
154 #[global_allocator]
155 // SAFETY: We’re compiling for Solana and declaring this as a global
156 // allocator which can exist only one.
157 static A: $crate::BumpAllocator<$G> = unsafe {
158 $crate::BumpAllocator::new()
159 };
160
161 A.global()
162 }
163 };
164
165 ($visibility:vis type $G:ty) => {
166 $crate::custom_global!($visibility fn global() -> $G);
167 };
168
169 ($visibility:vis fn $name:ident() -> struct $G:ident { $($tt:tt)* }) => {
170 #[derive(bytemuck::Zeroable)]
171 $visibility struct $G { $($tt)* }
172
173 // SAFETY: $G might not be Sync. However, Solana is single-threaded so
174 // we don’t need to worry about thread safety. Since this
175 // implementation is used when building for Solana, we can safely lie to
176 // the compiler about $G being Sync.
177 //
178 // We need $G to be Sync because it’s !Sync status percolates to
179 // BumpAllocator<$G> and since that’s a static variable, Rust requires
180 // that it’s Sync.
181 #[cfg(target_os = "solana")]
182 unsafe impl core::marker::Sync for $G {}
183
184 $crate::custom_global!($visibility fn $name() -> $G);
185 };
186
187 ($visibility:vis struct $G:ident { $($tt:tt)* }) => {
188 $crate::custom_global!(
189 $visibility fn global() -> struct $G { $($tt)* }
190 );
191 }
192}