1 //! Generate various kinds of Wasm memory.
2 
3 use anyhow::Result;
4 use arbitrary::{Arbitrary, Unstructured};
5 
6 /// A description of a memory config, image, etc... that can be used to test
7 /// memory accesses.
8 #[derive(Debug)]
9 pub struct MemoryAccesses {
10     /// The configuration to use with this test case.
11     pub config: crate::generators::Config,
12     /// The heap image to use with this test case.
13     pub image: HeapImage,
14     /// The offset immediate to encode in the `load{8,16,32,64}` functions'
15     /// various load instructions.
16     pub offset: u32,
17     /// The amount (in pages) to grow the memory.
18     pub growth: u32,
19 }
20 
21 impl<'a> Arbitrary<'a> for MemoryAccesses {
22     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
23         let image = HeapImage::arbitrary(u)?;
24 
25         // Don't grow too much, since oss-fuzz/asan get upset if we try,
26         // even if we allow it to fail.
27         let one_mib = 1 << 20; // 1 MiB
28         let max_growth = one_mib / (1 << image.page_size_log2.unwrap_or(16));
29         let mut growth: u32 = u.int_in_range(0..=max_growth)?;
30 
31         // Occasionally, round to a power of two, since these tend to be
32         // interesting numbers that overlap with the host page size and things
33         // like that.
34         if growth > 0 && u.ratio(1, 20)? {
35             growth = (growth - 1).next_power_of_two();
36         }
37 
38         Ok(MemoryAccesses {
39             config: u.arbitrary()?,
40             image,
41             offset: u.arbitrary()?,
42             growth,
43         })
44     }
45 }
46 
47 /// A memory heap image.
48 pub struct HeapImage {
49     /// The minimum size (in pages) of this memory.
50     pub minimum: u32,
51     /// The maximum size (in pages) of this memory.
52     pub maximum: Option<u32>,
53     /// Whether this memory should be indexed with `i64` (rather than `i32`).
54     pub memory64: bool,
55     /// The log2 of the page size for this memory.
56     pub page_size_log2: Option<u32>,
57     /// Data segments for this memory.
58     pub segments: Vec<(u32, Vec<u8>)>,
59 }
60 
61 impl std::fmt::Debug for HeapImage {
62     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63         struct Segments<'a>(&'a [(u32, Vec<u8>)]);
64         impl std::fmt::Debug for Segments<'_> {
65             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66                 write!(f, "[..; {}]", self.0.len())
67             }
68         }
69 
70         f.debug_struct("HeapImage")
71             .field("minimum", &self.minimum)
72             .field("maximum", &self.maximum)
73             .field("memory64", &self.memory64)
74             .field("page_size_log2", &self.page_size_log2)
75             .field("segments", &Segments(&self.segments))
76             .finish()
77     }
78 }
79 
80 impl<'a> Arbitrary<'a> for HeapImage {
81     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
82         let minimum = u.int_in_range(0..=4)?;
83         let maximum = if u.arbitrary()? {
84             Some(u.int_in_range(minimum..=10)?)
85         } else {
86             None
87         };
88         let memory64 = u.arbitrary()?;
89         let page_size_log2 = match u.int_in_range(0..=2)? {
90             0 => None,
91             1 => Some(0),
92             2 => Some(16),
93             _ => unreachable!(),
94         };
95         let mut segments = vec![];
96         if minimum > 0 {
97             for _ in 0..u.int_in_range(0..=4)? {
98                 let last_addressable = (1u32 << page_size_log2.unwrap_or(16)) * minimum - 1;
99                 let offset = u.int_in_range(0..=last_addressable)?;
100                 let max_len =
101                     std::cmp::min(u.len(), usize::try_from(last_addressable - offset).unwrap());
102                 let len = u.int_in_range(0..=max_len)?;
103                 let data = u.bytes(len)?.to_vec();
104                 segments.push((offset, data));
105             }
106         }
107         Ok(HeapImage {
108             minimum,
109             maximum,
110             memory64,
111             page_size_log2,
112             segments,
113         })
114     }
115 }
116 
117 /// Represents a normal memory configuration for Wasmtime with the given
118 /// static and dynamic memory sizes.
119 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
120 #[expect(missing_docs, reason = "self-describing fields")]
121 pub struct MemoryConfig {
122     pub memory_reservation: Option<u64>,
123     pub memory_guard_size: Option<u64>,
124     pub memory_reservation_for_growth: Option<u64>,
125     pub guard_before_linear_memory: bool,
126     pub cranelift_enable_heap_access_spectre_mitigations: Option<bool>,
127     pub memory_init_cow: bool,
128 }
129 
130 impl<'a> Arbitrary<'a> for MemoryConfig {
131     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
132         Ok(Self {
133             // Allow up to 8GiB reservations of the virtual address space for
134             // the initial memory reservation.
135             memory_reservation: interesting_virtual_memory_size(u, 33)?,
136 
137             // Allow up to 4GiB guard page reservations to be made.
138             memory_guard_size: interesting_virtual_memory_size(u, 32)?,
139 
140             // Allow up up to 1GiB extra memory to grow into for dynamic
141             // memories.
142             memory_reservation_for_growth: interesting_virtual_memory_size(u, 30)?,
143 
144             guard_before_linear_memory: u.arbitrary()?,
145             cranelift_enable_heap_access_spectre_mitigations: u.arbitrary()?,
146             memory_init_cow: u.arbitrary()?,
147         })
148     }
149 }
150 
151 /// Helper function to generate "interesting numbers" for virtual memory
152 /// configuration options that `Config` supports.
153 fn interesting_virtual_memory_size(
154     u: &mut Unstructured<'_>,
155     max_log2: u32,
156 ) -> arbitrary::Result<Option<u64>> {
157     // Most of the time return "none" meaning "use the default settings".
158     if u.ratio(3, 4)? {
159         return Ok(None);
160     }
161 
162     // Otherwise do a split between various strategies.
163     #[derive(Arbitrary)]
164     enum Interesting {
165         Zero,
166         PowerOfTwo,
167         Arbitrary,
168     }
169 
170     let size = match u.arbitrary()? {
171         Interesting::Zero => 0,
172         Interesting::PowerOfTwo => 1 << u.int_in_range(0..=max_log2)?,
173         Interesting::Arbitrary => u.int_in_range(0..=1 << max_log2)?,
174     };
175     Ok(Some(size))
176 }
177 
178 impl MemoryConfig {
179     /// Apply this memory configuration to the given config.
180     pub fn configure(&self, cfg: &mut wasmtime_cli_flags::CommonOptions) {
181         cfg.opts.memory_reservation = self.memory_reservation;
182         cfg.opts.memory_guard_size = self.memory_guard_size;
183         cfg.opts.memory_reservation_for_growth = self.memory_reservation_for_growth;
184         cfg.opts.guard_before_linear_memory = Some(self.guard_before_linear_memory);
185         cfg.opts.memory_init_cow = Some(self.memory_init_cow);
186 
187         if let Some(enable) = self.cranelift_enable_heap_access_spectre_mitigations {
188             cfg.codegen.cranelift.push((
189                 "enable_heap_access_spectre_mitigation".to_string(),
190                 Some(enable.to_string()),
191             ));
192         }
193     }
194 }
195