use crate::InstanceState;
use crate::info::ModuleContext;
#[cfg(not(feature = "rayon"))]
use crate::rayoff::{IntoParallelIterator, ParallelExtend};
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelIterator, ParallelExtend, ParallelIterator};
use std::convert::TryFrom;
use std::ops::Range;
const MAX_DATA_SEGMENTS: usize = 10_000;
pub struct Snapshot {
pub globals: Vec<(u32, SnapshotVal)>,
pub memory_mins: Vec<u64>,
pub data_segments: Vec<DataSegment>,
}
#[expect(missing_docs, reason = "self-describing variants")]
pub enum SnapshotVal {
I32(i32),
I64(i64),
F32(u32),
F64(u64),
V128(u128),
}
#[derive(Clone)]
pub struct DataSegment {
pub memory_index: u32,
pub data: Vec<u8>,
pub offset: u64,
pub is64: bool,
}
pub async fn snapshot(module: &ModuleContext<'_>, ctx: &mut impl InstanceState) -> Snapshot {
log::debug!("Snapshotting the initialized state");
let globals = snapshot_globals(module, ctx).await;
let (memory_mins, data_segments) = snapshot_memories(module, ctx).await;
Snapshot {
globals,
memory_mins,
data_segments,
}
}
async fn snapshot_globals(
module: &ModuleContext<'_>,
ctx: &mut impl InstanceState,
) -> Vec<(u32, SnapshotVal)> {
log::debug!("Snapshotting global values");
let mut ret = Vec::new();
for (i, ty, name) in module.defined_globals() {
if let Some(name) = name {
let val = ctx.global_get(name, ty.content_type).await;
ret.push((i, val));
}
}
ret
}
#[derive(Clone)]
struct DataSegmentRange {
memory_index: u32,
range: Range<usize>,
}
impl DataSegmentRange {
fn gap(&self, other: &Self) -> usize {
debug_assert_eq!(self.memory_index, other.memory_index);
debug_assert!(self.range.end <= other.range.start);
other.range.start - self.range.end
}
fn merge(&mut self, other: &Self) {
debug_assert_eq!(self.memory_index, other.memory_index);
debug_assert!(self.range.end <= other.range.start);
self.range.end = other.range.end;
}
}
async fn snapshot_memories(
module: &ModuleContext<'_>,
instance: &mut impl InstanceState,
) -> (Vec<u64>, Vec<DataSegment>) {
log::debug!("Snapshotting memories");
let mut memory_mins = vec![];
let mut data_segments = vec![];
let iter = module
.defined_memories()
.zip(module.defined_memory_exports.as_ref().unwrap());
for ((memory_index, ty), name) in iter {
instance
.memory_contents(&name, |memory| {
let page_size = 1 << ty.page_size_log2.unwrap_or(16);
let num_wasm_pages = memory.len() / page_size;
memory_mins.push(num_wasm_pages as u64);
let memory_data = &memory[..];
data_segments.par_extend((0..num_wasm_pages).into_par_iter().flat_map(|i| {
let page_end = (i + 1) * page_size;
let mut start = i * page_size;
let mut segments = vec![];
while start < page_end {
let nonzero = match memory_data[start..page_end]
.iter()
.position(|byte| *byte != 0)
{
None => break,
Some(i) => i,
};
start += nonzero;
let end = memory_data[start..page_end]
.iter()
.position(|byte| *byte == 0)
.map_or(page_end, |zero| start + zero);
segments.push(DataSegmentRange {
memory_index,
range: start..end,
});
start = end;
}
segments
}));
})
.await;
}
if data_segments.is_empty() {
return (memory_mins, Vec::new());
}
data_segments.sort_by_key(|s| (s.memory_index, s.range.start));
const MIN_ACTIVE_SEGMENT_OVERHEAD: usize = 4;
let mut merged_data_segments = Vec::with_capacity(data_segments.len());
merged_data_segments.push(data_segments[0].clone());
for b in &data_segments[1..] {
let a = merged_data_segments.last_mut().unwrap();
if a.memory_index != b.memory_index {
merged_data_segments.push(b.clone());
continue;
}
let gap = a.gap(b);
if gap > MIN_ACTIVE_SEGMENT_OVERHEAD {
merged_data_segments.push(b.clone());
continue;
}
a.merge(b);
}
remove_excess_segments(&mut merged_data_segments);
let mut final_data_segments = Vec::with_capacity(merged_data_segments.len());
let mut merged = merged_data_segments.iter().peekable();
let iter = module
.defined_memories()
.zip(module.defined_memory_exports.as_ref().unwrap());
for ((memory_index, ty), name) in iter {
instance
.memory_contents(&name, |memory| {
while let Some(segment) = merged.next_if(|s| s.memory_index == memory_index) {
final_data_segments.push(DataSegment {
memory_index,
data: memory[segment.range.clone()].to_vec(),
offset: segment.range.start.try_into().unwrap(),
is64: ty.memory64,
});
}
})
.await;
}
assert!(merged.next().is_none());
(memory_mins, final_data_segments)
}
fn remove_excess_segments(merged_data_segments: &mut Vec<DataSegmentRange>) {
if merged_data_segments.len() < MAX_DATA_SEGMENTS {
return;
}
let excess = merged_data_segments.len() - MAX_DATA_SEGMENTS;
#[derive(Clone, Copy, PartialEq, Eq)]
struct GapIndex {
gap: u32,
index: u32,
}
let mut smallest_gaps = Vec::with_capacity(merged_data_segments.len() - 1);
for (index, w) in merged_data_segments.windows(2).enumerate() {
if w[0].memory_index != w[1].memory_index {
continue;
}
let gap = match u32::try_from(w[0].gap(&w[1])) {
Ok(gap) => gap,
Err(_) => continue,
};
let index = u32::try_from(index).unwrap();
smallest_gaps.push(GapIndex { gap, index });
}
smallest_gaps.sort_unstable_by_key(|g| g.gap);
smallest_gaps.truncate(excess);
smallest_gaps.sort_unstable_by(|a, b| a.index.cmp(&b.index).reverse());
for GapIndex { index, .. } in smallest_gaps {
let index = usize::try_from(index).unwrap();
let [a, b] = merged_data_segments
.get_disjoint_mut([index, index + 1])
.unwrap();
a.merge(b);
merged_data_segments.swap_remove(index + 1);
}
merged_data_segments.sort_by_key(|s| (s.memory_index, s.range.start));
}