lucet_module_data/
linear_memory.rs

1use crate::Error;
2use serde::{Deserialize, Serialize};
3
4/// Specification of the linear memory of a module
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct LinearMemorySpec<'a> {
7    /// Specification of the heap used to implement the linear memory
8    pub heap: HeapSpec,
9    /// Initialization values for linear memory
10    #[serde(borrow)]
11    pub initializer: SparseData<'a>,
12}
13
14/// Specification of the linear memory of a module
15///
16/// This is a version of [`LinearMemorySpec`](../struct.LinearMemorySpec.html) with an
17/// `OwnedSparseData` for the initializer.
18/// This type is useful when directly building up a value to be serialized.
19pub struct OwnedLinearMemorySpec {
20    /// Specification of the heap used to implement the linear memory
21    pub heap: HeapSpec,
22    /// Initialization values for linear memory
23    pub initializer: OwnedSparseData,
24}
25
26impl OwnedLinearMemorySpec {
27    pub fn to_ref<'a>(&'a self) -> LinearMemorySpec<'a> {
28        LinearMemorySpec {
29            heap: self.heap.clone(),
30            initializer: self.initializer.to_ref(),
31        }
32    }
33}
34
35/// Specifications about the heap of a Lucet module.
36#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct HeapSpec {
38    /// Total bytes of memory for the heap to possibly expand into, as configured for Cranelift
39    /// codegen.
40    ///
41    /// All of this memory is addressable. Only some part of it is accessible - from 0 to the
42    /// initial size, guaranteed, and up to the `max_size`.  This size allows Cranelift to elide
43    /// checks of the *base pointer*. At the moment that just means checking if it is greater than
44    /// 4gb, in which case it can elide the base pointer check completely. In the future, Cranelift
45    /// could use a solver to elide more base pointer checks if it can prove the calculation will
46    /// always be less than this bound.
47    ///
48    /// Specified in bytes, and must be evenly divisible by the host page size (4K).
49    pub reserved_size: u64,
50
51    /// Total bytes of memory *after* the reserved area, as configured for Cranelift codegen.
52    ///
53    /// All of this memory is addressable, but it is never accessible - it is guaranteed to trap if
54    /// an access happens in this region. This size allows Cranelift to use *common subexpression
55    /// elimination* to reduce checks of the *sum of base pointer and offset* (where the offset is
56    /// always rounded up to a multiple of the guard size, to be friendly to CSE).
57    ///
58    /// Specified in bytes, and must be evenly divisible by the host page size (4K).
59    pub guard_size: u64,
60
61    /// Total bytes of memory for the WebAssembly program's linear memory upon initialization.
62    ///
63    /// Specified in bytes, must be evenly divisible by the WebAssembly page size (64K), and must be
64    /// less than or equal to `reserved_size`.
65    pub initial_size: u64,
66
67    /// Maximum bytes of memory for the WebAssembly program's linear memory at any time.
68    ///
69    /// This is not necessarily the same as `reserved_size` - we want to be able to tune the check
70    /// bound there separately than the declaration of a max size in the client program.
71    ///
72    /// The program may optionally define this value. If it does, it must be less than the
73    /// `reserved_size`. If it does not, the max size is left up to the runtime, and is allowed to
74    /// be less than `reserved_size`.
75    pub max_size: Option<u64>,
76}
77
78impl HeapSpec {
79    pub fn new(
80        reserved_size: u64,
81        guard_size: u64,
82        initial_size: u64,
83        max_size: Option<u64>,
84    ) -> Self {
85        Self {
86            reserved_size,
87            guard_size,
88            initial_size,
89            max_size,
90        }
91    }
92
93    /// Some very small test programs dont specify a memory import or definition.
94    pub fn empty() -> Self {
95        Self {
96            reserved_size: 0,
97            guard_size: 0,
98            initial_size: 0,
99            max_size: None,
100        }
101    }
102}
103
104/// A sparse representation of a Lucet module's initial heap.
105///
106/// The lifetime parameter exists to support zero-copy deserialization for the `&[u8]` slices
107/// representing non-zero pages. For a variant with owned `Vec<u8>` pages, see
108/// [`OwnedSparseData`](owned/struct.OwnedSparseData.html).
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct SparseData<'a> {
111    /// Indices into the vector correspond to the offset, in host page (4k) increments, from the
112    /// base of the instance heap.
113    ///
114    /// If the option at a given index is None, the page is initialized as zeros. Otherwise,
115    /// the contents of the page are given as a slice of exactly 4k bytes.
116    ///
117    /// The deserializer of this datastructure does not make sure the 4k invariant holds,
118    /// but the constructor on the serializier side does.
119    #[serde(borrow)]
120    pages: Vec<Option<&'a [u8]>>,
121}
122
123impl<'a> SparseData<'a> {
124    /// Create a new `SparseData` from its constituent pages.
125    ///
126    /// Entries in the `pages` argument which are `Some` must contain a slice of exactly the host
127    /// page size (4096), otherwise this function returns `Error::IncorrectPageSize`. Entries which
128    /// are `None` are interpreted as empty pages, which will be zeroed by the runtime.
129    pub fn new(pages: Vec<Option<&'a [u8]>>) -> Result<Self, Error> {
130        if !pages.iter().all(|page| match page {
131            Some(contents) => contents.len() == 4096,
132            None => true,
133        }) {
134            return Err(Error::IncorrectPageSize);
135        }
136
137        Ok(Self { pages })
138    }
139
140    pub fn pages(&self) -> &[Option<&'a [u8]>] {
141        &self.pages
142    }
143
144    pub fn get_page(&self, offset: usize) -> &Option<&'a [u8]> {
145        self.pages.get(offset).unwrap_or(&None)
146    }
147
148    pub fn len(&self) -> usize {
149        self.pages.len()
150    }
151}
152
153/// A sparse representation of a Lucet module's initial heap.
154///
155/// This is a version of [`SparseData`](../struct.SparseData.html) with owned `Vec<u8>`s
156/// representing pages. This type is useful when directly building up a value to be serialized.
157pub struct OwnedSparseData {
158    pages: Vec<Option<Vec<u8>>>,
159}
160
161impl OwnedSparseData {
162    /// Create a new `OwnedSparseData` from its consitutent pages.
163    ///
164    /// Entries in the `pages` argument which are `Some` must contain a vector of exactly the host
165    /// page size (4096), otherwise this function returns `Error::IncorrectPageSize`. Entries which
166    /// are `None` are interpreted as empty pages, which will be zeroed by the runtime.
167    pub fn new(pages: Vec<Option<Vec<u8>>>) -> Result<Self, Error> {
168        if !pages.iter().all(|page| match page {
169            Some(contents) => contents.len() == 4096,
170            None => true,
171        }) {
172            return Err(Error::IncorrectPageSize);
173        }
174        Ok(Self { pages })
175    }
176
177    /// Create a [`SparseData`](../struct.SparseData.html) backed by the values in this
178    /// `OwnedSparseData`.
179    pub fn to_ref<'a>(&'a self) -> SparseData<'a> {
180        SparseData::new(
181            self.pages
182                .iter()
183                .map(|c| match c {
184                    Some(data) => Some(data.as_slice()),
185                    None => None,
186                })
187                .collect(),
188        )
189        .expect("SparseData invariant enforced by OwnedSparseData constructor")
190    }
191}