Skip to content

Commit bb9dfc3

Browse files
committed
Improve analyze performance
1 parent 82c9434 commit bb9dfc3

File tree

4 files changed

+74
-45
lines changed

4 files changed

+74
-45
lines changed

Cargo.lock

Lines changed: 7 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ cosmwasm-std = { path = "../std", version = "2.0.0-rc.1", default-features = fal
3737
cosmwasm-crypto = { path = "../crypto", version = "2.0.0-rc.1" }
3838
derivative = "2"
3939
hex = "0.4"
40+
rayon = "1.9.0"
4041
schemars = "0.8.3"
4142
serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] }
4243
serde_json = "1.0.40"

packages/vm/src/compatibility.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ const MAX_FUNCTION_RESULTS: usize = 1;
8181

8282
/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
8383
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
84-
let module = ParsedWasm::parse(wasm_code)?;
84+
let mut module = ParsedWasm::parse(wasm_code)?;
85+
module.validate_funcs()?;
8586

8687
check_wasm_tables(&module)?;
8788
check_wasm_memories(&module)?;

packages/vm/src/parsed_wasm.rs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
1+
use std::{fmt, mem};
2+
3+
use rayon::iter::{IntoParallelIterator, ParallelIterator};
14
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,
47
};
58

69
use crate::{VmError, VmResult};
710

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+
837
/// A parsed and validated wasm module.
938
/// It keeps track of the parts that are important for our static analysis and compatibility checks.
1039
#[derive(Debug)]
@@ -25,6 +54,8 @@ pub struct ParsedWasm<'a> {
2554
pub max_func_results: usize,
2655
/// How many function parameters are used in the module
2756
pub total_func_params: usize,
57+
/// Collections of functions that are potentially pending validation
58+
pub func_validator: FunctionValidator<'a>,
2859
}
2960

3061
impl<'a> ParsedWasm<'a> {
@@ -66,18 +97,15 @@ impl<'a> ParsedWasm<'a> {
6697
max_func_params: 0,
6798
max_func_results: 0,
6899
total_func_params: 0,
100+
func_validator: FunctionValidator::Pending(Vec::new()),
69101
};
70102

71-
let mut fun_allocations = Default::default();
72103
for p in Parser::new(0).parse_all(wasm) {
73104
let p = p?;
74105
// validate the payload
75106
if let ValidPayload::Func(fv, body) = validator.payload(&p)? {
76107
// 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));
81109
this.function_count += 1;
82110
}
83111

@@ -147,4 +175,33 @@ impl<'a> ParsedWasm<'a> {
147175

148176
Ok(this)
149177
}
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+
}
150207
}

0 commit comments

Comments
 (0)