1#![no_std]
2#![doc(test(
3 no_crate_inject,
4 attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables))
5))]
6#![warn(missing_docs, rust_2018_idioms, unreachable_pub)]
7#![forbid(unsafe_code)]
8extern crate alloc;
11
12#[cfg(feature = "std")]
13extern crate std;
14
15#[cfg(feature = "log")]
17#[allow(clippy::single_component_path_imports, unused_imports)]
18use log;
19
20#[cfg(not(feature = "log"))]
22#[allow(unused_imports, unused_macros)]
23pub(crate) mod log {
24 macro_rules! debug ( ($($tt:tt)*) => {{}} );
25 macro_rules! info ( ($($tt:tt)*) => {{}} );
26 macro_rules! error ( ($($tt:tt)*) => {{}} );
27 pub(crate) use debug;
28 pub(crate) use error;
29 pub(crate) use info;
30}
31
32mod conversion;
33mod error;
34mod macros;
35mod module;
36mod optimize;
37mod visit;
38
39#[cfg(parallel_parser)]
40mod parallel;
41
42pub use error::*;
43use module::ModuleReader;
44use wasmparser::{Validator, WasmFeatures};
45
46pub use tinywasm_types::Module;
47
48#[non_exhaustive]
50#[derive(Debug, Clone)]
51pub struct ParserOptions {
52 pub validation: bool,
57 pub optimize_local_memory_allocation: bool,
59 pub optimize_rewrite: bool,
61
62 #[cfg(parallel_parser)]
63 pub parser_threads: Option<usize>,
71}
72
73impl Default for ParserOptions {
74 fn default() -> Self {
75 Self {
76 validation: true,
77 optimize_local_memory_allocation: true,
78 optimize_rewrite: true,
79 #[cfg(parallel_parser)]
80 parser_threads: None,
81 }
82 }
83}
84
85impl ParserOptions {
86 pub const fn with_validation(mut self, enabled: bool) -> Self {
91 self.validation = enabled;
92 self
93 }
94
95 pub const fn validation(&self) -> bool {
97 self.validation
98 }
99
100 pub const fn with_local_memory_allocation_optimization(mut self, enabled: bool) -> Self {
102 self.optimize_local_memory_allocation = enabled;
103 self
104 }
105
106 pub const fn optimize_local_memory_allocation(&self) -> bool {
108 self.optimize_local_memory_allocation
109 }
110
111 pub const fn with_rewrite_optimization(mut self, enabled: bool) -> Self {
113 self.optimize_rewrite = enabled;
114 self
115 }
116
117 pub const fn optimize_rewrite(&self) -> bool {
119 self.optimize_rewrite
120 }
121
122 #[cfg(parallel_parser)]
123 pub const fn with_parser_threads(mut self, threads: usize) -> Self {
127 self.parser_threads = Some(threads);
128 self
129 }
130
131 #[cfg(parallel_parser)]
132 pub const fn parser_threads(&self) -> Option<usize> {
134 self.parser_threads
135 }
136}
137
138#[derive(Debug, Default)]
140pub struct Parser {
141 options: ParserOptions,
142}
143
144impl Parser {
145 pub fn new() -> Self {
147 Self::default()
148 }
149
150 pub fn with_options(options: ParserOptions) -> Self {
152 Self { options }
153 }
154
155 pub const fn options(&self) -> &ParserOptions {
157 &self.options
158 }
159
160 fn create_validator(_options: ParserOptions) -> Validator {
161 let features = WasmFeatures::CALL_INDIRECT_OVERLONG
162 | WasmFeatures::BULK_MEMORY_OPT
163 | WasmFeatures::RELAXED_SIMD
164 | WasmFeatures::GC_TYPES
165 | WasmFeatures::REFERENCE_TYPES
166 | WasmFeatures::MUTABLE_GLOBAL
167 | WasmFeatures::MULTI_VALUE
168 | WasmFeatures::FLOATS
169 | WasmFeatures::BULK_MEMORY
170 | WasmFeatures::SATURATING_FLOAT_TO_INT
171 | WasmFeatures::SIGN_EXTENSION
172 | WasmFeatures::EXTENDED_CONST
173 | WasmFeatures::FUNCTION_REFERENCES
174 | WasmFeatures::TAIL_CALL
175 | WasmFeatures::MULTI_MEMORY
176 | WasmFeatures::SIMD
177 | WasmFeatures::MEMORY64
178 | WasmFeatures::CUSTOM_PAGE_SIZES
179 | WasmFeatures::WIDE_ARITHMETIC;
180 Validator::new_with_features(features)
181 }
182
183 #[cfg(feature = "std")]
184 fn read_more(stream: &mut impl std::io::Read, buffer: &mut alloc::vec::Vec<u8>, hint: usize) -> Result<usize> {
185 let len = buffer.len();
186 buffer.resize(len + hint, 0);
187 let read_bytes = stream
188 .read(&mut buffer[len..])
189 .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?;
190 buffer.truncate(len + read_bytes);
191 Ok(read_bytes)
192 }
193
194 pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result<Module> {
196 let wasm = wasm.as_ref();
197 let mut validator = self.options.validation().then(|| Self::create_validator(self.options.clone()));
198 let mut reader = ModuleReader::default();
199
200 for payload in wasmparser::Parser::new(0).parse_all(wasm) {
201 match payload? {
202 wasmparser::Payload::CodeSectionStart { count, range, size } => {
203 reader.begin_code_section(count, range, size, validator.as_mut(), &self.options)?;
204 }
205 wasmparser::Payload::CodeSectionEntry(function) => {
206 reader.process_borrowed_code_section_entry(function, validator.as_mut(), &self.options)?;
207 }
208 payload => reader.process_payload(payload, validator.as_mut())?,
209 }
210 }
211
212 if !reader.end_reached {
213 return Err(ParseError::EndNotReached);
214 }
215
216 reader.process_pending_functions(&self.options)?;
217 reader.into_module(&self.options)
218 }
219
220 #[cfg(feature = "std")]
221 pub fn parse_module_file(&self, path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
223 let file = crate::std::fs::File::open(&path)
224 .map_err(|e| ParseError::Other(alloc::format!("Error opening file {:?}: {}", path.as_ref(), e)))?;
225 self.parse_module_stream(&mut crate::std::io::BufReader::new(file))
226 }
227
228 #[cfg(feature = "std")]
229 pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result<Module> {
231 let mut validator = self.options.validation().then(|| Self::create_validator(self.options.clone()));
232 let mut reader = ModuleReader::default();
233 let mut buffer = alloc::vec::Vec::new();
234 let mut parser = wasmparser::Parser::new(0);
235 let mut eof = false;
236 let mut buffer_offset = 0;
237
238 loop {
239 match parser.parse(&buffer[buffer_offset..], eof)? {
240 wasmparser::Chunk::NeedMoreData(hint) => {
241 if buffer_offset != 0 {
242 buffer.copy_within(buffer_offset.., 0);
243 buffer.truncate(buffer.len() - buffer_offset);
244 buffer_offset = 0;
245 }
246 let read_bytes = Self::read_more(&mut stream, &mut buffer, hint as usize)?;
247 eof = read_bytes == 0;
248 }
249 wasmparser::Chunk::Parsed { consumed, payload } => {
250 #[cfg(parallel_parser)]
251 let mut deferred_code_section = None;
252
253 match payload {
254 wasmparser::Payload::CodeSectionStart { count, range, size } => {
255 let defer = reader.begin_code_section(
256 count,
257 range.clone(),
258 size,
259 validator.as_mut(),
260 &self.options,
261 )?;
262
263 #[cfg(parallel_parser)]
264 if defer {
265 deferred_code_section = Some((count, range.end - size as usize, size as usize));
266 }
267
268 #[cfg(not(parallel_parser))]
269 let _ = defer;
270 }
271 wasmparser::Payload::CodeSectionEntry(function) => {
272 reader.process_inline_code_section_entry(function, validator.as_mut(), &self.options)?;
273 }
274 payload => {
275 reader.process_payload(payload, validator.as_mut())?;
276 }
277 }
278 buffer_offset += consumed;
279
280 #[cfg(parallel_parser)]
281 if let Some((count, body_offset, section_size)) = deferred_code_section {
282 while buffer.len() - buffer_offset < section_size {
283 let remaining = section_size - (buffer.len() - buffer_offset);
284 let read_bytes = Self::read_more(&mut stream, &mut buffer, remaining)?;
285 if read_bytes == 0 {
286 return Err(ParseError::ParseError {
287 message: "unexpected end-of-file".into(),
288 offset: body_offset + buffer.len() - buffer_offset,
289 });
290 }
291 }
292
293 let section_end = buffer_offset + section_size;
294 let section_bytes = alloc::sync::Arc::<[u8]>::from(buffer[buffer_offset..section_end].to_vec());
295 reader.queue_owned_code_section(count, body_offset, section_bytes, validator.as_mut())?;
296 parser.skip_section();
297 buffer_offset = section_end;
298 continue;
299 }
300
301 if reader.end_reached {
302 if buffer_offset != buffer.len() {
303 return Err(ParseError::Other("trailing bytes after end of module".into()));
304 }
305
306 if !eof {
307 let read_bytes = Self::read_more(&mut stream, &mut buffer, 1)?;
308 eof = read_bytes == 0;
309
310 if !eof {
311 return Err(ParseError::Other("trailing bytes after end of module".into()));
312 }
313 }
314 }
315
316 if reader.end_reached || eof {
317 reader.process_pending_functions(&self.options)?;
318 return reader.into_module(&self.options);
319 }
320 }
321 };
322 }
323 }
324}
325
326impl TryFrom<ModuleReader<'_>> for Module {
327 type Error = ParseError;
328
329 fn try_from(reader: ModuleReader<'_>) -> Result<Self> {
330 reader.into_module(&ParserOptions::default())
331 }
332}
333
334pub fn parse_bytes(wasm: &[u8]) -> Result<Module> {
336 Parser::new().parse_module_bytes(wasm)
337}
338
339#[cfg(feature = "std")]
340pub fn parse_file(path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
342 Parser::new().parse_module_file(path)
343}
344
345#[cfg(feature = "std")]
346pub fn parse_stream(stream: impl crate::std::io::Read) -> Result<Module> {
348 Parser::new().parse_module_stream(stream)
349}