Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ Unreleased.

### Changed

* `Config::operator_cost` now applies to operators inside constant expressions
(global initializers, element and data segment offsets, element segment
expressions) and to the synthesized call to a module's `start` function.
Previously each of those was charged 1 fuel unit regardless of the configured
cost.
[#14215](https://github.com/bytecodealliance/wasmtime/pull/14215)

--------------------------------------------------------------------------------

Release notes for previous releases of Wasmtime can be found on the respective
Expand Down
6 changes: 4 additions & 2 deletions crates/cranelift/src/func_environ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6115,7 +6115,9 @@ impl FuncEnvironment<'_> {
// Manuall manage fuel around the call as the `Call` opcode does for
// normal wasm to ensure that it's correctly accounted for.
if self.tunables.consume_fuel {
self.fuel_consumed += 1;
self.fuel_consumed += self.tunables.operator_cost.cost(&Operator::Call {
function_index: func.as_u32(),
});
self.fuel_increment_var(builder);
self.fuel_save_from_var(builder);
}
Expand Down Expand Up @@ -6145,7 +6147,7 @@ impl FuncEnvironment<'_> {
let mut stack = Vec::new();
for op in expr.ops() {
if self.tunables.consume_fuel {
self.fuel_consumed += 1;
self.fuel_consumed += self.tunables.operator_cost.cost(&op.to_operator());
}
match op {
ConstOp::I32Const(i) => {
Expand Down
61 changes: 61 additions & 0 deletions crates/environ/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,67 @@ impl ConstOp {
}
})
}

/// Convert a `ConstOp` back to a `wasmparser::Operator`.
///
/// `RefNull`'s heap type does not round-trip, so only use this where the
/// immediates do not matter, such as looking up an operator's fuel cost.
pub fn to_operator(&self) -> wasmparser::Operator<'static> {
use wasmparser::{AbstractHeapType, HeapType, Ieee32, Ieee64, Operator, V128};
match self {
ConstOp::I32Const(value) => Operator::I32Const { value: *value },
ConstOp::I64Const(value) => Operator::I64Const { value: *value },
ConstOp::F32Const(bits) => Operator::F32Const {
value: Ieee32::from(f32::from_bits(*bits)),
},
ConstOp::F64Const(bits) => Operator::F64Const {
value: Ieee64::from(f64::from_bits(*bits)),
},
ConstOp::V128Const(value) => Operator::V128Const {
value: V128::from(*value as i128),
},
ConstOp::GlobalGet(index) => Operator::GlobalGet {
global_index: index.as_u32(),
},
ConstOp::RefI31 => Operator::RefI31,
ConstOp::RefNull(_) => Operator::RefNull {
hty: HeapType::Abstract {
shared: false,
ty: AbstractHeapType::Any,
},
},
ConstOp::RefFunc(index) => Operator::RefFunc {
function_index: index.as_u32(),
},
ConstOp::I32Add => Operator::I32Add,
ConstOp::I32Sub => Operator::I32Sub,
ConstOp::I32Mul => Operator::I32Mul,
ConstOp::I64Add => Operator::I64Add,
ConstOp::I64Sub => Operator::I64Sub,
ConstOp::I64Mul => Operator::I64Mul,
ConstOp::StructNew { struct_type_index } => Operator::StructNew {
struct_type_index: struct_type_index.as_u32(),
},
ConstOp::StructNewDefault { struct_type_index } => Operator::StructNewDefault {
struct_type_index: struct_type_index.as_u32(),
},
ConstOp::ArrayNew { array_type_index } => Operator::ArrayNew {
array_type_index: array_type_index.as_u32(),
},
ConstOp::ArrayNewDefault { array_type_index } => Operator::ArrayNewDefault {
array_type_index: array_type_index.as_u32(),
},
ConstOp::ArrayNewFixed {
array_type_index,
array_size,
} => Operator::ArrayNewFixed {
array_type_index: array_type_index.as_u32(),
array_size: *array_size,
},
ConstOp::ExternConvertAny => Operator::ExternConvertAny,
ConstOp::AnyConvertExtern => Operator::AnyConvertExtern,
}
}
}

/// The type that can be used to index into [Memory] and [Table].
Expand Down
4 changes: 4 additions & 0 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,10 @@ impl Config {
/// configures per-byte, per-element, and per-page costs for operators whose
/// work depends on a runtime operand.
///
/// These costs apply both to operators in function bodies and to operators
/// in constant expressions evaluated at instantiation time, such as global
/// initializers and element or data segment offsets.
///
/// This is only relevant when [`Config::consume_fuel`] is enabled.
pub fn operator_cost(&mut self, cost: OperatorCost) -> &mut Self {
self.tunables.operator_cost = Some(OperatorCostStrategy::table(cost));
Expand Down
73 changes: 73 additions & 0 deletions tests/all/fuel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,3 +1052,76 @@ fn fuel_around_table_grow() -> Result<()> {
assert_eq!(trap, Trap::TableOutOfBounds);
Ok(())
}

#[wasmtime_test(wasm_features(extended_const))]
#[cfg_attr(miri, ignore)]
fn const_expr_honors_operator_cost(config: &mut Config) -> Result<()> {
const WAT: &str = r#"
(module
(global $g i32 (i32.add (i32.const 1) (i32.const 2)))
(export "g" (global $g))
(func $start)
(start $start))
"#;

fn instantiation_fuel(config: &mut Config, op_cost: OperatorCost) -> Result<u64> {
config.consume_fuel(true).operator_cost(op_cost);
let engine = Engine::new(config)?;
let module = Module::new(&engine, WAT)?;

let mut store = Store::new(&engine, ());
store.set_fuel(10_000)?;
let instance = Instance::new(&mut store, &module, &[])?;

let g = instance
.get_global(&mut store, "g")
.unwrap()
.get(&mut store);
assert_eq!(g.i32(), Some(3), "global initializer did not run");

Ok(10_000 - store.get_fuel()?)
}

assert_eq!(instantiation_fuel(config, OperatorCost::default())?, 6);

let custom = OperatorCost {
I32Const: 7,
I32Add: 100,
..Default::default()
};
assert_eq!(instantiation_fuel(config, custom)?, 117);

Ok(())
}

#[wasmtime_test]
#[cfg_attr(miri, ignore)]
fn module_start_call_honors_operator_cost(config: &mut Config) -> Result<()> {
const WAT: &str = r#"
(module
(func $start)
(start $start))
"#;

fn instantiation_fuel(config: &mut Config, op_cost: OperatorCost) -> Result<u64> {
config.consume_fuel(true).operator_cost(op_cost);
let engine = Engine::new(config)?;
let module = Module::new(&engine, WAT)?;

let mut store = Store::new(&engine, ());
store.set_fuel(10_000)?;
Instance::new(&mut store, &module, &[])?;

Ok(10_000 - store.get_fuel()?)
}

assert_eq!(instantiation_fuel(config, OperatorCost::default())?, 3);

let custom = OperatorCost {
Call: 50,
..Default::default()
};
assert_eq!(instantiation_fuel(config, custom)?, 52);

Ok(())
}
Loading