summaryrefslogtreecommitdiffstats
path: root/third_party/rust/wasmparser/benches
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/wasmparser/benches')
-rw-r--r--third_party/rust/wasmparser/benches/benchmark.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/third_party/rust/wasmparser/benches/benchmark.rs b/third_party/rust/wasmparser/benches/benchmark.rs
new file mode 100644
index 0000000000..5fb7b83905
--- /dev/null
+++ b/third_party/rust/wasmparser/benches/benchmark.rs
@@ -0,0 +1,350 @@
+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, HeapType, Parser, Payload, ValType, 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::Wat(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(_) => {}
+ }
+ }
+ 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 define_benchmarks(c: &mut Criterion) {
+ fn validator() -> Validator {
+ Validator::new_with_features(WasmFeatures {
+ reference_types: true,
+ multi_value: true,
+ simd: true,
+ relaxed_simd: true,
+ exceptions: true,
+ component_model: true,
+ bulk_memory: true,
+ threads: true,
+ tail_call: true,
+ multi_memory: true,
+ memory64: true,
+ extended_const: true,
+ floats: true,
+ mutable_global: true,
+ saturating_float_to_int: true,
+ sign_extension: true,
+ function_references: true,
+ memory_control: true,
+ })
+ }
+
+ 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 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)*) => {
+ $(
+ 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);
+}