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