Skip to main content

byteflow/bytecode/
opcode.rs

1//! The Byteflow instruction set architecture (ISA v0).
2//!
3//! Byteflow is register-based (à la Lua 5.x / Dalvik) rather than stack-based
4//! (à la JVM/CPython). Register machines need roughly 40-50% fewer dispatched
5//! instructions than an equivalent stack machine because they avoid PUSH/POP
6//! traffic for every intermediate value, at the cost of slightly larger
7//! instruction words. For an interpreter whose steady-state cost is dominated
8//! by dispatch (branch prediction + icache misses), fewer instructions per
9//! logical operation wins.
10//!
11//! Every opcode fits in a single byte so a `Vec<Instruction>` is dense and
12//! the dispatch table (see `byteflow-vm::interp`) can be a flat jump table
13//! indexed directly by discriminant, with no bounds check in release builds
14//! (enforced instead at decode/verification time, see [`crate::verify`]).
15
16/// A single Byteflow opcode.
17///
18/// Numeric values are part of the stable on-disk ABI (`byteflow-bytecode`
19/// module format, see [`super::chunk::MAGIC`]) — never renumber an existing
20/// variant, only append.
21#[repr(u8)]
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum Opcode {
24    /// Stop the current Flow's VM loop. Terminal state.
25    Halt = 0x00,
26
27    // ---- data movement ----------------------------------------------------
28    /// `LoadConst ra, kb`  →  `r[a] = constants[b]`
29    LoadConst = 0x01,
30    /// `Move ra, rb`  →  `r[a] = r[b]`
31    Move = 0x02,
32    /// `LoadImm ra, imm`  →  `r[a] = imm as i64` (fast path, skips const pool)
33    LoadImm = 0x03,
34
35    // ---- arithmetic (integer; float variants share the same encoding
36    // ---- but operate on Value::Float, selected by the operand's runtime tag)
37    Add = 0x10,
38    Sub = 0x11,
39    Mul = 0x12,
40    Div = 0x13,
41    Mod = 0x14,
42    Neg = 0x15,
43
44    // ---- comparison → writes a Value::Bool into ra
45    Eq = 0x18,
46    Lt = 0x19,
47    Le = 0x1A,
48
49    // ---- control flow -------------------------------------------------
50    /// `Jump imm` → unconditional relative jump (imm = signed offset in
51    /// instructions from the *next* pc).
52    Jump = 0x20,
53    /// `Branch ra, imm` → jump by `imm` iff `r[a]` is falsy (Bool(false),
54    /// Unit, or Int(0)). This is the only conditional branch; `if/else` and
55    /// loops both lower to Branch + Jump, keeping the interpreter's branch
56    /// predictor state small.
57    Branch = 0x21,
58
59    // ---- procedure calls (native Rust functions registered via FFI, or
60    // ---- other bytecode functions in the same chunk) ------------------
61    /// `Call ra, fb, nc` → call function `fb` with `nc` arguments taken from
62    /// `r[a..a+nc]`, result written back into `r[a]`.
63    Call = 0x30,
64    /// `Return ra` → return `r[a]` to the caller frame (or complete the
65    /// Flow if this is the outermost frame).
66    Return = 0x31,
67    /// `CallNative ra, fb, nc` → like `Call` but `fb` indexes the native
68    /// function table instead of the bytecode function table.
69    CallNative = 0x32,
70
71    // ---- Flow model --------------------------------------------------
72    /// `Spawn ra, fb, nc` → create a new virtual Flow starting at
73    /// function `fb`, passing `nc` arguments taken from `r[a+1..a+1+nc]`
74    /// (deliberately *not* overlapping `r[a]` itself, which is where the
75    /// scheduler writes a **Cap** to the child once created — FlowCap;
76    /// see [`crate::VmResult::Spawn`]).
77    Spawn = 0x40,
78    /// `Yield` → cooperative yield. Control returns to the scheduler, the
79    /// Flow is re-enqueued as `Ready` and may resume on any worker.
80    Yield = 0x41,
81    /// `Sleep ra` → suspend until `r[a]` (interpreted as milliseconds,
82    /// Value::Int) has elapsed. Registered on the timer wheel.
83    Sleep = 0x42,
84    /// `Exit ra` → terminate the Flow, `r[a]` is delivered to `.join()`.
85    Exit = 0x43,
86    /// `SelfPid ra` → `r[a] =` a **self Cap** (`SEND|ASK`) for this flow.
87    /// (Opcode name kept for ABI; the value is `Value::Cap`, not Pid.)
88    /// The VM does not store its own id (it has no scheduler state); this
89    /// is a scheduler effect, same class as `Spawn`/`Receive`.
90    SelfPid = 0x44,
91
92    // ---- messaging --------------------------------------------------------
93    /// `Send ra, rb` → Atomic Hop: deliver `r[b]` (`Message`) to the Cap in
94    /// `r[a]`. Never blocks the sender flow (see [`crate::docs::mailbox`]).
95    ///
96    /// VM requires Cap + Message; worker resolves Cap (SEND), stamps sender
97    /// + `reply_cap`, then pushes to the resolved mailbox.
98    Send = 0x50,
99    /// `Receive ra` → pop the next message into `r[a]`; if the mailbox is
100    /// empty, suspends the Flow in `Waiting` state until a message
101    /// arrives.
102    Receive = 0x51,
103    /// `ReceiveTimeout ra, rb` → like `Receive` but gives up after `r[b]`
104    /// milliseconds, writing `Value::Unit` into `r[a]` on timeout.
105    ReceiveTimeout = 0x52,
106    /// `ReceiveMatch ra, rb` → **Atomic Hop selective receive**: block until
107    /// a [`crate::Value::Message`] with `tag == r[b]` (as `u16`) is available.
108    /// Non-matching hops stay in the mailbox in FIFO order (skip, don't drop).
109    ReceiveMatch = 0x53,
110    /// `ReceiveMatchImm ra, imm` → like `ReceiveMatch` with an immediate tag
111    /// (`imm` must fit in `u16`).
112    ReceiveMatchImm = 0x54,
113    /// `Ask ra, rb, rc` → **atomic request/reply hop**.
114    ///
115    /// 1. Validate `r[b]` as Cap and `r[c]` as Message (VM).
116    /// 2. Scheduler resolves Cap (ASK), stamps `sender` + mints `reply_cap`.
117    /// 3. Deliver the request to the resolved FlowId (like `Send`).
118    /// 4. Suspend until a reply hop matches
119    ///    `request_id == request.request_id && sender == resolved_FlowId`.
120    /// 5. Write the reply `Message` into `r[a]`.
121    ///
122    /// Append-only ABI slot (`0x55`).
123    Ask = 0x55,
124    /// `Monitor ra, rb` → install a one-way watch on Cap `r[b]`; write
125    /// [`crate::MonitorRef`] as `Int` into `r[a]`.
126    Monitor = 0x56,
127    /// `Demonitor ra` → drop the monitor in `r[a]` (`Int` ref).
128    Demonitor = 0x57,
129    /// `Link ra, rb` → bidirectional link with Cap `r[b]`; write
130    /// [`crate::LinkId`] as `Int` into `r[a]`.
131    Link = 0x58,
132    /// `Unlink ra` → drop the link in `r[a]` (`Int` id).
133    Unlink = 0x59,
134    /// `AskTimeout ra, rb, rc, rd` → like `Ask`, but give up after
135    /// `r[imm]` milliseconds and write `Value::Unit` into `ra`.
136    ///
137    /// Encoding: `a=dest`, `b=cap`, `c=msg`, `imm=millis_reg`.
138    /// Append-only ABI slot (`0x5A`); `Trap` stays `0x60`.
139    AskTimeout = 0x5A,
140
141    // ---- diagnostics / safety ------------------------------------------
142    /// `Trap imm` → deliberate fault (assertion failure, div-by-zero, bad
143    /// opcode encountered by a corrupt/foreign module, capability
144    /// violation). Propagates to the Flow supervisor as [`crate::FlowOutcome::Failed`].
145    Trap = 0x60,
146    /// `Nop` → no-op, used by the assembler to pad jump targets.
147    Nop = 0x61,
148}
149
150impl Opcode {
151    /// Decode a raw byte into an `Opcode`, used when loading foreign/untrusted
152    /// modules. Rejects anything outside the currently defined ISA rather
153    /// than transmuting garbage into a jump-table index (which is exactly
154    /// the class of bug that turns a VM into a code-execution primitive).
155    #[inline]
156    pub fn from_u8(byte: u8) -> Option<Opcode> {
157        use Opcode::*;
158        Some(match byte {
159            0x00 => Halt,
160            0x01 => LoadConst,
161            0x02 => Move,
162            0x03 => LoadImm,
163            0x10 => Add,
164            0x11 => Sub,
165            0x12 => Mul,
166            0x13 => Div,
167            0x14 => Mod,
168            0x15 => Neg,
169            0x18 => Eq,
170            0x19 => Lt,
171            0x1A => Le,
172            0x20 => Jump,
173            0x21 => Branch,
174            0x30 => Call,
175            0x31 => Return,
176            0x32 => CallNative,
177            0x40 => Spawn,
178            0x41 => Yield,
179            0x42 => Sleep,
180            0x43 => Exit,
181            0x44 => SelfPid,
182            0x50 => Send,
183            0x51 => Receive,
184            0x52 => ReceiveTimeout,
185            0x53 => ReceiveMatch,
186            0x54 => ReceiveMatchImm,
187            0x55 => Ask,
188            0x56 => Monitor,
189            0x57 => Demonitor,
190            0x58 => Link,
191            0x59 => Unlink,
192            0x5A => AskTimeout,
193            0x60 => Trap,
194            0x61 => Nop,
195            _ => return None,
196        })
197    }
198
199    #[inline]
200    pub fn as_u8(self) -> u8 {
201        self as u8
202    }
203}
204
205impl std::fmt::Display for Opcode {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        let name = match self {
208            Opcode::Halt => "Halt",
209            Opcode::LoadConst => "LoadConst",
210            Opcode::Move => "Move",
211            Opcode::LoadImm => "LoadImm",
212            Opcode::Add => "Add",
213            Opcode::Sub => "Sub",
214            Opcode::Mul => "Mul",
215            Opcode::Div => "Div",
216            Opcode::Mod => "Mod",
217            Opcode::Neg => "Neg",
218            Opcode::Eq => "Eq",
219            Opcode::Lt => "Lt",
220            Opcode::Le => "Le",
221            Opcode::Jump => "Jump",
222            Opcode::Branch => "Branch",
223            Opcode::Call => "Call",
224            Opcode::Return => "Return",
225            Opcode::CallNative => "CallNative",
226            Opcode::Spawn => "Spawn",
227            Opcode::Yield => "Yield",
228            Opcode::Sleep => "Sleep",
229            Opcode::Exit => "Exit",
230            Opcode::SelfPid => "SelfPid",
231            Opcode::Send => "Send",
232            Opcode::Receive => "Receive",
233            Opcode::ReceiveTimeout => "ReceiveTimeout",
234            Opcode::ReceiveMatch => "ReceiveMatch",
235            Opcode::ReceiveMatchImm => "ReceiveMatchImm",
236            Opcode::Ask => "Ask",
237            Opcode::Monitor => "Monitor",
238            Opcode::Demonitor => "Demonitor",
239            Opcode::Link => "Link",
240            Opcode::Unlink => "Unlink",
241            Opcode::AskTimeout => "AskTimeout",
242            Opcode::Trap => "Trap",
243            Opcode::Nop => "Nop",
244        };
245        f.write_str(name)
246    }
247}