1use crate::wasm_op::WasmOp;
7use anyhow::{Context, Result};
8use std::collections::HashMap;
9use wasmparser::{ExternalKind, Parser, Payload};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ImportKind {
14 Function(u32),
16 Memory,
18 Table,
20 Global,
22}
23
24#[derive(Debug, Clone)]
26pub struct ImportEntry {
27 pub module: String,
29 pub name: String,
31 pub kind: ImportKind,
33 pub index: u32,
35}
36
37#[derive(Debug, Clone)]
39pub struct WasmMemory {
40 pub index: u32,
42 pub initial_pages: u32,
44 pub max_pages: Option<u32>,
46 pub shared: bool,
48}
49
50#[derive(Debug, Clone)]
56pub struct WasmGlobal {
57 pub index: u32,
59 pub init_i32: Option<i32>,
61 pub mutable: bool,
63}
64
65impl WasmMemory {
66 pub fn initial_bytes(&self) -> u32 {
68 self.initial_pages * 65536
69 }
70
71 pub fn max_bytes(&self) -> u32 {
73 self.max_pages.unwrap_or(self.initial_pages) * 65536
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct DecodedModule {
80 pub functions: Vec<FunctionOps>,
82 pub memories: Vec<WasmMemory>,
84 pub data_segments: Vec<(u32, Vec<u8>)>,
86 pub imports: Vec<ImportEntry>,
88 pub num_imported_funcs: u32,
90 pub func_arg_counts: Vec<u32>,
96 pub type_arg_counts: Vec<u32>,
100 pub func_ret_i64: Vec<bool>,
104 pub type_ret_i64: Vec<bool>,
106 pub func_params_i64: Vec<Vec<bool>>,
111 pub globals: Vec<WasmGlobal>,
116 pub elem_func_indices: Vec<u32>,
123 pub wsc_facts: Vec<crate::wsc_facts::WscFact>,
132}
133
134pub fn decode_wasm_module(wasm_bytes: &[u8]) -> Result<DecodedModule> {
136 let mut functions = Vec::new();
137 let mut memories = Vec::new();
138 let mut data_segments = Vec::new();
139 let mut globals: Vec<WasmGlobal> = Vec::new();
140 let mut imports = Vec::new();
141 let mut func_index = 0u32;
142 let mut num_imported_funcs = 0u32;
143 let mut export_names: HashMap<u32, String> = HashMap::new();
144 let mut type_arg_counts: Vec<u32> = Vec::new();
147 let mut func_arg_counts: Vec<u32> = Vec::new();
148 let mut type_ret_i64: Vec<bool> = Vec::new();
149 let mut func_ret_i64: Vec<bool> = Vec::new();
150 let mut type_params_i64: Vec<Vec<bool>> = Vec::new();
152 let mut func_params_i64: Vec<Vec<bool>> = Vec::new();
153 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
155 let mut elem_func_indices: Vec<u32> = Vec::new();
156 let mut name_section_names: HashMap<u32, String> = HashMap::new();
162 let mut wsc_facts: Option<Vec<crate::wsc_facts::WscFact>> = None;
166
167 for payload in Parser::new(0).parse_all(wasm_bytes) {
168 let payload = payload.context("Failed to parse WASM payload")?;
169
170 match payload {
171 Payload::TypeSection(reader) => {
172 for rec_group in reader {
175 let rec_group = rec_group.context("Failed to parse type")?;
176 for sub_ty in rec_group.types() {
177 type_block_arity.push(match &sub_ty.composite_type.inner {
182 wasmparser::CompositeInnerType::Func(f) => (
183 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
184 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
185 ),
186 _ => (u8::MAX, u8::MAX),
187 });
188 let (count, ret_i64, params_i64) = match &sub_ty.composite_type.inner {
189 wasmparser::CompositeInnerType::Func(func_ty) => (
190 func_ty.params().len() as u32,
191 func_ty
192 .results()
193 .first()
194 .is_some_and(|t| *t == wasmparser::ValType::I64),
195 func_ty
200 .params()
201 .iter()
202 .map(|t| {
203 matches!(
204 t,
205 wasmparser::ValType::I64 | wasmparser::ValType::F64
206 )
207 })
208 .collect::<Vec<bool>>(),
209 ),
210 _ => (0, false, Vec::new()),
211 };
212 type_arg_counts.push(count);
213 type_ret_i64.push(ret_i64);
214 type_params_i64.push(params_i64);
215 }
216 }
217 }
218 Payload::ImportSection(reader) => {
219 for import in reader.into_imports() {
225 let import = import.context("Failed to parse import")?;
226 let (kind, idx) = match import.ty {
227 wasmparser::TypeRef::Func(type_idx) => {
228 let idx = num_imported_funcs;
229 num_imported_funcs += 1;
230 func_arg_counts
233 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
234 func_ret_i64.push(
235 type_ret_i64
236 .get(type_idx as usize)
237 .copied()
238 .unwrap_or(false),
239 );
240 func_params_i64.push(
241 type_params_i64
242 .get(type_idx as usize)
243 .cloned()
244 .unwrap_or_default(),
245 );
246 (ImportKind::Function(type_idx), idx)
247 }
248 wasmparser::TypeRef::Memory(_) => (ImportKind::Memory, 0),
249 wasmparser::TypeRef::Table(_) => (ImportKind::Table, 0),
250 wasmparser::TypeRef::Global(_) => (ImportKind::Global, 0),
251 _ => continue,
252 };
253 imports.push(ImportEntry {
254 module: import.module.to_string(),
255 name: import.name.to_string(),
256 kind,
257 index: idx,
258 });
259 }
260 }
261 Payload::FunctionSection(reader) => {
262 for ty in reader {
267 let type_idx = ty.context("Failed to parse function type index")?;
268 func_arg_counts
269 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
270 func_ret_i64.push(
271 type_ret_i64
272 .get(type_idx as usize)
273 .copied()
274 .unwrap_or(false),
275 );
276 func_params_i64.push(
277 type_params_i64
278 .get(type_idx as usize)
279 .cloned()
280 .unwrap_or_default(),
281 );
282 }
283 }
284 Payload::MemorySection(reader) => {
285 for (idx, memory) in reader.into_iter().enumerate() {
286 let mem = memory.context("Failed to parse memory")?;
287 memories.push(WasmMemory {
288 index: idx as u32,
289 initial_pages: mem.initial as u32,
290 max_pages: mem.maximum.map(|m| m as u32),
291 shared: mem.shared,
292 });
293 }
294 }
295 Payload::GlobalSection(reader) => {
296 for (idx, global) in reader.into_iter().enumerate() {
302 let global = global.context("Failed to parse global")?;
303 let mut init_i32 = None;
304 let mut ops = global.init_expr.get_operators_reader();
305 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
306 init_i32 = Some(value);
307 }
308 globals.push(WasmGlobal {
309 index: idx as u32,
310 init_i32,
311 mutable: global.ty.mutable,
312 });
313 }
314 }
315 Payload::DataSection(reader) => {
316 for data in reader {
317 let data = data.context("Failed to parse data segment")?;
318 if let wasmparser::DataKind::Active {
319 memory_index: 0,
320 offset_expr,
321 } = data.kind
322 {
323 let mut ops = offset_expr.get_operators_reader();
324 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
325 data_segments.push((value as u32, data.data.to_vec()));
326 }
327 }
328 }
329 }
330 Payload::ElementSection(reader) => {
331 for elem in reader {
338 let elem = elem.context("Failed to parse element segment")?;
339 match elem.items {
340 wasmparser::ElementItems::Functions(funcs) => {
341 for f in funcs {
342 elem_func_indices
343 .push(f.context("Failed to parse element func index")?);
344 }
345 }
346 wasmparser::ElementItems::Expressions(_, exprs) => {
347 for expr in exprs {
348 let expr = expr.context("Failed to parse element expr")?;
349 for op in expr.get_operators_reader() {
350 if let wasmparser::Operator::RefFunc { function_index } =
351 op.context("Failed to parse element op")?
352 {
353 elem_func_indices.push(function_index);
354 }
355 }
356 }
357 }
358 }
359 }
360 }
361 Payload::ExportSection(exports) => {
362 for export in exports {
363 let export = export.context("Failed to parse export")?;
364 if export.kind == ExternalKind::Func {
365 export_names.insert(export.index, export.name.to_string());
366 }
367 }
368 }
369 Payload::CodeSectionEntry(body) => {
370 let (ops, op_offsets, block_arity, unsupported) =
371 decode_function_body(&body, &type_block_arity)?;
372 let actual_index = num_imported_funcs + func_index;
373 let export_name = export_names.get(&actual_index).cloned();
374
375 functions.push(FunctionOps {
376 index: actual_index,
377 export_name,
378 debug_name: None, ops,
380 op_offsets,
381 unsupported,
382 block_arity,
383 });
384 func_index += 1;
385 }
386 Payload::CustomSection(c) => {
387 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
389 parse_name_section_func_names(reader, &mut name_section_names);
390 }
391 if c.name() == crate::wsc_facts::WSC_FACTS_SECTION_NAME && wsc_facts.is_none() {
398 let parsed = crate::wsc_facts::parse_wsc_facts(c.data());
399 if let Some(reason) = &parsed.section_ignored {
400 eprintln!(
401 "warning: ignoring unparseable `wsc.facts` custom section \
402 ({reason}) — facts are optional accelerators, compilation \
403 is unaffected (#494 fail-safe skew rule)"
404 );
405 } else if parsed.records_skipped > 0 {
406 eprintln!(
407 "warning: skipped {} unknown/undecodable `wsc.facts` \
408 record(s) (likely a newer loom emitter); {} known fact(s) \
409 kept, compilation is unaffected (#494 fail-safe skew rule)",
410 parsed.records_skipped,
411 parsed.facts.len()
412 );
413 }
414 wsc_facts = Some(parsed.facts);
415 }
416 }
417 _ => {}
418 }
419 }
420
421 apply_name_section(&mut functions, &name_section_names);
422
423 Ok(DecodedModule {
424 functions,
425 memories,
426 data_segments,
427 imports,
428 num_imported_funcs,
429 func_arg_counts,
430 type_arg_counts,
431 func_ret_i64,
432 type_ret_i64,
433 func_params_i64,
434 globals,
435 elem_func_indices,
436 wsc_facts: wsc_facts.unwrap_or_default(),
437 })
438}
439
440fn parse_name_section_func_names(
446 reader: wasmparser::NameSectionReader<'_>,
447 out: &mut HashMap<u32, String>,
448) {
449 for subsection in reader.into_iter().flatten() {
450 if let wasmparser::Name::Function(map) = subsection {
451 for naming in map.into_iter().flatten() {
452 out.insert(naming.index, naming.name.to_string());
453 }
454 }
455 }
456}
457
458fn apply_name_section(functions: &mut [FunctionOps], names: &HashMap<u32, String>) {
462 if names.is_empty() {
463 return;
464 }
465 for f in functions {
466 f.debug_name = names.get(&f.index).cloned();
467 }
468}
469
470pub fn decode_wasm_functions(wasm_bytes: &[u8]) -> Result<Vec<FunctionOps>> {
472 let mut functions = Vec::new();
473 let mut func_index = 0u32;
474 let mut num_imported_funcs = 0u32;
475 let mut export_names: HashMap<u32, String> = HashMap::new();
476 let mut name_section_names: HashMap<u32, String> = HashMap::new();
477 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
479
480 for payload in Parser::new(0).parse_all(wasm_bytes) {
481 let payload = payload.context("Failed to parse WASM payload")?;
482
483 match payload {
484 Payload::TypeSection(reader) => {
485 for rec_group in reader {
490 let rec_group = rec_group.context("Failed to parse type")?;
491 for sub_ty in rec_group.types() {
492 type_block_arity.push(match &sub_ty.composite_type.inner {
493 wasmparser::CompositeInnerType::Func(f) => (
494 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
495 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
496 ),
497 _ => (u8::MAX, u8::MAX),
498 });
499 }
500 }
501 }
502 Payload::ImportSection(imports) => {
503 for import in imports.into_imports() {
506 let import = import.context("Failed to parse import")?;
507 if matches!(import.ty, wasmparser::TypeRef::Func(_)) {
508 num_imported_funcs += 1;
509 }
510 }
511 }
512 Payload::ExportSection(exports) => {
513 for export in exports {
514 let export = export.context("Failed to parse export")?;
515 if export.kind == ExternalKind::Func {
516 export_names.insert(export.index, export.name.to_string());
517 }
518 }
519 }
520 Payload::CodeSectionEntry(body) => {
521 let (ops, op_offsets, block_arity, unsupported) =
522 decode_function_body(&body, &type_block_arity)?;
523 let actual_index = num_imported_funcs + func_index;
524 let export_name = export_names.get(&actual_index).cloned();
525
526 functions.push(FunctionOps {
527 index: actual_index,
528 export_name,
529 debug_name: None, ops,
531 op_offsets,
532 unsupported,
533 block_arity,
534 });
535 func_index += 1;
536 }
537 Payload::CustomSection(c) => {
538 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
540 parse_name_section_func_names(reader, &mut name_section_names);
541 }
542 }
543 _ => {}
544 }
545 }
546
547 apply_name_section(&mut functions, &name_section_names);
548
549 Ok(functions)
550}
551
552#[derive(Debug, Clone)]
554pub struct FunctionOps {
555 pub index: u32,
557 pub export_name: Option<String>,
559 pub debug_name: Option<String>,
568 pub ops: Vec<WasmOp>,
570 pub op_offsets: Vec<u32>,
578 pub unsupported: Option<String>,
588 pub block_arity: Vec<(u8, u8)>,
604}
605
606fn blocktype_arity(bt: &wasmparser::BlockType, type_block_arity: &[(u8, u8)]) -> (u8, u8) {
611 match bt {
612 wasmparser::BlockType::Empty => (0, 0),
613 wasmparser::BlockType::Type(_) => (0, 1),
614 wasmparser::BlockType::FuncType(i) => type_block_arity
615 .get(*i as usize)
616 .copied()
617 .unwrap_or((u8::MAX, u8::MAX)),
618 }
619}
620
621type DecodedBody = (Vec<WasmOp>, Vec<u32>, Vec<(u8, u8)>, Option<String>);
625
626fn decode_function_body(
632 body: &wasmparser::FunctionBody,
633 type_block_arity: &[(u8, u8)],
634) -> Result<DecodedBody> {
635 let mut ops = Vec::new();
636 let mut op_offsets = Vec::new();
641 let mut block_arity: Vec<(u8, u8)> = Vec::new();
644 let mut unsupported: Option<String> = None;
645
646 let ops_reader = body.get_operators_reader()?;
647 for item in ops_reader.into_iter_with_offsets() {
648 let (op, offset) = item.context("Failed to read operator")?;
649
650 if let Some(wasm_op) = convert_operator(&op) {
651 if let wasmparser::Operator::Block { blockty }
654 | wasmparser::Operator::Loop { blockty }
655 | wasmparser::Operator::If { blockty } = &op
656 {
657 block_arity.push(blocktype_arity(blockty, type_block_arity));
658 }
659 ops.push(wasm_op);
660 op_offsets.push(offset as u32);
661 } else if unsupported.is_none() && !is_intentionally_ignored(&op) {
662 unsupported = Some(format!("{op:?}"));
666 }
667 }
668
669 Ok((ops, op_offsets, block_arity, unsupported))
670}
671
672fn is_intentionally_ignored(op: &wasmparser::Operator) -> bool {
677 use wasmparser::Operator::*;
678 matches!(op, Nop | Unreachable)
679}
680
681fn convert_operator(op: &wasmparser::Operator) -> Option<WasmOp> {
683 use wasmparser::Operator::*;
684
685 match op {
686 I32Const { value } => Some(WasmOp::I32Const(*value)),
688
689 I32Add => Some(WasmOp::I32Add),
691 I32Sub => Some(WasmOp::I32Sub),
692 I32Mul => Some(WasmOp::I32Mul),
693 I32DivS => Some(WasmOp::I32DivS),
694 I32DivU => Some(WasmOp::I32DivU),
695 I32RemS => Some(WasmOp::I32RemS),
696 I32RemU => Some(WasmOp::I32RemU),
697
698 I64Const { value } => Some(WasmOp::I64Const(*value)),
700
701 I64Add => Some(WasmOp::I64Add),
703 I64Sub => Some(WasmOp::I64Sub),
704 I64Mul => Some(WasmOp::I64Mul),
705 I64DivS => Some(WasmOp::I64DivS),
706 I64DivU => Some(WasmOp::I64DivU),
707 I64RemS => Some(WasmOp::I64RemS),
708 I64RemU => Some(WasmOp::I64RemU),
709
710 I64And => Some(WasmOp::I64And),
712 I64Or => Some(WasmOp::I64Or),
713 I64Xor => Some(WasmOp::I64Xor),
714 I64Shl => Some(WasmOp::I64Shl),
715 I64ShrS => Some(WasmOp::I64ShrS),
716 I64ShrU => Some(WasmOp::I64ShrU),
717 I64Rotl => Some(WasmOp::I64Rotl),
718 I64Rotr => Some(WasmOp::I64Rotr),
719 I64Clz => Some(WasmOp::I64Clz),
720 I64Ctz => Some(WasmOp::I64Ctz),
721 I64Popcnt => Some(WasmOp::I64Popcnt),
722 I64Extend8S => Some(WasmOp::I64Extend8S),
723 I64Extend16S => Some(WasmOp::I64Extend16S),
724 I64Extend32S => Some(WasmOp::I64Extend32S),
725 I64ExtendI32U => Some(WasmOp::I64ExtendI32U),
731 I64ExtendI32S => Some(WasmOp::I64ExtendI32S),
732 I32WrapI64 => Some(WasmOp::I32WrapI64),
733
734 I64Eqz => Some(WasmOp::I64Eqz),
736 I64Eq => Some(WasmOp::I64Eq),
737 I64Ne => Some(WasmOp::I64Ne),
738 I64LtS => Some(WasmOp::I64LtS),
739 I64LtU => Some(WasmOp::I64LtU),
740 I64LeS => Some(WasmOp::I64LeS),
741 I64LeU => Some(WasmOp::I64LeU),
742 I64GtS => Some(WasmOp::I64GtS),
743 I64GtU => Some(WasmOp::I64GtU),
744 I64GeS => Some(WasmOp::I64GeS),
745 I64GeU => Some(WasmOp::I64GeU),
746
747 I32And => Some(WasmOp::I32And),
749 I32Or => Some(WasmOp::I32Or),
750 I32Xor => Some(WasmOp::I32Xor),
751 I32Shl => Some(WasmOp::I32Shl),
752 I32ShrS => Some(WasmOp::I32ShrS),
753 I32ShrU => Some(WasmOp::I32ShrU),
754 I32Rotl => Some(WasmOp::I32Rotl),
755 I32Rotr => Some(WasmOp::I32Rotr),
756 I32Clz => Some(WasmOp::I32Clz),
757 I32Ctz => Some(WasmOp::I32Ctz),
758 I32Popcnt => Some(WasmOp::I32Popcnt),
759 I32Extend8S => Some(WasmOp::I32Extend8S),
760 I32Extend16S => Some(WasmOp::I32Extend16S),
761
762 I32Eqz => Some(WasmOp::I32Eqz),
764 I32Eq => Some(WasmOp::I32Eq),
765 I32Ne => Some(WasmOp::I32Ne),
766 I32LtS => Some(WasmOp::I32LtS),
767 I32LtU => Some(WasmOp::I32LtU),
768 I32LeS => Some(WasmOp::I32LeS),
769 I32LeU => Some(WasmOp::I32LeU),
770 I32GtS => Some(WasmOp::I32GtS),
771 I32GtU => Some(WasmOp::I32GtU),
772 I32GeS => Some(WasmOp::I32GeS),
773 I32GeU => Some(WasmOp::I32GeU),
774
775 I32Load { memarg } => Some(WasmOp::I32Load {
777 offset: memarg.offset as u32,
778 align: memarg.align as u32,
779 }),
780 I32Store { memarg } => Some(WasmOp::I32Store {
781 offset: memarg.offset as u32,
782 align: memarg.align as u32,
783 }),
784 I64Load { memarg } => Some(WasmOp::I64Load {
791 offset: memarg.offset as u32,
792 align: memarg.align as u32,
793 }),
794 I64Store { memarg } => Some(WasmOp::I64Store {
795 offset: memarg.offset as u32,
796 align: memarg.align as u32,
797 }),
798
799 I32Load8S { memarg } => Some(WasmOp::I32Load8S {
801 offset: memarg.offset as u32,
802 align: memarg.align as u32,
803 }),
804 I32Load8U { memarg } => Some(WasmOp::I32Load8U {
805 offset: memarg.offset as u32,
806 align: memarg.align as u32,
807 }),
808 I32Load16S { memarg } => Some(WasmOp::I32Load16S {
809 offset: memarg.offset as u32,
810 align: memarg.align as u32,
811 }),
812 I32Load16U { memarg } => Some(WasmOp::I32Load16U {
813 offset: memarg.offset as u32,
814 align: memarg.align as u32,
815 }),
816
817 I32Store8 { memarg } => Some(WasmOp::I32Store8 {
819 offset: memarg.offset as u32,
820 align: memarg.align as u32,
821 }),
822 I32Store16 { memarg } => Some(WasmOp::I32Store16 {
823 offset: memarg.offset as u32,
824 align: memarg.align as u32,
825 }),
826
827 LocalGet { local_index } => Some(WasmOp::LocalGet(*local_index)),
829 LocalSet { local_index } => Some(WasmOp::LocalSet(*local_index)),
830 LocalTee { local_index } => Some(WasmOp::LocalTee(*local_index)),
831 GlobalGet { global_index } => Some(WasmOp::GlobalGet(*global_index)),
832 GlobalSet { global_index } => Some(WasmOp::GlobalSet(*global_index)),
833
834 Block { .. } => Some(WasmOp::Block),
836 Loop { .. } => Some(WasmOp::Loop),
837 Br { relative_depth } => Some(WasmOp::Br(*relative_depth)),
838 BrIf { relative_depth } => Some(WasmOp::BrIf(*relative_depth)),
839 BrTable { targets } => {
845 let default = targets.default();
846 let tgts: Vec<u32> = targets.targets().filter_map(Result::ok).collect();
847 Some(WasmOp::BrTable {
848 targets: tgts,
849 default,
850 })
851 }
852 Return => Some(WasmOp::Return),
853 Call { function_index } => Some(WasmOp::Call(*function_index)),
854 CallIndirect {
855 type_index,
856 table_index,
857 ..
858 } => Some(WasmOp::CallIndirect {
859 type_index: *type_index,
860 table_index: *table_index,
861 }),
862
863 End => Some(WasmOp::End),
865
866 Nop | Unreachable => None,
868
869 Drop => Some(WasmOp::Drop),
871
872 Select => Some(WasmOp::Select),
874
875 If { .. } => Some(WasmOp::If),
877 Else => Some(WasmOp::Else),
878
879 I64Load8S { memarg } => Some(WasmOp::I64Load8S {
881 offset: memarg.offset as u32,
882 align: memarg.align as u32,
883 }),
884 I64Load8U { memarg } => Some(WasmOp::I64Load8U {
885 offset: memarg.offset as u32,
886 align: memarg.align as u32,
887 }),
888 I64Load16S { memarg } => Some(WasmOp::I64Load16S {
889 offset: memarg.offset as u32,
890 align: memarg.align as u32,
891 }),
892 I64Load16U { memarg } => Some(WasmOp::I64Load16U {
893 offset: memarg.offset as u32,
894 align: memarg.align as u32,
895 }),
896 I64Load32S { memarg } => Some(WasmOp::I64Load32S {
897 offset: memarg.offset as u32,
898 align: memarg.align as u32,
899 }),
900 I64Load32U { memarg } => Some(WasmOp::I64Load32U {
901 offset: memarg.offset as u32,
902 align: memarg.align as u32,
903 }),
904
905 I64Store8 { memarg } => Some(WasmOp::I64Store8 {
907 offset: memarg.offset as u32,
908 align: memarg.align as u32,
909 }),
910 I64Store16 { memarg } => Some(WasmOp::I64Store16 {
911 offset: memarg.offset as u32,
912 align: memarg.align as u32,
913 }),
914 I64Store32 { memarg } => Some(WasmOp::I64Store32 {
915 offset: memarg.offset as u32,
916 align: memarg.align as u32,
917 }),
918
919 MemorySize { mem, .. } => Some(WasmOp::MemorySize(*mem)),
921 MemoryGrow { mem, .. } => Some(WasmOp::MemoryGrow(*mem)),
922
923 MemoryCopy {
930 dst_mem: 0,
931 src_mem: 0,
932 } => Some(WasmOp::MemoryCopy),
933 MemoryFill { mem: 0 } => Some(WasmOp::MemoryFill),
934
935 V128Const { value } => {
939 let mut bytes = [0u8; 16];
940 bytes.copy_from_slice(value.bytes());
941 Some(WasmOp::V128Const(bytes))
942 }
943 V128Load { memarg } => Some(WasmOp::V128Load {
944 offset: memarg.offset as u32,
945 align: memarg.align as u32,
946 }),
947 V128Store { memarg } => Some(WasmOp::V128Store {
948 offset: memarg.offset as u32,
949 align: memarg.align as u32,
950 }),
951
952 V128And => Some(WasmOp::V128And),
954 V128Or => Some(WasmOp::V128Or),
955 V128Xor => Some(WasmOp::V128Xor),
956 V128Not => Some(WasmOp::V128Not),
957 V128AndNot => Some(WasmOp::V128AndNot),
958
959 I8x16Add => Some(WasmOp::I8x16Add),
961 I8x16Sub => Some(WasmOp::I8x16Sub),
962 I8x16Neg => Some(WasmOp::I8x16Neg),
963 I8x16Eq => Some(WasmOp::I8x16Eq),
964 I8x16Ne => Some(WasmOp::I8x16Ne),
965 I8x16LtS => Some(WasmOp::I8x16LtS),
966 I8x16LtU => Some(WasmOp::I8x16LtU),
967 I8x16GtS => Some(WasmOp::I8x16GtS),
968 I8x16GtU => Some(WasmOp::I8x16GtU),
969 I8x16LeS => Some(WasmOp::I8x16LeS),
970 I8x16LeU => Some(WasmOp::I8x16LeU),
971 I8x16GeS => Some(WasmOp::I8x16GeS),
972 I8x16GeU => Some(WasmOp::I8x16GeU),
973 I8x16Splat => Some(WasmOp::I8x16Splat),
974 I8x16ExtractLaneS { lane } => Some(WasmOp::I8x16ExtractLaneS(*lane)),
975 I8x16ExtractLaneU { lane } => Some(WasmOp::I8x16ExtractLaneU(*lane)),
976 I8x16ReplaceLane { lane } => Some(WasmOp::I8x16ReplaceLane(*lane)),
977 I8x16Shuffle { lanes } => Some(WasmOp::I8x16Shuffle(*lanes)),
978 I8x16Swizzle => Some(WasmOp::I8x16Swizzle),
979
980 I16x8Add => Some(WasmOp::I16x8Add),
982 I16x8Sub => Some(WasmOp::I16x8Sub),
983 I16x8Mul => Some(WasmOp::I16x8Mul),
984 I16x8Neg => Some(WasmOp::I16x8Neg),
985 I16x8Eq => Some(WasmOp::I16x8Eq),
986 I16x8Ne => Some(WasmOp::I16x8Ne),
987 I16x8LtS => Some(WasmOp::I16x8LtS),
988 I16x8LtU => Some(WasmOp::I16x8LtU),
989 I16x8GtS => Some(WasmOp::I16x8GtS),
990 I16x8GtU => Some(WasmOp::I16x8GtU),
991 I16x8LeS => Some(WasmOp::I16x8LeS),
992 I16x8LeU => Some(WasmOp::I16x8LeU),
993 I16x8GeS => Some(WasmOp::I16x8GeS),
994 I16x8GeU => Some(WasmOp::I16x8GeU),
995 I16x8Splat => Some(WasmOp::I16x8Splat),
996 I16x8ExtractLaneS { lane } => Some(WasmOp::I16x8ExtractLaneS(*lane)),
997 I16x8ExtractLaneU { lane } => Some(WasmOp::I16x8ExtractLaneU(*lane)),
998 I16x8ReplaceLane { lane } => Some(WasmOp::I16x8ReplaceLane(*lane)),
999
1000 I32x4Add => Some(WasmOp::I32x4Add),
1002 I32x4Sub => Some(WasmOp::I32x4Sub),
1003 I32x4Mul => Some(WasmOp::I32x4Mul),
1004 I32x4Neg => Some(WasmOp::I32x4Neg),
1005 I32x4Eq => Some(WasmOp::I32x4Eq),
1006 I32x4Ne => Some(WasmOp::I32x4Ne),
1007 I32x4LtS => Some(WasmOp::I32x4LtS),
1008 I32x4LtU => Some(WasmOp::I32x4LtU),
1009 I32x4GtS => Some(WasmOp::I32x4GtS),
1010 I32x4GtU => Some(WasmOp::I32x4GtU),
1011 I32x4LeS => Some(WasmOp::I32x4LeS),
1012 I32x4LeU => Some(WasmOp::I32x4LeU),
1013 I32x4GeS => Some(WasmOp::I32x4GeS),
1014 I32x4GeU => Some(WasmOp::I32x4GeU),
1015 I32x4Splat => Some(WasmOp::I32x4Splat),
1016 I32x4ExtractLane { lane } => Some(WasmOp::I32x4ExtractLane(*lane)),
1017 I32x4ReplaceLane { lane } => Some(WasmOp::I32x4ReplaceLane(*lane)),
1018
1019 I64x2Add => Some(WasmOp::I64x2Add),
1021 I64x2Sub => Some(WasmOp::I64x2Sub),
1022 I64x2Mul => Some(WasmOp::I64x2Mul),
1023 I64x2Neg => Some(WasmOp::I64x2Neg),
1024 I64x2Eq => Some(WasmOp::I64x2Eq),
1025 I64x2Ne => Some(WasmOp::I64x2Ne),
1026 I64x2LtS => Some(WasmOp::I64x2LtS),
1027 I64x2GtS => Some(WasmOp::I64x2GtS),
1028 I64x2LeS => Some(WasmOp::I64x2LeS),
1029 I64x2GeS => Some(WasmOp::I64x2GeS),
1030 I64x2Splat => Some(WasmOp::I64x2Splat),
1031 I64x2ExtractLane { lane } => Some(WasmOp::I64x2ExtractLane(*lane)),
1032 I64x2ReplaceLane { lane } => Some(WasmOp::I64x2ReplaceLane(*lane)),
1033
1034 F32x4Add => Some(WasmOp::F32x4Add),
1036 F32x4Sub => Some(WasmOp::F32x4Sub),
1037 F32x4Mul => Some(WasmOp::F32x4Mul),
1038 F32x4Div => Some(WasmOp::F32x4Div),
1039 F32x4Abs => Some(WasmOp::F32x4Abs),
1040 F32x4Neg => Some(WasmOp::F32x4Neg),
1041 F32x4Sqrt => Some(WasmOp::F32x4Sqrt),
1042 F32x4Eq => Some(WasmOp::F32x4Eq),
1043 F32x4Ne => Some(WasmOp::F32x4Ne),
1044 F32x4Lt => Some(WasmOp::F32x4Lt),
1045 F32x4Le => Some(WasmOp::F32x4Le),
1046 F32x4Gt => Some(WasmOp::F32x4Gt),
1047 F32x4Ge => Some(WasmOp::F32x4Ge),
1048 F32x4Splat => Some(WasmOp::F32x4Splat),
1049 F32x4ExtractLane { lane } => Some(WasmOp::F32x4ExtractLane(*lane)),
1050 F32x4ReplaceLane { lane } => Some(WasmOp::F32x4ReplaceLane(*lane)),
1051
1052 _ => None,
1054 }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059 use super::*;
1060
1061 #[test]
1062 fn test_decode_simple_add() {
1063 let wat = r#"
1064 (module
1065 (func (export "add") (param i32 i32) (result i32)
1066 local.get 0
1067 local.get 1
1068 i32.add
1069 )
1070 )
1071 "#;
1072
1073 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1074 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1075
1076 assert_eq!(functions.len(), 1);
1077 assert_eq!(functions[0].index, 0);
1078 assert_eq!(functions[0].export_name, Some("add".to_string()));
1079 assert_eq!(
1080 functions[0].ops,
1081 vec![
1082 WasmOp::LocalGet(0),
1083 WasmOp::LocalGet(1),
1084 WasmOp::I32Add,
1085 WasmOp::End
1086 ]
1087 );
1088 }
1089
1090 #[test]
1096 fn test_decode_i64_i32_width_conversions() {
1097 let wat = r#"
1098 (module
1099 (func (export "conv") (param i32 i64) (result i32)
1100 local.get 0
1101 i64.extend_i32_u
1102 local.get 0
1103 i64.extend_i32_s
1104 i64.add
1105 local.get 1
1106 i64.add
1107 i32.wrap_i64
1108 )
1109 )
1110 "#;
1111 let wasm = wat::parse_str(wat).expect("parse");
1112 let functions = decode_wasm_functions(&wasm).expect("decode");
1113 let ops = &functions[0].ops;
1114 assert!(
1115 ops.contains(&WasmOp::I64ExtendI32U),
1116 "i64.extend_i32_u must decode (not be dropped): {ops:?}"
1117 );
1118 assert!(
1119 ops.contains(&WasmOp::I64ExtendI32S),
1120 "i64.extend_i32_s must decode (not be dropped): {ops:?}"
1121 );
1122 assert!(
1123 ops.contains(&WasmOp::I32WrapI64),
1124 "i32.wrap_i64 must decode (not be dropped): {ops:?}"
1125 );
1126 }
1127
1128 #[test]
1133 fn test_decode_br_table() {
1134 let wat = r#"
1135 (module
1136 (func (export "bt") (param i32) (result i32)
1137 (block (block (block
1138 local.get 0
1139 br_table 2 0 1 2)
1140 i32.const 30 return)
1141 i32.const 20 return)
1142 i32.const 10))
1143 "#;
1144 let wasm = wat::parse_str(wat).expect("parse");
1145 let functions = decode_wasm_functions(&wasm).expect("decode");
1146 let bt = functions[0]
1147 .ops
1148 .iter()
1149 .find_map(|o| match o {
1150 WasmOp::BrTable { targets, default } => Some((targets.clone(), *default)),
1151 _ => None,
1152 })
1153 .expect("br_table must decode (not be dropped)");
1154 assert_eq!(bt.0, vec![2, 0, 1], "br_table targets preserved in order");
1155 assert_eq!(bt.1, 2, "br_table default preserved");
1156 }
1157
1158 #[test]
1159 fn test_decode_arithmetic() {
1160 let wat = r#"
1161 (module
1162 (func (export "calc") (result i32)
1163 i32.const 5
1164 i32.const 3
1165 i32.mul
1166 i32.const 2
1167 i32.add
1168 )
1169 )
1170 "#;
1171
1172 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1173 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1174
1175 assert_eq!(functions.len(), 1);
1176 assert_eq!(functions[0].export_name, Some("calc".to_string()));
1177 assert_eq!(
1178 functions[0].ops,
1179 vec![
1180 WasmOp::I32Const(5),
1181 WasmOp::I32Const(3),
1182 WasmOp::I32Mul,
1183 WasmOp::I32Const(2),
1184 WasmOp::I32Add,
1185 WasmOp::End,
1186 ]
1187 );
1188 }
1189
1190 #[test]
1191 fn test_decode_multi_function_module() {
1192 let wat = r#"
1193 (module
1194 (func $helper)
1195 (func (export "add") (param i32 i32) (result i32)
1196 local.get 0
1197 local.get 1
1198 i32.add
1199 )
1200 (func (export "sub") (param i32 i32) (result i32)
1201 local.get 0
1202 local.get 1
1203 i32.sub
1204 )
1205 )
1206 "#;
1207
1208 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1209 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1210
1211 assert_eq!(functions.len(), 3);
1212 assert_eq!(functions[0].index, 0);
1213 assert_eq!(functions[0].export_name, None);
1214 assert_eq!(functions[1].index, 1);
1215 assert_eq!(functions[1].export_name, Some("add".to_string()));
1216 assert_eq!(functions[2].index, 2);
1217 assert_eq!(functions[2].export_name, Some("sub".to_string()));
1218 }
1219
1220 #[test]
1221 fn test_decode_module_with_imports() {
1222 let wat = r#"
1223 (module
1224 (import "env" "log" (func $log (param i32)))
1225 (import "env" "memory" (memory 1))
1226 (func (export "run") (param i32)
1227 local.get 0
1228 call 0
1229 )
1230 )
1231 "#;
1232
1233 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1234 let module = decode_wasm_module(&wasm).expect("Failed to decode");
1235
1236 assert_eq!(module.imports.len(), 2);
1238 assert_eq!(module.num_imported_funcs, 1);
1239
1240 assert_eq!(module.imports[0].module, "env");
1242 assert_eq!(module.imports[0].name, "log");
1243 assert!(matches!(module.imports[0].kind, ImportKind::Function(_)));
1244
1245 assert_eq!(module.imports[1].module, "env");
1247 assert_eq!(module.imports[1].name, "memory");
1248 assert_eq!(module.imports[1].kind, ImportKind::Memory);
1249
1250 assert_eq!(module.functions.len(), 1);
1252 assert_eq!(module.functions[0].index, 1);
1253 assert_eq!(module.functions[0].export_name, Some("run".to_string()));
1254 }
1255
1256 #[test]
1257 fn test_find_function_by_export_name() {
1258 let wat = r#"
1259 (module
1260 (func $helper)
1261 (func (export "add") (param i32 i32) (result i32)
1262 local.get 0
1263 local.get 1
1264 i32.add
1265 )
1266 )
1267 "#;
1268
1269 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1270 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1271
1272 let add_func = functions
1273 .iter()
1274 .find(|f| f.export_name.as_deref() == Some("add"))
1275 .expect("Should find 'add' function");
1276
1277 assert_eq!(add_func.index, 1);
1278 assert!(add_func.ops.contains(&WasmOp::I32Add));
1279 }
1280
1281 #[test]
1282 fn test_decode_subword_loads() {
1283 let wat = r#"
1284 (module
1285 (memory 1)
1286 (func (export "test") (param i32) (result i32)
1287 local.get 0
1288 i32.load8_u
1289 )
1290 )
1291 "#;
1292
1293 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1294 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1295
1296 assert_eq!(functions.len(), 1);
1297 assert!(functions[0].ops.contains(&WasmOp::I32Load8U {
1298 offset: 0,
1299 align: 0,
1300 }));
1301 }
1302
1303 #[test]
1304 fn test_decode_subword_stores() {
1305 let wat = r#"
1306 (module
1307 (memory 1)
1308 (func (export "test") (param i32 i32)
1309 local.get 0
1310 local.get 1
1311 i32.store8
1312 )
1313 )
1314 "#;
1315
1316 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1317 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1318
1319 assert_eq!(functions.len(), 1);
1320 assert!(functions[0].ops.contains(&WasmOp::I32Store8 {
1321 offset: 0,
1322 align: 0,
1323 }));
1324 }
1325
1326 #[test]
1327 fn test_decode_memory_size_grow() {
1328 let wat = r#"
1329 (module
1330 (memory 1)
1331 (func (export "test") (result i32)
1332 memory.size
1333 )
1334 )
1335 "#;
1336
1337 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1338 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1339
1340 assert_eq!(functions.len(), 1);
1341 assert!(functions[0].ops.contains(&WasmOp::MemorySize(0)));
1342 }
1343
1344 #[test]
1345 fn test_decode_memory_grow() {
1346 let wat = r#"
1347 (module
1348 (memory 1)
1349 (func (export "test") (param i32) (result i32)
1350 local.get 0
1351 memory.grow
1352 )
1353 )
1354 "#;
1355
1356 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1357 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1358
1359 assert_eq!(functions.len(), 1);
1360 assert!(functions[0].ops.contains(&WasmOp::MemoryGrow(0)));
1361 }
1362
1363 #[test]
1364 fn test_decode_bulk_memory_374() {
1365 let wat = r#"
1368 (module
1369 (memory 1)
1370 (func (export "cpy") (param i32 i32 i32)
1371 local.get 0 local.get 1 local.get 2 memory.copy)
1372 (func (export "fil") (param i32 i32 i32)
1373 local.get 0 local.get 1 local.get 2 memory.fill)
1374 )
1375 "#;
1376 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1377 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1378 assert_eq!(functions.len(), 2);
1379 assert!(functions[0].ops.contains(&WasmOp::MemoryCopy));
1380 assert!(functions[1].ops.contains(&WasmOp::MemoryFill));
1381 assert!(functions[0].unsupported.is_none());
1383 assert!(functions[1].unsupported.is_none());
1384 }
1385
1386 #[test]
1387 fn test_decode_i64_subword_loads() {
1388 let wat = r#"
1389 (module
1390 (memory 1)
1391 (func (export "test") (param i32) (result i64)
1392 local.get 0
1393 i64.load8_s
1394 )
1395 )
1396 "#;
1397
1398 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1399 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1400
1401 assert_eq!(functions.len(), 1);
1402 assert!(functions[0].ops.contains(&WasmOp::I64Load8S {
1403 offset: 0,
1404 align: 0,
1405 }));
1406 }
1407
1408 #[test]
1409 fn test_decode_all_subword_memory_ops() {
1410 let wat = r#"
1412 (module
1413 (memory 1)
1414 (func (export "test") (param i32)
1415 ;; i32 sub-word loads
1416 local.get 0
1417 i32.load8_s
1418 drop
1419 local.get 0
1420 i32.load8_u
1421 drop
1422 local.get 0
1423 i32.load16_s
1424 drop
1425 local.get 0
1426 i32.load16_u
1427 drop
1428
1429 ;; i32 sub-word stores
1430 local.get 0
1431 i32.const 42
1432 i32.store8
1433 local.get 0
1434 i32.const 42
1435 i32.store16
1436
1437 ;; i64 sub-word loads
1438 local.get 0
1439 i64.load8_s
1440 drop
1441 local.get 0
1442 i64.load8_u
1443 drop
1444 local.get 0
1445 i64.load16_s
1446 drop
1447 local.get 0
1448 i64.load16_u
1449 drop
1450 local.get 0
1451 i64.load32_s
1452 drop
1453 local.get 0
1454 i64.load32_u
1455 drop
1456
1457 ;; i64 sub-word stores
1458 local.get 0
1459 i64.const 42
1460 i64.store8
1461 local.get 0
1462 i64.const 42
1463 i64.store16
1464 local.get 0
1465 i64.const 42
1466 i64.store32
1467 )
1468 )
1469 "#;
1470
1471 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1472 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1473
1474 assert_eq!(functions.len(), 1);
1475 let ops = &functions[0].ops;
1476
1477 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8S { .. })));
1479 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8U { .. })));
1480 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16S { .. })));
1481 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16U { .. })));
1482 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store8 { .. })));
1483 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store16 { .. })));
1484
1485 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8S { .. })));
1487 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8U { .. })));
1488 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16S { .. })));
1489 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16U { .. })));
1490 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32S { .. })));
1491 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32U { .. })));
1492 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store8 { .. })));
1493 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store16 { .. })));
1494 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store32 { .. })));
1495 }
1496
1497 #[test]
1498 fn test_decode_simd_i32x4_add() {
1499 let wat = r#"
1500 (module
1501 (func (export "add_v128") (param v128 v128) (result v128)
1502 local.get 0
1503 local.get 1
1504 i32x4.add
1505 )
1506 )
1507 "#;
1508
1509 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1510 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1511
1512 assert_eq!(functions.len(), 1);
1513 assert!(
1514 functions[0].ops.contains(&WasmOp::I32x4Add),
1515 "Should decode i32x4.add: {:?}",
1516 functions[0].ops
1517 );
1518 }
1519
1520 #[test]
1521 fn test_decode_simd_v128_const() {
1522 let wat = r#"
1523 (module
1524 (func (export "const_v128") (result v128)
1525 v128.const i32x4 1 2 3 4
1526 )
1527 )
1528 "#;
1529
1530 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1531 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1532
1533 assert_eq!(functions.len(), 1);
1534 assert!(
1535 functions[0]
1536 .ops
1537 .iter()
1538 .any(|o| matches!(o, WasmOp::V128Const(_))),
1539 "Should decode v128.const: {:?}",
1540 functions[0].ops
1541 );
1542 }
1543
1544 #[test]
1545 fn test_decode_simd_v128_load_store() {
1546 let wat = r#"
1547 (module
1548 (memory 1)
1549 (func (export "load_store") (param i32)
1550 local.get 0
1551 v128.load
1552 local.get 0
1553 v128.store
1554 )
1555 )
1556 "#;
1557
1558 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1559 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1560
1561 assert_eq!(functions.len(), 1);
1562 let ops = &functions[0].ops;
1563 assert!(
1564 ops.iter().any(|o| matches!(o, WasmOp::V128Load { .. })),
1565 "Should decode v128.load"
1566 );
1567 assert!(
1568 ops.iter().any(|o| matches!(o, WasmOp::V128Store { .. })),
1569 "Should decode v128.store"
1570 );
1571 }
1572
1573 #[test]
1574 fn test_decode_simd_bitwise_ops() {
1575 let wat = r#"
1576 (module
1577 (func (export "bitwise") (param v128 v128) (result v128)
1578 local.get 0
1579 local.get 1
1580 v128.and
1581 )
1582 )
1583 "#;
1584
1585 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1586 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1587
1588 assert_eq!(functions.len(), 1);
1589 assert!(functions[0].ops.contains(&WasmOp::V128And));
1590 }
1591
1592 #[test]
1593 fn test_decode_simd_splat() {
1594 let wat = r#"
1595 (module
1596 (func (export "splat") (param i32) (result v128)
1597 local.get 0
1598 i32x4.splat
1599 )
1600 )
1601 "#;
1602
1603 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1604 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1605
1606 assert_eq!(functions.len(), 1);
1607 assert!(functions[0].ops.contains(&WasmOp::I32x4Splat));
1608 }
1609
1610 #[test]
1611 fn test_decode_simd_extract_lane() {
1612 let wat = r#"
1613 (module
1614 (func (export "extract") (param v128) (result i32)
1615 local.get 0
1616 i32x4.extract_lane 2
1617 )
1618 )
1619 "#;
1620
1621 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1622 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1623
1624 assert_eq!(functions.len(), 1);
1625 assert!(
1626 functions[0].ops.contains(&WasmOp::I32x4ExtractLane(2)),
1627 "Should decode i32x4.extract_lane 2"
1628 );
1629 }
1630
1631 #[test]
1632 fn test_decode_simd_f32x4_arithmetic() {
1633 let wat = r#"
1634 (module
1635 (func (export "f32x4_add") (param v128 v128) (result v128)
1636 local.get 0
1637 local.get 1
1638 f32x4.add
1639 )
1640 )
1641 "#;
1642
1643 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1644 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1645
1646 assert_eq!(functions.len(), 1);
1647 assert!(functions[0].ops.contains(&WasmOp::F32x4Add));
1648 }
1649
1650 #[test]
1651 fn test_369_scalar_float_op_flags_function_unsupported_not_dropped() {
1652 let wat = r#"
1656 (module
1657 (func (export "fadd") (param f32 f32) (result f32)
1658 local.get 0 local.get 1 f32.add)
1659 (func (export "iadd") (param i32 i32) (result i32)
1660 local.get 0 local.get 1 i32.add))
1661 "#;
1662 let wasm = wat::parse_str(wat).expect("parse");
1663 let functions = decode_wasm_functions(&wasm).expect("decode");
1664 let fadd = functions
1665 .iter()
1666 .find(|f| f.export_name.as_deref() == Some("fadd"))
1667 .unwrap();
1668 let iadd = functions
1669 .iter()
1670 .find(|f| f.export_name.as_deref() == Some("iadd"))
1671 .unwrap();
1672 assert!(
1673 fadd.unsupported.is_some(),
1674 "f32.add must flag the function unsupported (loud-skip), got {:?}",
1675 fadd.unsupported
1676 );
1677 assert!(
1678 fadd.unsupported.as_deref().unwrap().contains("F32Add"),
1679 "diagnostic should name the op: {:?}",
1680 fadd.unsupported
1681 );
1682 assert!(
1683 iadd.unsupported.is_none(),
1684 "a pure-integer function must NOT be flagged: {:?}",
1685 iadd.unsupported
1686 );
1687 }
1688
1689 #[test]
1690 fn test_decode_simd_multiple_ops() {
1691 let wat = r#"
1692 (module
1693 (func (export "simd_ops") (param v128 v128 v128) (result v128)
1694 ;; (a + b) * c
1695 local.get 0
1696 local.get 1
1697 i32x4.add
1698 local.get 2
1699 i32x4.mul
1700 )
1701 )
1702 "#;
1703
1704 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1705 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1706
1707 assert_eq!(functions.len(), 1);
1708 let ops = &functions[0].ops;
1709 assert!(ops.contains(&WasmOp::I32x4Add));
1710 assert!(ops.contains(&WasmOp::I32x4Mul));
1711 }
1712
1713 #[test]
1719 fn test_decode_records_aligned_increasing_op_offsets_dbg001() {
1720 let wat = r#"
1721 (module
1722 (func (export "f") (param i32 i32) (result i32)
1723 local.get 0
1724 local.get 1
1725 i32.add
1726 i32.const 7
1727 i32.mul))
1728 "#;
1729 let wasm = wat::parse_str(wat).expect("parse WAT");
1730 let functions = decode_wasm_functions(&wasm).expect("decode");
1731 let f = &functions[0];
1732
1733 assert_eq!(
1735 f.op_offsets.len(),
1736 f.ops.len(),
1737 "op_offsets must be parallel to ops"
1738 );
1739 assert!(!f.op_offsets.is_empty());
1740
1741 assert!(
1744 f.op_offsets.windows(2).all(|w| w[1] > w[0]),
1745 "wasm byte offsets must strictly increase: {:?}",
1746 f.op_offsets
1747 );
1748 assert!(
1749 f.op_offsets[0] >= 8,
1750 "module-relative offset is past the 8-byte wasm header"
1751 );
1752 }
1753
1754 #[test]
1757 fn test_decode_captures_global_initializer() {
1758 let wat = r#"
1759 (module
1760 (memory 2)
1761 (global $__stack_pointer (mut i32) (i32.const 65536))
1762 (global $immutable_const i32 (i32.const 7))
1763 (func (export "f") (result i32) global.get 0)
1764 )
1765 "#;
1766 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1767 let module = decode_wasm_module(&wasm).expect("Failed to decode");
1768
1769 assert_eq!(module.globals.len(), 2, "both globals captured");
1770 let sp = &module.globals[0];
1771 assert_eq!(sp.index, 0);
1772 assert_eq!(sp.init_i32, Some(65536), "stack-pointer init captured");
1773 assert!(sp.mutable, "stack pointer is mutable");
1774 let c = &module.globals[1];
1775 assert_eq!(c.init_i32, Some(7));
1776 assert!(!c.mutable, "second global is immutable");
1777 }
1778
1779 #[test]
1785 fn test_decode_records_block_arity_side_table_509() {
1786 let wat = r#"
1787 (module
1788 (func (export "f") (param i32) (result i32)
1789 (block (result i32)
1790 (block (nop))
1791 (local.get 0)
1792 (if (result i32)
1793 (then (i32.const 1))
1794 (else (i32.const 2)))))
1795 (func (export "g") (result i32)
1796 (block (result i32 i32)
1797 (i32.const 1) (i32.const 2))
1798 i32.add)
1799 (func (export "h") (param i32) (result i32)
1800 (local.get 0)
1801 (loop (param i32) (result i32))))
1802 "#;
1803 let wasm = wat::parse_str(wat).expect("parse WAT");
1804
1805 for functions in [
1807 decode_wasm_functions(&wasm).expect("decode"),
1808 decode_wasm_module(&wasm).expect("decode").functions,
1809 ] {
1810 assert_eq!(
1812 functions[0].block_arity,
1813 vec![(0, 1), (0, 0), (0, 1)],
1814 "f: ValType/Empty/ValType blocktypes"
1815 );
1816 assert_eq!(
1818 functions[1].block_arity,
1819 vec![(0, 2)],
1820 "g: functype blocktype result count from the type section"
1821 );
1822 assert_eq!(
1825 functions[2].block_arity,
1826 vec![(1, 1)],
1827 "h: loop params captured"
1828 );
1829 }
1830 }
1831}