1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use crate::Error;
use serde::{Deserialize, Serialize};

/// Specification of the linear memory of a module
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinearMemorySpec<'a> {
    /// Specification of the heap used to implement the linear memory
    pub heap: HeapSpec,
    /// Initialization values for linear memory
    #[serde(borrow)]
    pub initializer: SparseData<'a>,
}

/// Specification of the linear memory of a module
///
/// This is a version of [`LinearMemorySpec`](../struct.LinearMemorySpec.html) with an
/// `OwnedSparseData` for the initializer.
/// This type is useful when directly building up a value to be serialized.
pub struct OwnedLinearMemorySpec {
    /// Specification of the heap used to implement the linear memory
    pub heap: HeapSpec,
    /// Initialization values for linear memory
    pub initializer: OwnedSparseData,
}

impl OwnedLinearMemorySpec {
    pub fn to_ref<'a>(&'a self) -> LinearMemorySpec<'a> {
        LinearMemorySpec {
            heap: self.heap.clone(),
            initializer: self.initializer.to_ref(),
        }
    }
}

/// Specifications about the heap of a Lucet module.
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeapSpec {
    /// Total bytes of memory for the heap to possibly expand into, as configured for Cranelift
    /// codegen.
    ///
    /// All of this memory is addressable. Only some part of it is accessible - from 0 to the
    /// initial size, guaranteed, and up to the `max_size`.  This size allows Cranelift to elide
    /// checks of the *base pointer*. At the moment that just means checking if it is greater than
    /// 4gb, in which case it can elide the base pointer check completely. In the future, Cranelift
    /// could use a solver to elide more base pointer checks if it can prove the calculation will
    /// always be less than this bound.
    ///
    /// Specified in bytes, and must be evenly divisible by the host page size (4K).
    pub reserved_size: u64,

    /// Total bytes of memory *after* the reserved area, as configured for Cranelift codegen.
    ///
    /// All of this memory is addressable, but it is never accessible - it is guaranteed to trap if
    /// an access happens in this region. This size allows Cranelift to use *common subexpression
    /// elimination* to reduce checks of the *sum of base pointer and offset* (where the offset is
    /// always rounded up to a multiple of the guard size, to be friendly to CSE).
    ///
    /// Specified in bytes, and must be evenly divisible by the host page size (4K).
    pub guard_size: u64,

    /// Total bytes of memory for the WebAssembly program's linear memory upon initialization.
    ///
    /// Specified in bytes, must be evenly divisible by the WebAssembly page size (64K), and must be
    /// less than or equal to `reserved_size`.
    pub initial_size: u64,

    /// Maximum bytes of memory for the WebAssembly program's linear memory at any time.
    ///
    /// This is not necessarily the same as `reserved_size` - we want to be able to tune the check
    /// bound there separately than the declaration of a max size in the client program.
    ///
    /// The program may optionally define this value. If it does, it must be less than the
    /// `reserved_size`. If it does not, the max size is left up to the runtime, and is allowed to
    /// be less than `reserved_size`.
    pub max_size: Option<u64>,
}

impl HeapSpec {
    pub fn new(
        reserved_size: u64,
        guard_size: u64,
        initial_size: u64,
        max_size: Option<u64>,
    ) -> Self {
        Self {
            reserved_size,
            guard_size,
            initial_size,
            max_size,
        }
    }

    /// Some very small test programs dont specify a memory import or definition.
    pub fn empty() -> Self {
        Self {
            reserved_size: 0,
            guard_size: 0,
            initial_size: 0,
            max_size: None,
        }
    }
}

/// A sparse representation of a Lucet module's initial heap.
///
/// The lifetime parameter exists to support zero-copy deserialization for the `&[u8]` slices
/// representing non-zero pages. For a variant with owned `Vec<u8>` pages, see
/// [`OwnedSparseData`](owned/struct.OwnedSparseData.html).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SparseData<'a> {
    /// Indices into the vector correspond to the offset, in host page (4k) increments, from the
    /// base of the instance heap.
    ///
    /// If the option at a given index is None, the page is initialized as zeros. Otherwise,
    /// the contents of the page are given as a slice of exactly 4k bytes.
    ///
    /// The deserializer of this datastructure does not make sure the 4k invariant holds,
    /// but the constructor on the serializier side does.
    #[serde(borrow)]
    pages: Vec<Option<&'a [u8]>>,
}

impl<'a> SparseData<'a> {
    /// Create a new `SparseData` from its constituent pages.
    ///
    /// Entries in the `pages` argument which are `Some` must contain a slice of exactly the host
    /// page size (4096), otherwise this function returns `Error::IncorrectPageSize`. Entries which
    /// are `None` are interpreted as empty pages, which will be zeroed by the runtime.
    pub fn new(pages: Vec<Option<&'a [u8]>>) -> Result<Self, Error> {
        if !pages.iter().all(|page| match page {
            Some(contents) => contents.len() == 4096,
            None => true,
        }) {
            return Err(Error::IncorrectPageSize);
        }

        Ok(Self { pages })
    }

    pub fn pages(&self) -> &[Option<&'a [u8]>] {
        &self.pages
    }

    pub fn get_page(&self, offset: usize) -> &Option<&'a [u8]> {
        self.pages.get(offset).unwrap_or(&None)
    }

    pub fn len(&self) -> usize {
        self.pages.len()
    }
}

/// A sparse representation of a Lucet module's initial heap.
///
/// This is a version of [`SparseData`](../struct.SparseData.html) with owned `Vec<u8>`s
/// representing pages. This type is useful when directly building up a value to be serialized.
pub struct OwnedSparseData {
    pages: Vec<Option<Vec<u8>>>,
}

impl OwnedSparseData {
    /// Create a new `OwnedSparseData` from its consitutent pages.
    ///
    /// Entries in the `pages` argument which are `Some` must contain a vector of exactly the host
    /// page size (4096), otherwise this function returns `Error::IncorrectPageSize`. Entries which
    /// are `None` are interpreted as empty pages, which will be zeroed by the runtime.
    pub fn new(pages: Vec<Option<Vec<u8>>>) -> Result<Self, Error> {
        if !pages.iter().all(|page| match page {
            Some(contents) => contents.len() == 4096,
            None => true,
        }) {
            return Err(Error::IncorrectPageSize);
        }
        Ok(Self { pages })
    }

    /// Create a [`SparseData`](../struct.SparseData.html) backed by the values in this
    /// `OwnedSparseData`.
    pub fn to_ref<'a>(&'a self) -> SparseData<'a> {
        SparseData::new(
            self.pages
                .iter()
                .map(|c| match c {
                    Some(data) => Some(data.as_slice()),
                    None => None,
                })
                .collect(),
        )
        .expect("SparseData invariant enforced by OwnedSparseData constructor")
    }
}