Skip to main content

solana_syscalls/
mem_ops.rs

1use {super::*, crate::translate_mut};
2
3fn mem_op_consume(invoke_context: &mut InvokeContext, n: u64) -> Result<(), Error> {
4    let compute_cost = invoke_context.get_execution_cost();
5    let cost = compute_cost.mem_op_base_cost.max(
6        n.checked_div(compute_cost.cpi_bytes_per_unit)
7            .unwrap_or(u64::MAX),
8    );
9    invoke_context.compute_meter.consume_checked(cost)
10}
11
12/// Check that two regions do not overlap.
13pub(crate) fn is_nonoverlapping<N>(src: N, src_len: N, dst: N, dst_len: N) -> bool
14where
15    N: Ord + num_traits::SaturatingSub,
16{
17    // If the absolute distance between the ptrs is at least as big as the size of the other,
18    // they do not overlap.
19    if src > dst {
20        src.saturating_sub(&dst) >= dst_len
21    } else {
22        dst.saturating_sub(&src) >= src_len
23    }
24}
25
26declare_builtin_function!(
27    /// memcpy
28    SyscallMemcpy,
29    fn rust(
30        invoke_context: &mut InvokeContext<'_, '_>,
31        dst_addr: u64,
32        src_addr: u64,
33        n: u64,
34        _arg4: u64,
35        _arg5: u64,
36    ) -> Result<u64, Error> {
37        mem_op_consume(invoke_context, n)?;
38
39        if !is_nonoverlapping(src_addr, n, dst_addr, n) {
40            return Err(SyscallError::CopyOverlapping.into());
41        }
42
43        // host addresses can overlap so we always invoke memmove
44        memmove(invoke_context, dst_addr, src_addr, n)
45    }
46);
47
48declare_builtin_function!(
49    /// memmove
50    SyscallMemmove,
51    fn rust(
52        invoke_context: &mut InvokeContext<'_, '_>,
53        dst_addr: u64,
54        src_addr: u64,
55        n: u64,
56        _arg4: u64,
57        _arg5: u64,
58    ) -> Result<u64, Error> {
59        mem_op_consume(invoke_context, n)?;
60        memmove(invoke_context, dst_addr, src_addr, n)
61    }
62);
63
64declare_builtin_function!(
65    /// memcmp
66    SyscallMemcmp,
67    fn rust(
68        invoke_context: &mut InvokeContext<'_, '_>,
69        s1_addr: u64,
70        s2_addr: u64,
71        n: u64,
72        cmp_result_addr: u64,
73        _arg5: u64,
74    ) -> Result<u64, Error> {
75        mem_op_consume(invoke_context, n)?;
76        let check_aligned = invoke_context.get_check_aligned();
77        let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
78
79        let s1 = translate_slice::<u8>(
80            memory_mapping,
81            s1_addr,
82            n,
83            check_aligned,
84        )?;
85        let s2 = translate_slice::<u8>(
86            memory_mapping,
87            s2_addr,
88            n,
89            check_aligned,
90        )?;
91
92        debug_assert_eq!(s1.len(), n as usize);
93        debug_assert_eq!(s2.len(), n as usize);
94        // Safety:
95        // memcmp is marked unsafe since it assumes that the inputs are at least
96        // `n` bytes long. `s1` and `s2` are guaranteed to be exactly `n` bytes
97        // long because `translate_slice` would have failed otherwise.
98        let result = unsafe { memcmp(s1, s2, n as usize) };
99
100        translate_mut!(
101            memory_mapping,
102            check_aligned,
103            let cmp_result_ref_mut: (&mut std::mem::MaybeUninit<i32>) = map(cmp_result_addr)?;
104        );
105        cmp_result_ref_mut.write(result);
106
107        Ok(0)
108    }
109);
110
111declare_builtin_function!(
112    /// memset
113    SyscallMemset,
114    fn rust(
115        invoke_context: &mut InvokeContext<'_, '_>,
116        dst_addr: u64,
117        c: u64,
118        n: u64,
119        _arg4: u64,
120        _arg5: u64,
121    ) -> Result<u64, Error> {
122        mem_op_consume(invoke_context, n)?;
123
124        let check_aligned = invoke_context.get_check_aligned();
125        let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
126        translate_mut!(
127            memory_mapping,
128            check_aligned,
129            let s: (&mut [MaybeUninit<u8>]) = map(dst_addr, n)?;
130        );
131        s.fill(MaybeUninit::new(c as u8));
132        Ok(0)
133    }
134);
135
136fn memmove(
137    invoke_context: &mut InvokeContext,
138    dst_addr: u64,
139    src_addr: u64,
140    n: u64,
141) -> Result<u64, Error> {
142    let check_aligned = invoke_context.get_check_aligned();
143    let memory_mapping = invoke_context.memory_contexts.memory_mapping_mut()?;
144    // In a rare exception to the rule we manually translate addresses to a raw pointer rather than
145    // using `translate_mut!` because the src and dst memory regions may overlap for this syscall.
146    touch_slice_mut::<MaybeUninit<u8>>(memory_mapping, dst_addr, n)?;
147    let slice = translate_slice_inner!(
148        memory_mapping,
149        AccessType::Store,
150        dst_addr,
151        n,
152        MaybeUninit<u8>,
153        check_aligned,
154    )?;
155    let src_ptr = translate_slice::<u8>(memory_mapping, src_addr, n, check_aligned)?.as_ptr();
156    unsafe { std::ptr::copy(src_ptr.cast(), slice as *mut MaybeUninit<u8>, n as usize) };
157    Ok(0)
158}
159
160// Marked unsafe since it assumes that the slices are at least `n` bytes long.
161unsafe fn memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 {
162    let (s1pre, s1mid, s1end) = unsafe {
163        // SAFETY: Caller is required to guarantee both slices are at least n-long.
164        s1.get_unchecked(..n).align_to::<u128>()
165    };
166    let mut s2ptr = s2.as_ptr();
167    for s1pre_byte in s1pre.iter().copied() {
168        unsafe {
169            // SAFETY: we are guaranteed to stay in bounds of a slice `s2` by virtue of both slices
170            // containing at least `n` bytes (caller precondition.)
171            let s2pre_byte = *s2ptr;
172            if s1pre_byte != s2pre_byte {
173                return i32::from(s1pre_byte).wrapping_sub(s2pre_byte.into());
174            }
175            s2ptr = s2ptr.add(1);
176        }
177    }
178    for s1mid_value in s1mid.iter().copied() {
179        let s2mid_value = unsafe {
180            // SAFETY: Caller is required to guarantee both slices are at least n-long.
181            // SAFETY: Pointer is guaranteed to be dereferenceable by virtue of being derived from
182            // `s2` slice.
183            s2ptr.cast::<u128>().read_unaligned().to_le()
184        };
185        if s1mid_value != s2mid_value {
186            // It would seem that we could work with u128s directly here and leave it to LLVM to
187            // figure out how to split up the operations to u64s, but it seems to produce notably
188            // worse code than splitting the u64s out manually (even _when_ these splits result in
189            // the values being re-read from "memory").
190            let (s1_word, s2_word) = if s1mid_value as u64 != s2mid_value as u64 {
191                let w1 = s1mid_value as u64;
192                let w2 = s2mid_value as u64;
193                (w1, w2)
194            } else {
195                let w1 = (s1mid_value >> 64) as u64;
196                let w2 = (s2mid_value >> 64) as u64;
197                (w1, w2)
198            };
199            let shift = (s1_word ^ s2_word).trailing_zeros() & !7;
200            let b1 = (s1_word >> shift) as u8;
201            let b2 = (s2_word >> shift) as u8;
202            return i32::from(b1).wrapping_sub(b2.into());
203        }
204        unsafe {
205            // SAFETY: we are guaranteed to stay in bounds of a slice `s2` by virtue of both slices
206            // containing at least `n` bytes (caller precondition.)
207            s2ptr = s2ptr.add(std::mem::size_of::<u128>());
208        }
209    }
210    for s1end_byte in s1end.iter().copied() {
211        unsafe {
212            // This is the same as the `pre` slice loop above.
213            let s2end_byte = *s2ptr;
214            if s1end_byte != s2end_byte {
215                return i32::from(s1end_byte).wrapping_sub(s2end_byte.into());
216            }
217            s2ptr = s2ptr.add(1);
218        }
219    }
220    0
221}
222
223#[cfg(test)]
224#[allow(clippy::indexing_slicing)]
225#[allow(clippy::arithmetic_side_effects)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_is_nonoverlapping() {
231        for dst in 0..8 {
232            assert!(is_nonoverlapping(10, 3, dst, 3));
233        }
234        for dst in 8..13 {
235            assert!(!is_nonoverlapping(10, 3, dst, 3));
236        }
237        for dst in 13..20 {
238            assert!(is_nonoverlapping(10, 3, dst, 3));
239        }
240        assert!(is_nonoverlapping::<u8>(255, 3, 254, 1));
241        assert!(!is_nonoverlapping::<u8>(255, 2, 254, 3));
242    }
243}