Skip to content

Commit

Permalink
Improve lazy fuel consumption (#877)
Browse files Browse the repository at this point in the history
* add FuelTap abstraction and use it

* extend Fuel with enabled flag and fuel costs

This allows to efficiently use this type for fuel consumption in the executor.

* apply rustfmt

* cleanup code

* remove FuelTap abstraction

Replaced by a simpler Option<&mut Fuel> type.

* add improved fuel metering to MemoryEntity::grow

This new functionality is unused by the engine executor as for now.

* put let inside if

* improve fuel metering for TableEntity::grow

As with MemoryEntity::grow this new functionality is unused as of now in the Wasmi engine executor.

* make executor use new fuel for memory.grow

* make memory.copy execution use new fuel charging

* improve docs of Fuel impls

* make Fuel::consume_fuel_unchecked pub(crate)

* fix minor docs issue

* add Fuel::consume_fuel_if method and use it

* make memory.fill execution use new fuel charging

* use new fuel charging for memory.init execution

* make table.copy execution use new fuel charging

* make table.init execution use new fuel charging

* make table.fill execution use new fuel charging

* make table.grow execution use new fuel charging

* remove all no longer needed fuel methods

* remove FuelConsumptionMode config
  • Loading branch information
Robbepop committed Jan 11, 2024
1 parent 548edc8 commit 17b5321
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 488 deletions.
19 changes: 11 additions & 8 deletions crates/wasmi/src/engine/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
instance::InstanceEntity,
memory::DataSegment,
module::DEFAULT_MEMORY_INDEX,
store::Fuel,
table::TableEntity,
ElementSegment,
ElementSegmentEntity,
Expand Down Expand Up @@ -114,24 +115,25 @@ impl InstanceCache {
///
/// If there is no [`DataSegment`] for the [`Instance`] at the `index`.
#[inline]
pub fn get_default_memory_and_data_segment<'a>(
pub fn get_memory_init_triplet<'a>(
&mut self,
ctx: &'a mut StoreInner,
segment: DataSegmentIdx,
) -> (&'a mut [u8], &'a [u8]) {
) -> (&'a mut [u8], &'a [u8], &'a mut Fuel) {
let seg = self.get_data_segment(ctx, segment.to_u32());
let mem = self.default_memory(ctx);
let (memory, segment) = ctx.resolve_memory_mut_and_data_segment(mem, &seg);
(memory.data_mut(), segment.bytes())
let (memory, segment, fuel) = ctx.resolve_memory_init_triplet(mem, &seg);
(memory.data_mut(), segment.bytes(), fuel)
}

/// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`].
/// Returns all necessary data required to execute a `table.init` instruction.
///
/// # Panics
///
/// If there is no [`ElementSegment`] for the [`Instance`] at the `index`.
/// - If there is no [`Table`] for given `table` index.
/// - If there is no [`ElementSegment`] for `segment` index.
#[inline]
pub fn get_table_and_element_segment<'a>(
pub fn get_table_init_params<'a>(
&mut self,
ctx: &'a mut StoreInner,
table: TableIdx,
Expand All @@ -140,11 +142,12 @@ impl InstanceCache {
&'a InstanceEntity,
&'a mut TableEntity,
&'a ElementSegmentEntity,
&'a mut Fuel,
) {
let tab = self.get_table(ctx, table);
let seg = self.get_element_segment(ctx, segment);
let inst = self.instance();
ctx.resolve_instance_table_element(inst, &tab, &seg)
ctx.resolve_table_init_params(inst, &tab, &seg)
}

/// Loads the default [`Memory`] of the currently used [`Instance`].
Expand Down
38 changes: 14 additions & 24 deletions crates/wasmi/src/engine/code_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
core::UntypedValue,
engine::bytecode::Instruction,
module::{FuncIdx, ModuleHeader},
store::StoreInner,
store::{Fuel, FuelError},
Error,
};
use alloc::boxed::Box;
Expand All @@ -24,6 +24,7 @@ use core::{
sync::atomic::{AtomicU8, Ordering},
};
use wasmi_arena::{Arena, ArenaIndex};
use wasmi_core::TrapCode;
use wasmparser::{FuncToValidate, ValidatorResources};

/// A reference to a compiled function stored in the [`CodeMap`] of an [`Engine`](crate::Engine).
Expand Down Expand Up @@ -84,22 +85,6 @@ impl InternalFuncEntity {
Self::from(CompiledFuncEntity::uninit())
}

/// Charge fuel for compiling the given `bytes` representing the Wasm function body.
///
/// # Note
///
/// This only charges fuel if `ctx` is `Some` and `fuel_metering` is enabled.
fn charge_compilation_fuel(ctx: Option<&mut StoreInner>, bytes: &[u8]) -> Result<(), Error> {
let Some(ctx) = ctx else { return Ok(()) };
if !ctx.engine().config().get_consume_fuel() {
return Ok(());
}
let fuel_costs = ctx.engine().config().fuel_costs();
let delta = fuel_costs.fuel_for_bytes(bytes.len() as u64);
ctx.fuel_mut().consume_fuel(delta)?;
Ok(())
}

/// Compile the uncompiled [`FuncEntity`].
///
/// # Panics
Expand All @@ -111,7 +96,7 @@ impl InternalFuncEntity {
///
/// - If function translation failed.
/// - If `ctx` ran out of fuel in case fuel consumption is enabled.
fn compile(&mut self, ctx: Option<&mut StoreInner>) -> Result<(), Error> {
fn compile(&mut self, fuel: Option<&mut Fuel>) -> Result<(), Error> {
let uncompiled = match self {
InternalFuncEntity::Uncompiled(func) => func,
InternalFuncEntity::Compiled(func) => {
Expand All @@ -120,7 +105,12 @@ impl InternalFuncEntity {
};
let func_idx = uncompiled.func_idx;
let bytes = mem::take(&mut uncompiled.bytes);
Self::charge_compilation_fuel(ctx, bytes.as_slice())?;
if let Some(fuel) = fuel {
match fuel.consume_fuel(|costs| costs.fuel_for_bytes(bytes.as_slice().len() as u64)) {
Err(FuelError::OutOfFuel) => return Err(Error::from(TrapCode::OutOfFuel)),
Ok(_) | Err(FuelError::FuelMeteringDisabled) => {}
}
}
let module = uncompiled.module.clone();
let Some(engine) = module.engine().upgrade() else {
panic!(
Expand Down Expand Up @@ -633,7 +623,7 @@ impl FuncEntity {
#[cold]
pub fn compile_and_get(
&self,
mut ctx: Option<&mut StoreInner>,
mut fuel: Option<&mut Fuel>,
) -> Result<&CompiledFuncEntity, Error> {
loop {
if let Some(func) = self.get_compiled() {
Expand All @@ -655,8 +645,8 @@ impl FuncEntity {
let func = unsafe { &mut *self.func.get() };
// Note: We need to use `take` because Rust doesn't know that this part of
// the loop is only executed once.
let ctx = ctx.take();
match func.compile(ctx) {
let fuel = fuel.take();
match func.compile(fuel) {
Ok(()) => {
self.phase
.set_compiled()
Expand Down Expand Up @@ -726,15 +716,15 @@ impl CodeMap {
#[track_caller]
pub fn get(
&self,
ctx: Option<&mut StoreInner>,
fuel: Option<&mut Fuel>,
compiled_func: CompiledFunc,
) -> Result<&CompiledFuncEntity, Error> {
let Some(func) = self.funcs.get(compiled_func) else {
panic!("invalid compiled func: {compiled_func:?}")
};
match func.get_compiled() {
Some(func) => Ok(func),
None => func.compile_and_get(ctx),
None => func.compile_and_get(fuel),
}
}
}
Expand Down
73 changes: 0 additions & 73 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,62 +35,12 @@ pub struct Config {
floats: bool,
/// Is `true` if Wasmi executions shall consume fuel.
consume_fuel: bool,
/// The fuel consumption mode of the Wasmi [`Engine`](crate::Engine).
fuel_consumption_mode: FuelConsumptionMode,
/// The configured fuel costs of all Wasmi bytecode instructions.
fuel_costs: FuelCosts,
/// The mode of Wasm to Wasmi bytecode compilation.
compilation_mode: CompilationMode,
}

/// The fuel consumption mode of the Wasmi [`Engine`].
///
/// This mode affects when fuel is charged for Wasm bulk-operations.
/// Affected Wasm instructions are:
///
/// - `memory.{grow, copy, fill}`
/// - `data.init`
/// - `table.{grow, copy, fill}`
/// - `element.init`
///
/// The default fuel consumption mode is [`FuelConsumptionMode::Lazy`].
///
/// [`Engine`]: crate::Engine
#[derive(Debug, Default, Copy, Clone)]
pub enum FuelConsumptionMode {
/// Fuel consumption for bulk-operations is lazy.
///
/// Lazy fuel consumption means that fuel for bulk-operations
/// is checked before executing the instruction but only consumed
/// if the executed instruction succeeded. The reason for this is
/// that bulk-operations fail fast and therefore do not cost
/// a lot of compute power in case of failure.
///
/// # Note
///
/// Lazy fuel consumption makes sense as default mode since the
/// affected bulk-operations usually are very costly if they are
/// successful. Therefore users generally want to avoid having to
/// using more fuel than what was actually used, especially if there
/// is an underlying cost model associated to the used fuel.
#[default]
Lazy,
/// Fuel consumption for bulk-operations is eager.
///
/// Eager fuel consumption means that fuel for bulk-operations
/// is always consumed before executing the instruction independent
/// of it succeeding or failing.
///
/// # Note
///
/// A use case for when a user might prefer eager fuel consumption
/// is when the fuel **required** to perform an execution should be identical
/// to the actual fuel **consumed** by an execution. Otherwise it can be confusing
/// that the execution consumed `x` gas while it needs `x + gas_for_bulk_op` to
/// not run out of fuel.
Eager,
}

/// Type storing all kinds of fuel costs of instructions.
#[derive(Debug, Copy, Clone)]
pub struct FuelCosts {
Expand Down Expand Up @@ -229,7 +179,6 @@ impl Default for Config {
floats: true,
consume_fuel: false,
fuel_costs: FuelCosts::default(),
fuel_consumption_mode: FuelConsumptionMode::default(),
compilation_mode: CompilationMode::default(),
}
}
Expand Down Expand Up @@ -401,28 +350,6 @@ impl Config {
&self.fuel_costs
}

/// Configures the [`FuelConsumptionMode`] for the [`Engine`].
///
/// # Note
///
/// This has no effect if fuel metering is disabled for the [`Engine`].
///
/// [`Engine`]: crate::Engine
pub fn fuel_consumption_mode(&mut self, mode: FuelConsumptionMode) -> &mut Self {
self.fuel_consumption_mode = mode;
self
}

/// Returns the [`FuelConsumptionMode`] for the [`Engine`].
///
/// Returns `None` if fuel metering is disabled for the [`Engine`].
///
/// [`Engine`]: crate::Engine
pub(crate) fn get_fuel_consumption_mode(&self) -> Option<FuelConsumptionMode> {
self.get_consume_fuel()
.then_some(self.fuel_consumption_mode)
}

/// Sets the [`CompilationMode`] used for the [`Engine`].
///
/// [`Engine`]: crate::Engine
Expand Down

0 comments on commit 17b5321

Please sign in to comment.