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 emitter;
34mod error;
35mod macros;
36mod module;
37mod selection;
38mod validation;
39mod visit;
40
41#[cfg(parallel_parser)]
42mod parallel;
43
44pub use error::*;
45use module::ModuleReader;
46use validation::Validator;
47
48#[cfg(feature = "validate")]
49use wasmparser::WasmFeatures;
50
51pub use tinywasm_types::Module;
52
53#[non_exhaustive]
55#[derive(Debug, Clone)]
56pub struct ParserOptions {
57 pub validation: bool,
65
66 pub optimize: bool,
68
69 pub deduplicate_operands: bool,
71
72 #[cfg(parallel_parser)]
73 pub threads: Option<usize>,
81}
82
83impl Default for ParserOptions {
84 fn default() -> Self {
85 Self {
86 validation: cfg!(feature = "validate"),
87 optimize: true,
88 deduplicate_operands: true,
89 #[cfg(parallel_parser)]
90 threads: None,
91 }
92 }
93}
94
95impl ParserOptions {
96 pub fn new() -> Self {
98 Self::default()
99 }
100
101 pub const fn with_validation(mut self, enabled: bool) -> Self {
108 assert!(!enabled || cfg!(feature = "validate"), "validation requires the `validate` feature");
109 self.validation = enabled;
110 self
111 }
112
113 pub const fn validation(&self) -> bool {
115 self.validation
116 }
117
118 pub const fn with_optimize(mut self, enabled: bool) -> Self {
120 self.optimize = enabled;
121 self
122 }
123
124 pub const fn optimize(&self) -> bool {
126 self.optimize
127 }
128
129 pub const fn with_operand_deduplication(mut self, enabled: bool) -> Self {
131 self.deduplicate_operands = enabled;
132 self
133 }
134
135 pub const fn deduplicate_operands(&self) -> bool {
137 self.deduplicate_operands
138 }
139
140 #[cfg(parallel_parser)]
141 pub const fn with_threads(mut self, threads: usize) -> Self {
145 self.threads = Some(threads);
146 self
147 }
148
149 #[cfg(parallel_parser)]
150 pub const fn threads(&self) -> Option<usize> {
152 self.threads
153 }
154}
155
156#[derive(Debug, Default)]
158pub struct Parser {
159 options: ParserOptions,
160}
161
162impl Parser {
163 pub const fn new(options: ParserOptions) -> Self {
165 Self { options }
166 }
167
168 pub const fn options(&self) -> &ParserOptions {
170 &self.options
171 }
172
173 fn validator(&self) -> Option<Validator> {
174 #[cfg(feature = "validate")]
175 {
176 let features = WasmFeatures::WASM3
177 .difference(WasmFeatures::THREADS)
178 .union(WasmFeatures::CUSTOM_PAGE_SIZES)
179 .union(WasmFeatures::WIDE_ARITHMETIC)
180 .union(WasmFeatures::COMPACT_IMPORTS);
181 self.options.validation().then(|| Validator::new_with_features(features))
182 }
183 #[cfg(not(feature = "validate"))]
184 {
185 assert!(!self.options.validation(), "validation requires the `validate` feature");
186 None
187 }
188 }
189
190 #[cfg(feature = "std")]
191 fn read_more(stream: &mut impl std::io::Read, buffer: &mut alloc::vec::Vec<u8>, hint: usize) -> Result<usize> {
192 let len = buffer.len();
193 let increment = hint.clamp(1, 64 * 1024);
195 let new_len =
196 len.checked_add(increment).ok_or_else(|| ParseError::Other("stream buffer is too large".into()))?;
197 buffer
198 .try_reserve(increment)
199 .map_err(|e| ParseError::Other(alloc::format!("Error reserving stream buffer: {e}")))?;
200 buffer.resize(new_len, 0);
201 let read_bytes = loop {
202 match stream.read(&mut buffer[len..]) {
203 Ok(read_bytes) => break read_bytes,
204 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
205 Err(e) => {
206 buffer.truncate(len);
207 return Err(ParseError::Other(alloc::format!("Error reading from stream: {e}")));
208 }
209 }
210 };
211 buffer.truncate(len + read_bytes);
212 Ok(read_bytes)
213 }
214
215 pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result<Module> {
217 let wasm = wasm.as_ref();
218 let mut validator = self.validator();
219 let mut reader = ModuleReader::default();
220
221 for payload in wasmparser::Parser::new(0).parse_all(wasm) {
222 match payload? {
223 wasmparser::Payload::CodeSectionStart { count, range, size } => {
224 reader.begin_code_section(count, range, size, validator.as_mut(), &self.options)?;
225 }
226 wasmparser::Payload::CodeSectionEntry(function) => {
227 reader.process_borrowed_code_section_entry(function, validator.as_mut(), &self.options)?;
228 }
229 payload => reader.process_payload(payload, validator.as_mut())?,
230 }
231 }
232
233 if !reader.end_reached {
234 return Err(ParseError::EndNotReached);
235 }
236
237 reader.process_pending_functions(&self.options)?;
238 reader.into_module()
239 }
240
241 #[cfg(feature = "std")]
242 pub fn parse_module_file(&self, path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
244 let file = crate::std::fs::File::open(&path)
245 .map_err(|e| ParseError::Other(alloc::format!("Error opening file {:?}: {}", path.as_ref(), e)))?;
246 self.parse_module_stream(&mut crate::std::io::BufReader::new(file))
247 }
248
249 #[cfg(feature = "std")]
250 pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result<Module> {
252 let mut validator = self.validator();
253 let mut reader = ModuleReader::default();
254 let mut buffer = alloc::vec::Vec::new();
255 let mut parser = wasmparser::Parser::new(0);
256 let mut eof = false;
257 let mut buffer_offset = 0;
258
259 loop {
260 match parser.parse(&buffer[buffer_offset..], eof)? {
261 wasmparser::Chunk::NeedMoreData(hint) => {
262 if buffer_offset != 0 {
263 buffer.copy_within(buffer_offset.., 0);
264 buffer.truncate(buffer.len() - buffer_offset);
265 buffer_offset = 0;
266 }
267 let read_bytes = Self::read_more(&mut stream, &mut buffer, hint)?;
268 eof = read_bytes == 0;
269 }
270 wasmparser::Chunk::Parsed { consumed, payload } => {
271 #[cfg(parallel_parser)]
272 let mut deferred_code_section = None;
273
274 match payload {
275 wasmparser::Payload::CodeSectionStart { count, range, size } => {
276 let defer =
277 reader.begin_code_section(count, range, size, validator.as_mut(), &self.options)?;
278
279 #[cfg(parallel_parser)]
280 if defer {
281 deferred_code_section = Some((count, size as usize));
282 }
283
284 #[cfg(not(parallel_parser))]
285 let _ = defer;
286 }
287 wasmparser::Payload::CodeSectionEntry(function) => {
288 reader.process_inline_code_section_entry(function, validator.as_mut(), &self.options)?;
289 }
290 payload => {
291 reader.process_payload(payload, validator.as_mut())?;
292 }
293 }
294 buffer_offset += consumed;
295
296 #[cfg(parallel_parser)]
297 if let Some((count, section_size)) = deferred_code_section {
298 while buffer.len() - buffer_offset < section_size {
299 let remaining = section_size - (buffer.len() - buffer_offset);
300 let read_bytes = Self::read_more(&mut stream, &mut buffer, remaining)?;
301 if read_bytes == 0 {
302 return Err(ParseError::ParseError {
303 message: "unexpected end-of-file".into(),
304 offset: parser.offset() + (buffer.len() - buffer_offset) as u64,
305 });
306 }
307 }
308
309 let section_end = buffer_offset + section_size;
310 let section_bytes =
311 tinywasm_types::Shared::<[u8]>::from(buffer[buffer_offset..section_end].to_vec());
312 reader.queue_owned_code_section(count, parser.offset(), section_bytes, validator.as_mut())?;
313 parser.skip_section();
314 buffer_offset = section_end;
315 continue;
316 }
317
318 if reader.end_reached {
319 if buffer_offset != buffer.len() {
320 return Err(ParseError::Other("trailing bytes after end of module".into()));
321 }
322
323 if !eof {
324 let read_bytes = Self::read_more(&mut stream, &mut buffer, 1)?;
325 eof = read_bytes == 0;
326
327 if !eof {
328 return Err(ParseError::Other("trailing bytes after end of module".into()));
329 }
330 }
331 }
332
333 if reader.end_reached || eof {
334 reader.process_pending_functions(&self.options)?;
335 return reader.into_module();
336 }
337 }
338 };
339 }
340 }
341}
342
343impl TryFrom<ModuleReader<'_>> for Module {
344 type Error = ParseError;
345
346 fn try_from(reader: ModuleReader<'_>) -> Result<Self> {
347 reader.into_module()
348 }
349}
350
351pub fn parse_bytes(wasm: &[u8]) -> Result<Module> {
353 Parser::default().parse_module_bytes(wasm)
354}
355
356#[cfg(feature = "std")]
357pub fn parse_file(path: impl AsRef<crate::std::path::Path> + Clone) -> Result<Module> {
359 Parser::default().parse_module_file(path)
360}
361
362#[cfg(feature = "std")]
363pub fn parse_stream(stream: impl crate::std::io::Read) -> Result<Module> {
365 Parser::default().parse_module_stream(stream)
366}