AndSDev opened issue #14203:
Test Case
A module whose only "complicated" global initializer is a three-op extended const-expr.
ConstExpr::const_evalonly folds a single*.constop, soi32.addis deferred to the synthesized module-startup function (FuncKey::ModuleStartup):(module (global i32 (i32.add (i32.const 1) (i32.const 2))) (export "g2" (global 0)))The module declares no
startfunction.Steps to Reproduce
// Cargo.toml: [dependencies] wasmtime = "48" use wasmtime::*; const MODULE: &str = r#"(module (global i32 (i32.add (i32.const 1) (i32.const 2))) (export "g2" (global 0)))"#; const MODULE_WITH_START: &str = r#"(module (func $s) (start $s) (global i32 (i32.add (i32.const 1) (i32.const 2))) (export "g2" (global 0)))"#; fn measure(wat: &str) -> Result<u64> { let mut config = Config::new(); config.consume_fuel(true); let engine = Engine::new(&config)?; let module = Module::new(&engine, wat)?; let mut store = Store::new(&engine, ()); store.set_fuel(u64::MAX)?; let instance = Instance::new(&mut store, &module, &[])?; let v = instance .get_global(&mut store, "g2") .unwrap() .get(&mut store) .i32(); assert_eq!(v, Some(3), "initializer did not run"); Ok(u64::MAX - store.get_fuel()?) } fn main() -> Result<()> { println!("without start: consumed = {}", measure(MODULE)?); println!("with start: consumed = {}", measure(MODULE_WITH_START)?); Ok(()) }Output:
without start: consumed = 1 with start: consumed = 6Reproduced on
main(ffb04089ea) andrelease-48.0.0, Linux x86_64.Expected Results
The const-expr runs during instantiation, so its work must be metered. Its three ops each cost
1with the defaultOperatorCost, plus the startup function's flat entry cost of1, sowithout startshould consume4. Thewith startcontrol adds one call-site charge in the startup function and one entry charge in$s, hence6.Actual Results
without startconsumes1— only the flat entry cost of the startup function. The three const-expr op charges are accumulated into the translator'sfuel_consumedbuffer but never flushed intofuel_var, so they are discarded when the function exits. Thewith startcontrol consumes6, proving the work is real but only gets metered becausemodule_starthappens to flush the buffer before the call.Versions and Environment
Wasmtime version or commit:
mainatffb04089ea(alsorelease-48.0.0)Operating system: Linux
Architecture: x86_64
Extra Info
With
consume_fuel(true), instantiation-time const-expr work is silently un-metered when there is nostartfunction:translate_const_exprbuffers each op's charge intofuel_consumed, the module-startup function never flushes that buffer intofuel_var, andfuel_function_exitsavesfuel_varwithout flushing the leftover. Astartfunction happens to flush it viamodule_start, which is why the control shows6.Impact: a metering/DoS accounting bypass — large
array.new_fixed, many element-segment expressions, or long initializer chains can run at essentially zero fuel during instantiation. Affects all init paths routed throughtranslate_const_expr(complicated globals, table fills, active/passive element segments, non-static memory-segment offsets).Suggested fix: flush leftover
fuel_consumedbefore savingfuel_var, e.g. callfuel_increment_varat the end of module-startup translation or insidefuel_function_exit.
AndSDev added the bug label to Issue #14203.
alexcrichton added the wasmtime:fuel label to Issue #14203.
Last updated: Aug 30 2026 at 09:07 UTC