Skip to main content

hopper_runtime/
memory.rs

1//! Hopper-owned memory helpers backed by Solana memory syscalls.
2//!
3//! These helpers keep raw SVM memory operations behind Hopper Runtime, while
4//! the safe wrappers operate on Rust slices for normal program code.
5
6use crate::{ProgramError, ProgramResult};
7
8/// Copy bytes from `src` to `dst`. The regions must not overlap.
9///
10/// # Safety
11///
12/// `src` and `dst` must be valid for `len` bytes and must not overlap.
13#[inline(always)]
14pub unsafe fn copy_nonoverlapping(dst: *mut u8, src: *const u8, len: usize) {
15    // SAFETY: Caller upholds the non-overlapping raw-memory contract.
16    unsafe {
17        crate::syscalls::sol_memcpy_(dst, src, len as u64);
18    }
19}
20
21/// Copy bytes from `src` to `dst`, allowing overlap.
22///
23/// # Safety
24///
25/// `src` and `dst` must be valid for `len` bytes.
26#[inline(always)]
27pub unsafe fn copy(dst: *mut u8, src: *const u8, len: usize) {
28    // SAFETY: Caller upholds the raw-memory contract; memmove allows overlap.
29    unsafe {
30        crate::syscalls::sol_memmove_(dst, src, len as u64);
31    }
32}
33
34/// Fill a raw memory range with one byte.
35///
36/// # Safety
37///
38/// `dst` must be valid for `len` writable bytes.
39#[inline(always)]
40pub unsafe fn fill(dst: *mut u8, byte: u8, len: usize) {
41    // SAFETY: Caller guarantees the destination range is writable.
42    unsafe {
43        crate::syscalls::sol_memset_(dst, byte, len as u64);
44    }
45}
46
47/// Lexicographically compare two raw memory ranges.
48///
49/// # Safety
50///
51/// `left` and `right` must be valid for `len` bytes.
52#[inline(always)]
53pub unsafe fn compare(left: *const u8, right: *const u8, len: usize) -> core::cmp::Ordering {
54    let mut result = 0i32;
55    // SAFETY: Caller guarantees both ranges are readable and result is local.
56    unsafe {
57        crate::syscalls::sol_memcmp_(left, right, len as u64, &mut result as *mut i32);
58    }
59    match result {
60        0 => core::cmp::Ordering::Equal,
61        value if value < 0 => core::cmp::Ordering::Less,
62        _ => core::cmp::Ordering::Greater,
63    }
64}
65
66/// Copy `src` into the beginning of `dst`.
67#[inline]
68pub fn copy_bytes(dst: &mut [u8], src: &[u8]) -> ProgramResult {
69    if dst.len() < src.len() {
70        return Err(ProgramError::InvalidArgument);
71    }
72    if src.is_empty() {
73        return Ok(());
74    }
75    // SAFETY: Slices are valid and distinct borrows, so they do not overlap.
76    unsafe {
77        copy_nonoverlapping(dst.as_mut_ptr(), src.as_ptr(), src.len());
78    }
79    Ok(())
80}
81
82/// Move a byte range inside one buffer, allowing overlap.
83#[inline]
84pub fn move_within(
85    buffer: &mut [u8],
86    src_start: usize,
87    len: usize,
88    dst_start: usize,
89) -> ProgramResult {
90    let src_end = src_start
91        .checked_add(len)
92        .ok_or(ProgramError::InvalidArgument)?;
93    let dst_end = dst_start
94        .checked_add(len)
95        .ok_or(ProgramError::InvalidArgument)?;
96    if src_end > buffer.len() || dst_end > buffer.len() {
97        return Err(ProgramError::InvalidArgument);
98    }
99    if len == 0 || src_start == dst_start {
100        return Ok(());
101    }
102    // SAFETY: Bounds are checked above; memmove supports overlap.
103    unsafe {
104        copy(
105            buffer.as_mut_ptr().add(dst_start),
106            buffer.as_ptr().add(src_start),
107            len,
108        );
109    }
110    Ok(())
111}
112
113/// Fill a byte slice with `byte`.
114#[inline]
115pub fn fill_bytes(buffer: &mut [u8], byte: u8) {
116    if buffer.is_empty() {
117        return;
118    }
119    // SAFETY: The mutable slice is valid for its full length.
120    unsafe {
121        fill(buffer.as_mut_ptr(), byte, buffer.len());
122    }
123}
124
125/// Zero-fill a byte slice.
126#[inline(always)]
127pub fn zero_bytes(buffer: &mut [u8]) {
128    fill_bytes(buffer, 0);
129}
130
131/// Compare two slices through Hopper's memory boundary.
132#[inline]
133pub fn compare_bytes(left: &[u8], right: &[u8]) -> core::cmp::Ordering {
134    let prefix_len = core::cmp::min(left.len(), right.len());
135    if prefix_len != 0 {
136        // SAFETY: Slices are readable for `prefix_len` bytes.
137        let prefix_order = unsafe { compare(left.as_ptr(), right.as_ptr(), prefix_len) };
138        if prefix_order != core::cmp::Ordering::Equal {
139            return prefix_order;
140        }
141    }
142    left.len().cmp(&right.len())
143}
144
145/// Equality helper for byte slices.
146#[inline]
147pub fn bytes_eq(left: &[u8], right: &[u8]) -> bool {
148    left.len() == right.len() && compare_bytes(left, right) == core::cmp::Ordering::Equal
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn copy_bytes_copies_prefix() {
157        let mut dst = [0u8; 5];
158        copy_bytes(&mut dst, &[1, 2, 3]).unwrap();
159        assert_eq!(dst, [1, 2, 3, 0, 0]);
160    }
161
162    #[test]
163    fn move_within_allows_overlap() {
164        let mut data = [1u8, 2, 3, 4, 5];
165        move_within(&mut data, 0, 4, 1).unwrap();
166        assert_eq!(data, [1, 1, 2, 3, 4]);
167    }
168
169    #[test]
170    fn fill_and_compare_bytes() {
171        let mut data = [9u8; 4];
172        zero_bytes(&mut data);
173        assert_eq!(data, [0u8; 4]);
174        assert!(bytes_eq(&data, &[0, 0, 0, 0]));
175        assert_eq!(compare_bytes(&[1, 2], &[1, 3]), core::cmp::Ordering::Less);
176    }
177}