From 4f9919d8dd68bf8929857ac062131175aa9c3db6 Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 6 Nov 2026 19:50:23 +0000 Subject: [PATCH 1/4] Share compilation and copy independent commands and source into new worlds --- host/src/main.rs | 9 +- host/src/process/cache.rs | 340 ++++++++++++++++++++++++ host/src/process/mod.rs | 111 ++++---- host/src/world.rs | 32 ++- host/tests/p1.rs | 122 ++++++++- host/tests/process.rs | 67 ++++- userland/brush/build.nu | 6 + userland/brush/lookup.rs | 28 ++ userland/bundle.nu | 38 ++- userland/coreutils/src/bin/arch.rs | 5 + userland/coreutils/src/bin/b2sum.rs | 5 + userland/coreutils/src/bin/base32.rs | 5 + userland/coreutils/src/bin/base64.rs | 5 + userland/coreutils/src/bin/basename.rs | 5 + userland/coreutils/src/bin/basenc.rs | 5 + userland/coreutils/src/bin/cat.rs | 5 + userland/coreutils/src/bin/cksum.rs | 5 + userland/coreutils/src/bin/comm.rs | 5 + userland/coreutils/src/bin/cp.rs | 5 + userland/coreutils/src/bin/csplit.rs | 5 + userland/coreutils/src/bin/cut.rs | 5 + userland/coreutils/src/bin/date.rs | 5 + userland/coreutils/src/bin/dd.rs | 5 + userland/coreutils/src/bin/dir.rs | 5 + userland/coreutils/src/bin/dircolors.rs | 5 + userland/coreutils/src/bin/dirname.rs | 5 + userland/coreutils/src/bin/echo.rs | 5 + userland/coreutils/src/bin/expand.rs | 5 + userland/coreutils/src/bin/expr.rs | 5 + userland/coreutils/src/bin/factor.rs | 5 + userland/coreutils/src/bin/false.rs | 5 + userland/coreutils/src/bin/fmt.rs | 5 + userland/coreutils/src/bin/fold.rs | 5 + userland/coreutils/src/bin/head.rs | 5 + userland/coreutils/src/bin/hostid.rs | 5 + userland/coreutils/src/bin/join.rs | 5 + userland/coreutils/src/bin/link.rs | 5 + userland/coreutils/src/bin/ln.rs | 5 + userland/coreutils/src/bin/ls.rs | 5 + userland/coreutils/src/bin/md5sum.rs | 5 + userland/coreutils/src/bin/mkdir.rs | 5 + userland/coreutils/src/bin/mktemp.rs | 5 + userland/coreutils/src/bin/mv.rs | 5 + userland/coreutils/src/bin/nice.rs | 5 + userland/coreutils/src/bin/nl.rs | 5 + userland/coreutils/src/bin/nproc.rs | 5 + userland/coreutils/src/bin/numfmt.rs | 5 + userland/coreutils/src/bin/od.rs | 5 + userland/coreutils/src/bin/paste.rs | 5 + userland/coreutils/src/bin/pathchk.rs | 5 + userland/coreutils/src/bin/pr.rs | 5 + userland/coreutils/src/bin/printenv.rs | 5 + userland/coreutils/src/bin/printf.rs | 5 + userland/coreutils/src/bin/ptx.rs | 5 + userland/coreutils/src/bin/pwd.rs | 5 + userland/coreutils/src/bin/readlink.rs | 5 + userland/coreutils/src/bin/realpath.rs | 5 + userland/coreutils/src/bin/rm.rs | 5 + userland/coreutils/src/bin/rmdir.rs | 5 + userland/coreutils/src/bin/seq.rs | 5 + userland/coreutils/src/bin/sha1sum.rs | 5 + userland/coreutils/src/bin/sha224sum.rs | 5 + userland/coreutils/src/bin/sha256sum.rs | 5 + userland/coreutils/src/bin/sha384sum.rs | 5 + userland/coreutils/src/bin/sha512sum.rs | 5 + userland/coreutils/src/bin/shred.rs | 5 + userland/coreutils/src/bin/shuf.rs | 5 + userland/coreutils/src/bin/sleep.rs | 5 + userland/coreutils/src/bin/sort.rs | 5 + userland/coreutils/src/bin/split.rs | 5 + userland/coreutils/src/bin/sum.rs | 5 + userland/coreutils/src/bin/tail.rs | 5 + userland/coreutils/src/bin/tee.rs | 5 + userland/coreutils/src/bin/test.rs | 5 + userland/coreutils/src/bin/touch.rs | 5 + userland/coreutils/src/bin/tr.rs | 5 + userland/coreutils/src/bin/true.rs | 5 + userland/coreutils/src/bin/truncate.rs | 5 + userland/coreutils/src/bin/tsort.rs | 5 + userland/coreutils/src/bin/tty.rs | 5 + userland/coreutils/src/bin/uname.rs | 5 + userland/coreutils/src/bin/unexpand.rs | 5 + userland/coreutils/src/bin/uniq.rs | 5 + userland/coreutils/src/bin/unlink.rs | 5 + userland/coreutils/src/bin/vdir.rs | 5 + userland/coreutils/src/bin/wc.rs | 5 + userland/coreutils/src/bin/yes.rs | 5 + userland/coreutils/src/main.rs | 98 ------- userland/coreutils/tools.txt | 78 ------ userland/procps/src/main.rs | 2 +- userland/spawn/src/lib.rs | 8 +- 91 files changed, 1062 insertions(+), 267 deletions(-) create mode 100644 host/src/process/cache.rs create mode 100644 userland/brush/lookup.rs create mode 100644 userland/coreutils/src/bin/arch.rs create mode 100644 userland/coreutils/src/bin/b2sum.rs create mode 100644 userland/coreutils/src/bin/base32.rs create mode 100644 userland/coreutils/src/bin/base64.rs create mode 100644 userland/coreutils/src/bin/basename.rs create mode 100644 userland/coreutils/src/bin/basenc.rs create mode 100644 userland/coreutils/src/bin/cat.rs create mode 100644 userland/coreutils/src/bin/cksum.rs create mode 100644 userland/coreutils/src/bin/comm.rs create mode 100644 userland/coreutils/src/bin/cp.rs create mode 100644 userland/coreutils/src/bin/csplit.rs create mode 100644 userland/coreutils/src/bin/cut.rs create mode 100644 userland/coreutils/src/bin/date.rs create mode 100644 userland/coreutils/src/bin/dd.rs create mode 100644 userland/coreutils/src/bin/dir.rs create mode 100644 userland/coreutils/src/bin/dircolors.rs create mode 100644 userland/coreutils/src/bin/dirname.rs create mode 100644 userland/coreutils/src/bin/echo.rs create mode 100644 userland/coreutils/src/bin/expand.rs create mode 100644 userland/coreutils/src/bin/expr.rs create mode 100644 userland/coreutils/src/bin/factor.rs create mode 100644 userland/coreutils/src/bin/false.rs create mode 100644 userland/coreutils/src/bin/fmt.rs create mode 100644 userland/coreutils/src/bin/fold.rs create mode 100644 userland/coreutils/src/bin/head.rs create mode 100644 userland/coreutils/src/bin/hostid.rs create mode 100644 userland/coreutils/src/bin/join.rs create mode 100644 userland/coreutils/src/bin/link.rs create mode 100644 userland/coreutils/src/bin/ln.rs create mode 100644 userland/coreutils/src/bin/ls.rs create mode 100644 userland/coreutils/src/bin/md5sum.rs create mode 100644 userland/coreutils/src/bin/mkdir.rs create mode 100644 userland/coreutils/src/bin/mktemp.rs create mode 100644 userland/coreutils/src/bin/mv.rs create mode 100644 userland/coreutils/src/bin/nice.rs create mode 100644 userland/coreutils/src/bin/nl.rs create mode 100644 userland/coreutils/src/bin/nproc.rs create mode 100644 userland/coreutils/src/bin/numfmt.rs create mode 100644 userland/coreutils/src/bin/od.rs create mode 100644 userland/coreutils/src/bin/paste.rs create mode 100644 userland/coreutils/src/bin/pathchk.rs create mode 100644 userland/coreutils/src/bin/pr.rs create mode 100644 userland/coreutils/src/bin/printenv.rs create mode 100644 userland/coreutils/src/bin/printf.rs create mode 100644 userland/coreutils/src/bin/ptx.rs create mode 100644 userland/coreutils/src/bin/pwd.rs create mode 100644 userland/coreutils/src/bin/readlink.rs create mode 100644 userland/coreutils/src/bin/realpath.rs create mode 100644 userland/coreutils/src/bin/rm.rs create mode 100644 userland/coreutils/src/bin/rmdir.rs create mode 100644 userland/coreutils/src/bin/seq.rs create mode 100644 userland/coreutils/src/bin/sha1sum.rs create mode 100644 userland/coreutils/src/bin/sha224sum.rs create mode 100644 userland/coreutils/src/bin/sha256sum.rs create mode 100644 userland/coreutils/src/bin/sha384sum.rs create mode 100644 userland/coreutils/src/bin/sha512sum.rs create mode 100644 userland/coreutils/src/bin/shred.rs create mode 100644 userland/coreutils/src/bin/shuf.rs create mode 100644 userland/coreutils/src/bin/sleep.rs create mode 100644 userland/coreutils/src/bin/sort.rs create mode 100644 userland/coreutils/src/bin/split.rs create mode 100644 userland/coreutils/src/bin/sum.rs create mode 100644 userland/coreutils/src/bin/tail.rs create mode 100644 userland/coreutils/src/bin/tee.rs create mode 100644 userland/coreutils/src/bin/test.rs create mode 100644 userland/coreutils/src/bin/touch.rs create mode 100644 userland/coreutils/src/bin/tr.rs create mode 100644 userland/coreutils/src/bin/true.rs create mode 100644 userland/coreutils/src/bin/truncate.rs create mode 100644 userland/coreutils/src/bin/tsort.rs create mode 100644 userland/coreutils/src/bin/tty.rs create mode 100644 userland/coreutils/src/bin/uname.rs create mode 100644 userland/coreutils/src/bin/unexpand.rs create mode 100644 userland/coreutils/src/bin/uniq.rs create mode 100644 userland/coreutils/src/bin/unlink.rs create mode 100644 userland/coreutils/src/bin/vdir.rs create mode 100644 userland/coreutils/src/bin/wc.rs create mode 100644 userland/coreutils/src/bin/yes.rs delete mode 100644 userland/coreutils/src/main.rs delete mode 100644 userland/coreutils/tools.txt diff --git a/host/src/main.rs b/host/src/main.rs index 44866a0..879e293 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -5,6 +5,9 @@ use std::path::PathBuf; #[derive(Parser)] #[command(version, about)] struct Cli { + /// Shared host-user compile cache (otherwise SAUSAGE_CACHE or the XDG cache folder). + #[arg(long, global = true)] + cache: Option, #[command(subcommand)] command: Command, } @@ -29,7 +32,11 @@ enum Command { #[tokio::main] async fn main() -> anyhow::Result<()> { - match Cli::parse().command { + let cli = Cli::parse(); + if let Some(cache) = cli.cache { + sausage::process::configure_cache(cache)?; + } + match cli.command { Command::Run { world, userland } => { let bundle = userland.or_else(|| std::env::var_os("SAUSAGE_USERLAND").map(PathBuf::from)); diff --git a/host/src/process/cache.rs b/host/src/process/cache.rs new file mode 100644 index 0000000..972feea --- /dev/null +++ b/host/src/process/cache.rs @@ -0,0 +1,340 @@ +//! Shares compiled programs and single-flight compilation across worlds in one host process. +use super::{Compiled, State, adapter, bindings}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::{Arc, Mutex, OnceLock}, +}; +use tokio::sync::watch; +type Compilation = watch::Receiver>>; +use wasmtime::{ + Engine, + component::{Component, Linker}, +}; +use wasmtime_wasi::p2::bindings::CommandPre; + +static DIRECTORY: OnceLock = OnceLock::new(); +static SHARED: Mutex>> = Mutex::new(None); + +/// Select the host user's cache before opening a world. +pub fn configure(directory: PathBuf) -> anyhow::Result<()> { + DIRECTORY + .set(directory) + .map_err(|_| anyhow::anyhow!("cache directory already configured")) +} +fn directory() -> anyhow::Result { + if let Some(dir) = DIRECTORY.get() { + return Ok(dir.clone()); + } + if let Some(dir) = std::env::var_os("SAUSAGE_CACHE") { + return Ok(dir.into()); + } + if let Some(dir) = std::env::var_os("XDG_CACHE_HOME") { + return Ok(PathBuf::from(dir).join("sausage")); + } + Ok( + PathBuf::from(std::env::var_os("HOME").ok_or_else(|| anyhow::anyhow!("HOME is not set"))?) + .join(".cache/sausage"), + ) +} +pub(super) fn shared() -> anyhow::Result> { + let mut shared = SHARED.lock().unwrap(); + if shared.is_none() { + *shared = Some(Compiler::new(&directory()?)?); + } + Ok(shared.as_ref().unwrap().clone()) +} +pub(super) struct Compiler { + pub engine: Engine, + programs: Mutex, Compilation>>, + #[cfg(test)] + before_compile: Mutex>>, + #[cfg(test)] + compilations: std::sync::atomic::AtomicUsize, +} +impl Compiler { + pub fn new(directory: &Path) -> anyhow::Result> { + Self::with_opt(directory, wasmtime::OptLevel::Speed) + } + fn with_opt(directory: &Path, opt: wasmtime::OptLevel) -> anyhow::Result> { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let mut config = wasmtime::Config::new(); + config.epoch_interruption(true); + config.cranelift_opt_level(opt); + let mut cache = wasmtime::CacheConfig::new(); + cache.with_directory(directory); + config.cache(Some(wasmtime::Cache::new(cache)?)); + Ok(Arc::new(Self { + engine: Engine::new(&config)?, + programs: Mutex::new(HashMap::new()), + #[cfg(test)] + before_compile: Mutex::new(None), + #[cfg(test)] + compilations: std::sync::atomic::AtomicUsize::new(0), + })) + } + pub async fn get(self: &Arc, bytes: Vec) -> anyhow::Result { + let mut result = self + .programs + .lock() + .unwrap() + .entry(bytes.clone()) + .or_insert_with(|| { + let (tx, rx) = watch::channel(None); + let compiler = self.clone(); + // The job owns completion even if a command or a precompile waiter is cancelled. + tokio::spawn(async move { + let result = + tokio::task::spawn_blocking(move || compiler.compile(&bytes)).await; + tx.send_replace(Some( + result + .map_err(anyhow::Error::from) + .and_then(|r| r) + .map_err(|e| format!("{e:#}")), + )); + }); + rx + }) + .clone(); + loop { + if let Some(result) = result.borrow().clone() { + return result.map_err(|e| anyhow::anyhow!(e)); + } + result.changed().await?; + } + } + fn compile(&self, bytes: &[u8]) -> anyhow::Result { + #[cfg(test)] + { + self.compilations + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if let Some(hook) = self.before_compile.lock().unwrap().take() { + hook(); + } + } + if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") { + let module = wasmtime::Module::new(&self.engine, bytes)?; + let mut linker = wasmtime::Linker::new(&self.engine); + sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| { + s.preview1.as_mut().unwrap() + })?; + Ok(Compiled::Module(linker.instantiate_pre(&module)?)) + } else { + let component = Component::new(&self.engine, bytes)?; + let mut linker = Linker::new(&self.engine); + wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; + wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?; + adapter::add_to_linker(&mut linker, |s: &mut State| adapter::View { + adapter: &mut s.fs, + table: &mut s.table, + })?; + bindings::sausage::process::host::add_to_linker::<_, wasmtime::component::HasSelf<_>>( + &mut linker, + |s| s, + )?; + Ok(Compiled::Component(CommandPre::new( + linker.instantiate_pre(&component)?, + )?)) + } + } +} +/// Compile programs in both command folders without blocking world startup. +pub(super) fn precompile( + compiler: Arc, + root: Arc, +) -> tokio::task::JoinHandle> { + tokio::spawn(async move { + let mut tasks = tokio::task::JoinSet::new(); + for folder in ["/bin", "/usr/local/bin"] { + let entries = match root.list(folder).await { + Ok(entries) => entries, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue, + Err(e) => return Err(e.into()), + }; + for entry in entries.into_iter().filter(|entry| !entry.directory) { + let root = root.clone(); + let compiler = compiler.clone(); + let path = format!("{folder}/{}", entry.name); + tasks.spawn(async move { + let file = root + .open( + &path, + sausage_vfs::Open { + follow: true, + ..Default::default() + }, + ) + .await?; + let bytes = file.read(0, file.stat().await?.size as usize).await?; + if bytes.get(..4) != Some(b"\0asm") { + return Ok::<_, anyhow::Error>(false); + } + compiler.get(bytes).await?; + Ok(true) + }); + } + } + let mut count = 0; + while let Some(result) = tasks.join_next().await { + if result?? { + count += 1; + } + } + Ok(count) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::exec::{Command, Exec, Options}; + use std::{future::Future, sync::atomic::Ordering}; + + #[tokio::test(flavor = "multi_thread")] + #[ignore = "Temporary T15 timing probe; remove before review"] + async fn measure_world() { + let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle"); + let temp = tempfile::tempdir().unwrap(); + for (index, script) in ["echo hi | tr a-z A-Z", "git --version", "rg --version"] + .into_iter() + .enumerate() + { + let cache = temp.path().join(format!("cache-{index}")); + let start = std::time::Instant::now(); + let world = crate::world::World::open_with_userland( + &temp.path().join(format!("cold-{index}")), + Some(&bundle), + ) + .unwrap(); + let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap(); + let mut output = |_: crate::exec::Stream, _: &[u8]| {}; + assert_eq!( + runtime + .exec( + Command::Shell(script.into()), + Options::default(), + &mut output + ) + .await + .unwrap(), + 0 + ); + println!( + "profile={} state=cold command={script:?} seconds={:.6}", + if cfg!(debug_assertions) { + "debug" + } else { + "release" + }, + start.elapsed().as_secs_f64() + ); + let count = runtime.precompile().await.unwrap().unwrap(); + println!( + "background commands={count} seconds={:.6}", + start.elapsed().as_secs_f64() + ); + let start = std::time::Instant::now(); + assert_eq!( + runtime + .exec( + Command::Shell(script.into()), + Options::default(), + &mut output + ) + .await + .unwrap(), + 0 + ); + println!( + "state=same-host command={script:?} seconds={:.6}", + start.elapsed().as_secs_f64() + ); + drop(runtime); + let start = std::time::Instant::now(); + let world = crate::world::World::open_with_userland( + &temp.path().join(format!("warm-{index}")), + Some(&bundle), + ) + .unwrap(); + let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap(); + assert_eq!( + runtime + .exec( + Command::Shell(script.into()), + Options::default(), + &mut output + ) + .await + .unwrap(), + 0 + ); + println!( + "state=fresh-warm command={script:?} seconds={:.6}", + start.elapsed().as_secs_f64() + ); + runtime.precompile().await.unwrap().unwrap(); + } + } + + #[test] + #[ignore = "Temporary T15 timing probe; remove before review"] + fn measure_optimizer() { + let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle/bin"); + let temp = tempfile::tempdir().unwrap(); + for opt in [wasmtime::OptLevel::Speed, wasmtime::OptLevel::None] { + let compiler = Compiler::with_opt(&temp.path().join(format!("{opt:?}")), opt).unwrap(); + for name in ["sh", "tr", "git", "rg"] { + let bytes = std::fs::read(bundle.join(format!("{name}.wasm"))).unwrap(); + let start = std::time::Instant::now(); + compiler.compile(&bytes).unwrap(); + println!( + "optimizer={opt:?} program={name} bytes={} compile_seconds={:.6}", + bytes.len(), + start.elapsed().as_secs_f64() + ); + } + } + } + + #[tokio::test(flavor = "multi_thread")] + async fn command_joins_precompile_even_after_waiter_cancellation() { + let temp = tempfile::tempdir().unwrap(); + let compiler = Compiler::new(&temp.path().join("cache")).unwrap(); + let world = crate::world::World::open(&temp.path().join("world")).unwrap(); + let bytes = + wat::parse_str(r#"(module (memory (export "memory") 1) (func (export "_start")))"#) + .unwrap(); + std::fs::create_dir_all(temp.path().join("world/world/bin")).unwrap(); + std::fs::write(temp.path().join("world/world/bin/program.wasm"), &bytes).unwrap(); + let (started_tx, started_rx) = tokio::sync::oneshot::channel(); + let (finish_tx, finish_rx) = std::sync::mpsc::channel(); + *compiler.before_compile.lock().unwrap() = Some(Box::new(move || { + started_tx.send(()).unwrap(); + finish_rx.recv().unwrap(); + })); + // Startup returns while the compiler remains blocked behind the test gate. + let runtime = super::super::Runtime::with_compiler(world, compiler.clone()).unwrap(); + started_rx.await.unwrap(); + let background = compiler.clone(); + let precompile = tokio::spawn(async move { background.get(bytes).await }); + let mut output = |_: crate::exec::Stream, _: &[u8]| {}; + let command = runtime.exec( + Command::Argv(vec!["/bin/program".into()]), + Options::default(), + &mut output, + ); + tokio::pin!(command); + let pending = std::future::poll_fn(|cx| { + std::task::Poll::Ready(command.as_mut().poll(cx).is_pending()) + }) + .await; + assert!(pending); + assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1); + precompile.abort(); + let _ = precompile.await; + finish_tx.send(()).unwrap(); + assert_eq!(command.await.unwrap(), 0); + assert_eq!(compiler.compilations.load(Ordering::Relaxed), 1); + assert!(!temp.path().join("world/host/compile-cache").exists()); + } +} diff --git a/host/src/process/mod.rs b/host/src/process/mod.rs index 187c423..fd525e8 100644 --- a/host/src/process/mod.rs +++ b/host/src/process/mod.rs @@ -5,7 +5,7 @@ use crate::{ }; use sausage_vfs::{ Mounts, - adapter::{self, Adapter, View}, + adapter::{self, Adapter}, }; use std::{ collections::HashMap, @@ -22,7 +22,7 @@ use tokio::{ }; use wasmtime::{ Engine, Store, - component::{Component, Linker, Resource, ResourceTable}, + component::{Resource, ResourceTable}, }; use wasmtime_wasi::{ WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, @@ -45,6 +45,8 @@ mod bindings { }); } use bindings::sausage::process::host::{Host, HostChild, Request, Stdio}; +mod cache; +pub use cache::configure as configure_cache; mod services; mod stdio; pub use services::supervise; @@ -77,7 +79,8 @@ struct Entry { pub struct Runtime { world: World, engine: Engine, - cache: Mutex, Compiled>>, + compiler: Arc, + cache: Mutex>>, entries: Mutex>, next: AtomicU32, alive: Arc, @@ -138,14 +141,14 @@ impl Runtime { Self::new(World::open(path)?) } pub fn new(world: World) -> anyhow::Result> { - // Deno and WASI HTTP enable different providers. Select one before HTTPS creates a client. - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - let mut config = wasmtime::Config::new(); - config.epoch_interruption(true); - let mut cache = wasmtime::CacheConfig::new(); - cache.with_directory(world.host_dir.join("compile-cache")); - config.cache(Some(wasmtime::Cache::new(cache)?)); - let engine = Engine::new(&config)?; + Self::with_compiler(world, cache::shared()?) + } + /// Open an independent engine using an explicit shared disk cache. + pub fn new_with_cache(world: World, directory: &Path) -> anyhow::Result> { + Self::with_compiler(world, cache::Compiler::new(directory)?) + } + fn with_compiler(world: World, compiler: Arc) -> anyhow::Result> { + let engine = compiler.engine.clone(); let alive = Arc::new(AtomicBool::new(true)); let ticking = alive.clone(); let clock = engine.clone(); @@ -155,19 +158,61 @@ impl Runtime { clock.increment_epoch(); } }); - Ok(Arc::new(Self { + let runtime = Arc::new(Self { world, engine, alive, - cache: Mutex::new(HashMap::new()), + compiler, + cache: Mutex::new(Default::default()), entries: Mutex::new(HashMap::new()), next: AtomicU32::new(1), - })) + }); + runtime.precompile(); + Ok(runtime) + } + /// Join startup precompilation of both command folders through the shared content slots. + pub fn precompile(&self) -> tokio::task::JoinHandle> { + cache::precompile(self.compiler.clone(), self.world.root.clone()) } /// Number of distinct program contents compiled by this runtime. pub fn compiled_programs(&self) -> usize { self.cache.lock().unwrap().len() } + async fn resolve_program( + &self, + cwd: &str, + name: &str, + env: &[(String, String)], + ) -> anyhow::Result { + let paths = if name.contains('/') { + vec![sausage_vfs::resolve(cwd, name)] + } else { + let path = env + .iter() + .find(|(key, _)| key == "PATH") + .map(|(_, value)| value.as_str()) + .unwrap_or("/usr/local/bin:/bin"); + path.split(':') + .map(|folder| { + sausage_vfs::resolve( + cwd, + &std::path::Path::new(folder).join(name).to_string_lossy(), + ) + }) + .collect() + }; + for path in paths { + for candidate in [path.clone(), format!("{path}.wasm")] { + match self.world.root.stat(&candidate, true).await { + Ok(stat) if !stat.directory => return Ok(candidate), + Ok(_) => {} + Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + Err(e) => return Err(e.into()), + } + } + } + anyhow::bail!("program not found: {name}") + } fn kill_command(&self, command: u32) { for entry in self.entries.lock().unwrap().values() { if entry.command == command { @@ -222,7 +267,9 @@ impl Runtime { request.cwd = sausage_vfs::resolve("/", &request.cwd); request.env.retain(|(name, _)| name != "PWD"); request.env.push(("PWD".into(), request.cwd.clone())); - let executable = sausage_vfs::resolve(&request.cwd, &request.program); + let executable = self + .resolve_program(&request.cwd, &request.program, &request.env) + .await?; let file = self .world .root @@ -237,38 +284,8 @@ impl Runtime { let bytes = file .read(0, usize::try_from(file.stat().await?.size)?) .await?; - let pre = { - let mut cache = self.cache.lock().unwrap(); - if let Some(pre) = cache.get(&bytes) { - pre.clone() - } else { - let pre = if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") { - let module = wasmtime::Module::new(&self.engine, &bytes)?; - let mut linker = wasmtime::Linker::new(&self.engine); - sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| { - s.preview1.as_mut().unwrap() - })?; - Compiled::Module(linker.instantiate_pre(&module)?) - } else { - let component = Component::new(&self.engine, &bytes)?; - let mut linker = Linker::new(&self.engine); - wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; - // The library linker includes exit-with-code. Only Wasmtime's CLI gates it. - wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?; - adapter::add_to_linker(&mut linker, |s: &mut State| View { - adapter: &mut s.fs, - table: &mut s.table, - })?; - bindings::sausage::process::host::add_to_linker::< - _, - wasmtime::component::HasSelf<_>, - >(&mut linker, |s| s)?; - Compiled::Component(CommandPre::new(linker.instantiate_pre(&component)?)?) - }; - cache.insert(bytes, pre.clone()); - pre - } - }; + let pre = self.compiler.get(bytes.clone()).await?; + self.cache.lock().unwrap().insert(bytes); let pid = self.next.fetch_add(1, Ordering::Relaxed); let command = if command == 0 { pid } else { command }; let group = match request.group { diff --git a/host/src/world.rs b/host/src/world.rs index 0185b1f..b77674e 100644 --- a/host/src/world.rs +++ b/host/src/world.rs @@ -19,17 +19,37 @@ pub struct World { pub processes: crate::process::Processes, } +fn copy_tree(source: &Path, destination: &Path) -> std::io::Result<()> { + std::fs::create_dir_all(destination)?; + for entry in std::fs::read_dir(source)? { + let entry = entry?; + let target = destination.join(entry.file_name()); + if entry.file_type()?.is_dir() { + copy_tree(&entry.path(), &target)?; + } else { + std::fs::copy(entry.path(), target)?; + } + } + Ok(()) +} + impl World { /// Open (and create if missing) the world directory `dir`, including `/proc` and `/run` mounts. pub fn open(dir: &Path) -> anyhow::Result { Self::open_with_userland(dir, None) } - /// Mount an optional host bundle read-only at `/bin` (D43). + /// Copy an optional bundle into a new world. Existing worlds retain their programs. pub fn open_with_userland(dir: &Path, userland: Option<&Path>) -> anyhow::Result { let root_dir = dir.join("world"); let host_dir = dir.join("host"); + let fresh = !root_dir.exists(); std::fs::create_dir_all(root_dir.join("usr/local/bin"))?; + if fresh && let Some(bundle) = userland { + for folder in ["bin", "src"] { + copy_tree(&bundle.join(folder), &root_dir.join(folder))?; + } + } std::fs::create_dir_all(&host_dir)?; let mut mounts = Mounts::default(); mounts.mount( @@ -55,17 +75,11 @@ impl World { } Ok(text.into_bytes()) }); - if let Some(bundle) = userland { - mounts.mount( - "/bin", - Arc::new(Host::new(bundle, Process::default())?), - true, - ); - } mounts.mount("/proc", Arc::new(proc), true); mounts.mount("/run", Arc::new(Memory::default()), false); + let root: Arc = Arc::new(mounts); Ok(World { - root: Arc::new(mounts), + root, host_dir, processes, }) diff --git a/host/tests/p1.rs b/host/tests/p1.rs index ade9671..65fd7f9 100644 --- a/host/tests/p1.rs +++ b/host/tests/p1.rs @@ -1,13 +1,13 @@ -//! P1 integration: a read-only bundle and real shell calls from the durable harness. +//! P1 integration: independent bundle copies and real shell calls from the durable harness. use sausage::{harness::run_module, world::World}; use sausage_vfs::Open; use std::path::PathBuf; #[tokio::test(flavor = "current_thread")] -async fn bundle_mount_and_real_shell() -> anyhow::Result<()> { +async fn bundle_copy_and_real_shell() -> anyhow::Result<()> { let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".."); - let bundle = repo.join("target/userland/bin"); - if !bundle.join("sh").exists() { + let bundle = repo.join("target/userland/bundle"); + if !bundle.join("bin/sh.wasm").exists() { assert!( std::process::Command::new("nu") .env_remove("CARGO_TARGET_DIR") @@ -18,9 +18,16 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> { } let dir = tempfile::tempdir()?; let world = World::open_with_userland(dir.path(), Some(&bundle))?; - assert!(world.root.stat("/bin/sh", true).await?.size > 0); + assert!(world.root.stat("/bin/sh.wasm", true).await?.size > 0); + use std::os::unix::fs::MetadataExt; + for entry in std::fs::read_dir(dir.path().join("world/bin"))? { + let entry = entry?; + assert_eq!(entry.path().extension().unwrap(), "wasm"); + assert_eq!(entry.metadata()?.nlink(), 1); + assert!(!entry.file_type()?.is_symlink()); + } assert!(world.root.stat("/usr/local/bin", true).await?.directory); - let denied = world + let writable = world .root .open( "/bin/forbidden", @@ -31,17 +38,72 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> { }, ) .await; - assert!(matches!(denied, Err(e) if e.kind() == std::io::ErrorKind::ReadOnlyFilesystem)); + assert!(writable.is_ok()); let exec = sausage::process::exec(World { root: world.root.clone(), host_dir: world.host_dir.clone(), processes: world.processes.clone(), })?; + use sausage::exec::{Command, Options, Stream}; + for (script, expected) in [ + ("command -v ls", "/bin/ls.wasm\n"), + ("ls", "bin"), + ("git --version", "git version"), + ("printf 'a\\n' | xargs echo", "a\n"), + ("env FOO=1 printenv FOO", "1\n"), + ] { + let mut out = Vec::new(); + let code = exec + .exec( + Command::Shell(script.into()), + Options::default(), + &mut |stream, bytes| { + if matches!(stream, Stream::Stdout) { + out.extend_from_slice(bytes); + } + }, + ) + .await?; + assert_eq!(code, 0, "{script}"); + assert!(String::from_utf8(out)?.contains(expected), "{script}"); + } + let mut history = Vec::new(); + assert_eq!( + exec.exec( + Command::Shell("cd /src/sausage && git log --oneline".into()), + Options::default(), + &mut |stream, bytes| { + if matches!(stream, Stream::Stdout) { + history.extend_from_slice(bytes); + } + }, + ) + .await?, + 0, + ); + let history = String::from_utf8(history)?; + assert_eq!(history.lines().count(), 1); + assert!(history.contains("Bundle source ")); + let mut status = Vec::new(); + assert_eq!( + exec.exec( + Command::Shell("cd /src/sausage && git status --short".into()), + Options::default(), + &mut |stream, bytes| { + if matches!(stream, Stream::Stdout) { + status.extend_from_slice(bytes); + } + }, + ) + .await?, + 0, + ); + assert!(status.is_empty(), "{}", String::from_utf8_lossy(&status)); let source = format!( "globalThis.testP1 = true;\n{}", include_str!("../js/test-bundle.js") ); - run_module(world, exec, &source).await?; + run_module(world, exec.clone(), &source).await?; assert_eq!( std::fs::read_to_string(dir.path().join("world/test.txt"))?, "after\n" @@ -59,6 +121,50 @@ async fn bundle_mount_and_real_shell() -> anyhow::Result<()> { let actual = std::fs::read_to_string(spills[0].path())?; let expected: String = (1..=10000).map(|n| format!("{n}\n")).collect(); assert_eq!(actual, expected); + let source_cat = std::fs::read(bundle.join("bin/cat.wasm"))?; + let head = std::fs::read(dir.path().join("world/bin/head.wasm"))?; + let mut out = Vec::new(); + assert_eq!( + exec.exec( + Command::Shell( + "cp /bin/true.wasm /bin/cat.wasm; cat /test.txt; head -n 1 /test.txt".into() + ), + Options::default(), + &mut |stream, bytes| { + if matches!(stream, Stream::Stdout) { + out.extend_from_slice(bytes); + } + } + ) + .await?, + 0 + ); + assert_eq!(out, b"after\n"); + assert_eq!(std::fs::read(bundle.join("bin/cat.wasm"))?, source_cat); + assert_eq!(std::fs::read(dir.path().join("world/bin/head.wasm"))?, head); + assert_eq!( + exec.exec( + Command::Shell("cp /bin/true.wasm /usr/local/bin/head.wasm; head /test.txt".into()), + Options::default(), + &mut |_, bytes| assert!(bytes.is_empty()) + ) + .await?, + 0 + ); + let changed = std::fs::read(dir.path().join("world/bin/cat.wasm"))?; + let reopened = World::open_with_userland( + dir.path(), + Some(std::path::Path::new("/nonexistent-bundle")), + )?; + assert_eq!( + reopened + .root + .open("/bin/cat.wasm", Open::default()) + .await? + .read(0, changed.len()) + .await?, + changed + ); // An unbundled world remains usable by process tests and file-only callers. let unbundled = tempfile::tempdir()?; let world = World::open(unbundled.path())?; diff --git a/host/tests/process.rs b/host/tests/process.rs index 3332f75..d980b7a 100644 --- a/host/tests/process.rs +++ b/host/tests/process.rs @@ -9,6 +9,39 @@ use std::{ sync::{Arc, Once}, time::Duration, }; +#[test] +fn world_copies_programs_and_nested_source_only_once() -> anyhow::Result<()> { + let temp = tempfile::tempdir()?; + let bundle = temp.path().join("bundle"); + std::fs::create_dir_all(bundle.join("bin"))?; + std::fs::create_dir_all(bundle.join("src/sausage/.git"))?; + std::fs::write(bundle.join("bin/probe.wasm"), b"program")?; + std::fs::write( + bundle.join("src/sausage/.git/HEAD"), + b"ref: refs/heads/main\n", + )?; + std::fs::write(bundle.join("src/sausage/README.md"), b"source")?; + let path = temp.path().join("world"); + let _world = World::open_with_userland(&path, Some(&bundle))?; + assert_eq!( + std::fs::read(path.join("world/bin/probe.wasm"))?, + b"program" + ); + assert_eq!( + std::fs::read(path.join("world/src/sausage/README.md"))?, + b"source" + ); + assert!(path.join("world/src/sausage/.git/HEAD").is_file()); + std::fs::write(path.join("world/src/sausage/README.md"), b"agent patch")?; + let _world = World::open_with_userland(&path, Some(Path::new("/missing-bundle")))?; + assert_eq!( + std::fs::read(path.join("world/src/sausage/README.md"))?, + b"agent patch" + ); + assert!(!path.join("host/compile-cache").exists()); + Ok(()) +} + mod features { //! Acceptance coverage for preview1, identity, inherited IO, services, HTTP, and bundle aliases. use super::*; @@ -260,28 +293,32 @@ mod features { empty(&processes).await; } #[tokio::test(flavor = "multi_thread")] - async fn relative_aliases_share_compilation() { + async fn independent_copies_share_compilation() { let _serial = SERIAL.lock().await; let (temp, runtime, _) = world(); let bin = temp.path().join("world/bin"); - for name in ["true", "echo"] { - std::os::unix::fs::symlink("cat", bin.join(name)).unwrap(); + for name in ["read-a.wasm", "read-b.wasm"] { + std::fs::copy(bin.join("cat.wasm"), bin.join(name)).unwrap(); } assert_eq!( - run(&runtime, Command::Argv(vec!["/bin/true".into()]), "/") - .await - .0, + run( + &runtime, + Command::Argv(vec!["/bin/read-a".into(), "/f".into()]), + "/" + ) + .await + .0, 0 ); assert_eq!(runtime.compiled_programs(), 1); let (code, out, _) = run( &runtime, - Command::Argv(vec!["/bin/echo".into(), "shared".into()]), + Command::Argv(vec!["/bin/read-b".into(), "/f".into()]), "/", ) .await; assert_eq!(code, 0); - assert_eq!(out, b"shared\n"); + assert_eq!(out, b"root\n"); assert_eq!(runtime.compiled_programs(), 1); } #[tokio::test(flavor = "multi_thread")] @@ -464,13 +501,17 @@ fn world() -> (tempfile::TempDir, Arc, sausage::process::Processes) { let world = World::open(temp.path()).unwrap(); let cache = Path::new(env!("CARGO_TARGET_TMPDIR")).join("process-host/compile-cache"); std::fs::create_dir_all(&cache).unwrap(); - std::os::unix::fs::symlink(cache, world.host_dir.join("compile-cache")).unwrap(); + let processes = world.processes.clone(); fixture(&temp.path().join("world")); let artifacts = repo().join("userland/target/wasm32-wasip2/release"); let bin = temp.path().join("world/bin"); for tool in ["cat", "tr", "ls", "wc", "yes", "head", "sleep"] { - std::fs::copy(artifacts.join("sausage-coreutils.wasm"), bin.join(tool)).unwrap(); + std::fs::copy( + artifacts.join(format!("{tool}.wasm")), + bin.join(format!("{tool}.wasm")), + ) + .unwrap(); } for tool in ["ps", "kill"] { std::fs::copy(artifacts.join("sausage-procps.wasm"), bin.join(tool)).unwrap(); @@ -491,7 +532,11 @@ fn world() -> (tempfile::TempDir, Arc, sausage::process::Processes) { bin.join("git"), ) .unwrap(); - (temp, Runtime::new(world).unwrap(), processes) + ( + temp, + Runtime::new_with_cache(world, &cache).unwrap(), + processes, + ) } async fn run(runtime: &Arc, command: Command, cwd: &str) -> (i32, Vec, Vec) { let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); diff --git a/userland/brush/build.nu b/userland/brush/build.nu index 46213f3..da7908b 100644 --- a/userland/brush/build.nu +++ b/userland/brush/build.nu @@ -18,6 +18,12 @@ def main [] { if (^git -C $source rev-parse HEAD | str trim) != $revision { error make {msg: 'unexpected brush revision'} } let core = ($source | path join brush-core) copy-if-changed ($here | path join process.rs) ($core | path join src sys wasm process.rs) + copy-if-changed ($here | path join lookup.rs) ($core | path join src sys wasm sausage-lookup.rs) + let fs = ($core | path join src sys wasm fs.rs) + let fs_text = (open --raw $fs) + if not ($fs_text | str contains 'include!("sausage-lookup.rs")') { + $fs_text + "\ninclude!(\"sausage-lookup.rs\");\n" | save --force $fs + } mkdir ($core | path join sausage-process) let wit = ($here | path join .. .. host wit | path expand) copy-if-changed ($wit | path join process.wit) ($core | path join sausage-process process.wit) diff --git a/userland/brush/lookup.rs b/userland/brush/lookup.rs new file mode 100644 index 0000000..260f3f8 --- /dev/null +++ b/userland/brush/lookup.rs @@ -0,0 +1,28 @@ +/// Resolve ordinary files before the WASI suffix in each PATH folder. +pub fn resolve_executable(path: std::path::PathBuf) -> Option { + if path.is_file() { + return Some(path); + } + let mut name = path.into_os_string(); + name.push(".wasm"); + let path = std::path::PathBuf::from(name); + path.is_file().then_some(path) +} +pub(crate) fn resolve_executable_path( + path: std::path::PathBuf, + working_dir: &std::path::Path, +) -> std::path::PathBuf { + let absolute = working_dir.join(&path); + if absolute.is_file() { + return path; + } + let mut name = absolute.into_os_string(); + name.push(".wasm"); + if std::path::PathBuf::from(name).is_file() { + let mut name = path.into_os_string(); + name.push(".wasm"); + name.into() + } else { + path + } +} diff --git a/userland/bundle.nu b/userland/bundle.nu index fa8e28b..f743b15 100644 --- a/userland/bundle.nu +++ b/userland/bundle.nu @@ -1,8 +1,9 @@ #!/usr/bin/env nu -# Build every workspace CLI and assemble the read-only /bin bundle. +# Build independent commands and a committed source snapshot of their repository. def main [destination?: path] { let here = $env.FILE_PWD - let destination = ($destination | default ($here | path join .. target userland bin) | path expand) + let destination = ($destination | default ($here | path join .. target userland bundle) | path expand) + let bin = ($destination | path join bin) let object = ($here | path join cwd-init init.o) let sources = [($here | path join cwd-init init.rs) ($here | path join cwd-init exit.wit) ($here | path join cwd-init build.nu)] if not ($object | path exists) or ($sources | any {|source| (ls $source | get modified.0) > (ls $object | get modified.0) }) { @@ -14,23 +15,46 @@ def main [destination?: path] { if $env.LAST_EXIT_CODE != 0 { error make {msg: 'userland build failed'} } ^nu ($here | path join brush build.nu) if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush build failed'} } - mkdir $destination + mkdir $bin for package in ($metadata.packages | where {|p| $p.id in $metadata.workspace_members }) { let tools = ($package.manifest_path | path dirname | path join tools.txt) for binary in ($package.targets | where {|t| 'bin' in $t.kind }) { let source = ($metadata.target_directory | path join wasm32-wasip2 release $'($binary.name).wasm') let names = if ($tools | path exists) { open --raw $tools | lines | where {|s| ($s | str trim) != '' } } else { [$binary.name] } for name in $names { - ^ln -f $source ($destination | path join $name) - if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle link failed: ($name)'} } + ^cp --remove-destination $source ($bin | path join $'($name).wasm') + if $env.LAST_EXIT_CODE != 0 { error make {msg: $'bundle copy failed: ($name)'} } } } } let brush_metadata = (^cargo metadata --manifest-path ($here | path join brush source Cargo.toml) --no-deps --format-version 1 | from json) let brush = ($brush_metadata.target_directory | path join wasm32-wasip2 release brush.wasm) for name in [sh bash] { - ^ln -f $brush ($destination | path join $name) - if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush link failed'} } + ^cp --remove-destination $brush ($bin | path join $'($name).wasm') + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'brush copy failed'} } + } + let repo = ($here | path join .. | path expand) + let revision = (^git -C $repo rev-parse HEAD | str trim) + let snapshot = ($destination | path join src sausage) + if ($snapshot | path exists) { rm --recursive --force $snapshot } + mkdir $snapshot + let archive = ($destination | path join source.tar) + ^git -C $repo archive --output $archive HEAD host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source archive failed'} } + ^tar -xf $archive -C $snapshot + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source extraction failed'} } + rm $archive + ^git -C $snapshot init --quiet --initial-branch=main + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git init failed'} } + ^git -C $snapshot add host vfs userland Cargo.toml Cargo.lock flake.nix flake.lock README.md AGENTS.md .gitignore + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git add failed'} } + with-env { + GIT_AUTHOR_NAME: 'Sausage', GIT_AUTHOR_EMAIL: 'sausage@localhost', + GIT_COMMITTER_NAME: 'Sausage', GIT_COMMITTER_EMAIL: 'sausage@localhost', + GIT_AUTHOR_DATE: '2000-01-01T00:00:00+0000', GIT_COMMITTER_DATE: '2000-01-01T00:00:00+0000' + } { + ^git -C $snapshot commit --no-gpg-sign --quiet -m $'Bundle source ($revision)' + if $env.LAST_EXIT_CODE != 0 { error make {msg: 'source git commit failed'} } } print $destination } diff --git a/userland/coreutils/src/bin/arch.rs b/userland/coreutils/src/bin/arch.rs new file mode 100644 index 0000000..f682a17 --- /dev/null +++ b/userland/coreutils/src/bin/arch.rs @@ -0,0 +1,5 @@ +//! Implements the arch command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("arch").expect("utility supplies localization"); + std::process::exit(uu_arch::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/b2sum.rs b/userland/coreutils/src/bin/b2sum.rs new file mode 100644 index 0000000..d21bbd5 --- /dev/null +++ b/userland/coreutils/src/bin/b2sum.rs @@ -0,0 +1,5 @@ +//! Implements the b2sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("b2sum").expect("utility supplies localization"); + std::process::exit(uu_b2sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/base32.rs b/userland/coreutils/src/bin/base32.rs new file mode 100644 index 0000000..4a0c434 --- /dev/null +++ b/userland/coreutils/src/bin/base32.rs @@ -0,0 +1,5 @@ +//! Implements the base32 command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("base32").expect("utility supplies localization"); + std::process::exit(uu_base32::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/base64.rs b/userland/coreutils/src/bin/base64.rs new file mode 100644 index 0000000..79e0ce9 --- /dev/null +++ b/userland/coreutils/src/bin/base64.rs @@ -0,0 +1,5 @@ +//! Implements the base64 command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("base64").expect("utility supplies localization"); + std::process::exit(uu_base64::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/basename.rs b/userland/coreutils/src/bin/basename.rs new file mode 100644 index 0000000..234fc0a --- /dev/null +++ b/userland/coreutils/src/bin/basename.rs @@ -0,0 +1,5 @@ +//! Implements the basename command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("basename").expect("utility supplies localization"); + std::process::exit(uu_basename::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/basenc.rs b/userland/coreutils/src/bin/basenc.rs new file mode 100644 index 0000000..05c22fb --- /dev/null +++ b/userland/coreutils/src/bin/basenc.rs @@ -0,0 +1,5 @@ +//! Implements the basenc command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("basenc").expect("utility supplies localization"); + std::process::exit(uu_basenc::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/cat.rs b/userland/coreutils/src/bin/cat.rs new file mode 100644 index 0000000..0f4ff1b --- /dev/null +++ b/userland/coreutils/src/bin/cat.rs @@ -0,0 +1,5 @@ +//! Implements the cat command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("cat").expect("utility supplies localization"); + std::process::exit(uu_cat::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/cksum.rs b/userland/coreutils/src/bin/cksum.rs new file mode 100644 index 0000000..58294cd --- /dev/null +++ b/userland/coreutils/src/bin/cksum.rs @@ -0,0 +1,5 @@ +//! Implements the cksum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("cksum").expect("utility supplies localization"); + std::process::exit(uu_cksum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/comm.rs b/userland/coreutils/src/bin/comm.rs new file mode 100644 index 0000000..51ef1e5 --- /dev/null +++ b/userland/coreutils/src/bin/comm.rs @@ -0,0 +1,5 @@ +//! Implements the comm command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("comm").expect("utility supplies localization"); + std::process::exit(uu_comm::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/cp.rs b/userland/coreutils/src/bin/cp.rs new file mode 100644 index 0000000..c320dd0 --- /dev/null +++ b/userland/coreutils/src/bin/cp.rs @@ -0,0 +1,5 @@ +//! Implements the cp command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("cp").expect("utility supplies localization"); + std::process::exit(uu_cp::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/csplit.rs b/userland/coreutils/src/bin/csplit.rs new file mode 100644 index 0000000..8d24215 --- /dev/null +++ b/userland/coreutils/src/bin/csplit.rs @@ -0,0 +1,5 @@ +//! Implements the csplit command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("csplit").expect("utility supplies localization"); + std::process::exit(uu_csplit::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/cut.rs b/userland/coreutils/src/bin/cut.rs new file mode 100644 index 0000000..bc1dbf4 --- /dev/null +++ b/userland/coreutils/src/bin/cut.rs @@ -0,0 +1,5 @@ +//! Implements the cut command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("cut").expect("utility supplies localization"); + std::process::exit(uu_cut::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/date.rs b/userland/coreutils/src/bin/date.rs new file mode 100644 index 0000000..7f95526 --- /dev/null +++ b/userland/coreutils/src/bin/date.rs @@ -0,0 +1,5 @@ +//! Implements the date command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("date").expect("utility supplies localization"); + std::process::exit(uu_date::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/dd.rs b/userland/coreutils/src/bin/dd.rs new file mode 100644 index 0000000..1475ca2 --- /dev/null +++ b/userland/coreutils/src/bin/dd.rs @@ -0,0 +1,5 @@ +//! Implements the dd command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("dd").expect("utility supplies localization"); + std::process::exit(uu_dd::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/dir.rs b/userland/coreutils/src/bin/dir.rs new file mode 100644 index 0000000..76c0d22 --- /dev/null +++ b/userland/coreutils/src/bin/dir.rs @@ -0,0 +1,5 @@ +//! Implements the dir command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("ls").expect("utility supplies localization"); + std::process::exit(uu_dir::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/dircolors.rs b/userland/coreutils/src/bin/dircolors.rs new file mode 100644 index 0000000..aaa7527 --- /dev/null +++ b/userland/coreutils/src/bin/dircolors.rs @@ -0,0 +1,5 @@ +//! Implements the dircolors command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("dircolors").expect("utility supplies localization"); + std::process::exit(uu_dircolors::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/dirname.rs b/userland/coreutils/src/bin/dirname.rs new file mode 100644 index 0000000..3fe4ff4 --- /dev/null +++ b/userland/coreutils/src/bin/dirname.rs @@ -0,0 +1,5 @@ +//! Implements the dirname command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("dirname").expect("utility supplies localization"); + std::process::exit(uu_dirname::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/echo.rs b/userland/coreutils/src/bin/echo.rs new file mode 100644 index 0000000..dd479b5 --- /dev/null +++ b/userland/coreutils/src/bin/echo.rs @@ -0,0 +1,5 @@ +//! Implements the echo command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("echo").expect("utility supplies localization"); + std::process::exit(uu_echo::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/expand.rs b/userland/coreutils/src/bin/expand.rs new file mode 100644 index 0000000..3e0b0ca --- /dev/null +++ b/userland/coreutils/src/bin/expand.rs @@ -0,0 +1,5 @@ +//! Implements the expand command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("expand").expect("utility supplies localization"); + std::process::exit(uu_expand::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/expr.rs b/userland/coreutils/src/bin/expr.rs new file mode 100644 index 0000000..001ec55 --- /dev/null +++ b/userland/coreutils/src/bin/expr.rs @@ -0,0 +1,5 @@ +//! Implements the expr command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("expr").expect("utility supplies localization"); + std::process::exit(uu_expr::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/factor.rs b/userland/coreutils/src/bin/factor.rs new file mode 100644 index 0000000..71cd796 --- /dev/null +++ b/userland/coreutils/src/bin/factor.rs @@ -0,0 +1,5 @@ +//! Implements the factor command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("factor").expect("utility supplies localization"); + std::process::exit(uu_factor::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/false.rs b/userland/coreutils/src/bin/false.rs new file mode 100644 index 0000000..cf3a464 --- /dev/null +++ b/userland/coreutils/src/bin/false.rs @@ -0,0 +1,5 @@ +//! Implements the false command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("false").expect("utility supplies localization"); + std::process::exit(uu_false::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/fmt.rs b/userland/coreutils/src/bin/fmt.rs new file mode 100644 index 0000000..ad6d2ac --- /dev/null +++ b/userland/coreutils/src/bin/fmt.rs @@ -0,0 +1,5 @@ +//! Implements the fmt command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("fmt").expect("utility supplies localization"); + std::process::exit(uu_fmt::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/fold.rs b/userland/coreutils/src/bin/fold.rs new file mode 100644 index 0000000..caa0eb5 --- /dev/null +++ b/userland/coreutils/src/bin/fold.rs @@ -0,0 +1,5 @@ +//! Implements the fold command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("fold").expect("utility supplies localization"); + std::process::exit(uu_fold::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/head.rs b/userland/coreutils/src/bin/head.rs new file mode 100644 index 0000000..eb0fbf4 --- /dev/null +++ b/userland/coreutils/src/bin/head.rs @@ -0,0 +1,5 @@ +//! Implements the head command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("head").expect("utility supplies localization"); + std::process::exit(uu_head::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/hostid.rs b/userland/coreutils/src/bin/hostid.rs new file mode 100644 index 0000000..4966820 --- /dev/null +++ b/userland/coreutils/src/bin/hostid.rs @@ -0,0 +1,5 @@ +//! Implements the hostid command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("hostid").expect("utility supplies localization"); + std::process::exit(uu_hostid::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/join.rs b/userland/coreutils/src/bin/join.rs new file mode 100644 index 0000000..3831cc4 --- /dev/null +++ b/userland/coreutils/src/bin/join.rs @@ -0,0 +1,5 @@ +//! Implements the join command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("join").expect("utility supplies localization"); + std::process::exit(uu_join::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/link.rs b/userland/coreutils/src/bin/link.rs new file mode 100644 index 0000000..956480f --- /dev/null +++ b/userland/coreutils/src/bin/link.rs @@ -0,0 +1,5 @@ +//! Implements the link command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("link").expect("utility supplies localization"); + std::process::exit(uu_link::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/ln.rs b/userland/coreutils/src/bin/ln.rs new file mode 100644 index 0000000..c893121 --- /dev/null +++ b/userland/coreutils/src/bin/ln.rs @@ -0,0 +1,5 @@ +//! Implements the ln command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("ln").expect("utility supplies localization"); + std::process::exit(uu_ln::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/ls.rs b/userland/coreutils/src/bin/ls.rs new file mode 100644 index 0000000..5545e9e --- /dev/null +++ b/userland/coreutils/src/bin/ls.rs @@ -0,0 +1,5 @@ +//! Implements the ls command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("ls").expect("utility supplies localization"); + std::process::exit(uu_ls::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/md5sum.rs b/userland/coreutils/src/bin/md5sum.rs new file mode 100644 index 0000000..735797e --- /dev/null +++ b/userland/coreutils/src/bin/md5sum.rs @@ -0,0 +1,5 @@ +//! Implements the md5sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("md5sum").expect("utility supplies localization"); + std::process::exit(uu_md5sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/mkdir.rs b/userland/coreutils/src/bin/mkdir.rs new file mode 100644 index 0000000..c2f8923 --- /dev/null +++ b/userland/coreutils/src/bin/mkdir.rs @@ -0,0 +1,5 @@ +//! Implements the mkdir command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("mkdir").expect("utility supplies localization"); + std::process::exit(uu_mkdir::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/mktemp.rs b/userland/coreutils/src/bin/mktemp.rs new file mode 100644 index 0000000..71e0781 --- /dev/null +++ b/userland/coreutils/src/bin/mktemp.rs @@ -0,0 +1,5 @@ +//! Implements the mktemp command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("mktemp").expect("utility supplies localization"); + std::process::exit(uu_mktemp::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/mv.rs b/userland/coreutils/src/bin/mv.rs new file mode 100644 index 0000000..f62bfb1 --- /dev/null +++ b/userland/coreutils/src/bin/mv.rs @@ -0,0 +1,5 @@ +//! Implements the mv command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("mv").expect("utility supplies localization"); + std::process::exit(uu_mv::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/nice.rs b/userland/coreutils/src/bin/nice.rs new file mode 100644 index 0000000..b31f52d --- /dev/null +++ b/userland/coreutils/src/bin/nice.rs @@ -0,0 +1,5 @@ +//! Implements the nice command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("nice").expect("utility supplies localization"); + std::process::exit(uu_nice::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/nl.rs b/userland/coreutils/src/bin/nl.rs new file mode 100644 index 0000000..0a50d3c --- /dev/null +++ b/userland/coreutils/src/bin/nl.rs @@ -0,0 +1,5 @@ +//! Implements the nl command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("nl").expect("utility supplies localization"); + std::process::exit(uu_nl::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/nproc.rs b/userland/coreutils/src/bin/nproc.rs new file mode 100644 index 0000000..fcc81f2 --- /dev/null +++ b/userland/coreutils/src/bin/nproc.rs @@ -0,0 +1,5 @@ +//! Implements the nproc command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("nproc").expect("utility supplies localization"); + std::process::exit(uu_nproc::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/numfmt.rs b/userland/coreutils/src/bin/numfmt.rs new file mode 100644 index 0000000..07829ae --- /dev/null +++ b/userland/coreutils/src/bin/numfmt.rs @@ -0,0 +1,5 @@ +//! Implements the numfmt command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("numfmt").expect("utility supplies localization"); + std::process::exit(uu_numfmt::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/od.rs b/userland/coreutils/src/bin/od.rs new file mode 100644 index 0000000..cab0501 --- /dev/null +++ b/userland/coreutils/src/bin/od.rs @@ -0,0 +1,5 @@ +//! Implements the od command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("od").expect("utility supplies localization"); + std::process::exit(uu_od::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/paste.rs b/userland/coreutils/src/bin/paste.rs new file mode 100644 index 0000000..17559db --- /dev/null +++ b/userland/coreutils/src/bin/paste.rs @@ -0,0 +1,5 @@ +//! Implements the paste command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("paste").expect("utility supplies localization"); + std::process::exit(uu_paste::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/pathchk.rs b/userland/coreutils/src/bin/pathchk.rs new file mode 100644 index 0000000..0a919d9 --- /dev/null +++ b/userland/coreutils/src/bin/pathchk.rs @@ -0,0 +1,5 @@ +//! Implements the pathchk command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("pathchk").expect("utility supplies localization"); + std::process::exit(uu_pathchk::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/pr.rs b/userland/coreutils/src/bin/pr.rs new file mode 100644 index 0000000..0ca52d0 --- /dev/null +++ b/userland/coreutils/src/bin/pr.rs @@ -0,0 +1,5 @@ +//! Implements the pr command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("pr").expect("utility supplies localization"); + std::process::exit(uu_pr::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/printenv.rs b/userland/coreutils/src/bin/printenv.rs new file mode 100644 index 0000000..a3f3800 --- /dev/null +++ b/userland/coreutils/src/bin/printenv.rs @@ -0,0 +1,5 @@ +//! Implements the printenv command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("printenv").expect("utility supplies localization"); + std::process::exit(uu_printenv::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/printf.rs b/userland/coreutils/src/bin/printf.rs new file mode 100644 index 0000000..720c8a7 --- /dev/null +++ b/userland/coreutils/src/bin/printf.rs @@ -0,0 +1,5 @@ +//! Implements the printf command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("printf").expect("utility supplies localization"); + std::process::exit(uu_printf::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/ptx.rs b/userland/coreutils/src/bin/ptx.rs new file mode 100644 index 0000000..a5d7fa7 --- /dev/null +++ b/userland/coreutils/src/bin/ptx.rs @@ -0,0 +1,5 @@ +//! Implements the ptx command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("ptx").expect("utility supplies localization"); + std::process::exit(uu_ptx::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/pwd.rs b/userland/coreutils/src/bin/pwd.rs new file mode 100644 index 0000000..c62049f --- /dev/null +++ b/userland/coreutils/src/bin/pwd.rs @@ -0,0 +1,5 @@ +//! Implements the pwd command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("pwd").expect("utility supplies localization"); + std::process::exit(uu_pwd::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/readlink.rs b/userland/coreutils/src/bin/readlink.rs new file mode 100644 index 0000000..5fb109b --- /dev/null +++ b/userland/coreutils/src/bin/readlink.rs @@ -0,0 +1,5 @@ +//! Implements the readlink command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("readlink").expect("utility supplies localization"); + std::process::exit(uu_readlink::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/realpath.rs b/userland/coreutils/src/bin/realpath.rs new file mode 100644 index 0000000..fb4e428 --- /dev/null +++ b/userland/coreutils/src/bin/realpath.rs @@ -0,0 +1,5 @@ +//! Implements the realpath command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("realpath").expect("utility supplies localization"); + std::process::exit(uu_realpath::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/rm.rs b/userland/coreutils/src/bin/rm.rs new file mode 100644 index 0000000..f1a3549 --- /dev/null +++ b/userland/coreutils/src/bin/rm.rs @@ -0,0 +1,5 @@ +//! Implements the rm command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("rm").expect("utility supplies localization"); + std::process::exit(uu_rm::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/rmdir.rs b/userland/coreutils/src/bin/rmdir.rs new file mode 100644 index 0000000..ee5edc1 --- /dev/null +++ b/userland/coreutils/src/bin/rmdir.rs @@ -0,0 +1,5 @@ +//! Implements the rmdir command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("rmdir").expect("utility supplies localization"); + std::process::exit(uu_rmdir::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/seq.rs b/userland/coreutils/src/bin/seq.rs new file mode 100644 index 0000000..b6ed06d --- /dev/null +++ b/userland/coreutils/src/bin/seq.rs @@ -0,0 +1,5 @@ +//! Implements the seq command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("seq").expect("utility supplies localization"); + std::process::exit(uu_seq::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sha1sum.rs b/userland/coreutils/src/bin/sha1sum.rs new file mode 100644 index 0000000..c601c6e --- /dev/null +++ b/userland/coreutils/src/bin/sha1sum.rs @@ -0,0 +1,5 @@ +//! Implements the sha1sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sha1sum").expect("utility supplies localization"); + std::process::exit(uu_sha1sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sha224sum.rs b/userland/coreutils/src/bin/sha224sum.rs new file mode 100644 index 0000000..96be305 --- /dev/null +++ b/userland/coreutils/src/bin/sha224sum.rs @@ -0,0 +1,5 @@ +//! Implements the sha224sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sha224sum").expect("utility supplies localization"); + std::process::exit(uu_sha224sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sha256sum.rs b/userland/coreutils/src/bin/sha256sum.rs new file mode 100644 index 0000000..d8935f8 --- /dev/null +++ b/userland/coreutils/src/bin/sha256sum.rs @@ -0,0 +1,5 @@ +//! Implements the sha256sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sha256sum").expect("utility supplies localization"); + std::process::exit(uu_sha256sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sha384sum.rs b/userland/coreutils/src/bin/sha384sum.rs new file mode 100644 index 0000000..8897db2 --- /dev/null +++ b/userland/coreutils/src/bin/sha384sum.rs @@ -0,0 +1,5 @@ +//! Implements the sha384sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sha384sum").expect("utility supplies localization"); + std::process::exit(uu_sha384sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sha512sum.rs b/userland/coreutils/src/bin/sha512sum.rs new file mode 100644 index 0000000..2cdcc6e --- /dev/null +++ b/userland/coreutils/src/bin/sha512sum.rs @@ -0,0 +1,5 @@ +//! Implements the sha512sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sha512sum").expect("utility supplies localization"); + std::process::exit(uu_sha512sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/shred.rs b/userland/coreutils/src/bin/shred.rs new file mode 100644 index 0000000..55b5aa8 --- /dev/null +++ b/userland/coreutils/src/bin/shred.rs @@ -0,0 +1,5 @@ +//! Implements the shred command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("shred").expect("utility supplies localization"); + std::process::exit(uu_shred::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/shuf.rs b/userland/coreutils/src/bin/shuf.rs new file mode 100644 index 0000000..641ee63 --- /dev/null +++ b/userland/coreutils/src/bin/shuf.rs @@ -0,0 +1,5 @@ +//! Implements the shuf command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("shuf").expect("utility supplies localization"); + std::process::exit(uu_shuf::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sleep.rs b/userland/coreutils/src/bin/sleep.rs new file mode 100644 index 0000000..0ab1a97 --- /dev/null +++ b/userland/coreutils/src/bin/sleep.rs @@ -0,0 +1,5 @@ +//! Implements the sleep command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sleep").expect("utility supplies localization"); + std::process::exit(uu_sleep::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sort.rs b/userland/coreutils/src/bin/sort.rs new file mode 100644 index 0000000..c89f40e --- /dev/null +++ b/userland/coreutils/src/bin/sort.rs @@ -0,0 +1,5 @@ +//! Implements the sort command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sort").expect("utility supplies localization"); + std::process::exit(uu_sort::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/split.rs b/userland/coreutils/src/bin/split.rs new file mode 100644 index 0000000..737e83f --- /dev/null +++ b/userland/coreutils/src/bin/split.rs @@ -0,0 +1,5 @@ +//! Implements the split command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("split").expect("utility supplies localization"); + std::process::exit(uu_split::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/sum.rs b/userland/coreutils/src/bin/sum.rs new file mode 100644 index 0000000..9b80e68 --- /dev/null +++ b/userland/coreutils/src/bin/sum.rs @@ -0,0 +1,5 @@ +//! Implements the sum command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("sum").expect("utility supplies localization"); + std::process::exit(uu_sum::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/tail.rs b/userland/coreutils/src/bin/tail.rs new file mode 100644 index 0000000..b497795 --- /dev/null +++ b/userland/coreutils/src/bin/tail.rs @@ -0,0 +1,5 @@ +//! Implements the tail command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("tail").expect("utility supplies localization"); + std::process::exit(uu_tail::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/tee.rs b/userland/coreutils/src/bin/tee.rs new file mode 100644 index 0000000..42bae96 --- /dev/null +++ b/userland/coreutils/src/bin/tee.rs @@ -0,0 +1,5 @@ +//! Implements the tee command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("tee").expect("utility supplies localization"); + std::process::exit(uu_tee::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/test.rs b/userland/coreutils/src/bin/test.rs new file mode 100644 index 0000000..8a9f8f4 --- /dev/null +++ b/userland/coreutils/src/bin/test.rs @@ -0,0 +1,5 @@ +//! Implements the test command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("test").expect("utility supplies localization"); + std::process::exit(uu_test::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/touch.rs b/userland/coreutils/src/bin/touch.rs new file mode 100644 index 0000000..d81624a --- /dev/null +++ b/userland/coreutils/src/bin/touch.rs @@ -0,0 +1,5 @@ +//! Implements the touch command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("touch").expect("utility supplies localization"); + std::process::exit(uu_touch::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/tr.rs b/userland/coreutils/src/bin/tr.rs new file mode 100644 index 0000000..ddf2acb --- /dev/null +++ b/userland/coreutils/src/bin/tr.rs @@ -0,0 +1,5 @@ +//! Implements the tr command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("tr").expect("utility supplies localization"); + std::process::exit(uu_tr::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/true.rs b/userland/coreutils/src/bin/true.rs new file mode 100644 index 0000000..63979b6 --- /dev/null +++ b/userland/coreutils/src/bin/true.rs @@ -0,0 +1,5 @@ +//! Implements the true command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("true").expect("utility supplies localization"); + std::process::exit(uu_true::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/truncate.rs b/userland/coreutils/src/bin/truncate.rs new file mode 100644 index 0000000..5bca57f --- /dev/null +++ b/userland/coreutils/src/bin/truncate.rs @@ -0,0 +1,5 @@ +//! Implements the truncate command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("truncate").expect("utility supplies localization"); + std::process::exit(uu_truncate::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/tsort.rs b/userland/coreutils/src/bin/tsort.rs new file mode 100644 index 0000000..7babdb8 --- /dev/null +++ b/userland/coreutils/src/bin/tsort.rs @@ -0,0 +1,5 @@ +//! Implements the tsort command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("tsort").expect("utility supplies localization"); + std::process::exit(uu_tsort::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/tty.rs b/userland/coreutils/src/bin/tty.rs new file mode 100644 index 0000000..aeab75f --- /dev/null +++ b/userland/coreutils/src/bin/tty.rs @@ -0,0 +1,5 @@ +//! Implements the tty command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("tty").expect("utility supplies localization"); + std::process::exit(uu_tty::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/uname.rs b/userland/coreutils/src/bin/uname.rs new file mode 100644 index 0000000..a8c8953 --- /dev/null +++ b/userland/coreutils/src/bin/uname.rs @@ -0,0 +1,5 @@ +//! Implements the uname command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("uname").expect("utility supplies localization"); + std::process::exit(uu_uname::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/unexpand.rs b/userland/coreutils/src/bin/unexpand.rs new file mode 100644 index 0000000..7753075 --- /dev/null +++ b/userland/coreutils/src/bin/unexpand.rs @@ -0,0 +1,5 @@ +//! Implements the unexpand command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("unexpand").expect("utility supplies localization"); + std::process::exit(uu_unexpand::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/uniq.rs b/userland/coreutils/src/bin/uniq.rs new file mode 100644 index 0000000..5891b29 --- /dev/null +++ b/userland/coreutils/src/bin/uniq.rs @@ -0,0 +1,5 @@ +//! Implements the uniq command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("uniq").expect("utility supplies localization"); + std::process::exit(uu_uniq::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/unlink.rs b/userland/coreutils/src/bin/unlink.rs new file mode 100644 index 0000000..0249957 --- /dev/null +++ b/userland/coreutils/src/bin/unlink.rs @@ -0,0 +1,5 @@ +//! Implements the unlink command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("unlink").expect("utility supplies localization"); + std::process::exit(uu_unlink::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/vdir.rs b/userland/coreutils/src/bin/vdir.rs new file mode 100644 index 0000000..c3b426d --- /dev/null +++ b/userland/coreutils/src/bin/vdir.rs @@ -0,0 +1,5 @@ +//! Implements the vdir command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("ls").expect("utility supplies localization"); + std::process::exit(uu_vdir::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/wc.rs b/userland/coreutils/src/bin/wc.rs new file mode 100644 index 0000000..b866eaf --- /dev/null +++ b/userland/coreutils/src/bin/wc.rs @@ -0,0 +1,5 @@ +//! Implements the wc command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("wc").expect("utility supplies localization"); + std::process::exit(uu_wc::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/bin/yes.rs b/userland/coreutils/src/bin/yes.rs new file mode 100644 index 0000000..ac14fcf --- /dev/null +++ b/userland/coreutils/src/bin/yes.rs @@ -0,0 +1,5 @@ +//! Implements the yes command as an independent WASI binary. +fn main() { + uucore::locale::setup_localization("yes").expect("utility supplies localization"); + std::process::exit(uu_yes::uumain(uucore::args_os())); +} \ No newline at end of file diff --git a/userland/coreutils/src/main.rs b/userland/coreutils/src/main.rs deleted file mode 100644 index c77947d..0000000 --- a/userland/coreutils/src/main.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Dispatches the portable uutils 0.12 multicall component by argv[0]. -fn main() { - let argv0 = std::env::args().next().unwrap(); - let name = std::path::Path::new(&argv0) - .file_name() - .unwrap() - .to_str() - .unwrap(); - let name = if name == "[" { "test" } else { name }; - let locale = match name { - "dir" | "vdir" => "ls", - _ => name, - }; - uucore::locale::setup_localization(locale).expect("utility supplies localization"); - let args = uucore::args_os(); - let code = match name { - "arch" => uu_arch::uumain(args), - "base32" => uu_base32::uumain(args), - "base64" => uu_base64::uumain(args), - "basename" => uu_basename::uumain(args), - "basenc" => uu_basenc::uumain(args), - "cat" => uu_cat::uumain(args), - "cksum" => uu_cksum::uumain(args), - "b2sum" => uu_b2sum::uumain(args), - "md5sum" => uu_md5sum::uumain(args), - "nice" => uu_nice::uumain(args), - "sha1sum" => uu_sha1sum::uumain(args), - "sha224sum" => uu_sha224sum::uumain(args), - "sha256sum" => uu_sha256sum::uumain(args), - "sha384sum" => uu_sha384sum::uumain(args), - "sha512sum" => uu_sha512sum::uumain(args), - "comm" => uu_comm::uumain(args), - "cp" => uu_cp::uumain(args), - "csplit" => uu_csplit::uumain(args), - "cut" => uu_cut::uumain(args), - "date" => uu_date::uumain(args), - "dd" => uu_dd::uumain(args), - "dir" => uu_dir::uumain(args), - "dircolors" => uu_dircolors::uumain(args), - "dirname" => uu_dirname::uumain(args), - "echo" => uu_echo::uumain(args), - "expand" => uu_expand::uumain(args), - "expr" => uu_expr::uumain(args), - "factor" => uu_factor::uumain(args), - "false" => uu_false::uumain(args), - "fmt" => uu_fmt::uumain(args), - "fold" => uu_fold::uumain(args), - "head" => uu_head::uumain(args), - "hostid" => uu_hostid::uumain(args), - "join" => uu_join::uumain(args), - "link" => uu_link::uumain(args), - "ln" => uu_ln::uumain(args), - "ls" => uu_ls::uumain(args), - "mkdir" => uu_mkdir::uumain(args), - "mktemp" => uu_mktemp::uumain(args), - "mv" => uu_mv::uumain(args), - "nl" => uu_nl::uumain(args), - "numfmt" => uu_numfmt::uumain(args), - "od" => uu_od::uumain(args), - "paste" => uu_paste::uumain(args), - "pathchk" => uu_pathchk::uumain(args), - "pr" => uu_pr::uumain(args), - "printenv" => uu_printenv::uumain(args), - "printf" => uu_printf::uumain(args), - "ptx" => uu_ptx::uumain(args), - "pwd" => uu_pwd::uumain(args), - "readlink" => uu_readlink::uumain(args), - "realpath" => uu_realpath::uumain(args), - "rm" => uu_rm::uumain(args), - "rmdir" => uu_rmdir::uumain(args), - "seq" => uu_seq::uumain(args), - "shred" => uu_shred::uumain(args), - "shuf" => uu_shuf::uumain(args), - "sleep" => uu_sleep::uumain(args), - "sort" => uu_sort::uumain(args), - "split" => uu_split::uumain(args), - "sum" => uu_sum::uumain(args), - "tail" => uu_tail::uumain(args), - "tee" => uu_tee::uumain(args), - "test" => uu_test::uumain(args), - "touch" => uu_touch::uumain(args), - "tr" => uu_tr::uumain(args), - "true" => uu_true::uumain(args), - "truncate" => uu_truncate::uumain(args), - "tsort" => uu_tsort::uumain(args), - "tty" => uu_tty::uumain(args), - "uname" => uu_uname::uumain(args), - "unexpand" => uu_unexpand::uumain(args), - "uniq" => uu_uniq::uumain(args), - "unlink" => uu_unlink::uumain(args), - "vdir" => uu_vdir::uumain(args), - "wc" => uu_wc::uumain(args), - "yes" => uu_yes::uumain(args), - "nproc" => uu_nproc::uumain(args), - _ => 127, - }; - std::process::exit(code); -} diff --git a/userland/coreutils/tools.txt b/userland/coreutils/tools.txt deleted file mode 100644 index a57aa31..0000000 --- a/userland/coreutils/tools.txt +++ /dev/null @@ -1,78 +0,0 @@ -arch -base32 -base64 -basename -basenc -cat -cksum -b2sum -md5sum -nice -sha1sum -sha224sum -sha256sum -sha384sum -sha512sum -comm -cp -csplit -cut -date -dd -dir -dircolors -dirname -echo -expand -expr -factor -false -fmt -fold -head -hostid -join -link -ln -ls -mkdir -mktemp -mv -nl -numfmt -od -paste -pathchk -pr -printenv -printf -ptx -pwd -readlink -realpath -rm -rmdir -seq -shred -shuf -sleep -sort -split -sum -tail -tee -test -touch -tr -true -truncate -tsort -tty -uname -unexpand -uniq -unlink -vdir -wc -yes -nproc diff --git a/userland/procps/src/main.rs b/userland/procps/src/main.rs index 4ebe58c..c783b03 100644 --- a/userland/procps/src/main.rs +++ b/userland/procps/src/main.rs @@ -5,7 +5,7 @@ mod bindings { fn main() { let args: Vec = std::env::args().collect(); match std::path::Path::new(&args[0]) - .file_name() + .file_stem() .unwrap() .to_str() .unwrap() diff --git a/userland/spawn/src/lib.rs b/userland/spawn/src/lib.rs index 004ee87..8acd6cf 100644 --- a/userland/spawn/src/lib.rs +++ b/userland/spawn/src/lib.rs @@ -41,13 +41,7 @@ impl Command { pub fn stderr(&mut self, mode: Stdio) -> &mut Self { self.stderr = mode; self } /// Resolve PATH and start the component. pub fn spawn(&self) -> io::Result { - let program = if self.program.contains('/') { self.program.clone() } else { - self.env.get("PATH").map(String::as_str).unwrap_or("/usr/local/bin:/bin").split(':') - .map(|dir| self.cwd.join(dir).join(&self.program)) - .find(|p| p.is_file()).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, &self.program[..]))? - .to_string_lossy().into_owned() - }; - let child = host::spawn(&host::Request { program, argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(), + let child = host::spawn(&host::Request { program: self.program.clone(), argv: std::iter::once(self.program.clone()).chain(self.args.clone()).collect(), env: self.env.clone().into_iter().collect(), cwd: self.cwd.to_string_lossy().into_owned(), group: self.group, stdin: self.stdin, stdout: self.stdout, stderr: self.stderr }).map_err(io::Error::other)?; Ok(Child { stdin: matches!(self.stdin, Stdio::Pipe).then(|| child.stdin()), From cd324678fe86b090c45fa3743b90d9f635b24bef Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 6 Nov 2026 20:04:06 +0000 Subject: [PATCH 2/4] Preserve indexed executable modes in WASI git --- userland/git/src/changes.rs | 18 +++++++++++++++++- userland/git/src/staging.rs | 4 ++-- userland/git/test.nu | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/userland/git/src/changes.rs b/userland/git/src/changes.rs index 5c26ca3..b91fdfe 100644 --- a/userland/git/src/changes.rs +++ b/userland/git/src/changes.rs @@ -238,6 +238,22 @@ pub fn status(repo: &gix::Repository) -> Result { .map_err(|e| error("status", e))?; for item in iter { let item = item.map_err(|e| error("status", e))?; + #[cfg(target_os = "wasi")] + if matches!( + &item, + Item::Modification { + status: gix::status::plumbing::index_as_worktree::EntryStatus::Change( + gix::status::plumbing::index_as_worktree::Change::Modification { + executable_bit_changed: true, + content_change: None, + .. + } + ), + .. + } + ) { + continue; + } let path = item.rela_path().to_str_lossy().into_owned(); let Some(summary) = item.summary() else { continue; @@ -332,7 +348,7 @@ fn worktree_file( } else { entry.mode }; - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "wasi")))] let mode = if mode != 0o120000 && caps.executable_bit { use std::os::unix::fs::PermissionsExt; if meta.permissions().mode() & 0o111 != 0 { diff --git a/userland/git/src/staging.rs b/userland/git/src/staging.rs index d5d873d..a0e59a3 100644 --- a/userland/git/src/staging.rs +++ b/userland/git/src/staging.rs @@ -229,7 +229,7 @@ fn work_entry( Mode::SYMLINK, ) } else if m.is_file() { - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "wasi")))] let mode = { use std::os::unix::fs::PermissionsExt; if m.permissions().mode() & 0o111 != 0 { @@ -238,7 +238,7 @@ fn work_entry( Mode::FILE } }; - #[cfg(not(unix))] + #[cfg(any(not(unix), target_os = "wasi"))] let mode = if previous == Some(Mode::FILE_EXECUTABLE) { Mode::FILE_EXECUTABLE } else { diff --git a/userland/git/test.nu b/userland/git/test.nu index 5b57829..eb0c78b 100644 --- a/userland/git/test.nu +++ b/userland/git/test.nu @@ -268,6 +268,24 @@ def main [component: path] { print $'($args | str join " "): (if $equal {"PASS documented usage"} else {"FAIL documented usage"}) wasi=($result.exit_code)' if not $equal { $failures += 1; print ($result | to json) } } + # WASI checkout and staging retain an existing executable file's indexed mode. + let executable = ($root | path join executable) + mkdir $executable + ^git -C $executable init -q -b main + "#!/bin/sh\necho executable\n" | save ($executable | path join run.sh) + ^chmod +x ($executable | path join run.sh) + ^git -C $executable add run.sh + ^git -C $executable commit --no-gpg-sign -q -m executable + rm ($executable | path join run.sh) + mut executable_ok = true + for args in [[checkout -q HEAD -- run.sh] [status --short] [diff] [add run.sh] [diff --cached] [status --short]] { + let result = (^wasmtime run -S http=y -S cli-exit-with-code=y --dir $'($executable)::/mode' $component -C /mode ...$args | complete) + if $result.exit_code != 0 or $result.stdout != '' { $executable_ok = false; print ($result | to json) } + } + let indexed = (^git -C $executable ls-files --stage run.sh | complete) + $executable_ok = ($executable_ok and $indexed.exit_code == 0 and ($indexed.stdout | str starts-with '100755 ')) + print $'executable checkout stays clean: (if $executable_ok {"PASS"} else {"FAIL"})' + if not $executable_ok { $failures += 1 } # A merge tree that differs from both parents requires a combined diff. 'combined resolution\n' | save --force ($root | path join native/repo/file.txt) let native = ($root | path join native/repo) From e8732ede15ee0586b17bc632518c719000a104ff Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 6 Nov 2026 21:39:22 +0000 Subject: [PATCH 3/4] Limit background compilation and reduce cold compile cost --- host/src/process/cache.rs | 115 ++------------------------------------ host/src/process/mod.rs | 6 +- 2 files changed, 6 insertions(+), 115 deletions(-) diff --git a/host/src/process/cache.rs b/host/src/process/cache.rs index 972feea..bc81b55 100644 --- a/host/src/process/cache.rs +++ b/host/src/process/cache.rs @@ -54,13 +54,10 @@ pub(super) struct Compiler { } impl Compiler { pub fn new(directory: &Path) -> anyhow::Result> { - Self::with_opt(directory, wasmtime::OptLevel::Speed) - } - fn with_opt(directory: &Path, opt: wasmtime::OptLevel) -> anyhow::Result> { let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let mut config = wasmtime::Config::new(); config.epoch_interruption(true); - config.cranelift_opt_level(opt); + config.cranelift_opt_level(wasmtime::OptLevel::None); let mut cache = wasmtime::CacheConfig::new(); cache.with_directory(directory); config.cache(Some(wasmtime::Cache::new(cache)?)); @@ -145,6 +142,8 @@ pub(super) fn precompile( ) -> tokio::task::JoinHandle> { tokio::spawn(async move { let mut tasks = tokio::task::JoinSet::new(); + // Leave CPU and file bandwidth for commands during startup. + let slots = Arc::new(tokio::sync::Semaphore::new(2)); for folder in ["/bin", "/usr/local/bin"] { let entries = match root.list(folder).await { Ok(entries) => entries, @@ -155,7 +154,9 @@ pub(super) fn precompile( let root = root.clone(); let compiler = compiler.clone(); let path = format!("{folder}/{}", entry.name); + let slots = slots.clone(); tasks.spawn(async move { + let _permit = slots.acquire().await?; let file = root .open( &path, @@ -190,112 +191,6 @@ mod tests { use crate::exec::{Command, Exec, Options}; use std::{future::Future, sync::atomic::Ordering}; - #[tokio::test(flavor = "multi_thread")] - #[ignore = "Temporary T15 timing probe; remove before review"] - async fn measure_world() { - let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle"); - let temp = tempfile::tempdir().unwrap(); - for (index, script) in ["echo hi | tr a-z A-Z", "git --version", "rg --version"] - .into_iter() - .enumerate() - { - let cache = temp.path().join(format!("cache-{index}")); - let start = std::time::Instant::now(); - let world = crate::world::World::open_with_userland( - &temp.path().join(format!("cold-{index}")), - Some(&bundle), - ) - .unwrap(); - let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap(); - let mut output = |_: crate::exec::Stream, _: &[u8]| {}; - assert_eq!( - runtime - .exec( - Command::Shell(script.into()), - Options::default(), - &mut output - ) - .await - .unwrap(), - 0 - ); - println!( - "profile={} state=cold command={script:?} seconds={:.6}", - if cfg!(debug_assertions) { - "debug" - } else { - "release" - }, - start.elapsed().as_secs_f64() - ); - let count = runtime.precompile().await.unwrap().unwrap(); - println!( - "background commands={count} seconds={:.6}", - start.elapsed().as_secs_f64() - ); - let start = std::time::Instant::now(); - assert_eq!( - runtime - .exec( - Command::Shell(script.into()), - Options::default(), - &mut output - ) - .await - .unwrap(), - 0 - ); - println!( - "state=same-host command={script:?} seconds={:.6}", - start.elapsed().as_secs_f64() - ); - drop(runtime); - let start = std::time::Instant::now(); - let world = crate::world::World::open_with_userland( - &temp.path().join(format!("warm-{index}")), - Some(&bundle), - ) - .unwrap(); - let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap(); - assert_eq!( - runtime - .exec( - Command::Shell(script.into()), - Options::default(), - &mut output - ) - .await - .unwrap(), - 0 - ); - println!( - "state=fresh-warm command={script:?} seconds={:.6}", - start.elapsed().as_secs_f64() - ); - runtime.precompile().await.unwrap().unwrap(); - } - } - - #[test] - #[ignore = "Temporary T15 timing probe; remove before review"] - fn measure_optimizer() { - let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle/bin"); - let temp = tempfile::tempdir().unwrap(); - for opt in [wasmtime::OptLevel::Speed, wasmtime::OptLevel::None] { - let compiler = Compiler::with_opt(&temp.path().join(format!("{opt:?}")), opt).unwrap(); - for name in ["sh", "tr", "git", "rg"] { - let bytes = std::fs::read(bundle.join(format!("{name}.wasm"))).unwrap(); - let start = std::time::Instant::now(); - compiler.compile(&bytes).unwrap(); - println!( - "optimizer={opt:?} program={name} bytes={} compile_seconds={:.6}", - bytes.len(), - start.elapsed().as_secs_f64() - ); - } - } - } - #[tokio::test(flavor = "multi_thread")] async fn command_joins_precompile_even_after_waiter_cancellation() { let temp = tempfile::tempdir().unwrap(); diff --git a/host/src/process/mod.rs b/host/src/process/mod.rs index fd525e8..2db6095 100644 --- a/host/src/process/mod.rs +++ b/host/src/process/mod.rs @@ -167,13 +167,9 @@ impl Runtime { entries: Mutex::new(HashMap::new()), next: AtomicU32::new(1), }); - runtime.precompile(); + cache::precompile(runtime.compiler.clone(), runtime.world.root.clone()); Ok(runtime) } - /// Join startup precompilation of both command folders through the shared content slots. - pub fn precompile(&self) -> tokio::task::JoinHandle> { - cache::precompile(self.compiler.clone(), self.world.root.clone()) - } /// Number of distinct program contents compiled by this runtime. pub fn compiled_programs(&self) -> usize { self.cache.lock().unwrap().len() From b3d91d37e3b8c85152582f6d455fc6b6898396d7 Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 6 Nov 2026 22:18:33 +0000 Subject: [PATCH 4/4] process: keep Cranelift's default optimization level Compiles are cached per content and guests run many times; OptLevel::None saved 12.7% compile time but slows every run. --- host/src/process/cache.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/host/src/process/cache.rs b/host/src/process/cache.rs index bc81b55..df70a73 100644 --- a/host/src/process/cache.rs +++ b/host/src/process/cache.rs @@ -57,7 +57,8 @@ impl Compiler { let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let mut config = wasmtime::Config::new(); config.epoch_interruption(true); - config.cranelift_opt_level(wasmtime::OptLevel::None); + // Keep Cranelift's default optimization: compiles are cached per content, and guests run + // many times. OptLevel::None saved only 12.7% of compile time (T15) but slows every run. let mut cache = wasmtime::CacheConfig::new(); cache.with_directory(directory); config.cache(Some(wasmtime::Cache::new(cache)?));