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 { 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 { 61 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 { 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 { 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. 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. 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