@@ -3,6 +3,7 @@ use crate::{IndexType, Limits, Memory, TripleExt};
33use core:: { fmt, str:: FromStr } ;
44use serde_derive:: { Deserialize , Serialize } ;
55use target_lexicon:: { PointerWidth , Triple } ;
6+ use wasmparser:: Operator ;
67
78macro_rules! define_tunables {
89 (
@@ -46,8 +47,8 @@ macro_rules! define_tunables {
4647 /// Configure the `Tunables` provided.
4748 pub fn configure( & self , tunables: & mut Tunables ) {
4849 $(
49- if let Some ( val) = self . $field {
50- tunables. $field = val;
50+ if let Some ( val) = & self . $field {
51+ tunables. $field = val. clone ( ) ;
5152 }
5253 ) *
5354 }
@@ -87,6 +88,9 @@ define_tunables! {
8788 /// will be consumed every time a wasm instruction is executed.
8889 pub consume_fuel: bool ,
8990
91+ /// The cost of each operator. If fuel is not enabled, this is ignored.
92+ pub operator_cost: OperatorCostStrategy ,
93+
9094 /// Whether or not we use epoch-based interruption.
9195 pub epoch_interruption: bool ,
9296
@@ -193,7 +197,7 @@ impl Tunables {
193197 }
194198
195199 /// Returns the default set of tunables for running under MIRI.
196- pub const fn default_miri ( ) -> Tunables {
200+ pub fn default_miri ( ) -> Tunables {
197201 Tunables {
198202 collector : None ,
199203
@@ -208,6 +212,7 @@ impl Tunables {
208212 debug_native : false ,
209213 parse_wasm_debuginfo : true ,
210214 consume_fuel : false ,
215+ operator_cost : OperatorCostStrategy :: Default ,
211216 epoch_interruption : false ,
212217 memory_may_move : true ,
213218 guard_before_linear_memory : true ,
@@ -229,7 +234,7 @@ impl Tunables {
229234 }
230235
231236 /// Returns the default set of tunables for running under a 32-bit host.
232- pub const fn default_u32 ( ) -> Tunables {
237+ pub fn default_u32 ( ) -> Tunables {
233238 Tunables {
234239 // For 32-bit we scale way down to 10MB of reserved memory. This
235240 // impacts performance severely but allows us to have more than a
@@ -244,7 +249,7 @@ impl Tunables {
244249 }
245250
246251 /// Returns the default set of tunables for running under a 64-bit host.
247- pub const fn default_u64 ( ) -> Tunables {
252+ pub fn default_u64 ( ) -> Tunables {
248253 Tunables {
249254 // 64-bit has tons of address space to static memories can have 4gb
250255 // address space reservations liberally by default, allowing us to
@@ -326,3 +331,134 @@ impl FromStr for IntraModuleInlining {
326331 }
327332 }
328333}
334+
335+ /// The cost of each operator.
336+ ///
337+ /// Note: a more dynamic approach (e.g. a user-supplied callback) can be
338+ /// added as a variant in the future if needed.
339+ #[ derive( Clone , Hash , Serialize , Deserialize , Debug , PartialEq , Eq , Default ) ]
340+ pub enum OperatorCostStrategy {
341+ /// A table of operator costs.
342+ Table ( Box < OperatorCost > ) ,
343+
344+ /// Each cost defaults to 1 fuel unit, except `Nop`, `Drop` and
345+ /// a few control flow operators.
346+ #[ default]
347+ Default ,
348+ }
349+
350+ impl OperatorCostStrategy {
351+ /// Create a new operator cost strategy with a table of costs.
352+ pub fn table ( cost : OperatorCost ) -> Self {
353+ OperatorCostStrategy :: Table ( Box :: new ( cost) )
354+ }
355+
356+ /// Get the cost of an operator.
357+ pub fn cost ( & self , op : & Operator ) -> i64 {
358+ match self {
359+ OperatorCostStrategy :: Table ( cost) => cost. cost ( op) ,
360+ OperatorCostStrategy :: Default => default_operator_cost ( op) ,
361+ }
362+ }
363+ }
364+
365+ const fn default_operator_cost ( op : & Operator ) -> i64 {
366+ match op {
367+ // Nop and drop generate no code, so don't consume fuel for them.
368+ Operator :: Nop | Operator :: Drop => 0 ,
369+
370+ // Control flow may create branches, but is generally cheap and
371+ // free, so don't consume fuel. Note the lack of `if` since some
372+ // cost is incurred with the conditional check.
373+ Operator :: Block { .. }
374+ | Operator :: Loop { .. }
375+ | Operator :: Unreachable
376+ | Operator :: Return
377+ | Operator :: Else
378+ | Operator :: End => 0 ,
379+
380+ // Everything else, just call it one operation.
381+ _ => 1 ,
382+ }
383+ }
384+
385+ macro_rules! default_cost {
386+ // Nop and drop generate no code, so don't consume fuel for them.
387+ ( Nop ) => {
388+ 0
389+ } ;
390+ ( Drop ) => {
391+ 0
392+ } ;
393+
394+ // Control flow may create branches, but is generally cheap and
395+ // free, so don't consume fuel. Note the lack of `if` since some
396+ // cost is incurred with the conditional check.
397+ ( Block ) => {
398+ 0
399+ } ;
400+ ( Loop ) => {
401+ 0
402+ } ;
403+ ( Unreachable ) => {
404+ 0
405+ } ;
406+ ( Return ) => {
407+ 0
408+ } ;
409+ ( Else ) => {
410+ 0
411+ } ;
412+ ( End ) => {
413+ 0
414+ } ;
415+
416+ // Everything else, just call it one operation.
417+ ( $op: ident) => {
418+ 1
419+ } ;
420+ }
421+
422+ macro_rules! define_operator_cost {
423+ ( $( @$proposal: ident $op: ident $( { $( $arg: ident: $argty: ty) ,* } ) ? => $visit: ident ( $( $ann: tt) * ) ) * ) => {
424+ /// The fuel cost of each operator in a table.
425+ #[ derive( Clone , Hash , Serialize , Deserialize , Debug , PartialEq , Eq ) ]
426+ #[ allow( missing_docs, non_snake_case, reason = "to avoid triggering clippy lints" ) ]
427+ pub struct OperatorCost {
428+ $(
429+ pub $op: u8 ,
430+ ) *
431+ }
432+
433+ impl OperatorCost {
434+ /// Returns the cost of the given operator.
435+ pub fn cost( & self , op: & Operator ) -> i64 {
436+ match op {
437+ $(
438+ Operator :: $op $( { $( $arg: _) ,* } ) ? => self . $op as i64 ,
439+ ) *
440+ unknown => panic!( "unknown op: {unknown:?}" ) ,
441+ }
442+ }
443+ }
444+
445+ impl OperatorCost {
446+ /// Creates a new `OperatorCost` table with default costs for each operator.
447+ pub const fn new( ) -> Self {
448+ Self {
449+ $(
450+ $op: default_cost!( $op) ,
451+ ) *
452+ }
453+ }
454+ }
455+
456+ impl Default for OperatorCost {
457+ fn default ( ) -> Self {
458+ Self :: new( )
459+ }
460+ }
461+ }
462+ }
463+
464+ wasmparser:: for_each_operator!( define_operator_cost) ;
0 commit comments