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