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