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}