use std::time::Instant;
#[cfg(feature = "preinit")]
use std::path::Path;
#[cfg(feature = "embedded")]
use eryx::Session;
fn main() -> anyhow::Result<()> {
let args: Vec<String> = std::env::args().collect();
let cwasm_path = args
.iter()
.position(|a| a == "--output")
.and_then(|i| args.get(i + 1))
.map(|s| s.as_str())
.unwrap_or("crates/eryx-runtime/runtime.cwasm");
let target = args
.iter()
.position(|a| a == "--target")
.and_then(|i| args.get(i + 1))
.map(|s| s.as_str());
let wasm_path = std::env::var("ERYX_WASM_PATH")
.unwrap_or_else(|_| "crates/eryx-runtime/runtime.wasm".to_string());
println!("=== Pre-compilation Example ===\n");
println!("WASM path: {wasm_path}");
println!("Output path: {cwasm_path}");
if let Some(t) = target {
println!("Target: {t}");
} else if let Ok(t) = std::env::var("ERYX_TARGET") {
println!("Target: {t} (from ERYX_TARGET env)");
} else {
println!("Target: native (use --target or ERYX_TARGET for portable builds)");
}
let wasm_bytes = std::fs::read(&wasm_path)?;
println!(
"Original WASM size: {} bytes ({:.1} MB)",
wasm_bytes.len(),
wasm_bytes.len() as f64 / 1_000_000.0
);
#[cfg(feature = "preinit")]
let component_bytes = {
let python_stdlib = find_python_stdlib()?;
println!("Python stdlib: {}\n", python_stdlib.display());
println!("--- Step 1: Pre-initializing Python ---");
println!("This runs Python's interpreter initialization and captures the memory state.");
let start = Instant::now();
let rt = tokio::runtime::Runtime::new()?;
let preinit_bytes = rt.block_on(async {
eryx::preinit::pre_initialize(
&python_stdlib,
None, &[], &[], )
.await
})?;
let preinit_time = start.elapsed();
println!("Pre-init time: {preinit_time:?}");
println!(
"Pre-initialized size: {} bytes ({:.1} MB)",
preinit_bytes.len(),
preinit_bytes.len() as f64 / 1_000_000.0
);
preinit_bytes
};
#[cfg(not(feature = "preinit"))]
let component_bytes = {
println!("\n--- Step 1: Skipping pre-initialization ---");
println!("Enable preinit feature for faster session creation.");
println!("Without pre-init, session creation will take ~450ms instead of ~1-5ms.\n");
wasm_bytes
};
println!("\n--- Step 2: Pre-compiling to native code ---");
let start = Instant::now();
let precompiled = eryx::PythonExecutor::precompile_with_target(&component_bytes, target)?;
let precompile_time = start.elapsed();
println!("Pre-compile time: {precompile_time:?}");
println!(
"Pre-compiled size: {} bytes ({:.1} MB)",
precompiled.len(),
precompiled.len() as f64 / 1_000_000.0
);
println!("\n--- Step 3: Saving pre-compiled WASM ---");
std::fs::write(cwasm_path, &precompiled)?;
println!("Saved to: {cwasm_path}");
#[cfg(feature = "embedded")]
{
println!("\n--- Step 4: Verification ---");
println!("Loading freshly created {cwasm_path} to verify it works...");
let resources = eryx::embedded::EmbeddedResources::get()?;
let rt = tokio::runtime::Runtime::new()?;
#[allow(unsafe_code)]
let sandbox = unsafe {
eryx::Sandbox::builder()
.with_precompiled_file(cwasm_path)
.with_python_stdlib(resources.stdlib())
.build()?
};
println!("\nSandbox creation (10x):");
let start = Instant::now();
for _ in 0..10 {
#[allow(unsafe_code)]
let _sandbox = unsafe {
eryx::Sandbox::builder()
.with_precompiled_file(cwasm_path)
.with_python_stdlib(resources.stdlib())
.build()?
};
}
let sandbox_time = start.elapsed();
println!(
" 10 sandboxes in {:?} (avg {:?})",
sandbox_time,
sandbox_time / 10
);
println!("\nSession creation (5x):");
let start = Instant::now();
for _ in 0..5 {
let _session = rt.block_on(eryx::InProcessSession::new(&sandbox))?;
}
let session_time = start.elapsed();
println!(
" 5 sessions in {:?} (avg {:?})",
session_time,
session_time / 5
);
println!("\nSession execution (10x):");
let mut session = rt.block_on(eryx::InProcessSession::new(&sandbox))?;
let start = Instant::now();
for _ in 0..10 {
rt.block_on(session.execute("x = 1 + 1"))?;
}
let exec_time = start.elapsed();
println!(
" 10 executions in {:?} (avg {:?})",
exec_time,
exec_time / 10
);
let result = rt.block_on(session.execute("print('Hello from pre-compiled sandbox!')"))?;
println!("\nOutput: {}", result.stdout.trim());
println!("\n=== Summary ===");
#[cfg(feature = "preinit")]
println!("Mode: Pre-initialized + Pre-compiled (fastest)");
#[cfg(not(feature = "preinit"))]
println!("Mode: Pre-compiled only (no pre-init)");
println!();
println!("Runtime performance:");
println!(" Sandbox creation: {:?} avg", sandbox_time / 10);
println!(" Session creation: {:?} avg", session_time / 5);
println!(" Session execute: {:?} avg", exec_time / 10);
}
#[cfg(not(feature = "embedded"))]
{
println!("\n--- Step 4: Verification skipped ---");
println!("Run with --features embedded to verify the pre-compiled file works.");
}
println!();
println!("Pre-compiled file saved to: {cwasm_path}");
println!("This file is used by the `embedded` feature for fast sandbox creation.");
Ok(())
}
#[cfg(feature = "preinit")]
fn find_python_stdlib() -> anyhow::Result<std::path::PathBuf> {
for var in ["ERYX_PYTHON_STDLIB", "PYTHON_STDLIB_PATH"] {
if let Ok(path) = std::env::var(var) {
let p = Path::new(&path);
if p.exists() && p.join("encodings").exists() {
return Ok(p.to_path_buf());
}
}
}
let candidates = [
"crates/eryx-wasm-runtime/tests/python-stdlib",
"../eryx-wasm-runtime/tests/python-stdlib",
];
for candidate in &candidates {
let path = Path::new(candidate);
if path.exists() && path.join("encodings").exists() {
return Ok(path.to_path_buf());
}
}
anyhow::bail!(
"Could not find Python stdlib. Tried: {:?}\n\
Run `mise run setup-eryx-runtime-tests` to extract the stdlib, \
or set ERYX_PYTHON_STDLIB environment variable.",
candidates
)
}