Skip to main content

vihaco_cpu/
instruction.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use vihaco::Instruction;
5use vihaco::value::{Type, Value};
6
7/// `#[derive(Parse)]` notes:
8///
9/// - Real `.sst` syntax uses **dot-suffixed** types (`add.i64`, `load.i64 0`).
10///   The `parse_helpers::cpu_type` / `cpu_const_value` helpers consume the
11///   leading `.`; that's why the typed variants set `delimiters(open = "",
12///   close = "", separator = "")` and use `#[parse_with]` on the `Type` field.
13/// - `Const(Value::String/FunctionRef/HeapRef)`, `Branch(_)`,
14///   `ConditionalBranch(_, _)`, `Call(_, _)`, and bare `ret` use symbolic
15///   operands that need a shared interner / symbol table not available to a
16///   stateless `Parse` impl. Their `parse_with` helpers return `never_u32` so
17///   `Instruction::parser()` errors on those mnemonics — the Module
18///   orchestrator (Item 4 of the migration plan) intercepts them first.
19/// - Variant order is preserved from the pre-migration layout so derived
20///   opcodes stay stable. The single exception: `IndirectCall` is moved
21///   ahead of `Call` so the prefix-ordering check (`call` ⊂ `call_indirect`)
22///   passes.
23#[derive(Debug, Clone, PartialEq, Instruction, vihaco_parser::Parse)]
24#[instruction(width = 16)]
25pub enum Instruction {
26    // no-ops
27    /// span <file:file_id> <start:u32> <end:u32>
28    /// `span 0 1 2` — three space-separated u32s.
29    #[delimiters(open = "", close = "", separator = " ")]
30    Span(u32, u32, u32),
31
32    /// Label definition.
33    Label,
34
35    /// `func_start <name>` — marks function entry. `<name>` is symbolic and
36    /// orchestrator-resolved; the unit variant carries no payload.
37    #[token = "func_start"]
38    FunctionStart,
39    /// `func_end <name>` — marks function exit (debug only).
40    #[token = "func_end"]
41    FunctionEnd,
42
43    /// `breakpoint`. Must precede `Branch` (whose token `br` would be a
44    /// prefix of `breakpoint`).
45    Breakpoint,
46
47    // control flows
48    /// `br <target>` — symbolic. Deferred to orchestrator.
49    #[token = "br"]
50    #[delimiters(open = "", close = "", separator = "")]
51    Branch(#[parse_with = "crate::parse_helpers::never_u32"] u32),
52
53    /// `cond_br <true_target>, <false_target>` — symbolic. Deferred.
54    #[token = "cond_br"]
55    #[delimiters(open = "", close = "", separator = ",")]
56    ConditionalBranch(
57        #[parse_with = "crate::parse_helpers::never_u32"] u32,
58        #[parse_with = "crate::parse_helpers::never_u32"] u32,
59    ),
60
61    /// `ret` (bare) is the form real `.sst` uses; numeric `ret <n>` has no
62    /// precedent so we defer. Orchestrator emits `Return(0)` for bare `ret`.
63    #[token = "ret"]
64    #[delimiters(open = "", close = "", separator = "")]
65    Return(#[parse_with = "crate::parse_helpers::never_u32"] u32),
66
67    /// `call_indirect`. **Must precede `Call`** for the prefix check.
68    #[token = "call_indirect"]
69    IndirectCall,
70
71    /// `call <arity>, <addr>` — symbolic addr. Deferred.
72    #[token = "call"]
73    #[delimiters(open = "", close = "", separator = ",")]
74    Call(
75        #[parse_with = "crate::parse_helpers::never_u32"] u32,
76        #[parse_with = "crate::parse_helpers::never_u32"] u32,
77    ),
78
79    /// `halt` — stop execution.
80    Halt,
81
82    // traps / IO
83    /// `print` — write top-of-stack to stdout.
84    Print,
85
86    // memory operations
87    /// `load.<type> <address>` — two fields with single-space separator.
88    #[delimiters(open = "", close = "", separator = " ")]
89    Load(#[parse_with = "crate::parse_helpers::cpu_type"] Type, u32),
90    /// `store.<type> <address>`.
91    #[delimiters(open = "", close = "", separator = " ")]
92    Store(#[parse_with = "crate::parse_helpers::cpu_type"] Type, u32),
93
94    /// `dup`.
95    Dup,
96
97    /// `heap_alloc <n>`.
98    #[token = "heap_alloc"]
99    #[delimiters(open = "", close = "", separator = "")]
100    HeapAlloc(u32),
101
102    /// `get_item`. Must precede `Ge` (token `ge` ⊂ `get_item`).
103    #[token = "get_item"]
104    GetItem,
105
106    /// `heap_dealloc` — pops a HeapRef and marks the slot dead, returning it
107    /// to the free list for reuse by the next `heap_alloc`.
108    #[token = "heap_dealloc"]
109    HeapDealloc,
110
111    /// `const.<type> <literal>` — numeric/bool only here. `.str`/`.fn_ref`/
112    /// `.heap_ref` are orchestrator-handled.
113    #[token = "const"]
114    #[delimiters(open = "", close = "", separator = "")]
115    Const(#[parse_with = "crate::parse_helpers::cpu_const_value"] Value),
116
117    // arithmetic operations
118    #[delimiters(open = "", close = "", separator = "")]
119    Add(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
120    #[delimiters(open = "", close = "", separator = "")]
121    Sub(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
122    #[delimiters(open = "", close = "", separator = "")]
123    Mul(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
124    #[delimiters(open = "", close = "", separator = "")]
125    Div(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
126    #[delimiters(open = "", close = "", separator = "")]
127    Rem(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
128    #[delimiters(open = "", close = "", separator = "")]
129    Neg(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
130
131    // integer / bitwise operations
132    #[delimiters(open = "", close = "", separator = "")]
133    Shl(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
134    #[delimiters(open = "", close = "", separator = "")]
135    Shr(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
136    #[delimiters(open = "", close = "", separator = "")]
137    Rol(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
138    #[delimiters(open = "", close = "", separator = "")]
139    Ror(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
140    #[token = "bitand"]
141    #[delimiters(open = "", close = "", separator = "")]
142    BitAnd(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
143    #[token = "bitor"]
144    #[delimiters(open = "", close = "", separator = "")]
145    BitOr(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
146    #[token = "bitxor"]
147    #[delimiters(open = "", close = "", separator = "")]
148    BitXor(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
149
150    // boolean operations
151    Not,
152    And,
153    Or,
154    Xor,
155
156    // comparison operations
157    #[delimiters(open = "", close = "", separator = "")]
158    Eq(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
159    #[delimiters(open = "", close = "", separator = "")]
160    Ne(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
161    #[delimiters(open = "", close = "", separator = "")]
162    Lt(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
163    #[delimiters(open = "", close = "", separator = "")]
164    Gt(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
165    #[delimiters(open = "", close = "", separator = "")]
166    Le(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
167    #[delimiters(open = "", close = "", separator = "")]
168    Ge(#[parse_with = "crate::parse_helpers::cpu_type"] Type),
169}
170
171impl<T: Into<Value>> From<T> for Instruction {
172    fn from(value: T) -> Self {
173        Instruction::Const(value.into())
174    }
175}
176
177impl vihaco::CanonicalInstructionSyntax for Instruction {
178    fn variants() -> &'static [vihaco::CanonicalInstructionVariantSyntax] {
179        &[
180            vihaco::CanonicalInstructionVariantSyntax {
181                mnemonic: "cpu::const_i64",
182                operands: &[vihaco::OperandKind::I64],
183            },
184            vihaco::CanonicalInstructionVariantSyntax {
185                mnemonic: "cpu::const_f64",
186                operands: &[vihaco::OperandKind::F64],
187            },
188            vihaco::CanonicalInstructionVariantSyntax {
189                mnemonic: "cpu::const_bool",
190                operands: &[vihaco::OperandKind::Bool],
191            },
192            vihaco::CanonicalInstructionVariantSyntax {
193                mnemonic: "cpu::const_u64",
194                operands: &[vihaco::OperandKind::NonNegativeU64],
195            },
196            vihaco::CanonicalInstructionVariantSyntax {
197                mnemonic: "cpu::fn_ref",
198                operands: &[vihaco::OperandKind::Symbol],
199            },
200            vihaco::CanonicalInstructionVariantSyntax {
201                mnemonic: "cpu::call_direct",
202                operands: &[vihaco::OperandKind::Symbol],
203            },
204        ]
205    }
206}
207
208#[cfg(test)]
209#[allow(clippy::approx_constant)]
210mod parse_tests {
211    use super::Instruction;
212    use chumsky::Parser as _;
213    use vihaco::value::{Type, Value};
214    use vihaco_parser_core::Parse;
215
216    fn parse(input: &str) -> Instruction {
217        Instruction::parser()
218            .parse(input)
219            .into_result()
220            .unwrap_or_else(|e| panic!("parse({input:?}) failed: {e:?}"))
221    }
222
223    #[test]
224    fn parses_unit_variants() {
225        for (input, expected) in [
226            ("halt", Instruction::Halt),
227            ("print", Instruction::Print),
228            ("dup", Instruction::Dup),
229            ("breakpoint", Instruction::Breakpoint),
230            ("label", Instruction::Label),
231            ("func_start", Instruction::FunctionStart),
232            ("func_end", Instruction::FunctionEnd),
233            ("get_item", Instruction::GetItem),
234            ("not", Instruction::Not),
235            ("and", Instruction::And),
236            ("or", Instruction::Or),
237            ("xor", Instruction::Xor),
238            ("call_indirect", Instruction::IndirectCall),
239        ] {
240            assert_eq!(parse(input), expected, "input {input:?}");
241        }
242    }
243
244    #[test]
245    fn parses_typed_arith() {
246        assert_eq!(parse("add.i64"), Instruction::Add(Type::I64));
247        assert_eq!(parse("sub.f64"), Instruction::Sub(Type::F64));
248        assert_eq!(parse("mul.u32"), Instruction::Mul(Type::U32));
249        assert_eq!(parse("div.u64"), Instruction::Div(Type::U64));
250        assert_eq!(parse("lt.i64"), Instruction::Lt(Type::I64));
251        assert_eq!(parse("ge.f64"), Instruction::Ge(Type::F64));
252        assert_eq!(parse("bitand.i64"), Instruction::BitAnd(Type::I64));
253        assert_eq!(parse("shl.u64"), Instruction::Shl(Type::U64));
254    }
255
256    #[test]
257    fn parses_load_store() {
258        assert_eq!(parse("load.i64 7"), Instruction::Load(Type::I64, 7));
259        assert_eq!(parse("store.f64 42"), Instruction::Store(Type::F64, 42));
260    }
261
262    #[test]
263    fn parses_heap_alloc() {
264        assert_eq!(parse("heap_alloc 5"), Instruction::HeapAlloc(5));
265    }
266
267    #[test]
268    fn parses_span() {
269        assert_eq!(parse("span 0 1 2"), Instruction::Span(0, 1, 2));
270    }
271
272    #[test]
273    fn parses_const_numeric_flavors() {
274        assert_eq!(parse("const.i64 42"), Instruction::Const(Value::I64(42)));
275        assert_eq!(parse("const.u64 7"), Instruction::Const(Value::U64(7)));
276        assert_eq!(parse("const.u32 3"), Instruction::Const(Value::U32(3)));
277        assert_eq!(
278            parse("const.f64 3.14"),
279            Instruction::Const(Value::F64(3.14))
280        );
281        assert_eq!(
282            parse("const.bool true"),
283            Instruction::Const(Value::Bool(true))
284        );
285    }
286
287    #[test]
288    fn defers_symbolic_branch_to_orchestrator() {
289        // `br @body` cannot be parsed by the derive — `never_u32` ensures this.
290        // The Module orchestrator (Item 4) handles the symbolic form.
291        assert!(Instruction::parser().parse("br @body").has_errors());
292        assert!(Instruction::parser().parse("cond_br @a, @b").has_errors());
293        assert!(Instruction::parser().parse("call @main, 0").has_errors());
294        assert!(Instruction::parser().parse("ret").has_errors());
295    }
296
297    #[test]
298    fn defers_const_string_to_orchestrator() {
299        // `const.str "hello"` requires interner state — handled by the orchestrator.
300        assert!(
301            Instruction::parser()
302                .parse("const.str \"hello\"")
303                .has_errors()
304        );
305    }
306}