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 optimize_local_memory_allocation: bool,
54 pub optimize_rewrite: bool,
56 pub optimize_remove_nop: bool,
58
59 #[cfg(parallel_parser)]
60 pub parser_threads: Option<usize>,
68}
69
70impl Default for ParserOptions {
71 fn default() -> Self {
72 Self {
73 optimize_local_memory_allocation: true,
74 optimize_rewrite: true,
75 optimize_remove_nop: true,
76 #[cfg(parallel_parser)]
77 parser_threads: None,
78 }
79 }
80}
81
82impl ParserOptions {
83 pub const fn with_local_memory_allocation_optimization(mut self, enabled: bool) -> Self {
85 self.optimize_local_memory_allocation = enabled;
86 self
87 }
88
89 pub const fn optimize_local_memory_allocation(&self) -> bool {
91 self.optimize_local_memory_allocation
92 }
93
94 pub const fn with_rewrite_optimization(mut self, enabled: bool) -> Self {
96 self.optimize_rewrite = enabled;
97 self
98 }
99
100 pub const fn optimize_rewrite(&self) -> bool {
102 self.optimize_rewrite
103 }
104
105 pub const fn with_nop_removal_optimization(mut self, enabled: bool) -> Self {
107 self.optimize_remove_nop = enabled;
108 self
109 }
110
111 pub const fn optimize_remove_nop(&self) -> bool {
113 self.optimize_remove_nop
114 }
115
116 #[cfg(parallel_parser)]
117 pub const fn with_parser_threads(mut self, threads: usize) -> Self {
121 self.parser_threads = Some(threads);
122 self
123 }
124
125 #[cfg(parallel_parser)]
126 pub const fn parser_threads(&self) -> Option<usize> {
128 self.parser_threads
129 }
130}
131
132#[derive(Debug, Default)]
134pub struct Parser {
135 options: ParserOptions,
136}
137
138impl Parser {
139 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn with_options(options: ParserOptions) -> Self {
146 Self { options }
147 }
148
149 pub const fn options(&self) -> &ParserOptions {
151 &self.options
152 }
153
154 fn create_validator(_options: ParserOptions) -> Validator {
155 let features = WasmFeatures::CALL_INDIRECT_OVERLONG
156 | WasmFeatures::BULK_MEMORY_OPT
157 | WasmFeatures::RELAXED_SIMD
158 | WasmFeatures::GC_TYPES
159 | WasmFeatures::REFERENCE_TYPES
160 | WasmFeatures::MUTABLE_GLOBAL
161 | WasmFeatures::MULTI_VALUE
162 | WasmFeatures::FLOATS
163 | WasmFeatures::BULK_MEMORY
164 | WasmFeatures::SATURATING_FLOAT_TO_INT
165 | WasmFeatures::SIGN_EXTENSION
166 | WasmFeatures::EXTENDED_CONST
167 | WasmFeatures::FUNCTION_REFERENCES
168 | WasmFeatures::TAIL_CALL
169 | WasmFeatures::MULTI_MEMORY
170 | WasmFeatures::SIMD
171 | WasmFeatures::MEMORY64
172 | WasmFeatures::CUSTOM_PAGE_SIZES
173 | WasmFeatures::WIDE_ARITHMETIC;
174 Validator::new_with_features(features)
175 }
176
177 #[cfg(feature = "std")]
178 fn read_more(stream: &mut impl std::io::Read, buffer: &mut alloc::vec::Vec<u8>, hint: usize) -> Result<usize> {
179 let len = buffer.len();
180 buffer.extend((0..hint).map(|_| 0u8));
181 let read_bytes = stream
182 .read(&mut buffer[len..])
183 .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?;
184 buffer.truncate(len + read_bytes);
185 Ok(read_bytes)
186 }
187
188 pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result<Module> {
190 let wasm = wasm.as_ref();
191 let mut validator = Self::create_validator(self.options.clone());
192 let mut reader = ModuleReader::default();
193
194 for payload in wasmparser::Parser::new(0).parse_all(wasm) {
195 match payload? {
196 wasmparser::Payload::CodeSectionStart { count, range, size } => {
197 reader.begin_code_section(count, range, size, &mut validator, &self.options)?;
198 }
199 wasmparser::Payload::CodeSectionEntry(function) => {
200 reader.process_borrowed_code_section_entry(function, &mut validator, &self.options)?;
201 }
202 payload => reader.process_payload(payload, &mut validator)?,
203 }
204 }
205
206 if !reader.end_reached {
207 return Err(ParseError::EndNotReached);
208 }
209
210 reader.process_pending_functions(&self.options)?;
211 reader.into_module(&self.options)
212 }
213
214 #[cfg(feature = "std")]
215 pub fn parse_module_file(&self, path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
217 let file = crate::std::fs::File::open(&path)
218 .map_err(|e| ParseError::Other(alloc::format!("Error opening file {:?}: {}", path.as_ref(), e)))?;
219 self.parse_module_stream(&mut crate::std::io::BufReader::new(file))
220 }
221
222 #[cfg(feature = "std")]
223 pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result<Module> {
225 let mut validator = Self::create_validator(self.options.clone());
226 let mut reader = ModuleReader::default();
227 let mut buffer = alloc::vec::Vec::new();
228 let mut parser = wasmparser::Parser::new(0);
229 let mut eof = false;
230
231 loop {
232 match parser.parse(&buffer, eof)? {
233 wasmparser::Chunk::NeedMoreData(hint) => {
234 let read_bytes = Self::read_more(&mut stream, &mut buffer, hint as usize)?;
235 eof = read_bytes == 0;
236 }
237 wasmparser::Chunk::Parsed { consumed, payload } => {
238 #[cfg(parallel_parser)]
239 let mut deferred_code_section = None;
240
241 match payload {
242 wasmparser::Payload::CodeSectionStart { count, range, size } => {
243 let defer =
244 reader.begin_code_section(count, range.clone(), size, &mut validator, &self.options)?;
245
246 #[cfg(parallel_parser)]
247 if defer {
248 deferred_code_section = Some((count, range.end - size as usize, size as usize));
249 }
250
251 #[cfg(not(parallel_parser))]
252 let _ = defer;
253
254 buffer.drain(..consumed);
255 }
256 wasmparser::Payload::CodeSectionEntry(function) => {
257 reader.process_inline_code_section_entry(function, &mut validator, &self.options)?;
258 buffer.drain(..consumed);
259 }
260 payload => {
261 reader.process_payload(payload, &mut validator)?;
262 buffer.drain(..consumed);
263 }
264 }
265
266 #[cfg(parallel_parser)]
267 if let Some((count, body_offset, section_size)) = deferred_code_section {
268 while buffer.len() < section_size {
269 let remaining = section_size - buffer.len();
270 let read_bytes = Self::read_more(&mut stream, &mut buffer, remaining)?;
271 if read_bytes == 0 {
272 return Err(ParseError::ParseError {
273 message: "unexpected end-of-file".into(),
274 offset: body_offset + buffer.len(),
275 });
276 }
277 }
278
279 let section_bytes = alloc::sync::Arc::<[u8]>::from(buffer[..section_size].to_vec());
280 reader.queue_owned_code_section(count, body_offset, section_bytes, &mut validator)?;
281 parser.skip_section();
282 buffer.drain(..section_size);
283 continue;
284 }
285
286 if eof || reader.end_reached {
287 reader.process_pending_functions(&self.options)?;
288 return reader.into_module(&self.options);
289 }
290 }
291 };
292 }
293 }
294}
295
296impl TryFrom<ModuleReader<'_>> for Module {
297 type Error = ParseError;
298
299 fn try_from(reader: ModuleReader<'_>) -> Result<Self> {
300 reader.into_module(&ParserOptions::default())
301 }
302}
303
304pub fn parse_bytes(wasm: &[u8]) -> Result<Module> {
306 let data = Parser::new().parse_module_bytes(wasm)?;
307 Ok(data)
308}
309
310#[cfg(feature = "std")]
311pub fn parse_file(path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
313 let data = Parser::new().parse_module_file(path)?;
314 Ok(data)
315}
316
317#[cfg(feature = "std")]
318pub fn parse_stream(stream: impl crate::std::io::Read) -> Result<Module> {
320 let data = Parser::new().parse_module_stream(stream)?;
321 Ok(data)
322}