Skip to main content

synth_core/
wasm_decoder.rs

1//! WASM Binary Decoder - Converts wasmparser operators to WasmOp sequences
2//!
3//! This module bridges the gap between parsed WASM binaries and any backend.
4//! It extracts function bodies and converts wasmparser operators to our internal WasmOp format.
5
6use crate::wasm_op::WasmOp;
7use anyhow::{Context, Result};
8use std::collections::HashMap;
9use wasmparser::{ExternalKind, Parser, Payload};
10
11/// Kind of a WASM import
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ImportKind {
14    /// Imported function with type index
15    Function(u32),
16    /// Imported memory
17    Memory,
18    /// Imported table
19    Table,
20    /// Imported global
21    Global,
22}
23
24/// A WASM import entry with full metadata
25#[derive(Debug, Clone)]
26pub struct ImportEntry {
27    /// Module name (e.g., "wasi:cli/stdout" or "env")
28    pub module: String,
29    /// Field name (e.g., "write" or "memory")
30    pub name: String,
31    /// Import kind and associated data
32    pub kind: ImportKind,
33    /// Index of this import within its kind (e.g., function import index)
34    pub index: u32,
35}
36
37/// WASM linear memory specification
38#[derive(Debug, Clone)]
39pub struct WasmMemory {
40    /// Memory index
41    pub index: u32,
42    /// Initial size in pages (64KB each)
43    pub initial_pages: u32,
44    /// Maximum size in pages (if specified)
45    pub max_pages: Option<u32>,
46    /// Whether memory is shared (requires threads proposal)
47    pub shared: bool,
48}
49
50impl WasmMemory {
51    /// Get initial size in bytes
52    pub fn initial_bytes(&self) -> u32 {
53        self.initial_pages * 65536
54    }
55
56    /// Get maximum size in bytes (or initial if not specified)
57    pub fn max_bytes(&self) -> u32 {
58        self.max_pages.unwrap_or(self.initial_pages) * 65536
59    }
60}
61
62/// Decoded WASM module with functions and memory
63#[derive(Debug, Clone)]
64pub struct DecodedModule {
65    /// Decoded functions
66    pub functions: Vec<FunctionOps>,
67    /// Linear memories
68    pub memories: Vec<WasmMemory>,
69    /// Data segments (offset, data) for memory initialization
70    pub data_segments: Vec<(u32, Vec<u8>)>,
71    /// Import entries (module name, field name, kind)
72    pub imports: Vec<ImportEntry>,
73    /// Number of imported functions (for distinguishing import calls from local calls)
74    pub num_imported_funcs: u32,
75    /// AAPCS integer-argument count per function, indexed by the *full* WASM
76    /// function index (imported functions first, then locally-defined ones).
77    /// Used by the backend to marshal call arguments into R0–R3 (issue #195).
78    /// Counts every parameter as one slot (i64/f64 over-counted — see the
79    /// backend's `set_func_arg_counts` scope note).
80    pub func_arg_counts: Vec<u32>,
81    /// AAPCS integer-argument count per *function type*, indexed by type index.
82    /// Used by `call_indirect`, whose callee arg count comes from the static
83    /// type index (issue #195).
84    pub type_arg_counts: Vec<u32>,
85}
86
87/// Decode a WASM binary and extract functions, memory, and data segments
88pub fn decode_wasm_module(wasm_bytes: &[u8]) -> Result<DecodedModule> {
89    let mut functions = Vec::new();
90    let mut memories = Vec::new();
91    let mut data_segments = Vec::new();
92    let mut imports = Vec::new();
93    let mut func_index = 0u32;
94    let mut num_imported_funcs = 0u32;
95    let mut export_names: HashMap<u32, String> = HashMap::new();
96    // #195: per-type AAPCS arg count (indexed by type index) and per-function
97    // arg count (indexed by full function index: imports first, then locals).
98    let mut type_arg_counts: Vec<u32> = Vec::new();
99    let mut func_arg_counts: Vec<u32> = Vec::new();
100
101    for payload in Parser::new(0).parse_all(wasm_bytes) {
102        let payload = payload.context("Failed to parse WASM payload")?;
103
104        match payload {
105            Payload::TypeSection(reader) => {
106                // Record the parameter count of each function type so calls can
107                // marshal the right number of arguments (issue #195).
108                for rec_group in reader {
109                    let rec_group = rec_group.context("Failed to parse type")?;
110                    for sub_ty in rec_group.types() {
111                        let count = match &sub_ty.composite_type.inner {
112                            wasmparser::CompositeInnerType::Func(func_ty) => {
113                                func_ty.params().len() as u32
114                            }
115                            _ => 0,
116                        };
117                        type_arg_counts.push(count);
118                    }
119                }
120            }
121            Payload::ImportSection(reader) => {
122                for import in reader {
123                    let import = import.context("Failed to parse import")?;
124                    let (kind, idx) = match import.ty {
125                        wasmparser::TypeRef::Func(type_idx) => {
126                            let idx = num_imported_funcs;
127                            num_imported_funcs += 1;
128                            // Record the imported function's arg count at its
129                            // full function index (imports come first).
130                            func_arg_counts
131                                .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
132                            (ImportKind::Function(type_idx), idx)
133                        }
134                        wasmparser::TypeRef::Memory(_) => (ImportKind::Memory, 0),
135                        wasmparser::TypeRef::Table(_) => (ImportKind::Table, 0),
136                        wasmparser::TypeRef::Global(_) => (ImportKind::Global, 0),
137                        _ => continue,
138                    };
139                    imports.push(ImportEntry {
140                        module: import.module.to_string(),
141                        name: import.name.to_string(),
142                        kind,
143                        index: idx,
144                    });
145                }
146            }
147            Payload::FunctionSection(reader) => {
148                // Each entry gives the type index of a locally-defined function,
149                // in order. Their full function indices follow the imports, so
150                // appending to `func_arg_counts` keeps it indexed by full index
151                // (issue #195).
152                for ty in reader {
153                    let type_idx = ty.context("Failed to parse function type index")?;
154                    func_arg_counts
155                        .push(type_arg_counts.get(type_idx as usize).copied().unwrap_or(0));
156                }
157            }
158            Payload::MemorySection(reader) => {
159                for (idx, memory) in reader.into_iter().enumerate() {
160                    let mem = memory.context("Failed to parse memory")?;
161                    memories.push(WasmMemory {
162                        index: idx as u32,
163                        initial_pages: mem.initial as u32,
164                        max_pages: mem.maximum.map(|m| m as u32),
165                        shared: mem.shared,
166                    });
167                }
168            }
169            Payload::DataSection(reader) => {
170                for data in reader {
171                    let data = data.context("Failed to parse data segment")?;
172                    if let wasmparser::DataKind::Active {
173                        memory_index: 0,
174                        offset_expr,
175                    } = data.kind
176                    {
177                        let mut ops = offset_expr.get_operators_reader();
178                        if let Ok(wasmparser::Operator::I32Const { value }) = ops.read() {
179                            data_segments.push((value as u32, data.data.to_vec()));
180                        }
181                    }
182                }
183            }
184            Payload::ExportSection(exports) => {
185                for export in exports {
186                    let export = export.context("Failed to parse export")?;
187                    if export.kind == ExternalKind::Func {
188                        export_names.insert(export.index, export.name.to_string());
189                    }
190                }
191            }
192            Payload::CodeSectionEntry(body) => {
193                let ops = decode_function_body(&body)?;
194                let actual_index = num_imported_funcs + func_index;
195                let export_name = export_names.get(&actual_index).cloned();
196
197                functions.push(FunctionOps {
198                    index: actual_index,
199                    export_name,
200                    ops,
201                });
202                func_index += 1;
203            }
204            _ => {}
205        }
206    }
207
208    Ok(DecodedModule {
209        functions,
210        memories,
211        data_segments,
212        imports,
213        num_imported_funcs,
214        func_arg_counts,
215        type_arg_counts,
216    })
217}
218
219/// Decode a WASM binary and extract all function bodies as WasmOp sequences
220pub fn decode_wasm_functions(wasm_bytes: &[u8]) -> Result<Vec<FunctionOps>> {
221    let mut functions = Vec::new();
222    let mut func_index = 0u32;
223    let mut num_imported_funcs = 0u32;
224    let mut export_names: HashMap<u32, String> = HashMap::new();
225
226    for payload in Parser::new(0).parse_all(wasm_bytes) {
227        let payload = payload.context("Failed to parse WASM payload")?;
228
229        match payload {
230            Payload::ImportSection(imports) => {
231                for import in imports {
232                    let import = import.context("Failed to parse import")?;
233                    if matches!(import.ty, wasmparser::TypeRef::Func(_)) {
234                        num_imported_funcs += 1;
235                    }
236                }
237            }
238            Payload::ExportSection(exports) => {
239                for export in exports {
240                    let export = export.context("Failed to parse export")?;
241                    if export.kind == ExternalKind::Func {
242                        export_names.insert(export.index, export.name.to_string());
243                    }
244                }
245            }
246            Payload::CodeSectionEntry(body) => {
247                let ops = decode_function_body(&body)?;
248                let actual_index = num_imported_funcs + func_index;
249                let export_name = export_names.get(&actual_index).cloned();
250
251                functions.push(FunctionOps {
252                    index: actual_index,
253                    export_name,
254                    ops,
255                });
256                func_index += 1;
257            }
258            _ => {}
259        }
260    }
261
262    Ok(functions)
263}
264
265/// Decoded function with its WasmOp sequence
266#[derive(Debug, Clone)]
267pub struct FunctionOps {
268    /// Function index in the module (includes imported functions)
269    pub index: u32,
270    /// Export name if this function is exported
271    pub export_name: Option<String>,
272    /// The WASM operations in this function body
273    pub ops: Vec<WasmOp>,
274}
275
276/// Decode a single function body to WasmOp sequence
277fn decode_function_body(body: &wasmparser::FunctionBody) -> Result<Vec<WasmOp>> {
278    let mut ops = Vec::new();
279
280    let ops_reader = body.get_operators_reader()?;
281    for op_result in ops_reader {
282        let op = op_result.context("Failed to read operator")?;
283
284        if let Some(wasm_op) = convert_operator(&op) {
285            ops.push(wasm_op);
286        }
287    }
288
289    Ok(ops)
290}
291
292/// Convert a wasmparser Operator to our WasmOp enum
293fn convert_operator(op: &wasmparser::Operator) -> Option<WasmOp> {
294    use wasmparser::Operator::*;
295
296    match op {
297        // Constants
298        I32Const { value } => Some(WasmOp::I32Const(*value)),
299
300        // i32 Arithmetic
301        I32Add => Some(WasmOp::I32Add),
302        I32Sub => Some(WasmOp::I32Sub),
303        I32Mul => Some(WasmOp::I32Mul),
304        I32DivS => Some(WasmOp::I32DivS),
305        I32DivU => Some(WasmOp::I32DivU),
306        I32RemS => Some(WasmOp::I32RemS),
307        I32RemU => Some(WasmOp::I32RemU),
308
309        // i64 Constants
310        I64Const { value } => Some(WasmOp::I64Const(*value)),
311
312        // i64 Arithmetic
313        I64Add => Some(WasmOp::I64Add),
314        I64Sub => Some(WasmOp::I64Sub),
315        I64Mul => Some(WasmOp::I64Mul),
316        I64DivS => Some(WasmOp::I64DivS),
317        I64DivU => Some(WasmOp::I64DivU),
318        I64RemS => Some(WasmOp::I64RemS),
319        I64RemU => Some(WasmOp::I64RemU),
320
321        // i64 Bitwise
322        I64And => Some(WasmOp::I64And),
323        I64Or => Some(WasmOp::I64Or),
324        I64Xor => Some(WasmOp::I64Xor),
325        I64Shl => Some(WasmOp::I64Shl),
326        I64ShrS => Some(WasmOp::I64ShrS),
327        I64ShrU => Some(WasmOp::I64ShrU),
328        I64Rotl => Some(WasmOp::I64Rotl),
329        I64Rotr => Some(WasmOp::I64Rotr),
330        I64Clz => Some(WasmOp::I64Clz),
331        I64Ctz => Some(WasmOp::I64Ctz),
332        I64Popcnt => Some(WasmOp::I64Popcnt),
333        I64Extend8S => Some(WasmOp::I64Extend8S),
334        I64Extend16S => Some(WasmOp::I64Extend16S),
335        I64Extend32S => Some(WasmOp::I64Extend32S),
336        // i32<->i64 width conversions. Previously UNMAPPED → silently dropped,
337        // which left an i32 value as a 64-bit operand with a garbage high half
338        // (harmless when a following `i64.shl 32` discards it, but a latent
339        // miscompile for extend-then-arithmetic, and it breaks width-correct
340        // register allocation). (#204)
341        I64ExtendI32U => Some(WasmOp::I64ExtendI32U),
342        I64ExtendI32S => Some(WasmOp::I64ExtendI32S),
343        I32WrapI64 => Some(WasmOp::I32WrapI64),
344
345        // i64 Comparison
346        I64Eqz => Some(WasmOp::I64Eqz),
347        I64Eq => Some(WasmOp::I64Eq),
348        I64Ne => Some(WasmOp::I64Ne),
349        I64LtS => Some(WasmOp::I64LtS),
350        I64LtU => Some(WasmOp::I64LtU),
351        I64LeS => Some(WasmOp::I64LeS),
352        I64LeU => Some(WasmOp::I64LeU),
353        I64GtS => Some(WasmOp::I64GtS),
354        I64GtU => Some(WasmOp::I64GtU),
355        I64GeS => Some(WasmOp::I64GeS),
356        I64GeU => Some(WasmOp::I64GeU),
357
358        // Bitwise
359        I32And => Some(WasmOp::I32And),
360        I32Or => Some(WasmOp::I32Or),
361        I32Xor => Some(WasmOp::I32Xor),
362        I32Shl => Some(WasmOp::I32Shl),
363        I32ShrS => Some(WasmOp::I32ShrS),
364        I32ShrU => Some(WasmOp::I32ShrU),
365        I32Rotl => Some(WasmOp::I32Rotl),
366        I32Rotr => Some(WasmOp::I32Rotr),
367        I32Clz => Some(WasmOp::I32Clz),
368        I32Ctz => Some(WasmOp::I32Ctz),
369        I32Popcnt => Some(WasmOp::I32Popcnt),
370        I32Extend8S => Some(WasmOp::I32Extend8S),
371        I32Extend16S => Some(WasmOp::I32Extend16S),
372
373        // Comparison
374        I32Eqz => Some(WasmOp::I32Eqz),
375        I32Eq => Some(WasmOp::I32Eq),
376        I32Ne => Some(WasmOp::I32Ne),
377        I32LtS => Some(WasmOp::I32LtS),
378        I32LtU => Some(WasmOp::I32LtU),
379        I32LeS => Some(WasmOp::I32LeS),
380        I32LeU => Some(WasmOp::I32LeU),
381        I32GtS => Some(WasmOp::I32GtS),
382        I32GtU => Some(WasmOp::I32GtU),
383        I32GeS => Some(WasmOp::I32GeS),
384        I32GeU => Some(WasmOp::I32GeU),
385
386        // Memory
387        I32Load { memarg } => Some(WasmOp::I32Load {
388            offset: memarg.offset as u32,
389            align: memarg.align as u32,
390        }),
391        I32Store { memarg } => Some(WasmOp::I32Store {
392            offset: memarg.offset as u32,
393            align: memarg.align as u32,
394        }),
395
396        // Sub-word loads (i32)
397        I32Load8S { memarg } => Some(WasmOp::I32Load8S {
398            offset: memarg.offset as u32,
399            align: memarg.align as u32,
400        }),
401        I32Load8U { memarg } => Some(WasmOp::I32Load8U {
402            offset: memarg.offset as u32,
403            align: memarg.align as u32,
404        }),
405        I32Load16S { memarg } => Some(WasmOp::I32Load16S {
406            offset: memarg.offset as u32,
407            align: memarg.align as u32,
408        }),
409        I32Load16U { memarg } => Some(WasmOp::I32Load16U {
410            offset: memarg.offset as u32,
411            align: memarg.align as u32,
412        }),
413
414        // Sub-word stores (i32)
415        I32Store8 { memarg } => Some(WasmOp::I32Store8 {
416            offset: memarg.offset as u32,
417            align: memarg.align as u32,
418        }),
419        I32Store16 { memarg } => Some(WasmOp::I32Store16 {
420            offset: memarg.offset as u32,
421            align: memarg.align as u32,
422        }),
423
424        // Local/Global
425        LocalGet { local_index } => Some(WasmOp::LocalGet(*local_index)),
426        LocalSet { local_index } => Some(WasmOp::LocalSet(*local_index)),
427        LocalTee { local_index } => Some(WasmOp::LocalTee(*local_index)),
428        GlobalGet { global_index } => Some(WasmOp::GlobalGet(*global_index)),
429        GlobalSet { global_index } => Some(WasmOp::GlobalSet(*global_index)),
430
431        // Control flow
432        Block { .. } => Some(WasmOp::Block),
433        Loop { .. } => Some(WasmOp::Loop),
434        Br { relative_depth } => Some(WasmOp::Br(*relative_depth)),
435        BrIf { relative_depth } => Some(WasmOp::BrIf(*relative_depth)),
436        Return => Some(WasmOp::Return),
437        Call { function_index } => Some(WasmOp::Call(*function_index)),
438        CallIndirect {
439            type_index,
440            table_index,
441            ..
442        } => Some(WasmOp::CallIndirect {
443            type_index: *type_index,
444            table_index: *table_index,
445        }),
446
447        // End is needed for control flow pattern matching
448        End => Some(WasmOp::End),
449
450        // Nop/Unreachable - skip these
451        Nop | Unreachable => None,
452
453        // Drop is needed for br_if pattern matching
454        Drop => Some(WasmOp::Drop),
455
456        // Select
457        Select => Some(WasmOp::Select),
458
459        // If/Else - simplified handling
460        If { .. } => Some(WasmOp::If),
461        Else => Some(WasmOp::Else),
462
463        // i64 sub-word loads
464        I64Load8S { memarg } => Some(WasmOp::I64Load8S {
465            offset: memarg.offset as u32,
466            align: memarg.align as u32,
467        }),
468        I64Load8U { memarg } => Some(WasmOp::I64Load8U {
469            offset: memarg.offset as u32,
470            align: memarg.align as u32,
471        }),
472        I64Load16S { memarg } => Some(WasmOp::I64Load16S {
473            offset: memarg.offset as u32,
474            align: memarg.align as u32,
475        }),
476        I64Load16U { memarg } => Some(WasmOp::I64Load16U {
477            offset: memarg.offset as u32,
478            align: memarg.align as u32,
479        }),
480        I64Load32S { memarg } => Some(WasmOp::I64Load32S {
481            offset: memarg.offset as u32,
482            align: memarg.align as u32,
483        }),
484        I64Load32U { memarg } => Some(WasmOp::I64Load32U {
485            offset: memarg.offset as u32,
486            align: memarg.align as u32,
487        }),
488
489        // i64 sub-word stores
490        I64Store8 { memarg } => Some(WasmOp::I64Store8 {
491            offset: memarg.offset as u32,
492            align: memarg.align as u32,
493        }),
494        I64Store16 { memarg } => Some(WasmOp::I64Store16 {
495            offset: memarg.offset as u32,
496            align: memarg.align as u32,
497        }),
498        I64Store32 { memarg } => Some(WasmOp::I64Store32 {
499            offset: memarg.offset as u32,
500            align: memarg.align as u32,
501        }),
502
503        // Memory management
504        MemorySize { mem, .. } => Some(WasmOp::MemorySize(*mem)),
505        MemoryGrow { mem, .. } => Some(WasmOp::MemoryGrow(*mem)),
506
507        // ========================================================================
508        // v128 SIMD operations (WASM SIMD proposal, 0xFD prefix)
509        // ========================================================================
510        V128Const { value } => {
511            let mut bytes = [0u8; 16];
512            bytes.copy_from_slice(value.bytes());
513            Some(WasmOp::V128Const(bytes))
514        }
515        V128Load { memarg } => Some(WasmOp::V128Load {
516            offset: memarg.offset as u32,
517            align: memarg.align as u32,
518        }),
519        V128Store { memarg } => Some(WasmOp::V128Store {
520            offset: memarg.offset as u32,
521            align: memarg.align as u32,
522        }),
523
524        // v128 bitwise
525        V128And => Some(WasmOp::V128And),
526        V128Or => Some(WasmOp::V128Or),
527        V128Xor => Some(WasmOp::V128Xor),
528        V128Not => Some(WasmOp::V128Not),
529        V128AndNot => Some(WasmOp::V128AndNot),
530
531        // i8x16
532        I8x16Add => Some(WasmOp::I8x16Add),
533        I8x16Sub => Some(WasmOp::I8x16Sub),
534        I8x16Neg => Some(WasmOp::I8x16Neg),
535        I8x16Eq => Some(WasmOp::I8x16Eq),
536        I8x16Ne => Some(WasmOp::I8x16Ne),
537        I8x16LtS => Some(WasmOp::I8x16LtS),
538        I8x16LtU => Some(WasmOp::I8x16LtU),
539        I8x16GtS => Some(WasmOp::I8x16GtS),
540        I8x16GtU => Some(WasmOp::I8x16GtU),
541        I8x16LeS => Some(WasmOp::I8x16LeS),
542        I8x16LeU => Some(WasmOp::I8x16LeU),
543        I8x16GeS => Some(WasmOp::I8x16GeS),
544        I8x16GeU => Some(WasmOp::I8x16GeU),
545        I8x16Splat => Some(WasmOp::I8x16Splat),
546        I8x16ExtractLaneS { lane } => Some(WasmOp::I8x16ExtractLaneS(*lane)),
547        I8x16ExtractLaneU { lane } => Some(WasmOp::I8x16ExtractLaneU(*lane)),
548        I8x16ReplaceLane { lane } => Some(WasmOp::I8x16ReplaceLane(*lane)),
549        I8x16Shuffle { lanes } => Some(WasmOp::I8x16Shuffle(*lanes)),
550        I8x16Swizzle => Some(WasmOp::I8x16Swizzle),
551
552        // i16x8
553        I16x8Add => Some(WasmOp::I16x8Add),
554        I16x8Sub => Some(WasmOp::I16x8Sub),
555        I16x8Mul => Some(WasmOp::I16x8Mul),
556        I16x8Neg => Some(WasmOp::I16x8Neg),
557        I16x8Eq => Some(WasmOp::I16x8Eq),
558        I16x8Ne => Some(WasmOp::I16x8Ne),
559        I16x8LtS => Some(WasmOp::I16x8LtS),
560        I16x8LtU => Some(WasmOp::I16x8LtU),
561        I16x8GtS => Some(WasmOp::I16x8GtS),
562        I16x8GtU => Some(WasmOp::I16x8GtU),
563        I16x8LeS => Some(WasmOp::I16x8LeS),
564        I16x8LeU => Some(WasmOp::I16x8LeU),
565        I16x8GeS => Some(WasmOp::I16x8GeS),
566        I16x8GeU => Some(WasmOp::I16x8GeU),
567        I16x8Splat => Some(WasmOp::I16x8Splat),
568        I16x8ExtractLaneS { lane } => Some(WasmOp::I16x8ExtractLaneS(*lane)),
569        I16x8ExtractLaneU { lane } => Some(WasmOp::I16x8ExtractLaneU(*lane)),
570        I16x8ReplaceLane { lane } => Some(WasmOp::I16x8ReplaceLane(*lane)),
571
572        // i32x4
573        I32x4Add => Some(WasmOp::I32x4Add),
574        I32x4Sub => Some(WasmOp::I32x4Sub),
575        I32x4Mul => Some(WasmOp::I32x4Mul),
576        I32x4Neg => Some(WasmOp::I32x4Neg),
577        I32x4Eq => Some(WasmOp::I32x4Eq),
578        I32x4Ne => Some(WasmOp::I32x4Ne),
579        I32x4LtS => Some(WasmOp::I32x4LtS),
580        I32x4LtU => Some(WasmOp::I32x4LtU),
581        I32x4GtS => Some(WasmOp::I32x4GtS),
582        I32x4GtU => Some(WasmOp::I32x4GtU),
583        I32x4LeS => Some(WasmOp::I32x4LeS),
584        I32x4LeU => Some(WasmOp::I32x4LeU),
585        I32x4GeS => Some(WasmOp::I32x4GeS),
586        I32x4GeU => Some(WasmOp::I32x4GeU),
587        I32x4Splat => Some(WasmOp::I32x4Splat),
588        I32x4ExtractLane { lane } => Some(WasmOp::I32x4ExtractLane(*lane)),
589        I32x4ReplaceLane { lane } => Some(WasmOp::I32x4ReplaceLane(*lane)),
590
591        // i64x2
592        I64x2Add => Some(WasmOp::I64x2Add),
593        I64x2Sub => Some(WasmOp::I64x2Sub),
594        I64x2Mul => Some(WasmOp::I64x2Mul),
595        I64x2Neg => Some(WasmOp::I64x2Neg),
596        I64x2Eq => Some(WasmOp::I64x2Eq),
597        I64x2Ne => Some(WasmOp::I64x2Ne),
598        I64x2LtS => Some(WasmOp::I64x2LtS),
599        I64x2GtS => Some(WasmOp::I64x2GtS),
600        I64x2LeS => Some(WasmOp::I64x2LeS),
601        I64x2GeS => Some(WasmOp::I64x2GeS),
602        I64x2Splat => Some(WasmOp::I64x2Splat),
603        I64x2ExtractLane { lane } => Some(WasmOp::I64x2ExtractLane(*lane)),
604        I64x2ReplaceLane { lane } => Some(WasmOp::I64x2ReplaceLane(*lane)),
605
606        // f32x4
607        F32x4Add => Some(WasmOp::F32x4Add),
608        F32x4Sub => Some(WasmOp::F32x4Sub),
609        F32x4Mul => Some(WasmOp::F32x4Mul),
610        F32x4Div => Some(WasmOp::F32x4Div),
611        F32x4Abs => Some(WasmOp::F32x4Abs),
612        F32x4Neg => Some(WasmOp::F32x4Neg),
613        F32x4Sqrt => Some(WasmOp::F32x4Sqrt),
614        F32x4Eq => Some(WasmOp::F32x4Eq),
615        F32x4Ne => Some(WasmOp::F32x4Ne),
616        F32x4Lt => Some(WasmOp::F32x4Lt),
617        F32x4Le => Some(WasmOp::F32x4Le),
618        F32x4Gt => Some(WasmOp::F32x4Gt),
619        F32x4Ge => Some(WasmOp::F32x4Ge),
620        F32x4Splat => Some(WasmOp::F32x4Splat),
621        F32x4ExtractLane { lane } => Some(WasmOp::F32x4ExtractLane(*lane)),
622        F32x4ReplaceLane { lane } => Some(WasmOp::F32x4ReplaceLane(*lane)),
623
624        // Other operators not yet supported
625        _ => None,
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632
633    #[test]
634    fn test_decode_simple_add() {
635        let wat = r#"
636            (module
637                (func (export "add") (param i32 i32) (result i32)
638                    local.get 0
639                    local.get 1
640                    i32.add
641                )
642            )
643        "#;
644
645        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
646        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
647
648        assert_eq!(functions.len(), 1);
649        assert_eq!(functions[0].index, 0);
650        assert_eq!(functions[0].export_name, Some("add".to_string()));
651        assert_eq!(
652            functions[0].ops,
653            vec![
654                WasmOp::LocalGet(0),
655                WasmOp::LocalGet(1),
656                WasmOp::I32Add,
657                WasmOp::End
658            ]
659        );
660    }
661
662    /// #204 regression: `i64.extend_i32_u`, `i64.extend_i32_s` and
663    /// `i32.wrap_i64` must DECODE (they were previously unmapped → silently
664    /// dropped by `convert_operator`, leaving an i32 value as a 64-bit operand
665    /// with a garbage high half — the root cause of gale's miscompiled
666    /// `(new_count << 32)` pack). The decoder must surface all three.
667    #[test]
668    fn test_decode_i64_i32_width_conversions() {
669        let wat = r#"
670            (module
671                (func (export "conv") (param i32 i64) (result i32)
672                    local.get 0
673                    i64.extend_i32_u
674                    local.get 0
675                    i64.extend_i32_s
676                    i64.add
677                    local.get 1
678                    i64.add
679                    i32.wrap_i64
680                )
681            )
682        "#;
683        let wasm = wat::parse_str(wat).expect("parse");
684        let functions = decode_wasm_functions(&wasm).expect("decode");
685        let ops = &functions[0].ops;
686        assert!(
687            ops.contains(&WasmOp::I64ExtendI32U),
688            "i64.extend_i32_u must decode (not be dropped): {ops:?}"
689        );
690        assert!(
691            ops.contains(&WasmOp::I64ExtendI32S),
692            "i64.extend_i32_s must decode (not be dropped): {ops:?}"
693        );
694        assert!(
695            ops.contains(&WasmOp::I32WrapI64),
696            "i32.wrap_i64 must decode (not be dropped): {ops:?}"
697        );
698    }
699
700    #[test]
701    fn test_decode_arithmetic() {
702        let wat = r#"
703            (module
704                (func (export "calc") (result i32)
705                    i32.const 5
706                    i32.const 3
707                    i32.mul
708                    i32.const 2
709                    i32.add
710                )
711            )
712        "#;
713
714        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
715        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
716
717        assert_eq!(functions.len(), 1);
718        assert_eq!(functions[0].export_name, Some("calc".to_string()));
719        assert_eq!(
720            functions[0].ops,
721            vec![
722                WasmOp::I32Const(5),
723                WasmOp::I32Const(3),
724                WasmOp::I32Mul,
725                WasmOp::I32Const(2),
726                WasmOp::I32Add,
727                WasmOp::End,
728            ]
729        );
730    }
731
732    #[test]
733    fn test_decode_multi_function_module() {
734        let wat = r#"
735            (module
736                (func $helper)
737                (func (export "add") (param i32 i32) (result i32)
738                    local.get 0
739                    local.get 1
740                    i32.add
741                )
742                (func (export "sub") (param i32 i32) (result i32)
743                    local.get 0
744                    local.get 1
745                    i32.sub
746                )
747            )
748        "#;
749
750        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
751        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
752
753        assert_eq!(functions.len(), 3);
754        assert_eq!(functions[0].index, 0);
755        assert_eq!(functions[0].export_name, None);
756        assert_eq!(functions[1].index, 1);
757        assert_eq!(functions[1].export_name, Some("add".to_string()));
758        assert_eq!(functions[2].index, 2);
759        assert_eq!(functions[2].export_name, Some("sub".to_string()));
760    }
761
762    #[test]
763    fn test_decode_module_with_imports() {
764        let wat = r#"
765            (module
766                (import "env" "log" (func $log (param i32)))
767                (import "env" "memory" (memory 1))
768                (func (export "run") (param i32)
769                    local.get 0
770                    call 0
771                )
772            )
773        "#;
774
775        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
776        let module = decode_wasm_module(&wasm).expect("Failed to decode");
777
778        // Should have 2 imports (1 func, 1 memory)
779        assert_eq!(module.imports.len(), 2);
780        assert_eq!(module.num_imported_funcs, 1);
781
782        // First import is the function
783        assert_eq!(module.imports[0].module, "env");
784        assert_eq!(module.imports[0].name, "log");
785        assert!(matches!(module.imports[0].kind, ImportKind::Function(_)));
786
787        // Second import is memory
788        assert_eq!(module.imports[1].module, "env");
789        assert_eq!(module.imports[1].name, "memory");
790        assert_eq!(module.imports[1].kind, ImportKind::Memory);
791
792        // Should have 1 local function (index 1, because import is index 0)
793        assert_eq!(module.functions.len(), 1);
794        assert_eq!(module.functions[0].index, 1);
795        assert_eq!(module.functions[0].export_name, Some("run".to_string()));
796    }
797
798    #[test]
799    fn test_find_function_by_export_name() {
800        let wat = r#"
801            (module
802                (func $helper)
803                (func (export "add") (param i32 i32) (result i32)
804                    local.get 0
805                    local.get 1
806                    i32.add
807                )
808            )
809        "#;
810
811        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
812        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
813
814        let add_func = functions
815            .iter()
816            .find(|f| f.export_name.as_deref() == Some("add"))
817            .expect("Should find 'add' function");
818
819        assert_eq!(add_func.index, 1);
820        assert!(add_func.ops.contains(&WasmOp::I32Add));
821    }
822
823    #[test]
824    fn test_decode_subword_loads() {
825        let wat = r#"
826            (module
827                (memory 1)
828                (func (export "test") (param i32) (result i32)
829                    local.get 0
830                    i32.load8_u
831                )
832            )
833        "#;
834
835        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
836        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
837
838        assert_eq!(functions.len(), 1);
839        assert!(functions[0].ops.contains(&WasmOp::I32Load8U {
840            offset: 0,
841            align: 0,
842        }));
843    }
844
845    #[test]
846    fn test_decode_subword_stores() {
847        let wat = r#"
848            (module
849                (memory 1)
850                (func (export "test") (param i32 i32)
851                    local.get 0
852                    local.get 1
853                    i32.store8
854                )
855            )
856        "#;
857
858        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
859        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
860
861        assert_eq!(functions.len(), 1);
862        assert!(functions[0].ops.contains(&WasmOp::I32Store8 {
863            offset: 0,
864            align: 0,
865        }));
866    }
867
868    #[test]
869    fn test_decode_memory_size_grow() {
870        let wat = r#"
871            (module
872                (memory 1)
873                (func (export "test") (result i32)
874                    memory.size
875                )
876            )
877        "#;
878
879        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
880        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
881
882        assert_eq!(functions.len(), 1);
883        assert!(functions[0].ops.contains(&WasmOp::MemorySize(0)));
884    }
885
886    #[test]
887    fn test_decode_memory_grow() {
888        let wat = r#"
889            (module
890                (memory 1)
891                (func (export "test") (param i32) (result i32)
892                    local.get 0
893                    memory.grow
894                )
895            )
896        "#;
897
898        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
899        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
900
901        assert_eq!(functions.len(), 1);
902        assert!(functions[0].ops.contains(&WasmOp::MemoryGrow(0)));
903    }
904
905    #[test]
906    fn test_decode_i64_subword_loads() {
907        let wat = r#"
908            (module
909                (memory 1)
910                (func (export "test") (param i32) (result i64)
911                    local.get 0
912                    i64.load8_s
913                )
914            )
915        "#;
916
917        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
918        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
919
920        assert_eq!(functions.len(), 1);
921        assert!(functions[0].ops.contains(&WasmOp::I64Load8S {
922            offset: 0,
923            align: 0,
924        }));
925    }
926
927    #[test]
928    fn test_decode_all_subword_memory_ops() {
929        // Test that all sub-word operations are decoded from WAT
930        let wat = r#"
931            (module
932                (memory 1)
933                (func (export "test") (param i32)
934                    ;; i32 sub-word loads
935                    local.get 0
936                    i32.load8_s
937                    drop
938                    local.get 0
939                    i32.load8_u
940                    drop
941                    local.get 0
942                    i32.load16_s
943                    drop
944                    local.get 0
945                    i32.load16_u
946                    drop
947
948                    ;; i32 sub-word stores
949                    local.get 0
950                    i32.const 42
951                    i32.store8
952                    local.get 0
953                    i32.const 42
954                    i32.store16
955
956                    ;; i64 sub-word loads
957                    local.get 0
958                    i64.load8_s
959                    drop
960                    local.get 0
961                    i64.load8_u
962                    drop
963                    local.get 0
964                    i64.load16_s
965                    drop
966                    local.get 0
967                    i64.load16_u
968                    drop
969                    local.get 0
970                    i64.load32_s
971                    drop
972                    local.get 0
973                    i64.load32_u
974                    drop
975
976                    ;; i64 sub-word stores
977                    local.get 0
978                    i64.const 42
979                    i64.store8
980                    local.get 0
981                    i64.const 42
982                    i64.store16
983                    local.get 0
984                    i64.const 42
985                    i64.store32
986                )
987            )
988        "#;
989
990        let wasm = wat::parse_str(wat).expect("Failed to parse WAT");
991        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
992
993        assert_eq!(functions.len(), 1);
994        let ops = &functions[0].ops;
995
996        // Verify i32 sub-word ops are present
997        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8S { .. })));
998        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load8U { .. })));
999        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16S { .. })));
1000        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Load16U { .. })));
1001        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store8 { .. })));
1002        assert!(ops.iter().any(|o| matches!(o, WasmOp::I32Store16 { .. })));
1003
1004        // Verify i64 sub-word ops are present
1005        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8S { .. })));
1006        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load8U { .. })));
1007        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16S { .. })));
1008        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load16U { .. })));
1009        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32S { .. })));
1010        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Load32U { .. })));
1011        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store8 { .. })));
1012        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store16 { .. })));
1013        assert!(ops.iter().any(|o| matches!(o, WasmOp::I64Store32 { .. })));
1014    }
1015
1016    #[test]
1017    fn test_decode_simd_i32x4_add() {
1018        let wat = r#"
1019            (module
1020                (func (export "add_v128") (param v128 v128) (result v128)
1021                    local.get 0
1022                    local.get 1
1023                    i32x4.add
1024                )
1025            )
1026        "#;
1027
1028        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1029        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1030
1031        assert_eq!(functions.len(), 1);
1032        assert!(
1033            functions[0].ops.contains(&WasmOp::I32x4Add),
1034            "Should decode i32x4.add: {:?}",
1035            functions[0].ops
1036        );
1037    }
1038
1039    #[test]
1040    fn test_decode_simd_v128_const() {
1041        let wat = r#"
1042            (module
1043                (func (export "const_v128") (result v128)
1044                    v128.const i32x4 1 2 3 4
1045                )
1046            )
1047        "#;
1048
1049        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1050        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1051
1052        assert_eq!(functions.len(), 1);
1053        assert!(
1054            functions[0]
1055                .ops
1056                .iter()
1057                .any(|o| matches!(o, WasmOp::V128Const(_))),
1058            "Should decode v128.const: {:?}",
1059            functions[0].ops
1060        );
1061    }
1062
1063    #[test]
1064    fn test_decode_simd_v128_load_store() {
1065        let wat = r#"
1066            (module
1067                (memory 1)
1068                (func (export "load_store") (param i32)
1069                    local.get 0
1070                    v128.load
1071                    local.get 0
1072                    v128.store
1073                )
1074            )
1075        "#;
1076
1077        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1078        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1079
1080        assert_eq!(functions.len(), 1);
1081        let ops = &functions[0].ops;
1082        assert!(
1083            ops.iter().any(|o| matches!(o, WasmOp::V128Load { .. })),
1084            "Should decode v128.load"
1085        );
1086        assert!(
1087            ops.iter().any(|o| matches!(o, WasmOp::V128Store { .. })),
1088            "Should decode v128.store"
1089        );
1090    }
1091
1092    #[test]
1093    fn test_decode_simd_bitwise_ops() {
1094        let wat = r#"
1095            (module
1096                (func (export "bitwise") (param v128 v128) (result v128)
1097                    local.get 0
1098                    local.get 1
1099                    v128.and
1100                )
1101            )
1102        "#;
1103
1104        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1105        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1106
1107        assert_eq!(functions.len(), 1);
1108        assert!(functions[0].ops.contains(&WasmOp::V128And));
1109    }
1110
1111    #[test]
1112    fn test_decode_simd_splat() {
1113        let wat = r#"
1114            (module
1115                (func (export "splat") (param i32) (result v128)
1116                    local.get 0
1117                    i32x4.splat
1118                )
1119            )
1120        "#;
1121
1122        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1123        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1124
1125        assert_eq!(functions.len(), 1);
1126        assert!(functions[0].ops.contains(&WasmOp::I32x4Splat));
1127    }
1128
1129    #[test]
1130    fn test_decode_simd_extract_lane() {
1131        let wat = r#"
1132            (module
1133                (func (export "extract") (param v128) (result i32)
1134                    local.get 0
1135                    i32x4.extract_lane 2
1136                )
1137            )
1138        "#;
1139
1140        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1141        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1142
1143        assert_eq!(functions.len(), 1);
1144        assert!(
1145            functions[0].ops.contains(&WasmOp::I32x4ExtractLane(2)),
1146            "Should decode i32x4.extract_lane 2"
1147        );
1148    }
1149
1150    #[test]
1151    fn test_decode_simd_f32x4_arithmetic() {
1152        let wat = r#"
1153            (module
1154                (func (export "f32x4_add") (param v128 v128) (result v128)
1155                    local.get 0
1156                    local.get 1
1157                    f32x4.add
1158                )
1159            )
1160        "#;
1161
1162        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1163        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1164
1165        assert_eq!(functions.len(), 1);
1166        assert!(functions[0].ops.contains(&WasmOp::F32x4Add));
1167    }
1168
1169    #[test]
1170    fn test_decode_simd_multiple_ops() {
1171        let wat = r#"
1172            (module
1173                (func (export "simd_ops") (param v128 v128 v128) (result v128)
1174                    ;; (a + b) * c
1175                    local.get 0
1176                    local.get 1
1177                    i32x4.add
1178                    local.get 2
1179                    i32x4.mul
1180                )
1181            )
1182        "#;
1183
1184        let wasm = wat::parse_str(wat).expect("Failed to parse WAT with SIMD");
1185        let functions = decode_wasm_functions(&wasm).expect("Failed to decode");
1186
1187        assert_eq!(functions.len(), 1);
1188        let ops = &functions[0].ops;
1189        assert!(ops.contains(&WasmOp::I32x4Add));
1190        assert!(ops.contains(&WasmOp::I32x4Mul));
1191    }
1192}