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, Copy, PartialEq, Eq)]
57pub enum GlobalInit {
58 I32(i32),
60 I64(i64),
63}
64
65#[derive(Debug, Clone)]
71pub struct WasmGlobal {
72 pub index: u32,
74 pub init: Option<GlobalInit>,
78 pub mutable: bool,
80 pub slot_bytes: u32,
85}
86
87impl WasmMemory {
88 pub fn initial_bytes(&self) -> u32 {
90 self.initial_pages * 65536
91 }
92
93 pub fn max_bytes(&self) -> u32 {
95 self.max_pages.unwrap_or(self.initial_pages) * 65536
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct ElemSegmentInfo {
103 pub table_index: u32,
106 pub offset: Option<u32>,
110 pub funcs: Option<Vec<u32>>,
113}
114
115#[derive(Debug, Clone, Default, PartialEq, Eq)]
118pub struct TableGuards {
119 pub table_size: Option<u32>,
122 pub base_byte_offset: Option<u32>,
127 pub type_reject: Vec<Option<String>>,
131}
132
133#[derive(Debug, Clone, Default, PartialEq, Eq)]
162pub struct CallIndirectGuards {
163 pub tables: Vec<TableGuards>,
166}
167
168impl CallIndirectGuards {
169 pub fn single_table(table_size: Option<u32>, type_reject: Vec<Option<String>>) -> Self {
172 Self {
173 tables: vec![TableGuards {
174 table_size,
175 base_byte_offset: Some(0),
176 type_reject,
177 }],
178 }
179 }
180}
181
182#[derive(Debug, Clone)]
184pub struct DecodedModule {
185 pub functions: Vec<FunctionOps>,
187 pub memories: Vec<WasmMemory>,
189 pub data_segments: Vec<(u32, Vec<u8>)>,
191 pub imports: Vec<ImportEntry>,
193 pub num_imported_funcs: u32,
195 pub func_arg_counts: Vec<u32>,
201 pub type_arg_counts: Vec<u32>,
205 pub func_ret_i64: Vec<bool>,
209 pub type_ret_i64: Vec<bool>,
211 pub func_params_i64: Vec<Vec<bool>>,
216 pub globals: Vec<WasmGlobal>,
221 pub elem_func_indices: Vec<u32>,
228 pub table_size: Option<u32>,
231 pub table_sizes: Vec<Option<u32>>,
241 pub elem_segments: Vec<ElemSegmentInfo>,
248 pub func_type_indices: Vec<u32>,
251 pub type_signatures: Vec<String>,
256 pub wsc_facts: Vec<crate::wsc_facts::WscFact>,
265}
266
267impl DecodedModule {
268 pub fn call_indirect_guards(&self) -> CallIndirectGuards {
275 let n_types = self.type_signatures.len();
276
277 let global_poison: Option<&'static str> = self
283 .elem_segments
284 .iter()
285 .any(|seg| seg.offset.is_none() || seg.table_index as usize >= self.table_sizes.len())
286 .then_some(
287 "element segment is not statically verifiable (passive/declared \
288 segment, non-const offset, out-of-range table, or non-`ref.func` \
289 entry)",
290 );
291
292 let mut tables = Vec::with_capacity(self.table_sizes.len());
293 let mut base_words: Option<u32> = Some(0);
297 for (n, &size) in self.table_sizes.iter().enumerate() {
298 let base_byte_offset = base_words.and_then(|w| w.checked_mul(4));
299 let type_reject = self.table_type_reject(n as u32, size, global_poison, n_types);
300 tables.push(TableGuards {
301 table_size: size,
302 base_byte_offset,
303 type_reject,
304 });
305 base_words = match (base_words, size) {
306 (Some(w), Some(s)) => w.checked_add(s),
307 _ => None,
308 };
309 }
310 CallIndirectGuards { tables }
311 }
312
313 fn table_type_reject(
317 &self,
318 n: u32,
319 size: Option<u32>,
320 global_poison: Option<&str>,
321 n_types: usize,
322 ) -> Vec<Option<String>> {
323 let reject_all = |reason: String| vec![Some(reason); n_types];
324
325 if let Some(reason) = global_poison {
326 return reject_all(reason.to_string());
327 }
328 let Some(size) = size else {
329 return reject_all(format!(
330 "table {n} has no compile-time-fixed size (imported table with \
331 growable limits)"
332 ));
333 };
334
335 let mut slots: Vec<Option<u32>> = vec![None; size as usize];
337 for seg in self.elem_segments.iter().filter(|s| s.table_index == n) {
338 let (Some(off), Some(funcs)) = (seg.offset, seg.funcs.as_ref()) else {
339 return reject_all(format!(
343 "element segment targeting table {n} is not statically \
344 verifiable (non-`ref.func` entry)"
345 ));
346 };
347 for (k, &f) in funcs.iter().enumerate() {
348 let Some(slot) = slots.get_mut(off as usize + k) else {
349 return reject_all(format!(
350 "element segment (offset {off}, {} entries) writes past \
351 table {n}'s declared size {size}",
352 funcs.len()
353 ));
354 };
355 *slot = Some(f);
356 }
357 }
358 if let Some(i) = slots.iter().position(|s| s.is_none()) {
362 return reject_all(format!(
363 "table {n} slot {i} is uninitialized (null funcref) — a \
364 `call_indirect` reaching it must trap, and the raw \
365 code-pointer table has no runtime null/type id to check"
366 ));
367 }
368
369 (0..n_types)
370 .map(|t| {
371 for f in slots.iter().flatten() {
372 let Some(&fty) = self.func_type_indices.get(*f as usize) else {
373 return Some(format!(
374 "table {n} entry references function {f} with no known type"
375 ));
376 };
377 if self.type_signatures.get(fty as usize) != self.type_signatures.get(t) {
378 return Some(format!(
379 "table {n} entry (function {f}, type {fty}) has a different \
380 signature than expected type {t} — a runtime type check \
381 is not implementable on the raw code-pointer table"
382 ));
383 }
384 }
385 None
386 })
387 .collect()
388 }
389}
390
391pub fn decode_wasm_module(wasm_bytes: &[u8]) -> Result<DecodedModule> {
393 let mut functions = Vec::new();
394 let mut memories = Vec::new();
395 let mut data_segments = Vec::new();
396 let mut globals: Vec<WasmGlobal> = Vec::new();
397 let mut imports = Vec::new();
398 let mut func_index = 0u32;
399 let mut num_imported_funcs = 0u32;
400 let mut export_names: HashMap<u32, String> = HashMap::new();
401 let mut type_arg_counts: Vec<u32> = Vec::new();
404 let mut func_arg_counts: Vec<u32> = Vec::new();
405 let mut type_ret_i64: Vec<bool> = Vec::new();
406 let mut func_ret_i64: Vec<bool> = Vec::new();
407 let mut type_params_i64: Vec<Vec<bool>> = Vec::new();
409 let mut func_params_i64: Vec<Vec<bool>> = Vec::new();
410 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
412 let mut elem_func_indices: Vec<u32> = Vec::new();
413 let mut table_sizes: Vec<Option<u32>> = Vec::new();
417 let mut elem_segments: Vec<ElemSegmentInfo> = Vec::new();
418 let mut func_type_indices: Vec<u32> = Vec::new();
419 let mut type_signatures: Vec<String> = Vec::new();
420 let mut name_section_names: HashMap<u32, String> = HashMap::new();
426 let mut wsc_facts: Option<Vec<crate::wsc_facts::WscFact>> = None;
430 let mut num_imported_globals = 0u32;
437 let mut float_globals: std::collections::HashSet<u32> = std::collections::HashSet::new();
438
439 for payload in Parser::new(0).parse_all(wasm_bytes) {
440 let payload = payload.context("Failed to parse WASM payload")?;
441
442 match payload {
443 Payload::TypeSection(reader) => {
444 for rec_group in reader {
447 let rec_group = rec_group.context("Failed to parse type")?;
448 for sub_ty in rec_group.types() {
449 type_block_arity.push(match &sub_ty.composite_type.inner {
454 wasmparser::CompositeInnerType::Func(f) => (
455 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
456 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
457 ),
458 _ => (u8::MAX, u8::MAX),
459 });
460 let (count, ret_i64, params_i64) = match &sub_ty.composite_type.inner {
461 wasmparser::CompositeInnerType::Func(func_ty) => (
462 func_ty.params().len() as u32,
463 func_ty
464 .results()
465 .first()
466 .is_some_and(|t| *t == wasmparser::ValType::I64),
467 func_ty
472 .params()
473 .iter()
474 .map(|t| {
475 matches!(
476 t,
477 wasmparser::ValType::I64 | wasmparser::ValType::F64
478 )
479 })
480 .collect::<Vec<bool>>(),
481 ),
482 _ => (0, false, Vec::new()),
483 };
484 type_arg_counts.push(count);
485 type_ret_i64.push(ret_i64);
486 type_params_i64.push(params_i64);
487 type_signatures.push(match &sub_ty.composite_type.inner {
491 wasmparser::CompositeInnerType::Func(f) => {
492 format!("{:?}->{:?}", f.params(), f.results())
493 }
494 other => format!("non-func:{other:?}"),
495 });
496 }
497 }
498 }
499 Payload::ImportSection(reader) => {
500 for import in reader.into_imports() {
506 let import = import.context("Failed to parse import")?;
507 let (kind, idx) = match import.ty {
508 wasmparser::TypeRef::Func(type_idx) => {
509 let idx = num_imported_funcs;
510 num_imported_funcs += 1;
511 func_type_indices.push(type_idx); func_arg_counts
515 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
516 func_ret_i64.push(
517 type_ret_i64
518 .get(type_idx as usize)
519 .copied()
520 .unwrap_or(false),
521 );
522 func_params_i64.push(
523 type_params_i64
524 .get(type_idx as usize)
525 .cloned()
526 .unwrap_or_default(),
527 );
528 (ImportKind::Function(type_idx), idx)
529 }
530 wasmparser::TypeRef::Memory(_) => (ImportKind::Memory, 0),
531 wasmparser::TypeRef::Table(t) => {
532 table_sizes.push(match (u32::try_from(t.initial), t.maximum) {
540 (Ok(init), Some(max)) if u64::from(init) == max => Some(init),
541 _ => None,
542 });
543 (ImportKind::Table, 0)
544 }
545 wasmparser::TypeRef::Global(g) => {
546 if matches!(
550 g.content_type,
551 wasmparser::ValType::F32 | wasmparser::ValType::F64
552 ) {
553 float_globals.insert(num_imported_globals);
554 }
555 num_imported_globals += 1;
556 (ImportKind::Global, 0)
557 }
558 _ => continue,
559 };
560 imports.push(ImportEntry {
561 module: import.module.to_string(),
562 name: import.name.to_string(),
563 kind,
564 index: idx,
565 });
566 }
567 }
568 Payload::FunctionSection(reader) => {
569 for ty in reader {
574 let type_idx = ty.context("Failed to parse function type index")?;
575 func_type_indices.push(type_idx); func_arg_counts
577 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
578 func_ret_i64.push(
579 type_ret_i64
580 .get(type_idx as usize)
581 .copied()
582 .unwrap_or(false),
583 );
584 func_params_i64.push(
585 type_params_i64
586 .get(type_idx as usize)
587 .cloned()
588 .unwrap_or_default(),
589 );
590 }
591 }
592 Payload::TableSection(reader) => {
593 for table in reader {
600 let table = table.context("Failed to parse table")?;
601 table_sizes.push(u32::try_from(table.ty.initial).ok());
602 }
603 }
604 Payload::MemorySection(reader) => {
605 for (idx, memory) in reader.into_iter().enumerate() {
606 let mem = memory.context("Failed to parse memory")?;
607 memories.push(WasmMemory {
608 index: idx as u32,
609 initial_pages: mem.initial as u32,
610 max_pages: mem.maximum.map(|m| m as u32),
611 shared: mem.shared,
612 });
613 }
614 }
615 Payload::GlobalSection(reader) => {
616 for (idx, global) in reader.into_iter().enumerate() {
624 let global = global.context("Failed to parse global")?;
625 let mut ops = global.init_expr.get_operators_reader();
626 let init = match ops.read() {
627 Ok(wasmparser::Operator::I32Const { value }) => {
628 Some(GlobalInit::I32(value))
629 }
630 Ok(wasmparser::Operator::I64Const { value }) => {
631 Some(GlobalInit::I64(value))
632 }
633 _ => None,
634 };
635 let slot_bytes = match global.ty.content_type {
640 wasmparser::ValType::I64 | wasmparser::ValType::F64 => 8,
641 wasmparser::ValType::V128 => 16,
642 _ => 4,
643 };
644 if matches!(
650 global.ty.content_type,
651 wasmparser::ValType::F32 | wasmparser::ValType::F64
652 ) {
653 float_globals.insert(num_imported_globals + idx as u32);
654 }
655 globals.push(WasmGlobal {
656 index: idx as u32,
657 init,
658 mutable: global.ty.mutable,
659 slot_bytes,
660 });
661 }
662 }
663 Payload::DataSection(reader) => {
664 for data in reader {
665 let data = data.context("Failed to parse data segment")?;
666 if let wasmparser::DataKind::Active {
667 memory_index: 0,
668 offset_expr,
669 } = data.kind
670 {
671 let mut ops = offset_expr.get_operators_reader();
672 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
673 data_segments.push((value as u32, data.data.to_vec()));
674 }
675 }
676 }
677 }
678 Payload::ElementSection(reader) => {
679 for elem in reader {
686 let elem = elem.context("Failed to parse element segment")?;
687 let (seg_table, seg_offset): (u32, Option<u32>) = match &elem.kind {
693 wasmparser::ElementKind::Active {
694 table_index,
695 offset_expr,
696 } => {
697 let mut ops = offset_expr.get_operators_reader();
698 let off = match ops.read() {
699 Ok(wasmparser::Operator::I32Const { value }) => {
700 u32::try_from(value).ok()
701 }
702 _ => None,
703 };
704 (table_index.unwrap_or(0), off)
705 }
706 _ => (0, None),
707 };
708 let mut seg_funcs: Option<Vec<u32>> = Some(Vec::new());
709 match elem.items {
710 wasmparser::ElementItems::Functions(funcs) => {
711 for f in funcs {
712 let f = f.context("Failed to parse element func index")?;
713 elem_func_indices.push(f);
714 if let Some(v) = seg_funcs.as_mut() {
715 v.push(f);
716 }
717 }
718 }
719 wasmparser::ElementItems::Expressions(_, exprs) => {
720 for expr in exprs {
721 let expr = expr.context("Failed to parse element expr")?;
722 let mut entry_func: Option<u32> = None;
727 let mut plain = true;
728 for (k, op) in expr.get_operators_reader().into_iter().enumerate() {
729 match (k, op.context("Failed to parse element op")?) {
730 (0, wasmparser::Operator::RefFunc { function_index }) => {
731 elem_func_indices.push(function_index);
732 entry_func = Some(function_index);
733 }
734 (_, wasmparser::Operator::End) => {}
735 (_, wasmparser::Operator::RefFunc { function_index }) => {
736 elem_func_indices.push(function_index);
740 plain = false;
741 }
742 _ => plain = false,
743 }
744 }
745 match (plain, entry_func, seg_funcs.as_mut()) {
746 (true, Some(f), Some(v)) => v.push(f),
747 _ => seg_funcs = None,
748 }
749 }
750 }
751 }
752 elem_segments.push(ElemSegmentInfo {
753 table_index: seg_table,
754 offset: seg_offset,
755 funcs: seg_funcs,
756 });
757 }
758 }
759 Payload::ExportSection(exports) => {
760 for export in exports {
761 let export = export.context("Failed to parse export")?;
762 if export.kind == ExternalKind::Func {
763 export_names.insert(export.index, export.name.to_string());
764 }
765 }
766 }
767 Payload::CodeSectionEntry(body) => {
768 let (ops, op_offsets, block_arity, unsupported) =
769 decode_function_body(&body, &type_block_arity, &float_globals)?;
770 let actual_index = num_imported_funcs + func_index;
771 let export_name = export_names.get(&actual_index).cloned();
772
773 functions.push(FunctionOps {
774 index: actual_index,
775 export_name,
776 debug_name: None, ops,
778 op_offsets,
779 unsupported,
780 block_arity,
781 });
782 func_index += 1;
783 }
784 Payload::CustomSection(c) => {
785 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
787 parse_name_section_func_names(reader, &mut name_section_names);
788 }
789 if c.name() == crate::wsc_facts::WSC_FACTS_SECTION_NAME && wsc_facts.is_none() {
796 let parsed = crate::wsc_facts::parse_wsc_facts(c.data());
797 if let Some(reason) = &parsed.section_ignored {
798 eprintln!(
799 "warning: ignoring unparseable `wsc.facts` custom section \
800 ({reason}) — facts are optional accelerators, compilation \
801 is unaffected (#494 fail-safe skew rule)"
802 );
803 } else if parsed.records_skipped > 0 {
804 eprintln!(
805 "warning: skipped {} unknown/undecodable `wsc.facts` \
806 record(s) (likely a newer loom emitter); {} known fact(s) \
807 kept, compilation is unaffected (#494 fail-safe skew rule)",
808 parsed.records_skipped,
809 parsed.facts.len()
810 );
811 }
812 wsc_facts = Some(parsed.facts);
813 }
814 }
815 _ => {}
816 }
817 }
818
819 apply_name_section(&mut functions, &name_section_names);
820
821 Ok(DecodedModule {
822 functions,
823 memories,
824 data_segments,
825 imports,
826 num_imported_funcs,
827 func_arg_counts,
828 type_arg_counts,
829 func_ret_i64,
830 type_ret_i64,
831 func_params_i64,
832 globals,
833 elem_func_indices,
834 table_size: table_sizes.first().copied().flatten(),
835 table_sizes,
836 elem_segments,
837 func_type_indices,
838 type_signatures,
839 wsc_facts: wsc_facts.unwrap_or_default(),
840 })
841}
842
843fn parse_name_section_func_names(
849 reader: wasmparser::NameSectionReader<'_>,
850 out: &mut HashMap<u32, String>,
851) {
852 for subsection in reader.into_iter().flatten() {
853 if let wasmparser::Name::Function(map) = subsection {
854 for naming in map.into_iter().flatten() {
855 out.insert(naming.index, naming.name.to_string());
856 }
857 }
858 }
859}
860
861fn apply_name_section(functions: &mut [FunctionOps], names: &HashMap<u32, String>) {
865 if names.is_empty() {
866 return;
867 }
868 for f in functions {
869 f.debug_name = names.get(&f.index).cloned();
870 }
871}
872
873pub fn decode_wasm_functions(wasm_bytes: &[u8]) -> Result<Vec<FunctionOps>> {
875 let mut functions = Vec::new();
876 let mut func_index = 0u32;
877 let mut num_imported_funcs = 0u32;
878 let mut export_names: HashMap<u32, String> = HashMap::new();
879 let mut name_section_names: HashMap<u32, String> = HashMap::new();
880 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
882 let mut num_imported_globals = 0u32;
885 let mut float_globals: std::collections::HashSet<u32> = std::collections::HashSet::new();
886
887 for payload in Parser::new(0).parse_all(wasm_bytes) {
888 let payload = payload.context("Failed to parse WASM payload")?;
889
890 match payload {
891 Payload::TypeSection(reader) => {
892 for rec_group in reader {
897 let rec_group = rec_group.context("Failed to parse type")?;
898 for sub_ty in rec_group.types() {
899 type_block_arity.push(match &sub_ty.composite_type.inner {
900 wasmparser::CompositeInnerType::Func(f) => (
901 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
902 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
903 ),
904 _ => (u8::MAX, u8::MAX),
905 });
906 }
907 }
908 }
909 Payload::ImportSection(imports) => {
910 for import in imports.into_imports() {
913 let import = import.context("Failed to parse import")?;
914 match import.ty {
915 wasmparser::TypeRef::Func(_) => num_imported_funcs += 1,
916 wasmparser::TypeRef::Global(g) => {
917 if matches!(
920 g.content_type,
921 wasmparser::ValType::F32 | wasmparser::ValType::F64
922 ) {
923 float_globals.insert(num_imported_globals);
924 }
925 num_imported_globals += 1;
926 }
927 _ => {}
928 }
929 }
930 }
931 Payload::GlobalSection(reader) => {
932 for (idx, global) in reader.into_iter().enumerate() {
936 let global = global.context("Failed to parse global")?;
937 if matches!(
938 global.ty.content_type,
939 wasmparser::ValType::F32 | wasmparser::ValType::F64
940 ) {
941 float_globals.insert(num_imported_globals + idx as u32);
942 }
943 }
944 }
945 Payload::ExportSection(exports) => {
946 for export in exports {
947 let export = export.context("Failed to parse export")?;
948 if export.kind == ExternalKind::Func {
949 export_names.insert(export.index, export.name.to_string());
950 }
951 }
952 }
953 Payload::CodeSectionEntry(body) => {
954 let (ops, op_offsets, block_arity, unsupported) =
955 decode_function_body(&body, &type_block_arity, &float_globals)?;
956 let actual_index = num_imported_funcs + func_index;
957 let export_name = export_names.get(&actual_index).cloned();
958
959 functions.push(FunctionOps {
960 index: actual_index,
961 export_name,
962 debug_name: None, ops,
964 op_offsets,
965 unsupported,
966 block_arity,
967 });
968 func_index += 1;
969 }
970 Payload::CustomSection(c) => {
971 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
973 parse_name_section_func_names(reader, &mut name_section_names);
974 }
975 }
976 _ => {}
977 }
978 }
979
980 apply_name_section(&mut functions, &name_section_names);
981
982 Ok(functions)
983}
984
985#[derive(Debug, Clone)]
987pub struct FunctionOps {
988 pub index: u32,
990 pub export_name: Option<String>,
992 pub debug_name: Option<String>,
1001 pub ops: Vec<WasmOp>,
1003 pub op_offsets: Vec<u32>,
1011 pub unsupported: Option<String>,
1021 pub block_arity: Vec<(u8, u8)>,
1037}
1038
1039fn blocktype_arity(bt: &wasmparser::BlockType, type_block_arity: &[(u8, u8)]) -> (u8, u8) {
1044 match bt {
1045 wasmparser::BlockType::Empty => (0, 0),
1046 wasmparser::BlockType::Type(_) => (0, 1),
1047 wasmparser::BlockType::FuncType(i) => type_block_arity
1048 .get(*i as usize)
1049 .copied()
1050 .unwrap_or((u8::MAX, u8::MAX)),
1051 }
1052}
1053
1054type DecodedBody = (Vec<WasmOp>, Vec<u32>, Vec<(u8, u8)>, Option<String>);
1058
1059fn decode_function_body(
1065 body: &wasmparser::FunctionBody,
1066 type_block_arity: &[(u8, u8)],
1067 float_globals: &std::collections::HashSet<u32>,
1068) -> Result<DecodedBody> {
1069 let mut ops = Vec::new();
1070 let mut op_offsets = Vec::new();
1075 let mut block_arity: Vec<(u8, u8)> = Vec::new();
1078 let mut unsupported: Option<String> = None;
1079
1080 let ops_reader = body.get_operators_reader()?;
1081 for item in ops_reader.into_iter_with_offsets() {
1082 let (op, offset) = item.context("Failed to read operator")?;
1083
1084 if let Some(wasm_op) = convert_operator(&op) {
1085 if let wasmparser::Operator::Block { blockty }
1088 | wasmparser::Operator::Loop { blockty }
1089 | wasmparser::Operator::If { blockty } = &op
1090 {
1091 block_arity.push(blocktype_arity(blockty, type_block_arity));
1092 }
1093 if unsupported.is_none()
1099 && let WasmOp::GlobalGet(i) | WasmOp::GlobalSet(i) = &wasm_op
1100 && float_globals.contains(i)
1101 {
1102 unsupported = Some(format!(
1103 "{wasm_op:?} on an f32/f64-typed global — float globals \
1104 have no lowering, the initializer would be silently \
1105 zeroed (GI-FPU-001)"
1106 ));
1107 }
1108 ops.push(wasm_op);
1109 op_offsets.push(offset as u32);
1110 } else if unsupported.is_none() && !is_intentionally_ignored(&op) {
1111 unsupported = Some(format!("{op:?}"));
1115 }
1116 }
1117
1118 Ok((ops, op_offsets, block_arity, unsupported))
1119}
1120
1121fn is_intentionally_ignored(op: &wasmparser::Operator) -> bool {
1126 use wasmparser::Operator::*;
1127 matches!(op, Nop | Unreachable)
1128}
1129
1130fn convert_operator(op: &wasmparser::Operator) -> Option<WasmOp> {
1132 use wasmparser::Operator::*;
1133
1134 match op {
1135 I32Const { value } => Some(WasmOp::I32Const(*value)),
1137
1138 I32Add => Some(WasmOp::I32Add),
1140 I32Sub => Some(WasmOp::I32Sub),
1141 I32Mul => Some(WasmOp::I32Mul),
1142 I32DivS => Some(WasmOp::I32DivS),
1143 I32DivU => Some(WasmOp::I32DivU),
1144 I32RemS => Some(WasmOp::I32RemS),
1145 I32RemU => Some(WasmOp::I32RemU),
1146
1147 I64Const { value } => Some(WasmOp::I64Const(*value)),
1149
1150 I64Add => Some(WasmOp::I64Add),
1152 I64Sub => Some(WasmOp::I64Sub),
1153 I64Mul => Some(WasmOp::I64Mul),
1154 I64DivS => Some(WasmOp::I64DivS),
1155 I64DivU => Some(WasmOp::I64DivU),
1156 I64RemS => Some(WasmOp::I64RemS),
1157 I64RemU => Some(WasmOp::I64RemU),
1158
1159 I64And => Some(WasmOp::I64And),
1161 I64Or => Some(WasmOp::I64Or),
1162 I64Xor => Some(WasmOp::I64Xor),
1163 I64Shl => Some(WasmOp::I64Shl),
1164 I64ShrS => Some(WasmOp::I64ShrS),
1165 I64ShrU => Some(WasmOp::I64ShrU),
1166 I64Rotl => Some(WasmOp::I64Rotl),
1167 I64Rotr => Some(WasmOp::I64Rotr),
1168 I64Clz => Some(WasmOp::I64Clz),
1169 I64Ctz => Some(WasmOp::I64Ctz),
1170 I64Popcnt => Some(WasmOp::I64Popcnt),
1171 I64Extend8S => Some(WasmOp::I64Extend8S),
1172 I64Extend16S => Some(WasmOp::I64Extend16S),
1173 I64Extend32S => Some(WasmOp::I64Extend32S),
1174 I64ExtendI32U => Some(WasmOp::I64ExtendI32U),
1180 I64ExtendI32S => Some(WasmOp::I64ExtendI32S),
1181 I32WrapI64 => Some(WasmOp::I32WrapI64),
1182
1183 I64Eqz => Some(WasmOp::I64Eqz),
1185 I64Eq => Some(WasmOp::I64Eq),
1186 I64Ne => Some(WasmOp::I64Ne),
1187 I64LtS => Some(WasmOp::I64LtS),
1188 I64LtU => Some(WasmOp::I64LtU),
1189 I64LeS => Some(WasmOp::I64LeS),
1190 I64LeU => Some(WasmOp::I64LeU),
1191 I64GtS => Some(WasmOp::I64GtS),
1192 I64GtU => Some(WasmOp::I64GtU),
1193 I64GeS => Some(WasmOp::I64GeS),
1194 I64GeU => Some(WasmOp::I64GeU),
1195
1196 I32And => Some(WasmOp::I32And),
1198 I32Or => Some(WasmOp::I32Or),
1199 I32Xor => Some(WasmOp::I32Xor),
1200 I32Shl => Some(WasmOp::I32Shl),
1201 I32ShrS => Some(WasmOp::I32ShrS),
1202 I32ShrU => Some(WasmOp::I32ShrU),
1203 I32Rotl => Some(WasmOp::I32Rotl),
1204 I32Rotr => Some(WasmOp::I32Rotr),
1205 I32Clz => Some(WasmOp::I32Clz),
1206 I32Ctz => Some(WasmOp::I32Ctz),
1207 I32Popcnt => Some(WasmOp::I32Popcnt),
1208 I32Extend8S => Some(WasmOp::I32Extend8S),
1209 I32Extend16S => Some(WasmOp::I32Extend16S),
1210
1211 I32Eqz => Some(WasmOp::I32Eqz),
1213 I32Eq => Some(WasmOp::I32Eq),
1214 I32Ne => Some(WasmOp::I32Ne),
1215 I32LtS => Some(WasmOp::I32LtS),
1216 I32LtU => Some(WasmOp::I32LtU),
1217 I32LeS => Some(WasmOp::I32LeS),
1218 I32LeU => Some(WasmOp::I32LeU),
1219 I32GtS => Some(WasmOp::I32GtS),
1220 I32GtU => Some(WasmOp::I32GtU),
1221 I32GeS => Some(WasmOp::I32GeS),
1222 I32GeU => Some(WasmOp::I32GeU),
1223
1224 I32Load { memarg } => Some(WasmOp::I32Load {
1226 offset: memarg.offset as u32,
1227 align: memarg.align as u32,
1228 }),
1229 I32Store { memarg } => Some(WasmOp::I32Store {
1230 offset: memarg.offset as u32,
1231 align: memarg.align as u32,
1232 }),
1233 I64Load { memarg } => Some(WasmOp::I64Load {
1240 offset: memarg.offset as u32,
1241 align: memarg.align as u32,
1242 }),
1243 I64Store { memarg } => Some(WasmOp::I64Store {
1244 offset: memarg.offset as u32,
1245 align: memarg.align as u32,
1246 }),
1247
1248 I32Load8S { memarg } => Some(WasmOp::I32Load8S {
1250 offset: memarg.offset as u32,
1251 align: memarg.align as u32,
1252 }),
1253 I32Load8U { memarg } => Some(WasmOp::I32Load8U {
1254 offset: memarg.offset as u32,
1255 align: memarg.align as u32,
1256 }),
1257 I32Load16S { memarg } => Some(WasmOp::I32Load16S {
1258 offset: memarg.offset as u32,
1259 align: memarg.align as u32,
1260 }),
1261 I32Load16U { memarg } => Some(WasmOp::I32Load16U {
1262 offset: memarg.offset as u32,
1263 align: memarg.align as u32,
1264 }),
1265
1266 I32Store8 { memarg } => Some(WasmOp::I32Store8 {
1268 offset: memarg.offset as u32,
1269 align: memarg.align as u32,
1270 }),
1271 I32Store16 { memarg } => Some(WasmOp::I32Store16 {
1272 offset: memarg.offset as u32,
1273 align: memarg.align as u32,
1274 }),
1275
1276 LocalGet { local_index } => Some(WasmOp::LocalGet(*local_index)),
1278 LocalSet { local_index } => Some(WasmOp::LocalSet(*local_index)),
1279 LocalTee { local_index } => Some(WasmOp::LocalTee(*local_index)),
1280 GlobalGet { global_index } => Some(WasmOp::GlobalGet(*global_index)),
1281 GlobalSet { global_index } => Some(WasmOp::GlobalSet(*global_index)),
1282
1283 Block { .. } => Some(WasmOp::Block),
1285 Loop { .. } => Some(WasmOp::Loop),
1286 Br { relative_depth } => Some(WasmOp::Br(*relative_depth)),
1287 BrIf { relative_depth } => Some(WasmOp::BrIf(*relative_depth)),
1288 BrTable { targets } => {
1294 let default = targets.default();
1295 let tgts: Vec<u32> = targets.targets().filter_map(Result::ok).collect();
1296 Some(WasmOp::BrTable {
1297 targets: tgts,
1298 default,
1299 })
1300 }
1301 Return => Some(WasmOp::Return),
1302 Call { function_index } => Some(WasmOp::Call(*function_index)),
1303 CallIndirect {
1304 type_index,
1305 table_index,
1306 ..
1307 } => Some(WasmOp::CallIndirect {
1308 type_index: *type_index,
1309 table_index: *table_index,
1310 }),
1311
1312 End => Some(WasmOp::End),
1314
1315 Nop | Unreachable => None,
1317
1318 Drop => Some(WasmOp::Drop),
1320
1321 Select => Some(WasmOp::Select),
1323
1324 If { .. } => Some(WasmOp::If),
1326 Else => Some(WasmOp::Else),
1327
1328 I64Load8S { memarg } => Some(WasmOp::I64Load8S {
1330 offset: memarg.offset as u32,
1331 align: memarg.align as u32,
1332 }),
1333 I64Load8U { memarg } => Some(WasmOp::I64Load8U {
1334 offset: memarg.offset as u32,
1335 align: memarg.align as u32,
1336 }),
1337 I64Load16S { memarg } => Some(WasmOp::I64Load16S {
1338 offset: memarg.offset as u32,
1339 align: memarg.align as u32,
1340 }),
1341 I64Load16U { memarg } => Some(WasmOp::I64Load16U {
1342 offset: memarg.offset as u32,
1343 align: memarg.align as u32,
1344 }),
1345 I64Load32S { memarg } => Some(WasmOp::I64Load32S {
1346 offset: memarg.offset as u32,
1347 align: memarg.align as u32,
1348 }),
1349 I64Load32U { memarg } => Some(WasmOp::I64Load32U {
1350 offset: memarg.offset as u32,
1351 align: memarg.align as u32,
1352 }),
1353
1354 I64Store8 { memarg } => Some(WasmOp::I64Store8 {
1356 offset: memarg.offset as u32,
1357 align: memarg.align as u32,
1358 }),
1359 I64Store16 { memarg } => Some(WasmOp::I64Store16 {
1360 offset: memarg.offset as u32,
1361 align: memarg.align as u32,
1362 }),
1363 I64Store32 { memarg } => Some(WasmOp::I64Store32 {
1364 offset: memarg.offset as u32,
1365 align: memarg.align as u32,
1366 }),
1367
1368 MemorySize { mem, .. } => Some(WasmOp::MemorySize(*mem)),
1370 MemoryGrow { mem, .. } => Some(WasmOp::MemoryGrow(*mem)),
1371
1372 MemoryCopy {
1379 dst_mem: 0,
1380 src_mem: 0,
1381 } => Some(WasmOp::MemoryCopy),
1382 MemoryFill { mem: 0 } => Some(WasmOp::MemoryFill),
1383
1384 V128Const { value } => {
1388 let mut bytes = [0u8; 16];
1389 bytes.copy_from_slice(value.bytes());
1390 Some(WasmOp::V128Const(bytes))
1391 }
1392 V128Load { memarg } => Some(WasmOp::V128Load {
1393 offset: memarg.offset as u32,
1394 align: memarg.align as u32,
1395 }),
1396 V128Store { memarg } => Some(WasmOp::V128Store {
1397 offset: memarg.offset as u32,
1398 align: memarg.align as u32,
1399 }),
1400
1401 V128And => Some(WasmOp::V128And),
1403 V128Or => Some(WasmOp::V128Or),
1404 V128Xor => Some(WasmOp::V128Xor),
1405 V128Not => Some(WasmOp::V128Not),
1406 V128AndNot => Some(WasmOp::V128AndNot),
1407
1408 I8x16Add => Some(WasmOp::I8x16Add),
1410 I8x16Sub => Some(WasmOp::I8x16Sub),
1411 I8x16Neg => Some(WasmOp::I8x16Neg),
1412 I8x16Eq => Some(WasmOp::I8x16Eq),
1413 I8x16Ne => Some(WasmOp::I8x16Ne),
1414 I8x16LtS => Some(WasmOp::I8x16LtS),
1415 I8x16LtU => Some(WasmOp::I8x16LtU),
1416 I8x16GtS => Some(WasmOp::I8x16GtS),
1417 I8x16GtU => Some(WasmOp::I8x16GtU),
1418 I8x16LeS => Some(WasmOp::I8x16LeS),
1419 I8x16LeU => Some(WasmOp::I8x16LeU),
1420 I8x16GeS => Some(WasmOp::I8x16GeS),
1421 I8x16GeU => Some(WasmOp::I8x16GeU),
1422 I8x16Splat => Some(WasmOp::I8x16Splat),
1423 I8x16ExtractLaneS { lane } => Some(WasmOp::I8x16ExtractLaneS(*lane)),
1424 I8x16ExtractLaneU { lane } => Some(WasmOp::I8x16ExtractLaneU(*lane)),
1425 I8x16ReplaceLane { lane } => Some(WasmOp::I8x16ReplaceLane(*lane)),
1426 I8x16Shuffle { lanes } => Some(WasmOp::I8x16Shuffle(*lanes)),
1427 I8x16Swizzle => Some(WasmOp::I8x16Swizzle),
1428
1429 I16x8Add => Some(WasmOp::I16x8Add),
1431 I16x8Sub => Some(WasmOp::I16x8Sub),
1432 I16x8Mul => Some(WasmOp::I16x8Mul),
1433 I16x8Neg => Some(WasmOp::I16x8Neg),
1434 I16x8Eq => Some(WasmOp::I16x8Eq),
1435 I16x8Ne => Some(WasmOp::I16x8Ne),
1436 I16x8LtS => Some(WasmOp::I16x8LtS),
1437 I16x8LtU => Some(WasmOp::I16x8LtU),
1438 I16x8GtS => Some(WasmOp::I16x8GtS),
1439 I16x8GtU => Some(WasmOp::I16x8GtU),
1440 I16x8LeS => Some(WasmOp::I16x8LeS),
1441 I16x8LeU => Some(WasmOp::I16x8LeU),
1442 I16x8GeS => Some(WasmOp::I16x8GeS),
1443 I16x8GeU => Some(WasmOp::I16x8GeU),
1444 I16x8Splat => Some(WasmOp::I16x8Splat),
1445 I16x8ExtractLaneS { lane } => Some(WasmOp::I16x8ExtractLaneS(*lane)),
1446 I16x8ExtractLaneU { lane } => Some(WasmOp::I16x8ExtractLaneU(*lane)),
1447 I16x8ReplaceLane { lane } => Some(WasmOp::I16x8ReplaceLane(*lane)),
1448
1449 I32x4Add => Some(WasmOp::I32x4Add),
1451 I32x4Sub => Some(WasmOp::I32x4Sub),
1452 I32x4Mul => Some(WasmOp::I32x4Mul),
1453 I32x4Neg => Some(WasmOp::I32x4Neg),
1454 I32x4Eq => Some(WasmOp::I32x4Eq),
1455 I32x4Ne => Some(WasmOp::I32x4Ne),
1456 I32x4LtS => Some(WasmOp::I32x4LtS),
1457 I32x4LtU => Some(WasmOp::I32x4LtU),
1458 I32x4GtS => Some(WasmOp::I32x4GtS),
1459 I32x4GtU => Some(WasmOp::I32x4GtU),
1460 I32x4LeS => Some(WasmOp::I32x4LeS),
1461 I32x4LeU => Some(WasmOp::I32x4LeU),
1462 I32x4GeS => Some(WasmOp::I32x4GeS),
1463 I32x4GeU => Some(WasmOp::I32x4GeU),
1464 I32x4Splat => Some(WasmOp::I32x4Splat),
1465 I32x4ExtractLane { lane } => Some(WasmOp::I32x4ExtractLane(*lane)),
1466 I32x4ReplaceLane { lane } => Some(WasmOp::I32x4ReplaceLane(*lane)),
1467
1468 I64x2Add => Some(WasmOp::I64x2Add),
1470 I64x2Sub => Some(WasmOp::I64x2Sub),
1471 I64x2Mul => Some(WasmOp::I64x2Mul),
1472 I64x2Neg => Some(WasmOp::I64x2Neg),
1473 I64x2Eq => Some(WasmOp::I64x2Eq),
1474 I64x2Ne => Some(WasmOp::I64x2Ne),
1475 I64x2LtS => Some(WasmOp::I64x2LtS),
1476 I64x2GtS => Some(WasmOp::I64x2GtS),
1477 I64x2LeS => Some(WasmOp::I64x2LeS),
1478 I64x2GeS => Some(WasmOp::I64x2GeS),
1479 I64x2Splat => Some(WasmOp::I64x2Splat),
1480 I64x2ExtractLane { lane } => Some(WasmOp::I64x2ExtractLane(*lane)),
1481 I64x2ReplaceLane { lane } => Some(WasmOp::I64x2ReplaceLane(*lane)),
1482
1483 F32x4Add => Some(WasmOp::F32x4Add),
1485 F32x4Sub => Some(WasmOp::F32x4Sub),
1486 F32x4Mul => Some(WasmOp::F32x4Mul),
1487 F32x4Div => Some(WasmOp::F32x4Div),
1488 F32x4Abs => Some(WasmOp::F32x4Abs),
1489 F32x4Neg => Some(WasmOp::F32x4Neg),
1490 F32x4Sqrt => Some(WasmOp::F32x4Sqrt),
1491 F32x4Eq => Some(WasmOp::F32x4Eq),
1492 F32x4Ne => Some(WasmOp::F32x4Ne),
1493 F32x4Lt => Some(WasmOp::F32x4Lt),
1494 F32x4Le => Some(WasmOp::F32x4Le),
1495 F32x4Gt => Some(WasmOp::F32x4Gt),
1496 F32x4Ge => Some(WasmOp::F32x4Ge),
1497 F32x4Splat => Some(WasmOp::F32x4Splat),
1498 F32x4ExtractLane { lane } => Some(WasmOp::F32x4ExtractLane(*lane)),
1499 F32x4ReplaceLane { lane } => Some(WasmOp::F32x4ReplaceLane(*lane)),
1500
1501 _ => None,
1503 }
1504}
1505
1506#[cfg(test)]
1507mod tests {
1508 use super::*;
1509
1510 #[test]
1511 fn test_decode_simple_add() {
1512 let wat = r#"
1513 (module
1514 (func (export "add") (param i32 i32) (result i32)
1515 local.get 0
1516 local.get 1
1517 i32.add
1518 )
1519 )
1520 "#;
1521
1522 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1523 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1524
1525 assert_eq!(functions.len(), 1);
1526 assert_eq!(functions[0].index, 0);
1527 assert_eq!(functions[0].export_name, Some("add".to_string()));
1528 assert_eq!(
1529 functions[0].ops,
1530 vec![
1531 WasmOp::LocalGet(0),
1532 WasmOp::LocalGet(1),
1533 WasmOp::I32Add,
1534 WasmOp::End
1535 ]
1536 );
1537 }
1538
1539 #[test]
1545 fn test_decode_i64_i32_width_conversions() {
1546 let wat = r#"
1547 (module
1548 (func (export "conv") (param i32 i64) (result i32)
1549 local.get 0
1550 i64.extend_i32_u
1551 local.get 0
1552 i64.extend_i32_s
1553 i64.add
1554 local.get 1
1555 i64.add
1556 i32.wrap_i64
1557 )
1558 )
1559 "#;
1560 let wasm = wat::parse_str(wat).expect("parse");
1561 let functions = decode_wasm_functions(&wasm).expect("decode");
1562 let ops = &functions[0].ops;
1563 assert!(
1564 ops.contains(&WasmOp::I64ExtendI32U),
1565 "i64.extend_i32_u must decode (not be dropped): {ops:?}"
1566 );
1567 assert!(
1568 ops.contains(&WasmOp::I64ExtendI32S),
1569 "i64.extend_i32_s must decode (not be dropped): {ops:?}"
1570 );
1571 assert!(
1572 ops.contains(&WasmOp::I32WrapI64),
1573 "i32.wrap_i64 must decode (not be dropped): {ops:?}"
1574 );
1575 }
1576
1577 #[test]
1582 fn test_decode_br_table() {
1583 let wat = r#"
1584 (module
1585 (func (export "bt") (param i32) (result i32)
1586 (block (block (block
1587 local.get 0
1588 br_table 2 0 1 2)
1589 i32.const 30 return)
1590 i32.const 20 return)
1591 i32.const 10))
1592 "#;
1593 let wasm = wat::parse_str(wat).expect("parse");
1594 let functions = decode_wasm_functions(&wasm).expect("decode");
1595 let bt = functions[0]
1596 .ops
1597 .iter()
1598 .find_map(|o| match o {
1599 WasmOp::BrTable { targets, default } => Some((targets.clone(), *default)),
1600 _ => None,
1601 })
1602 .expect("br_table must decode (not be dropped)");
1603 assert_eq!(bt.0, vec![2, 0, 1], "br_table targets preserved in order");
1604 assert_eq!(bt.1, 2, "br_table default preserved");
1605 }
1606
1607 #[test]
1608 fn test_decode_arithmetic() {
1609 let wat = r#"
1610 (module
1611 (func (export "calc") (result i32)
1612 i32.const 5
1613 i32.const 3
1614 i32.mul
1615 i32.const 2
1616 i32.add
1617 )
1618 )
1619 "#;
1620
1621 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1622 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1623
1624 assert_eq!(functions.len(), 1);
1625 assert_eq!(functions[0].export_name, Some("calc".to_string()));
1626 assert_eq!(
1627 functions[0].ops,
1628 vec![
1629 WasmOp::I32Const(5),
1630 WasmOp::I32Const(3),
1631 WasmOp::I32Mul,
1632 WasmOp::I32Const(2),
1633 WasmOp::I32Add,
1634 WasmOp::End,
1635 ]
1636 );
1637 }
1638
1639 #[test]
1640 fn test_decode_multi_function_module() {
1641 let wat = r#"
1642 (module
1643 (func $helper)
1644 (func (export "add") (param i32 i32) (result i32)
1645 local.get 0
1646 local.get 1
1647 i32.add
1648 )
1649 (func (export "sub") (param i32 i32) (result i32)
1650 local.get 0
1651 local.get 1
1652 i32.sub
1653 )
1654 )
1655 "#;
1656
1657 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1658 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1659
1660 assert_eq!(functions.len(), 3);
1661 assert_eq!(functions[0].index, 0);
1662 assert_eq!(functions[0].export_name, None);
1663 assert_eq!(functions[1].index, 1);
1664 assert_eq!(functions[1].export_name, Some("add".to_string()));
1665 assert_eq!(functions[2].index, 2);
1666 assert_eq!(functions[2].export_name, Some("sub".to_string()));
1667 }
1668
1669 #[test]
1670 fn test_decode_module_with_imports() {
1671 let wat = r#"
1672 (module
1673 (import "env" "log" (func $log (param i32)))
1674 (import "env" "memory" (memory 1))
1675 (func (export "run") (param i32)
1676 local.get 0
1677 call 0
1678 )
1679 )
1680 "#;
1681
1682 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1683 let module = decode_wasm_module(&wasm).expect("Failed to decode");
1684
1685 assert_eq!(module.imports.len(), 2);
1687 assert_eq!(module.num_imported_funcs, 1);
1688
1689 assert_eq!(module.imports[0].module, "env");
1691 assert_eq!(module.imports[0].name, "log");
1692 assert!(matches!(module.imports[0].kind, ImportKind::Function(_)));
1693
1694 assert_eq!(module.imports[1].module, "env");
1696 assert_eq!(module.imports[1].name, "memory");
1697 assert_eq!(module.imports[1].kind, ImportKind::Memory);
1698
1699 assert_eq!(module.functions.len(), 1);
1701 assert_eq!(module.functions[0].index, 1);
1702 assert_eq!(module.functions[0].export_name, Some("run".to_string()));
1703 }
1704
1705 #[test]
1706 fn test_find_function_by_export_name() {
1707 let wat = r#"
1708 (module
1709 (func $helper)
1710 (func (export "add") (param i32 i32) (result i32)
1711 local.get 0
1712 local.get 1
1713 i32.add
1714 )
1715 )
1716 "#;
1717
1718 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1719 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1720
1721 let add_func = functions
1722 .iter()
1723 .find(|f| f.export_name.as_deref() == Some("add"))
1724 .expect("Should find 'add' function");
1725
1726 assert_eq!(add_func.index, 1);
1727 assert!(add_func.ops.contains(&WasmOp::I32Add));
1728 }
1729
1730 #[test]
1731 fn test_decode_subword_loads() {
1732 let wat = r#"
1733 (module
1734 (memory 1)
1735 (func (export "test") (param i32) (result i32)
1736 local.get 0
1737 i32.load8_u
1738 )
1739 )
1740 "#;
1741
1742 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1743 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1744
1745 assert_eq!(functions.len(), 1);
1746 assert!(functions[0].ops.contains(&WasmOp::I32Load8U {
1747 offset: 0,
1748 align: 0,
1749 }));
1750 }
1751
1752 #[test]
1753 fn test_decode_subword_stores() {
1754 let wat = r#"
1755 (module
1756 (memory 1)
1757 (func (export "test") (param i32 i32)
1758 local.get 0
1759 local.get 1
1760 i32.store8
1761 )
1762 )
1763 "#;
1764
1765 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1766 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1767
1768 assert_eq!(functions.len(), 1);
1769 assert!(functions[0].ops.contains(&WasmOp::I32Store8 {
1770 offset: 0,
1771 align: 0,
1772 }));
1773 }
1774
1775 #[test]
1776 fn test_decode_memory_size_grow() {
1777 let wat = r#"
1778 (module
1779 (memory 1)
1780 (func (export "test") (result i32)
1781 memory.size
1782 )
1783 )
1784 "#;
1785
1786 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1787 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1788
1789 assert_eq!(functions.len(), 1);
1790 assert!(functions[0].ops.contains(&WasmOp::MemorySize(0)));
1791 }
1792
1793 #[test]
1794 fn test_decode_memory_grow() {
1795 let wat = r#"
1796 (module
1797 (memory 1)
1798 (func (export "test") (param i32) (result i32)
1799 local.get 0
1800 memory.grow
1801 )
1802 )
1803 "#;
1804
1805 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1806 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1807
1808 assert_eq!(functions.len(), 1);
1809 assert!(functions[0].ops.contains(&WasmOp::MemoryGrow(0)));
1810 }
1811
1812 #[test]
1813 fn test_decode_bulk_memory_374() {
1814 let wat = r#"
1817 (module
1818 (memory 1)
1819 (func (export "cpy") (param i32 i32 i32)
1820 local.get 0 local.get 1 local.get 2 memory.copy)
1821 (func (export "fil") (param i32 i32 i32)
1822 local.get 0 local.get 1 local.get 2 memory.fill)
1823 )
1824 "#;
1825 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1826 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1827 assert_eq!(functions.len(), 2);
1828 assert!(functions[0].ops.contains(&WasmOp::MemoryCopy));
1829 assert!(functions[1].ops.contains(&WasmOp::MemoryFill));
1830 assert!(functions[0].unsupported.is_none());
1832 assert!(functions[1].unsupported.is_none());
1833 }
1834
1835 #[test]
1836 fn test_decode_i64_subword_loads() {
1837 let wat = r#"
1838 (module
1839 (memory 1)
1840 (func (export "test") (param i32) (result i64)
1841 local.get 0
1842 i64.load8_s
1843 )
1844 )
1845 "#;
1846
1847 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1848 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1849
1850 assert_eq!(functions.len(), 1);
1851 assert!(functions[0].ops.contains(&WasmOp::I64Load8S {
1852 offset: 0,
1853 align: 0,
1854 }));
1855 }
1856
1857 #[test]
1858 fn test_decode_all_subword_memory_ops() {
1859 let wat = r#"
1861 (module
1862 (memory 1)
1863 (func (export "test") (param i32)
1864 ;; i32 sub-word loads
1865 local.get 0
1866 i32.load8_s
1867 drop
1868 local.get 0
1869 i32.load8_u
1870 drop
1871 local.get 0
1872 i32.load16_s
1873 drop
1874 local.get 0
1875 i32.load16_u
1876 drop
1877
1878 ;; i32 sub-word stores
1879 local.get 0
1880 i32.const 42
1881 i32.store8
1882 local.get 0
1883 i32.const 42
1884 i32.store16
1885
1886 ;; i64 sub-word loads
1887 local.get 0
1888 i64.load8_s
1889 drop
1890 local.get 0
1891 i64.load8_u
1892 drop
1893 local.get 0
1894 i64.load16_s
1895 drop
1896 local.get 0
1897 i64.load16_u
1898 drop
1899 local.get 0
1900 i64.load32_s
1901 drop
1902 local.get 0
1903 i64.load32_u
1904 drop
1905
1906 ;; i64 sub-word stores
1907 local.get 0
1908 i64.const 42
1909 i64.store8
1910 local.get 0
1911 i64.const 42
1912 i64.store16
1913 local.get 0
1914 i64.const 42
1915 i64.store32
1916 )
1917 )
1918 "#;
1919
1920 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1921 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1922
1923 assert_eq!(functions.len(), 1);
1924 let ops = &functions[0].ops;
1925
1926 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8S { .. })));
1928 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8U { .. })));
1929 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16S { .. })));
1930 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16U { .. })));
1931 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store8 { .. })));
1932 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store16 { .. })));
1933
1934 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8S { .. })));
1936 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8U { .. })));
1937 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16S { .. })));
1938 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16U { .. })));
1939 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32S { .. })));
1940 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32U { .. })));
1941 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store8 { .. })));
1942 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store16 { .. })));
1943 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store32 { .. })));
1944 }
1945
1946 #[test]
1947 fn test_decode_simd_i32x4_add() {
1948 let wat = r#"
1949 (module
1950 (func (export "add_v128") (param v128 v128) (result v128)
1951 local.get 0
1952 local.get 1
1953 i32x4.add
1954 )
1955 )
1956 "#;
1957
1958 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1959 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1960
1961 assert_eq!(functions.len(), 1);
1962 assert!(
1963 functions[0].ops.contains(&WasmOp::I32x4Add),
1964 "Should decode i32x4.add: {:?}",
1965 functions[0].ops
1966 );
1967 }
1968
1969 #[test]
1970 fn test_decode_simd_v128_const() {
1971 let wat = r#"
1972 (module
1973 (func (export "const_v128") (result v128)
1974 v128.const i32x4 1 2 3 4
1975 )
1976 )
1977 "#;
1978
1979 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1980 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1981
1982 assert_eq!(functions.len(), 1);
1983 assert!(
1984 functions[0]
1985 .ops
1986 .iter()
1987 .any(|o| matches!(o, WasmOp::V128Const(_))),
1988 "Should decode v128.const: {:?}",
1989 functions[0].ops
1990 );
1991 }
1992
1993 #[test]
1994 fn test_decode_simd_v128_load_store() {
1995 let wat = r#"
1996 (module
1997 (memory 1)
1998 (func (export "load_store") (param i32)
1999 local.get 0
2000 v128.load
2001 local.get 0
2002 v128.store
2003 )
2004 )
2005 "#;
2006
2007 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2008 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2009
2010 assert_eq!(functions.len(), 1);
2011 let ops = &functions[0].ops;
2012 assert!(
2013 ops.iter().any(|o| matches!(o, WasmOp::V128Load { .. })),
2014 "Should decode v128.load"
2015 );
2016 assert!(
2017 ops.iter().any(|o| matches!(o, WasmOp::V128Store { .. })),
2018 "Should decode v128.store"
2019 );
2020 }
2021
2022 #[test]
2023 fn test_decode_simd_bitwise_ops() {
2024 let wat = r#"
2025 (module
2026 (func (export "bitwise") (param v128 v128) (result v128)
2027 local.get 0
2028 local.get 1
2029 v128.and
2030 )
2031 )
2032 "#;
2033
2034 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2035 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2036
2037 assert_eq!(functions.len(), 1);
2038 assert!(functions[0].ops.contains(&WasmOp::V128And));
2039 }
2040
2041 #[test]
2042 fn test_decode_simd_splat() {
2043 let wat = r#"
2044 (module
2045 (func (export "splat") (param i32) (result v128)
2046 local.get 0
2047 i32x4.splat
2048 )
2049 )
2050 "#;
2051
2052 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2053 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2054
2055 assert_eq!(functions.len(), 1);
2056 assert!(functions[0].ops.contains(&WasmOp::I32x4Splat));
2057 }
2058
2059 #[test]
2060 fn test_decode_simd_extract_lane() {
2061 let wat = r#"
2062 (module
2063 (func (export "extract") (param v128) (result i32)
2064 local.get 0
2065 i32x4.extract_lane 2
2066 )
2067 )
2068 "#;
2069
2070 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2071 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2072
2073 assert_eq!(functions.len(), 1);
2074 assert!(
2075 functions[0].ops.contains(&WasmOp::I32x4ExtractLane(2)),
2076 "Should decode i32x4.extract_lane 2"
2077 );
2078 }
2079
2080 #[test]
2081 fn test_decode_simd_f32x4_arithmetic() {
2082 let wat = r#"
2083 (module
2084 (func (export "f32x4_add") (param v128 v128) (result v128)
2085 local.get 0
2086 local.get 1
2087 f32x4.add
2088 )
2089 )
2090 "#;
2091
2092 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2093 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2094
2095 assert_eq!(functions.len(), 1);
2096 assert!(functions[0].ops.contains(&WasmOp::F32x4Add));
2097 }
2098
2099 #[test]
2100 fn test_369_scalar_float_op_flags_function_unsupported_not_dropped() {
2101 let wat = r#"
2105 (module
2106 (func (export "fadd") (param f32 f32) (result f32)
2107 local.get 0 local.get 1 f32.add)
2108 (func (export "iadd") (param i32 i32) (result i32)
2109 local.get 0 local.get 1 i32.add))
2110 "#;
2111 let wasm = wat::parse_str(wat).expect("parse");
2112 let functions = decode_wasm_functions(&wasm).expect("decode");
2113 let fadd = functions
2114 .iter()
2115 .find(|f| f.export_name.as_deref() == Some("fadd"))
2116 .unwrap();
2117 let iadd = functions
2118 .iter()
2119 .find(|f| f.export_name.as_deref() == Some("iadd"))
2120 .unwrap();
2121 assert!(
2122 fadd.unsupported.is_some(),
2123 "f32.add must flag the function unsupported (loud-skip), got {:?}",
2124 fadd.unsupported
2125 );
2126 assert!(
2127 fadd.unsupported.as_deref().unwrap().contains("F32Add"),
2128 "diagnostic should name the op: {:?}",
2129 fadd.unsupported
2130 );
2131 assert!(
2132 iadd.unsupported.is_none(),
2133 "a pure-integer function must NOT be flagged: {:?}",
2134 iadd.unsupported
2135 );
2136 }
2137
2138 #[test]
2139 fn test_369_float_global_access_flags_function_unsupported() {
2140 let wat = r#"
2148 (module
2149 (global $fg f32 (f32.const 2.5))
2150 (global $dg (mut f64) (f64.const 1.5))
2151 (global $ig (mut i32) (i32.const 7))
2152 (func (export "fget") (result f32) global.get $fg)
2153 (func (export "dset") (param f64) local.get 0 global.set $dg)
2154 (func (export "iget") (result i32) global.get $ig))
2155 "#;
2156 let wasm = wat::parse_str(wat).expect("parse");
2157
2158 let module = decode_wasm_module(&wasm).expect("decode module");
2162 for functions in [
2163 &module.functions,
2164 &decode_wasm_functions(&wasm).expect("decode fns"),
2165 ] {
2166 let by_name = |n: &str| {
2167 functions
2168 .iter()
2169 .find(|f| f.export_name.as_deref() == Some(n))
2170 .unwrap()
2171 };
2172 let fget = by_name("fget");
2173 assert!(
2174 fget.unsupported.is_some(),
2175 "global.get of an f32 global must flag the function (loud-skip), got {:?}",
2176 fget.unsupported
2177 );
2178 let reason = fget.unsupported.as_deref().unwrap();
2179 assert!(
2180 reason.contains("GlobalGet") && reason.contains("GI-FPU-001"),
2181 "diagnostic should name the op and GI-FPU-001: {reason:?}"
2182 );
2183 let dset = by_name("dset");
2184 assert!(
2185 dset.unsupported
2186 .as_deref()
2187 .is_some_and(|r| r.contains("GlobalSet")),
2188 "global.set of an f64 global must flag the function, got {:?}",
2189 dset.unsupported
2190 );
2191 assert!(
2192 by_name("iget").unsupported.is_none(),
2193 "an i32 global access must NOT be flagged: {:?}",
2194 by_name("iget").unsupported
2195 );
2196 }
2197 }
2198
2199 #[test]
2200 fn test_369_imported_float_global_shifts_index_space() {
2201 let wat = r#"
2205 (module
2206 (import "env" "fg" (global f64))
2207 (global $ig i32 (i32.const 3))
2208 (func (export "fget") (result f64) global.get 0)
2209 (func (export "iget") (result i32) global.get 1))
2210 "#;
2211 let wasm = wat::parse_str(wat).expect("parse");
2212 let functions = decode_wasm_functions(&wasm).expect("decode");
2213 let by_name = |n: &str| {
2214 functions
2215 .iter()
2216 .find(|f| f.export_name.as_deref() == Some(n))
2217 .unwrap()
2218 };
2219 assert!(
2220 by_name("fget")
2221 .unsupported
2222 .as_deref()
2223 .is_some_and(|r| r.contains("GI-FPU-001")),
2224 "imported f64 global access must flag: {:?}",
2225 by_name("fget").unsupported
2226 );
2227 assert!(
2228 by_name("iget").unsupported.is_none(),
2229 "defined i32 global at shifted index 1 must NOT flag: {:?}",
2230 by_name("iget").unsupported
2231 );
2232 }
2233
2234 #[test]
2235 fn test_decode_simd_multiple_ops() {
2236 let wat = r#"
2237 (module
2238 (func (export "simd_ops") (param v128 v128 v128) (result v128)
2239 ;; (a + b) * c
2240 local.get 0
2241 local.get 1
2242 i32x4.add
2243 local.get 2
2244 i32x4.mul
2245 )
2246 )
2247 "#;
2248
2249 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2250 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2251
2252 assert_eq!(functions.len(), 1);
2253 let ops = &functions[0].ops;
2254 assert!(ops.contains(&WasmOp::I32x4Add));
2255 assert!(ops.contains(&WasmOp::I32x4Mul));
2256 }
2257
2258 #[test]
2264 fn test_decode_records_aligned_increasing_op_offsets_dbg001() {
2265 let wat = r#"
2266 (module
2267 (func (export "f") (param i32 i32) (result i32)
2268 local.get 0
2269 local.get 1
2270 i32.add
2271 i32.const 7
2272 i32.mul))
2273 "#;
2274 let wasm = wat::parse_str(wat).expect("parse WAT");
2275 let functions = decode_wasm_functions(&wasm).expect("decode");
2276 let f = &functions[0];
2277
2278 assert_eq!(
2280 f.op_offsets.len(),
2281 f.ops.len(),
2282 "op_offsets must be parallel to ops"
2283 );
2284 assert!(!f.op_offsets.is_empty());
2285
2286 assert!(
2289 f.op_offsets.windows(2).all(|w| w[1] > w[0]),
2290 "wasm byte offsets must strictly increase: {:?}",
2291 f.op_offsets
2292 );
2293 assert!(
2294 f.op_offsets[0] >= 8,
2295 "module-relative offset is past the 8-byte wasm header"
2296 );
2297 }
2298
2299 #[test]
2302 fn test_decode_captures_global_initializer() {
2303 let wat = r#"
2304 (module
2305 (memory 2)
2306 (global $__stack_pointer (mut i32) (i32.const 65536))
2307 (global $immutable_const i32 (i32.const 7))
2308 (func (export "f") (result i32) global.get 0)
2309 )
2310 "#;
2311 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2312 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2313
2314 assert_eq!(module.globals.len(), 2, "both globals captured");
2315 let sp = &module.globals[0];
2316 assert_eq!(sp.index, 0);
2317 assert_eq!(
2318 sp.init,
2319 Some(GlobalInit::I32(65536)),
2320 "stack-pointer init captured"
2321 );
2322 assert!(sp.mutable, "stack pointer is mutable");
2323 let c = &module.globals[1];
2324 assert_eq!(c.init, Some(GlobalInit::I32(7)));
2325 assert!(!c.mutable, "second global is immutable");
2326 assert_eq!(sp.slot_bytes, 4, "i32 global occupies one 4-byte slot");
2327 assert_eq!(c.slot_bytes, 4);
2328 }
2329
2330 #[test]
2334 fn test_decode_records_global_slot_widths_643() {
2335 let wat = r#"
2336 (module
2337 (global $c (mut i64) (i64.const 0))
2338 (global $k (mut i32) (i32.const 0))
2339 (global $f (mut f64) (f64.const 0))
2340 (func (export "f") (result i32) global.get 1)
2341 )
2342 "#;
2343 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2344 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2345
2346 assert_eq!(module.globals.len(), 3);
2347 assert_eq!(module.globals[0].slot_bytes, 8, "i64 global is 8 bytes");
2348 assert_eq!(module.globals[1].slot_bytes, 4, "i32 global is 4 bytes");
2349 assert_eq!(module.globals[2].slot_bytes, 8, "f64 global is 8 bytes");
2350 }
2351
2352 #[test]
2357 fn test_decode_captures_i64_global_initializer_649() {
2358 let wat = r#"
2359 (module
2360 (global $g (mut i64) (i64.const 0x123456789ABCDEF0))
2361 (global $n (mut i64) (i64.const -1))
2362 (global $f (mut f64) (f64.const 1.5))
2363 (global $h (mut f32) (f32.const 2.5))
2364 (func (export "f") (result i32) i32.const 0)
2365 )
2366 "#;
2367 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2368 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2369
2370 assert_eq!(module.globals.len(), 4);
2371 assert_eq!(
2372 module.globals[0].init,
2373 Some(GlobalInit::I64(0x123456789ABCDEF0u64 as i64)),
2374 "nonzero i64 init captured with both words"
2375 );
2376 assert_eq!(module.globals[1].init, Some(GlobalInit::I64(-1)));
2377 assert_eq!(
2378 module.globals[2].init, None,
2379 "f64 init is NOT captured (GI-FPU-001 loud-skip lane)"
2380 );
2381 assert_eq!(
2382 module.globals[3].init, None,
2383 "f32 init is NOT captured (GI-FPU-001 loud-skip lane)"
2384 );
2385 }
2386
2387 #[test]
2393 fn test_decode_records_block_arity_side_table_509() {
2394 let wat = r#"
2395 (module
2396 (func (export "f") (param i32) (result i32)
2397 (block (result i32)
2398 (block (nop))
2399 (local.get 0)
2400 (if (result i32)
2401 (then (i32.const 1))
2402 (else (i32.const 2)))))
2403 (func (export "g") (result i32)
2404 (block (result i32 i32)
2405 (i32.const 1) (i32.const 2))
2406 i32.add)
2407 (func (export "h") (param i32) (result i32)
2408 (local.get 0)
2409 (loop (param i32) (result i32))))
2410 "#;
2411 let wasm = wat::parse_str(wat).expect("parse WAT");
2412
2413 for functions in [
2415 decode_wasm_functions(&wasm).expect("decode"),
2416 decode_wasm_module(&wasm).expect("decode").functions,
2417 ] {
2418 assert_eq!(
2420 functions[0].block_arity,
2421 vec![(0, 1), (0, 0), (0, 1)],
2422 "f: ValType/Empty/ValType blocktypes"
2423 );
2424 assert_eq!(
2426 functions[1].block_arity,
2427 vec![(0, 2)],
2428 "g: functype blocktype result count from the type section"
2429 );
2430 assert_eq!(
2433 functions[2].block_arity,
2434 vec![(1, 1)],
2435 "h: loop params captured"
2436 );
2437 }
2438 }
2439
2440 #[test]
2444 fn test_call_indirect_guards_closed_world_verified_642() {
2445 let wat = r#"
2447 (module
2448 (type $bin (func (param i32 i32) (result i32)))
2449 (table 3 funcref)
2450 (elem (i32.const 0) $add $sub $mul)
2451 (func $add (param i32 i32) (result i32)
2452 (i32.add (local.get 0) (local.get 1)))
2453 (func $sub (param i32 i32) (result i32)
2454 (i32.sub (local.get 0) (local.get 1)))
2455 (func $mul (param i32 i32) (result i32)
2456 (i32.mul (local.get 0) (local.get 1)))
2457 (func (export "f") (param i32 i32) (result i32)
2458 (call_indirect (type $bin)
2459 (local.get 0) (i32.const 10) (local.get 1)))
2460 )
2461 "#;
2462 let wasm = wat::parse_str(wat).expect("parse");
2463 let module = decode_wasm_module(&wasm).expect("decode");
2464
2465 assert_eq!(module.table_size, Some(3), "table section min size");
2466 assert_eq!(module.table_sizes, vec![Some(3)], "#650 per-table sizes");
2467 assert_eq!(
2468 module.elem_segments,
2469 vec![ElemSegmentInfo {
2470 table_index: 0,
2471 offset: Some(0),
2472 funcs: Some(vec![0, 1, 2]),
2473 }]
2474 );
2475 assert_eq!(module.func_type_indices.len(), 4);
2479
2480 let guards = module.call_indirect_guards();
2481 assert_eq!(guards.tables.len(), 1);
2482 assert_eq!(guards.tables[0].table_size, Some(3));
2483 assert_eq!(
2484 guards.tables[0].base_byte_offset,
2485 Some(0),
2486 "#650: a single-table module keeps table 0 at R11 offset 0 by construction"
2487 );
2488 assert_eq!(
2491 guards.tables[0].type_reject.first(),
2492 Some(&None),
2493 "closed-world type check must verify the homogeneous table: {:?}",
2494 guards.tables[0].type_reject
2495 );
2496 }
2497
2498 #[test]
2502 fn test_call_indirect_guards_heterogeneous_table_rejects_642() {
2503 let wat = r#"
2504 (module
2505 (type $bin (func (param i32 i32) (result i32)))
2506 (type $un (func (param i32) (result i32)))
2507 (table 2 funcref)
2508 (elem (i32.const 0) $add $neg)
2509 (func $add (type $bin)
2510 (i32.add (local.get 0) (local.get 1)))
2511 (func $neg (type $un)
2512 (i32.sub (i32.const 0) (local.get 0)))
2513 (func (export "f") (param i32 i32) (result i32)
2514 (call_indirect (type $bin)
2515 (local.get 0) (i32.const 10) (local.get 1)))
2516 )
2517 "#;
2518 let wasm = wat::parse_str(wat).expect("parse");
2519 let module = decode_wasm_module(&wasm).expect("decode");
2520 let guards = module.call_indirect_guards();
2521 assert_eq!(guards.tables[0].table_size, Some(2));
2522 assert!(
2525 guards.tables[0].type_reject[0].is_some() && guards.tables[0].type_reject[1].is_some(),
2526 "heterogeneous table must reject every expected type: {:?}",
2527 guards.tables[0].type_reject
2528 );
2529 }
2530
2531 #[test]
2535 fn test_call_indirect_guards_null_slot_rejects_642() {
2536 let wat = r#"
2537 (module
2538 (type $s (func (result i32)))
2539 (table 3 funcref)
2540 (elem (i32.const 0) $f0 $f1)
2541 (func $f0 (result i32) (i32.const 10))
2542 (func $f1 (result i32) (i32.const 11))
2543 (func (export "run") (param i32) (result i32)
2544 (call_indirect (type $s) (local.get 0)))
2545 )
2546 "#;
2547 let wasm = wat::parse_str(wat).expect("parse");
2548 let module = decode_wasm_module(&wasm).expect("decode");
2549 let guards = module.call_indirect_guards();
2550 assert_eq!(guards.tables[0].table_size, Some(3));
2551 assert!(
2552 guards.tables[0].type_reject.iter().all(|r| r.is_some()),
2553 "slot 2 is a null funcref — every type must be rejected: {:?}",
2554 guards.tables[0].type_reject
2555 );
2556 assert!(
2557 guards.tables[0].type_reject[0]
2558 .as_deref()
2559 .unwrap()
2560 .contains("slot 2"),
2561 "reason names the uninitialized slot: {:?}",
2562 guards.tables[0].type_reject[0]
2563 );
2564 }
2565
2566 #[test]
2569 fn test_call_indirect_guards_no_table_642() {
2570 let wat = r#"
2571 (module
2572 (func (export "f") (param i32) (result i32) (local.get 0))
2573 )
2574 "#;
2575 let wasm = wat::parse_str(wat).expect("parse");
2576 let module = decode_wasm_module(&wasm).expect("decode");
2577 assert_eq!(module.table_size, None);
2578 assert!(module.table_sizes.is_empty(), "#650: no tables declared");
2579 let guards = module.call_indirect_guards();
2580 assert!(
2581 guards.tables.is_empty(),
2582 "no table → no guard entry → every call_indirect declines"
2583 );
2584 }
2585
2586 #[test]
2589 fn test_call_indirect_guards_duplicate_types_verified_642() {
2590 let wat = r#"
2591 (module
2592 (type $a (func (result i32)))
2593 (type $b (func (result i32)))
2594 (table 1 funcref)
2595 (elem (i32.const 0) $f)
2596 (func $f (type $a) (i32.const 7))
2597 (func (export "run") (param i32) (result i32)
2598 (call_indirect (type $b) (local.get 0)))
2599 )
2600 "#;
2601 let wasm = wat::parse_str(wat).expect("parse");
2602 let module = decode_wasm_module(&wasm).expect("decode");
2603 let guards = module.call_indirect_guards();
2604 assert_eq!(
2608 &guards.tables[0].type_reject[0..2],
2609 &[None, None],
2610 "structural signature comparison must accept duplicate types: {:?}",
2611 guards.tables[0].type_reject
2612 );
2613 assert!(
2614 guards.tables[0].type_reject[2].is_some(),
2615 "the structurally-different third type must still be rejected"
2616 );
2617 }
2618
2619 #[test]
2625 fn test_call_indirect_guards_multi_table_650() {
2626 let wat = r#"
2629 (module
2630 (type $t (func (param i32) (result i32)))
2631 (type $u (func (param i32 i32) (result i32)))
2632 (table $t0 3 3 funcref)
2633 (table $t1 2 2 funcref)
2634 (func $a0 (type $t) (i32.add (local.get 0) (i32.const 100)))
2635 (func $a1 (type $t) (i32.add (local.get 0) (i32.const 200)))
2636 (func $a2 (type $t) (i32.add (local.get 0) (i32.const 300)))
2637 (func $b0 (type $u) (i32.add (local.get 0) (local.get 1)))
2638 (func $b1 (type $u) (i32.sub (local.get 0) (local.get 1)))
2639 (elem (table $t0) (i32.const 0) func $a0 $a1 $a2)
2640 (elem (table $t1) (i32.const 0) func $b0 $b1)
2641 (func (export "f") (param i32 i32) (result i32)
2642 (call_indirect $t1 (type $u)
2643 (local.get 0) (i32.const 10) (local.get 1)))
2644 )
2645 "#;
2646 let wasm = wat::parse_str(wat).expect("parse");
2647 let module = decode_wasm_module(&wasm).expect("decode");
2648 assert_eq!(module.table_sizes, vec![Some(3), Some(2)]);
2649 assert_eq!(module.table_size, Some(3), "compat accessor = table 0");
2650 assert_eq!(
2651 module.elem_segments[0].table_index, 0,
2652 "segment 0 targets table 0"
2653 );
2654 assert_eq!(
2655 module.elem_segments[1],
2656 ElemSegmentInfo {
2657 table_index: 1,
2658 offset: Some(0),
2659 funcs: Some(vec![3, 4]),
2660 },
2661 "segment 1 is statically attributed to table 1 (#650)"
2662 );
2663
2664 let guards = module.call_indirect_guards();
2665 assert_eq!(guards.tables.len(), 2);
2666 assert_eq!(guards.tables[0].table_size, Some(3));
2667 assert_eq!(guards.tables[0].base_byte_offset, Some(0));
2668 assert_eq!(guards.tables[1].table_size, Some(2));
2669 assert_eq!(
2670 guards.tables[1].base_byte_offset,
2671 Some(12),
2672 "table 1 base = size(table 0) * 4 within the contiguous R11 region"
2673 );
2674 assert_eq!(guards.tables[0].type_reject[0], None, "table 0 vs $t");
2677 assert!(guards.tables[0].type_reject[1].is_some(), "table 0 vs $u");
2678 assert!(guards.tables[1].type_reject[0].is_some(), "table 1 vs $t");
2679 assert_eq!(guards.tables[1].type_reject[1], None, "table 1 vs $u");
2680 }
2681
2682 #[test]
2686 fn test_call_indirect_guards_unknown_size_poisons_later_bases_650() {
2687 let wat = r#"
2688 (module
2689 (type $t (func (result i32)))
2690 (import "env" "tbl" (table 4 funcref))
2691 (table $d 1 1 funcref)
2692 (func $f (type $t) (i32.const 7))
2693 (elem (table $d) (i32.const 0) func $f)
2694 (func (export "run") (param i32) (result i32)
2695 (call_indirect $d (type $t) (local.get 0)))
2696 )
2697 "#;
2698 let wasm = wat::parse_str(wat).expect("parse");
2699 let module = decode_wasm_module(&wasm).expect("decode");
2700 assert_eq!(
2701 module.table_sizes,
2702 vec![None, Some(1)],
2703 "growable import (no max) has no sound compile-time size"
2704 );
2705 let guards = module.call_indirect_guards();
2706 assert_eq!(guards.tables[0].base_byte_offset, Some(0));
2707 assert!(
2708 guards.tables[0].type_reject.iter().all(|r| r.is_some()),
2709 "unknown-size table rejects every type"
2710 );
2711 assert_eq!(
2712 guards.tables[1].base_byte_offset, None,
2713 "a later table's base is not a compile-time constant when a \
2714 preceding table's size is unknown (#650)"
2715 );
2716 assert_eq!(guards.tables[1].table_size, Some(1));
2717 }
2718}