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 pub has_null_slots: bool,
141}
142
143#[derive(Debug, Clone, Default, PartialEq, Eq)]
180pub struct CallIndirectGuards {
181 pub tables: Vec<TableGuards>,
184}
185
186impl CallIndirectGuards {
187 pub fn single_table(table_size: Option<u32>, type_reject: Vec<Option<String>>) -> Self {
190 Self {
191 tables: vec![TableGuards {
192 table_size,
193 base_byte_offset: Some(0),
194 type_reject,
195 has_null_slots: false,
196 }],
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct DecodedModule {
204 pub functions: Vec<FunctionOps>,
206 pub memories: Vec<WasmMemory>,
208 pub data_segments: Vec<(u32, Vec<u8>)>,
210 pub imports: Vec<ImportEntry>,
212 pub num_imported_funcs: u32,
214 pub func_arg_counts: Vec<u32>,
220 pub type_arg_counts: Vec<u32>,
224 pub func_ret_i64: Vec<bool>,
228 pub type_ret_i64: Vec<bool>,
230 pub func_params_i64: Vec<Vec<bool>>,
235 pub globals: Vec<WasmGlobal>,
240 pub elem_func_indices: Vec<u32>,
247 pub table_size: Option<u32>,
250 pub table_sizes: Vec<Option<u32>>,
260 pub elem_segments: Vec<ElemSegmentInfo>,
267 pub func_type_indices: Vec<u32>,
270 pub type_signatures: Vec<String>,
275 pub wsc_facts: Vec<crate::wsc_facts::WscFact>,
284}
285
286impl DecodedModule {
287 pub fn call_indirect_guards(&self) -> CallIndirectGuards {
294 let n_types = self.type_signatures.len();
295
296 let global_poison: Option<&'static str> = self
302 .elem_segments
303 .iter()
304 .any(|seg| seg.offset.is_none() || seg.table_index as usize >= self.table_sizes.len())
305 .then_some(
306 "element segment is not statically verifiable (passive/declared \
307 segment, non-const offset, out-of-range table, or non-`ref.func` \
308 entry)",
309 );
310
311 let mut tables = Vec::with_capacity(self.table_sizes.len());
312 let mut base_words: Option<u32> = Some(0);
316 for (n, &size) in self.table_sizes.iter().enumerate() {
317 let base_byte_offset = base_words.and_then(|w| w.checked_mul(4));
318 let (type_reject, has_null_slots) =
319 self.table_type_reject(n as u32, size, global_poison, n_types);
320 tables.push(TableGuards {
321 table_size: size,
322 base_byte_offset,
323 type_reject,
324 has_null_slots,
325 });
326 base_words = match (base_words, size) {
327 (Some(w), Some(s)) => w.checked_add(s),
328 _ => None,
329 };
330 }
331 CallIndirectGuards { tables }
332 }
333
334 fn table_type_reject(
343 &self,
344 n: u32,
345 size: Option<u32>,
346 global_poison: Option<&str>,
347 n_types: usize,
348 ) -> (Vec<Option<String>>, bool) {
349 let reject_all = |reason: String| (vec![Some(reason); n_types], false);
350
351 if let Some(reason) = global_poison {
352 return reject_all(reason.to_string());
353 }
354 let Some(size) = size else {
355 return reject_all(format!(
356 "table {n} has no compile-time-fixed size (imported table with \
357 growable limits)"
358 ));
359 };
360
361 let mut slots: Vec<Option<u32>> = vec![None; size as usize];
363 for seg in self.elem_segments.iter().filter(|s| s.table_index == n) {
364 let (Some(off), Some(funcs)) = (seg.offset, seg.funcs.as_ref()) else {
365 return reject_all(format!(
369 "element segment targeting table {n} is not statically \
370 verifiable (non-`ref.func` entry)"
371 ));
372 };
373 for (k, &f) in funcs.iter().enumerate() {
374 let Some(slot) = slots.get_mut(off as usize + k) else {
375 return reject_all(format!(
376 "element segment (offset {off}, {} entries) writes past \
377 table {n}'s declared size {size}",
378 funcs.len()
379 ));
380 };
381 *slot = Some(f);
382 }
383 }
384 let has_null_slots = slots.iter().any(|s| s.is_none());
393
394 let rejects = (0..n_types)
395 .map(|t| {
396 for f in slots.iter().flatten() {
397 let Some(&fty) = self.func_type_indices.get(*f as usize) else {
398 return Some(format!(
399 "table {n} entry references function {f} with no known type"
400 ));
401 };
402 if self.type_signatures.get(fty as usize) != self.type_signatures.get(t) {
403 return Some(format!(
404 "table {n} entry (function {f}, type {fty}) has a different \
405 signature than expected type {t} — a runtime type check \
406 is not implementable on the raw code-pointer table"
407 ));
408 }
409 }
410 None
411 })
412 .collect();
413 (rejects, has_null_slots)
414 }
415}
416
417pub fn decode_wasm_module(wasm_bytes: &[u8]) -> Result<DecodedModule> {
419 let mut functions = Vec::new();
420 let mut memories = Vec::new();
421 let mut data_segments = Vec::new();
422 let mut globals: Vec<WasmGlobal> = Vec::new();
423 let mut imports = Vec::new();
424 let mut func_index = 0u32;
425 let mut num_imported_funcs = 0u32;
426 let mut export_names: HashMap<u32, String> = HashMap::new();
427 let mut type_arg_counts: Vec<u32> = Vec::new();
430 let mut func_arg_counts: Vec<u32> = Vec::new();
431 let mut type_ret_i64: Vec<bool> = Vec::new();
432 let mut func_ret_i64: Vec<bool> = Vec::new();
433 let mut type_params_i64: Vec<Vec<bool>> = Vec::new();
435 let mut func_params_i64: Vec<Vec<bool>> = Vec::new();
436 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
438 let mut elem_func_indices: Vec<u32> = Vec::new();
439 let mut table_sizes: Vec<Option<u32>> = Vec::new();
443 let mut elem_segments: Vec<ElemSegmentInfo> = Vec::new();
444 let mut func_type_indices: Vec<u32> = Vec::new();
445 let mut type_signatures: Vec<String> = Vec::new();
446 let mut name_section_names: HashMap<u32, String> = HashMap::new();
452 let mut wsc_facts: Option<Vec<crate::wsc_facts::WscFact>> = None;
456 let mut num_imported_globals = 0u32;
463 let mut float_globals: std::collections::HashSet<u32> = std::collections::HashSet::new();
464
465 for payload in Parser::new(0).parse_all(wasm_bytes) {
466 let payload = payload.context("Failed to parse WASM payload")?;
467
468 match payload {
469 Payload::TypeSection(reader) => {
470 for rec_group in reader {
473 let rec_group = rec_group.context("Failed to parse type")?;
474 for sub_ty in rec_group.types() {
475 type_block_arity.push(match &sub_ty.composite_type.inner {
480 wasmparser::CompositeInnerType::Func(f) => (
481 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
482 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
483 ),
484 _ => (u8::MAX, u8::MAX),
485 });
486 let (count, ret_i64, params_i64) = match &sub_ty.composite_type.inner {
487 wasmparser::CompositeInnerType::Func(func_ty) => (
488 func_ty.params().len() as u32,
489 func_ty
490 .results()
491 .first()
492 .is_some_and(|t| *t == wasmparser::ValType::I64),
493 func_ty
498 .params()
499 .iter()
500 .map(|t| {
501 matches!(
502 t,
503 wasmparser::ValType::I64 | wasmparser::ValType::F64
504 )
505 })
506 .collect::<Vec<bool>>(),
507 ),
508 _ => (0, false, Vec::new()),
509 };
510 type_arg_counts.push(count);
511 type_ret_i64.push(ret_i64);
512 type_params_i64.push(params_i64);
513 type_signatures.push(match &sub_ty.composite_type.inner {
517 wasmparser::CompositeInnerType::Func(f) => {
518 format!("{:?}->{:?}", f.params(), f.results())
519 }
520 other => format!("non-func:{other:?}"),
521 });
522 }
523 }
524 }
525 Payload::ImportSection(reader) => {
526 for import in reader.into_imports() {
532 let import = import.context("Failed to parse import")?;
533 let (kind, idx) = match import.ty {
534 wasmparser::TypeRef::Func(type_idx) => {
535 let idx = num_imported_funcs;
536 num_imported_funcs += 1;
537 func_type_indices.push(type_idx); func_arg_counts
541 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
542 func_ret_i64.push(
543 type_ret_i64
544 .get(type_idx as usize)
545 .copied()
546 .unwrap_or(false),
547 );
548 func_params_i64.push(
549 type_params_i64
550 .get(type_idx as usize)
551 .cloned()
552 .unwrap_or_default(),
553 );
554 (ImportKind::Function(type_idx), idx)
555 }
556 wasmparser::TypeRef::Memory(_) => (ImportKind::Memory, 0),
557 wasmparser::TypeRef::Table(t) => {
558 table_sizes.push(match (u32::try_from(t.initial), t.maximum) {
566 (Ok(init), Some(max)) if u64::from(init) == max => Some(init),
567 _ => None,
568 });
569 (ImportKind::Table, 0)
570 }
571 wasmparser::TypeRef::Global(g) => {
572 if matches!(
576 g.content_type,
577 wasmparser::ValType::F32 | wasmparser::ValType::F64
578 ) {
579 float_globals.insert(num_imported_globals);
580 }
581 num_imported_globals += 1;
582 (ImportKind::Global, 0)
583 }
584 _ => continue,
585 };
586 imports.push(ImportEntry {
587 module: import.module.to_string(),
588 name: import.name.to_string(),
589 kind,
590 index: idx,
591 });
592 }
593 }
594 Payload::FunctionSection(reader) => {
595 for ty in reader {
600 let type_idx = ty.context("Failed to parse function type index")?;
601 func_type_indices.push(type_idx); func_arg_counts
603 .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
604 func_ret_i64.push(
605 type_ret_i64
606 .get(type_idx as usize)
607 .copied()
608 .unwrap_or(false),
609 );
610 func_params_i64.push(
611 type_params_i64
612 .get(type_idx as usize)
613 .cloned()
614 .unwrap_or_default(),
615 );
616 }
617 }
618 Payload::TableSection(reader) => {
619 for table in reader {
626 let table = table.context("Failed to parse table")?;
627 table_sizes.push(u32::try_from(table.ty.initial).ok());
628 }
629 }
630 Payload::MemorySection(reader) => {
631 for (idx, memory) in reader.into_iter().enumerate() {
632 let mem = memory.context("Failed to parse memory")?;
633 memories.push(WasmMemory {
634 index: idx as u32,
635 initial_pages: mem.initial as u32,
636 max_pages: mem.maximum.map(|m| m as u32),
637 shared: mem.shared,
638 });
639 }
640 }
641 Payload::GlobalSection(reader) => {
642 for (idx, global) in reader.into_iter().enumerate() {
650 let global = global.context("Failed to parse global")?;
651 let mut ops = global.init_expr.get_operators_reader();
652 let init = match ops.read() {
653 Ok(wasmparser::Operator::I32Const { value }) => {
654 Some(GlobalInit::I32(value))
655 }
656 Ok(wasmparser::Operator::I64Const { value }) => {
657 Some(GlobalInit::I64(value))
658 }
659 _ => None,
660 };
661 let slot_bytes = match global.ty.content_type {
666 wasmparser::ValType::I64 | wasmparser::ValType::F64 => 8,
667 wasmparser::ValType::V128 => 16,
668 _ => 4,
669 };
670 if matches!(
676 global.ty.content_type,
677 wasmparser::ValType::F32 | wasmparser::ValType::F64
678 ) {
679 float_globals.insert(num_imported_globals + idx as u32);
680 }
681 globals.push(WasmGlobal {
682 index: idx as u32,
683 init,
684 mutable: global.ty.mutable,
685 slot_bytes,
686 });
687 }
688 }
689 Payload::DataSection(reader) => {
690 for data in reader {
691 let data = data.context("Failed to parse data segment")?;
692 if let wasmparser::DataKind::Active {
693 memory_index: 0,
694 offset_expr,
695 } = data.kind
696 {
697 let mut ops = offset_expr.get_operators_reader();
698 if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
699 data_segments.push((value as u32, data.data.to_vec()));
700 }
701 }
702 }
703 }
704 Payload::ElementSection(reader) => {
705 for elem in reader {
712 let elem = elem.context("Failed to parse element segment")?;
713 let (seg_table, seg_offset): (u32, Option<u32>) = match &elem.kind {
719 wasmparser::ElementKind::Active {
720 table_index,
721 offset_expr,
722 } => {
723 let mut ops = offset_expr.get_operators_reader();
724 let off = match ops.read() {
725 Ok(wasmparser::Operator::I32Const { value }) => {
726 u32::try_from(value).ok()
727 }
728 _ => None,
729 };
730 (table_index.unwrap_or(0), off)
731 }
732 _ => (0, None),
733 };
734 let mut seg_funcs: Option<Vec<u32>> = Some(Vec::new());
735 match elem.items {
736 wasmparser::ElementItems::Functions(funcs) => {
737 for f in funcs {
738 let f = f.context("Failed to parse element func index")?;
739 elem_func_indices.push(f);
740 if let Some(v) = seg_funcs.as_mut() {
741 v.push(f);
742 }
743 }
744 }
745 wasmparser::ElementItems::Expressions(_, exprs) => {
746 for expr in exprs {
747 let expr = expr.context("Failed to parse element expr")?;
748 let mut entry_func: Option<u32> = None;
753 let mut plain = true;
754 for (k, op) in expr.get_operators_reader().into_iter().enumerate() {
755 match (k, op.context("Failed to parse element op")?) {
756 (0, wasmparser::Operator::RefFunc { function_index }) => {
757 elem_func_indices.push(function_index);
758 entry_func = Some(function_index);
759 }
760 (_, wasmparser::Operator::End) => {}
761 (_, wasmparser::Operator::RefFunc { function_index }) => {
762 elem_func_indices.push(function_index);
766 plain = false;
767 }
768 _ => plain = false,
769 }
770 }
771 match (plain, entry_func, seg_funcs.as_mut()) {
772 (true, Some(f), Some(v)) => v.push(f),
773 _ => seg_funcs = None,
774 }
775 }
776 }
777 }
778 elem_segments.push(ElemSegmentInfo {
779 table_index: seg_table,
780 offset: seg_offset,
781 funcs: seg_funcs,
782 });
783 }
784 }
785 Payload::ExportSection(exports) => {
786 for export in exports {
787 let export = export.context("Failed to parse export")?;
788 if export.kind == ExternalKind::Func {
789 export_names.insert(export.index, export.name.to_string());
790 }
791 }
792 }
793 Payload::CodeSectionEntry(body) => {
794 let (ops, op_offsets, block_arity, unsupported) =
795 decode_function_body(&body, &type_block_arity, &float_globals)?;
796 let actual_index = num_imported_funcs + func_index;
797 let export_name = export_names.get(&actual_index).cloned();
798
799 functions.push(FunctionOps {
800 index: actual_index,
801 export_name,
802 debug_name: None, ops,
804 op_offsets,
805 unsupported,
806 block_arity,
807 });
808 func_index += 1;
809 }
810 Payload::CustomSection(c) => {
811 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
813 parse_name_section_func_names(reader, &mut name_section_names);
814 }
815 if c.name() == crate::wsc_facts::WSC_FACTS_SECTION_NAME && wsc_facts.is_none() {
822 let parsed = crate::wsc_facts::parse_wsc_facts(c.data());
823 if let Some(reason) = &parsed.section_ignored {
824 eprintln!(
825 "warning: ignoring unparseable `wsc.facts` custom section \
826 ({reason}) — facts are optional accelerators, compilation \
827 is unaffected (#494 fail-safe skew rule)"
828 );
829 } else if parsed.records_skipped > 0 {
830 eprintln!(
831 "warning: skipped {} unknown/undecodable `wsc.facts` \
832 record(s) (likely a newer loom emitter); {} known fact(s) \
833 kept, compilation is unaffected (#494 fail-safe skew rule)",
834 parsed.records_skipped,
835 parsed.facts.len()
836 );
837 }
838 wsc_facts = Some(parsed.facts);
839 }
840 }
841 _ => {}
842 }
843 }
844
845 apply_name_section(&mut functions, &name_section_names);
846
847 Ok(DecodedModule {
848 functions,
849 memories,
850 data_segments,
851 imports,
852 num_imported_funcs,
853 func_arg_counts,
854 type_arg_counts,
855 func_ret_i64,
856 type_ret_i64,
857 func_params_i64,
858 globals,
859 elem_func_indices,
860 table_size: table_sizes.first().copied().flatten(),
861 table_sizes,
862 elem_segments,
863 func_type_indices,
864 type_signatures,
865 wsc_facts: wsc_facts.unwrap_or_default(),
866 })
867}
868
869fn parse_name_section_func_names(
875 reader: wasmparser::NameSectionReader<'_>,
876 out: &mut HashMap<u32, String>,
877) {
878 for subsection in reader.into_iter().flatten() {
879 if let wasmparser::Name::Function(map) = subsection {
880 for naming in map.into_iter().flatten() {
881 out.insert(naming.index, naming.name.to_string());
882 }
883 }
884 }
885}
886
887fn apply_name_section(functions: &mut [FunctionOps], names: &HashMap<u32, String>) {
891 if names.is_empty() {
892 return;
893 }
894 for f in functions {
895 f.debug_name = names.get(&f.index).cloned();
896 }
897}
898
899pub fn decode_wasm_functions(wasm_bytes: &[u8]) -> Result<Vec<FunctionOps>> {
901 let mut functions = Vec::new();
902 let mut func_index = 0u32;
903 let mut num_imported_funcs = 0u32;
904 let mut export_names: HashMap<u32, String> = HashMap::new();
905 let mut name_section_names: HashMap<u32, String> = HashMap::new();
906 let mut type_block_arity: Vec<(u8, u8)> = Vec::new();
908 let mut num_imported_globals = 0u32;
911 let mut float_globals: std::collections::HashSet<u32> = std::collections::HashSet::new();
912
913 for payload in Parser::new(0).parse_all(wasm_bytes) {
914 let payload = payload.context("Failed to parse WASM payload")?;
915
916 match payload {
917 Payload::TypeSection(reader) => {
918 for rec_group in reader {
923 let rec_group = rec_group.context("Failed to parse type")?;
924 for sub_ty in rec_group.types() {
925 type_block_arity.push(match &sub_ty.composite_type.inner {
926 wasmparser::CompositeInnerType::Func(f) => (
927 u8::try_from(f.params().len()).unwrap_or(u8::MAX),
928 u8::try_from(f.results().len()).unwrap_or(u8::MAX),
929 ),
930 _ => (u8::MAX, u8::MAX),
931 });
932 }
933 }
934 }
935 Payload::ImportSection(imports) => {
936 for import in imports.into_imports() {
939 let import = import.context("Failed to parse import")?;
940 match import.ty {
941 wasmparser::TypeRef::Func(_) => num_imported_funcs += 1,
942 wasmparser::TypeRef::Global(g) => {
943 if matches!(
946 g.content_type,
947 wasmparser::ValType::F32 | wasmparser::ValType::F64
948 ) {
949 float_globals.insert(num_imported_globals);
950 }
951 num_imported_globals += 1;
952 }
953 _ => {}
954 }
955 }
956 }
957 Payload::GlobalSection(reader) => {
958 for (idx, global) in reader.into_iter().enumerate() {
962 let global = global.context("Failed to parse global")?;
963 if matches!(
964 global.ty.content_type,
965 wasmparser::ValType::F32 | wasmparser::ValType::F64
966 ) {
967 float_globals.insert(num_imported_globals + idx as u32);
968 }
969 }
970 }
971 Payload::ExportSection(exports) => {
972 for export in exports {
973 let export = export.context("Failed to parse export")?;
974 if export.kind == ExternalKind::Func {
975 export_names.insert(export.index, export.name.to_string());
976 }
977 }
978 }
979 Payload::CodeSectionEntry(body) => {
980 let (ops, op_offsets, block_arity, unsupported) =
981 decode_function_body(&body, &type_block_arity, &float_globals)?;
982 let actual_index = num_imported_funcs + func_index;
983 let export_name = export_names.get(&actual_index).cloned();
984
985 functions.push(FunctionOps {
986 index: actual_index,
987 export_name,
988 debug_name: None, ops,
990 op_offsets,
991 unsupported,
992 block_arity,
993 });
994 func_index += 1;
995 }
996 Payload::CustomSection(c) => {
997 if let wasmparser::KnownCustom::Name(reader) = c.as_known() {
999 parse_name_section_func_names(reader, &mut name_section_names);
1000 }
1001 }
1002 _ => {}
1003 }
1004 }
1005
1006 apply_name_section(&mut functions, &name_section_names);
1007
1008 Ok(functions)
1009}
1010
1011#[derive(Debug, Clone)]
1013pub struct FunctionOps {
1014 pub index: u32,
1016 pub export_name: Option<String>,
1018 pub debug_name: Option<String>,
1027 pub ops: Vec<WasmOp>,
1029 pub op_offsets: Vec<u32>,
1037 pub unsupported: Option<String>,
1047 pub block_arity: Vec<(u8, u8)>,
1063}
1064
1065fn blocktype_arity(bt: &wasmparser::BlockType, type_block_arity: &[(u8, u8)]) -> (u8, u8) {
1070 match bt {
1071 wasmparser::BlockType::Empty => (0, 0),
1072 wasmparser::BlockType::Type(_) => (0, 1),
1073 wasmparser::BlockType::FuncType(i) => type_block_arity
1074 .get(*i as usize)
1075 .copied()
1076 .unwrap_or((u8::MAX, u8::MAX)),
1077 }
1078}
1079
1080type DecodedBody = (Vec<WasmOp>, Vec<u32>, Vec<(u8, u8)>, Option<String>);
1084
1085fn decode_function_body(
1091 body: &wasmparser::FunctionBody,
1092 type_block_arity: &[(u8, u8)],
1093 float_globals: &std::collections::HashSet<u32>,
1094) -> Result<DecodedBody> {
1095 let mut ops = Vec::new();
1096 let mut op_offsets = Vec::new();
1101 let mut block_arity: Vec<(u8, u8)> = Vec::new();
1104 let mut unsupported: Option<String> = None;
1105
1106 let ops_reader = body.get_operators_reader()?;
1107 for item in ops_reader.into_iter_with_offsets() {
1108 let (op, offset) = item.context("Failed to read operator")?;
1109
1110 if let Some(wasm_op) = convert_operator(&op) {
1111 if let wasmparser::Operator::Block { blockty }
1114 | wasmparser::Operator::Loop { blockty }
1115 | wasmparser::Operator::If { blockty } = &op
1116 {
1117 block_arity.push(blocktype_arity(blockty, type_block_arity));
1118 }
1119 if unsupported.is_none()
1125 && let WasmOp::GlobalGet(i) | WasmOp::GlobalSet(i) = &wasm_op
1126 && float_globals.contains(i)
1127 {
1128 unsupported = Some(format!(
1129 "{wasm_op:?} on an f32/f64-typed global — float globals \
1130 have no lowering, the initializer would be silently \
1131 zeroed (GI-FPU-001)"
1132 ));
1133 }
1134 ops.push(wasm_op);
1135 op_offsets.push(offset as u32);
1136 } else if unsupported.is_none() && !is_intentionally_ignored(&op) {
1137 unsupported = Some(format!("{op:?}"));
1141 }
1142 }
1143
1144 Ok((ops, op_offsets, block_arity, unsupported))
1145}
1146
1147fn is_intentionally_ignored(op: &wasmparser::Operator) -> bool {
1156 use wasmparser::Operator::*;
1157 matches!(op, Nop)
1158}
1159
1160fn convert_operator(op: &wasmparser::Operator) -> Option<WasmOp> {
1162 use wasmparser::Operator::*;
1163
1164 match op {
1165 I32Const { value } => Some(WasmOp::I32Const(*value)),
1167
1168 I32Add => Some(WasmOp::I32Add),
1170 I32Sub => Some(WasmOp::I32Sub),
1171 I32Mul => Some(WasmOp::I32Mul),
1172 I32DivS => Some(WasmOp::I32DivS),
1173 I32DivU => Some(WasmOp::I32DivU),
1174 I32RemS => Some(WasmOp::I32RemS),
1175 I32RemU => Some(WasmOp::I32RemU),
1176
1177 I64Const { value } => Some(WasmOp::I64Const(*value)),
1179
1180 I64Add => Some(WasmOp::I64Add),
1182 I64Sub => Some(WasmOp::I64Sub),
1183 I64Mul => Some(WasmOp::I64Mul),
1184 I64DivS => Some(WasmOp::I64DivS),
1185 I64DivU => Some(WasmOp::I64DivU),
1186 I64RemS => Some(WasmOp::I64RemS),
1187 I64RemU => Some(WasmOp::I64RemU),
1188
1189 I64And => Some(WasmOp::I64And),
1191 I64Or => Some(WasmOp::I64Or),
1192 I64Xor => Some(WasmOp::I64Xor),
1193 I64Shl => Some(WasmOp::I64Shl),
1194 I64ShrS => Some(WasmOp::I64ShrS),
1195 I64ShrU => Some(WasmOp::I64ShrU),
1196 I64Rotl => Some(WasmOp::I64Rotl),
1197 I64Rotr => Some(WasmOp::I64Rotr),
1198 I64Clz => Some(WasmOp::I64Clz),
1199 I64Ctz => Some(WasmOp::I64Ctz),
1200 I64Popcnt => Some(WasmOp::I64Popcnt),
1201 I64Extend8S => Some(WasmOp::I64Extend8S),
1202 I64Extend16S => Some(WasmOp::I64Extend16S),
1203 I64Extend32S => Some(WasmOp::I64Extend32S),
1204 I64ExtendI32U => Some(WasmOp::I64ExtendI32U),
1210 I64ExtendI32S => Some(WasmOp::I64ExtendI32S),
1211 I32WrapI64 => Some(WasmOp::I32WrapI64),
1212
1213 I64Eqz => Some(WasmOp::I64Eqz),
1215 I64Eq => Some(WasmOp::I64Eq),
1216 I64Ne => Some(WasmOp::I64Ne),
1217 I64LtS => Some(WasmOp::I64LtS),
1218 I64LtU => Some(WasmOp::I64LtU),
1219 I64LeS => Some(WasmOp::I64LeS),
1220 I64LeU => Some(WasmOp::I64LeU),
1221 I64GtS => Some(WasmOp::I64GtS),
1222 I64GtU => Some(WasmOp::I64GtU),
1223 I64GeS => Some(WasmOp::I64GeS),
1224 I64GeU => Some(WasmOp::I64GeU),
1225
1226 I32And => Some(WasmOp::I32And),
1228 I32Or => Some(WasmOp::I32Or),
1229 I32Xor => Some(WasmOp::I32Xor),
1230 I32Shl => Some(WasmOp::I32Shl),
1231 I32ShrS => Some(WasmOp::I32ShrS),
1232 I32ShrU => Some(WasmOp::I32ShrU),
1233 I32Rotl => Some(WasmOp::I32Rotl),
1234 I32Rotr => Some(WasmOp::I32Rotr),
1235 I32Clz => Some(WasmOp::I32Clz),
1236 I32Ctz => Some(WasmOp::I32Ctz),
1237 I32Popcnt => Some(WasmOp::I32Popcnt),
1238 I32Extend8S => Some(WasmOp::I32Extend8S),
1239 I32Extend16S => Some(WasmOp::I32Extend16S),
1240
1241 I32Eqz => Some(WasmOp::I32Eqz),
1243 I32Eq => Some(WasmOp::I32Eq),
1244 I32Ne => Some(WasmOp::I32Ne),
1245 I32LtS => Some(WasmOp::I32LtS),
1246 I32LtU => Some(WasmOp::I32LtU),
1247 I32LeS => Some(WasmOp::I32LeS),
1248 I32LeU => Some(WasmOp::I32LeU),
1249 I32GtS => Some(WasmOp::I32GtS),
1250 I32GtU => Some(WasmOp::I32GtU),
1251 I32GeS => Some(WasmOp::I32GeS),
1252 I32GeU => Some(WasmOp::I32GeU),
1253
1254 I32Load { memarg } => Some(WasmOp::I32Load {
1256 offset: memarg.offset as u32,
1257 align: memarg.align as u32,
1258 }),
1259 I32Store { memarg } => Some(WasmOp::I32Store {
1260 offset: memarg.offset as u32,
1261 align: memarg.align as u32,
1262 }),
1263 I64Load { memarg } => Some(WasmOp::I64Load {
1270 offset: memarg.offset as u32,
1271 align: memarg.align as u32,
1272 }),
1273 I64Store { memarg } => Some(WasmOp::I64Store {
1274 offset: memarg.offset as u32,
1275 align: memarg.align as u32,
1276 }),
1277
1278 I32Load8S { memarg } => Some(WasmOp::I32Load8S {
1280 offset: memarg.offset as u32,
1281 align: memarg.align as u32,
1282 }),
1283 I32Load8U { memarg } => Some(WasmOp::I32Load8U {
1284 offset: memarg.offset as u32,
1285 align: memarg.align as u32,
1286 }),
1287 I32Load16S { memarg } => Some(WasmOp::I32Load16S {
1288 offset: memarg.offset as u32,
1289 align: memarg.align as u32,
1290 }),
1291 I32Load16U { memarg } => Some(WasmOp::I32Load16U {
1292 offset: memarg.offset as u32,
1293 align: memarg.align as u32,
1294 }),
1295
1296 I32Store8 { memarg } => Some(WasmOp::I32Store8 {
1298 offset: memarg.offset as u32,
1299 align: memarg.align as u32,
1300 }),
1301 I32Store16 { memarg } => Some(WasmOp::I32Store16 {
1302 offset: memarg.offset as u32,
1303 align: memarg.align as u32,
1304 }),
1305
1306 LocalGet { local_index } => Some(WasmOp::LocalGet(*local_index)),
1308 LocalSet { local_index } => Some(WasmOp::LocalSet(*local_index)),
1309 LocalTee { local_index } => Some(WasmOp::LocalTee(*local_index)),
1310 GlobalGet { global_index } => Some(WasmOp::GlobalGet(*global_index)),
1311 GlobalSet { global_index } => Some(WasmOp::GlobalSet(*global_index)),
1312
1313 Block { .. } => Some(WasmOp::Block),
1315 Loop { .. } => Some(WasmOp::Loop),
1316 Br { relative_depth } => Some(WasmOp::Br(*relative_depth)),
1317 BrIf { relative_depth } => Some(WasmOp::BrIf(*relative_depth)),
1318 BrTable { targets } => {
1324 let default = targets.default();
1325 let tgts: Vec<u32> = targets.targets().filter_map(Result::ok).collect();
1326 Some(WasmOp::BrTable {
1327 targets: tgts,
1328 default,
1329 })
1330 }
1331 Return => Some(WasmOp::Return),
1332 Call { function_index } => Some(WasmOp::Call(*function_index)),
1333 CallIndirect {
1334 type_index,
1335 table_index,
1336 ..
1337 } => Some(WasmOp::CallIndirect {
1338 type_index: *type_index,
1339 table_index: *table_index,
1340 }),
1341
1342 End => Some(WasmOp::End),
1344
1345 Unreachable => Some(WasmOp::Unreachable),
1352
1353 Nop => None,
1355
1356 Drop => Some(WasmOp::Drop),
1358
1359 Select => Some(WasmOp::Select),
1361
1362 If { .. } => Some(WasmOp::If),
1364 Else => Some(WasmOp::Else),
1365
1366 I64Load8S { memarg } => Some(WasmOp::I64Load8S {
1368 offset: memarg.offset as u32,
1369 align: memarg.align as u32,
1370 }),
1371 I64Load8U { memarg } => Some(WasmOp::I64Load8U {
1372 offset: memarg.offset as u32,
1373 align: memarg.align as u32,
1374 }),
1375 I64Load16S { memarg } => Some(WasmOp::I64Load16S {
1376 offset: memarg.offset as u32,
1377 align: memarg.align as u32,
1378 }),
1379 I64Load16U { memarg } => Some(WasmOp::I64Load16U {
1380 offset: memarg.offset as u32,
1381 align: memarg.align as u32,
1382 }),
1383 I64Load32S { memarg } => Some(WasmOp::I64Load32S {
1384 offset: memarg.offset as u32,
1385 align: memarg.align as u32,
1386 }),
1387 I64Load32U { memarg } => Some(WasmOp::I64Load32U {
1388 offset: memarg.offset as u32,
1389 align: memarg.align as u32,
1390 }),
1391
1392 I64Store8 { memarg } => Some(WasmOp::I64Store8 {
1394 offset: memarg.offset as u32,
1395 align: memarg.align as u32,
1396 }),
1397 I64Store16 { memarg } => Some(WasmOp::I64Store16 {
1398 offset: memarg.offset as u32,
1399 align: memarg.align as u32,
1400 }),
1401 I64Store32 { memarg } => Some(WasmOp::I64Store32 {
1402 offset: memarg.offset as u32,
1403 align: memarg.align as u32,
1404 }),
1405
1406 MemorySize { mem, .. } => Some(WasmOp::MemorySize(*mem)),
1408 MemoryGrow { mem, .. } => Some(WasmOp::MemoryGrow(*mem)),
1409
1410 MemoryCopy {
1417 dst_mem: 0,
1418 src_mem: 0,
1419 } => Some(WasmOp::MemoryCopy),
1420 MemoryFill { mem: 0 } => Some(WasmOp::MemoryFill),
1421
1422 V128Const { value } => {
1426 let mut bytes = [0u8; 16];
1427 bytes.copy_from_slice(value.bytes());
1428 Some(WasmOp::V128Const(bytes))
1429 }
1430 V128Load { memarg } => Some(WasmOp::V128Load {
1431 offset: memarg.offset as u32,
1432 align: memarg.align as u32,
1433 }),
1434 V128Store { memarg } => Some(WasmOp::V128Store {
1435 offset: memarg.offset as u32,
1436 align: memarg.align as u32,
1437 }),
1438
1439 V128And => Some(WasmOp::V128And),
1441 V128Or => Some(WasmOp::V128Or),
1442 V128Xor => Some(WasmOp::V128Xor),
1443 V128Not => Some(WasmOp::V128Not),
1444 V128AndNot => Some(WasmOp::V128AndNot),
1445
1446 I8x16Add => Some(WasmOp::I8x16Add),
1448 I8x16Sub => Some(WasmOp::I8x16Sub),
1449 I8x16Neg => Some(WasmOp::I8x16Neg),
1450 I8x16Eq => Some(WasmOp::I8x16Eq),
1451 I8x16Ne => Some(WasmOp::I8x16Ne),
1452 I8x16LtS => Some(WasmOp::I8x16LtS),
1453 I8x16LtU => Some(WasmOp::I8x16LtU),
1454 I8x16GtS => Some(WasmOp::I8x16GtS),
1455 I8x16GtU => Some(WasmOp::I8x16GtU),
1456 I8x16LeS => Some(WasmOp::I8x16LeS),
1457 I8x16LeU => Some(WasmOp::I8x16LeU),
1458 I8x16GeS => Some(WasmOp::I8x16GeS),
1459 I8x16GeU => Some(WasmOp::I8x16GeU),
1460 I8x16Splat => Some(WasmOp::I8x16Splat),
1461 I8x16ExtractLaneS { lane } => Some(WasmOp::I8x16ExtractLaneS(*lane)),
1462 I8x16ExtractLaneU { lane } => Some(WasmOp::I8x16ExtractLaneU(*lane)),
1463 I8x16ReplaceLane { lane } => Some(WasmOp::I8x16ReplaceLane(*lane)),
1464 I8x16Shuffle { lanes } => Some(WasmOp::I8x16Shuffle(*lanes)),
1465 I8x16Swizzle => Some(WasmOp::I8x16Swizzle),
1466
1467 I16x8Add => Some(WasmOp::I16x8Add),
1469 I16x8Sub => Some(WasmOp::I16x8Sub),
1470 I16x8Mul => Some(WasmOp::I16x8Mul),
1471 I16x8Neg => Some(WasmOp::I16x8Neg),
1472 I16x8Eq => Some(WasmOp::I16x8Eq),
1473 I16x8Ne => Some(WasmOp::I16x8Ne),
1474 I16x8LtS => Some(WasmOp::I16x8LtS),
1475 I16x8LtU => Some(WasmOp::I16x8LtU),
1476 I16x8GtS => Some(WasmOp::I16x8GtS),
1477 I16x8GtU => Some(WasmOp::I16x8GtU),
1478 I16x8LeS => Some(WasmOp::I16x8LeS),
1479 I16x8LeU => Some(WasmOp::I16x8LeU),
1480 I16x8GeS => Some(WasmOp::I16x8GeS),
1481 I16x8GeU => Some(WasmOp::I16x8GeU),
1482 I16x8Splat => Some(WasmOp::I16x8Splat),
1483 I16x8ExtractLaneS { lane } => Some(WasmOp::I16x8ExtractLaneS(*lane)),
1484 I16x8ExtractLaneU { lane } => Some(WasmOp::I16x8ExtractLaneU(*lane)),
1485 I16x8ReplaceLane { lane } => Some(WasmOp::I16x8ReplaceLane(*lane)),
1486
1487 I32x4Add => Some(WasmOp::I32x4Add),
1489 I32x4Sub => Some(WasmOp::I32x4Sub),
1490 I32x4Mul => Some(WasmOp::I32x4Mul),
1491 I32x4Neg => Some(WasmOp::I32x4Neg),
1492 I32x4Eq => Some(WasmOp::I32x4Eq),
1493 I32x4Ne => Some(WasmOp::I32x4Ne),
1494 I32x4LtS => Some(WasmOp::I32x4LtS),
1495 I32x4LtU => Some(WasmOp::I32x4LtU),
1496 I32x4GtS => Some(WasmOp::I32x4GtS),
1497 I32x4GtU => Some(WasmOp::I32x4GtU),
1498 I32x4LeS => Some(WasmOp::I32x4LeS),
1499 I32x4LeU => Some(WasmOp::I32x4LeU),
1500 I32x4GeS => Some(WasmOp::I32x4GeS),
1501 I32x4GeU => Some(WasmOp::I32x4GeU),
1502 I32x4Splat => Some(WasmOp::I32x4Splat),
1503 I32x4ExtractLane { lane } => Some(WasmOp::I32x4ExtractLane(*lane)),
1504 I32x4ReplaceLane { lane } => Some(WasmOp::I32x4ReplaceLane(*lane)),
1505
1506 I64x2Add => Some(WasmOp::I64x2Add),
1508 I64x2Sub => Some(WasmOp::I64x2Sub),
1509 I64x2Mul => Some(WasmOp::I64x2Mul),
1510 I64x2Neg => Some(WasmOp::I64x2Neg),
1511 I64x2Eq => Some(WasmOp::I64x2Eq),
1512 I64x2Ne => Some(WasmOp::I64x2Ne),
1513 I64x2LtS => Some(WasmOp::I64x2LtS),
1514 I64x2GtS => Some(WasmOp::I64x2GtS),
1515 I64x2LeS => Some(WasmOp::I64x2LeS),
1516 I64x2GeS => Some(WasmOp::I64x2GeS),
1517 I64x2Splat => Some(WasmOp::I64x2Splat),
1518 I64x2ExtractLane { lane } => Some(WasmOp::I64x2ExtractLane(*lane)),
1519 I64x2ReplaceLane { lane } => Some(WasmOp::I64x2ReplaceLane(*lane)),
1520
1521 F32x4Add => Some(WasmOp::F32x4Add),
1523 F32x4Sub => Some(WasmOp::F32x4Sub),
1524 F32x4Mul => Some(WasmOp::F32x4Mul),
1525 F32x4Div => Some(WasmOp::F32x4Div),
1526 F32x4Abs => Some(WasmOp::F32x4Abs),
1527 F32x4Neg => Some(WasmOp::F32x4Neg),
1528 F32x4Sqrt => Some(WasmOp::F32x4Sqrt),
1529 F32x4Eq => Some(WasmOp::F32x4Eq),
1530 F32x4Ne => Some(WasmOp::F32x4Ne),
1531 F32x4Lt => Some(WasmOp::F32x4Lt),
1532 F32x4Le => Some(WasmOp::F32x4Le),
1533 F32x4Gt => Some(WasmOp::F32x4Gt),
1534 F32x4Ge => Some(WasmOp::F32x4Ge),
1535 F32x4Splat => Some(WasmOp::F32x4Splat),
1536 F32x4ExtractLane { lane } => Some(WasmOp::F32x4ExtractLane(*lane)),
1537 F32x4ReplaceLane { lane } => Some(WasmOp::F32x4ReplaceLane(*lane)),
1538
1539 _ => None,
1541 }
1542}
1543
1544#[cfg(test)]
1545mod tests {
1546 use super::*;
1547
1548 #[test]
1549 fn test_decode_simple_add() {
1550 let wat = r#"
1551 (module
1552 (func (export "add") (param i32 i32) (result i32)
1553 local.get 0
1554 local.get 1
1555 i32.add
1556 )
1557 )
1558 "#;
1559
1560 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1561 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1562
1563 assert_eq!(functions.len(), 1);
1564 assert_eq!(functions[0].index, 0);
1565 assert_eq!(functions[0].export_name, Some("add".to_string()));
1566 assert_eq!(
1567 functions[0].ops,
1568 vec![
1569 WasmOp::LocalGet(0),
1570 WasmOp::LocalGet(1),
1571 WasmOp::I32Add,
1572 WasmOp::End
1573 ]
1574 );
1575 }
1576
1577 #[test]
1583 fn test_decode_i64_i32_width_conversions() {
1584 let wat = r#"
1585 (module
1586 (func (export "conv") (param i32 i64) (result i32)
1587 local.get 0
1588 i64.extend_i32_u
1589 local.get 0
1590 i64.extend_i32_s
1591 i64.add
1592 local.get 1
1593 i64.add
1594 i32.wrap_i64
1595 )
1596 )
1597 "#;
1598 let wasm = wat::parse_str(wat).expect("parse");
1599 let functions = decode_wasm_functions(&wasm).expect("decode");
1600 let ops = &functions[0].ops;
1601 assert!(
1602 ops.contains(&WasmOp::I64ExtendI32U),
1603 "i64.extend_i32_u must decode (not be dropped): {ops:?}"
1604 );
1605 assert!(
1606 ops.contains(&WasmOp::I64ExtendI32S),
1607 "i64.extend_i32_s must decode (not be dropped): {ops:?}"
1608 );
1609 assert!(
1610 ops.contains(&WasmOp::I32WrapI64),
1611 "i32.wrap_i64 must decode (not be dropped): {ops:?}"
1612 );
1613 }
1614
1615 #[test]
1620 fn test_decode_br_table() {
1621 let wat = r#"
1622 (module
1623 (func (export "bt") (param i32) (result i32)
1624 (block (block (block
1625 local.get 0
1626 br_table 2 0 1 2)
1627 i32.const 30 return)
1628 i32.const 20 return)
1629 i32.const 10))
1630 "#;
1631 let wasm = wat::parse_str(wat).expect("parse");
1632 let functions = decode_wasm_functions(&wasm).expect("decode");
1633 let bt = functions[0]
1634 .ops
1635 .iter()
1636 .find_map(|o| match o {
1637 WasmOp::BrTable { targets, default } => Some((targets.clone(), *default)),
1638 _ => None,
1639 })
1640 .expect("br_table must decode (not be dropped)");
1641 assert_eq!(bt.0, vec![2, 0, 1], "br_table targets preserved in order");
1642 assert_eq!(bt.1, 2, "br_table default preserved");
1643 }
1644
1645 #[test]
1646 fn test_decode_arithmetic() {
1647 let wat = r#"
1648 (module
1649 (func (export "calc") (result i32)
1650 i32.const 5
1651 i32.const 3
1652 i32.mul
1653 i32.const 2
1654 i32.add
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_eq!(functions[0].export_name, Some("calc".to_string()));
1664 assert_eq!(
1665 functions[0].ops,
1666 vec![
1667 WasmOp::I32Const(5),
1668 WasmOp::I32Const(3),
1669 WasmOp::I32Mul,
1670 WasmOp::I32Const(2),
1671 WasmOp::I32Add,
1672 WasmOp::End,
1673 ]
1674 );
1675 }
1676
1677 #[test]
1678 fn test_decode_multi_function_module() {
1679 let wat = r#"
1680 (module
1681 (func $helper)
1682 (func (export "add") (param i32 i32) (result i32)
1683 local.get 0
1684 local.get 1
1685 i32.add
1686 )
1687 (func (export "sub") (param i32 i32) (result i32)
1688 local.get 0
1689 local.get 1
1690 i32.sub
1691 )
1692 )
1693 "#;
1694
1695 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1696 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1697
1698 assert_eq!(functions.len(), 3);
1699 assert_eq!(functions[0].index, 0);
1700 assert_eq!(functions[0].export_name, None);
1701 assert_eq!(functions[1].index, 1);
1702 assert_eq!(functions[1].export_name, Some("add".to_string()));
1703 assert_eq!(functions[2].index, 2);
1704 assert_eq!(functions[2].export_name, Some("sub".to_string()));
1705 }
1706
1707 #[test]
1708 fn test_decode_module_with_imports() {
1709 let wat = r#"
1710 (module
1711 (import "env" "log" (func $log (param i32)))
1712 (import "env" "memory" (memory 1))
1713 (func (export "run") (param i32)
1714 local.get 0
1715 call 0
1716 )
1717 )
1718 "#;
1719
1720 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1721 let module = decode_wasm_module(&wasm).expect("Failed to decode");
1722
1723 assert_eq!(module.imports.len(), 2);
1725 assert_eq!(module.num_imported_funcs, 1);
1726
1727 assert_eq!(module.imports[0].module, "env");
1729 assert_eq!(module.imports[0].name, "log");
1730 assert!(matches!(module.imports[0].kind, ImportKind::Function(_)));
1731
1732 assert_eq!(module.imports[1].module, "env");
1734 assert_eq!(module.imports[1].name, "memory");
1735 assert_eq!(module.imports[1].kind, ImportKind::Memory);
1736
1737 assert_eq!(module.functions.len(), 1);
1739 assert_eq!(module.functions[0].index, 1);
1740 assert_eq!(module.functions[0].export_name, Some("run".to_string()));
1741 }
1742
1743 #[test]
1744 fn test_find_function_by_export_name() {
1745 let wat = r#"
1746 (module
1747 (func $helper)
1748 (func (export "add") (param i32 i32) (result i32)
1749 local.get 0
1750 local.get 1
1751 i32.add
1752 )
1753 )
1754 "#;
1755
1756 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1757 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1758
1759 let add_func = functions
1760 .iter()
1761 .find(|f| f.export_name.as_deref() == Some("add"))
1762 .expect("Should find 'add' function");
1763
1764 assert_eq!(add_func.index, 1);
1765 assert!(add_func.ops.contains(&WasmOp::I32Add));
1766 }
1767
1768 #[test]
1769 fn test_decode_subword_loads() {
1770 let wat = r#"
1771 (module
1772 (memory 1)
1773 (func (export "test") (param i32) (result i32)
1774 local.get 0
1775 i32.load8_u
1776 )
1777 )
1778 "#;
1779
1780 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1781 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1782
1783 assert_eq!(functions.len(), 1);
1784 assert!(functions[0].ops.contains(&WasmOp::I32Load8U {
1785 offset: 0,
1786 align: 0,
1787 }));
1788 }
1789
1790 #[test]
1791 fn test_decode_subword_stores() {
1792 let wat = r#"
1793 (module
1794 (memory 1)
1795 (func (export "test") (param i32 i32)
1796 local.get 0
1797 local.get 1
1798 i32.store8
1799 )
1800 )
1801 "#;
1802
1803 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1804 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1805
1806 assert_eq!(functions.len(), 1);
1807 assert!(functions[0].ops.contains(&WasmOp::I32Store8 {
1808 offset: 0,
1809 align: 0,
1810 }));
1811 }
1812
1813 #[test]
1814 fn test_decode_memory_size_grow() {
1815 let wat = r#"
1816 (module
1817 (memory 1)
1818 (func (export "test") (result i32)
1819 memory.size
1820 )
1821 )
1822 "#;
1823
1824 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1825 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1826
1827 assert_eq!(functions.len(), 1);
1828 assert!(functions[0].ops.contains(&WasmOp::MemorySize(0)));
1829 }
1830
1831 #[test]
1832 fn test_decode_memory_grow() {
1833 let wat = r#"
1834 (module
1835 (memory 1)
1836 (func (export "test") (param i32) (result i32)
1837 local.get 0
1838 memory.grow
1839 )
1840 )
1841 "#;
1842
1843 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1844 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1845
1846 assert_eq!(functions.len(), 1);
1847 assert!(functions[0].ops.contains(&WasmOp::MemoryGrow(0)));
1848 }
1849
1850 #[test]
1851 fn test_decode_bulk_memory_374() {
1852 let wat = r#"
1855 (module
1856 (memory 1)
1857 (func (export "cpy") (param i32 i32 i32)
1858 local.get 0 local.get 1 local.get 2 memory.copy)
1859 (func (export "fil") (param i32 i32 i32)
1860 local.get 0 local.get 1 local.get 2 memory.fill)
1861 )
1862 "#;
1863 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1864 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1865 assert_eq!(functions.len(), 2);
1866 assert!(functions[0].ops.contains(&WasmOp::MemoryCopy));
1867 assert!(functions[1].ops.contains(&WasmOp::MemoryFill));
1868 assert!(functions[0].unsupported.is_none());
1870 assert!(functions[1].unsupported.is_none());
1871 }
1872
1873 #[test]
1874 fn test_decode_i64_subword_loads() {
1875 let wat = r#"
1876 (module
1877 (memory 1)
1878 (func (export "test") (param i32) (result i64)
1879 local.get 0
1880 i64.load8_s
1881 )
1882 )
1883 "#;
1884
1885 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1886 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1887
1888 assert_eq!(functions.len(), 1);
1889 assert!(functions[0].ops.contains(&WasmOp::I64Load8S {
1890 offset: 0,
1891 align: 0,
1892 }));
1893 }
1894
1895 #[test]
1896 fn test_decode_all_subword_memory_ops() {
1897 let wat = r#"
1899 (module
1900 (memory 1)
1901 (func (export "test") (param i32)
1902 ;; i32 sub-word loads
1903 local.get 0
1904 i32.load8_s
1905 drop
1906 local.get 0
1907 i32.load8_u
1908 drop
1909 local.get 0
1910 i32.load16_s
1911 drop
1912 local.get 0
1913 i32.load16_u
1914 drop
1915
1916 ;; i32 sub-word stores
1917 local.get 0
1918 i32.const 42
1919 i32.store8
1920 local.get 0
1921 i32.const 42
1922 i32.store16
1923
1924 ;; i64 sub-word loads
1925 local.get 0
1926 i64.load8_s
1927 drop
1928 local.get 0
1929 i64.load8_u
1930 drop
1931 local.get 0
1932 i64.load16_s
1933 drop
1934 local.get 0
1935 i64.load16_u
1936 drop
1937 local.get 0
1938 i64.load32_s
1939 drop
1940 local.get 0
1941 i64.load32_u
1942 drop
1943
1944 ;; i64 sub-word stores
1945 local.get 0
1946 i64.const 42
1947 i64.store8
1948 local.get 0
1949 i64.const 42
1950 i64.store16
1951 local.get 0
1952 i64.const 42
1953 i64.store32
1954 )
1955 )
1956 "#;
1957
1958 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
1959 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1960
1961 assert_eq!(functions.len(), 1);
1962 let ops = &functions[0].ops;
1963
1964 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8S { .. })));
1966 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8U { .. })));
1967 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16S { .. })));
1968 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16U { .. })));
1969 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store8 { .. })));
1970 assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store16 { .. })));
1971
1972 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8S { .. })));
1974 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8U { .. })));
1975 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16S { .. })));
1976 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16U { .. })));
1977 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32S { .. })));
1978 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32U { .. })));
1979 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store8 { .. })));
1980 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store16 { .. })));
1981 assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store32 { .. })));
1982 }
1983
1984 #[test]
1985 fn test_decode_simd_i32x4_add() {
1986 let wat = r#"
1987 (module
1988 (func (export "add_v128") (param v128 v128) (result v128)
1989 local.get 0
1990 local.get 1
1991 i32x4.add
1992 )
1993 )
1994 "#;
1995
1996 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1997 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1998
1999 assert_eq!(functions.len(), 1);
2000 assert!(
2001 functions[0].ops.contains(&WasmOp::I32x4Add),
2002 "Should decode i32x4.add: {:?}",
2003 functions[0].ops
2004 );
2005 }
2006
2007 #[test]
2008 fn test_decode_simd_v128_const() {
2009 let wat = r#"
2010 (module
2011 (func (export "const_v128") (result v128)
2012 v128.const i32x4 1 2 3 4
2013 )
2014 )
2015 "#;
2016
2017 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2018 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2019
2020 assert_eq!(functions.len(), 1);
2021 assert!(
2022 functions[0]
2023 .ops
2024 .iter()
2025 .any(|o| matches!(o, WasmOp::V128Const(_))),
2026 "Should decode v128.const: {:?}",
2027 functions[0].ops
2028 );
2029 }
2030
2031 #[test]
2032 fn test_decode_simd_v128_load_store() {
2033 let wat = r#"
2034 (module
2035 (memory 1)
2036 (func (export "load_store") (param i32)
2037 local.get 0
2038 v128.load
2039 local.get 0
2040 v128.store
2041 )
2042 )
2043 "#;
2044
2045 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2046 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2047
2048 assert_eq!(functions.len(), 1);
2049 let ops = &functions[0].ops;
2050 assert!(
2051 ops.iter().any(|o| matches!(o, WasmOp::V128Load { .. })),
2052 "Should decode v128.load"
2053 );
2054 assert!(
2055 ops.iter().any(|o| matches!(o, WasmOp::V128Store { .. })),
2056 "Should decode v128.store"
2057 );
2058 }
2059
2060 #[test]
2061 fn test_decode_simd_bitwise_ops() {
2062 let wat = r#"
2063 (module
2064 (func (export "bitwise") (param v128 v128) (result v128)
2065 local.get 0
2066 local.get 1
2067 v128.and
2068 )
2069 )
2070 "#;
2071
2072 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2073 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2074
2075 assert_eq!(functions.len(), 1);
2076 assert!(functions[0].ops.contains(&WasmOp::V128And));
2077 }
2078
2079 #[test]
2080 fn test_decode_simd_splat() {
2081 let wat = r#"
2082 (module
2083 (func (export "splat") (param i32) (result v128)
2084 local.get 0
2085 i32x4.splat
2086 )
2087 )
2088 "#;
2089
2090 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2091 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2092
2093 assert_eq!(functions.len(), 1);
2094 assert!(functions[0].ops.contains(&WasmOp::I32x4Splat));
2095 }
2096
2097 #[test]
2098 fn test_decode_simd_extract_lane() {
2099 let wat = r#"
2100 (module
2101 (func (export "extract") (param v128) (result i32)
2102 local.get 0
2103 i32x4.extract_lane 2
2104 )
2105 )
2106 "#;
2107
2108 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2109 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2110
2111 assert_eq!(functions.len(), 1);
2112 assert!(
2113 functions[0].ops.contains(&WasmOp::I32x4ExtractLane(2)),
2114 "Should decode i32x4.extract_lane 2"
2115 );
2116 }
2117
2118 #[test]
2119 fn test_decode_simd_f32x4_arithmetic() {
2120 let wat = r#"
2121 (module
2122 (func (export "f32x4_add") (param v128 v128) (result v128)
2123 local.get 0
2124 local.get 1
2125 f32x4.add
2126 )
2127 )
2128 "#;
2129
2130 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2131 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2132
2133 assert_eq!(functions.len(), 1);
2134 assert!(functions[0].ops.contains(&WasmOp::F32x4Add));
2135 }
2136
2137 #[test]
2138 fn test_369_scalar_float_op_flags_function_unsupported_not_dropped() {
2139 let wat = r#"
2143 (module
2144 (func (export "fadd") (param f32 f32) (result f32)
2145 local.get 0 local.get 1 f32.add)
2146 (func (export "iadd") (param i32 i32) (result i32)
2147 local.get 0 local.get 1 i32.add))
2148 "#;
2149 let wasm = wat::parse_str(wat).expect("parse");
2150 let functions = decode_wasm_functions(&wasm).expect("decode");
2151 let fadd = functions
2152 .iter()
2153 .find(|f| f.export_name.as_deref() == Some("fadd"))
2154 .unwrap();
2155 let iadd = functions
2156 .iter()
2157 .find(|f| f.export_name.as_deref() == Some("iadd"))
2158 .unwrap();
2159 assert!(
2160 fadd.unsupported.is_some(),
2161 "f32.add must flag the function unsupported (loud-skip), got {:?}",
2162 fadd.unsupported
2163 );
2164 assert!(
2165 fadd.unsupported.as_deref().unwrap().contains("F32Add"),
2166 "diagnostic should name the op: {:?}",
2167 fadd.unsupported
2168 );
2169 assert!(
2170 iadd.unsupported.is_none(),
2171 "a pure-integer function must NOT be flagged: {:?}",
2172 iadd.unsupported
2173 );
2174 }
2175
2176 #[test]
2177 fn test_369_float_global_access_flags_function_unsupported() {
2178 let wat = r#"
2186 (module
2187 (global $fg f32 (f32.const 2.5))
2188 (global $dg (mut f64) (f64.const 1.5))
2189 (global $ig (mut i32) (i32.const 7))
2190 (func (export "fget") (result f32) global.get $fg)
2191 (func (export "dset") (param f64) local.get 0 global.set $dg)
2192 (func (export "iget") (result i32) global.get $ig))
2193 "#;
2194 let wasm = wat::parse_str(wat).expect("parse");
2195
2196 let module = decode_wasm_module(&wasm).expect("decode module");
2200 for functions in [
2201 &module.functions,
2202 &decode_wasm_functions(&wasm).expect("decode fns"),
2203 ] {
2204 let by_name = |n: &str| {
2205 functions
2206 .iter()
2207 .find(|f| f.export_name.as_deref() == Some(n))
2208 .unwrap()
2209 };
2210 let fget = by_name("fget");
2211 assert!(
2212 fget.unsupported.is_some(),
2213 "global.get of an f32 global must flag the function (loud-skip), got {:?}",
2214 fget.unsupported
2215 );
2216 let reason = fget.unsupported.as_deref().unwrap();
2217 assert!(
2218 reason.contains("GlobalGet") && reason.contains("GI-FPU-001"),
2219 "diagnostic should name the op and GI-FPU-001: {reason:?}"
2220 );
2221 let dset = by_name("dset");
2222 assert!(
2223 dset.unsupported
2224 .as_deref()
2225 .is_some_and(|r| r.contains("GlobalSet")),
2226 "global.set of an f64 global must flag the function, got {:?}",
2227 dset.unsupported
2228 );
2229 assert!(
2230 by_name("iget").unsupported.is_none(),
2231 "an i32 global access must NOT be flagged: {:?}",
2232 by_name("iget").unsupported
2233 );
2234 }
2235 }
2236
2237 #[test]
2238 fn test_369_imported_float_global_shifts_index_space() {
2239 let wat = r#"
2243 (module
2244 (import "env" "fg" (global f64))
2245 (global $ig i32 (i32.const 3))
2246 (func (export "fget") (result f64) global.get 0)
2247 (func (export "iget") (result i32) global.get 1))
2248 "#;
2249 let wasm = wat::parse_str(wat).expect("parse");
2250 let functions = decode_wasm_functions(&wasm).expect("decode");
2251 let by_name = |n: &str| {
2252 functions
2253 .iter()
2254 .find(|f| f.export_name.as_deref() == Some(n))
2255 .unwrap()
2256 };
2257 assert!(
2258 by_name("fget")
2259 .unsupported
2260 .as_deref()
2261 .is_some_and(|r| r.contains("GI-FPU-001")),
2262 "imported f64 global access must flag: {:?}",
2263 by_name("fget").unsupported
2264 );
2265 assert!(
2266 by_name("iget").unsupported.is_none(),
2267 "defined i32 global at shifted index 1 must NOT flag: {:?}",
2268 by_name("iget").unsupported
2269 );
2270 }
2271
2272 #[test]
2273 fn test_decode_simd_multiple_ops() {
2274 let wat = r#"
2275 (module
2276 (func (export "simd_ops") (param v128 v128 v128) (result v128)
2277 ;; (a + b) * c
2278 local.get 0
2279 local.get 1
2280 i32x4.add
2281 local.get 2
2282 i32x4.mul
2283 )
2284 )
2285 "#;
2286
2287 let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
2288 let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
2289
2290 assert_eq!(functions.len(), 1);
2291 let ops = &functions[0].ops;
2292 assert!(ops.contains(&WasmOp::I32x4Add));
2293 assert!(ops.contains(&WasmOp::I32x4Mul));
2294 }
2295
2296 #[test]
2302 fn test_decode_records_aligned_increasing_op_offsets_dbg001() {
2303 let wat = r#"
2304 (module
2305 (func (export "f") (param i32 i32) (result i32)
2306 local.get 0
2307 local.get 1
2308 i32.add
2309 i32.const 7
2310 i32.mul))
2311 "#;
2312 let wasm = wat::parse_str(wat).expect("parse WAT");
2313 let functions = decode_wasm_functions(&wasm).expect("decode");
2314 let f = &functions[0];
2315
2316 assert_eq!(
2318 f.op_offsets.len(),
2319 f.ops.len(),
2320 "op_offsets must be parallel to ops"
2321 );
2322 assert!(!f.op_offsets.is_empty());
2323
2324 assert!(
2327 f.op_offsets.windows(2).all(|w| w[1] > w[0]),
2328 "wasm byte offsets must strictly increase: {:?}",
2329 f.op_offsets
2330 );
2331 assert!(
2332 f.op_offsets[0] >= 8,
2333 "module-relative offset is past the 8-byte wasm header"
2334 );
2335 }
2336
2337 #[test]
2340 fn test_decode_captures_global_initializer() {
2341 let wat = r#"
2342 (module
2343 (memory 2)
2344 (global $__stack_pointer (mut i32) (i32.const 65536))
2345 (global $immutable_const i32 (i32.const 7))
2346 (func (export "f") (result i32) global.get 0)
2347 )
2348 "#;
2349 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2350 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2351
2352 assert_eq!(module.globals.len(), 2, "both globals captured");
2353 let sp = &module.globals[0];
2354 assert_eq!(sp.index, 0);
2355 assert_eq!(
2356 sp.init,
2357 Some(GlobalInit::I32(65536)),
2358 "stack-pointer init captured"
2359 );
2360 assert!(sp.mutable, "stack pointer is mutable");
2361 let c = &module.globals[1];
2362 assert_eq!(c.init, Some(GlobalInit::I32(7)));
2363 assert!(!c.mutable, "second global is immutable");
2364 assert_eq!(sp.slot_bytes, 4, "i32 global occupies one 4-byte slot");
2365 assert_eq!(c.slot_bytes, 4);
2366 }
2367
2368 #[test]
2372 fn test_decode_records_global_slot_widths_643() {
2373 let wat = r#"
2374 (module
2375 (global $c (mut i64) (i64.const 0))
2376 (global $k (mut i32) (i32.const 0))
2377 (global $f (mut f64) (f64.const 0))
2378 (func (export "f") (result i32) global.get 1)
2379 )
2380 "#;
2381 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2382 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2383
2384 assert_eq!(module.globals.len(), 3);
2385 assert_eq!(module.globals[0].slot_bytes, 8, "i64 global is 8 bytes");
2386 assert_eq!(module.globals[1].slot_bytes, 4, "i32 global is 4 bytes");
2387 assert_eq!(module.globals[2].slot_bytes, 8, "f64 global is 8 bytes");
2388 }
2389
2390 #[test]
2395 fn test_decode_captures_i64_global_initializer_649() {
2396 let wat = r#"
2397 (module
2398 (global $g (mut i64) (i64.const 0x123456789ABCDEF0))
2399 (global $n (mut i64) (i64.const -1))
2400 (global $f (mut f64) (f64.const 1.5))
2401 (global $h (mut f32) (f32.const 2.5))
2402 (func (export "f") (result i32) i32.const 0)
2403 )
2404 "#;
2405 let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
2406 let module = decode_wasm_module(&wasm).expect("Failed to decode");
2407
2408 assert_eq!(module.globals.len(), 4);
2409 assert_eq!(
2410 module.globals[0].init,
2411 Some(GlobalInit::I64(0x123456789ABCDEF0u64 as i64)),
2412 "nonzero i64 init captured with both words"
2413 );
2414 assert_eq!(module.globals[1].init, Some(GlobalInit::I64(-1)));
2415 assert_eq!(
2416 module.globals[2].init, None,
2417 "f64 init is NOT captured (GI-FPU-001 loud-skip lane)"
2418 );
2419 assert_eq!(
2420 module.globals[3].init, None,
2421 "f32 init is NOT captured (GI-FPU-001 loud-skip lane)"
2422 );
2423 }
2424
2425 #[test]
2431 fn test_decode_records_block_arity_side_table_509() {
2432 let wat = r#"
2433 (module
2434 (func (export "f") (param i32) (result i32)
2435 (block (result i32)
2436 (block (nop))
2437 (local.get 0)
2438 (if (result i32)
2439 (then (i32.const 1))
2440 (else (i32.const 2)))))
2441 (func (export "g") (result i32)
2442 (block (result i32 i32)
2443 (i32.const 1) (i32.const 2))
2444 i32.add)
2445 (func (export "h") (param i32) (result i32)
2446 (local.get 0)
2447 (loop (param i32) (result i32))))
2448 "#;
2449 let wasm = wat::parse_str(wat).expect("parse WAT");
2450
2451 for functions in [
2453 decode_wasm_functions(&wasm).expect("decode"),
2454 decode_wasm_module(&wasm).expect("decode").functions,
2455 ] {
2456 assert_eq!(
2458 functions[0].block_arity,
2459 vec![(0, 1), (0, 0), (0, 1)],
2460 "f: ValType/Empty/ValType blocktypes"
2461 );
2462 assert_eq!(
2464 functions[1].block_arity,
2465 vec![(0, 2)],
2466 "g: functype blocktype result count from the type section"
2467 );
2468 assert_eq!(
2471 functions[2].block_arity,
2472 vec![(1, 1)],
2473 "h: loop params captured"
2474 );
2475 }
2476 }
2477
2478 #[test]
2482 fn test_call_indirect_guards_closed_world_verified_642() {
2483 let wat = r#"
2485 (module
2486 (type $bin (func (param i32 i32) (result i32)))
2487 (table 3 funcref)
2488 (elem (i32.const 0) $add $sub $mul)
2489 (func $add (param i32 i32) (result i32)
2490 (i32.add (local.get 0) (local.get 1)))
2491 (func $sub (param i32 i32) (result i32)
2492 (i32.sub (local.get 0) (local.get 1)))
2493 (func $mul (param i32 i32) (result i32)
2494 (i32.mul (local.get 0) (local.get 1)))
2495 (func (export "f") (param i32 i32) (result i32)
2496 (call_indirect (type $bin)
2497 (local.get 0) (i32.const 10) (local.get 1)))
2498 )
2499 "#;
2500 let wasm = wat::parse_str(wat).expect("parse");
2501 let module = decode_wasm_module(&wasm).expect("decode");
2502
2503 assert_eq!(module.table_size, Some(3), "table section min size");
2504 assert_eq!(module.table_sizes, vec![Some(3)], "#650 per-table sizes");
2505 assert_eq!(
2506 module.elem_segments,
2507 vec![ElemSegmentInfo {
2508 table_index: 0,
2509 offset: Some(0),
2510 funcs: Some(vec![0, 1, 2]),
2511 }]
2512 );
2513 assert_eq!(module.func_type_indices.len(), 4);
2517
2518 let guards = module.call_indirect_guards();
2519 assert_eq!(guards.tables.len(), 1);
2520 assert_eq!(guards.tables[0].table_size, Some(3));
2521 assert_eq!(
2522 guards.tables[0].base_byte_offset,
2523 Some(0),
2524 "#650: a single-table module keeps table 0 at R11 offset 0 by construction"
2525 );
2526 assert_eq!(
2529 guards.tables[0].type_reject.first(),
2530 Some(&None),
2531 "closed-world type check must verify the homogeneous table: {:?}",
2532 guards.tables[0].type_reject
2533 );
2534 assert!(
2535 !guards.tables[0].has_null_slots,
2536 "#664: a fully-initialized table must NOT request the runtime \
2537 null check (dispatch bytes stay identical by construction)"
2538 );
2539 }
2540
2541 #[test]
2545 fn test_call_indirect_guards_heterogeneous_table_rejects_642() {
2546 let wat = r#"
2547 (module
2548 (type $bin (func (param i32 i32) (result i32)))
2549 (type $un (func (param i32) (result i32)))
2550 (table 2 funcref)
2551 (elem (i32.const 0) $add $neg)
2552 (func $add (type $bin)
2553 (i32.add (local.get 0) (local.get 1)))
2554 (func $neg (type $un)
2555 (i32.sub (i32.const 0) (local.get 0)))
2556 (func (export "f") (param i32 i32) (result i32)
2557 (call_indirect (type $bin)
2558 (local.get 0) (i32.const 10) (local.get 1)))
2559 )
2560 "#;
2561 let wasm = wat::parse_str(wat).expect("parse");
2562 let module = decode_wasm_module(&wasm).expect("decode");
2563 let guards = module.call_indirect_guards();
2564 assert_eq!(guards.tables[0].table_size, Some(2));
2565 assert!(
2568 guards.tables[0].type_reject[0].is_some() && guards.tables[0].type_reject[1].is_some(),
2569 "heterogeneous table must reject every expected type: {:?}",
2570 guards.tables[0].type_reject
2571 );
2572 }
2573
2574 #[test]
2580 fn test_call_indirect_guards_null_slot_verifies_with_flag_664() {
2581 let wat = r#"
2582 (module
2583 (type $s (func (result i32)))
2584 (table 3 funcref)
2585 (elem (i32.const 0) $f0 $f1)
2586 (func $f0 (result i32) (i32.const 10))
2587 (func $f1 (result i32) (i32.const 11))
2588 (func (export "run") (param i32) (result i32)
2589 (call_indirect (type $s) (local.get 0)))
2590 )
2591 "#;
2592 let wasm = wat::parse_str(wat).expect("parse");
2593 let module = decode_wasm_module(&wasm).expect("decode");
2594 let guards = module.call_indirect_guards();
2595 assert_eq!(guards.tables[0].table_size, Some(3));
2596 assert_eq!(
2597 guards.tables[0].type_reject.first(),
2598 Some(&None),
2599 "initialized slots are homogeneous in $s — the verdict must \
2600 verify despite the null slot (#664): {:?}",
2601 guards.tables[0].type_reject
2602 );
2603 assert!(
2604 guards.tables[0].has_null_slots,
2605 "slot 2 is uninitialized — the lowering must emit the runtime \
2606 null check (#664)"
2607 );
2608 }
2609
2610 #[test]
2615 fn test_call_indirect_guards_sparse_table_664() {
2616 let wat = r#"
2617 (module
2618 (type $t (func (param i32) (result i32)))
2619 (table 4 4 funcref)
2620 (func $f1 (type $t) (i32.add (local.get 0) (i32.const 100)))
2621 (func $f3 (type $t) (i32.sub (i32.const 1000) (local.get 0)))
2622 (elem (i32.const 1) $f1)
2623 (elem (i32.const 3) $f3)
2624 (func (export "via") (param i32 i32) (result i32)
2625 (call_indirect (type $t) (local.get 0) (local.get 1)))
2626 )
2627 "#;
2628 let wasm = wat::parse_str(wat).expect("parse");
2629 let module = decode_wasm_module(&wasm).expect("decode");
2630 let guards = module.call_indirect_guards();
2631 assert_eq!(guards.tables[0].table_size, Some(4));
2632 assert_eq!(
2633 guards.tables[0].type_reject.first(),
2634 Some(&None),
2635 "slots 1,3 are homogeneous in $t — verified: {:?}",
2636 guards.tables[0].type_reject
2637 );
2638 assert!(guards.tables[0].has_null_slots, "slots 0,2 are null");
2639
2640 let wat = r#"
2642 (module
2643 (type $t (func (param i32) (result i32)))
2644 (type $u (func (param i32 i32) (result i32)))
2645 (table 4 4 funcref)
2646 (func $f1 (type $t) (local.get 0))
2647 (func $f3 (type $u) (i32.add (local.get 0) (local.get 1)))
2648 (elem (i32.const 1) $f1)
2649 (elem (i32.const 3) $f3)
2650 (func (export "via") (param i32 i32) (result i32)
2651 (call_indirect (type $t) (local.get 0) (local.get 1)))
2652 )
2653 "#;
2654 let wasm = wat::parse_str(wat).expect("parse");
2655 let module = decode_wasm_module(&wasm).expect("decode");
2656 let guards = module.call_indirect_guards();
2657 assert!(
2658 guards.tables[0].type_reject[0].is_some() && guards.tables[0].type_reject[1].is_some(),
2659 "a heterogeneous sparse table must still reject every type: {:?}",
2660 guards.tables[0].type_reject
2661 );
2662 }
2663
2664 #[test]
2667 fn test_call_indirect_guards_no_table_642() {
2668 let wat = r#"
2669 (module
2670 (func (export "f") (param i32) (result i32) (local.get 0))
2671 )
2672 "#;
2673 let wasm = wat::parse_str(wat).expect("parse");
2674 let module = decode_wasm_module(&wasm).expect("decode");
2675 assert_eq!(module.table_size, None);
2676 assert!(module.table_sizes.is_empty(), "#650: no tables declared");
2677 let guards = module.call_indirect_guards();
2678 assert!(
2679 guards.tables.is_empty(),
2680 "no table → no guard entry → every call_indirect declines"
2681 );
2682 }
2683
2684 #[test]
2687 fn test_call_indirect_guards_duplicate_types_verified_642() {
2688 let wat = r#"
2689 (module
2690 (type $a (func (result i32)))
2691 (type $b (func (result i32)))
2692 (table 1 funcref)
2693 (elem (i32.const 0) $f)
2694 (func $f (type $a) (i32.const 7))
2695 (func (export "run") (param i32) (result i32)
2696 (call_indirect (type $b) (local.get 0)))
2697 )
2698 "#;
2699 let wasm = wat::parse_str(wat).expect("parse");
2700 let module = decode_wasm_module(&wasm).expect("decode");
2701 let guards = module.call_indirect_guards();
2702 assert_eq!(
2706 &guards.tables[0].type_reject[0..2],
2707 &[None, None],
2708 "structural signature comparison must accept duplicate types: {:?}",
2709 guards.tables[0].type_reject
2710 );
2711 assert!(
2712 guards.tables[0].type_reject[2].is_some(),
2713 "the structurally-different third type must still be rejected"
2714 );
2715 }
2716
2717 #[test]
2723 fn test_call_indirect_guards_multi_table_650() {
2724 let wat = r#"
2727 (module
2728 (type $t (func (param i32) (result i32)))
2729 (type $u (func (param i32 i32) (result i32)))
2730 (table $t0 3 3 funcref)
2731 (table $t1 2 2 funcref)
2732 (func $a0 (type $t) (i32.add (local.get 0) (i32.const 100)))
2733 (func $a1 (type $t) (i32.add (local.get 0) (i32.const 200)))
2734 (func $a2 (type $t) (i32.add (local.get 0) (i32.const 300)))
2735 (func $b0 (type $u) (i32.add (local.get 0) (local.get 1)))
2736 (func $b1 (type $u) (i32.sub (local.get 0) (local.get 1)))
2737 (elem (table $t0) (i32.const 0) func $a0 $a1 $a2)
2738 (elem (table $t1) (i32.const 0) func $b0 $b1)
2739 (func (export "f") (param i32 i32) (result i32)
2740 (call_indirect $t1 (type $u)
2741 (local.get 0) (i32.const 10) (local.get 1)))
2742 )
2743 "#;
2744 let wasm = wat::parse_str(wat).expect("parse");
2745 let module = decode_wasm_module(&wasm).expect("decode");
2746 assert_eq!(module.table_sizes, vec![Some(3), Some(2)]);
2747 assert_eq!(module.table_size, Some(3), "compat accessor = table 0");
2748 assert_eq!(
2749 module.elem_segments[0].table_index, 0,
2750 "segment 0 targets table 0"
2751 );
2752 assert_eq!(
2753 module.elem_segments[1],
2754 ElemSegmentInfo {
2755 table_index: 1,
2756 offset: Some(0),
2757 funcs: Some(vec![3, 4]),
2758 },
2759 "segment 1 is statically attributed to table 1 (#650)"
2760 );
2761
2762 let guards = module.call_indirect_guards();
2763 assert_eq!(guards.tables.len(), 2);
2764 assert_eq!(guards.tables[0].table_size, Some(3));
2765 assert_eq!(guards.tables[0].base_byte_offset, Some(0));
2766 assert_eq!(guards.tables[1].table_size, Some(2));
2767 assert_eq!(
2768 guards.tables[1].base_byte_offset,
2769 Some(12),
2770 "table 1 base = size(table 0) * 4 within the contiguous R11 region"
2771 );
2772 assert_eq!(guards.tables[0].type_reject[0], None, "table 0 vs $t");
2775 assert!(guards.tables[0].type_reject[1].is_some(), "table 0 vs $u");
2776 assert!(guards.tables[1].type_reject[0].is_some(), "table 1 vs $t");
2777 assert_eq!(guards.tables[1].type_reject[1], None, "table 1 vs $u");
2778 }
2779
2780 #[test]
2784 fn test_call_indirect_guards_unknown_size_poisons_later_bases_650() {
2785 let wat = r#"
2786 (module
2787 (type $t (func (result i32)))
2788 (import "env" "tbl" (table 4 funcref))
2789 (table $d 1 1 funcref)
2790 (func $f (type $t) (i32.const 7))
2791 (elem (table $d) (i32.const 0) func $f)
2792 (func (export "run") (param i32) (result i32)
2793 (call_indirect $d (type $t) (local.get 0)))
2794 )
2795 "#;
2796 let wasm = wat::parse_str(wat).expect("parse");
2797 let module = decode_wasm_module(&wasm).expect("decode");
2798 assert_eq!(
2799 module.table_sizes,
2800 vec![None, Some(1)],
2801 "growable import (no max) has no sound compile-time size"
2802 );
2803 let guards = module.call_indirect_guards();
2804 assert_eq!(guards.tables[0].base_byte_offset, Some(0));
2805 assert!(
2806 guards.tables[0].type_reject.iter().all(|r| r.is_some()),
2807 "unknown-size table rejects every type"
2808 );
2809 assert_eq!(
2810 guards.tables[1].base_byte_offset, None,
2811 "a later table's base is not a compile-time constant when a \
2812 preceding table's size is unknown (#650)"
2813 );
2814 assert_eq!(guards.tables[1].table_size, Some(1));
2815 }
2816}