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

Normalize fuel costs to roughly 1 fuel per executed instruction #705

Merged
merged 1 commit into from
Mar 3, 2023
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
11 changes: 1 addition & 10 deletions crates/wasmi/src/engine/bytecode/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::engine::{config::FuelCosts, Instr};
use crate::engine::Instr;
use core::fmt::Display;

/// Defines how many stack values are going to be dropped and kept after branching.
Expand Down Expand Up @@ -62,15 +62,6 @@ 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
94 changes: 74 additions & 20 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::stack::StackLimits;
use super::{stack::StackLimits, DropKeep};
use core::{mem::size_of, num::NonZeroU64};
use wasmi_core::UntypedValue;
use wasmparser::WasmFeatures;

/// The default amount of stacks kept in the cache at most.
Expand Down Expand Up @@ -54,33 +56,85 @@ pub struct FuelCosts {
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.
/// Determines how many moved stack values consume one fuel upon a branch or return 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,
/// If this is zero then processing [`DropKeep`] costs nothing.
branch_kept_per_fuel: u64,
/// Determines how many function locals consume one fuel per function call.
///
/// # Note
///
/// - This is also applied to all function parameters since
/// they are translated to local variable slots.
/// - If this is zero then processing function locals costs nothing.
func_locals_per_fuel: u64,
/// How many memory bytes can be processed per fuel in a `bulk-memory` instruction.
///
/// # Note
///
/// If this is zero then processing memory bytes costs nothing.
memory_bytes_per_fuel: u64,
/// How many table elements can be processed per fuel in a `bulk-table` instruction.
///
/// # Note
///
/// If this is zero then processing table elements costs nothing.
table_elements_per_fuel: u64,
}

impl FuelCosts {
/// Returns the fuel consumption of the amount of items with costs per items.
fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 {
NonZeroU64::new(items_per_fuel)
.map(|items_per_fuel| len_items / items_per_fuel)
.unwrap_or(0)
}

/// Returns the fuel consumption for branches and returns using the given [`DropKeep`].
pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 {
if drop_keep.drop() == 0 {
return 0;
}
Self::costs_per(drop_keep.keep() as u64, self.branch_kept_per_fuel)
}

/// Returns the fuel consumption for calling a function with the amount of local variables.
///
/// # Note
///
/// Function parameters are also treated as local variables.
pub fn fuel_for_locals(&self, locals: u64) -> u64 {
Self::costs_per(locals, self.func_locals_per_fuel)
}

/// Returns the fuel consumption for processing the amount of memory bytes.
pub fn fuel_for_bytes(&self, bytes: u64) -> u64 {
Self::costs_per(bytes, self.memory_bytes_per_fuel)
}

/// Returns the fuel consumption for processing the amount of table elements.
pub fn fuel_for_elements(&self, elements: u64) -> u64 {
Self::costs_per(elements, self.table_elements_per_fuel)
}
}

impl Default for FuelCosts {
fn default() -> Self {
let memory_bytes_per_fuel = 64;
let bytes_per_register = size_of::<UntypedValue>() as u64;
let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register;
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,
base: 1,
entity: 1,
load: 1,
store: 1,
call: 1,
func_locals_per_fuel: registers_per_fuel,
branch_kept_per_fuel: registers_per_fuel,
memory_bytes_per_fuel,
table_elements_per_fuel: registers_per_fuel,
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions crates/wasmi/src/engine/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let result = self.consume_fuel_on_success(
|costs| {
let delta_in_bytes = delta.to_bytes().unwrap_or(0) as u64;
delta_in_bytes * costs.memory_per_byte
costs.fuel_for_bytes(delta_in_bytes)
},
|this| {
let memory = this.cache.default_memory(this.ctx);
Expand Down Expand Up @@ -929,7 +929,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let offset = i32::from(d) as usize;
let byte = u8::from(val);
self.consume_fuel_on_success(
|costs| n as u64 * costs.memory_per_byte,
|costs| costs.fuel_for_bytes(n as u64),
|this| {
let memory = this
.cache
Expand All @@ -952,7 +952,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let src_offset = i32::from(s) as usize;
let dst_offset = i32::from(d) as usize;
self.consume_fuel_on_success(
|costs| n as u64 * costs.memory_per_byte,
|costs| costs.fuel_for_bytes(n as u64),
|this| {
let data = this.cache.default_memory_bytes(this.ctx);
// These accesses just perform the bounds checks required by the Wasm spec.
Expand All @@ -977,7 +977,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let src_offset = i32::from(s) as usize;
let dst_offset = i32::from(d) as usize;
self.consume_fuel_on_success(
|costs| n as u64 * costs.memory_per_byte,
|costs| costs.fuel_for_bytes(n as u64),
|this| {
let (memory, data) = this
.cache
Expand Down Expand Up @@ -1019,7 +1019,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let (init, delta) = self.sp.pop2();
let delta: u32 = delta.into();
let result = self.consume_fuel_on_success(
|costs| u64::from(delta) * costs.table_per_element,
|costs| costs.fuel_for_elements(u64::from(delta)),
|this| {
let table = this.cache.get_table(this.ctx, table_index);
this.ctx
Expand All @@ -1044,7 +1044,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let dst: u32 = i.into();
let len: u32 = n.into();
self.consume_fuel_on_success(
|costs| u64::from(len) * costs.table_per_element,
|costs| costs.fuel_for_elements(u64::from(len)),
|this| {
let table = this.cache.get_table(this.ctx, table_index);
this.ctx
Expand Down Expand Up @@ -1089,7 +1089,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let src_index = u32::from(s);
let dst_index = u32::from(d);
self.consume_fuel_on_success(
|costs| u64::from(len) * costs.table_per_element,
|costs| costs.fuel_for_elements(u64::from(len)),
|this| {
// Query both tables and check if they are the same:
let dst = this.cache.get_table(this.ctx, dst);
Expand Down Expand Up @@ -1121,7 +1121,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
let src_index = u32::from(s);
let dst_index = u32::from(d);
self.consume_fuel_on_success(
|costs| u64::from(len) * costs.table_per_element,
|costs| costs.fuel_for_elements(u64::from(len)),
|this| {
let (instance, table, element) = this
.cache
Expand Down
11 changes: 11 additions & 0 deletions crates/wasmi/src/engine/func_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ impl<'parser> FuncBuilder<'parser> {
Ok(())
}

/// This informs the [`FuncBuilder`] that the function header translation is finished.
///
/// # Note
///
/// This was introduced to properly calculate the fuel costs for all local variables
/// and function parameters. After this function call no more locals and parameters may
/// be added to this function translation.
pub fn finish_translate_locals(&mut self) {
self.translator.finish_translate_locals()
}

/// Updates the current position within the Wasm binary while parsing operators.
pub fn update_pos(&mut self, pos: usize) {
self.pos = pos;
Expand Down
33 changes: 23 additions & 10 deletions crates/wasmi/src/engine/func_builder/translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ impl<'parser> FuncTranslator<'parser> {
/// Registers the function parameters in the emulated value stack.
fn init_func_params(&mut self) {
for _param_type in self.func_type().params() {
self.bump_fuel_consumption(self.fuel_costs().call_per_local);
self.locals.register_locals(1);
}
}
Expand All @@ -164,11 +163,23 @@ impl<'parser> FuncTranslator<'parser> {
///
/// If too many local variables have been registered.
pub fn register_locals(&mut self, amount: u32) {
let fuel_costs = u64::from(amount) * self.fuel_costs().call_per_local;
self.bump_fuel_consumption(fuel_costs);
self.locals.register_locals(amount);
}

/// This informs the [`FuncTranslator`] that the function header translation is finished.
///
/// # Note
///
/// This was introduced to properly calculate the fuel costs for all local variables
/// and function parameters. After this function call no more locals and parameters may
/// be added to this function translation.
pub fn finish_translate_locals(&mut self) {
self.bump_fuel_consumption(
self.fuel_costs()
.fuel_for_locals(u64::from(self.locals.len_registered())),
)
}

/// Finishes constructing the function and returns its [`FuncBody`].
pub fn finish(&mut self) -> Result<FuncBody, TranslationError> {
let func_body = self.alloc.inst_builder.finish(
Expand Down Expand Up @@ -996,7 +1007,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
match builder.acquire_target(relative_depth)? {
AcquiredTarget::Branch(end_label, drop_keep) => {
builder.bump_fuel_consumption(builder.fuel_costs().base);
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
builder
.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
let params = builder.branch_params(end_label, drop_keep);
builder
.alloc
Expand All @@ -1019,7 +1031,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
match builder.acquire_target(relative_depth)? {
AcquiredTarget::Branch(end_label, drop_keep) => {
builder.bump_fuel_consumption(builder.fuel_costs().base);
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
builder
.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
let params = builder.branch_params(end_label, drop_keep);
builder
.alloc
Expand Down Expand Up @@ -1052,7 +1065,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
match builder.acquire_target(depth.into_u32())? {
AcquiredTarget::Branch(label, drop_keep) => {
*max_drop_keep_fuel = (*max_drop_keep_fuel)
.max(drop_keep.fuel_consumption(builder.fuel_costs()));
.max(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
let base = builder.alloc.inst_builder.current_pc();
let instr = offset_instr(base, n + 1);
let offset = builder
Expand All @@ -1065,7 +1078,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
}
AcquiredTarget::Return(drop_keep) => {
*max_drop_keep_fuel = (*max_drop_keep_fuel)
.max(drop_keep.fuel_consumption(builder.fuel_costs()));
.max(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
Ok(Instruction::Return(drop_keep))
}
}
Expand Down Expand Up @@ -1118,7 +1131,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
self.translate_if_reachable(|builder| {
let drop_keep = builder.drop_keep_return()?;
builder.bump_fuel_consumption(builder.fuel_costs().base);
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
builder
.alloc
.inst_builder
Expand All @@ -1134,7 +1147,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
let func_type = builder.func_type_of(func_idx.into());
let drop_keep = builder.drop_keep_return_call(&func_type)?;
builder.bump_fuel_consumption(builder.fuel_costs().call);
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
builder
.alloc
.inst_builder
Expand All @@ -1156,7 +1169,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
builder.stack_height.pop1();
let drop_keep = builder.drop_keep_return_call(&func_type)?;
builder.bump_fuel_consumption(builder.fuel_costs().call);
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
builder
.alloc
.inst_builder
Expand Down
Loading