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 pub slot_bytes: u32,
68}
69
70impl WasmMemory {
71 pub fn initial_bytes(&self) -> u32 {
73 self.initial_pages * 65536
74 }
75
76 pub fn max_bytes(&self) -> u32 {
78 self.max_pages.unwrap_or(self.initial_pages) * 65536
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct ElemSegmentInfo {
86 pub offset: Option<u32>,
90 pub funcs: Option<Vec<u32>>,
93}
94
95#[derive(Debug, Clone, Default, PartialEq, Eq)]
115pub struct CallIndirectGuards {
116 pub table_size: Option<u32>,
118 pub type_reject: Vec<Option<String>>,
121}
122
123#[derive(Debug, Clone)]
125pub struct DecodedModule {
126 pub functions: Vec<FunctionOps>,
128 pub memories: Vec<WasmMemory>,
130 pub data_segments: Vec<(u32, Vec<u8>)>,
132 pub imports: Vec<ImportEntry>,
134 pub num_imported_funcs: u32,
136 pub func_arg_counts: Vec<u32>,
142 pub type_arg_counts: Vec<u32>,
146 pub func_ret_i64: Vec<bool>,
150 pub type_ret_i64: Vec<bool>,
152 pub func_params_i64: Vec<Vec<bool>>,
157 pub globals: Vec<WasmGlobal>,
162 pub elem_func_indices: Vec<u32>,
169 pub table_size: Option<u32>,
177 pub elem_segments: Vec<ElemSegmentInfo>,
184 pub func_type_indices: Vec<u32>,
187 pub type_signatures: Vec<String>,
192 pub wsc_facts: Vec<crate::wsc_facts::WscFact>,
201}
202
203impl DecodedModule {
204 pub fn call_indirect_guards(&self) -> CallIndirectGuards {
209 let n_types = self.type_signatures.len();
210 let reject_all = |table_size: Option<u32>, reason: String| CallIndirectGuards {
211 table_size,
212 type_reject: vec![Some(reason); n_types],
213 };
214
215 let Some(size) = self.table_size else {
216 return reject_all(
217 None,
218 "module has no table with a compile-time-fixed size".to_string(),
219 );
220 };
221
222 let mut slots: Vec<Option<u32>> = vec![None; size as usize];
224 for seg in &self.elem_segments {
225 let (Some(off), Some(funcs)) = (seg.offset, seg.funcs.as_ref()) else {
226 return reject_all(
227 Some(size),
228 "element segment is not statically verifiable (passive/declared \
229 segment, non-const offset, non-zero table, or non-`ref.func` \
230 entry)"
231 .to_string(),
232 );
233 };
234 for (k, &f) in funcs.iter().enumerate() {
235 let Some(slot) = slots.get_mut(off as usize + k) else {
236 return reject_all(
237 Some(size),
238 format!(
239 "element segment (offset {off}, {} entries) writes past the \
240 declared table size {size}",
241 funcs.len()
242 ),
243 );
244 };
245 *slot = Some(f);
246 }
247 }
248 if let Some(i) = slots.iter().position(|s| s.is_none()) {
252 return reject_all(
253 Some(size),
254 format!(
255 "table slot {i} is uninitialized (null funcref) — a \
256 `call_indirect` reaching it must trap, and the raw \
257 code-pointer table has no runtime null/type id to check"
258 ),
259 );
260 }
261
262 let type_reject = (0..n_types)
263 .map(|t| {
264 for f in slots.iter().flatten() {
265 let Some(&fty) = self.func_type_indices.get(*f as usize) else {
266 return Some(format!(
267 "table entry references function {f} with no known type"
268 ));
269 };
270 if self.type_signatures.get(fty as usize) != self.type_signatures.get(t) {
271 return Some(format!(
272 "table entry (function {f}, type {fty}) has a different \
273 signature than expected type {t} — a runtime type check \
274 is not implementable on the raw code-pointer table"
275 ));
276 }
277 }
278 None
279 })
280 .collect();
281
282 CallIndirectGuards {
283 table_size: Some(size),
284 type_reject,
285 }
286 }
287}
288
289pub fn decode_wasm_module(wasm_bytes: &[u8]) -> Result<DecodedModule> {
291 let mut functions = Vec::new();
292 let mut memories = Vec::new();
293 let mut data_segments = Vec::new();
294 let mut globals: Vec<WasmGlobal> = Vec::new();
295 let mut imports = Vec::new();
296 let mut func_index = 0u32;
297 let mut num_imported_funcs = 0u32;
298 let mut export_names: HashMap<u32, String> = HashMap::new();
299 let mut type_arg_counts: Vec<u32> = Vec::new();
302 let mut func_arg_counts: Vec<u32> = Vec::new();
303 let mut type_ret_i64: Vec<bool> = Vec::new();
304 let mut func_ret_i64: Vec<bool> = Vec::new();
305 let mut type_params_i64: Vec<Vec<bool>> = Vec::new();
307 let mut func_params_i64: Vec<Vec<bool>> = Vec::new();
308 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
310 let mut elem_func_indices: Vec<u32> = Vec::new();
311 let mut table_size: Option<u32> = None;
314 let mut saw_table = false;
315 let mut elem_segments: Vec<ElemSegmentInfo> = Vec::new();
316 let mut func_type_indices: Vec<u32> = Vec::new();
317 let mut type_signatures: Vec<String> = Vec::new();
318 let mut name_section_names: HashMap<u32, String> = HashMap::new();
324 let mut wsc_facts: Option<Vec<crate::wsc_facts::WscFact>> = None;
328
329 for payload in Parser::new(0).parse_all(wasm_bytes) {
330 let payload = payload.context("Failed to parse WASM payload")?;
331
332 match payload {
333 Payload::TypeSection(reader) => {
334 for rec_group in reader {
337 let rec_group = rec_group.context("Failed to parse type")?;
338 for sub_ty in rec_group.types() {
339 type_block_arity.push(match &sub_ty.composite_type.inner {
344 wasmparser::CompositeInnerType::Func(f) => (
345 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
346 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
347 ),
348 _ => (u8::MAX, u8::MAX),
349 });
350 let (count, ret_i64, params_i64) = match &sub_ty.composite_type.inner {
351 wasmparser::CompositeInnerType::Func(func_ty) => (
352 func_ty.params().len() as u32,
353 func_ty
354 .results()
355 .first()
356 .is_some_and(|t| *t == wasmparser::ValType::I64),
357 func_ty
362 .params()
363 .iter()
364 .map(|t| {
365 matches!(
366 t,
367 wasmparser::ValType::I64 | wasmparser::ValType::F64
368 )
369 })
370 .collect::<Vec<bool>>(),
371 ),
372 _ => (0, false, Vec::new()),
373 };
374 type_arg_counts.push(count);
375 type_ret_i64.push(ret_i64);
376 type_params_i64.push(params_i64);
377 type_signatures.push(match &sub_ty.composite_type.inner {
381 wasmparser::CompositeInnerType::Func(f) => {
382 format!("{:?}->{:?}", f.params(), f.results())
383 }
384 other => format!("non-func:{other:?}"),
385 });
386 }
387 }
388 }
389 Payload::ImportSection(reader) => {
390 for import in reader.into_imports() {
396 let import = import.context("Failed to parse import")?;
397 let (kind, idx) = match import.ty {
398 wasmparser::TypeRef::Func(type_idx) => {
399 let idx = num_imported_funcs;
400 num_imported_funcs += 1;
401 func_type_indices.push(type_idx); func_arg_counts
405 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
406 func_ret_i64.push(
407 type_ret_i64
408 .get(type_idx as usize)
409 .copied()
410 .unwrap_or(false),
411 );
412 func_params_i64.push(
413 type_params_i64
414 .get(type_idx as usize)
415 .cloned()
416 .unwrap_or_default(),
417 );
418 (ImportKind::Function(type_idx), idx)
419 }
420 wasmparser::TypeRef::Memory(_) => (ImportKind::Memory, 0),
421 wasmparser::TypeRef::Table(t) => {
422 if !saw_table {
428 saw_table = true;
429 table_size = match (u32::try_from(t.initial), t.maximum) {
430 (Ok(init), Some(max)) if u64::from(init) == max => Some(init),
431 _ => None,
432 };
433 }
434 (ImportKind::Table, 0)
435 }
436 wasmparser::TypeRef::Global(_) => (ImportKind::Global, 0),
437 _ => continue,
438 };
439 imports.push(ImportEntry {
440 module: import.module.to_string(),
441 name: import.name.to_string(),
442 kind,
443 index: idx,
444 });
445 }
446 }
447 Payload::FunctionSection(reader) => {
448 for ty in reader {
453 let type_idx = ty.context("Failed to parse function type index")?;
454 func_type_indices.push(type_idx); func_arg_counts
456 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
457 func_ret_i64.push(
458 type_ret_i64
459 .get(type_idx as usize)
460 .copied()
461 .unwrap_or(false),
462 );
463 func_params_i64.push(
464 type_params_i64
465 .get(type_idx as usize)
466 .cloned()
467 .unwrap_or_default(),
468 );
469 }
470 }
471 Payload::TableSection(reader) => {
472 for table in reader {
478 let table = table.context("Failed to parse table")?;
479 if !saw_table {
480 saw_table = true;
481 table_size = u32::try_from(table.ty.initial).ok();
482 }
483 }
484 }
485 Payload::MemorySection(reader) => {
486 for (idx, memory) in reader.into_iter().enumerate() {
487 let mem = memory.context("Failed to parse memory")?;
488 memories.push(WasmMemory {
489 index: idx as u32,
490 initial_pages: mem.initial as u32,
491 max_pages: mem.maximum.map(|m| m as u32),
492 shared: mem.shared,
493 });
494 }
495 }
496 Payload::GlobalSection(reader) => {
497 for (idx, global) in reader.into_iter().enumerate() {
503 let global = global.context("Failed to parse global")?;
504 let mut init_i32 = None;
505 let mut ops = global.init_expr.get_operators_reader();
506 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
507 init_i32 = Some(value);
508 }
509 let slot_bytes = match global.ty.content_type {
514 wasmparser::ValType::I64 | wasmparser::ValType::F64 => 8,
515 wasmparser::ValType::V128 => 16,
516 _ => 4,
517 };
518 globals.push(WasmGlobal {
519 index: idx as u32,
520 init_i32,
521 mutable: global.ty.mutable,
522 slot_bytes,
523 });
524 }
525 }
526 Payload::DataSection(reader) => {
527 for data in reader {
528 let data = data.context("Failed to parse data segment")?;
529 if let wasmparser::DataKind::Active {
530 memory_index: 0,
531 offset_expr,
532 } = data.kind
533 {
534 let mut ops = offset_expr.get_operators_reader();
535 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
536 data_segments.push((value as u32, data.data.to_vec()));
537 }
538 }
539 }
540 }
541 Payload::ElementSection(reader) => {
542 for elem in reader {
549 let elem = elem.context("Failed to parse element segment")?;
550 let seg_offset: Option<u32> = match &elem.kind {
554 wasmparser::ElementKind::Active {
555 table_index,
556 offset_expr,
557 } if table_index.unwrap_or(0) == 0 => {
558 let mut ops = offset_expr.get_operators_reader();
559 match ops.read() {
560 Ok(wasmparser::Operator::I32Const { value }) => {
561 u32::try_from(value).ok()
562 }
563 _ => None,
564 }
565 }
566 _ => None,
567 };
568 let mut seg_funcs: Option<Vec<u32>> = Some(Vec::new());
569 match elem.items {
570 wasmparser::ElementItems::Functions(funcs) => {
571 for f in funcs {
572 let f = f.context("Failed to parse element func index")?;
573 elem_func_indices.push(f);
574 if let Some(v) = seg_funcs.as_mut() {
575 v.push(f);
576 }
577 }
578 }
579 wasmparser::ElementItems::Expressions(_, exprs) => {
580 for expr in exprs {
581 let expr = expr.context("Failed to parse element expr")?;
582 let mut entry_func: Option<u32> = None;
587 let mut plain = true;
588 for (k, op) in expr.get_operators_reader().into_iter().enumerate() {
589 match (k, op.context("Failed to parse element op")?) {
590 (0, wasmparser::Operator::RefFunc { function_index }) => {
591 elem_func_indices.push(function_index);
592 entry_func = Some(function_index);
593 }
594 (_, wasmparser::Operator::End) => {}
595 (_, wasmparser::Operator::RefFunc { function_index }) => {
596 elem_func_indices.push(function_index);
600 plain = false;
601 }
602 _ => plain = false,
603 }
604 }
605 match (plain, entry_func, seg_funcs.as_mut()) {
606 (true, Some(f), Some(v)) => v.push(f),
607 _ => seg_funcs = None,
608 }
609 }
610 }
611 }
612 elem_segments.push(ElemSegmentInfo {
613 offset: seg_offset,
614 funcs: seg_funcs,
615 });
616 }
617 }
618 Payload::ExportSection(exports) => {
619 for export in exports {
620 let export = export.context("Failed to parse export")?;
621 if export.kind == ExternalKind::Func {
622 export_names.insert(export.index, export.name.to_string());
623 }
624 }
625 }
626 Payload::CodeSectionEntry(body) => {
627 let (ops, op_offsets, block_arity, unsupported) =
628 decode_function_body(&body, &type_block_arity)?;
629 let actual_index = num_imported_funcs + func_index;
630 let export_name = export_names.get(&actual_index).cloned();
631
632 functions.push(FunctionOps {
633 index: actual_index,
634 export_name,
635 debug_name: None, ops,
637 op_offsets,
638 unsupported,
639 block_arity,
640 });
641 func_index += 1;
642 }
643 Payload::CustomSection(c) => {
644 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
646 parse_name_section_func_names(reader, &mut name_section_names);
647 }
648 if c.name() == crate::wsc_facts::WSC_FACTS_SECTION_NAME && wsc_facts.is_none() {
655 let parsed = crate::wsc_facts::parse_wsc_facts(c.data());
656 if let Some(reason) = &parsed.section_ignored {
657 eprintln!(
658 "warning: ignoring unparseable `wsc.facts` custom section \
659 ({reason}) — facts are optional accelerators, compilation \
660 is unaffected (#494 fail-safe skew rule)"
661 );
662 } else if parsed.records_skipped > 0 {
663 eprintln!(
664 "warning: skipped {} unknown/undecodable `wsc.facts` \
665 record(s) (likely a newer loom emitter); {} known fact(s) \
666 kept, compilation is unaffected (#494 fail-safe skew rule)",
667 parsed.records_skipped,
668 parsed.facts.len()
669 );
670 }
671 wsc_facts = Some(parsed.facts);
672 }
673 }
674 _ => {}
675 }
676 }
677
678 apply_name_section(&mut functions, &name_section_names);
679
680 Ok(DecodedModule {
681 functions,
682 memories,
683 data_segments,
684 imports,
685 num_imported_funcs,
686 func_arg_counts,
687 type_arg_counts,
688 func_ret_i64,
689 type_ret_i64,
690 func_params_i64,
691 globals,
692 elem_func_indices,
693 table_size,
694 elem_segments,
695 func_type_indices,
696 type_signatures,
697 wsc_facts: wsc_facts.unwrap_or_default(),
698 })
699}
700
701fn parse_name_section_func_names(
707 reader: wasmparser::NameSectionReader<'_>,
708 out: &mut HashMap<u32, String>,
709) {
710 for subsection in reader.into_iter().flatten() {
711 if let wasmparser::Name::Function(map) = subsection {
712 for naming in map.into_iter().flatten() {
713 out.insert(naming.index, naming.name.to_string());
714 }
715 }
716 }
717}
718
719fn apply_name_section(functions: &mut [FunctionOps], names: &HashMap<u32, String>) {
723 if names.is_empty() {
724 return;
725 }
726 for f in functions {
727 f.debug_name = names.get(&f.index).cloned();
728 }
729}
730
731pub fn decode_wasm_functions(wasm_bytes: &[u8]) -> Result<Vec<FunctionOps>> {
733 let mut functions = Vec::new();
734 let mut func_index = 0u32;
735 let mut num_imported_funcs = 0u32;
736 let mut export_names: HashMap<u32, String> = HashMap::new();
737 let mut name_section_names: HashMap<u32, String> = HashMap::new();
738 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
740
741 for payload in Parser::new(0).parse_all(wasm_bytes) {
742 let payload = payload.context("Failed to parse WASM payload")?;
743
744 match payload {
745 Payload::TypeSection(reader) => {
746 for rec_group in reader {
751 let rec_group = rec_group.context("Failed to parse type")?;
752 for sub_ty in rec_group.types() {
753 type_block_arity.push(match &sub_ty.composite_type.inner {
754 wasmparser::CompositeInnerType::Func(f) => (
755 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
756 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
757 ),
758 _ => (u8::MAX, u8::MAX),
759 });
760 }
761 }
762 }
763 Payload::ImportSection(imports) => {
764 for import in imports.into_imports() {
767 let import = import.context("Failed to parse import")?;
768 if matches!(import.ty, wasmparser::TypeRef::Func(_)) {
769 num_imported_funcs += 1;
770 }
771 }
772 }
773 Payload::ExportSection(exports) => {
774 for export in exports {
775 let export = export.context("Failed to parse export")?;
776 if export.kind == ExternalKind::Func {
777 export_names.insert(export.index, export.name.to_string());
778 }
779 }
780 }
781 Payload::CodeSectionEntry(body) => {
782 let (ops, op_offsets, block_arity, unsupported) =
783 decode_function_body(&body, &type_block_arity)?;
784 let actual_index = num_imported_funcs + func_index;
785 let export_name = export_names.get(&actual_index).cloned();
786
787 functions.push(FunctionOps {
788 index: actual_index,
789 export_name,
790 debug_name: None, ops,
792 op_offsets,
793 unsupported,
794 block_arity,
795 });
796 func_index += 1;
797 }
798 Payload::CustomSection(c) => {
799 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
801 parse_name_section_func_names(reader, &mut name_section_names);
802 }
803 }
804 _ => {}
805 }
806 }
807
808 apply_name_section(&mut functions, &name_section_names);
809
810 Ok(functions)
811}
812
813#[derive(Debug, Clone)]
815pub struct FunctionOps {
816 pub index: u32,
818 pub export_name: Option<String>,
820 pub debug_name: Option<String>,
829 pub ops: Vec<WasmOp>,
831 pub op_offsets: Vec<u32>,
839 pub unsupported: Option<String>,
849 pub block_arity: Vec<(u8, u8)>,
865}
866
867fn blocktype_arity(bt: &wasmparser::BlockType, type_block_arity: &[(u8, u8)]) -> (u8, u8) {
872 match bt {
873 wasmparser::BlockType::Empty => (0, 0),
874 wasmparser::BlockType::Type(_) => (0, 1),
875 wasmparser::BlockType::FuncType(i) => type_block_arity
876 .get(*i as usize)
877 .copied()
878 .unwrap_or((u8::MAX, u8::MAX)),
879 }
880}
881
882type DecodedBody = (Vec<WasmOp>, Vec<u32>, Vec<(u8, u8)>, Option<String>);
886
887fn decode_function_body(
893 body: &wasmparser::FunctionBody,
894 type_block_arity: &[(u8, u8)],
895) -> Result<DecodedBody> {
896 let mut ops = Vec::new();
897 let mut op_offsets = Vec::new();
902 let mut block_arity: Vec<(u8, u8)> = Vec::new();
905 let mut unsupported: Option<String> = None;
906
907 let ops_reader = body.get_operators_reader()?;
908 for item in ops_reader.into_iter_with_offsets() {
909 let (op, offset) = item.context("Failed to read operator")?;
910
911 if let Some(wasm_op) = convert_operator(&op) {
912 if let wasmparser::Operator::Block { blockty }
915 | wasmparser::Operator::Loop { blockty }
916 | wasmparser::Operator::If { blockty } = &op
917 {
918 block_arity.push(blocktype_arity(blockty, type_block_arity));
919 }
920 ops.push(wasm_op);
921 op_offsets.push(offset as u32);
922 } else if unsupported.is_none() && !is_intentionally_ignored(&op) {
923 unsupported = Some(format!("{op:?}"));
927 }
928 }
929
930 Ok((ops, op_offsets, block_arity, unsupported))
931}
932
933fn is_intentionally_ignored(op: &wasmparser::Operator) -> bool {
938 use wasmparser::Operator::*;
939 matches!(op, Nop | Unreachable)
940}
941
942fn convert_operator(op: &wasmparser::Operator) -> Option<WasmOp> {
944 use wasmparser::Operator::*;
945
946 match op {
947 I32Const { value } => Some(WasmOp::I32Const(*value)),
949
950 I32Add => Some(WasmOp::I32Add),
952 I32Sub => Some(WasmOp::I32Sub),
953 I32Mul => Some(WasmOp::I32Mul),
954 I32DivS => Some(WasmOp::I32DivS),
955 I32DivU => Some(WasmOp::I32DivU),
956 I32RemS => Some(WasmOp::I32RemS),
957 I32RemU => Some(WasmOp::I32RemU),
958
959 I64Const { value } => Some(WasmOp::I64Const(*value)),
961
962 I64Add => Some(WasmOp::I64Add),
964 I64Sub => Some(WasmOp::I64Sub),
965 I64Mul => Some(WasmOp::I64Mul),
966 I64DivS => Some(WasmOp::I64DivS),
967 I64DivU => Some(WasmOp::I64DivU),
968 I64RemS => Some(WasmOp::I64RemS),
969 I64RemU => Some(WasmOp::I64RemU),
970
971 I64And => Some(WasmOp::I64And),
973 I64Or => Some(WasmOp::I64Or),
974 I64Xor => Some(WasmOp::I64Xor),
975 I64Shl => Some(WasmOp::I64Shl),
976 I64ShrS => Some(WasmOp::I64ShrS),
977 I64ShrU => Some(WasmOp::I64ShrU),
978 I64Rotl => Some(WasmOp::I64Rotl),
979 I64Rotr => Some(WasmOp::I64Rotr),
980 I64Clz => Some(WasmOp::I64Clz),
981 I64Ctz => Some(WasmOp::I64Ctz),
982 I64Popcnt => Some(WasmOp::I64Popcnt),
983 I64Extend8S => Some(WasmOp::I64Extend8S),
984 I64Extend16S => Some(WasmOp::I64Extend16S),
985 I64Extend32S => Some(WasmOp::I64Extend32S),
986 I64ExtendI32U => Some(WasmOp::I64ExtendI32U),
992 I64ExtendI32S => Some(WasmOp::I64ExtendI32S),
993 I32WrapI64 => Some(WasmOp::I32WrapI64),
994
995 I64Eqz => Some(WasmOp::I64Eqz),
997 I64Eq => Some(WasmOp::I64Eq),
998 I64Ne => Some(WasmOp::I64Ne),
999 I64LtS => Some(WasmOp::I64LtS),
1000 I64LtU => Some(WasmOp::I64LtU),
1001 I64LeS => Some(WasmOp::I64LeS),
1002 I64LeU => Some(WasmOp::I64LeU),
1003 I64GtS => Some(WasmOp::I64GtS),
1004 I64GtU => Some(WasmOp::I64GtU),
1005 I64GeS => Some(WasmOp::I64GeS),
1006 I64GeU => Some(WasmOp::I64GeU),
1007
1008 I32And => Some(WasmOp::I32And),
1010 I32Or => Some(WasmOp::I32Or),
1011 I32Xor => Some(WasmOp::I32Xor),
1012 I32Shl => Some(WasmOp::I32Shl),
1013 I32ShrS => Some(WasmOp::I32ShrS),
1014 I32ShrU => Some(WasmOp::I32ShrU),
1015 I32Rotl => Some(WasmOp::I32Rotl),
1016 I32Rotr => Some(WasmOp::I32Rotr),
1017 I32Clz => Some(WasmOp::I32Clz),
1018 I32Ctz => Some(WasmOp::I32Ctz),
1019 I32Popcnt => Some(WasmOp::I32Popcnt),
1020 I32Extend8S => Some(WasmOp::I32Extend8S),
1021 I32Extend16S => Some(WasmOp::I32Extend16S),
1022
1023 I32Eqz => Some(WasmOp::I32Eqz),
1025 I32Eq => Some(WasmOp::I32Eq),
1026 I32Ne => Some(WasmOp::I32Ne),
1027 I32LtS => Some(WasmOp::I32LtS),
1028 I32LtU => Some(WasmOp::I32LtU),
1029 I32LeS => Some(WasmOp::I32LeS),
1030 I32LeU => Some(WasmOp::I32LeU),
1031 I32GtS => Some(WasmOp::I32GtS),
1032 I32GtU => Some(WasmOp::I32GtU),
1033 I32GeS => Some(WasmOp::I32GeS),
1034 I32GeU => Some(WasmOp::I32GeU),
1035
1036 I32Load { memarg } => Some(WasmOp::I32Load {
1038 offset: memarg.offset as u32,
1039 align: memarg.align as u32,
1040 }),
1041 I32Store { memarg } => Some(WasmOp::I32Store {
1042 offset: memarg.offset as u32,
1043 align: memarg.align as u32,
1044 }),
1045 I64Load { memarg } => Some(WasmOp::I64Load {
1052 offset: memarg.offset as u32,
1053 align: memarg.align as u32,
1054 }),
1055 I64Store { memarg } => Some(WasmOp::I64Store {
1056 offset: memarg.offset as u32,
1057 align: memarg.align as u32,
1058 }),
1059
1060 I32Load8S { memarg } => Some(WasmOp::I32Load8S {
1062 offset: memarg.offset as u32,
1063 align: memarg.align as u32,
1064 }),
1065 I32Load8U { memarg } => Some(WasmOp::I32Load8U {
1066 offset: memarg.offset as u32,
1067 align: memarg.align as u32,
1068 }),
1069 I32Load16S { memarg } => Some(WasmOp::I32Load16S {
1070 offset: memarg.offset as u32,
1071 align: memarg.align as u32,
1072 }),
1073 I32Load16U { memarg } => Some(WasmOp::I32Load16U {
1074 offset: memarg.offset as u32,
1075 align: memarg.align as u32,
1076 }),
1077
1078 I32Store8 { memarg } => Some(WasmOp::I32Store8 {
1080 offset: memarg.offset as u32,
1081 align: memarg.align as u32,
1082 }),
1083 I32Store16 { memarg } => Some(WasmOp::I32Store16 {
1084 offset: memarg.offset as u32,
1085 align: memarg.align as u32,
1086 }),
1087
1088 LocalGet { local_index } => Some(WasmOp::LocalGet(*local_index)),
1090 LocalSet { local_index } => Some(WasmOp::LocalSet(*local_index)),
1091 LocalTee { local_index } => Some(WasmOp::LocalTee(*local_index)),
1092 GlobalGet { global_index } => Some(WasmOp::GlobalGet(*global_index)),
1093 GlobalSet { global_index } => Some(WasmOp::GlobalSet(*global_index)),
1094
1095 Block { .. } => Some(WasmOp::Block),
1097 Loop { .. } => Some(WasmOp::Loop),
1098 Br { relative_depth } => Some(WasmOp::Br(*relative_depth)),
1099 BrIf { relative_depth } => Some(WasmOp::BrIf(*relative_depth)),
1100 BrTable { targets } => {
1106 let default = targets.default();
1107 let tgts: Vec<u32> = targets.targets().filter_map(Result::ok).collect();
1108 Some(WasmOp::BrTable {
1109 targets: tgts,
1110 default,
1111 })
1112 }
1113 Return => Some(WasmOp::Return),
1114 Call { function_index } => Some(WasmOp::Call(*function_index)),
1115 CallIndirect {
1116 type_index,
1117 table_index,
1118 ..
1119 } => Some(WasmOp::CallIndirect {
1120 type_index: *type_index,
1121 table_index: *table_index,
1122 }),
1123
1124 End => Some(WasmOp::End),
1126
1127 Nop | Unreachable => None,
1129
1130 Drop => Some(WasmOp::Drop),
1132
1133 Select => Some(WasmOp::Select),
1135
1136 If { .. } => Some(WasmOp::If),
1138 Else => Some(WasmOp::Else),
1139
1140 I64Load8S { memarg } => Some(WasmOp::I64Load8S {
1142 offset: memarg.offset as u32,
1143 align: memarg.align as u32,
1144 }),
1145 I64Load8U { memarg } => Some(WasmOp::I64Load8U {
1146 offset: memarg.offset as u32,
1147 align: memarg.align as u32,
1148 }),
1149 I64Load16S { memarg } => Some(WasmOp::I64Load16S {
1150 offset: memarg.offset as u32,
1151 align: memarg.align as u32,
1152 }),
1153 I64Load16U { memarg } => Some(WasmOp::I64Load16U {
1154 offset: memarg.offset as u32,
1155 align: memarg.align as u32,
1156 }),
1157 I64Load32S { memarg } => Some(WasmOp::I64Load32S {
1158 offset: memarg.offset as u32,
1159 align: memarg.align as u32,
1160 }),
1161 I64Load32U { memarg } => Some(WasmOp::I64Load32U {
1162 offset: memarg.offset as u32,
1163 align: memarg.align as u32,
1164 }),
1165
1166 I64Store8 { memarg } => Some(WasmOp::I64Store8 {
1168 offset: memarg.offset as u32,
1169 align: memarg.align as u32,
1170 }),
1171 I64Store16 { memarg } => Some(WasmOp::I64Store16 {
1172 offset: memarg.offset as u32,
1173 align: memarg.align as u32,
1174 }),
1175 I64Store32 { memarg } => Some(WasmOp::I64Store32 {
1176 offset: memarg.offset as u32,
1177 align: memarg.align as u32,
1178 }),
1179
1180 MemorySize { mem, .. } => Some(WasmOp::MemorySize(*mem)),
1182 MemoryGrow { mem, .. } => Some(WasmOp::MemoryGrow(*mem)),
1183
1184 MemoryCopy {
1191 dst_mem: 0,
1192 src_mem: 0,
1193 } => Some(WasmOp::MemoryCopy),
1194 MemoryFill { mem: 0 } => Some(WasmOp::MemoryFill),
1195
1196 V128Const { value } => {
1200 let mut bytes = [0u8; 16];
1201 bytes.copy_from_slice(value.bytes());
1202 Some(WasmOp::V128Const(bytes))
1203 }
1204 V128Load { memarg } => Some(WasmOp::V128Load {
1205 offset: memarg.offset as u32,
1206 align: memarg.align as u32,
1207 }),
1208 V128Store { memarg } => Some(WasmOp::V128Store {
1209 offset: memarg.offset as u32,
1210 align: memarg.align as u32,
1211 }),
1212
1213 V128And => Some(WasmOp::V128And),
1215 V128Or => Some(WasmOp::V128Or),
1216 V128Xor => Some(WasmOp::V128Xor),
1217 V128Not => Some(WasmOp::V128Not),
1218 V128AndNot => Some(WasmOp::V128AndNot),
1219
1220 I8x16Add => Some(WasmOp::I8x16Add),
1222 I8x16Sub => Some(WasmOp::I8x16Sub),
1223 I8x16Neg => Some(WasmOp::I8x16Neg),
1224 I8x16Eq => Some(WasmOp::I8x16Eq),
1225 I8x16Ne => Some(WasmOp::I8x16Ne),
1226 I8x16LtS => Some(WasmOp::I8x16LtS),
1227 I8x16LtU => Some(WasmOp::I8x16LtU),
1228 I8x16GtS => Some(WasmOp::I8x16GtS),
1229 I8x16GtU => Some(WasmOp::I8x16GtU),
1230 I8x16LeS => Some(WasmOp::I8x16LeS),
1231 I8x16LeU => Some(WasmOp::I8x16LeU),
1232 I8x16GeS => Some(WasmOp::I8x16GeS),
1233 I8x16GeU => Some(WasmOp::I8x16GeU),
1234 I8x16Splat => Some(WasmOp::I8x16Splat),
1235 I8x16ExtractLaneS { lane } => Some(WasmOp::I8x16ExtractLaneS(*lane)),
1236 I8x16ExtractLaneU { lane } => Some(WasmOp::I8x16ExtractLaneU(*lane)),
1237 I8x16ReplaceLane { lane } => Some(WasmOp::I8x16ReplaceLane(*lane)),
1238 I8x16Shuffle { lanes } => Some(WasmOp::I8x16Shuffle(*lanes)),
1239 I8x16Swizzle => Some(WasmOp::I8x16Swizzle),
1240
1241 I16x8Add => Some(WasmOp::I16x8Add),
1243 I16x8Sub => Some(WasmOp::I16x8Sub),
1244 I16x8Mul => Some(WasmOp::I16x8Mul),
1245 I16x8Neg => Some(WasmOp::I16x8Neg),
1246 I16x8Eq => Some(WasmOp::I16x8Eq),
1247 I16x8Ne => Some(WasmOp::I16x8Ne),
1248 I16x8LtS => Some(WasmOp::I16x8LtS),
1249 I16x8LtU => Some(WasmOp::I16x8LtU),
1250 I16x8GtS => Some(WasmOp::I16x8GtS),
1251 I16x8GtU => Some(WasmOp::I16x8GtU),
1252 I16x8LeS => Some(WasmOp::I16x8LeS),
1253 I16x8LeU => Some(WasmOp::I16x8LeU),
1254 I16x8GeS => Some(WasmOp::I16x8GeS),
1255 I16x8GeU => Some(WasmOp::I16x8GeU),
1256 I16x8Splat => Some(WasmOp::I16x8Splat),
1257 I16x8ExtractLaneS { lane } => Some(WasmOp::I16x8ExtractLaneS(*lane)),
1258 I16x8ExtractLaneU { lane } => Some(WasmOp::I16x8ExtractLaneU(*lane)),
1259 I16x8ReplaceLane { lane } => Some(WasmOp::I16x8ReplaceLane(*lane)),
1260
1261 I32x4Add => Some(WasmOp::I32x4Add),
1263 I32x4Sub => Some(WasmOp::I32x4Sub),
1264 I32x4Mul => Some(WasmOp::I32x4Mul),
1265 I32x4Neg => Some(WasmOp::I32x4Neg),
1266 I32x4Eq => Some(WasmOp::I32x4Eq),
1267 I32x4Ne => Some(WasmOp::I32x4Ne),
1268 I32x4LtS => Some(WasmOp::I32x4LtS),
1269 I32x4LtU => Some(WasmOp::I32x4LtU),
1270 I32x4GtS => Some(WasmOp::I32x4GtS),
1271 I32x4GtU => Some(WasmOp::I32x4GtU),
1272 I32x4LeS => Some(WasmOp::I32x4LeS),
1273 I32x4LeU => Some(WasmOp::I32x4LeU),
1274 I32x4GeS => Some(WasmOp::I32x4GeS),
1275 I32x4GeU => Some(WasmOp::I32x4GeU),
1276 I32x4Splat => Some(WasmOp::I32x4Splat),
1277 I32x4ExtractLane { lane } => Some(WasmOp::I32x4ExtractLane(*lane)),
1278 I32x4ReplaceLane { lane } => Some(WasmOp::I32x4ReplaceLane(*lane)),
1279
1280 I64x2Add => Some(WasmOp::I64x2Add),
1282 I64x2Sub => Some(WasmOp::I64x2Sub),
1283 I64x2Mul => Some(WasmOp::I64x2Mul),
1284 I64x2Neg => Some(WasmOp::I64x2Neg),
1285 I64x2Eq => Some(WasmOp::I64x2Eq),
1286 I64x2Ne => Some(WasmOp::I64x2Ne),
1287 I64x2LtS => Some(WasmOp::I64x2LtS),
1288 I64x2GtS => Some(WasmOp::I64x2GtS),
1289 I64x2LeS => Some(WasmOp::I64x2LeS),
1290 I64x2GeS => Some(WasmOp::I64x2GeS),
1291 I64x2Splat => Some(WasmOp::I64x2Splat),
1292 I64x2ExtractLane { lane } => Some(WasmOp::I64x2ExtractLane(*lane)),
1293 I64x2ReplaceLane { lane } => Some(WasmOp::I64x2ReplaceLane(*lane)),
1294
1295 F32x4Add => Some(WasmOp::F32x4Add),
1297 F32x4Sub => Some(WasmOp::F32x4Sub),
1298 F32x4Mul => Some(WasmOp::F32x4Mul),
1299 F32x4Div => Some(WasmOp::F32x4Div),
1300 F32x4Abs => Some(WasmOp::F32x4Abs),
1301 F32x4Neg => Some(WasmOp::F32x4Neg),
1302 F32x4Sqrt => Some(WasmOp::F32x4Sqrt),
1303 F32x4Eq => Some(WasmOp::F32x4Eq),
1304 F32x4Ne => Some(WasmOp::F32x4Ne),
1305 F32x4Lt => Some(WasmOp::F32x4Lt),
1306 F32x4Le => Some(WasmOp::F32x4Le),
1307 F32x4Gt => Some(WasmOp::F32x4Gt),
1308 F32x4Ge => Some(WasmOp::F32x4Ge),
1309 F32x4Splat => Some(WasmOp::F32x4Splat),
1310 F32x4ExtractLane { lane } => Some(WasmOp::F32x4ExtractLane(*lane)),
1311 F32x4ReplaceLane { lane } => Some(WasmOp::F32x4ReplaceLane(*lane)),
1312
1313 _ => None,
1315 }
1316}
1317
1318#[cfg(test)]
1319mod tests {
1320 use super::*;
1321
1322 #[test]
1323 fn test_decode_simple_add() {
1324 let wat = r#"
1325 (module
1326 (func (export "add") (param i32 i32) (result i32)
1327 local.get 0
1328 local.get 1
1329 i32.add
1330 )
1331 )
1332 "#;
1333
1334 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1335 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1336
1337 assert_eq!(functions.len(), 1);
1338 assert_eq!(functions[0].index, 0);
1339 assert_eq!(functions[0].export_name, Some("add".to_string()));
1340 assert_eq!(
1341 functions[0].ops,
1342 vec![
1343 WasmOp::LocalGet(0),
1344 WasmOp::LocalGet(1),
1345 WasmOp::I32Add,
1346 WasmOp::End
1347 ]
1348 );
1349 }
1350
1351 #[test]
1357 fn test_decode_i64_i32_width_conversions() {
1358 let wat = r#"
1359 (module
1360 (func (export "conv") (param i32 i64) (result i32)
1361 local.get 0
1362 i64.extend_i32_u
1363 local.get 0
1364 i64.extend_i32_s
1365 i64.add
1366 local.get 1
1367 i64.add
1368 i32.wrap_i64
1369 )
1370 )
1371 "#;
1372 let wasm = wat::parse_str(wat).expect("parse");
1373 let functions = decode_wasm_functions(&wasm).expect("decode");
1374 let ops = &functions[0].ops;
1375 assert!(
1376 ops.contains(&WasmOp::I64ExtendI32U),
1377 "i64.extend_i32_u must decode (not be dropped): {ops:?}"
1378 );
1379 assert!(
1380 ops.contains(&WasmOp::I64ExtendI32S),
1381 "i64.extend_i32_s must decode (not be dropped): {ops:?}"
1382 );
1383 assert!(
1384 ops.contains(&WasmOp::I32WrapI64),
1385 "i32.wrap_i64 must decode (not be dropped): {ops:?}"
1386 );
1387 }
1388
1389 #[test]
1394 fn test_decode_br_table() {
1395 let wat = r#"
1396 (module
1397 (func (export "bt") (param i32) (result i32)
1398 (block (block (block
1399 local.get 0
1400 br_table 2 0 1 2)
1401 i32.const 30 return)
1402 i32.const 20 return)
1403 i32.const 10))
1404 "#;
1405 let wasm = wat::parse_str(wat).expect("parse");
1406 let functions = decode_wasm_functions(&wasm).expect("decode");
1407 let bt = functions[0]
1408 .ops
1409 .iter()
1410 .find_map(|o| match o {
1411 WasmOp::BrTable { targets, default } => Some((targets.clone(), *default)),
1412 _ => None,
1413 })
1414 .expect("br_table must decode (not be dropped)");
1415 assert_eq!(bt.0, vec![2, 0, 1], "br_table targets preserved in order");
1416 assert_eq!(bt.1, 2, "br_table default preserved");
1417 }
1418
1419 #[test]
1420 fn test_decode_arithmetic() {
1421 let wat = r#"
1422 (module
1423 (func (export "calc") (result i32)
1424 i32.const 5
1425 i32.const 3
1426 i32.mul
1427 i32.const 2
1428 i32.add
1429 )
1430 )
1431 "#;
1432
1433 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1434 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1435
1436 assert_eq!(functions.len(), 1);
1437 assert_eq!(functions[0].export_name, Some("calc".to_string()));
1438 assert_eq!(
1439 functions[0].ops,
1440 vec![
1441 WasmOp::I32Const(5),
1442 WasmOp::I32Const(3),
1443 WasmOp::I32Mul,
1444 WasmOp::I32Const(2),
1445 WasmOp::I32Add,
1446 WasmOp::End,
1447 ]
1448 );
1449 }
1450
1451 #[test]
1452 fn test_decode_multi_function_module() {
1453 let wat = r#"
1454 (module
1455 (func $helper)
1456 (func (export "add") (param i32 i32) (result i32)
1457 local.get 0
1458 local.get 1
1459 i32.add
1460 )
1461 (func (export "sub") (param i32 i32) (result i32)
1462 local.get 0
1463 local.get 1
1464 i32.sub
1465 )
1466 )
1467 "#;
1468
1469 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1470 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1471
1472 assert_eq!(functions.len(), 3);
1473 assert_eq!(functions[0].index, 0);
1474 assert_eq!(functions[0].export_name, None);
1475 assert_eq!(functions[1].index, 1);
1476 assert_eq!(functions[1].export_name, Some("add".to_string()));
1477 assert_eq!(functions[2].index, 2);
1478 assert_eq!(functions[2].export_name, Some("sub".to_string()));
1479 }
1480
1481 #[test]
1482 fn test_decode_module_with_imports() {
1483 let wat = r#"
1484 (module
1485 (import "env" "log" (func $log (param i32)))
1486 (import "env" "memory" (memory 1))
1487 (func (export "run") (param i32)
1488 local.get 0
1489 call 0
1490 )
1491 )
1492 "#;
1493
1494 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1495 let module = decode_wasm_module(&wasm).expect("Failed to decode");
1496
1497 assert_eq!(module.imports.len(), 2);
1499 assert_eq!(module.num_imported_funcs, 1);
1500
1501 assert_eq!(module.imports[0].module, "env");
1503 assert_eq!(module.imports[0].name, "log");
1504 assert!(matches!(module.imports[0].kind, ImportKind::Function(_)));
1505
1506 assert_eq!(module.imports[1].module, "env");
1508 assert_eq!(module.imports[1].name, "memory");
1509 assert_eq!(module.imports[1].kind, ImportKind::Memory);
1510
1511 assert_eq!(module.functions.len(), 1);
1513 assert_eq!(module.functions[0].index, 1);
1514 assert_eq!(module.functions[0].export_name, Some("run".to_string()));
1515 }
1516
1517 #[test]
1518 fn test_find_function_by_export_name() {
1519 let wat = r#"
1520 (module
1521 (func $helper)
1522 (func (export "add") (param i32 i32) (result i32)
1523 local.get 0
1524 local.get 1
1525 i32.add
1526 )
1527 )
1528 "#;
1529
1530 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1531 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1532
1533 let add_func = functions
1534 .iter()
1535 .find(|f| f.export_name.as_deref() == Some("add"))
1536 .expect("Should find 'add' function");
1537
1538 assert_eq!(add_func.index, 1);
1539 assert!(add_func.ops.contains(&WasmOp::I32Add));
1540 }
1541
1542 #[test]
1543 fn test_decode_subword_loads() {
1544 let wat = r#"
1545 (module
1546 (memory 1)
1547 (func (export "test") (param i32) (result i32)
1548 local.get 0
1549 i32.load8_u
1550 )
1551 )
1552 "#;
1553
1554 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1555 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1556
1557 assert_eq!(functions.len(), 1);
1558 assert!(functions[0].ops.contains(&WasmOp::I32Load8U {
1559 offset: 0,
1560 align: 0,
1561 }));
1562 }
1563
1564 #[test]
1565 fn test_decode_subword_stores() {
1566 let wat = r#"
1567 (module
1568 (memory 1)
1569 (func (export "test") (param i32 i32)
1570 local.get 0
1571 local.get 1
1572 i32.store8
1573 )
1574 )
1575 "#;
1576
1577 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1578 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1579
1580 assert_eq!(functions.len(), 1);
1581 assert!(functions[0].ops.contains(&WasmOp::I32Store8 {
1582 offset: 0,
1583 align: 0,
1584 }));
1585 }
1586
1587 #[test]
1588 fn test_decode_memory_size_grow() {
1589 let wat = r#"
1590 (module
1591 (memory 1)
1592 (func (export "test") (result i32)
1593 memory.size
1594 )
1595 )
1596 "#;
1597
1598 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1599 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1600
1601 assert_eq!(functions.len(), 1);
1602 assert!(functions[0].ops.contains(&WasmOp::MemorySize(0)));
1603 }
1604
1605 #[test]
1606 fn test_decode_memory_grow() {
1607 let wat = r#"
1608 (module
1609 (memory 1)
1610 (func (export "test") (param i32) (result i32)
1611 local.get 0
1612 memory.grow
1613 )
1614 )
1615 "#;
1616
1617 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1618 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1619
1620 assert_eq!(functions.len(), 1);
1621 assert!(functions[0].ops.contains(&WasmOp::MemoryGrow(0)));
1622 }
1623
1624 #[test]
1625 fn test_decode_bulk_memory_374() {
1626 let wat = r#"
1629 (module
1630 (memory 1)
1631 (func (export "cpy") (param i32 i32 i32)
1632 local.get 0 local.get 1 local.get 2 memory.copy)
1633 (func (export "fil") (param i32 i32 i32)
1634 local.get 0 local.get 1 local.get 2 memory.fill)
1635 )
1636 "#;
1637 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1638 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1639 assert_eq!(functions.len(), 2);
1640 assert!(functions[0].ops.contains(&WasmOp::MemoryCopy));
1641 assert!(functions[1].ops.contains(&WasmOp::MemoryFill));
1642 assert!(functions[0].unsupported.is_none());
1644 assert!(functions[1].unsupported.is_none());
1645 }
1646
1647 #[test]
1648 fn test_decode_i64_subword_loads() {
1649 let wat = r#"
1650 (module
1651 (memory 1)
1652 (func (export "test") (param i32) (result i64)
1653 local.get 0
1654 i64.load8_s
1655 )
1656 )
1657 "#;
1658
1659 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1660 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1661
1662 assert_eq!(functions.len(), 1);
1663 assert!(functions[0].ops.contains(&WasmOp::I64Load8S {
1664 offset: 0,
1665 align: 0,
1666 }));
1667 }
1668
1669 #[test]
1670 fn test_decode_all_subword_memory_ops() {
1671 let wat = r#"
1673 (module
1674 (memory 1)
1675 (func (export "test") (param i32)
1676 ;; i32 sub-word loads
1677 local.get 0
1678 i32.load8_s
1679 drop
1680 local.get 0
1681 i32.load8_u
1682 drop
1683 local.get 0
1684 i32.load16_s
1685 drop
1686 local.get 0
1687 i32.load16_u
1688 drop
1689
1690 ;; i32 sub-word stores
1691 local.get 0
1692 i32.const 42
1693 i32.store8
1694 local.get 0
1695 i32.const 42
1696 i32.store16
1697
1698 ;; i64 sub-word loads
1699 local.get 0
1700 i64.load8_s
1701 drop
1702 local.get 0
1703 i64.load8_u
1704 drop
1705 local.get 0
1706 i64.load16_s
1707 drop
1708 local.get 0
1709 i64.load16_u
1710 drop
1711 local.get 0
1712 i64.load32_s
1713 drop
1714 local.get 0
1715 i64.load32_u
1716 drop
1717
1718 ;; i64 sub-word stores
1719 local.get 0
1720 i64.const 42
1721 i64.store8
1722 local.get 0
1723 i64.const 42
1724 i64.store16
1725 local.get 0
1726 i64.const 42
1727 i64.store32
1728 )
1729 )
1730 "#;
1731
1732 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1733 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1734
1735 assert_eq!(functions.len(), 1);
1736 let ops = &functions[0].ops;
1737
1738 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8S { .. })));
1740 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8U { .. })));
1741 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16S { .. })));
1742 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16U { .. })));
1743 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store8 { .. })));
1744 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store16 { .. })));
1745
1746 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8S { .. })));
1748 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8U { .. })));
1749 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16S { .. })));
1750 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16U { .. })));
1751 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32S { .. })));
1752 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32U { .. })));
1753 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store8 { .. })));
1754 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store16 { .. })));
1755 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store32 { .. })));
1756 }
1757
1758 #[test]
1759 fn test_decode_simd_i32x4_add() {
1760 let wat = r#"
1761 (module
1762 (func (export "add_v128") (param v128 v128) (result v128)
1763 local.get 0
1764 local.get 1
1765 i32x4.add
1766 )
1767 )
1768 "#;
1769
1770 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1771 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1772
1773 assert_eq!(functions.len(), 1);
1774 assert!(
1775 functions[0].ops.contains(&WasmOp::I32x4Add),
1776 "Should decode i32x4.add: {:?}",
1777 functions[0].ops
1778 );
1779 }
1780
1781 #[test]
1782 fn test_decode_simd_v128_const() {
1783 let wat = r#"
1784 (module
1785 (func (export "const_v128") (result v128)
1786 v128.const i32x4 1 2 3 4
1787 )
1788 )
1789 "#;
1790
1791 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1792 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1793
1794 assert_eq!(functions.len(), 1);
1795 assert!(
1796 functions[0]
1797 .ops
1798 .iter()
1799 .any(|o| matches!(o, WasmOp::V128Const(_))),
1800 "Should decode v128.const: {:?}",
1801 functions[0].ops
1802 );
1803 }
1804
1805 #[test]
1806 fn test_decode_simd_v128_load_store() {
1807 let wat = r#"
1808 (module
1809 (memory 1)
1810 (func (export "load_store") (param i32)
1811 local.get 0
1812 v128.load
1813 local.get 0
1814 v128.store
1815 )
1816 )
1817 "#;
1818
1819 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1820 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1821
1822 assert_eq!(functions.len(), 1);
1823 let ops = &functions[0].ops;
1824 assert!(
1825 ops.iter().any(|o| matches!(o, WasmOp::V128Load { .. })),
1826 "Should decode v128.load"
1827 );
1828 assert!(
1829 ops.iter().any(|o| matches!(o, WasmOp::V128Store { .. })),
1830 "Should decode v128.store"
1831 );
1832 }
1833
1834 #[test]
1835 fn test_decode_simd_bitwise_ops() {
1836 let wat = r#"
1837 (module
1838 (func (export "bitwise") (param v128 v128) (result v128)
1839 local.get 0
1840 local.get 1
1841 v128.and
1842 )
1843 )
1844 "#;
1845
1846 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1847 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1848
1849 assert_eq!(functions.len(), 1);
1850 assert!(functions[0].ops.contains(&WasmOp::V128And));
1851 }
1852
1853 #[test]
1854 fn test_decode_simd_splat() {
1855 let wat = r#"
1856 (module
1857 (func (export "splat") (param i32) (result v128)
1858 local.get 0
1859 i32x4.splat
1860 )
1861 )
1862 "#;
1863
1864 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1865 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1866
1867 assert_eq!(functions.len(), 1);
1868 assert!(functions[0].ops.contains(&WasmOp::I32x4Splat));
1869 }
1870
1871 #[test]
1872 fn test_decode_simd_extract_lane() {
1873 let wat = r#"
1874 (module
1875 (func (export "extract") (param v128) (result i32)
1876 local.get 0
1877 i32x4.extract_lane 2
1878 )
1879 )
1880 "#;
1881
1882 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1883 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1884
1885 assert_eq!(functions.len(), 1);
1886 assert!(
1887 functions[0].ops.contains(&WasmOp::I32x4ExtractLane(2)),
1888 "Should decode i32x4.extract_lane 2"
1889 );
1890 }
1891
1892 #[test]
1893 fn test_decode_simd_f32x4_arithmetic() {
1894 let wat = r#"
1895 (module
1896 (func (export "f32x4_add") (param v128 v128) (result v128)
1897 local.get 0
1898 local.get 1
1899 f32x4.add
1900 )
1901 )
1902 "#;
1903
1904 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1905 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1906
1907 assert_eq!(functions.len(), 1);
1908 assert!(functions[0].ops.contains(&WasmOp::F32x4Add));
1909 }
1910
1911 #[test]
1912 fn test_369_scalar_float_op_flags_function_unsupported_not_dropped() {
1913 let wat = r#"
1917 (module
1918 (func (export "fadd") (param f32 f32) (result f32)
1919 local.get 0 local.get 1 f32.add)
1920 (func (export "iadd") (param i32 i32) (result i32)
1921 local.get 0 local.get 1 i32.add))
1922 "#;
1923 let wasm = wat::parse_str(wat).expect("parse");
1924 let functions = decode_wasm_functions(&wasm).expect("decode");
1925 let fadd = functions
1926 .iter()
1927 .find(|f| f.export_name.as_deref() == Some("fadd"))
1928 .unwrap();
1929 let iadd = functions
1930 .iter()
1931 .find(|f| f.export_name.as_deref() == Some("iadd"))
1932 .unwrap();
1933 assert!(
1934 fadd.unsupported.is_some(),
1935 "f32.add must flag the function unsupported (loud-skip), got {:?}",
1936 fadd.unsupported
1937 );
1938 assert!(
1939 fadd.unsupported.as_deref().unwrap().contains("F32Add"),
1940 "diagnostic should name the op: {:?}",
1941 fadd.unsupported
1942 );
1943 assert!(
1944 iadd.unsupported.is_none(),
1945 "a pure-integer function must NOT be flagged: {:?}",
1946 iadd.unsupported
1947 );
1948 }
1949
1950 #[test]
1951 fn test_decode_simd_multiple_ops() {
1952 let wat = r#"
1953 (module
1954 (func (export "simd_ops") (param v128 v128 v128) (result v128)
1955 ;; (a + b) * c
1956 local.get 0
1957 local.get 1
1958 i32x4.add
1959 local.get 2
1960 i32x4.mul
1961 )
1962 )
1963 "#;
1964
1965 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1966 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1967
1968 assert_eq!(functions.len(), 1);
1969 let ops = &functions[0].ops;
1970 assert!(ops.contains(&WasmOp::I32x4Add));
1971 assert!(ops.contains(&WasmOp::I32x4Mul));
1972 }
1973
1974 #[test]
1980 fn test_decode_records_aligned_increasing_op_offsets_dbg001() {
1981 let wat = r#"
1982 (module
1983 (func (export "f") (param i32 i32) (result i32)
1984 local.get 0
1985 local.get 1
1986 i32.add
1987 i32.const 7
1988 i32.mul))
1989 "#;
1990 let wasm = wat::parse_str(wat).expect("parse WAT");
1991 let functions = decode_wasm_functions(&wasm).expect("decode");
1992 let f = &functions[0];
1993
1994 assert_eq!(
1996 f.op_offsets.len(),
1997 f.ops.len(),
1998 "op_offsets must be parallel to ops"
1999 );
2000 assert!(!f.op_offsets.is_empty());
2001
2002 assert!(
2005 f.op_offsets.windows(2).all(|w| w[1] > w[0]),
2006 "wasm byte offsets must strictly increase: {:?}",
2007 f.op_offsets
2008 );
2009 assert!(
2010 f.op_offsets[0] >= 8,
2011 "module-relative offset is past the 8-byte wasm header"
2012 );
2013 }
2014
2015 #[test]
2018 fn test_decode_captures_global_initializer() {
2019 let wat = r#"
2020 (module
2021 (memory 2)
2022 (global $__stack_pointer (mut i32) (i32.const 65536))
2023 (global $immutable_const i32 (i32.const 7))
2024 (func (export "f") (result i32) global.get 0)
2025 )
2026 "#;
2027 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2028 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2029
2030 assert_eq!(module.globals.len(), 2, "both globals captured");
2031 let sp = &module.globals[0];
2032 assert_eq!(sp.index, 0);
2033 assert_eq!(sp.init_i32, Some(65536), "stack-pointer init captured");
2034 assert!(sp.mutable, "stack pointer is mutable");
2035 let c = &module.globals[1];
2036 assert_eq!(c.init_i32, Some(7));
2037 assert!(!c.mutable, "second global is immutable");
2038 assert_eq!(sp.slot_bytes, 4, "i32 global occupies one 4-byte slot");
2039 assert_eq!(c.slot_bytes, 4);
2040 }
2041
2042 #[test]
2046 fn test_decode_records_global_slot_widths_643() {
2047 let wat = r#"
2048 (module
2049 (global $c (mut i64) (i64.const 0))
2050 (global $k (mut i32) (i32.const 0))
2051 (global $f (mut f64) (f64.const 0))
2052 (func (export "f") (result i32) global.get 1)
2053 )
2054 "#;
2055 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2056 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2057
2058 assert_eq!(module.globals.len(), 3);
2059 assert_eq!(module.globals[0].slot_bytes, 8, "i64 global is 8 bytes");
2060 assert_eq!(module.globals[1].slot_bytes, 4, "i32 global is 4 bytes");
2061 assert_eq!(module.globals[2].slot_bytes, 8, "f64 global is 8 bytes");
2062 }
2063
2064 #[test]
2070 fn test_decode_records_block_arity_side_table_509() {
2071 let wat = r#"
2072 (module
2073 (func (export "f") (param i32) (result i32)
2074 (block (result i32)
2075 (block (nop))
2076 (local.get 0)
2077 (if (result i32)
2078 (then (i32.const 1))
2079 (else (i32.const 2)))))
2080 (func (export "g") (result i32)
2081 (block (result i32 i32)
2082 (i32.const 1) (i32.const 2))
2083 i32.add)
2084 (func (export "h") (param i32) (result i32)
2085 (local.get 0)
2086 (loop (param i32) (result i32))))
2087 "#;
2088 let wasm = wat::parse_str(wat).expect("parse WAT");
2089
2090 for functions in [
2092 decode_wasm_functions(&wasm).expect("decode"),
2093 decode_wasm_module(&wasm).expect("decode").functions,
2094 ] {
2095 assert_eq!(
2097 functions[0].block_arity,
2098 vec![(0, 1), (0, 0), (0, 1)],
2099 "f: ValType/Empty/ValType blocktypes"
2100 );
2101 assert_eq!(
2103 functions[1].block_arity,
2104 vec![(0, 2)],
2105 "g: functype blocktype result count from the type section"
2106 );
2107 assert_eq!(
2110 functions[2].block_arity,
2111 vec![(1, 1)],
2112 "h: loop params captured"
2113 );
2114 }
2115 }
2116
2117 #[test]
2121 fn test_call_indirect_guards_closed_world_verified_642() {
2122 let wat = r#"
2124 (module
2125 (type $bin (func (param i32 i32) (result i32)))
2126 (table 3 funcref)
2127 (elem (i32.const 0) $add $sub $mul)
2128 (func $add (param i32 i32) (result i32)
2129 (i32.add (local.get 0) (local.get 1)))
2130 (func $sub (param i32 i32) (result i32)
2131 (i32.sub (local.get 0) (local.get 1)))
2132 (func $mul (param i32 i32) (result i32)
2133 (i32.mul (local.get 0) (local.get 1)))
2134 (func (export "f") (param i32 i32) (result i32)
2135 (call_indirect (type $bin)
2136 (local.get 0) (i32.const 10) (local.get 1)))
2137 )
2138 "#;
2139 let wasm = wat::parse_str(wat).expect("parse");
2140 let module = decode_wasm_module(&wasm).expect("decode");
2141
2142 assert_eq!(module.table_size, Some(3), "table section min size");
2143 assert_eq!(
2144 module.elem_segments,
2145 vec![ElemSegmentInfo {
2146 offset: Some(0),
2147 funcs: Some(vec![0, 1, 2]),
2148 }]
2149 );
2150 assert_eq!(module.func_type_indices.len(), 4);
2154
2155 let guards = module.call_indirect_guards();
2156 assert_eq!(guards.table_size, Some(3));
2157 assert_eq!(
2160 guards.type_reject.first(),
2161 Some(&None),
2162 "closed-world type check must verify the homogeneous table: {:?}",
2163 guards.type_reject
2164 );
2165 }
2166
2167 #[test]
2171 fn test_call_indirect_guards_heterogeneous_table_rejects_642() {
2172 let wat = r#"
2173 (module
2174 (type $bin (func (param i32 i32) (result i32)))
2175 (type $un (func (param i32) (result i32)))
2176 (table 2 funcref)
2177 (elem (i32.const 0) $add $neg)
2178 (func $add (type $bin)
2179 (i32.add (local.get 0) (local.get 1)))
2180 (func $neg (type $un)
2181 (i32.sub (i32.const 0) (local.get 0)))
2182 (func (export "f") (param i32 i32) (result i32)
2183 (call_indirect (type $bin)
2184 (local.get 0) (i32.const 10) (local.get 1)))
2185 )
2186 "#;
2187 let wasm = wat::parse_str(wat).expect("parse");
2188 let module = decode_wasm_module(&wasm).expect("decode");
2189 let guards = module.call_indirect_guards();
2190 assert_eq!(guards.table_size, Some(2));
2191 assert!(
2194 guards.type_reject[0].is_some() && guards.type_reject[1].is_some(),
2195 "heterogeneous table must reject every expected type: {:?}",
2196 guards.type_reject
2197 );
2198 }
2199
2200 #[test]
2204 fn test_call_indirect_guards_null_slot_rejects_642() {
2205 let wat = r#"
2206 (module
2207 (type $s (func (result i32)))
2208 (table 3 funcref)
2209 (elem (i32.const 0) $f0 $f1)
2210 (func $f0 (result i32) (i32.const 10))
2211 (func $f1 (result i32) (i32.const 11))
2212 (func (export "run") (param i32) (result i32)
2213 (call_indirect (type $s) (local.get 0)))
2214 )
2215 "#;
2216 let wasm = wat::parse_str(wat).expect("parse");
2217 let module = decode_wasm_module(&wasm).expect("decode");
2218 let guards = module.call_indirect_guards();
2219 assert_eq!(guards.table_size, Some(3));
2220 assert!(
2221 guards.type_reject.iter().all(|r| r.is_some()),
2222 "slot 2 is a null funcref — every type must be rejected: {:?}",
2223 guards.type_reject
2224 );
2225 assert!(
2226 guards.type_reject[0].as_deref().unwrap().contains("slot 2"),
2227 "reason names the uninitialized slot: {:?}",
2228 guards.type_reject[0]
2229 );
2230 }
2231
2232 #[test]
2235 fn test_call_indirect_guards_no_table_642() {
2236 let wat = r#"
2237 (module
2238 (func (export "f") (param i32) (result i32) (local.get 0))
2239 )
2240 "#;
2241 let wasm = wat::parse_str(wat).expect("parse");
2242 let module = decode_wasm_module(&wasm).expect("decode");
2243 assert_eq!(module.table_size, None);
2244 let guards = module.call_indirect_guards();
2245 assert_eq!(guards.table_size, None);
2246 assert!(guards.type_reject.iter().all(|r| r.is_some()));
2247 }
2248
2249 #[test]
2252 fn test_call_indirect_guards_duplicate_types_verified_642() {
2253 let wat = r#"
2254 (module
2255 (type $a (func (result i32)))
2256 (type $b (func (result i32)))
2257 (table 1 funcref)
2258 (elem (i32.const 0) $f)
2259 (func $f (type $a) (i32.const 7))
2260 (func (export "run") (param i32) (result i32)
2261 (call_indirect (type $b) (local.get 0)))
2262 )
2263 "#;
2264 let wasm = wat::parse_str(wat).expect("parse");
2265 let module = decode_wasm_module(&wasm).expect("decode");
2266 let guards = module.call_indirect_guards();
2267 assert_eq!(
2271 &guards.type_reject[0..2],
2272 &[None, None],
2273 "structural signature comparison must accept duplicate types: {:?}",
2274 guards.type_reject
2275 );
2276 assert!(
2277 guards.type_reject[2].is_some(),
2278 "the structurally-different third type must still be rejected"
2279 );
2280 }
2281}