AndSDev opened issue #14205:
Feature
Add a
Config/Tunablesoption that exempts the synthesizedModuleStartupfunction from fuel metering, restoring the ≤ v36 behavior whereInstance::newwithstore.set_fuel(0)succeeds for modules that have no(start ...)function. Since #13487 ("Move most module initialization to compiled code"), module initialization (globals, segments, tables, etc.) runs as compiled Wasm insideModuleStartup(crates/cranelift/src/func_environ.rs) and is intentionally fuel-metered, soInstance::newnow consumes fuel even for modules with no(start ...)and a small fuel budget makes instantiation itself trap withTrap::OutOfFuel. Fuel is meant to bound Wasm function execution, not to forbid instantiation.Example
A module that requires a startup function but has no
(start ...)— here a passive element segment, which is initialized once during instantiation:(module (func $f (result i32) i32.const 42) (table 1 funcref) (elem $passive func $f))// Cargo.toml: [dependencies] wasmtime = "48" use wasmtime::*; const MODULE: &str = r#" (module (func $f (result i32) i32.const 42) (table 1 funcref) (elem $passive func $f)) "#; fn main() -> Result<()> { let mut config = Config::new(); config.consume_fuel(true); let engine = Engine::new(&config)?; let module = Module::new(&engine, MODULE)?; // No `(start ...)`, but the passive element segment forces Wasmtime to // synthesize a `ModuleStartup` function, and that function is fuel-metered. for fuel in [0, 1, 2] { let mut store = Store::new(&engine, ()); store.set_fuel(fuel)?; match Instance::new(&mut store, &module, &[]) { Ok(_) => { let consumed = fuel - store.get_fuel()?; println!("fuel={fuel}: instantiation succeeded, consumed {consumed} unit(s)"); } Err(e) => println!("fuel={fuel}: instantiation failed: {e}"), } } Ok(()) }Output (reproduced on
mainatf1412a598f, Wasmtime 48.0.0, Linux x86_64):fuel=0: instantiation failed: wasm trap: all fuel consumed by WebAssembly fuel=1: instantiation failed: wasm trap: all fuel consumed by WebAssembly fuel=2: instantiation succeeded, consumed 1 unit(s)
fuel=0traps insideInstance::neweven though the module has no(start ...), andfuel=1traps as well because the startup function's flat entry charge of1is mandatory; onlyfuel=2succeeds, showing that instantiation consumes exactly 1 unit for this module. In ≤ v36,set_fuel(0)+Instance::newsucceeded here, with fuel spent only when running an exported function. A module that needs no startup function still instantiates fine atfuel=0today.Why a cost parameter alone is insufficient
With
store.set_fuel(0)the fuel counter is0.fuel_check(func_environ.rs:626) traps once the counter becomes>= 0, andfuel_function_entryruns that check plus the mandatory initialfuel_consumed: 1(func_environ.rs:296) at the entry of every compiled function, includingModuleStartup. So even setting every startup cost to0still leaves0 + 1 >= 0→ trap. Only skipping the fuel entry/exit handling forModuleStartuprestores the v36 behavior.Benefit
- v36 compatibility: instantiation with
set_fuel(0)works again. Today it traps for any module whose initialization cannot be constant-folded or precomputed (measured: a passiveelem, a complicated global, and an activeexternreftable — none with(start ...)— each consume 1 unit of fuel onInstance::new, and trap when the budget is exhausted). Fuel should bound Wasm function execution, not forbid instantiation.Implementation
- Add
Config::consume_fuel_during_module_initialization(bool)(or an equivalentTunablesfield), defaulting totruefor current behavior. Whenfalse, compileFuncKey::ModuleStartupwith fuel accounting disabled for that function: skipfuel_function_entry/fuel_function_exitso it neither charges the flat entry cost (fuel_consumed: 1) nor runs the entry>= 0check that makesset_fuel(0)trap.- The
(start ...)call itself remains ordinary Wasm (it consumed fuel in v36 as well); scope the exemption to the synthesized initialization body.- Tests:
set_fuel(0)+Instance::newon a module that needs a startup function (e.g. a passiveelem); option on → traps, option off → succeeds.Alternatives
- Configurable startup cost: add a dedicated cost knob for the startup function, e.g.
OperatorCost::module_startup(a flat per-instance charge, defaulting to1to preserve current metering), replacing the hardcodedfuel_consumed: 1entry charge thatModuleStartupcurrently pays. Setting it to0restores the v36set_fuel(0)behavior, and values> 0keep metering while letting embedders charge a custom amount for instantiation-time work — strictly more flexible than the boolean flag. The required crutch: zeroing the cost is not enough on its own, becausefuel_check(func_environ.rs:626) traps once the counter becomes>= 0andfuel_function_entryruns that check unconditionally atModuleStartupentry. So the0case must also skipfuel_function_entry/fuel_function_exitforFuncKey::ModuleStartup— the same surgical skip the boolean flag needs, but keyed oncost == 0rather than a separate option.
alexcrichton added the wasmtime:fuel label to Issue #14205.
fitzgen commented on issue #14205:
FWIW, we don't make any hard guarantees for fuel consumption across versions of Wasmtime. Happy to have a new config option for disabling fuel consumption during instantiation, however.
Last updated: Aug 30 2026 at 09:07 UTC