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