gear_backend_common/
memory.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2023 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Work with WASM program memory in backends.
20
21use crate::{
22    runtime::RunFallibleError, BackendExternalities, BackendSyscallError, TrapExplanation,
23    UndefinedTerminationReason, UnrecoverableMemoryError,
24};
25use alloc::vec::Vec;
26use core::{
27    fmt::Debug,
28    marker::PhantomData,
29    mem,
30    mem::{size_of, MaybeUninit},
31    result::Result,
32    slice,
33};
34use gear_core::{
35    buffer::{RuntimeBuffer, RuntimeBufferSizeError},
36    memory::{Memory, MemoryError, MemoryInterval},
37};
38use gear_core_errors::MemoryError as FallibleMemoryError;
39use num_enum::{IntoPrimitive, TryFromPrimitive};
40use scale_info::scale::{Decode, DecodeAll, MaxEncodedLen};
41
42/// Memory access error during sys-call that lazy-pages have caught.
43/// 0 index is reserved for an ok result.
44#[derive(Debug, Clone, IntoPrimitive, TryFromPrimitive)]
45#[repr(u8)]
46pub enum ProcessAccessError {
47    OutOfBounds = 1,
48    GasLimitExceeded = 2,
49}
50
51#[derive(Debug, Clone, derive_more::From)]
52pub enum MemoryAccessError {
53    Memory(MemoryError),
54    ProcessAccess(ProcessAccessError),
55    RuntimeBuffer(RuntimeBufferSizeError),
56    // TODO: remove #2164
57    Decode,
58}
59
60impl BackendSyscallError for MemoryAccessError {
61    fn into_termination_reason(self) -> UndefinedTerminationReason {
62        match self {
63            MemoryAccessError::ProcessAccess(ProcessAccessError::OutOfBounds)
64            | MemoryAccessError::Memory(MemoryError::AccessOutOfBounds) => {
65                TrapExplanation::UnrecoverableExt(
66                    UnrecoverableMemoryError::AccessOutOfBounds.into(),
67                )
68                .into()
69            }
70            MemoryAccessError::RuntimeBuffer(RuntimeBufferSizeError) => {
71                TrapExplanation::UnrecoverableExt(
72                    UnrecoverableMemoryError::RuntimeAllocOutOfBounds.into(),
73                )
74                .into()
75            }
76            // TODO: In facts thats legacy from lazy pages V1 implementation,
77            // previously it was able to figure out that gas ended up in
78            // pre-process charges: now we need actual counter type, so
79            // it will be parsed and handled further (issue #3018).
80            MemoryAccessError::ProcessAccess(ProcessAccessError::GasLimitExceeded) => {
81                UndefinedTerminationReason::ProcessAccessErrorResourcesExceed
82            }
83            MemoryAccessError::Decode => unreachable!(),
84        }
85    }
86
87    fn into_run_fallible_error(self) -> RunFallibleError {
88        match self {
89            MemoryAccessError::Memory(MemoryError::AccessOutOfBounds)
90            | MemoryAccessError::ProcessAccess(ProcessAccessError::OutOfBounds) => {
91                RunFallibleError::FallibleExt(FallibleMemoryError::AccessOutOfBounds.into())
92            }
93            MemoryAccessError::RuntimeBuffer(RuntimeBufferSizeError) => {
94                RunFallibleError::FallibleExt(FallibleMemoryError::RuntimeAllocOutOfBounds.into())
95            }
96            e => RunFallibleError::UndefinedTerminationReason(e.into_termination_reason()),
97        }
98    }
99}
100
101/// Memory accesses recorder/registrar, which allow to register new accesses.
102pub trait MemoryAccessRecorder {
103    /// Register new read access.
104    fn register_read(&mut self, ptr: u32, size: u32) -> WasmMemoryRead;
105
106    /// Register new read static size type access.
107    fn register_read_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryReadAs<T>;
108
109    /// Register new read decoded type access.
110    fn register_read_decoded<T: Decode + MaxEncodedLen>(
111        &mut self,
112        ptr: u32,
113    ) -> WasmMemoryReadDecoded<T>;
114
115    /// Register new write access.
116    fn register_write(&mut self, ptr: u32, size: u32) -> WasmMemoryWrite;
117
118    /// Register new write static size access.
119    fn register_write_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryWriteAs<T>;
120}
121
122pub trait MemoryOwner {
123    /// Read from owned memory to new byte vector.
124    fn read(&mut self, read: WasmMemoryRead) -> Result<Vec<u8>, MemoryAccessError>;
125
126    /// Read from owned memory to new object `T`.
127    fn read_as<T: Sized>(&mut self, read: WasmMemoryReadAs<T>) -> Result<T, MemoryAccessError>;
128
129    /// Read from owned memory and decoded data into object `T`.
130    fn read_decoded<T: Decode + MaxEncodedLen>(
131        &mut self,
132        read: WasmMemoryReadDecoded<T>,
133    ) -> Result<T, MemoryAccessError>;
134
135    /// Write data from `buff` to owned memory.
136    fn write(&mut self, write: WasmMemoryWrite, buff: &[u8]) -> Result<(), MemoryAccessError>;
137
138    /// Write data from `obj` to owned memory.
139    fn write_as<T: Sized>(
140        &mut self,
141        write: WasmMemoryWriteAs<T>,
142        obj: T,
143    ) -> Result<(), MemoryAccessError>;
144}
145
146/// Memory access manager. Allows to pre-register memory accesses,
147/// and pre-process, them together. For example:
148/// ```ignore
149/// let manager = MemoryAccessManager::default();
150/// let read1 = manager.new_read(10, 20);
151/// let read2 = manager.new_read_as::<u128>(100);
152/// let write1 = manager.new_write_as::<usize>(190);
153///
154/// // First call of read or write interface leads to pre-processing of
155/// // all already registered memory accesses, and clear `self.reads` and `self.writes`.
156/// let value_u128 = manager.read_as(read2).unwrap();
157///
158/// // Next calls do not lead to access pre-processing.
159/// let value1 = manager.read().unwrap();
160/// manager.write_as(write1, 111).unwrap();
161/// ```
162#[derive(Debug)]
163pub struct MemoryAccessManager<Ext> {
164    // Contains non-zero length intervals only.
165    pub(crate) reads: Vec<MemoryInterval>,
166    pub(crate) writes: Vec<MemoryInterval>,
167    pub(crate) _phantom: PhantomData<Ext>,
168}
169
170impl<Ext> Default for MemoryAccessManager<Ext> {
171    fn default() -> Self {
172        Self {
173            reads: Vec::new(),
174            writes: Vec::new(),
175            _phantom: PhantomData,
176        }
177    }
178}
179
180impl<Ext> MemoryAccessRecorder for MemoryAccessManager<Ext> {
181    fn register_read(&mut self, ptr: u32, size: u32) -> WasmMemoryRead {
182        if size > 0 {
183            self.reads.push(MemoryInterval { offset: ptr, size });
184        }
185        WasmMemoryRead { ptr, size }
186    }
187
188    fn register_read_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryReadAs<T> {
189        let size = size_of::<T>() as u32;
190        if size > 0 {
191            self.reads.push(MemoryInterval { offset: ptr, size });
192        }
193        WasmMemoryReadAs {
194            ptr,
195            _phantom: PhantomData,
196        }
197    }
198
199    fn register_read_decoded<T: Decode + MaxEncodedLen>(
200        &mut self,
201        ptr: u32,
202    ) -> WasmMemoryReadDecoded<T> {
203        let size = T::max_encoded_len() as u32;
204        if size > 0 {
205            self.reads.push(MemoryInterval { offset: ptr, size });
206        }
207        WasmMemoryReadDecoded {
208            ptr,
209            _phantom: PhantomData,
210        }
211    }
212
213    fn register_write(&mut self, ptr: u32, size: u32) -> WasmMemoryWrite {
214        if size > 0 {
215            self.writes.push(MemoryInterval { offset: ptr, size });
216        }
217        WasmMemoryWrite { ptr, size }
218    }
219
220    fn register_write_as<T: Sized>(&mut self, ptr: u32) -> WasmMemoryWriteAs<T> {
221        let size = size_of::<T>() as u32;
222        if size > 0 {
223            self.writes.push(MemoryInterval { offset: ptr, size });
224        }
225        WasmMemoryWriteAs {
226            ptr,
227            _phantom: PhantomData,
228        }
229    }
230}
231
232impl<Ext: BackendExternalities> MemoryAccessManager<Ext> {
233    /// Call pre-processing of registered memory accesses. Clear `self.reads` and `self.writes`.
234    pub(crate) fn pre_process_memory_accesses(
235        &mut self,
236        gas_counter: &mut u64,
237    ) -> Result<(), MemoryAccessError> {
238        if self.reads.is_empty() && self.writes.is_empty() {
239            return Ok(());
240        }
241
242        let res = Ext::pre_process_memory_accesses(&self.reads, &self.writes, gas_counter);
243
244        self.reads.clear();
245        self.writes.clear();
246
247        res.map_err(Into::into)
248    }
249
250    /// Pre-process registered accesses if need and read data from `memory` to `buff`.
251    fn read_into_buf<M: Memory>(
252        &mut self,
253        memory: &M,
254        ptr: u32,
255        buff: &mut [u8],
256        gas_counter: &mut u64,
257    ) -> Result<(), MemoryAccessError> {
258        self.pre_process_memory_accesses(gas_counter)?;
259        memory.read(ptr, buff).map_err(Into::into)
260    }
261
262    /// Pre-process registered accesses if need and read data from `memory` into new vector.
263    pub fn read<M: Memory>(
264        &mut self,
265        memory: &M,
266        read: WasmMemoryRead,
267        gas_counter: &mut u64,
268    ) -> Result<Vec<u8>, MemoryAccessError> {
269        let buff = if read.size == 0 {
270            Vec::new()
271        } else {
272            let mut buff = RuntimeBuffer::try_new_default(read.size as usize)?.into_vec();
273            self.read_into_buf(memory, read.ptr, &mut buff, gas_counter)?;
274            buff
275        };
276        Ok(buff)
277    }
278
279    /// Pre-process registered accesses if need and read and decode data as `T` from `memory`.
280    pub fn read_decoded<M: Memory, T: Decode + MaxEncodedLen>(
281        &mut self,
282        memory: &M,
283        read: WasmMemoryReadDecoded<T>,
284        gas_counter: &mut u64,
285    ) -> Result<T, MemoryAccessError> {
286        let size = T::max_encoded_len();
287        let buff = if size == 0 {
288            Vec::new()
289        } else {
290            let mut buff = RuntimeBuffer::try_new_default(size)?.into_vec();
291            self.read_into_buf(memory, read.ptr, &mut buff, gas_counter)?;
292            buff
293        };
294        let decoded = T::decode_all(&mut &buff[..]).map_err(|_| MemoryAccessError::Decode)?;
295        Ok(decoded)
296    }
297
298    /// Pre-process registered accesses if need and read data as `T` from `memory`.
299    pub fn read_as<M: Memory, T: Sized>(
300        &mut self,
301        memory: &M,
302        read: WasmMemoryReadAs<T>,
303        gas_counter: &mut u64,
304    ) -> Result<T, MemoryAccessError> {
305        self.pre_process_memory_accesses(gas_counter)?;
306        read_memory_as(memory, read.ptr).map_err(Into::into)
307    }
308
309    /// Pre-process registered accesses if need and write data from `buff` to `memory`.
310    pub fn write<M: Memory>(
311        &mut self,
312        memory: &mut M,
313        write: WasmMemoryWrite,
314        buff: &[u8],
315        gas_counter: &mut u64,
316    ) -> Result<(), MemoryAccessError> {
317        if buff.len() != write.size as usize {
318            unreachable!("Backend bug error: buffer size is not equal to registered buffer size");
319        }
320        if write.size == 0 {
321            Ok(())
322        } else {
323            self.pre_process_memory_accesses(gas_counter)?;
324            memory.write(write.ptr, buff).map_err(Into::into)
325        }
326    }
327
328    /// Pre-process registered accesses if need and write `obj` data to `memory`.
329    pub fn write_as<M: Memory, T: Sized>(
330        &mut self,
331        memory: &mut M,
332        write: WasmMemoryWriteAs<T>,
333        obj: T,
334        gas_counter: &mut u64,
335    ) -> Result<(), MemoryAccessError> {
336        self.pre_process_memory_accesses(gas_counter)?;
337        write_memory_as(memory, write.ptr, obj).map_err(Into::into)
338    }
339}
340
341/// Writes object in given memory as bytes.
342fn write_memory_as<T: Sized>(
343    memory: &mut impl Memory,
344    ptr: u32,
345    obj: T,
346) -> Result<(), MemoryError> {
347    let size = mem::size_of::<T>();
348    if size > 0 {
349        // # Safety:
350        //
351        // Given object is `Sized` and we own them in the context of calling this
352        // function (it's on stack), it's safe to take ptr on the object and
353        // represent it as slice. Object will be dropped after `memory.write`
354        // finished execution and no one will rely on this slice.
355        //
356        // Bytes in memory always stored continuously and without paddings, properly
357        // aligned due to `[repr(C, packed)]` attribute of the types we use as T.
358        let slice = unsafe { slice::from_raw_parts(&obj as *const T as *const u8, size) };
359
360        memory.write(ptr, slice)
361    } else {
362        Ok(())
363    }
364}
365
366/// Reads bytes from given pointer to construct type T from them.
367fn read_memory_as<T: Sized>(memory: &impl Memory, ptr: u32) -> Result<T, MemoryError> {
368    let mut buf = MaybeUninit::<T>::uninit();
369
370    let size = mem::size_of::<T>();
371    if size > 0 {
372        // # Safety:
373        //
374        // Usage of mutable slice is safe for the same reason from `write_memory_as`.
375        // `MaybeUninit` is presented on stack as a contiguous sequence of bytes.
376        //
377        // It's also safe to construct T from any bytes, because we use the fn
378        // only for reading primitive const-size types that are `[repr(C)]`,
379        // so they always represented from sequence of bytes.
380        //
381        // Bytes in memory always stored continuously and without paddings, properly
382        // aligned due to `[repr(C, packed)]` attribute of the types we use as T.
383        let mut_slice = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, size) };
384
385        memory.read(ptr, mut_slice)?;
386    }
387
388    // # Safety:
389    //
390    // Assuming init is always safe here due to the fact that we read proper
391    // amount of bytes from the wasm memory, which is never uninited: they may
392    // be filled by zeroes or some trash (valid for our primitives used as T),
393    // but always exist.
394    Ok(unsafe { buf.assume_init() })
395}
396
397/// Read static size type access wrapper.
398pub struct WasmMemoryReadAs<T> {
399    pub(crate) ptr: u32,
400    pub(crate) _phantom: PhantomData<T>,
401}
402
403/// Read decoded type access wrapper.
404pub struct WasmMemoryReadDecoded<T: Decode + MaxEncodedLen> {
405    pub(crate) ptr: u32,
406    pub(crate) _phantom: PhantomData<T>,
407}
408
409/// Read access wrapper.
410pub struct WasmMemoryRead {
411    pub(crate) ptr: u32,
412    pub(crate) size: u32,
413}
414
415/// Write static size type access wrapper.
416pub struct WasmMemoryWriteAs<T> {
417    pub(crate) ptr: u32,
418    pub(crate) _phantom: PhantomData<T>,
419}
420
421/// Write access wrapper.
422pub struct WasmMemoryWrite {
423    pub(crate) ptr: u32,
424    pub(crate) size: u32,
425}