summaryrefslogtreecommitdiff
path: root/crates/parser/src/lib.rs
diff options
context:
space:
mode:
authorHenry <mail@henrygressmann.de>2026-04-26 14:49:09 +0200
committerHenry <mail@henrygressmann.de>2026-04-26 14:49:09 +0200
commit40bd20bb77b611f7974036c9f2b152a16a46c32a (patch)
treec703b2fab3302b3ccbd7a202b9c2a9b3178ea49d /crates/parser/src/lib.rs
parentcee820e5545c1fb9b423b915cf688414069cc960 (diff)
feat: multithreaded wasm parser
Signed-off-by: Henry <mail@henrygressmann.de>
Diffstat (limited to 'crates/parser/src/lib.rs')
-rw-r--r--crates/parser/src/lib.rs121
1 files changed, 109 insertions, 12 deletions
diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs
index 4eda84b..c4f6ff7 100644
--- a/crates/parser/src/lib.rs
+++ b/crates/parser/src/lib.rs
@@ -35,6 +35,10 @@ mod macros;
mod module;
mod optimize;
mod visit;
+
+#[cfg(parallel_parser)]
+mod parallel;
+
pub use error::*;
use module::ModuleReader;
use wasmparser::{Validator, WasmFeatures};
@@ -51,11 +55,27 @@ pub struct ParserOptions {
pub optimize_rewrite: bool,
/// Whether to remove `Nop` and `MergeBarrier` instructions after rewriting.
pub optimize_remove_nop: bool,
+
+ #[cfg(parallel_parser)]
+ /// Number of threads to use for parallel parsing.
+ ///
+ /// Requires the `parallel` feature. Ignored when the feature is disabled.
+ ///
+ /// - `None`: auto-detect based on available parallelism
+ /// - `Some(1)`: force single-threaded
+ /// - `Some(n)`: use up to `n` workers
+ pub parser_threads: Option<usize>,
}
impl Default for ParserOptions {
fn default() -> Self {
- Self { optimize_local_memory_allocation: true, optimize_rewrite: true, optimize_remove_nop: true }
+ Self {
+ optimize_local_memory_allocation: true,
+ optimize_rewrite: true,
+ optimize_remove_nop: true,
+ #[cfg(parallel_parser)]
+ parser_threads: None,
+ }
}
}
@@ -92,6 +112,21 @@ impl ParserOptions {
pub const fn optimize_remove_nop(&self) -> bool {
self.optimize_remove_nop
}
+
+ #[cfg(parallel_parser)]
+ /// Set the number of threads for parallel parsing.
+ ///
+ /// Requires the `parallel` feature to have any effect.
+ pub const fn with_parser_threads(mut self, threads: usize) -> Self {
+ self.parser_threads = Some(threads);
+ self
+ }
+
+ #[cfg(parallel_parser)]
+ /// Returns the configured parser thread count, or `None` for auto-detect.
+ pub const fn parser_threads(&self) -> Option<usize> {
+ self.parser_threads
+ }
}
/// A WebAssembly parser
@@ -139,6 +174,17 @@ impl Parser {
Validator::new_with_features(features)
}
+ #[cfg(feature = "std")]
+ fn read_more(stream: &mut impl std::io::Read, buffer: &mut alloc::vec::Vec<u8>, hint: usize) -> Result<usize> {
+ let len = buffer.len();
+ buffer.extend((0..hint).map(|_| 0u8));
+ let read_bytes = stream
+ .read(&mut buffer[len..])
+ .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?;
+ buffer.truncate(len + read_bytes);
+ Ok(read_bytes)
+ }
+
/// Parse a [`Module`] from bytes
pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result<Module> {
let wasm = wasm.as_ref();
@@ -146,13 +192,22 @@ impl Parser {
let mut reader = ModuleReader::default();
for payload in wasmparser::Parser::new(0).parse_all(wasm) {
- reader.process_payload(payload?, &mut validator)?;
+ match payload? {
+ wasmparser::Payload::CodeSectionStart { count, range, size } => {
+ reader.begin_code_section(count, range, size, &mut validator, &self.options)?;
+ }
+ wasmparser::Payload::CodeSectionEntry(function) => {
+ reader.process_borrowed_code_section_entry(function, &mut validator, &self.options)?;
+ }
+ payload => reader.process_payload(payload, &mut validator)?,
+ }
}
if !reader.end_reached {
return Err(ParseError::EndNotReached);
}
+ reader.process_pending_functions(&self.options)?;
reader.into_module(&self.options)
}
@@ -176,18 +231,60 @@ impl Parser {
loop {
match parser.parse(&buffer, eof)? {
wasmparser::Chunk::NeedMoreData(hint) => {
- let len = buffer.len();
- buffer.extend((0..hint).map(|_| 0u8));
- let read_bytes = stream
- .read(&mut buffer[len..])
- .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?;
- buffer.truncate(len + read_bytes);
+ let read_bytes = Self::read_more(&mut stream, &mut buffer, hint as usize)?;
eof = read_bytes == 0;
}
wasmparser::Chunk::Parsed { consumed, payload } => {
- reader.process_payload(payload, &mut validator)?;
- buffer.drain(..consumed);
+ #[cfg(parallel_parser)]
+ let mut deferred_code_section = None;
+
+ match payload {
+ wasmparser::Payload::CodeSectionStart { count, range, size } => {
+ let defer =
+ reader.begin_code_section(count, range.clone(), size, &mut validator, &self.options)?;
+
+ #[cfg(parallel_parser)]
+ if defer {
+ deferred_code_section = Some((count, range.end - size as usize, size as usize));
+ }
+
+ #[cfg(not(parallel_parser))]
+ let _ = defer;
+
+ buffer.drain(..consumed);
+ }
+ wasmparser::Payload::CodeSectionEntry(function) => {
+ reader.process_inline_code_section_entry(function, &mut validator, &self.options)?;
+ buffer.drain(..consumed);
+ }
+ payload => {
+ reader.process_payload(payload, &mut validator)?;
+ buffer.drain(..consumed);
+ }
+ }
+
+ #[cfg(parallel_parser)]
+ if let Some((count, body_offset, section_size)) = deferred_code_section {
+ while buffer.len() < section_size {
+ let remaining = section_size - buffer.len();
+ let read_bytes = Self::read_more(&mut stream, &mut buffer, remaining)?;
+ if read_bytes == 0 {
+ return Err(ParseError::ParseError {
+ message: "unexpected end-of-file".into(),
+ offset: body_offset + buffer.len(),
+ });
+ }
+ }
+
+ let section_bytes = alloc::sync::Arc::<[u8]>::from(buffer[..section_size].to_vec());
+ reader.queue_owned_code_section(count, body_offset, section_bytes, &mut validator)?;
+ parser.skip_section();
+ buffer.drain(..section_size);
+ continue;
+ }
+
if eof || reader.end_reached {
+ reader.process_pending_functions(&self.options)?;
return reader.into_module(&self.options);
}
}
@@ -196,10 +293,10 @@ impl Parser {
}
}
-impl TryFrom<ModuleReader> for Module {
+impl TryFrom<ModuleReader<'_>> for Module {
type Error = ParseError;
- fn try_from(reader: ModuleReader) -> Result<Self> {
+ fn try_from(reader: ModuleReader<'_>) -> Result<Self> {
reader.into_module(&ParserOptions::default())
}
}