| use anyhow::Result; |
| use criterion::{criterion_group, criterion_main, Criterion}; |
| use once_cell::unsync::Lazy; |
| use std::fs; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use wasmparser::{DataKind, ElementKind, Parser, Payload, Validator, VisitOperator, WasmFeatures}; |
| |
| /// A benchmark input. |
| pub struct BenchmarkInput { |
| /// The path to the benchmark file important for handling errors. |
| pub path: PathBuf, |
| /// The encoded Wasm module that is run by the benchmark. |
| pub wasm: Vec<u8>, |
| } |
| |
| impl BenchmarkInput { |
| /// Creates a new benchmark input. |
| pub fn new(test_path: PathBuf, encoded_wasm: Vec<u8>) -> Self { |
| Self { |
| path: test_path, |
| wasm: encoded_wasm, |
| } |
| } |
| } |
| |
| /// Returns a vector of all found benchmark input files under the given directory. |
| /// |
| /// Benchmark input files can be `.wat` or `.wast` formatted files. |
| /// For `.wast` files we pull out all the module directives and run them in the benchmarks. |
| fn collect_test_files(path: &Path, list: &mut Vec<BenchmarkInput>) -> Result<()> { |
| for entry in path.read_dir()? { |
| let entry = entry?; |
| let path = entry.path(); |
| if path.is_dir() { |
| collect_test_files(&path, list)?; |
| continue; |
| } |
| match path.extension().and_then(|ext| ext.to_str()) { |
| Some("wasm") => { |
| let wasm = fs::read(&path)?; |
| list.push(BenchmarkInput::new(path, wasm)); |
| } |
| Some("wat") | Some("txt") => { |
| if let Ok(wasm) = wat::parse_file(&path) { |
| list.push(BenchmarkInput::new(path, wasm)); |
| } |
| } |
| Some("wast") => { |
| let contents = fs::read_to_string(&path)?; |
| let buf = match wast::parser::ParseBuffer::new(&contents) { |
| Ok(buf) => buf, |
| Err(_) => continue, |
| }; |
| let wast: wast::Wast<'_> = match wast::parser::parse(&buf) { |
| Ok(wast) => wast, |
| Err(_) => continue, |
| }; |
| for directive in wast.directives { |
| match directive { |
| wast::WastDirective::Module(mut module) |
| | wast::WastDirective::ModuleDefinition(mut module) => { |
| let wasm = module.encode()?; |
| list.push(BenchmarkInput::new(path.clone(), wasm)); |
| } |
| _ => continue, |
| } |
| } |
| } |
| _ => (), |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Reads the input given the Wasm parser or validator. |
| /// |
| /// The `path` specifies which benchmark input file we are currently operating on |
| /// so that we can report better errors in case of failures. |
| fn read_all_wasm(wasm: &[u8]) -> Result<()> { |
| use Payload::*; |
| for item in Parser::new(0).parse_all(wasm) { |
| match item? { |
| TypeSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ImportSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| FunctionSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| TableSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| MemorySection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| TagSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| GlobalSection(s) => { |
| for item in s { |
| for op in item?.init_expr.get_operators_reader() { |
| op?; |
| } |
| } |
| } |
| ExportSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ElementSection(s) => { |
| for item in s { |
| let item = item?; |
| if let ElementKind::Active { offset_expr, .. } = item.kind { |
| for op in offset_expr.get_operators_reader() { |
| op?; |
| } |
| } |
| match item.items { |
| wasmparser::ElementItems::Functions(r) => { |
| for op in r { |
| op?; |
| } |
| } |
| wasmparser::ElementItems::Expressions(_, r) => { |
| for op in r { |
| op?; |
| } |
| } |
| } |
| } |
| } |
| DataSection(s) => { |
| for item in s { |
| let item = item?; |
| if let DataKind::Active { offset_expr, .. } = item.kind { |
| for op in offset_expr.get_operators_reader() { |
| op?; |
| } |
| } |
| } |
| } |
| CodeSectionEntry(body) => { |
| let mut reader = body.get_binary_reader(); |
| for _ in 0..reader.read_var_u32()? { |
| reader.read_var_u32()?; |
| reader.read::<wasmparser::ValType>()?; |
| } |
| while !reader.eof() { |
| reader.visit_operator(&mut NopVisit)?; |
| } |
| } |
| |
| // Component sections |
| ModuleSection { .. } => {} |
| InstanceSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| CoreTypeSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentSection { .. } => {} |
| ComponentInstanceSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentAliasSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentTypeSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentCanonicalSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentStartSection { .. } => {} |
| ComponentImportSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| ComponentExportSection(s) => { |
| for item in s { |
| item?; |
| } |
| } |
| |
| Version { .. } |
| | StartSection { .. } |
| | DataCountSection { .. } |
| | UnknownSection { .. } |
| | CustomSection { .. } |
| | CodeSectionStart { .. } |
| | End(_) => {} |
| |
| other => { |
| // NB: if you hit this panic if you'd be so kind as to grep |
| // through other locations in the code base that need to be |
| // updated as well. As of the time of this writing the locations |
| // might be: |
| // |
| // * src/bin/wasm-tools/objdump.rs |
| // * src/bin/wasm-tools/dump.rs |
| // * crates/wasm-encoder/src/reencode.rs |
| // * crates/wasm-encoder/src/reencode/component.rs |
| // * crates/wasmprinter/src/lib.rs |
| // * crates/wit-component/src/gc.rs |
| // |
| // This is required due to the `#[non_exhaustive]` nature of |
| // the `Payload` enum. |
| panic!("a new match statement should be added above for this case: {other:?}") |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Returns the default benchmark inputs that are proper `wasmparser` benchmark |
| /// test inputs. |
| fn collect_benchmark_inputs() -> Vec<BenchmarkInput> { |
| let mut ret = Vec::new(); |
| collect_test_files("../../tests".as_ref(), &mut ret).unwrap(); |
| // Sort to ideally get more deterministic perf that ignores filesystems |
| ret.sort_by_key(|p| p.path.clone()); |
| ret |
| } |
| |
| fn skip_validation(test: &Path) -> bool { |
| let broken = [ |
| "gc/gc-rec-sub.wat", |
| "proposals/gc/type-equivalence.wast", |
| "proposals/gc/type-subtyping.wast", |
| ]; |
| |
| let test_path = test.to_str().unwrap().replace("\\", "/"); // for windows paths |
| if broken.iter().any(|x| test_path.contains(x)) { |
| return true; |
| } |
| |
| false |
| } |
| |
| fn define_benchmarks(c: &mut Criterion) { |
| let _ = env_logger::try_init(); |
| |
| fn validator() -> Validator { |
| Validator::new_with_features(WasmFeatures::all()) |
| } |
| |
| let test_inputs = once_cell::unsync::Lazy::new(collect_benchmark_inputs); |
| |
| let parse_inputs = once_cell::unsync::Lazy::new(|| { |
| let mut list = Vec::new(); |
| for input in test_inputs.iter() { |
| if read_all_wasm(&input.wasm).is_ok() { |
| list.push(&input.wasm); |
| } |
| } |
| list |
| }); |
| c.bench_function("parse/tests", |b| { |
| Lazy::force(&parse_inputs); |
| b.iter(|| { |
| for wasm in parse_inputs.iter() { |
| read_all_wasm(wasm).unwrap(); |
| } |
| }) |
| }); |
| |
| let validate_inputs = once_cell::unsync::Lazy::new(|| { |
| let mut list = Vec::new(); |
| for input in test_inputs.iter() { |
| if skip_validation(&input.path) { |
| continue; |
| } |
| log::debug!("Validating {}", input.path.display()); |
| if validator().validate_all(&input.wasm).is_ok() { |
| list.push(&input.wasm); |
| } |
| } |
| list |
| }); |
| c.bench_function("validate/tests", |b| { |
| Lazy::force(&validate_inputs); |
| b.iter(|| { |
| for wasm in validate_inputs.iter() { |
| validator().validate_all(wasm).unwrap(); |
| } |
| }) |
| }); |
| |
| for file in std::fs::read_dir("benches").unwrap() { |
| let file = file.unwrap(); |
| let path = file.path(); |
| if path.extension().and_then(|s| s.to_str()) != Some("wasm") { |
| continue; |
| } |
| let name = path.file_stem().unwrap().to_str().unwrap(); |
| let wasm = Lazy::new(|| std::fs::read(&path).unwrap()); |
| c.bench_function(&format!("validate/{name}"), |b| { |
| Lazy::force(&wasm); |
| b.iter(|| { |
| validator().validate_all(&wasm).unwrap(); |
| }) |
| }); |
| c.bench_function(&format!("parse/{name}"), |b| { |
| Lazy::force(&wasm); |
| b.iter(|| { |
| read_all_wasm(&wasm).unwrap(); |
| }) |
| }); |
| } |
| } |
| |
| criterion_group!(benchmark, define_benchmarks); |
| criterion_main!(benchmark); |
| |
| struct NopVisit; |
| |
| macro_rules! define_visit_operator { |
| ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { |
| $( |
| fn $visit(&mut self $($(,$arg: $argty)*)?) { |
| define_visit_operator!(@visit $op $( $($arg)* )?); |
| } |
| )* |
| }; |
| |
| (@visit BrTable $table:ident) => { |
| for target in $table.targets() { |
| target.unwrap(); |
| } |
| }; |
| (@visit $($rest:tt)*) => {} |
| } |
| |
| #[allow(unused_variables)] |
| impl<'a> VisitOperator<'a> for NopVisit { |
| type Output = (); |
| |
| wasmparser::for_each_operator!(define_visit_operator); |
| } |