use crate::InstanceState;1use crate::info::ModuleContext;2#[cfg(not(feature = "rayon"))]3use crate::rayoff::{IntoParallelIterator, ParallelExtend};4#[cfg(feature = "rayon")]5use rayon::iter::{IntoParallelIterator, ParallelExtend, ParallelIterator};6use std::convert::TryFrom;7use std::ops::Range;89/// The maximum number of data segments that we will emit. Most10/// engines support more than this, but we want to leave some11/// headroom.12const MAX_DATA_SEGMENTS: usize = 10_000;1314/// A "snapshot" of Wasm state from its default value after having been initialized.15pub struct Snapshot {16/// Maps global index to its initialized value.17///18/// Note that this only tracks defined mutable globals, not all globals.19pub globals: Vec<(u32, SnapshotVal)>,2021/// A new minimum size for each memory (in units of pages).22pub memory_mins: Vec<u64>,2324/// Segments of non-zero memory.25pub data_segments: Vec<DataSegment>,26}2728/// A value from a snapshot, currently a subset of wasm types that aren't29/// reference types.30#[expect(missing_docs, reason = "self-describing variants")]31pub enum SnapshotVal {32I32(i32),33I64(i64),34F32(u32),35F64(u64),36V128(u128),37}3839/// A data segment initializer for a memory.40#[derive(Clone)]41pub struct DataSegment {42/// The index of this data segment's memory.43pub memory_index: u32,4445/// This data segment's initialized memory that it originated from.46pub data: Vec<u8>,4748/// The offset within the memory that `data` should be copied to.49pub offset: u64,5051/// Whether or not `memory_index` is a 64-bit memory.52pub is64: bool,53}5455/// Snapshot the given instance's globals, memories, and instances from the Wasm56/// defaults.57//58// TODO: when we support reference types, we will have to snapshot tables.59pub async fn snapshot(module: &ModuleContext<'_>, ctx: &mut impl InstanceState) -> Snapshot {60log::debug!("Snapshotting the initialized state");6162let globals = snapshot_globals(module, ctx).await;63let (memory_mins, data_segments) = snapshot_memories(module, ctx).await;6465Snapshot {66globals,67memory_mins,68data_segments,69}70}7172/// Get the initialized values of all globals.73async fn snapshot_globals(74module: &ModuleContext<'_>,75ctx: &mut impl InstanceState,76) -> Vec<(u32, SnapshotVal)> {77log::debug!("Snapshotting global values");7879let mut ret = Vec::new();80for (i, ty, name) in module.defined_globals() {81if let Some(name) = name {82let val = ctx.global_get(name, ty.content_type).await;83ret.push((i, val));84}85}86ret87}8889#[derive(Clone)]90struct DataSegmentRange {91memory_index: u32,92range: Range<usize>,93}9495impl DataSegmentRange {96/// What is the gap between two consecutive data segments?97///98/// `self` must be in front of `other` and they must not overlap with each99/// other.100fn gap(&self, other: &Self) -> usize {101debug_assert_eq!(self.memory_index, other.memory_index);102debug_assert!(self.range.end <= other.range.start);103other.range.start - self.range.end104}105106/// Merge two consecutive data segments.107///108/// `self` must be in front of `other` and they must not overlap with each109/// other.110fn merge(&mut self, other: &Self) {111debug_assert_eq!(self.memory_index, other.memory_index);112debug_assert!(self.range.end <= other.range.start);113self.range.end = other.range.end;114}115}116117/// Find the initialized minimum page size of each memory, as well as all118/// regions of non-zero memory.119async fn snapshot_memories(120module: &ModuleContext<'_>,121instance: &mut impl InstanceState,122) -> (Vec<u64>, Vec<DataSegment>) {123log::debug!("Snapshotting memories");124125// Find and record non-zero regions of memory (in parallel).126let mut memory_mins = vec![];127let mut data_segments = vec![];128let iter = module129.defined_memories()130.zip(module.defined_memory_exports.as_ref().unwrap());131for ((memory_index, ty), name) in iter {132instance133.memory_contents(&name, |memory| {134let page_size = 1 << ty.page_size_log2.unwrap_or(16);135let num_wasm_pages = memory.len() / page_size;136memory_mins.push(num_wasm_pages as u64);137138let memory_data = &memory[..];139140// Consider each Wasm page in parallel. Create data segments for each141// region of non-zero memory.142data_segments.par_extend((0..num_wasm_pages).into_par_iter().flat_map(|i| {143let page_end = (i + 1) * page_size;144let mut start = i * page_size;145let mut segments = vec![];146while start < page_end {147let nonzero = match memory_data[start..page_end]148.iter()149.position(|byte| *byte != 0)150{151None => break,152Some(i) => i,153};154start += nonzero;155let end = memory_data[start..page_end]156.iter()157.position(|byte| *byte == 0)158.map_or(page_end, |zero| start + zero);159segments.push(DataSegmentRange {160memory_index,161range: start..end,162});163start = end;164}165segments166}));167})168.await;169}170171if data_segments.is_empty() {172return (memory_mins, Vec::new());173}174175// Sort data segments to enforce determinism in the face of the176// parallelism above.177data_segments.sort_by_key(|s| (s.memory_index, s.range.start));178179// Merge any contiguous segments (caused by spanning a Wasm page boundary,180// and therefore created in separate logical threads above) or pages that181// are within four bytes of each other. Four because this is the minimum182// overhead of defining a new active data segment: one for the memory index183// LEB, two for the memory offset init expression (one for the `i32.const`184// opcode and another for the constant immediate LEB), and finally one for185// the data length LEB).186const MIN_ACTIVE_SEGMENT_OVERHEAD: usize = 4;187let mut merged_data_segments = Vec::with_capacity(data_segments.len());188merged_data_segments.push(data_segments[0].clone());189for b in &data_segments[1..] {190let a = merged_data_segments.last_mut().unwrap();191192// Only merge segments for the same memory.193if a.memory_index != b.memory_index {194merged_data_segments.push(b.clone());195continue;196}197198// Only merge segments if they are contiguous or if it is definitely199// more size efficient than leaving them apart.200let gap = a.gap(b);201if gap > MIN_ACTIVE_SEGMENT_OVERHEAD {202merged_data_segments.push(b.clone());203continue;204}205206// Okay, merge them together into `a` (so that the next iteration can207// merge it with its predecessor) and then omit `b`!208a.merge(b);209}210211remove_excess_segments(&mut merged_data_segments);212213// With the final set of data segments now extract the actual data of each214// memory, copying it into a `DataSegment`, to return the final list of215// segments.216//217// Here the memories are iterated over again and, in tandem, the218// `merged_data_segments` list is traversed to extract a `DataSegment` for219// each range that `merged_data_segments` indicates. This relies on220// `merged_data_segments` being a sorted list by `memory_index` at least.221let mut final_data_segments = Vec::with_capacity(merged_data_segments.len());222let mut merged = merged_data_segments.iter().peekable();223let iter = module224.defined_memories()225.zip(module.defined_memory_exports.as_ref().unwrap());226for ((memory_index, ty), name) in iter {227instance228.memory_contents(&name, |memory| {229while let Some(segment) = merged.next_if(|s| s.memory_index == memory_index) {230final_data_segments.push(DataSegment {231memory_index,232data: memory[segment.range.clone()].to_vec(),233offset: segment.range.start.try_into().unwrap(),234is64: ty.memory64,235});236}237})238.await;239}240assert!(merged.next().is_none());241242(memory_mins, final_data_segments)243}244245/// Engines apply a limit on how many segments a module may contain, and Wizer246/// can run afoul of it. When that happens, we need to merge data segments247/// together until our number of data segments fits within the limit.248fn remove_excess_segments(merged_data_segments: &mut Vec<DataSegmentRange>) {249if merged_data_segments.len() < MAX_DATA_SEGMENTS {250return;251}252253// We need to remove `excess` number of data segments.254let excess = merged_data_segments.len() - MAX_DATA_SEGMENTS;255256#[derive(Clone, Copy, PartialEq, Eq)]257struct GapIndex {258gap: u32,259// Use a `u32` instead of `usize` to fit `GapIndex` within a word on260// 64-bit systems, using less memory.261index: u32,262}263264// Find the gaps between the start of one segment and the next (if they are265// both in the same memory). We will merge the `excess` segments with the266// smallest gaps together. Because they are the smallest gaps, this will267// bloat the size of our data segment the least.268let mut smallest_gaps = Vec::with_capacity(merged_data_segments.len() - 1);269for (index, w) in merged_data_segments.windows(2).enumerate() {270if w[0].memory_index != w[1].memory_index {271continue;272}273let gap = match u32::try_from(w[0].gap(&w[1])) {274Ok(gap) => gap,275// If the gap is larger than 4G then don't consider these two data276// segments for merging and assume there's enough other data277// segments close enough together to still consider for merging to278// get under the limit.279Err(_) => continue,280};281let index = u32::try_from(index).unwrap();282smallest_gaps.push(GapIndex { gap, index });283}284smallest_gaps.sort_unstable_by_key(|g| g.gap);285smallest_gaps.truncate(excess);286287// Now merge the chosen segments together in reverse index order so that288// merging two segments doesn't mess up the index of the next segments we289// will to merge.290smallest_gaps.sort_unstable_by(|a, b| a.index.cmp(&b.index).reverse());291for GapIndex { index, .. } in smallest_gaps {292let index = usize::try_from(index).unwrap();293let [a, b] = merged_data_segments294.get_disjoint_mut([index, index + 1])295.unwrap();296a.merge(b);297298// Okay to use `swap_remove` here because, even though it makes299// `merged_data_segments` unsorted, the segments are still sorted within300// the range `0..index` and future iterations will only operate within301// that subregion because we are iterating over largest to smallest302// indices.303merged_data_segments.swap_remove(index + 1);304}305306// Finally, sort the data segments again so that our output is307// deterministic.308merged_data_segments.sort_by_key(|s| (s.memory_index, s.range.start));309}310311312