Skip to content

Commit 57517b2

Browse files
authored
Merge pull request bytecodealliance#25 from Robbepop/try-to-fix-wasmparser-benchmarks-2
Try to fix benchmark tests - bytecodealliance#2
2 parents b637e89 + beff3e9 commit 57517b2

1 file changed

Lines changed: 179 additions & 36 deletions

File tree

crates/wasmparser/benches/benchmark.rs

Lines changed: 179 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#[macro_use]
22
extern crate criterion;
33

4-
use criterion::Criterion;
5-
use std::fs::{read_dir, File};
6-
use std::io::Read;
4+
use criterion::{black_box, Criterion};
5+
use std::fs;
6+
use std::io;
77
use std::path::Path;
88
use std::path::PathBuf;
99
use wasmparser::{
@@ -22,65 +22,208 @@ const VALIDATOR_CONFIG: Option<ValidatingParserConfig> = Some(ValidatingParserCo
2222
},
2323
});
2424

25-
fn read_all_wasm<'a, T>(mut d: T)
25+
/// A benchmark input.
26+
pub struct BenchmarkInput {
27+
/// The path to the benchmark file important for handling errors.
28+
pub path: PathBuf,
29+
/// The encoded Wasm module that is run by the benchmark.
30+
pub wasm: Vec<u8>,
31+
}
32+
33+
impl BenchmarkInput {
34+
/// Creates a new benchmark input.
35+
pub fn new(test_path: PathBuf, encoded_wasm: Vec<u8>) -> Self {
36+
Self {
37+
path: test_path,
38+
wasm: encoded_wasm,
39+
}
40+
}
41+
}
42+
43+
/// Read a `.wat` formatted benchmark test file as benchmark input.
44+
fn read_wat_module(path: &PathBuf) -> BenchmarkInput {
45+
let encoded_wasm =
46+
wat::parse_file(path).expect("encountered error while parsing `.wat` file into `.wasm`");
47+
BenchmarkInput::new(path.clone(), encoded_wasm)
48+
}
49+
50+
/// Read a `.wast` formatted benchmark test file as benchmark input.
51+
///
52+
/// We simply pull out all the module directives of the `.wast` file and return them.
53+
fn read_wast_module(path: &PathBuf) -> Vec<BenchmarkInput> {
54+
let mut wast_file = fs::File::open(path)
55+
.ok()
56+
.expect("encountered error while reading `.wast` benchmark file");
57+
let mut wast_file_contents = String::new();
58+
use io::Read as _;
59+
wast_file
60+
.read_to_string(&mut wast_file_contents)
61+
.expect("encountered error while reading `.wast` benchmark file to string");
62+
let mut inputs = Vec::new();
63+
if let Ok(parse_buffer) = wast::parser::ParseBuffer::new(&wast_file_contents) {
64+
'outer: while let Ok(directive) = wast::parser::parse::<wast::WastDirective>(&parse_buffer)
65+
{
66+
match directive {
67+
wast::WastDirective::Module(mut module) => {
68+
let encoded_wasm = module
69+
.encode()
70+
.expect("encountered error while encoding the Wast module into Wasm");
71+
inputs.push(BenchmarkInput::new(path.clone(), encoded_wasm));
72+
}
73+
_ => continue 'outer,
74+
}
75+
}
76+
}
77+
inputs
78+
}
79+
80+
/// Visits all directory entries within the given directory path.
81+
///
82+
/// - `pred` can be used to filter some directories, e.g. all directories named
83+
/// `"proposals"`.
84+
/// - `cb` is the callback that is being called for every file within the non
85+
/// filtered and visited directories.
86+
fn visit_dirs<P, F>(dir: &Path, pred: &P, cb: &mut F) -> io::Result<()>
2687
where
27-
T: WasmDecoder<'a>,
88+
P: Fn(&fs::DirEntry) -> bool,
89+
F: FnMut(&fs::DirEntry),
2890
{
29-
loop {
30-
match *d.read() {
31-
ParserState::Error(ref e) => panic!("unexpected error while reading Wasm: {}", e),
32-
ParserState::EndWasm => return,
33-
_ => (),
91+
if dir.is_dir() {
92+
for entry in fs::read_dir(dir)? {
93+
let entry = entry?;
94+
let path = entry.path();
95+
if path.is_dir() && pred(&entry) {
96+
visit_dirs(&path, pred, cb)?;
97+
} else {
98+
cb(&entry);
99+
}
34100
}
35101
}
102+
Ok(())
36103
}
37104

38-
fn read_file_data(path: &PathBuf) -> Vec<u8> {
39-
let mut data = Vec::new();
40-
let mut f = File::open(path).ok().unwrap();
41-
f.read_to_end(&mut data).unwrap();
42-
data
105+
/// Returns a vector of all found benchmark input files under the given directory.
106+
///
107+
/// Benchmark input files can be `.wat` or `.wast` formatted files.
108+
/// For `.wast` files we pull out all the module directives and run them in the benchmarks.
109+
fn collect_test_files<P>(path: P) -> Vec<BenchmarkInput>
110+
where
111+
P: AsRef<Path>,
112+
{
113+
let mut file_contents: Vec<BenchmarkInput> = vec![];
114+
visit_dirs(
115+
path.as_ref(),
116+
&|_| true, // accept all benchmarks
117+
&mut |dir_entry| {
118+
let ext: Option<String> = dir_entry
119+
.path()
120+
.extension()
121+
.and_then(|ext| ext.to_str().map(|str| str.to_string()));
122+
match ext.as_ref().map(|string| string.as_str()) {
123+
Some("wat") => file_contents.push(read_wat_module(&dir_entry.path())),
124+
Some("wast") => {
125+
for wasm_module in read_wast_module(&dir_entry.path()) {
126+
file_contents.push(wasm_module)
127+
}
128+
}
129+
_ => (),
130+
}
131+
},
132+
)
133+
.expect("encountered error while reading test directory");
134+
file_contents
43135
}
44136

45-
fn collect_test_files<P>(path: P) -> Vec<Vec<u8>>
137+
/// Reads the input given the Wasm parser or validator.
138+
///
139+
/// The `path` specifies which benchmark input file we are currently operating on
140+
/// so that we can report better errors in case of failures.
141+
fn read_all_wasm<'a, T>(path: &PathBuf, mut d: T)
46142
where
47-
P: AsRef<Path>,
143+
T: WasmDecoder<'a>,
48144
{
49-
let mut file_contents: Vec<Vec<u8>> = vec![];
50-
for entry in read_dir(path).expect("cannot find the benchmark test files") {
51-
let dir = entry.unwrap();
52-
if !dir.file_type().unwrap().is_file() {
53-
continue;
145+
loop {
146+
match *d.read() {
147+
ParserState::Error(ref e) => {
148+
panic!("unexpected error while reading Wasm from {:?}: {}", path, e)
149+
}
150+
ParserState::EndWasm => return,
151+
_ => (),
54152
}
55-
file_contents.push(read_file_data(&dir.path()));
56153
}
57-
file_contents
154+
}
155+
156+
/// Returns the default benchmark inputs that are proper `wasmparser` benchmark
157+
/// test inputs.
158+
fn collect_benchmark_inputs() -> Vec<BenchmarkInput> {
159+
let from_testsuite = collect_test_files("../../testsuite");
160+
let from_tests = collect_test_files("../../tests");
161+
from_testsuite
162+
.into_iter()
163+
.chain(from_tests.into_iter())
164+
.collect::<Vec<_>>()
58165
}
59166

60167
fn it_works_benchmark(c: &mut Criterion) {
61-
let mut data = collect_test_files("../../tests");
62-
c.bench_function("it works benchmark", move |b| {
63-
for d in &mut data {
64-
b.iter(|| read_all_wasm(Parser::new(d.as_slice())));
168+
let mut inputs = collect_benchmark_inputs();
169+
// Filter out all benchmark inputs that fail to parse via `wasmparser`.
170+
inputs.retain(|input| {
171+
let mut parser = Parser::new(input.wasm.as_slice());
172+
'outer: loop {
173+
match parser.read() {
174+
ParserState::Error(_) => break 'outer false,
175+
ParserState::EndWasm => break 'outer true,
176+
_ => continue,
177+
}
65178
}
66179
});
180+
c.bench_function("it works benchmark", move |b| {
181+
b.iter(|| {
182+
for input in &mut inputs {
183+
let _ = black_box(read_all_wasm(
184+
&input.path,
185+
Parser::new(input.wasm.as_slice()),
186+
));
187+
}
188+
})
189+
});
67190
}
68191

69192
fn validator_not_fails_benchmark(c: &mut Criterion) {
70-
let mut data = collect_test_files("../../tests");
71-
c.bench_function("validator no fails benchmark", move |b| {
72-
for d in &mut data {
73-
b.iter(|| read_all_wasm(ValidatingParser::new(d.as_slice(), VALIDATOR_CONFIG)));
193+
let mut inputs = collect_benchmark_inputs();
194+
// Filter out all benchmark inputs that fail to validate via `wasmparser`.
195+
inputs.retain(|input| {
196+
let mut parser = ValidatingParser::new(input.wasm.as_slice(), VALIDATOR_CONFIG);
197+
'outer: loop {
198+
match parser.read() {
199+
ParserState::Error(_) => break 'outer false,
200+
ParserState::EndWasm => break 'outer true,
201+
_ => continue,
202+
}
74203
}
75204
});
205+
c.bench_function("validator no fails benchmark", move |b| {
206+
b.iter(|| {
207+
for input in &mut inputs {
208+
let _ = black_box(read_all_wasm(
209+
&input.path,
210+
ValidatingParser::new(input.wasm.as_slice(), VALIDATOR_CONFIG),
211+
));
212+
}
213+
});
214+
});
76215
}
77216

78217
fn validate_benchmark(c: &mut Criterion) {
79-
let mut data = collect_test_files("../../tests");
218+
let mut inputs = collect_benchmark_inputs();
219+
// Filter out all benchmark inputs that fail to validate via `wasmparser`.
220+
inputs.retain(|input| validate(input.wasm.as_slice(), VALIDATOR_CONFIG).is_ok());
80221
c.bench_function("validate benchmark", move |b| {
81-
for d in &mut data {
82-
b.iter(|| validate(&d, VALIDATOR_CONFIG));
83-
}
222+
b.iter(|| {
223+
for input in &mut inputs {
224+
let _ = black_box(validate(input.wasm.as_slice(), VALIDATOR_CONFIG));
225+
}
226+
})
84227
});
85228
}
86229

0 commit comments

Comments
 (0)