Skip to main content

wasm_pvm/
test_harness.rs

1//! Test harness for wasm-pvm unit tests
2//!
3//! This module provides utilities for testing the WASM to PVM compiler.
4//! It is available when running tests (`#[cfg(test)]`) or when the `test-harness` feature is enabled (`feature = "test-harness"`).
5//!
6//! # Example
7//!
8//! ```rust
9//! use wasm_pvm::test_harness::*;
10//!
11//! #[test]
12//! fn test_simple_add() {
13//!     let wasm = wat_to_wasm(r#"
14//!         (module
15//!             (memory 1)
16//!             (func (export "main") (param $args_ptr i32) (param $args_len i32) (result i64)
17//!                 ;; Load two i32 operands from args memory
18//!                 ;; Store their sum at address 0
19//!                 (i32.store (i32.const 0)
20//!                     (i32.add
21//!                         (i32.load (local.get $args_ptr))
22//!                         (i32.load (i32.add (local.get $args_ptr) (i32.const 4)))))
23//!                 ;; Return packed ptr=0, len=4
24//!                 (i64.const 17179869184)
25//!             )
26//!         )
27//!     "#).expect("Failed to parse WAT");
28//!
29//!     let program = compile(&wasm).expect("Failed to compile");
30//!     let code = program.code();
31//!     let instructions = code.instructions();
32//!
33//!     // Assert on generated instructions
34//!     assert_has_pattern(instructions, &[
35//!         InstructionPattern::Add32 { dst: Pat::Any, src1: Pat::Any, src2: Pat::Any },
36//!     ]);
37//! }
38//! ```
39
40// Test helpers are allowed to be verbose or duplicative.
41#![allow(
42    clippy::match_same_arms,
43    clippy::must_use_candidate,
44    clippy::manual_assert,
45    clippy::missing_panics_doc,
46    clippy::uninlined_format_args,
47    clippy::implicit_hasher
48)]
49
50use std::collections::HashMap;
51
52use crate::pvm::{Instruction, Opcode};
53use crate::translate::ImportAction;
54use crate::{CompileOptions, Error, Result, SpiProgram, compile, compile_with_options};
55
56/// Parse WAT (WebAssembly Text) format to WASM binary
57pub fn wat_to_wasm(wat: &str) -> Result<Vec<u8>> {
58    wat::parse_str(wat).map_err(|e| Error::Internal(format!("WAT parse error: {e}")))
59}
60
61/// Compile WAT directly to a SPI program
62pub fn compile_wat(wat: &str) -> Result<SpiProgram> {
63    let wasm = wat_to_wasm(wat)?;
64    compile(&wasm)
65}
66
67/// Compile WAT with an import map to a SPI program
68pub fn compile_wat_with_imports(
69    wat: &str,
70    import_map: HashMap<String, ImportAction>,
71) -> Result<SpiProgram> {
72    let wasm = wat_to_wasm(wat)?;
73    compile_with_options(
74        &wasm,
75        &CompileOptions {
76            import_map: Some(import_map),
77            ..CompileOptions::default()
78        },
79    )
80}
81
82/// Compile WAT with custom compile options
83pub fn compile_wat_with_options(wat: &str, options: &CompileOptions) -> Result<SpiProgram> {
84    let wasm = wat_to_wasm(wat)?;
85    compile_with_options(&wasm, options)
86}
87
88/// Extract the instruction sequence from a SPI program
89pub fn extract_instructions(program: &SpiProgram) -> Vec<Instruction> {
90    program.code().instructions().to_vec()
91}
92
93/// Pattern matching for instruction fields
94#[derive(Debug, Clone)]
95pub enum Pat<T> {
96    /// Match any value
97    Any,
98    /// Match exact value
99    Exact(T),
100    /// Match if value satisfies predicate
101    Predicate(fn(&T) -> bool),
102}
103
104impl<T: PartialEq> Pat<T> {
105    /// Check if a value matches this pattern
106    pub fn matches(&self, value: &T) -> bool {
107        match self {
108            Pat::Any => true,
109            Pat::Exact(expected) => value == expected,
110            Pat::Predicate(pred) => pred(value),
111        }
112    }
113}
114
115/// Pattern for matching instructions in tests
116///
117/// This allows flexible matching of instruction sequences where some
118/// fields can be wildcards or predicates.
119#[derive(Debug, Clone)]
120pub enum InstructionPattern {
121    /// Match any instruction
122    Any,
123    /// Match a specific instruction kind with pattern fields
124    LoadImm {
125        reg: Pat<u8>,
126        value: Pat<i32>,
127    },
128    LoadImm64 {
129        reg: Pat<u8>,
130        value: Pat<u64>,
131    },
132    Add32 {
133        dst: Pat<u8>,
134        src1: Pat<u8>,
135        src2: Pat<u8>,
136    },
137    Sub32 {
138        dst: Pat<u8>,
139        src1: Pat<u8>,
140        src2: Pat<u8>,
141    },
142    Mul32 {
143        dst: Pat<u8>,
144        src1: Pat<u8>,
145        src2: Pat<u8>,
146    },
147    DivU32 {
148        dst: Pat<u8>,
149        src1: Pat<u8>,
150        src2: Pat<u8>,
151    },
152    DivS32 {
153        dst: Pat<u8>,
154        src1: Pat<u8>,
155        src2: Pat<u8>,
156    },
157    RemU32 {
158        dst: Pat<u8>,
159        src1: Pat<u8>,
160        src2: Pat<u8>,
161    },
162    RemS32 {
163        dst: Pat<u8>,
164        src1: Pat<u8>,
165        src2: Pat<u8>,
166    },
167    Add64 {
168        dst: Pat<u8>,
169        src1: Pat<u8>,
170        src2: Pat<u8>,
171    },
172    Sub64 {
173        dst: Pat<u8>,
174        src1: Pat<u8>,
175        src2: Pat<u8>,
176    },
177    Mul64 {
178        dst: Pat<u8>,
179        src1: Pat<u8>,
180        src2: Pat<u8>,
181    },
182    DivU64 {
183        dst: Pat<u8>,
184        src1: Pat<u8>,
185        src2: Pat<u8>,
186    },
187    DivS64 {
188        dst: Pat<u8>,
189        src1: Pat<u8>,
190        src2: Pat<u8>,
191    },
192    RemU64 {
193        dst: Pat<u8>,
194        src1: Pat<u8>,
195        src2: Pat<u8>,
196    },
197    RemS64 {
198        dst: Pat<u8>,
199        src1: Pat<u8>,
200        src2: Pat<u8>,
201    },
202    And {
203        dst: Pat<u8>,
204        src1: Pat<u8>,
205        src2: Pat<u8>,
206    },
207    Or {
208        dst: Pat<u8>,
209        src1: Pat<u8>,
210        src2: Pat<u8>,
211    },
212    Xor {
213        dst: Pat<u8>,
214        src1: Pat<u8>,
215        src2: Pat<u8>,
216    },
217    ShloL32 {
218        dst: Pat<u8>,
219        src1: Pat<u8>,
220        src2: Pat<u8>,
221    },
222    ShloR32 {
223        dst: Pat<u8>,
224        src1: Pat<u8>,
225        src2: Pat<u8>,
226    },
227    SharR32 {
228        dst: Pat<u8>,
229        src1: Pat<u8>,
230        src2: Pat<u8>,
231    },
232    ShloL64 {
233        dst: Pat<u8>,
234        src1: Pat<u8>,
235        src2: Pat<u8>,
236    },
237    ShloR64 {
238        dst: Pat<u8>,
239        src1: Pat<u8>,
240        src2: Pat<u8>,
241    },
242    SharR64 {
243        dst: Pat<u8>,
244        src1: Pat<u8>,
245        src2: Pat<u8>,
246    },
247    SetLtU {
248        dst: Pat<u8>,
249        src1: Pat<u8>,
250        src2: Pat<u8>,
251    },
252    SetLtS {
253        dst: Pat<u8>,
254        src1: Pat<u8>,
255        src2: Pat<u8>,
256    },
257    SetLtUImm {
258        dst: Pat<u8>,
259        src: Pat<u8>,
260        value: Pat<i32>,
261    },
262    SetLtSImm {
263        dst: Pat<u8>,
264        src: Pat<u8>,
265        value: Pat<i32>,
266    },
267    Jump {
268        offset: Pat<i32>,
269    },
270    JumpInd {
271        reg: Pat<u8>,
272        offset: Pat<i32>,
273    },
274    AddImm32 {
275        dst: Pat<u8>,
276        src: Pat<u8>,
277        value: Pat<i32>,
278    },
279    AddImm64 {
280        dst: Pat<u8>,
281        src: Pat<u8>,
282        value: Pat<i32>,
283    },
284    BranchLtU {
285        reg1: Pat<u8>,
286        reg2: Pat<u8>,
287        offset: Pat<i32>,
288    },
289    BranchGeU {
290        reg1: Pat<u8>,
291        reg2: Pat<u8>,
292        offset: Pat<i32>,
293    },
294    BranchEqImm {
295        reg: Pat<u8>,
296        value: Pat<i32>,
297        offset: Pat<i32>,
298    },
299    BranchNeImm {
300        reg: Pat<u8>,
301        value: Pat<i32>,
302        offset: Pat<i32>,
303    },
304    BranchGeSImm {
305        reg: Pat<u8>,
306        value: Pat<i32>,
307        offset: Pat<i32>,
308    },
309    LoadIndU8 {
310        dst: Pat<u8>,
311        base: Pat<u8>,
312        offset: Pat<i32>,
313    },
314    LoadIndI8 {
315        dst: Pat<u8>,
316        base: Pat<u8>,
317        offset: Pat<i32>,
318    },
319    LoadIndU16 {
320        dst: Pat<u8>,
321        base: Pat<u8>,
322        offset: Pat<i32>,
323    },
324    LoadIndI16 {
325        dst: Pat<u8>,
326        base: Pat<u8>,
327        offset: Pat<i32>,
328    },
329    LoadIndU32 {
330        dst: Pat<u8>,
331        base: Pat<u8>,
332        offset: Pat<i32>,
333    },
334    LoadIndU64 {
335        dst: Pat<u8>,
336        base: Pat<u8>,
337        offset: Pat<i32>,
338    },
339    StoreIndU8 {
340        base: Pat<u8>,
341        src: Pat<u8>,
342        offset: Pat<i32>,
343    },
344    StoreIndU16 {
345        base: Pat<u8>,
346        src: Pat<u8>,
347        offset: Pat<i32>,
348    },
349    StoreIndU32 {
350        base: Pat<u8>,
351        src: Pat<u8>,
352        offset: Pat<i32>,
353    },
354    StoreIndU64 {
355        base: Pat<u8>,
356        src: Pat<u8>,
357        offset: Pat<i32>,
358    },
359    CountSetBits32 {
360        dst: Pat<u8>,
361        src: Pat<u8>,
362    },
363    CountSetBits64 {
364        dst: Pat<u8>,
365        src: Pat<u8>,
366    },
367    LeadingZeroBits32 {
368        dst: Pat<u8>,
369        src: Pat<u8>,
370    },
371    LeadingZeroBits64 {
372        dst: Pat<u8>,
373        src: Pat<u8>,
374    },
375    TrailingZeroBits32 {
376        dst: Pat<u8>,
377        src: Pat<u8>,
378    },
379    TrailingZeroBits64 {
380        dst: Pat<u8>,
381        src: Pat<u8>,
382    },
383    SignExtend8 {
384        dst: Pat<u8>,
385        src: Pat<u8>,
386    },
387    SignExtend16 {
388        dst: Pat<u8>,
389        src: Pat<u8>,
390    },
391    ZeroExtend16 {
392        dst: Pat<u8>,
393        src: Pat<u8>,
394    },
395    CmovIzImm {
396        cond: Pat<u8>,
397        dst: Pat<u8>,
398        value: Pat<i32>,
399    },
400    CmovNzImm {
401        cond: Pat<u8>,
402        dst: Pat<u8>,
403        value: Pat<i32>,
404    },
405    StoreImmU8 {
406        address: Pat<i32>,
407        value: Pat<i32>,
408    },
409    StoreImmU16 {
410        address: Pat<i32>,
411        value: Pat<i32>,
412    },
413    StoreImmU32 {
414        address: Pat<i32>,
415        value: Pat<i32>,
416    },
417    StoreImmU64 {
418        address: Pat<i32>,
419        value: Pat<i32>,
420    },
421    Trap,
422    Fallthrough,
423    Ecalli {
424        index: Pat<u32>,
425    },
426}
427
428impl InstructionPattern {
429    /// Check if an instruction matches this pattern
430    pub fn matches(&self, instr: &Instruction) -> bool {
431        use InstructionPattern as P;
432
433        match (self, instr) {
434            (P::Any, _) => true,
435            (P::Trap, Instruction::Trap) => true,
436            (P::Fallthrough, Instruction::Fallthrough) => true,
437            (
438                P::LoadImm {
439                    reg: r_pat,
440                    value: v_pat,
441                },
442                Instruction::LoadImm { reg, value },
443            ) => r_pat.matches(reg) && v_pat.matches(value),
444            (
445                P::LoadImm64 {
446                    reg: r_pat,
447                    value: v_pat,
448                },
449                Instruction::LoadImm64 { reg, value },
450            ) => r_pat.matches(reg) && v_pat.matches(value),
451            (
452                P::Add32 {
453                    dst: d_pat,
454                    src1: s1_pat,
455                    src2: s2_pat,
456                },
457                Instruction::Add32 { dst, src1, src2 },
458            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
459            (
460                P::Sub32 {
461                    dst: d_pat,
462                    src1: s1_pat,
463                    src2: s2_pat,
464                },
465                Instruction::Sub32 { dst, src1, src2 },
466            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
467            (
468                P::Mul32 {
469                    dst: d_pat,
470                    src1: s1_pat,
471                    src2: s2_pat,
472                },
473                Instruction::Mul32 { dst, src1, src2 },
474            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
475            (
476                P::DivU32 {
477                    dst: d_pat,
478                    src1: s1_pat,
479                    src2: s2_pat,
480                },
481                Instruction::DivU32 { dst, src1, src2 },
482            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
483            (
484                P::DivS32 {
485                    dst: d_pat,
486                    src1: s1_pat,
487                    src2: s2_pat,
488                },
489                Instruction::DivS32 { dst, src1, src2 },
490            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
491            (
492                P::RemU32 {
493                    dst: d_pat,
494                    src1: s1_pat,
495                    src2: s2_pat,
496                },
497                Instruction::RemU32 { dst, src1, src2 },
498            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
499            (
500                P::RemS32 {
501                    dst: d_pat,
502                    src1: s1_pat,
503                    src2: s2_pat,
504                },
505                Instruction::RemS32 { dst, src1, src2 },
506            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
507            (
508                P::Add64 {
509                    dst: d_pat,
510                    src1: s1_pat,
511                    src2: s2_pat,
512                },
513                Instruction::Add64 { dst, src1, src2 },
514            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
515            (
516                P::Sub64 {
517                    dst: d_pat,
518                    src1: s1_pat,
519                    src2: s2_pat,
520                },
521                Instruction::Sub64 { dst, src1, src2 },
522            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
523            (
524                P::Mul64 {
525                    dst: d_pat,
526                    src1: s1_pat,
527                    src2: s2_pat,
528                },
529                Instruction::Mul64 { dst, src1, src2 },
530            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
531            (
532                P::DivU64 {
533                    dst: d_pat,
534                    src1: s1_pat,
535                    src2: s2_pat,
536                },
537                Instruction::DivU64 { dst, src1, src2 },
538            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
539            (
540                P::DivS64 {
541                    dst: d_pat,
542                    src1: s1_pat,
543                    src2: s2_pat,
544                },
545                Instruction::DivS64 { dst, src1, src2 },
546            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
547            (
548                P::RemU64 {
549                    dst: d_pat,
550                    src1: s1_pat,
551                    src2: s2_pat,
552                },
553                Instruction::RemU64 { dst, src1, src2 },
554            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
555            (
556                P::RemS64 {
557                    dst: d_pat,
558                    src1: s1_pat,
559                    src2: s2_pat,
560                },
561                Instruction::RemS64 { dst, src1, src2 },
562            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
563            (
564                P::And {
565                    dst: d_pat,
566                    src1: s1_pat,
567                    src2: s2_pat,
568                },
569                Instruction::And { dst, src1, src2 },
570            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
571            (
572                P::Or {
573                    dst: d_pat,
574                    src1: s1_pat,
575                    src2: s2_pat,
576                },
577                Instruction::Or { dst, src1, src2 },
578            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
579            (
580                P::Xor {
581                    dst: d_pat,
582                    src1: s1_pat,
583                    src2: s2_pat,
584                },
585                Instruction::Xor { dst, src1, src2 },
586            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
587            (
588                P::ShloL32 {
589                    dst: d_pat,
590                    src1: s1_pat,
591                    src2: s2_pat,
592                },
593                Instruction::ShloL32 { dst, src1, src2 },
594            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
595            (
596                P::ShloR32 {
597                    dst: d_pat,
598                    src1: s1_pat,
599                    src2: s2_pat,
600                },
601                Instruction::ShloR32 { dst, src1, src2 },
602            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
603            (
604                P::SharR32 {
605                    dst: d_pat,
606                    src1: s1_pat,
607                    src2: s2_pat,
608                },
609                Instruction::SharR32 { dst, src1, src2 },
610            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
611            (
612                P::ShloL64 {
613                    dst: d_pat,
614                    src1: s1_pat,
615                    src2: s2_pat,
616                },
617                Instruction::ShloL64 { dst, src1, src2 },
618            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
619            (
620                P::ShloR64 {
621                    dst: d_pat,
622                    src1: s1_pat,
623                    src2: s2_pat,
624                },
625                Instruction::ShloR64 { dst, src1, src2 },
626            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
627            (
628                P::SharR64 {
629                    dst: d_pat,
630                    src1: s1_pat,
631                    src2: s2_pat,
632                },
633                Instruction::SharR64 { dst, src1, src2 },
634            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
635            (
636                P::SetLtU {
637                    dst: d_pat,
638                    src1: s1_pat,
639                    src2: s2_pat,
640                },
641                Instruction::SetLtU { dst, src1, src2 },
642            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
643            (
644                P::SetLtS {
645                    dst: d_pat,
646                    src1: s1_pat,
647                    src2: s2_pat,
648                },
649                Instruction::SetLtS { dst, src1, src2 },
650            ) => d_pat.matches(dst) && s1_pat.matches(src1) && s2_pat.matches(src2),
651            (
652                P::SetLtUImm {
653                    dst: d_pat,
654                    src: s_pat,
655                    value: v_pat,
656                },
657                Instruction::SetLtUImm { dst, src, value },
658            ) => d_pat.matches(dst) && s_pat.matches(src) && v_pat.matches(value),
659            (
660                P::SetLtSImm {
661                    dst: d_pat,
662                    src: s_pat,
663                    value: v_pat,
664                },
665                Instruction::SetLtSImm { dst, src, value },
666            ) => d_pat.matches(dst) && s_pat.matches(src) && v_pat.matches(value),
667            (P::Jump { offset: o_pat }, Instruction::Jump { offset }) => o_pat.matches(offset),
668            (
669                P::JumpInd {
670                    reg: r_pat,
671                    offset: o_pat,
672                },
673                Instruction::JumpInd { reg, offset },
674            ) => r_pat.matches(reg) && o_pat.matches(offset),
675            (
676                P::AddImm32 {
677                    dst: d_pat,
678                    src: s_pat,
679                    value: v_pat,
680                },
681                Instruction::AddImm32 { dst, src, value },
682            ) => d_pat.matches(dst) && s_pat.matches(src) && v_pat.matches(value),
683            (
684                P::AddImm64 {
685                    dst: d_pat,
686                    src: s_pat,
687                    value: v_pat,
688                },
689                Instruction::AddImm64 { dst, src, value },
690            ) => d_pat.matches(dst) && s_pat.matches(src) && v_pat.matches(value),
691            (
692                P::BranchLtU {
693                    reg1: r1_pat,
694                    reg2: r2_pat,
695                    offset: o_pat,
696                },
697                Instruction::BranchLtU { reg1, reg2, offset },
698            ) => r1_pat.matches(reg1) && r2_pat.matches(reg2) && o_pat.matches(offset),
699            (
700                P::BranchGeU {
701                    reg1: r1_pat,
702                    reg2: r2_pat,
703                    offset: o_pat,
704                },
705                Instruction::BranchGeU { reg1, reg2, offset },
706            ) => r1_pat.matches(reg1) && r2_pat.matches(reg2) && o_pat.matches(offset),
707            (
708                P::BranchEqImm {
709                    reg: r_pat,
710                    value: v_pat,
711                    offset: o_pat,
712                },
713                Instruction::BranchEqImm { reg, value, offset },
714            ) => r_pat.matches(reg) && v_pat.matches(value) && o_pat.matches(offset),
715            (
716                P::BranchNeImm {
717                    reg: r_pat,
718                    value: v_pat,
719                    offset: o_pat,
720                },
721                Instruction::BranchNeImm { reg, value, offset },
722            ) => r_pat.matches(reg) && v_pat.matches(value) && o_pat.matches(offset),
723            (
724                P::BranchGeSImm {
725                    reg: r_pat,
726                    value: v_pat,
727                    offset: o_pat,
728                },
729                Instruction::BranchGeSImm { reg, value, offset },
730            ) => r_pat.matches(reg) && v_pat.matches(value) && o_pat.matches(offset),
731            (
732                P::LoadIndU8 {
733                    dst: d_pat,
734                    base: b_pat,
735                    offset: o_pat,
736                },
737                Instruction::LoadIndU8 { dst, base, offset },
738            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
739            (
740                P::LoadIndI8 {
741                    dst: d_pat,
742                    base: b_pat,
743                    offset: o_pat,
744                },
745                Instruction::LoadIndI8 { dst, base, offset },
746            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
747            (
748                P::LoadIndU16 {
749                    dst: d_pat,
750                    base: b_pat,
751                    offset: o_pat,
752                },
753                Instruction::LoadIndU16 { dst, base, offset },
754            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
755            (
756                P::LoadIndI16 {
757                    dst: d_pat,
758                    base: b_pat,
759                    offset: o_pat,
760                },
761                Instruction::LoadIndI16 { dst, base, offset },
762            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
763            (
764                P::LoadIndU32 {
765                    dst: d_pat,
766                    base: b_pat,
767                    offset: o_pat,
768                },
769                Instruction::LoadIndU32 { dst, base, offset },
770            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
771            (
772                P::LoadIndU64 {
773                    dst: d_pat,
774                    base: b_pat,
775                    offset: o_pat,
776                },
777                Instruction::LoadIndU64 { dst, base, offset },
778            ) => d_pat.matches(dst) && b_pat.matches(base) && o_pat.matches(offset),
779            (
780                P::StoreIndU8 {
781                    base: b_pat,
782                    src: s_pat,
783                    offset: o_pat,
784                },
785                Instruction::StoreIndU8 { base, src, offset },
786            ) => b_pat.matches(base) && s_pat.matches(src) && o_pat.matches(offset),
787            (
788                P::StoreIndU16 {
789                    base: b_pat,
790                    src: s_pat,
791                    offset: o_pat,
792                },
793                Instruction::StoreIndU16 { base, src, offset },
794            ) => b_pat.matches(base) && s_pat.matches(src) && o_pat.matches(offset),
795            (
796                P::StoreIndU32 {
797                    base: b_pat,
798                    src: s_pat,
799                    offset: o_pat,
800                },
801                Instruction::StoreIndU32 { base, src, offset },
802            ) => b_pat.matches(base) && s_pat.matches(src) && o_pat.matches(offset),
803            (
804                P::StoreIndU64 {
805                    base: b_pat,
806                    src: s_pat,
807                    offset: o_pat,
808                },
809                Instruction::StoreIndU64 { base, src, offset },
810            ) => b_pat.matches(base) && s_pat.matches(src) && o_pat.matches(offset),
811            (
812                P::CountSetBits32 {
813                    dst: d_pat,
814                    src: s_pat,
815                },
816                Instruction::CountSetBits32 { dst, src },
817            ) => d_pat.matches(dst) && s_pat.matches(src),
818            (
819                P::CountSetBits64 {
820                    dst: d_pat,
821                    src: s_pat,
822                },
823                Instruction::CountSetBits64 { dst, src },
824            ) => d_pat.matches(dst) && s_pat.matches(src),
825            (
826                P::LeadingZeroBits32 {
827                    dst: d_pat,
828                    src: s_pat,
829                },
830                Instruction::LeadingZeroBits32 { dst, src },
831            ) => d_pat.matches(dst) && s_pat.matches(src),
832            (
833                P::LeadingZeroBits64 {
834                    dst: d_pat,
835                    src: s_pat,
836                },
837                Instruction::LeadingZeroBits64 { dst, src },
838            ) => d_pat.matches(dst) && s_pat.matches(src),
839            (
840                P::TrailingZeroBits32 {
841                    dst: d_pat,
842                    src: s_pat,
843                },
844                Instruction::TrailingZeroBits32 { dst, src },
845            ) => d_pat.matches(dst) && s_pat.matches(src),
846            (
847                P::TrailingZeroBits64 {
848                    dst: d_pat,
849                    src: s_pat,
850                },
851                Instruction::TrailingZeroBits64 { dst, src },
852            ) => d_pat.matches(dst) && s_pat.matches(src),
853            (
854                P::SignExtend8 {
855                    dst: d_pat,
856                    src: s_pat,
857                },
858                Instruction::SignExtend8 { dst, src },
859            ) => d_pat.matches(dst) && s_pat.matches(src),
860            (
861                P::SignExtend16 {
862                    dst: d_pat,
863                    src: s_pat,
864                },
865                Instruction::SignExtend16 { dst, src },
866            ) => d_pat.matches(dst) && s_pat.matches(src),
867            (
868                P::ZeroExtend16 {
869                    dst: d_pat,
870                    src: s_pat,
871                },
872                Instruction::ZeroExtend16 { dst, src },
873            ) => d_pat.matches(dst) && s_pat.matches(src),
874            (
875                P::CmovIzImm {
876                    cond: c_pat,
877                    dst: d_pat,
878                    value: v_pat,
879                },
880                Instruction::CmovIzImm { cond, dst, value },
881            ) => c_pat.matches(cond) && d_pat.matches(dst) && v_pat.matches(value),
882            (
883                P::CmovNzImm {
884                    cond: c_pat,
885                    dst: d_pat,
886                    value: v_pat,
887                },
888                Instruction::CmovNzImm { cond, dst, value },
889            ) => c_pat.matches(cond) && d_pat.matches(dst) && v_pat.matches(value),
890            (
891                P::StoreImmU8 {
892                    address: a_pat,
893                    value: v_pat,
894                },
895                Instruction::StoreImmU8 { address, value },
896            ) => a_pat.matches(address) && v_pat.matches(value),
897            (
898                P::StoreImmU16 {
899                    address: a_pat,
900                    value: v_pat,
901                },
902                Instruction::StoreImmU16 { address, value },
903            ) => a_pat.matches(address) && v_pat.matches(value),
904            (
905                P::StoreImmU32 {
906                    address: a_pat,
907                    value: v_pat,
908                },
909                Instruction::StoreImmU32 { address, value },
910            ) => a_pat.matches(address) && v_pat.matches(value),
911            (
912                P::StoreImmU64 {
913                    address: a_pat,
914                    value: v_pat,
915                },
916                Instruction::StoreImmU64 { address, value },
917            ) => a_pat.matches(address) && v_pat.matches(value),
918            (P::Ecalli { index: i_pat }, Instruction::Ecalli { index }) => i_pat.matches(index),
919            _ => false,
920        }
921    }
922}
923
924/// Find a pattern in an instruction sequence
925///
926/// Returns the index of the first match, or None if not found
927pub fn find_pattern(instructions: &[Instruction], pattern: &[InstructionPattern]) -> Option<usize> {
928    if pattern.is_empty() {
929        return Some(0);
930    }
931
932    'outer: for start in 0..=instructions.len().saturating_sub(pattern.len()) {
933        for (i, pat) in pattern.iter().enumerate() {
934            if !pat.matches(&instructions[start + i]) {
935                continue 'outer;
936            }
937        }
938        return Some(start);
939    }
940    None
941}
942
943/// Assert that an instruction sequence contains a pattern
944///
945/// Panics with a descriptive message if the pattern is not found
946pub fn assert_has_pattern(instructions: &[Instruction], pattern: &[InstructionPattern]) {
947    if find_pattern(instructions, pattern).is_none() {
948        panic!(
949            "Pattern not found in instruction sequence.\n\nExpected pattern:\n{}\n\nActual instructions:\n{}",
950            format_patterns(pattern),
951            format_instructions(instructions)
952        );
953    }
954}
955
956/// Assert that instructions match a pattern exactly
957///
958/// Panics with a descriptive message if they don't match
959pub fn assert_matches(instructions: &[Instruction], pattern: &[InstructionPattern]) {
960    if instructions.len() != pattern.len() {
961        panic!(
962            "Instruction count mismatch: expected {}, got {}.\n\nExpected pattern:\n{}\n\nActual instructions:\n{}",
963            pattern.len(),
964            instructions.len(),
965            format_patterns(pattern),
966            format_instructions(instructions)
967        );
968    }
969
970    for (i, (instr, pat)) in instructions.iter().zip(pattern.iter()).enumerate() {
971        if !pat.matches(instr) {
972            panic!(
973                "Instruction mismatch at index {}:\nExpected: {:?}\nActual:   {:?}\n\nFull pattern:\n{}\n\nFull instructions:\n{}",
974                i,
975                pat,
976                instr,
977                format_patterns(pattern),
978                format_instructions(instructions)
979            );
980        }
981    }
982}
983
984/// Format patterns for display
985fn format_patterns(patterns: &[InstructionPattern]) -> String {
986    patterns
987        .iter()
988        .map(|p| format!("  {:?}", p))
989        .collect::<Vec<_>>()
990        .join("\n")
991}
992
993/// Format instructions for display
994fn format_instructions(instructions: &[Instruction]) -> String {
995    instructions
996        .iter()
997        .map(|i| format!("  {:?}", i))
998        .collect::<Vec<_>>()
999        .join("\n")
1000}
1001
1002/// Count instructions with a specific opcode
1003pub fn count_opcode(instructions: &[Instruction], opcode: Opcode) -> usize {
1004    instructions
1005        .iter()
1006        .filter(|i| i.opcode() == Some(opcode))
1007        .count()
1008}
1009
1010/// Check if an instruction sequence contains a specific opcode
1011pub fn has_opcode(instructions: &[Instruction], opcode: Opcode) -> bool {
1012    instructions.iter().any(|i| i.opcode() == Some(opcode))
1013}
1014
1015/// Filter instructions by opcode
1016pub fn filter_by_opcode(instructions: &[Instruction], opcode: Opcode) -> Vec<&Instruction> {
1017    instructions
1018        .iter()
1019        .filter(|i| i.opcode() == Some(opcode))
1020        .collect()
1021}
1022
1023/// Helper extension trait for instructions
1024pub trait InstructionExt {
1025    /// Get the opcode of this instruction, if applicable
1026    fn opcode(&self) -> Option<Opcode>;
1027}
1028
1029impl InstructionExt for Instruction {
1030    fn opcode(&self) -> Option<Opcode> {
1031        match self {
1032            Instruction::Trap => Some(Opcode::Trap),
1033            Instruction::Fallthrough => Some(Opcode::Fallthrough),
1034            Instruction::LoadImm64 { .. } => Some(Opcode::LoadImm64),
1035            Instruction::LoadImm { .. } => Some(Opcode::LoadImm),
1036            Instruction::Add32 { .. } => Some(Opcode::Add32),
1037            Instruction::Sub32 { .. } => Some(Opcode::Sub32),
1038            Instruction::Mul32 { .. } => Some(Opcode::Mul32),
1039            Instruction::DivU32 { .. } => Some(Opcode::DivU32),
1040            Instruction::DivS32 { .. } => Some(Opcode::DivS32),
1041            Instruction::RemU32 { .. } => Some(Opcode::RemU32),
1042            Instruction::RemS32 { .. } => Some(Opcode::RemS32),
1043            Instruction::Add64 { .. } => Some(Opcode::Add64),
1044            Instruction::Sub64 { .. } => Some(Opcode::Sub64),
1045            Instruction::Mul64 { .. } => Some(Opcode::Mul64),
1046            Instruction::DivU64 { .. } => Some(Opcode::DivU64),
1047            Instruction::DivS64 { .. } => Some(Opcode::DivS64),
1048            Instruction::RemU64 { .. } => Some(Opcode::RemU64),
1049            Instruction::RemS64 { .. } => Some(Opcode::RemS64),
1050            Instruction::ShloL64 { .. } => Some(Opcode::ShloL64),
1051            Instruction::ShloR64 { .. } => Some(Opcode::ShloR64),
1052            Instruction::SharR64 { .. } => Some(Opcode::SharR64),
1053            Instruction::AddImm32 { .. } => Some(Opcode::AddImm32),
1054            Instruction::AddImm64 { .. } => Some(Opcode::AddImm64),
1055            Instruction::AndImm { .. } => Some(Opcode::AndImm),
1056            Instruction::XorImm { .. } => Some(Opcode::XorImm),
1057            Instruction::OrImm { .. } => Some(Opcode::OrImm),
1058            Instruction::MulImm32 { .. } => Some(Opcode::MulImm32),
1059            Instruction::MulImm64 { .. } => Some(Opcode::MulImm64),
1060            Instruction::ShloLImm32 { .. } => Some(Opcode::ShloLImm32),
1061            Instruction::ShloRImm32 { .. } => Some(Opcode::ShloRImm32),
1062            Instruction::SharRImm32 { .. } => Some(Opcode::SharRImm32),
1063            Instruction::ShloLImm64 { .. } => Some(Opcode::ShloLImm64),
1064            Instruction::ShloRImm64 { .. } => Some(Opcode::ShloRImm64),
1065            Instruction::SharRImm64 { .. } => Some(Opcode::SharRImm64),
1066            Instruction::NegAddImm32 { .. } => Some(Opcode::NegAddImm32),
1067            Instruction::NegAddImm64 { .. } => Some(Opcode::NegAddImm64),
1068            Instruction::SetGtUImm { .. } => Some(Opcode::SetGtUImm),
1069            Instruction::SetGtSImm { .. } => Some(Opcode::SetGtSImm),
1070            Instruction::Jump { .. } => Some(Opcode::Jump),
1071            Instruction::LoadImmJump { .. } => Some(Opcode::LoadImmJump),
1072            Instruction::JumpInd { .. } => Some(Opcode::JumpInd),
1073            Instruction::LoadIndU32 { .. } => Some(Opcode::LoadIndU32),
1074            Instruction::StoreIndU32 { .. } => Some(Opcode::StoreIndU32),
1075            Instruction::LoadIndU64 { .. } => Some(Opcode::LoadIndU64),
1076            Instruction::StoreIndU64 { .. } => Some(Opcode::StoreIndU64),
1077            Instruction::BranchNeImm { .. } => Some(Opcode::BranchNeImm),
1078            Instruction::BranchEqImm { .. } => Some(Opcode::BranchEqImm),
1079            Instruction::BranchGeSImm { .. } => Some(Opcode::BranchGeSImm),
1080            Instruction::BranchLtUImm { .. } => Some(Opcode::BranchLtUImm),
1081            Instruction::BranchLeUImm { .. } => Some(Opcode::BranchLeUImm),
1082            Instruction::BranchGeUImm { .. } => Some(Opcode::BranchGeUImm),
1083            Instruction::BranchGtUImm { .. } => Some(Opcode::BranchGtUImm),
1084            Instruction::BranchLtSImm { .. } => Some(Opcode::BranchLtSImm),
1085            Instruction::BranchLeSImm { .. } => Some(Opcode::BranchLeSImm),
1086            Instruction::BranchGtSImm { .. } => Some(Opcode::BranchGtSImm),
1087            Instruction::MoveReg { .. } => Some(Opcode::MoveReg),
1088            Instruction::BranchEq { .. } => Some(Opcode::BranchEq),
1089            Instruction::BranchNe { .. } => Some(Opcode::BranchNe),
1090            Instruction::BranchGeU { .. } => Some(Opcode::BranchGeU),
1091            Instruction::BranchLtU { .. } => Some(Opcode::BranchLtU),
1092            Instruction::BranchLtS { .. } => Some(Opcode::BranchLtS),
1093            Instruction::BranchGeS { .. } => Some(Opcode::BranchGeS),
1094            Instruction::SetLtU { .. } => Some(Opcode::SetLtU),
1095            Instruction::SetLtS { .. } => Some(Opcode::SetLtS),
1096            Instruction::CmovIz { .. } => Some(Opcode::CmovIz),
1097            Instruction::CmovNz { .. } => Some(Opcode::CmovNz),
1098            Instruction::And { .. } => Some(Opcode::And),
1099            Instruction::Xor { .. } => Some(Opcode::Xor),
1100            Instruction::Or { .. } => Some(Opcode::Or),
1101            Instruction::SetLtUImm { .. } => Some(Opcode::SetLtUImm),
1102            Instruction::SetLtSImm { .. } => Some(Opcode::SetLtSImm),
1103            Instruction::ShloL32 { .. } => Some(Opcode::ShloL32),
1104            Instruction::ShloR32 { .. } => Some(Opcode::ShloR32),
1105            Instruction::SharR32 { .. } => Some(Opcode::SharR32),
1106            Instruction::Sbrk { .. } => Some(Opcode::Sbrk),
1107            Instruction::CountSetBits64 { .. } => Some(Opcode::CountSetBits64),
1108            Instruction::CountSetBits32 { .. } => Some(Opcode::CountSetBits32),
1109            Instruction::LeadingZeroBits64 { .. } => Some(Opcode::LeadingZeroBits64),
1110            Instruction::LeadingZeroBits32 { .. } => Some(Opcode::LeadingZeroBits32),
1111            Instruction::TrailingZeroBits64 { .. } => Some(Opcode::TrailingZeroBits64),
1112            Instruction::TrailingZeroBits32 { .. } => Some(Opcode::TrailingZeroBits32),
1113            Instruction::SignExtend8 { .. } => Some(Opcode::SignExtend8),
1114            Instruction::SignExtend16 { .. } => Some(Opcode::SignExtend16),
1115            Instruction::ZeroExtend16 { .. } => Some(Opcode::ZeroExtend16),
1116            Instruction::LoadIndU8 { .. } => Some(Opcode::LoadIndU8),
1117            Instruction::LoadIndI8 { .. } => Some(Opcode::LoadIndI8),
1118            Instruction::LoadIndU16 { .. } => Some(Opcode::LoadIndU16),
1119            Instruction::LoadIndI16 { .. } => Some(Opcode::LoadIndI16),
1120            Instruction::StoreIndU8 { .. } => Some(Opcode::StoreIndU8),
1121            Instruction::StoreIndU16 { .. } => Some(Opcode::StoreIndU16),
1122            Instruction::CmovIzImm { .. } => Some(Opcode::CmovIzImm),
1123            Instruction::CmovNzImm { .. } => Some(Opcode::CmovNzImm),
1124            Instruction::StoreImmIndU8 { .. } => Some(Opcode::StoreImmIndU8),
1125            Instruction::StoreImmIndU16 { .. } => Some(Opcode::StoreImmIndU16),
1126            Instruction::StoreImmIndU32 { .. } => Some(Opcode::StoreImmIndU32),
1127            Instruction::StoreImmIndU64 { .. } => Some(Opcode::StoreImmIndU64),
1128            Instruction::Ecalli { .. } => Some(Opcode::Ecalli),
1129            Instruction::StoreImmU8 { .. } => Some(Opcode::StoreImmU8),
1130            Instruction::StoreImmU16 { .. } => Some(Opcode::StoreImmU16),
1131            Instruction::StoreImmU32 { .. } => Some(Opcode::StoreImmU32),
1132            Instruction::StoreImmU64 { .. } => Some(Opcode::StoreImmU64),
1133            Instruction::LoadU8 { .. } => Some(Opcode::LoadU8),
1134            Instruction::LoadI8 { .. } => Some(Opcode::LoadI8),
1135            Instruction::LoadU16 { .. } => Some(Opcode::LoadU16),
1136            Instruction::LoadI16 { .. } => Some(Opcode::LoadI16),
1137            Instruction::LoadU32 { .. } => Some(Opcode::LoadU32),
1138            Instruction::LoadI32 { .. } => Some(Opcode::LoadI32),
1139            Instruction::LoadU64 { .. } => Some(Opcode::LoadU64),
1140            Instruction::StoreU8 { .. } => Some(Opcode::StoreU8),
1141            Instruction::StoreU16 { .. } => Some(Opcode::StoreU16),
1142            Instruction::StoreU32 { .. } => Some(Opcode::StoreU32),
1143            Instruction::StoreU64 { .. } => Some(Opcode::StoreU64),
1144            Instruction::LoadIndI32 { .. } => Some(Opcode::LoadIndI32),
1145            Instruction::ReverseBytes { .. } => Some(Opcode::ReverseBytes),
1146            Instruction::ShloLImmAlt32 { .. } => Some(Opcode::ShloLImmAlt32),
1147            Instruction::ShloRImmAlt32 { .. } => Some(Opcode::ShloRImmAlt32),
1148            Instruction::SharRImmAlt32 { .. } => Some(Opcode::SharRImmAlt32),
1149            Instruction::ShloLImmAlt64 { .. } => Some(Opcode::ShloLImmAlt64),
1150            Instruction::ShloRImmAlt64 { .. } => Some(Opcode::ShloRImmAlt64),
1151            Instruction::SharRImmAlt64 { .. } => Some(Opcode::SharRImmAlt64),
1152            Instruction::RotRImm64 { .. } => Some(Opcode::RotRImm64),
1153            Instruction::RotRImmAlt64 { .. } => Some(Opcode::RotRImmAlt64),
1154            Instruction::RotRImm32 { .. } => Some(Opcode::RotRImm32),
1155            Instruction::RotRImmAlt32 { .. } => Some(Opcode::RotRImmAlt32),
1156            Instruction::LoadImmJumpInd { .. } => Some(Opcode::LoadImmJumpInd),
1157            Instruction::MulUpperSS { .. } => Some(Opcode::MulUpperSS),
1158            Instruction::MulUpperUU { .. } => Some(Opcode::MulUpperUU),
1159            Instruction::MulUpperSU { .. } => Some(Opcode::MulUpperSU),
1160            Instruction::RotL64 { .. } => Some(Opcode::RotL64),
1161            Instruction::RotL32 { .. } => Some(Opcode::RotL32),
1162            Instruction::RotR64 { .. } => Some(Opcode::RotR64),
1163            Instruction::RotR32 { .. } => Some(Opcode::RotR32),
1164            Instruction::AndInv { .. } => Some(Opcode::AndInv),
1165            Instruction::OrInv { .. } => Some(Opcode::OrInv),
1166            Instruction::Xnor { .. } => Some(Opcode::Xnor),
1167            Instruction::Max { .. } => Some(Opcode::Max),
1168            Instruction::MaxU { .. } => Some(Opcode::MaxU),
1169            Instruction::Min { .. } => Some(Opcode::Min),
1170            Instruction::MinU { .. } => Some(Opcode::MinU),
1171            Instruction::Unknown { .. } => None,
1172        }
1173    }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178    use super::*;
1179
1180    #[test]
1181    fn test_wat_to_wasm_simple() {
1182        let wat = r#"
1183            (module
1184                (func (export "main") (result i32)
1185                    i32.const 42
1186                )
1187            )
1188        "#;
1189        let wasm = wat_to_wasm(wat).expect("Failed to parse WAT");
1190        assert!(!wasm.is_empty());
1191        // WASM magic number
1192        assert_eq!(&wasm[0..4], &[0x00, 0x61, 0x73, 0x6d]);
1193    }
1194
1195    #[test]
1196    fn test_instruction_pattern_matching() {
1197        let instr = Instruction::Add32 {
1198            dst: 5,
1199            src1: 2,
1200            src2: 3,
1201        };
1202
1203        // Exact match
1204        let pattern_exact = InstructionPattern::Add32 {
1205            dst: Pat::Exact(5),
1206            src1: Pat::Exact(2),
1207            src2: Pat::Exact(3),
1208        };
1209        assert!(pattern_exact.matches(&instr));
1210
1211        // Wildcard match
1212        let pattern_wildcard = InstructionPattern::Add32 {
1213            dst: Pat::Any,
1214            src1: Pat::Any,
1215            src2: Pat::Any,
1216        };
1217        assert!(pattern_wildcard.matches(&instr));
1218
1219        // Wrong instruction type
1220        let pattern_wrong = InstructionPattern::Sub32 {
1221            dst: Pat::Any,
1222            src1: Pat::Any,
1223            src2: Pat::Any,
1224        };
1225        assert!(!pattern_wrong.matches(&instr));
1226    }
1227
1228    #[test]
1229    fn test_find_pattern() {
1230        let instructions = vec![
1231            Instruction::LoadImm { reg: 2, value: 5 },
1232            Instruction::LoadImm { reg: 3, value: 7 },
1233            Instruction::Add32 {
1234                dst: 4,
1235                src1: 2,
1236                src2: 3,
1237            },
1238            Instruction::Trap,
1239        ];
1240
1241        let pattern = vec![
1242            InstructionPattern::LoadImm {
1243                reg: Pat::Any,
1244                value: Pat::Any,
1245            },
1246            InstructionPattern::Add32 {
1247                dst: Pat::Any,
1248                src1: Pat::Exact(2),
1249                src2: Pat::Any,
1250            },
1251        ];
1252
1253        assert_eq!(find_pattern(&instructions, &pattern), Some(1));
1254    }
1255
1256    #[test]
1257    fn test_count_opcode() {
1258        let instructions = vec![
1259            Instruction::LoadImm { reg: 2, value: 5 },
1260            Instruction::LoadImm { reg: 3, value: 7 },
1261            Instruction::Add32 {
1262                dst: 4,
1263                src1: 2,
1264                src2: 3,
1265            },
1266            Instruction::Trap,
1267        ];
1268
1269        assert_eq!(count_opcode(&instructions, Opcode::LoadImm), 2);
1270        assert_eq!(count_opcode(&instructions, Opcode::Add32), 1);
1271        assert_eq!(count_opcode(&instructions, Opcode::Trap), 1);
1272        assert_eq!(count_opcode(&instructions, Opcode::Sub32), 0);
1273    }
1274
1275    #[test]
1276    fn test_pat_predicate() {
1277        let is_positive = |v: &i32| *v > 0;
1278        let pat = Pat::Predicate(is_positive);
1279
1280        assert!(pat.matches(&5));
1281        assert!(!pat.matches(&-1));
1282        assert!(!pat.matches(&0));
1283    }
1284
1285    #[test]
1286    fn test_compile_simple_wat() {
1287        let wat = r#"
1288            (module
1289                (func (export "main") (param i32 i32) (result i32)
1290                    local.get 0
1291                    local.get 1
1292                    i32.add
1293                )
1294            )
1295        "#;
1296
1297        let program = compile_wat(wat).expect("Failed to compile");
1298        let code = program.code();
1299        let instructions = code.instructions();
1300
1301        // Should have some instructions
1302        assert!(!instructions.is_empty());
1303
1304        // Should contain at least one Add32
1305        assert!(has_opcode(instructions, Opcode::Add32));
1306    }
1307}