From 6d88104f28636acdbe8f94ab7f28c32e1994b13d Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 10:26:41 +0100 Subject: [PATCH] feat(runtime): add wasmtime-runtime feature gate for real Wasm execution Cargo.toml: - New feature: wasmtime-runtime = [dep:wasmtime, dep:wasmtime-wasi] - Default is off so the normal build remains lightweight. - wasmtime 30 and wasmtime-wasi 30 added as optional dependencies. src/main.rs: - run_module(wasm_path) replaces the inline stub. - cfg(not(feature = wasmtime-runtime)): prints READY and returns. Preserves all existing test and development behaviour unchanged. - cfg(feature = wasmtime-runtime): creates a Wasmtime Engine + Module, builds a WASI linker with inherited stdout/stderr, prints READY, then instantiates the module and calls _start. READY is printed before _start so weft-appd can record the session as Running before the app enters its event loop. The production service binary is built with: cargo build -p weft-runtime --release --features wasmtime-runtime --- crates/weft-runtime/Cargo.toml | 6 +++++ crates/weft-runtime/src/main.rs | 44 ++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/crates/weft-runtime/Cargo.toml b/crates/weft-runtime/Cargo.toml index 263b810..e636340 100644 --- a/crates/weft-runtime/Cargo.toml +++ b/crates/weft-runtime/Cargo.toml @@ -8,7 +8,13 @@ rust-version.workspace = true name = "weft-runtime" path = "src/main.rs" +[features] +default = [] +wasmtime-runtime = ["dep:wasmtime", "dep:wasmtime-wasi"] + [dependencies] anyhow = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +wasmtime = { version = "30", optional = true } +wasmtime-wasi = { version = "30", optional = true } diff --git a/crates/weft-runtime/src/main.rs b/crates/weft-runtime/src/main.rs index bf2065d..1dcb8e0 100644 --- a/crates/weft-runtime/src/main.rs +++ b/crates/weft-runtime/src/main.rs @@ -29,12 +29,8 @@ fn main() -> anyhow::Result<()> { anyhow::bail!("app.wasm not found at {}", wasm_path.display()); } - // TODO: Load wasm_path into a Wasmtime Engine and run the module. - // Until Wasmtime is integrated, print READY and exit cleanly so that - // weft-appd can complete the session lifecycle in tests and development. - tracing::info!(session_id, %app_id, wasm = %wasm_path.display(), "Wasmtime integration pending"); - - println!("READY"); + tracing::info!(session_id, %app_id, wasm = %wasm_path.display(), "executing module"); + run_module(&wasm_path)?; tracing::info!(session_id, %app_id, "exiting"); Ok(()) @@ -51,6 +47,42 @@ fn resolve_package(app_id: &str) -> anyhow::Result { anyhow::bail!("package '{}' not found in any package store", app_id) } +#[cfg(not(feature = "wasmtime-runtime"))] +fn run_module(_wasm_path: &std::path::Path) -> anyhow::Result<()> { + println!("READY"); + Ok(()) +} + +#[cfg(feature = "wasmtime-runtime")] +fn run_module(wasm_path: &std::path::Path) -> anyhow::Result<()> { + use anyhow::Context as _; + use wasmtime::{Config, Engine, Module, Store}; + + let engine = Engine::new(&Config::default()).context("create Wasmtime engine")?; + let module = Module::from_file(&engine, wasm_path).context("load Wasm module")?; + + let mut linker: wasmtime::Linker = wasmtime::Linker::new(&engine); + wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).context("add WASI to linker")?; + + let wasi = wasmtime_wasi::WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build(); + let mut store = Store::new(&engine, wasi); + + println!("READY"); + + let instance = linker + .instantiate(&mut store, &module) + .context("instantiate module")?; + let start = instance + .get_typed_func::<(), ()>(&mut store, "_start") + .context("get _start export")?; + start.call(&mut store, ()).context("call _start")?; + + Ok(()) +} + fn package_store_roots() -> Vec { if let Ok(explicit) = std::env::var("WEFT_APP_STORE") { return vec![PathBuf::from(explicit)];