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}