bytecodealliance / bytecodealliance/wasmtime
Exempt module instantiation from fuel (v36-compatible `set_fuel(0)`)
- Dominant language
- Rust
- Stars
- 18.6k
- Forks
- 1.8k
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 126
Description
#### Feature
Add a `Config`/`Tunables` option that exempts the synthesized `ModuleStartup` function from fuel metering, restoring the ≤ v36 behavior where `Instance::new` with `store.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 inside `ModuleStartup` (`crates/cranelift/src/func_environ.rs`) and is intentionally fuel-metered, so `Instance::new` now consumes fuel even for modules with no `(start ...)` and a small fuel budget makes instantiation itself trap with `Trap::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:
```wat
(module
(func $f (result i32) i32.const 42)
(table 1 funcref)
(elem $passive func $f))
```
```rust
// 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 `main` at `f1412a598f`, 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=0` traps inside `Instance::new` even though the module has no `(start ...)`, and `fuel=1` traps as well because the startup function's flat entry charge of `1` is mandatory; only `fuel=2` succeeds, showing that instantiation consumes exactly 1 unit for this module. In ≤ v36, `set_fuel(0)` + `Instance::new` succeeded here, with fuel spent only when running an exported function. A module that needs no startup function still instantiates fine at `fuel=0` today.
##### Why a cost parameter alone is insufficient
With `store.set_fuel(0)` the fuel counter is `0`. `fuel_check` (`func_environ.rs:626`) traps once the counter becomes `>= 0`, and `fuel_function_entry` runs that check plus the mandatory initial `fuel_consumed: 1` (`func_environ.rs:296`) at the entry of every compiled function, including `ModuleStartup`. So even setting every startup cost to `0` still leaves `0 + 1 >= 0` → trap. Only skipping the fuel entry/exit handling for `ModuleStartup` restores 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 passive `elem`, a complicated global, and an active `externref` table — none with `(start ...)` — each consume 1 unit of fuel on `Instance::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 equivalent `Tunables` field), defaulting to `true` for current behavior. When `false`, compile `FuncKey::ModuleStartup` with fuel accounting disabled for that function: skip `fuel_function_entry`/`fuel_function_exit` so it neither charges the flat entry cost (`fuel_consumed: 1`) nor runs the entry `>= 0` check that makes `set_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::new` on a module that needs a startup function (e.g. a passive `elem`); 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 to `1` to preserve current metering), replacing the hardcoded `fuel_consumed: 1` entry charge that `ModuleStartup` currently pays. Setting it to `0` restores the v36 `set_fuel(0)` behavior, and values `> 0` keep 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, because `fuel_check` (`func_environ.rs:626`) traps once the counter becomes `>= 0` and `fuel_function_entry` runs that check unconditionally at `ModuleStartup` entry. So the `0` case must also skip `fuel_function_entry`/`fuel_function_exit` for `FuncKey::ModuleStartup` — the same surgical skip the boolean flag needs, but keyed on `cost == 0` rather than a separate option.
Contributor guide
Research direction
Start in crates/cranelift/src/func_environ.rs, especially fuel_function_entry, fuel_function_exit, fuel_check, and the handling of FuncKey::ModuleStartup. Trace how Config or Tunables reaches compilation, then add coverage for a passive elem module with set_fuel(0), verifying the current option traps and the exemption allows Instance::new to succeed. Confirm that an explicit (start ...) call remains fuel-metered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, compilers
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100