use std::path::{Path, PathBuf};
use std::time::Instant;
#[derive(Debug, Default, Clone)]
pub struct PrewarmStats {
pub seen: usize,
pub fresh: usize,
pub compiled: usize,
pub failed: usize,
pub bytes: usize,
pub elapsed_ms: u64,
}
pub fn prewarm_fpath(dirs: &[PathBuf]) -> PrewarmStats {
let t0 = Instant::now();
let mut stats = PrewarmStats::default();
let mut batch: Vec<(String, Vec<u8>, String, [u8; 32])> = Vec::new();
let mut claimed: std::collections::HashSet<String> = std::collections::HashSet::new();
for dir in dirs {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(e) => {
tracing::debug!(dir = %dir.display(), error = %e, "prewarm: unreadable fpath dir");
continue;
}
};
for entry in entries.flatten() {
let name = match entry.file_name().into_string() {
Ok(n) => n,
Err(_) => continue,
};
if !name.starts_with('_') || name.ends_with(".zwc") {
continue;
}
let path = entry.path();
match std::fs::metadata(&path) {
Ok(m) if m.is_file() => {}
_ => continue,
}
if !claimed.insert(name.clone()) {
continue; }
stats.seen += 1;
let Some(source) = definition_source(&name, &path) else {
stats.failed += 1;
continue;
};
let sha = crate::autoload_cache::source_digest(&source);
let dir_key = dir.to_string_lossy().to_string();
if crate::autoload_cache::try_load_for_source(&name, &dir_key, &sha).is_some() {
stats.fresh += 1;
continue;
}
match compile_source(&name, &path, &source) {
Some(blob) => {
stats.bytes += blob.len();
stats.compiled += 1;
batch.push((name, blob, dir_key, sha));
}
None => stats.failed += 1,
}
}
}
if let Err(e) = crate::autoload_cache::try_put_many(&batch) {
tracing::warn!(error = %e, "prewarm: shard write failed");
}
stats.elapsed_ms = t0.elapsed().as_millis() as u64;
tracing::info!(
seen = stats.seen,
compiled = stats.compiled,
fresh = stats.fresh,
failed = stats.failed,
bytes = stats.bytes,
ms = stats.elapsed_ms,
"prewarm: autoload bytecode",
);
stats
}
fn muted<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
let saved_noerrs = {
let mut g = crate::ported::utils::noerrs_lock().lock().ok()?;
let prev = *g;
*g = 1;
prev
};
let result = f();
if let Ok(mut g) = crate::ported::utils::noerrs_lock().lock() {
*g = saved_noerrs;
}
result
}
fn definition_source(name: &str, path: &Path) -> Option<String> {
let body = std::fs::read_to_string(path).ok()?;
muted(|| {
Some(crate::vm_helper::autoload_definition_source(
name, &body, false,
))
})
}
fn compile_source(name: &str, path: &Path, source: &str) -> Option<Vec<u8>> {
muted(|| compile_source_inner(name, path, source))
}
fn compile_source_inner(name: &str, path: &Path, source: &str) -> Option<Vec<u8>> {
crate::ported::input::strin.with(|s| s.set(s.get() + 1));
let saved_errflag = crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed);
crate::ported::utils::errflag.fetch_and(
!crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
crate::ported::parse::parse_init(source);
let program = crate::ported::parse::parse();
let failed = (crate::ported::utils::errflag.load(std::sync::atomic::Ordering::Relaxed)
& crate::ported::utils::ERRFLAG_ERROR)
!= 0;
crate::ported::utils::errflag.store(saved_errflag, std::sync::atomic::Ordering::Relaxed);
crate::ported::input::strin.with(|s| s.set(s.get() - 1));
if failed || program.lists.is_empty() {
tracing::debug!(name, path = %path.display(), "prewarm: body did not parse");
return None;
}
let chunk = crate::compile_zsh::ZshCompiler::new().compile(&program);
bincode::serialize(&chunk).ok()
}
pub fn default_dirs() -> Vec<PathBuf> {
let from_array = crate::ported::params::getaparam("fpath").unwrap_or_default();
if !from_array.is_empty() {
return from_array.into_iter().map(PathBuf::from).collect();
}
std::env::var("FPATH")
.unwrap_or_default()
.split(':')
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.collect()
}