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 and offers
24//! mutable global state support.
25//!
26#![doc = include_str!("../README.md")]
27
28extern crate alloc;
29
30#[cfg(any(test, target_os = "solana"))]
31mod imp;
32
33#[cfg(any(test, target_os = "solana"))]
34pub use imp::BumpAllocator;
35
36
37/// On Solana, defines `BumpAllocator` as the global allocator.
38///
39/// When compiling for Solana, defines a new instance of `BumpAllocator` and
40/// declares it as a the global allocator.  When compiling for other platforms,
41/// does nothing.
42///
43/// Note that, as always when defining custom global allocator on Solana, if
44/// using `solana_program::entrypoint` or `anchor::program` macros, the smart
45/// contract must define and enable `custom-heap` feature.  Otherwise, global
46/// allocator defined by this macro and in `solana_program` will clash.
47///
48/// # Example
49///
50/// ```ignore
51/// #[cfg(not(feature = "cpi"))]
52/// solana_allocator::custom_heap!();
53/// ```
54#[macro_export]
55macro_rules! custom_heap {
56    () => {
57        #[cfg(target_os = "solana")]
58        #[global_allocator]
59        // SAFETY: We’re compiling for Solana and declaring this as a global
60        // allocator which can exist only one.
61        static A: $crate::BumpAllocator<()> =
62            unsafe { $crate::BumpAllocator::new() };
63    };
64}
65
66
67/// On Solana, defines `BumpAllocator` as the global allocator with given
68/// global state.
69///
70/// When compiling for Solana, defines a new global allocator and a function
71/// which returns static object living in mutable memory.  The name of the
72/// function and type of the global object depend on the invocation.
73///
74/// See also caveats in [`custom_heap`] macro.
75///
76/// # Existing type
77///
78/// ```ignore
79/// custom_global!($visibility fn $name() -> $Global);
80/// custom_global!($visibility type $Global);
81/// ```
82///
83/// Defines function `$name` with specified visibility which returns a reference
84/// to a `'static` reference to object of type `$Global`.  In the second
85/// invocation, the name of the function is `global`.
86///
87/// `$Global` must be a [`Sync`](`core::marker::Sync`) and
88/// [`bytemuck::Zeroable`] type.  Furthermore, the `$name` function returns
89/// a shared reference to the static objects which doesn’t allow modification
90/// unless the type has internal mutability.  This can be achieved by
91/// [`Cell`](`core::cell::Cell`).
92///
93/// # Global struct definition
94///
95/// ```ignore
96/// custom_global!($visibility fn $name() -> struct $Global { ... });
97/// custom_global!($visibility struct $Global { ... });
98/// ```
99///
100/// Defines a struct `$Global` and uses that as the global object.  Note that
101/// all fields of the struct must be [`bytemuck::Zeroable`] *however* they do
102/// not need to be [`Sync`](`core::marker::Sync`).  When building on Solana, the
103/// macro will unsafely declare `$Global` as `Sync` based on the observation
104/// that Solana is single-threaded thus passing data between threads is not
105/// a concern.
106///
107/// Due to Rust technical limitations, to use this form the crate must depend on
108/// `bytemuck` crate.  If it doesn’t, compilation will fail with ‘could not find
109/// `bytemuck` in the list of imported crates’ error.  This can be done by
110/// adding the following to `Cargo.toml`:
111///
112/// ```toml
113/// [dependencies.bytemuck]
114/// version = "*"
115/// ```
116///
117/// # Non-Solana target
118///
119/// When not building for Solana (i.e. for `not(target_os = "solana")`
120/// configuration), the macro doesn’t set the global allocator nor defines the
121/// `$name` function returning the global state.  (Note that the invocation with
122/// `struct $Global` definition defines the struct regardless of the target).
123///
124/// Caller is responsible for using appropriate conditional compilation to
125/// provide all the necessary global state.  One approach is to provide wrapper
126/// functions which use the global object when building on Solana and use static
127/// atomic type or locked variables when building on other platforms.
128///
129/// # Example
130///
131/// ```ignore
132/// pub(crate) mod global {
133///     #[cfg(not(feature = "cpi"))]
134///     solana_allocator::custom_global!(struct GlobalData {
135///         counter: Cell<usize>,
136///     });
137///
138///     #[cfg(all(target_os = "solana", not(feature = "cpi")))]
139///     pub fn unique() -> usize {
140///         let counter = &global().counter;
141///         let value = counter.get();
142///         counter.set(value + 1);
143///         value
144///     }
145///
146///     #[cfg(not(target_os = "solana"))]
147///     pub fn unique() -> usize {
148///         use std::sync::atomic::{AtomicUsize, Ordering};
149///         static COUNTER: AtomicUsize = AtomicUsize::new(0);
150///         COUNTER.fetch_add(1, Ordering::SeqCst)
151///     }
152/// }
153/// ```
154#[macro_export]
155macro_rules! custom_global {
156    ($visibility:vis fn $name:ident() -> $G:ty) => {
157        $crate::__private_custom_global!($visibility fn $name() -> $G);
158    };
159
160    ($visibility:vis type $G:ty) => {
161        $crate::custom_global!($visibility fn global() -> $G);
162    };
163
164    ($visibility:vis fn $name:ident() -> struct $G:ident { $($tt:tt)* }) => {
165        #[derive(bytemuck::Zeroable)]
166        $visibility struct $G { $($tt)* }
167
168        // SAFETY: $G might not be Sync.  However, Solana is single-threaded so
169        // we don’t need to worry about thread safety.  Since this
170        // implementation is used when building for Solana, we can safely lie to
171        // the compiler about $G being Sync.
172        //
173        // We need $G to be Sync because it’s !Sync status percolates to
174        // BumpAllocator<$G> and since that’s a static variable, Rust requires
175        // that it’s Sync.
176        #[cfg(target_os = "solana")]
177        unsafe impl core::marker::Sync for $G {}
178
179        $crate::custom_global!($visibility fn $name() -> $G);
180    };
181
182    ($visibility:vis struct $G:ident { $($tt:tt)* }) => {
183        $crate::custom_global!(
184            $visibility fn global() -> struct $G { $($tt)* }
185        );
186    }
187}
188
189// Normally, hide the `A` symbol inside of the function so it’s not visible.
190#[cfg(not(feature = "__internal"))]
191#[macro_export]
192#[doc(hidden)]
193macro_rules! __private_custom_global {
194    ($visibility:vis fn $name:ident() -> $G:ty) => {
195        #[cfg(target_os = "solana")]
196        $visibility fn $name() -> &'static $G {
197            #[global_allocator]
198            // SAFETY: We’re compiling for Solana and declaring this as a global
199            // allocator which can exist only one.
200            static A: $crate::BumpAllocator<$G> = unsafe {
201                $crate::BumpAllocator::new()
202            };
203            A.global()
204        }
205    }
206}
207
208// When `__internal` feature is enabled, declare `A` symbol outside of the
209// function so that it’s visible to the rest of the code.
210#[cfg(all(target_os = "solana", feature = "__internal"))]
211#[macro_export]
212#[doc(hidden)]
213macro_rules! __private_custom_global {
214    ($visibility:vis fn $name:ident() -> $G:ty) => {
215        #[global_allocator]
216        // SAFETY: We’re compiling for Solana and declaring this as a global
217        // allocator which can exist only one.
218        static A: $crate::BumpAllocator<$G> = unsafe {
219            $crate::BumpAllocator::new()
220        };
221
222        $visibility fn $name() -> &'static $G { A.global() }
223    }
224}