#[cfg(target_arch = "wasm32")]
use futures::executor;
use runmat_time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use std::fs::File;
#[cfg(not(target_arch = "wasm32"))]
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Context;
#[cfg(not(target_arch = "wasm32"))]
use memmap2::Mmap;
#[cfg(target_arch = "wasm32")]
type Mmap = ();
use parking_lot::RwLock;
#[cfg(target_arch = "wasm32")]
use runmat_filesystem;
use crate::compression::CompressionEngine;
use crate::format::*;
use crate::validation::{SnapshotValidator, ValidationConfig};
use crate::{LoadingStats, Snapshot, SnapshotConfig, SnapshotError, SnapshotResult};
fn u64_to_usize(value: u64, context: &str) -> SnapshotResult<usize> {
usize::try_from(value).map_err(|_| SnapshotError::Configuration {
message: format!("{context} ({value}) exceeds platform limits"),
})
}
pub struct SnapshotLoader {
config: SnapshotConfig,
compression: CompressionEngine,
#[cfg(feature = "validation")]
validator: SnapshotValidator,
mmap_cache: Arc<RwLock<Vec<Mmap>>>,
stats: LoadingStats,
}
#[cfg(not(target_arch = "wasm32"))]
struct FormatLoader {
file: File,
mmap: Option<Mmap>,
header: SnapshotHeader,
config: SnapshotConfig,
}
impl SnapshotLoader {
pub fn new(config: SnapshotConfig) -> Self {
let compression = CompressionEngine::new(crate::compression::CompressionConfig {
adaptive_selection: false, prefer_speed: true,
..Default::default()
});
#[cfg(feature = "validation")]
let validator = SnapshotValidator::with_config(ValidationConfig {
strict_mode: false, ..ValidationConfig::default()
});
Self {
config,
compression,
#[cfg(feature = "validation")]
validator,
mmap_cache: Arc::new(RwLock::new(Vec::new())),
stats: LoadingStats {
load_time: Duration::ZERO,
decompression_time: Duration::ZERO,
validation_time: Duration::ZERO,
initialization_time: Duration::ZERO,
total_size: 0,
compressed_size: 0,
compression_ratio: 1.0,
builtin_count: 0,
cache_hit_rate: 0.0,
},
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load<P: AsRef<Path>>(&mut self, path: P) -> SnapshotResult<(Snapshot, LoadingStats)> {
let start_time = Instant::now();
log::info!("Loading snapshot from {}", path.as_ref().display());
let format_loader = self.open_snapshot_file(path.as_ref())?;
let data = self.load_snapshot_data(&format_loader)?;
let snapshot = self.deserialize_snapshot(&data)?;
#[cfg(feature = "validation")]
if self.config.validation_enabled {
self.validate_snapshot(&snapshot)?;
}
self.initialize_runtime_integration(&snapshot)?;
self.stats.load_time = start_time.elapsed();
log::info!("Snapshot loaded successfully in {:?}", self.stats.load_time);
Ok((snapshot, self.stats.clone()))
}
#[cfg(target_arch = "wasm32")]
pub fn load<P: AsRef<Path>>(&mut self, path: P) -> SnapshotResult<(Snapshot, LoadingStats)> {
let start_time = Instant::now();
let path_ref = path.as_ref();
log::info!(
"Loading snapshot via filesystem provider from {}",
path_ref.display()
);
let bytes = executor::block_on(runmat_filesystem::read_async(path_ref))?;
let read_duration = start_time.elapsed();
let (snapshot, _) = self.load_from_bytes(&bytes)?;
self.stats.load_time += read_duration;
log::info!("Snapshot loaded successfully in {:?}", self.stats.load_time);
Ok((snapshot, self.stats.clone()))
}
pub fn load_from_bytes(&mut self, bytes: &[u8]) -> SnapshotResult<(Snapshot, LoadingStats)> {
let start_time = Instant::now();
self.stats.total_size = bytes.len() as u64;
if bytes.len() < 4 {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Byte buffer too small to contain snapshot header size",
)));
}
let header_size = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if bytes.len() < 4 + header_size {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Byte buffer too small to contain snapshot header",
)));
}
let header_data = &bytes[4..4 + header_size];
let header: SnapshotHeader = bincode::deserialize(header_data)
.context("Failed to deserialize snapshot header")
.map_err(|e| SnapshotError::Configuration {
message: e.to_string(),
})?;
header.validate()?;
let data_start = if header.data_info.data_offset != 0 {
u64_to_usize(header.data_info.data_offset, "snapshot data offset")?
} else {
4 + header_size
};
let compressed_size =
u64_to_usize(header.data_info.compressed_size, "snapshot compressed size")?;
let data_end = data_start.checked_add(compressed_size).ok_or_else(|| {
SnapshotError::Configuration {
message: "Snapshot data section overflowed buffer length".to_string(),
}
})?;
if data_end > bytes.len() {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Snapshot data section extends beyond provided buffer",
)));
}
let compressed_data = &bytes[data_start..data_end];
self.stats.compressed_size = compressed_data.len() as u64;
let decompression_start = Instant::now();
let decompressed = if matches!(
header.data_info.compression.algorithm,
CompressionAlgorithm::None
) {
compressed_data.to_vec()
} else {
self.compression
.decompress(compressed_data, &header.data_info.compression)?
};
self.stats.decompression_time = decompression_start.elapsed();
let snapshot = self.deserialize_snapshot(&decompressed)?;
#[cfg(feature = "validation")]
if self.config.validation_enabled {
self.validate_snapshot(&snapshot)?;
}
self.initialize_runtime_integration(&snapshot)?;
self.stats.load_time = start_time.elapsed();
Ok((snapshot, self.stats.clone()))
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn load_async<P: AsRef<Path>>(
&mut self,
path: P,
) -> SnapshotResult<(Snapshot, LoadingStats)> {
let start_time = Instant::now();
let path = path.as_ref();
let file = tokio::fs::File::open(path)
.await
.with_context(|| format!("Failed to open snapshot file: {}", path.display()))
.map_err(|e| SnapshotError::Configuration {
message: e.to_string(),
})?;
let metadata = file.metadata().await.map_err(SnapshotError::Io)?;
let file_size = metadata.len() as usize;
self.stats.total_size = file_size as u64;
let mut file_contents = Vec::with_capacity(file_size);
let mut reader = tokio::io::BufReader::new(file);
use tokio::io::AsyncReadExt;
reader
.read_to_end(&mut file_contents)
.await
.map_err(SnapshotError::Io)?;
if file_contents.len() < std::mem::size_of::<SnapshotHeader>() {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"File too small to contain valid snapshot header",
)));
}
let header = parse_snapshot_header(&file_contents)?;
header.validate()?;
let data_start = u64_to_usize(header.data_info.data_offset, "snapshot data offset")?;
let compressed_size =
u64_to_usize(header.data_info.compressed_size, "snapshot compressed size")?;
let data_end = data_start + compressed_size;
if data_end > file_contents.len() {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Data section extends beyond file size",
)));
}
let compressed_data = &file_contents[data_start..data_end];
self.stats.compressed_size = compressed_data.len() as u64;
let decompression_start = Instant::now();
let decompressed_data = if matches!(
header.data_info.compression.algorithm,
CompressionAlgorithm::None
) {
compressed_data.to_vec()
} else {
self.compression
.decompress(compressed_data, &header.data_info.compression)?
};
self.stats.decompression_time = decompression_start.elapsed();
let snapshot = self.deserialize_snapshot(&decompressed_data)?;
if self.config.validation_enabled {
}
let load_time = start_time.elapsed();
self.stats.load_time = load_time;
self.stats.builtin_count = snapshot.builtins.functions.len() as u64;
Ok((snapshot, self.stats.clone()))
}
#[cfg(target_arch = "wasm32")]
pub async fn load_async<P: AsRef<Path>>(
&mut self,
_path: P,
) -> SnapshotResult<(Snapshot, LoadingStats)> {
Err(SnapshotError::Configuration {
message: "Asynchronous snapshot loading from files is unavailable on wasm targets"
.to_string(),
})
}
#[cfg(not(target_arch = "wasm32"))]
fn open_snapshot_file(&mut self, path: &Path) -> SnapshotResult<FormatLoader> {
let start = Instant::now();
let file = File::open(path)
.with_context(|| format!("Failed to open snapshot file: {}", path.display()))
.map_err(|e| crate::SnapshotError::Configuration {
message: e.to_string(),
})?;
let metadata = file.metadata()?;
let file_size = metadata.len() as usize;
self.stats.total_size = file_size as u64;
let mmap = if self.config.memory_mapping_enabled && file_size > 4096 {
match unsafe { Mmap::map(&file) } {
Ok(mmap) => {
log::debug!("Created memory mapping for snapshot file ({file_size} bytes)");
Some(mmap)
}
Err(e) => {
log::warn!("Failed to create memory mapping, falling back to regular I/O: {e}");
None
}
}
} else {
None
};
let mut format_loader = FormatLoader {
file,
mmap,
header: SnapshotHeader::new(SnapshotMetadata::current()), config: self.config.clone(),
};
format_loader.header = format_loader.read_header()?;
format_loader.header.validate()?;
self.stats.compressed_size = format_loader.header.data_info.compressed_size;
self.stats.compression_ratio = format_loader.header.data_info.compressed_size as f64
/ format_loader.header.data_info.uncompressed_size as f64;
let load_time = start.elapsed();
log::debug!("File opened and header validated in {load_time:?}");
Ok(format_loader)
}
#[cfg(not(target_arch = "wasm32"))]
fn load_snapshot_data(&mut self, format_loader: &FormatLoader) -> SnapshotResult<Vec<u8>> {
let start = Instant::now();
let compressed_data = format_loader.read_data_section()?;
let decompression_start = Instant::now();
let data = if matches!(
format_loader.header.data_info.compression.algorithm,
CompressionAlgorithm::None
) {
compressed_data
} else {
self.compression.decompress(
&compressed_data,
&format_loader.header.data_info.compression,
)?
};
self.stats.decompression_time = decompression_start.elapsed();
let load_time = start.elapsed();
log::debug!(
"Data loaded and decompressed in {:?} (decompression: {:?})",
load_time,
self.stats.decompression_time
);
Ok(data)
}
fn deserialize_snapshot(&mut self, data: &[u8]) -> SnapshotResult<Snapshot> {
let start = Instant::now();
let snapshot: Snapshot = bincode::deserialize(data)
.context("Failed to deserialize snapshot data")
.map_err(|e| crate::SnapshotError::Configuration {
message: e.to_string(),
})?;
self.stats.builtin_count = snapshot.builtins.functions.len() as u64;
self.stats.builtin_count = snapshot.builtins.functions.len() as u64;
let deserialize_time = start.elapsed();
log::debug!("Snapshot deserialized in {deserialize_time:?}");
log::debug!(
"Builtin registry counts: names={}, functions={}",
snapshot.builtins.name_index.len(),
snapshot.builtins.functions.len()
);
Ok(snapshot)
}
#[cfg(feature = "validation")]
fn validate_snapshot(&mut self, snapshot: &Snapshot) -> SnapshotResult<()> {
let start = Instant::now();
let content_result = self.validator.validate_content(snapshot)?;
if !content_result.is_ok() {
for error in &content_result.errors {
log::error!(
"Snapshot content validation error ({:?}): {}",
error.severity,
error.message
);
}
for warning in &content_result.warnings {
log::warn!("Snapshot content validation warning: {}", warning.message);
}
if self.config.validation_enabled {
return Err(SnapshotError::Validation {
message: "Snapshot content validation failed".to_string(),
});
} else {
log::warn!("Snapshot content validation failed, but continuing");
}
}
let compat_result = self.validator.validate_compatibility(snapshot)?;
if !compat_result.is_ok() {
log::warn!("Snapshot compatibility issues detected");
for warning in compat_result.warnings {
log::warn!("Compatibility: {}", warning.message);
}
}
self.stats.validation_time = start.elapsed();
log::debug!("Snapshot validated in {:?}", self.stats.validation_time);
Ok(())
}
fn initialize_runtime_integration(&mut self, snapshot: &Snapshot) -> SnapshotResult<()> {
let start = Instant::now();
self.initialize_builtin_dispatch(&snapshot.builtins)?;
self.apply_optimization_hints(&snapshot.optimization_hints)?;
self.configure_gc(&snapshot.gc_presets)?;
self.stats.initialization_time = start.elapsed();
log::debug!(
"Runtime integration initialized in {:?}",
self.stats.initialization_time
);
Ok(())
}
fn initialize_builtin_dispatch(&self, registry: &crate::BuiltinRegistry) -> SnapshotResult<()> {
let current_builtins = runmat_builtins::builtin_functions();
let mut dispatch_table = Vec::with_capacity(registry.functions.len());
for function_meta in ®istry.functions {
if let Some(builtin) = current_builtins
.iter()
.find(|b| b.name == function_meta.name)
{
dispatch_table.push(builtin.implementation);
} else {
log::warn!(
"Builtin function '{}' not found in current runtime",
function_meta.name
);
dispatch_table.push(|_args| {
Box::pin(async {
Err(runmat_async::runtime_error(
"Function not available in current runtime",
)
.build())
})
});
}
}
{
let mut table = registry.dispatch_table.write();
*table = dispatch_table;
}
log::debug!(
"Initialized dispatch table with {} functions",
registry.functions.len()
);
Ok(())
}
fn apply_optimization_hints(&self, hints: &crate::OptimizationHints) -> SnapshotResult<()> {
for hint in &hints.jit_hints {
log::debug!(
"JIT hint: {} ({:?}) - expected gain: {:.1}x",
hint.pattern,
hint.hint_type,
hint.expected_performance_gain
);
}
for hint in &hints.memory_hints {
log::debug!(
"Memory hint: {} ({:?}) - alignment: {}",
hint.data_structure,
hint.hint_type,
hint.alignment
);
}
for hint in &hints.execution_hints {
log::debug!(
"Execution hint: {} ({:?}) - frequency: {}",
hint.pattern,
hint.hint_type,
hint.frequency
);
}
Ok(())
}
fn configure_gc(&self, presets: &crate::GcPresetCache) -> SnapshotResult<()> {
if let Some(default_config) = presets.presets.get(&presets.default_preset) {
match runmat_gc::gc_configure(default_config.clone()) {
Ok(_) => {
log::debug!("GC configured with preset '{}'", presets.default_preset);
}
Err(e) => {
log::warn!("Failed to configure GC with snapshot preset: {e}");
}
}
}
Ok(())
}
pub fn stats(&self) -> &LoadingStats {
&self.stats
}
pub fn clear_cache(&mut self) {
let mut cache = self.mmap_cache.write();
cache.clear();
log::debug!("Memory mapping cache cleared");
}
}
#[cfg(not(target_arch = "wasm32"))]
impl FormatLoader {
fn read_header(&mut self) -> SnapshotResult<SnapshotHeader> {
let use_mmap = self.config.memory_mapping_enabled;
let validate_data = self.config.validation_enabled;
if use_mmap && self.mmap.is_some() {
let mmap_data = self.mmap.as_ref().unwrap();
if mmap_data.len() < 4 {
return Err(crate::SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"File too small to contain header size",
)));
}
let header_size =
u32::from_le_bytes([mmap_data[0], mmap_data[1], mmap_data[2], mmap_data[3]])
as usize;
if mmap_data.len() < 4 + header_size {
return Err(crate::SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"File too small to contain header",
)));
}
let header_data = &mmap_data[4..4 + header_size];
let header: SnapshotHeader = bincode::deserialize(header_data)
.context("Failed to deserialize header from memory map")
.map_err(|e| crate::SnapshotError::Configuration {
message: e.to_string(),
})?;
if validate_data {
header.validate()?;
}
Ok(header)
} else {
let mut reader = BufReader::new(&self.file);
reader.seek(SeekFrom::Start(0))?;
let mut size_buffer = [0u8; 4];
reader.read_exact(&mut size_buffer)?;
let header_size = u32::from_le_bytes(size_buffer) as usize;
let mut header_buffer = vec![0u8; header_size];
reader.read_exact(&mut header_buffer)?;
let header: SnapshotHeader = bincode::deserialize(&header_buffer)
.context("Failed to deserialize header")
.map_err(|e| crate::SnapshotError::Configuration {
message: e.to_string(),
})?;
if validate_data {
header.validate()?;
}
Ok(header)
}
}
fn read_data_section(&self) -> SnapshotResult<Vec<u8>> {
if let Some(ref mmap) = self.mmap {
let header_size = bincode::serialized_size(&self.header)? as usize;
let data_start = 4 + header_size; let compressed_size = u64_to_usize(
self.header.data_info.compressed_size,
"snapshot compressed size",
)?;
let data_end = data_start + compressed_size;
if data_end > mmap.len() {
return Err(SnapshotError::Corrupted {
reason: "Data section extends beyond file".to_string(),
});
}
Ok(mmap[data_start..data_end].to_vec())
} else {
let file = &self.file;
let header_size = bincode::serialized_size(&self.header)? as u64;
let data_start = 4 + header_size; let mut reader = BufReader::new(file);
reader.seek(SeekFrom::Start(data_start))?;
let compressed_size = u64_to_usize(
self.header.data_info.compressed_size,
"snapshot compressed size",
)?;
let mut data = vec![0u8; compressed_size];
reader.read_exact(&mut data)?;
Ok(data)
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl SnapshotLoader {
pub fn peek_header<P: AsRef<Path>>(path: P) -> SnapshotResult<SnapshotHeader> {
let file = File::open(path.as_ref())
.with_context(|| format!("Failed to open snapshot file: {}", path.as_ref().display()))
.map_err(|e| crate::SnapshotError::Configuration {
message: e.to_string(),
})?;
let mut format_loader = FormatLoader {
file,
mmap: None,
header: SnapshotHeader::new(SnapshotMetadata::current()),
config: SnapshotConfig::default(),
};
format_loader.read_header()
}
pub fn quick_validate<P: AsRef<Path>>(path: P) -> SnapshotResult<bool> {
match Self::peek_header(path) {
Ok(header) => Ok(header.validate().is_ok()),
Err(_) => Ok(false),
}
}
pub fn get_metadata<P: AsRef<Path>>(path: P) -> SnapshotResult<SnapshotMetadata> {
let header = Self::peek_header(path)?;
Ok(header.metadata)
}
pub fn estimate_load_time<P: AsRef<Path>>(path: P) -> SnapshotResult<Duration> {
let header = Self::peek_header(path)?;
Ok(header.estimated_load_time())
}
}
#[cfg(target_arch = "wasm32")]
impl SnapshotLoader {
pub fn peek_header<P: AsRef<Path>>(path: P) -> SnapshotResult<SnapshotHeader> {
let bytes = executor::block_on(runmat_filesystem::read_async(path.as_ref()))
.map_err(SnapshotError::Io)?;
let header = parse_snapshot_header(&bytes)?;
header.validate()?;
Ok(header)
}
pub fn quick_validate<P: AsRef<Path>>(path: P) -> SnapshotResult<bool> {
match Self::peek_header(path) {
Ok(header) => Ok(header.validate().is_ok()),
Err(_) => Ok(false),
}
}
pub fn get_metadata<P: AsRef<Path>>(path: P) -> SnapshotResult<SnapshotMetadata> {
let header = Self::peek_header(path)?;
Ok(header.metadata)
}
pub fn estimate_load_time<P: AsRef<Path>>(path: P) -> SnapshotResult<Duration> {
let header = Self::peek_header(path)?;
Ok(header.estimated_load_time())
}
}
fn parse_snapshot_header(bytes: &[u8]) -> SnapshotResult<SnapshotHeader> {
if bytes.len() < 4 {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Snapshot bytes too small to contain header size",
)));
}
let header_size = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if bytes.len() < 4 + header_size {
return Err(SnapshotError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Snapshot bytes too small to contain full header",
)));
}
bincode::deserialize(&bytes[4..4 + header_size]).map_err(|e| SnapshotError::Configuration {
message: format!("Failed to deserialize snapshot header: {e}"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loader_creation() {
let config = SnapshotConfig::default();
let loader = SnapshotLoader::new(config);
assert_eq!(loader.stats.load_time, Duration::ZERO);
}
#[test]
fn test_quick_validate_nonexistent() {
assert!(!SnapshotLoader::quick_validate("nonexistent.snapshot").unwrap_or(true));
}
#[test]
fn test_header_peek() {
let result = SnapshotLoader::peek_header("nonexistent.snapshot");
assert!(result.is_err());
}
#[test]
fn test_metadata_extraction() {
let result = SnapshotLoader::get_metadata("nonexistent.snapshot");
assert!(result.is_err());
}
}