oasis_runtime_sdk_contracts/abi/oasis/
memory.rs

1//! Host-guest memory management.
2use std::convert::TryInto;
3
4use oasis_runtime_sdk::context::Context;
5
6use super::OasisV1;
7use crate::{abi::ExecutionContext, Config};
8
9/// Name of the memory allocation export.
10pub const EXPORT_ALLOCATE: &str = "allocate";
11/// Name of the memory deallocation export.
12pub const EXPORT_DEALLOCATE: &str = "deallocate";
13
14/// Memory region allocated inside the WASM instance, owned by the host.
15#[derive(Debug)]
16pub struct Region {
17    pub offset: usize,
18    pub length: usize,
19}
20
21#[derive(Debug, thiserror::Error)]
22pub enum RegionError {
23    #[error("region too big")]
24    RegionTooBig,
25    #[error("bad allocation function: {0}")]
26    BadAllocationFunction(#[source] anyhow::Error),
27    #[error("region allocation failed: {0}")]
28    AllocationFailed(#[source] anyhow::Error),
29    #[error("region size mismatch")]
30    SizeMismatch,
31    #[error("bad region pointer")]
32    BadPointer,
33}
34
35impl Region {
36    /// Converts a region to WASM function arguments.
37    pub fn to_arg(&self) -> (u32, u32) {
38        (self.offset as u32, self.length as u32)
39    }
40
41    /// Converts WASM arguments to a region.
42    pub fn from_arg(arg: (u32, u32)) -> Self {
43        Region {
44            offset: arg.0 as usize,
45            length: arg.1 as usize,
46        }
47    }
48
49    /// Dereferences a pointer to the region.
50    pub fn deref(memory: &wasm3::Memory<'_>, arg: u32) -> Result<Self, RegionError> {
51        let arg = arg as usize;
52
53        // Make sure the pointer is within WASM memory.
54        if arg + 8 > memory.size() {
55            return Err(RegionError::BadPointer);
56        }
57
58        // WASM uses little-endian encoding.
59        let dst = memory.as_slice();
60        let offset = u32::from_le_bytes(dst[arg..arg + 4].try_into().unwrap()) as usize;
61        let length = u32::from_le_bytes(dst[arg + 4..arg + 8].try_into().unwrap()) as usize;
62
63        // Ensure that the dereferenced region fits in WASM memory.
64        if offset + length > memory.size() {
65            return Err(RegionError::BadPointer);
66        }
67
68        Ok(Region { offset, length })
69    }
70
71    /// Copies slice content into a previously allocated WASM memory region.
72    pub fn copy_from_slice(
73        &self,
74        memory: &mut wasm3::Memory<'_>,
75        src: &[u8],
76    ) -> Result<(), RegionError> {
77        // Make sure the region is the right size.
78        if src.len() != self.length {
79            return Err(RegionError::SizeMismatch);
80        }
81
82        // Make sure the region fits in WASM memory.
83        if (self.offset + self.length) > memory.size() {
84            return Err(RegionError::BadPointer);
85        }
86
87        let dst = &mut memory.as_slice_mut()[self.offset..self.offset + self.length];
88        dst.copy_from_slice(src);
89
90        Ok(())
91    }
92
93    /// Returns the memory region as a slice.
94    pub fn as_slice<'mem>(
95        &self,
96        memory: &'mem wasm3::Memory<'_>,
97    ) -> Result<&'mem [u8], RegionError> {
98        // Make sure the region fits in WASM memory.
99        if (self.offset + self.length) > memory.size() {
100            return Err(RegionError::BadPointer);
101        }
102
103        Ok(&memory.as_slice()[self.offset..self.offset + self.length])
104    }
105
106    /// Returns the memory region as a mutable slice.
107    pub fn as_slice_mut<'mem>(
108        &self,
109        memory: &'mem mut wasm3::Memory<'_>,
110    ) -> Result<&'mem mut [u8], RegionError> {
111        // Make sure the region fits in WASM memory.
112        if (self.offset + self.length) > memory.size() {
113            return Err(RegionError::BadPointer);
114        }
115
116        Ok(&mut memory.as_slice_mut()[self.offset..self.offset + self.length])
117    }
118
119    /// Returns the serialized region.
120    pub fn serialize(&self) -> [u8; 8] {
121        let mut data = [0u8; 8];
122        data[..4].copy_from_slice(&(self.offset as u32).to_le_bytes());
123        data[4..].copy_from_slice(&(self.length as u32).to_le_bytes());
124        data
125    }
126}
127
128impl<Cfg: Config> OasisV1<Cfg> {
129    /// Allocates a chunk of memory inside the WASM instance.
130    pub fn allocate<C: Context>(
131        instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
132        length: usize,
133    ) -> Result<Region, RegionError> {
134        let length: u32 = length.try_into().map_err(|_| RegionError::RegionTooBig)?;
135
136        // Call the allocation function inside the WASM contract.
137        let func = instance
138            .find_function::<u32, u32>(EXPORT_ALLOCATE)
139            .map_err(|err| RegionError::BadAllocationFunction(err.into()))?;
140        let offset = func
141            .call(length) // Must be called without context.
142            .map_err(|err| RegionError::AllocationFailed(err.into()))?;
143
144        // Generate a region based on the returned value.
145        let region = Region {
146            offset: offset as usize,
147            length: length as usize,
148        };
149
150        // Validate returned region.
151        instance
152            .runtime()
153            .try_with_memory(|memory| -> Result<(), RegionError> {
154                // Make sure the region fits in WASM memory.
155                if (region.offset + region.length) > memory.size() {
156                    return Err(RegionError::BadPointer);
157                }
158                Ok(())
159            })
160            .unwrap()?;
161
162        Ok(region)
163    }
164
165    /// Allocates a chunk of memory inside the WASM instance and copies the given slice into it.
166    pub fn allocate_and_copy<C: Context>(
167        instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
168        data: &[u8],
169    ) -> Result<Region, RegionError> {
170        // Allocate memory for the destination buffer.
171        let dst = Self::allocate(instance, data.len())?;
172        // Copy over data.
173        instance
174            .runtime()
175            .try_with_memory(|mut memory| -> Result<(), RegionError> {
176                dst.copy_from_slice(&mut memory, data)?;
177                Ok(())
178            })
179            .unwrap()?;
180
181        Ok(dst)
182    }
183
184    /// Serializes the given type into CBOR, allocates a chunk of memory inside the WASM instance
185    /// and copies the serialized data into it.
186    pub fn serialize_and_allocate<C, T>(
187        instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
188        data: T,
189    ) -> Result<Region, RegionError>
190    where
191        C: Context,
192        T: cbor::Encode,
193    {
194        let data = cbor::to_vec(data);
195        Self::allocate_and_copy(instance, &data)
196    }
197
198    /// Serializes the given type into CBOR, allocates a chunk of memory inside the WASM instance
199    /// and copies the region and serialized data into it. Returns a pointer to the serialized region.
200    ///
201    /// This method is useful when you need to a pointer to the region of the serialized data,
202    /// since it avoids an additional allocation for the region itself as it pre-allocates it with the data.
203    /// This is an optimized version of calling `serialize_and_allocate` followed by `allocate_region`
204    /// which does two separate allocations.
205    pub fn serialize_and_allocate_as_ptr<C, T>(
206        instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
207        data: T,
208    ) -> Result<u32, RegionError>
209    where
210        C: Context,
211        T: cbor::Encode,
212    {
213        let data = cbor::to_vec(data);
214        // Allocate enough for the data and the serialized region.
215        let outer = Self::allocate(instance, data.len() + 8)?;
216        // First 8 bytes are reserved for the region itself. Inner is the region
217        // for the actual data.
218        let inner = Region {
219            offset: outer.offset + 8,
220            length: outer.length - 8,
221        };
222
223        instance
224            .runtime()
225            .try_with_memory(|mut memory| -> Result<(), RegionError> {
226                inner.copy_from_slice(&mut memory, &data)?;
227
228                let dst = &mut memory.as_slice_mut()[outer.offset..outer.offset + 8];
229                dst.copy_from_slice(&inner.serialize());
230
231                Ok(())
232            })
233            .unwrap()?;
234
235        Ok(outer.offset as u32)
236    }
237
238    /// Allocates a region in WASM memory and returns a pointer to it.
239    pub fn allocate_region<C: Context>(
240        instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
241        region: Region,
242    ) -> Result<u32, RegionError> {
243        let data = region.serialize();
244
245        // Allocate memory for the destination buffer.
246        let dst = Self::allocate(instance, data.len())?;
247        instance
248            .runtime()
249            .try_with_memory(|mut memory| -> Result<(), RegionError> {
250                dst.copy_from_slice(&mut memory, &data)?;
251                Ok(())
252            })
253            .unwrap()?;
254
255        Ok(dst.offset as u32)
256    }
257}