Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for optional fuel metering #653

Merged
merged 77 commits into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
0730fd6
add TrapCode::OutOfFuel
Robbepop Feb 5, 2023
2a59256
add support for fuel metering to Config
Robbepop Feb 5, 2023
cfa4a65
add fuel metering support to Store
Robbepop Feb 5, 2023
77a16f0
add fuel metering support to Caller
Robbepop Feb 5, 2023
64d74b3
apply rustfmt
Robbepop Feb 5, 2023
ccb16bf
add ConsumeFuel wasmi bytecode instruction
Robbepop Feb 5, 2023
c51defb
add convenience method for ConsumeFuel construction
Robbepop Feb 6, 2023
bb74cc8
extend codegen tests to work with metered code
Robbepop Feb 6, 2023
5a3de31
add Instruction::consumed_fuel method
Robbepop Feb 6, 2023
738c338
add initial tests for fuel metering codegen
Robbepop Feb 6, 2023
0791743
apply rustfmt
Robbepop Feb 6, 2023
22b31ef
respect DropKeep in fuel metering
Robbepop Feb 6, 2023
a697cd6
use const for FUEL_PER_KEPT value
Robbepop Feb 6, 2023
aee84f9
apply clippy suggestion
Robbepop Feb 6, 2023
b1b8977
refactor BlockType constructor
Robbepop Feb 6, 2023
a8ba20d
add is_fuel_metering_enabled check to FuncTranslator
Robbepop Feb 6, 2023
05ad655
simplify Store's Fuel implementation
Robbepop Feb 6, 2023
e68ce39
fix comment
Robbepop Feb 6, 2023
62ab087
Merge branch 'master' into rf-fuel-metering
Robbepop Feb 7, 2023
c4b551e
Merge branch 'master' into rf-fuel-metering
Robbepop Feb 7, 2023
e4a8860
fix doc link
Robbepop Feb 7, 2023
5318fce
remove duped FuncTranslator init in FuncBuilder
Robbepop Feb 8, 2023
0f0805f
add consume_fuel instr to BlockControlFrame
Robbepop Feb 8, 2023
300d702
move FuncBuilderAllocations to translator module
Robbepop Feb 8, 2023
a3789d3
remove unused imports
Robbepop Feb 8, 2023
f1d65ea
remove superflous engine field from FuncTranslator
Robbepop Feb 8, 2023
a952f15
remove superflous engine parameters
Robbepop Feb 8, 2023
9ec99f2
apply rustfmt
Robbepop Feb 8, 2023
3a65f9a
refactor FuncTranslator initialization
Robbepop Feb 8, 2023
8057850
rename register methods to init methods
Robbepop Feb 8, 2023
32ed222
add doc comment
Robbepop Feb 8, 2023
b67949b
add optional consume_fuel field to control frames
Robbepop Feb 8, 2023
cdf5c10
add Instruction::add_fuel convenience method
Robbepop Feb 8, 2023
67d1706
Merge branch 'master' into rf-fuel-metering
Robbepop Feb 10, 2023
68c39f5
add FuelCosts to Config
Robbepop Feb 10, 2023
5dea3e3
add entitiy to FuelCosts and adjust numbers
Robbepop Feb 10, 2023
22e789b
add optional consume fuel instruction to function basic block
Robbepop Feb 10, 2023
c7c7304
apply rustfmt
Robbepop Feb 10, 2023
b15dbd6
conditionally add ConsumeFuel instructions to block entries
Robbepop Feb 10, 2023
dce8d7d
fix nostd build
Robbepop Feb 10, 2023
5116e15
fix internal doc links
Robbepop Feb 10, 2023
ba4a17f
apply rustfmt
Robbepop Feb 10, 2023
6eaf9f6
add InstructionBuilder::add_fuel method
Robbepop Feb 10, 2023
5dbe355
apply clippy suggestion
Robbepop Feb 10, 2023
9fdbf78
fix internal doc link
Robbepop Feb 10, 2023
34c2d00
add FuncTranslator::add_fuel convenience method
Robbepop Feb 10, 2023
46ed117
add FuncTranslator::fuel_costs getter
Robbepop Feb 10, 2023
dfa707e
make Engine::config return Config by reference
Robbepop Feb 10, 2023
c8fd0a3
refactor FuncTranslator::fuel_costs getter
Robbepop Feb 10, 2023
7445c6a
return FuelCosts without Option
Robbepop Feb 10, 2023
01492a7
use FuncTranslator::fuel_costs in more places
Robbepop Feb 10, 2023
f4e7052
remove unused fuel cost code
Robbepop Feb 10, 2023
3c3c751
add fuel costs to memory.size
Robbepop Feb 10, 2023
1c91428
rename add_fuel to bump_fuel_consumption
Robbepop Feb 10, 2023
46c72c5
properly bump fuel consumption for many instructions
Robbepop Feb 10, 2023
e15e813
bump fuel consumption in more places
Robbepop Feb 10, 2023
e9fc34d
bump fuel consumption in more places
Robbepop Feb 10, 2023
9163894
fix compilation
Robbepop Feb 10, 2023
6aafefd
bump fuel consumption in more places
Robbepop Feb 10, 2023
11d98bf
bump fuel consumption in even more required places
Robbepop Feb 10, 2023
2e2849e
consume fuel for bulk-memory and table instructions
Robbepop Feb 10, 2023
c167e16
make existing fuel metering tests base on default fuel costs
Robbepop Feb 11, 2023
379a820
apply clippy suggestion
Robbepop Feb 11, 2023
650c22d
add fuel metering test for nested loops
Robbepop Feb 11, 2023
6623e89
fix bug in fuel metering codegen for if blocks
Robbepop Feb 11, 2023
1d96ace
add fuel metering test for if blocks
Robbepop Feb 11, 2023
ec6387f
silence bad clippy lints for test
Robbepop Feb 11, 2023
5d65cba
add fuel metering test
Robbepop Feb 11, 2023
9922052
add more fuel metering tests
Robbepop Feb 11, 2023
4631e22
add fuel metering test for globals
Robbepop Feb 11, 2023
fbf9da0
only charge fuel for memory.grow if successful
Robbepop Feb 11, 2023
67b8d05
fix internal doc link
Robbepop Feb 11, 2023
547ee39
add fuel metering tests for calls
Robbepop Feb 11, 2023
499b075
use $b for local in test
Robbepop Feb 11, 2023
7763747
add fuel metering tests for load and store operations
Robbepop Feb 11, 2023
ca1a01e
apply rustfmt
Robbepop Feb 11, 2023
53cd277
add simple runtime fuel metering test
Robbepop Feb 11, 2023
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
8 changes: 8 additions & 0 deletions crates/core/src/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ pub enum TrapCode {
/// with an index that points to a function with signature different of what is
/// expected by this indirect call, this trap is raised.
BadSignature,

/// This trap is raised when a WebAssembly execution ran out of fuel.
///
/// The `wasmi` execution engine can be configured to instrument its
/// internal bytecode so that fuel is consumed for each executed instruction.
/// This is useful to deterministically halt or yield a WebAssembly execution.
OutOfFuel,
}

impl TrapCode {
Expand All @@ -297,6 +304,7 @@ impl TrapCode {
Self::BadConversionToInteger => "invalid conversion to integer",
Self::StackOverflow => "call stack exhausted",
Self::BadSignature => "indirect call type mismatch",
Self::OutOfFuel => "all fuel consumed by WebAssembly",
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi/src/snapshots/preview_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ macro_rules! impl_add_to_linker_for_funcs {
fn $fname:ident ($( $arg:ident : $typ:ty ),* $(,)? ) -> $ret:tt
);+ $(;)?
) => {
fn add_wasi_snapshot_to_wasmi_linker<'a, T, U>(
fn add_wasi_snapshot_to_wasmi_linker<T, U>(
linker: &mut Linker<T>,
mut store_ctx: impl AsContextMut<UserState = T>,
wasi_ctx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static)
Expand Down
27 changes: 27 additions & 0 deletions crates/wasmi/src/engine/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ pub enum Instruction {
len_targets: usize,
},
Unreachable,
ConsumeFuel {
amount: u64,
},
Return(DropKeep),
ReturnIfNez(DropKeep),
Call(FuncIdx),
Expand Down Expand Up @@ -279,4 +282,28 @@ impl Instruction {
local_depth: LocalDepth::from(local_depth),
}
}

/// Convenience method to create a new `ConsumeFuel` instruction.
pub fn consume_fuel(amount: u64) -> Self {
Self::ConsumeFuel { amount }
}

/// Increases the fuel consumption of the [`ConsumeFuel`] instruction by `delta`.
///
/// # Panics
///
/// - If `self` is not a [`ConsumeFuel`] instruction.
/// - If the new fuel consumption overflows the internal `u64` value.
///
/// [`ConsumeFuel`]: Instruction::ConsumeFuel
pub fn bump_fuel_consumption(&mut self, delta: u64) {
match self {
Self::ConsumeFuel { amount } => {
*amount = amount.checked_add(delta).unwrap_or_else(|| {
panic!("overflowed fuel consumption. current = {amount}, delta = {delta}",)
})
}
instr => panic!("expected Instruction::ConsumeFuel but found: {instr:?}"),
}
}
}
11 changes: 10 additions & 1 deletion crates/wasmi/src/engine/bytecode/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::engine::Instr;
use crate::engine::{config::FuelCosts, Instr};
use core::fmt::Display;

/// Defines how many stack values are going to be dropped and kept after branching.
Expand Down Expand Up @@ -62,6 +62,15 @@ impl DropKeep {
pub fn keep(self) -> usize {
self.keep as usize
}

/// Returns the fuel consumption required for this [`DropKeep`].
pub fn fuel_consumption(self, costs: &FuelCosts) -> u64 {
if self.drop == 0 {
// If nothing is dropped no copy is required.
return 0;
}
u64::from(self.keep) * costs.branch_per_kept
}
}

/// A function index.
Expand Down
3 changes: 1 addition & 2 deletions crates/wasmi/src/engine/cache.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::bytecode::{DataSegmentIdx, ElementSegmentIdx, TableIdx};
use crate::{
instance::InstanceEntity,
memory::DataSegment,
Expand All @@ -14,8 +15,6 @@ use crate::{
use core::ptr::NonNull;
use wasmi_core::UntypedValue;

use super::bytecode::{DataSegmentIdx, ElementSegmentIdx, TableIdx};

/// A cache for frequently used entities of an [`Instance`].
#[derive(Debug)]
pub struct InstanceCache {
Expand Down
90 changes: 90 additions & 0 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,60 @@ pub struct Config {
reference_types: bool,
/// Is `true` if Wasm instructions on `f32` and `f64` types are allowed.
floats: bool,
/// Is `true` if `wasmi` executions shall consume fuel.
consume_fuel: bool,
/// The configured fuel costs of all `wasmi` bytecode instructions.
fuel_costs: FuelCosts,
}

/// Type storing all kinds of fuel costs of instructions.
#[derive(Debug, Copy, Clone)]
pub struct FuelCosts {
/// The base fuel costs for all instructions.
pub base: u64,
/// The fuel cost for instruction operating on Wasm entities.
///
/// # Note
///
/// A Wasm entitiy is one of `func`, `global`, `memory` or `table`.
/// Those instructions are usually a bit more costly since they need
/// multiplie indirect accesses through the Wasm instance and store.
pub entity: u64,
/// The fuel cost offset for `memory.load` instructions.
pub load: u64,
/// The fuel cost offset for `memory.store` instructions.
pub store: u64,
/// The fuel cost offset for `call` and `call_indirect` instructions.
pub call: u64,
/// The fuel cost offset per local variable for `call` and `call_indirect` instruction.
///
/// # Note
///
/// This is also applied to all function parameters since
/// they are translated to local variable slots.
pub call_per_local: u64,
/// The fuel cost offset per kept value for branches that need to copy values on the stack.
pub branch_per_kept: u64,
/// The fuel cost offset per byte for `bulk-memory` instructions.
pub memory_per_byte: u64,
/// The fuel cost offset per element for `bulk-table` instructions.
pub table_per_element: u64,
}

impl Default for FuelCosts {
fn default() -> Self {
Self {
base: 100,
entity: 500,
load: 650,
store: 450,
call: 1500,
call_per_local: 10,
branch_per_kept: 10,
memory_per_byte: 2,
table_per_element: 10,
}
}
}

impl Default for Config {
Expand All @@ -41,6 +95,8 @@ impl Default for Config {
bulk_memory: true,
reference_types: true,
floats: true,
consume_fuel: false,
fuel_costs: FuelCosts::default(),
}
}
}
Expand Down Expand Up @@ -158,6 +214,40 @@ impl Config {
self
}

/// Configures whether `wasmi` will consume fuel during execution to either halt execution as desired.
///
/// # Note
///
/// This configuration can be used to make `wasmi` instrument its internal bytecode
/// so that it consumes fuel as it executes. Once an execution runs out of fuel
/// a [`TrapCode::OutOfFuel`](crate::core::TrapCode::OutOfFuel) trap is raised.
/// This way users can deterministically halt or yield the execution of WebAssembly code.
///
/// - Use [`Store::add_fuel`](crate::Store::add_fuel) to pour some fuel into the [`Store`] before
/// executing some code as the [`Store`] start with no fuel.
/// - Use [`Caller::consume_fuel`](crate::Caller::consume_fuel) to charge costs for executed host functions.
///
/// Disabled by default.
///
/// [`Store`]: crate::Store
/// [`Engine`]: crate::Engine
pub fn consume_fuel(&mut self, enable: bool) -> &mut Self {
self.consume_fuel = enable;
self
}

/// Returns `true` if the [`Config`] enables fuel consumption by the [`Engine`].
///
/// [`Engine`]: crate::Engine
pub(crate) fn get_consume_fuel(&self) -> bool {
self.consume_fuel
}

/// Returns the configured [`FuelCosts`].
pub(crate) fn fuel_costs(&self) -> &FuelCosts {
&self.fuel_costs
}

/// Returns the [`WasmFeatures`] represented by the [`Config`].
pub fn wasm_features(&self) -> WasmFeatures {
WasmFeatures {
Expand Down
Loading