Skip to main content

wasm_opcode_table/
lib.rs

1pub mod instructions;
2
3pub use instructions::{
4    parse_instructions_toml, validate_instructions_table, validate_stack_entry, ControlFrame,
5    ControlKind, Immediate, Instruction, InstructionsTable, LabelTarget, Opcode, StackEntry,
6    StackType, TypeExpr, ValidateError,
7};
8
9#[cfg(feature = "instructions-toml")]
10pub use instructions::{instructions, INSTRUCTIONS_TOML};
11
12#[cfg(test)]
13mod tests {
14    use super::*;
15
16    const RELAXED_SIMD_NAMES: &[&str] = &[
17        "i8x16.relaxed_swizzle",
18        "i32x4.relaxed_trunc_f32x4_s",
19        "i32x4.relaxed_trunc_f32x4_u",
20        "i32x4.relaxed_trunc_f64x2_s_zero",
21        "i32x4.relaxed_trunc_f64x2_u_zero",
22        "f32x4.relaxed_madd",
23        "f32x4.relaxed_nmadd",
24        "f64x2.relaxed_madd",
25        "f64x2.relaxed_nmadd",
26        "i8x16.relaxed_laneselect",
27        "i16x8.relaxed_laneselect",
28        "i32x4.relaxed_laneselect",
29        "i64x2.relaxed_laneselect",
30        "f32x4.relaxed_min",
31        "f32x4.relaxed_max",
32        "f64x2.relaxed_min",
33        "f64x2.relaxed_max",
34        "i16x8.relaxed_q15mulr_s",
35        "i16x8.relaxed_dot_i8x16_i7x16_s",
36        "i32x4.relaxed_dot_i8x16_i7x16_add_s",
37    ];
38
39    const SAMPLE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/instructions.toml"));
40
41    #[test]
42    fn table_parses() {
43        let table = parse_instructions_toml(SAMPLE).unwrap();
44        assert!(table.instructions.len() >= 566);
45        assert_eq!(table.instructions[0].name, "unreachable");
46        assert_eq!(table.instructions[0].opcode, Opcode::Single(0));
47        validate_instructions_table(&table).unwrap();
48    }
49
50    #[test]
51    fn multi_byte_opcode_and_immediates() {
52        let table = parse_instructions_toml(SAMPLE).unwrap();
53        let fill = table
54            .instructions
55            .iter()
56            .find(|i| i.name == "table.fill")
57            .expect("table.fill");
58        assert_eq!(fill.opcode, Opcode::Multi(0xFC, 17));
59        let imms = fill.immediates.as_ref().expect("immediates");
60        assert_eq!(imms[0].ty, "tableidx");
61        assert_eq!(imms[0].name.as_deref(), Some("x"));
62    }
63
64    #[test]
65    fn control_stack_entry() {
66        let table = parse_instructions_toml(SAMPLE).unwrap();
67        let block = table
68            .instructions
69            .iter()
70            .find(|i| i.name == "block")
71            .expect("block");
72        let stack = block.stack_type.as_ref().expect("stack-type");
73        match &stack.to[0] {
74            StackEntry::Control(cf) => {
75                assert_eq!(cf.control, ControlKind::Block);
76                assert_eq!(cf.start.0, "params(bt)");
77                assert_eq!(cf.end.0, "results(bt)");
78                assert_eq!(cf.label, LabelTarget::End);
79            }
80            _ => panic!("expected control frame"),
81        }
82    }
83
84    #[test]
85    fn types_of_vs_type_of() {
86        let table = parse_instructions_toml(SAMPLE).unwrap();
87
88        let block = table
89            .instructions
90            .iter()
91            .find(|i| i.name == "block")
92            .expect("block");
93        match &block.stack_type.as_ref().unwrap().from[0] {
94            StackEntry::TypesOf(expr) => assert_eq!(expr.0, "params(bt)"),
95            _ => panic!("expected types-of"),
96        }
97
98        let local_get = table
99            .instructions
100            .iter()
101            .find(|i| i.name == "local.get")
102            .expect("local.get");
103        match &local_get.stack_type.as_ref().unwrap().to[0] {
104            StackEntry::TypeOf(expr) => assert_eq!(expr.0, "x"),
105            _ => panic!("expected type-of"),
106        }
107    }
108
109    #[test]
110    fn relaxed_simd_instructions() {
111        let table = parse_instructions_toml(SAMPLE).unwrap();
112        let relaxed: Vec<_> = table
113            .instructions
114            .iter()
115            .filter(|i| i.feature.as_deref() == Some("relaxed-simd"))
116            .collect();
117        assert_eq!(relaxed.len(), 20);
118
119        for (idx, name) in RELAXED_SIMD_NAMES.iter().enumerate() {
120            let insn = table
121                .instructions
122                .iter()
123                .find(|i| i.name == *name)
124                .unwrap_or_else(|| panic!("missing {name}"));
125            assert_eq!(insn.opcode, Opcode::Multi(0xFD, idx as u32 + 256));
126            assert_eq!(insn.category, "vector");
127            assert_eq!(insn.feature.as_deref(), Some("relaxed-simd"));
128        }
129    }
130
131    #[test]
132    fn loop_label_is_start() {
133        let table = parse_instructions_toml(SAMPLE).unwrap();
134        let loop_insn = table
135            .instructions
136            .iter()
137            .find(|i| i.name == "loop")
138            .expect("loop");
139        match &loop_insn.stack_type.as_ref().unwrap().to[0] {
140            StackEntry::Control(cf) => assert_eq!(cf.label, LabelTarget::Start),
141            _ => panic!("expected control frame"),
142        }
143    }
144}
145
146#[cfg(all(test, feature = "instructions-toml"))]
147mod embedded_tests {
148    use super::*;
149
150    #[test]
151    fn embedded_entrypoint() {
152        let table = instructions();
153        assert!(table.instructions.len() >= 566);
154        assert!(!INSTRUCTIONS_TOML.is_empty());
155        validate_instructions_table(table).unwrap();
156    }
157}