Skip to content

Commit cf7736f

Browse files
authored
Normalize fuel costs to roughly 1 fuel per executed instruction (#705)
normalize fuel costs to roughly 1 fuel per executed instruction
1 parent 223f815 commit cf7736f

File tree

8 files changed

+161
-71
lines changed

8 files changed

+161
-71
lines changed

crates/wasmi/src/engine/bytecode/utils.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::engine::{config::FuelCosts, Instr};
1+
use crate::engine::Instr;
22
use core::fmt::Display;
33

44
/// Defines how many stack values are going to be dropped and kept after branching.
@@ -62,15 +62,6 @@ impl DropKeep {
6262
pub fn keep(self) -> usize {
6363
self.keep as usize
6464
}
65-
66-
/// Returns the fuel consumption required for this [`DropKeep`].
67-
pub fn fuel_consumption(self, costs: &FuelCosts) -> u64 {
68-
if self.drop == 0 {
69-
// If nothing is dropped no copy is required.
70-
return 0;
71-
}
72-
u64::from(self.keep) * costs.branch_per_kept
73-
}
7465
}
7566

7667
/// A function index.

crates/wasmi/src/engine/config.rs

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use super::stack::StackLimits;
1+
use super::{stack::StackLimits, DropKeep};
2+
use core::{mem::size_of, num::NonZeroU64};
3+
use wasmi_core::UntypedValue;
24
use wasmparser::WasmFeatures;
35

46
/// The default amount of stacks kept in the cache at most.
@@ -54,33 +56,85 @@ pub struct FuelCosts {
5456
pub store: u64,
5557
/// The fuel cost offset for `call` and `call_indirect` instructions.
5658
pub call: u64,
57-
/// The fuel cost offset per local variable for `call` and `call_indirect` instruction.
59+
/// Determines how many moved stack values consume one fuel upon a branch or return instruction.
5860
///
5961
/// # Note
6062
///
61-
/// This is also applied to all function parameters since
62-
/// they are translated to local variable slots.
63-
pub call_per_local: u64,
64-
/// The fuel cost offset per kept value for branches that need to copy values on the stack.
65-
pub branch_per_kept: u64,
66-
/// The fuel cost offset per byte for `bulk-memory` instructions.
67-
pub memory_per_byte: u64,
68-
/// The fuel cost offset per element for `bulk-table` instructions.
69-
pub table_per_element: u64,
63+
/// If this is zero then processing [`DropKeep`] costs nothing.
64+
branch_kept_per_fuel: u64,
65+
/// Determines how many function locals consume one fuel per function call.
66+
///
67+
/// # Note
68+
///
69+
/// - This is also applied to all function parameters since
70+
/// they are translated to local variable slots.
71+
/// - If this is zero then processing function locals costs nothing.
72+
func_locals_per_fuel: u64,
73+
/// How many memory bytes can be processed per fuel in a `bulk-memory` instruction.
74+
///
75+
/// # Note
76+
///
77+
/// If this is zero then processing memory bytes costs nothing.
78+
memory_bytes_per_fuel: u64,
79+
/// How many table elements can be processed per fuel in a `bulk-table` instruction.
80+
///
81+
/// # Note
82+
///
83+
/// If this is zero then processing table elements costs nothing.
84+
table_elements_per_fuel: u64,
85+
}
86+
87+
impl FuelCosts {
88+
/// Returns the fuel consumption of the amount of items with costs per items.
89+
fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 {
90+
NonZeroU64::new(items_per_fuel)
91+
.map(|items_per_fuel| len_items / items_per_fuel)
92+
.unwrap_or(0)
93+
}
94+
95+
/// Returns the fuel consumption for branches and returns using the given [`DropKeep`].
96+
pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 {
97+
if drop_keep.drop() == 0 {
98+
return 0;
99+
}
100+
Self::costs_per(drop_keep.keep() as u64, self.branch_kept_per_fuel)
101+
}
102+
103+
/// Returns the fuel consumption for calling a function with the amount of local variables.
104+
///
105+
/// # Note
106+
///
107+
/// Function parameters are also treated as local variables.
108+
pub fn fuel_for_locals(&self, locals: u64) -> u64 {
109+
Self::costs_per(locals, self.func_locals_per_fuel)
110+
}
111+
112+
/// Returns the fuel consumption for processing the amount of memory bytes.
113+
pub fn fuel_for_bytes(&self, bytes: u64) -> u64 {
114+
Self::costs_per(bytes, self.memory_bytes_per_fuel)
115+
}
116+
117+
/// Returns the fuel consumption for processing the amount of table elements.
118+
pub fn fuel_for_elements(&self, elements: u64) -> u64 {
119+
Self::costs_per(elements, self.table_elements_per_fuel)
120+
}
70121
}
71122

72123
impl Default for FuelCosts {
73124
fn default() -> Self {
125+
let memory_bytes_per_fuel = 64;
126+
let bytes_per_register = size_of::<UntypedValue>() as u64;
127+
let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register;
74128
Self {
75-
base: 100,
76-
entity: 500,
77-
load: 650,
78-
store: 450,
79-
call: 1500,
80-
call_per_local: 10,
81-
branch_per_kept: 10,
82-
memory_per_byte: 2,
83-
table_per_element: 10,
129+
base: 1,
130+
entity: 1,
131+
load: 1,
132+
store: 1,
133+
call: 1,
134+
func_locals_per_fuel: registers_per_fuel,
135+
branch_kept_per_fuel: registers_per_fuel,
136+
memory_bytes_per_fuel,
137+
table_elements_per_fuel: registers_per_fuel,
84138
}
85139
}
86140
}

crates/wasmi/src/engine/executor.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
895895
let result = self.consume_fuel_on_success(
896896
|costs| {
897897
let delta_in_bytes = delta.to_bytes().unwrap_or(0) as u64;
898-
delta_in_bytes * costs.memory_per_byte
898+
costs.fuel_for_bytes(delta_in_bytes)
899899
},
900900
|this| {
901901
let memory = this.cache.default_memory(this.ctx);
@@ -929,7 +929,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
929929
let offset = i32::from(d) as usize;
930930
let byte = u8::from(val);
931931
self.consume_fuel_on_success(
932-
|costs| n as u64 * costs.memory_per_byte,
932+
|costs| costs.fuel_for_bytes(n as u64),
933933
|this| {
934934
let memory = this
935935
.cache
@@ -952,7 +952,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
952952
let src_offset = i32::from(s) as usize;
953953
let dst_offset = i32::from(d) as usize;
954954
self.consume_fuel_on_success(
955-
|costs| n as u64 * costs.memory_per_byte,
955+
|costs| costs.fuel_for_bytes(n as u64),
956956
|this| {
957957
let data = this.cache.default_memory_bytes(this.ctx);
958958
// These accesses just perform the bounds checks required by the Wasm spec.
@@ -977,7 +977,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
977977
let src_offset = i32::from(s) as usize;
978978
let dst_offset = i32::from(d) as usize;
979979
self.consume_fuel_on_success(
980-
|costs| n as u64 * costs.memory_per_byte,
980+
|costs| costs.fuel_for_bytes(n as u64),
981981
|this| {
982982
let (memory, data) = this
983983
.cache
@@ -1019,7 +1019,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
10191019
let (init, delta) = self.sp.pop2();
10201020
let delta: u32 = delta.into();
10211021
let result = self.consume_fuel_on_success(
1022-
|costs| u64::from(delta) * costs.table_per_element,
1022+
|costs| costs.fuel_for_elements(u64::from(delta)),
10231023
|this| {
10241024
let table = this.cache.get_table(this.ctx, table_index);
10251025
this.ctx
@@ -1044,7 +1044,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
10441044
let dst: u32 = i.into();
10451045
let len: u32 = n.into();
10461046
self.consume_fuel_on_success(
1047-
|costs| u64::from(len) * costs.table_per_element,
1047+
|costs| costs.fuel_for_elements(u64::from(len)),
10481048
|this| {
10491049
let table = this.cache.get_table(this.ctx, table_index);
10501050
this.ctx
@@ -1089,7 +1089,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
10891089
let src_index = u32::from(s);
10901090
let dst_index = u32::from(d);
10911091
self.consume_fuel_on_success(
1092-
|costs| u64::from(len) * costs.table_per_element,
1092+
|costs| costs.fuel_for_elements(u64::from(len)),
10931093
|this| {
10941094
// Query both tables and check if they are the same:
10951095
let dst = this.cache.get_table(this.ctx, dst);
@@ -1121,7 +1121,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> {
11211121
let src_index = u32::from(s);
11221122
let dst_index = u32::from(d);
11231123
self.consume_fuel_on_success(
1124-
|costs| u64::from(len) * costs.table_per_element,
1124+
|costs| costs.fuel_for_elements(u64::from(len)),
11251125
|this| {
11261126
let (instance, table, element) = this
11271127
.cache

crates/wasmi/src/engine/func_builder/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ impl<'parser> FuncBuilder<'parser> {
6565
Ok(())
6666
}
6767

68+
/// This informs the [`FuncBuilder`] that the function header translation is finished.
69+
///
70+
/// # Note
71+
///
72+
/// This was introduced to properly calculate the fuel costs for all local variables
73+
/// and function parameters. After this function call no more locals and parameters may
74+
/// be added to this function translation.
75+
pub fn finish_translate_locals(&mut self) {
76+
self.translator.finish_translate_locals()
77+
}
78+
6879
/// Updates the current position within the Wasm binary while parsing operators.
6980
pub fn update_pos(&mut self, pos: usize) {
7081
self.pos = pos;

crates/wasmi/src/engine/func_builder/translator.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ impl<'parser> FuncTranslator<'parser> {
153153
/// Registers the function parameters in the emulated value stack.
154154
fn init_func_params(&mut self) {
155155
for _param_type in self.func_type().params() {
156-
self.bump_fuel_consumption(self.fuel_costs().call_per_local);
157156
self.locals.register_locals(1);
158157
}
159158
}
@@ -164,11 +163,23 @@ impl<'parser> FuncTranslator<'parser> {
164163
///
165164
/// If too many local variables have been registered.
166165
pub fn register_locals(&mut self, amount: u32) {
167-
let fuel_costs = u64::from(amount) * self.fuel_costs().call_per_local;
168-
self.bump_fuel_consumption(fuel_costs);
169166
self.locals.register_locals(amount);
170167
}
171168

169+
/// This informs the [`FuncTranslator`] that the function header translation is finished.
170+
///
171+
/// # Note
172+
///
173+
/// This was introduced to properly calculate the fuel costs for all local variables
174+
/// and function parameters. After this function call no more locals and parameters may
175+
/// be added to this function translation.
176+
pub fn finish_translate_locals(&mut self) {
177+
self.bump_fuel_consumption(
178+
self.fuel_costs()
179+
.fuel_for_locals(u64::from(self.locals.len_registered())),
180+
)
181+
}
182+
172183
/// Finishes constructing the function and returns its [`FuncBody`].
173184
pub fn finish(&mut self) -> Result<FuncBody, TranslationError> {
174185
let func_body = self.alloc.inst_builder.finish(
@@ -996,7 +1007,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
9961007
match builder.acquire_target(relative_depth)? {
9971008
AcquiredTarget::Branch(end_label, drop_keep) => {
9981009
builder.bump_fuel_consumption(builder.fuel_costs().base);
999-
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
1010+
builder
1011+
.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
10001012
let params = builder.branch_params(end_label, drop_keep);
10011013
builder
10021014
.alloc
@@ -1019,7 +1031,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
10191031
match builder.acquire_target(relative_depth)? {
10201032
AcquiredTarget::Branch(end_label, drop_keep) => {
10211033
builder.bump_fuel_consumption(builder.fuel_costs().base);
1022-
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
1034+
builder
1035+
.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
10231036
let params = builder.branch_params(end_label, drop_keep);
10241037
builder
10251038
.alloc
@@ -1052,7 +1065,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
10521065
match builder.acquire_target(depth.into_u32())? {
10531066
AcquiredTarget::Branch(label, drop_keep) => {
10541067
*max_drop_keep_fuel = (*max_drop_keep_fuel)
1055-
.max(drop_keep.fuel_consumption(builder.fuel_costs()));
1068+
.max(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
10561069
let base = builder.alloc.inst_builder.current_pc();
10571070
let instr = offset_instr(base, n + 1);
10581071
let offset = builder
@@ -1065,7 +1078,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
10651078
}
10661079
AcquiredTarget::Return(drop_keep) => {
10671080
*max_drop_keep_fuel = (*max_drop_keep_fuel)
1068-
.max(drop_keep.fuel_consumption(builder.fuel_costs()));
1081+
.max(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
10691082
Ok(Instruction::Return(drop_keep))
10701083
}
10711084
}
@@ -1118,7 +1131,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
11181131
self.translate_if_reachable(|builder| {
11191132
let drop_keep = builder.drop_keep_return()?;
11201133
builder.bump_fuel_consumption(builder.fuel_costs().base);
1121-
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
1134+
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
11221135
builder
11231136
.alloc
11241137
.inst_builder
@@ -1134,7 +1147,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
11341147
let func_type = builder.func_type_of(func_idx.into());
11351148
let drop_keep = builder.drop_keep_return_call(&func_type)?;
11361149
builder.bump_fuel_consumption(builder.fuel_costs().call);
1137-
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
1150+
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
11381151
builder
11391152
.alloc
11401153
.inst_builder
@@ -1156,7 +1169,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> {
11561169
builder.stack_height.pop1();
11571170
let drop_keep = builder.drop_keep_return_call(&func_type)?;
11581171
builder.bump_fuel_consumption(builder.fuel_costs().call);
1159-
builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs()));
1172+
builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep));
11601173
builder
11611174
.alloc
11621175
.inst_builder

0 commit comments

Comments
 (0)