1
1
//! `metering` is a middleware for tracking how many operators are executed in total
2
2
//! and putting a limit on the total number of operators executed.
3
3
4
+ use core:: convert:: TryFrom ;
4
5
use std:: convert:: TryInto ;
5
6
use std:: fmt;
6
7
use std:: sync:: Mutex ;
@@ -11,7 +12,7 @@ use wasmer::{
11
12
ExportIndex , FunctionMiddleware , GlobalInit , GlobalType , Instance , LocalFunctionIndex ,
12
13
MiddlewareReaderState , ModuleMiddleware , Mutability , Type ,
13
14
} ;
14
- use wasmer_types:: GlobalIndex ;
15
+ use wasmer_types:: { GlobalIndex , Value } ;
15
16
use wasmer_vm:: ModuleInfo ;
16
17
17
18
/// The module-level metering middleware.
@@ -28,6 +29,9 @@ pub struct Metering<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> {
28
29
/// Function that maps each operator to a cost in "points".
29
30
cost_function : F ,
30
31
32
+ /// The global index in the current module for the error code.
33
+ error_code_index : Mutex < Option < GlobalIndex > > ,
34
+
31
35
/// The global index in the current module for remaining points.
32
36
remaining_points_index : Mutex < Option < GlobalIndex > > ,
33
37
}
@@ -37,19 +41,52 @@ pub struct FunctionMetering<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync
37
41
/// Function that maps each operator to a cost in "points".
38
42
cost_function : F ,
39
43
44
+ /// The global index in the current module for the error code.
45
+ error_code_index : GlobalIndex ,
46
+
40
47
/// The global index in the current module for remaining points.
41
48
remaining_points_index : GlobalIndex ,
42
49
43
50
/// Accumulated cost of the current basic block.
44
51
accumulated_cost : u64 ,
45
52
}
46
53
54
+ #[ derive( Debug , PartialEq ) ]
55
+ pub enum MeteringError {
56
+ NoError ,
57
+ OutOfGas ,
58
+ }
59
+
60
+ impl TryFrom < i32 > for MeteringError {
61
+ type Error = ( ) ;
62
+
63
+ fn try_from ( value : i32 ) -> Result < Self , Self :: Error > {
64
+ match value {
65
+ value if value == MeteringError :: NoError as _ => Ok ( MeteringError :: NoError ) ,
66
+ value if value == MeteringError :: OutOfGas as _ => Ok ( MeteringError :: OutOfGas ) ,
67
+ _ => Err ( ( ) ) ,
68
+ }
69
+ }
70
+ }
71
+
72
+ impl < T > TryFrom < Value < T > > for MeteringError {
73
+ type Error = ( ) ;
74
+
75
+ fn try_from ( v : Value < T > ) -> Result < Self , Self :: Error > {
76
+ match v {
77
+ Value :: I32 ( value) => value. try_into ( ) ,
78
+ _ => Err ( ( ) ) ,
79
+ }
80
+ }
81
+ }
82
+
47
83
impl < F : Fn ( & Operator ) -> u64 + Copy + Clone + Send + Sync > Metering < F > {
48
84
/// Creates a `Metering` middleware.
49
85
pub fn new ( initial_limit : u64 , cost_function : F ) -> Self {
50
86
Self {
51
87
initial_limit,
52
88
cost_function,
89
+ error_code_index : Mutex :: new ( None ) ,
53
90
remaining_points_index : Mutex :: new ( None ) ,
54
91
}
55
92
}
@@ -60,6 +97,7 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> fmt::Debug for Meteri
60
97
f. debug_struct ( "Metering" )
61
98
. field ( "initial_limit" , & self . initial_limit )
62
99
. field ( "cost_function" , & "<function>" )
100
+ . field ( "error_code_index" , & self . error_code_index )
63
101
. field ( "remaining_points_index" , & self . remaining_points_index )
64
102
. finish ( )
65
103
}
@@ -72,6 +110,11 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync + 'static> ModuleMiddl
72
110
fn generate_function_middleware ( & self , _: LocalFunctionIndex ) -> Box < dyn FunctionMiddleware > {
73
111
Box :: new ( FunctionMetering {
74
112
cost_function : self . cost_function ,
113
+ error_code_index : self
114
+ . error_code_index
115
+ . lock ( )
116
+ . unwrap ( )
117
+ . expect ( "Metering::generate_function_middleware: Error code index not set up." ) ,
75
118
remaining_points_index : self . remaining_points_index . lock ( ) . unwrap ( ) . expect (
76
119
"Metering::generate_function_middleware: Remaining points index not set up." ,
77
120
) ,
@@ -81,23 +124,37 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync + 'static> ModuleMiddl
81
124
82
125
/// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins.
83
126
fn transform_module_info ( & self , module_info : & mut ModuleInfo ) {
127
+ let mut error_code_index = self . error_code_index . lock ( ) . unwrap ( ) ;
84
128
let mut remaining_points_index = self . remaining_points_index . lock ( ) . unwrap ( ) ;
85
- if remaining_points_index. is_some ( ) {
129
+
130
+ if error_code_index. is_some ( ) || remaining_points_index. is_some ( ) {
86
131
panic ! ( "Metering::transform_module_info: Attempting to use a `Metering` middleware from multiple modules." ) ;
87
132
}
88
133
134
+ // Append a global for the error code and initialize it.
135
+ let error_code_global_index = module_info
136
+ . globals
137
+ . push ( GlobalType :: new ( Type :: I32 , Mutability :: Var ) ) ;
138
+ * error_code_index = Some ( error_code_global_index. clone ( ) ) ;
139
+
89
140
// Append a global for remaining points and initialize it.
90
- let global_index = module_info
141
+ let remaining_points_global_index = module_info
91
142
. globals
92
143
. push ( GlobalType :: new ( Type :: I64 , Mutability :: Var ) ) ;
93
- * remaining_points_index = Some ( global_index. clone ( ) ) ;
144
+ * remaining_points_index = Some ( remaining_points_global_index. clone ( ) ) ;
145
+
94
146
module_info
95
147
. global_initializers
96
148
. push ( GlobalInit :: I64Const ( self . initial_limit as i64 ) ) ;
97
149
98
150
module_info. exports . insert (
99
- "remaining_points" . to_string ( ) ,
100
- ExportIndex :: Global ( global_index) ,
151
+ "wasmer_metering_error_code" . to_string ( ) ,
152
+ ExportIndex :: Global ( error_code_global_index) ,
153
+ ) ;
154
+
155
+ module_info. exports . insert (
156
+ "wasmer_metering_remaining_points" . to_string ( ) ,
157
+ ExportIndex :: Global ( remaining_points_global_index) ,
101
158
) ;
102
159
}
103
160
}
@@ -106,6 +163,7 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> fmt::Debug for Functi
106
163
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
107
164
f. debug_struct ( "FunctionMetering" )
108
165
. field ( "cost_function" , & "<function>" )
166
+ . field ( "error_code_index" , & self . error_code_index )
109
167
. field ( "remaining_points_index" , & self . remaining_points_index )
110
168
. finish ( )
111
169
}
@@ -143,7 +201,11 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> FunctionMiddleware
143
201
Operator :: I64Const { value : self . accumulated_cost as i64 } ,
144
202
Operator :: I64LtU ,
145
203
Operator :: If { ty : WpTypeOrFuncType :: Type ( WpType :: EmptyBlockType ) } ,
146
- Operator :: Unreachable , // FIXME: Signal the error properly.
204
+ Operator :: I32Const { value : MeteringError :: OutOfGas as i32 } ,
205
+ Operator :: GlobalSet { global_index : self . error_code_index . as_u32 ( ) } ,
206
+ Operator :: I64Const { value : 0 } ,
207
+ Operator :: GlobalSet { global_index : self . remaining_points_index . as_u32 ( ) } ,
208
+ Operator :: Unreachable ,
147
209
Operator :: End ,
148
210
149
211
// globals[remaining_points_index] -= self.accumulated_cost;
@@ -164,6 +226,28 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> FunctionMiddleware
164
226
}
165
227
}
166
228
229
+ /// Get the error code in an `Instance`.
230
+ ///
231
+ /// When instance execution traps, this error code will help know if it was caused by
232
+ /// remaining points being exhausted.
233
+ ///
234
+ /// This can be used in a headless engine after an ahead-of-time compilation
235
+ /// as all required state lives in the instance.
236
+ ///
237
+ /// # Panic
238
+ ///
239
+ /// The instance Module must have been processed with the [`Metering`] middleware
240
+ /// at compile time, otherwise this will panic.
241
+ pub fn get_error ( instance : & Instance ) -> MeteringError {
242
+ instance
243
+ . exports
244
+ . get_global ( "wasmer_metering_error_code" )
245
+ . expect ( "Can't get `wasmer_metering_error_code` from Instance" )
246
+ . get ( )
247
+ . try_into ( )
248
+ . expect ( "`wasmer_metering_error_code` from Instance has wrong type" )
249
+ }
250
+
167
251
/// Get the remaining points in an `Instance`.
168
252
///
169
253
/// This can be used in a headless engine after an ahead-of-time compilation
@@ -176,11 +260,11 @@ impl<F: Fn(&Operator) -> u64 + Copy + Clone + Send + Sync> FunctionMiddleware
176
260
pub fn get_remaining_points ( instance : & Instance ) -> u64 {
177
261
instance
178
262
. exports
179
- . get_global ( "remaining_points " )
180
- . expect ( "Can't get `remaining_points ` from Instance" )
263
+ . get_global ( "wasmer_metering_remaining_points " )
264
+ . expect ( "Can't get `wasmer_metering_remaining_points ` from Instance" )
181
265
. get ( )
182
266
. try_into ( )
183
- . expect ( "`remaining_points ` from Instance has wrong type" )
267
+ . expect ( "`wasmer_metering_remaining_points ` from Instance has wrong type" )
184
268
}
185
269
186
270
/// Set the provided remaining points in an `Instance`.
@@ -195,10 +279,10 @@ pub fn get_remaining_points(instance: &Instance) -> u64 {
195
279
pub fn set_remaining_points ( instance : & Instance , points : u64 ) {
196
280
instance
197
281
. exports
198
- . get_global ( "remaining_points " )
199
- . expect ( "Can't get `remaining_points ` from Instance" )
282
+ . get_global ( "wasmer_metering_remaining_points " )
283
+ . expect ( "Can't get `wasmer_metering_remaining_points ` from Instance" )
200
284
. set ( points. into ( ) )
201
- . expect ( "Can't set `remaining_points ` in Instance" ) ;
285
+ . expect ( "Can't set `wasmer_metering_remaining_points ` in Instance" ) ;
202
286
}
203
287
204
288
#[ cfg( test) ]
@@ -258,16 +342,17 @@ mod tests {
258
342
. unwrap ( ) ;
259
343
add_one. call ( 1 ) . unwrap ( ) ;
260
344
assert_eq ! ( get_remaining_points( & instance) , 6 ) ;
345
+ assert_eq ! ( get_error( & instance) , MeteringError :: NoError ) ;
261
346
262
347
// Second call
263
348
add_one. call ( 1 ) . unwrap ( ) ;
264
349
assert_eq ! ( get_remaining_points( & instance) , 2 ) ;
350
+ assert_eq ! ( get_error( & instance) , MeteringError :: NoError ) ;
265
351
266
352
// Third call fails due to limit
267
353
assert ! ( add_one. call( 1 ) . is_err( ) ) ;
268
- // TODO: what do we expect now? 0 or 2? See https://github.com/wasmerio/wasmer/issues/1931
269
- // assert_eq!(metering.get_remaining_points(&instance), 2);
270
- // assert_eq!(metering.get_remaining_points(&instance), 0);
354
+ assert_eq ! ( get_remaining_points( & instance) , 0 ) ;
355
+ assert_eq ! ( get_error( & instance) , MeteringError :: OutOfGas ) ;
271
356
}
272
357
273
358
#[ test]
@@ -294,9 +379,14 @@ mod tests {
294
379
// Ensure we can use the new points now
295
380
add_one. call ( 1 ) . unwrap ( ) ;
296
381
assert_eq ! ( get_remaining_points( & instance) , 8 ) ;
382
+ assert_eq ! ( get_error( & instance) , MeteringError :: NoError ) ;
383
+
297
384
add_one. call ( 1 ) . unwrap ( ) ;
298
385
assert_eq ! ( get_remaining_points( & instance) , 4 ) ;
386
+ assert_eq ! ( get_error( & instance) , MeteringError :: NoError ) ;
387
+
299
388
add_one. call ( 1 ) . unwrap ( ) ;
300
389
assert_eq ! ( get_remaining_points( & instance) , 0 ) ;
390
+ assert_eq ! ( get_error( & instance) , MeteringError :: NoError ) ;
301
391
}
302
392
}
0 commit comments