1
+ use std:: { fmt, mem} ;
2
+
3
+ use rayon:: iter:: { IntoParallelIterator , ParallelIterator } ;
1
4
use wasmer:: wasmparser:: {
2
- CompositeType , Export , Import , MemoryType , Parser , Payload , TableType , ValidPayload , Validator ,
3
- WasmFeatures ,
5
+ BinaryReaderError , CompositeType , Export , FuncToValidate , FunctionBody , Import , MemoryType ,
6
+ Parser , Payload , TableType , ValidPayload , Validator , ValidatorResources , WasmFeatures ,
4
7
} ;
5
8
6
9
use crate :: { VmError , VmResult } ;
7
10
11
+ pub struct OpaqueDebug < T > ( pub T ) ;
12
+
13
+ impl < T > fmt:: Debug for OpaqueDebug < T > {
14
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
15
+ f. debug_struct ( std:: any:: type_name :: < T > ( ) )
16
+ . finish_non_exhaustive ( )
17
+ }
18
+ }
19
+
20
+ #[ derive( Debug ) ]
21
+ pub enum FunctionValidator < ' a > {
22
+ Pending ( Vec < OpaqueDebug < ( FuncToValidate < ValidatorResources > , FunctionBody < ' a > ) > > ) ,
23
+ Success ,
24
+ Error ( BinaryReaderError ) ,
25
+ }
26
+
27
+ impl < ' a > FunctionValidator < ' a > {
28
+ fn push ( & mut self , item : ( FuncToValidate < ValidatorResources > , FunctionBody < ' a > ) ) {
29
+ let Self :: Pending ( ref mut funcs) = self else {
30
+ panic ! ( "attempted to push function into non-pending validator" ) ;
31
+ } ;
32
+
33
+ funcs. push ( OpaqueDebug ( item) ) ;
34
+ }
35
+ }
36
+
8
37
/// A parsed and validated wasm module.
9
38
/// It keeps track of the parts that are important for our static analysis and compatibility checks.
10
39
#[ derive( Debug ) ]
@@ -25,6 +54,8 @@ pub struct ParsedWasm<'a> {
25
54
pub max_func_results : usize ,
26
55
/// How many function parameters are used in the module
27
56
pub total_func_params : usize ,
57
+ /// Collections of functions that are potentially pending validation
58
+ pub func_validator : FunctionValidator < ' a > ,
28
59
}
29
60
30
61
impl < ' a > ParsedWasm < ' a > {
@@ -66,18 +97,15 @@ impl<'a> ParsedWasm<'a> {
66
97
max_func_params : 0 ,
67
98
max_func_results : 0 ,
68
99
total_func_params : 0 ,
100
+ func_validator : FunctionValidator :: Pending ( Vec :: new ( ) ) ,
69
101
} ;
70
102
71
- let mut fun_allocations = Default :: default ( ) ;
72
103
for p in Parser :: new ( 0 ) . parse_all ( wasm) {
73
104
let p = p?;
74
105
// validate the payload
75
106
if let ValidPayload :: Func ( fv, body) = validator. payload ( & p) ? {
76
107
// also validate function bodies
77
- let mut fun_validator = fv. into_validator ( fun_allocations) ;
78
- fun_validator. validate ( & body) ?;
79
- fun_allocations = fun_validator. into_allocations ( ) ;
80
-
108
+ this. func_validator . push ( ( fv, body) ) ;
81
109
this. function_count += 1 ;
82
110
}
83
111
@@ -147,4 +175,33 @@ impl<'a> ParsedWasm<'a> {
147
175
148
176
Ok ( this)
149
177
}
178
+
179
+ /// Perform the expensive operation of validating each function body
180
+ ///
181
+ /// Note: This function caches the output of this function into the field `funcs_to_validate` so repeated invocations are cheap.
182
+ pub fn validate_funcs ( & mut self ) -> VmResult < ( ) > {
183
+ match self . func_validator {
184
+ FunctionValidator :: Pending ( ref mut funcs) => {
185
+ let result = mem:: take ( funcs) // This is fine since `Vec::default()` doesn't allocate
186
+ . into_par_iter ( )
187
+ . try_for_each ( |OpaqueDebug ( ( func, body) ) | {
188
+ // Reusing the allocations between validations only results in an ~6% performance improvement
189
+ // The parallelization is blowing this out of the water by a magnitude of 5x
190
+ // Especially when combining this with a high-performance allocator, the missing buffer reuse will be pretty much irrelevant
191
+ let mut validator = func. into_validator ( Default :: default ( ) ) ;
192
+ validator. validate ( & body) ?;
193
+ Ok ( ( ) )
194
+ } ) ;
195
+
196
+ self . func_validator = match result {
197
+ Ok ( ( ) ) => FunctionValidator :: Success ,
198
+ Err ( err) => FunctionValidator :: Error ( err) ,
199
+ } ;
200
+
201
+ self . validate_funcs ( )
202
+ }
203
+ FunctionValidator :: Success => Ok ( ( ) ) ,
204
+ FunctionValidator :: Error ( ref err) => Err ( err. clone ( ) . into ( ) ) ,
205
+ }
206
+ }
150
207
}
0 commit comments