solana_program_memory/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Basic low-level memory operations.
5//!
6//! Within the SBF environment, these are implemented as syscalls and executed by
7//! the runtime in native code.
8
9use core::mem::MaybeUninit;
10
11#[cfg(any(target_os = "solana", target_arch = "bpf"))]
12pub mod syscalls {
13    pub use solana_define_syscall::definitions::{
14        sol_memcmp_, sol_memcpy_, sol_memmove_, sol_memset_,
15    };
16}
17
18/// Check that two regions do not overlap.
19#[cfg(any(test, not(any(target_os = "solana", target_arch = "bpf"))))]
20fn is_nonoverlapping(src: usize, src_len: usize, dst: usize, dst_len: usize) -> bool {
21    // If the absolute distance between the ptrs is at least as big as the size of the other,
22    // they do not overlap.
23    if src > dst {
24        src.saturating_sub(dst) >= dst_len
25    } else {
26        dst.saturating_sub(src) >= src_len
27    }
28}
29
30#[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
31#[allow(clippy::arithmetic_side_effects)]
32pub mod stubs {
33    use super::is_nonoverlapping;
34    /// # Safety
35    pub unsafe fn sol_memcpy(dst: *mut u8, src: *const u8, n: usize) {
36        // cannot be overlapping
37        assert!(
38            is_nonoverlapping(src as usize, n, dst as usize, n),
39            "memcpy does not support overlapping regions"
40        );
41        core::ptr::copy_nonoverlapping(src, dst, n);
42    }
43    /// # Safety
44    pub unsafe fn sol_memmove(dst: *mut u8, src: *const u8, n: usize) {
45        core::ptr::copy(src, dst, n);
46    }
47    /// # Safety
48    pub unsafe fn sol_memcmp(s1: *const u8, s2: *const u8, n: usize, result: *mut i32) {
49        let mut i = 0;
50        while i < n {
51            let a = *s1.add(i);
52            let b = *s2.add(i);
53            if a != b {
54                *result = a as i32 - b as i32;
55                return;
56            }
57            i += 1;
58        }
59        *result = 0
60    }
61    /// # Safety
62    pub unsafe fn sol_memset(s: *mut u8, c: u8, n: usize) {
63        let s = core::slice::from_raw_parts_mut(s, n);
64        for val in s.iter_mut().take(n) {
65            *val = c;
66        }
67    }
68}
69
70/// Like C `memcpy`.
71///
72/// # Arguments
73///
74/// - `dst` - Destination
75/// - `src` - Source
76/// - `n` - Number of bytes to copy
77///
78/// # Errors
79///
80/// When executed within a SBF program, the memory regions spanning `n` bytes
81/// from from the start of `dst` and `src` must be mapped program memory. If not,
82/// the program will abort.
83///
84/// The memory regions spanning `n` bytes from `dst` and `src` from the start
85/// of `dst` and `src` must not overlap. If they do, then the program will abort
86/// or, if run outside of the SBF VM, will panic.
87///
88/// # Safety
89///
90/// This function does not verify that `n` is less than or equal to the
91/// lengths of the `dst` and `src` slices passed to it &mdash; it will copy
92/// bytes to and from beyond the slices.
93///
94/// Specifying an `n` greater than either the length of `dst` or `src` will
95/// likely introduce undefined behavior.
96#[inline]
97pub unsafe fn sol_memcpy(dst: &mut [u8], src: &[u8], n: usize) {
98    #[cfg(any(target_os = "solana", target_arch = "bpf"))]
99    syscalls::sol_memcpy_(dst.as_mut_ptr(), src.as_ptr(), n as u64);
100
101    #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
102    stubs::sol_memcpy(dst.as_mut_ptr(), src.as_ptr(), n);
103}
104
105/// Like C `memmove`.
106///
107/// # Arguments
108///
109/// - `dst` - Destination
110/// - `src` - Source
111/// - `n` - Number of bytes to copy
112///
113/// # Errors
114///
115/// When executed within a SBF program, the memory regions spanning `n` bytes
116/// from from `dst` and `src` must be mapped program memory. If not, the program
117/// will abort.
118///
119/// # Safety
120///
121/// The same safety rules apply as in [`ptr::copy`].
122///
123/// [`ptr::copy`]: https://doc.rust-lang.org/core/ptr/fn.copy.html
124#[inline]
125pub unsafe fn sol_memmove(dst: *mut u8, src: *const u8, n: usize) {
126    #[cfg(any(target_os = "solana", target_arch = "bpf"))]
127    syscalls::sol_memmove_(dst, src, n as u64);
128
129    #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
130    stubs::sol_memmove(dst, src, n);
131}
132
133/// Like C `memcmp`.
134///
135/// # Arguments
136///
137/// - `s1` - Slice to be compared
138/// - `s2` - Slice to be compared
139/// - `n` - Number of bytes to compare
140///
141/// # Errors
142///
143/// When executed within a SBF program, the memory regions spanning `n` bytes
144/// from from the start of `dst` and `src` must be mapped program memory. If not,
145/// the program will abort.
146///
147/// # Safety
148///
149/// It does not verify that `n` is less than or equal to the lengths of the
150/// `dst` and `src` slices passed to it &mdash; it will read bytes beyond the
151/// slices.
152///
153/// Specifying an `n` greater than either the length of `dst` or `src` will
154/// likely introduce undefined behavior.
155#[inline]
156pub unsafe fn sol_memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 {
157    let mut result: MaybeUninit<i32> = MaybeUninit::uninit();
158
159    #[cfg(any(target_os = "solana", target_arch = "bpf"))]
160    syscalls::sol_memcmp_(s1.as_ptr(), s2.as_ptr(), n as u64, result.as_mut_ptr());
161
162    #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
163    stubs::sol_memcmp(s1.as_ptr(), s2.as_ptr(), n, result.as_mut_ptr());
164
165    result.assume_init()
166}
167
168/// Like C `memset`.
169///
170/// # Arguments
171///
172/// - `s` - Slice to be set
173/// - `c` - Repeated byte to set
174/// - `n` - Number of bytes to set
175///
176/// # Errors
177///
178/// When executed within a SBF program, the memory region spanning `n` bytes
179/// from from the start of `s` must be mapped program memory. If not, the program
180/// will abort.
181///
182/// # Safety
183///
184/// This function does not verify that `n` is less than or equal to the length
185/// of the `s` slice passed to it &mdash; it will write bytes beyond the
186/// slice.
187///
188/// Specifying an `n` greater than the length of `s` will likely introduce
189/// undefined behavior.
190#[inline]
191pub unsafe fn sol_memset(s: &mut [u8], c: u8, n: usize) {
192    #[cfg(any(target_os = "solana", target_arch = "bpf"))]
193    syscalls::sol_memset_(s.as_mut_ptr(), c, n as u64);
194
195    #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
196    stubs::sol_memset(s.as_mut_ptr(), c, n);
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_is_nonoverlapping() {
205        for dst in 0..8 {
206            assert!(is_nonoverlapping(10, 3, dst, 3));
207        }
208        for dst in 8..13 {
209            assert!(!is_nonoverlapping(10, 3, dst, 3));
210        }
211        for dst in 13..20 {
212            assert!(is_nonoverlapping(10, 3, dst, 3));
213        }
214        assert!(is_nonoverlapping(usize::MAX, 3, usize::MAX - 1, 1));
215        assert!(!is_nonoverlapping(usize::MAX, 2, usize::MAX - 1, 3));
216    }
217}