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 /// `Delegate ra, rb, rights, rc?` → write an attenuated Cap into `r[a]`.
141 ///
142 /// `r[b]` is the source Cap; `imm` is the requested rights mask;
143 /// `c = 255` means no extra native-mask narrowing. The scheduler
144 /// calls `Cap::attenuate` — the only derivation path.
145 /// Append-only ABI slot (`0x5B`); `Trap` stays `0x60`.
146 Delegate = 0x5B,
147 /// `FreshRequestId ra` → `r[a] =` next per-flow correlation id (`Int`).
148 ///
149 /// Starts at 1; `0` is reserved as “unset” so `Send` / `Ask` can mint.
150 /// Not a capability — uniqueness, not unpredictability.
151 /// Append-only ABI slot (`0x5C`); `Trap` stays `0x60`.
152 FreshRequestId = 0x5C,
153 /// `ReceiveMatchCorr ra, rb, rc` → wait for a hop with
154 /// `tag == r[b]` and `request_id == r[c]`. Non-matching hops stay queued.
155 /// Append-only ABI slot (`0x5D`); `Trap` stays `0x60`.
156 ReceiveMatchCorr = 0x5D,
157 /// `ReceiveMatchCorrImm ra, rb, imm` → like [`Self::ReceiveMatchCorr`]
158 /// with immediate tag (`imm` as `u16`) and `request_id` from `r[b]`.
159 /// Append-only ABI slot (`0x5E`); `Trap` stays `0x60`.
160 ReceiveMatchCorrImm = 0x5E,
161 /// `RegisterName ra` → publish `r[a]` (`Str`) as this flow's name.
162 /// Only the calling flow is registered. Requires `SEND` on self-authority
163 /// (confined spawn cannot squat names). Append-only (`0x62`).
164 RegisterName = 0x62,
165 /// `Whereis ra, rb` → look up `r[b]` (`Str`); write a **SEND** Cap for
166 /// the caller, or `Unit` if missing. Never a raw FlowId. (`0x63`)
167 Whereis = 0x63,
168
169 // ---- diagnostics / safety ------------------------------------------
170 /// `Trap imm` → deliberate fault (assertion failure, div-by-zero, bad
171 /// opcode encountered by a corrupt/foreign module, capability
172 /// violation). Propagates to the Flow supervisor as [`crate::FlowOutcome::Failed`].
173 Trap = 0x60,
174 /// `Nop` → no-op, used by the assembler to pad jump targets.
175 Nop = 0x61,
176}
177
178impl Opcode {
179 /// Decode a raw byte into an `Opcode`, used when loading foreign/untrusted
180 /// modules. Rejects anything outside the currently defined ISA rather
181 /// than transmuting garbage into a jump-table index (which is exactly
182 /// the class of bug that turns a VM into a code-execution primitive).
183 #[inline]
184 pub fn from_u8(byte: u8) -> Option<Opcode> {
185 use Opcode::*;
186 Some(match byte {
187 0x00 => Halt,
188 0x01 => LoadConst,
189 0x02 => Move,
190 0x03 => LoadImm,
191 0x10 => Add,
192 0x11 => Sub,
193 0x12 => Mul,
194 0x13 => Div,
195 0x14 => Mod,
196 0x15 => Neg,
197 0x18 => Eq,
198 0x19 => Lt,
199 0x1A => Le,
200 0x20 => Jump,
201 0x21 => Branch,
202 0x30 => Call,
203 0x31 => Return,
204 0x32 => CallNative,
205 0x40 => Spawn,
206 0x41 => Yield,
207 0x42 => Sleep,
208 0x43 => Exit,
209 0x44 => SelfPid,
210 0x50 => Send,
211 0x51 => Receive,
212 0x52 => ReceiveTimeout,
213 0x53 => ReceiveMatch,
214 0x54 => ReceiveMatchImm,
215 0x55 => Ask,
216 0x56 => Monitor,
217 0x57 => Demonitor,
218 0x58 => Link,
219 0x59 => Unlink,
220 0x5A => AskTimeout,
221 0x5B => Delegate,
222 0x5C => FreshRequestId,
223 0x5D => ReceiveMatchCorr,
224 0x5E => ReceiveMatchCorrImm,
225 0x60 => Trap,
226 0x62 => RegisterName,
227 0x63 => Whereis,
228 0x61 => Nop,
229 _ => return None,
230 })
231 }
232
233 #[inline]
234 pub fn as_u8(self) -> u8 {
235 self as u8
236 }
237}
238
239impl std::fmt::Display for Opcode {
240 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241 let name = match self {
242 Opcode::Halt => "Halt",
243 Opcode::LoadConst => "LoadConst",
244 Opcode::Move => "Move",
245 Opcode::LoadImm => "LoadImm",
246 Opcode::Add => "Add",
247 Opcode::Sub => "Sub",
248 Opcode::Mul => "Mul",
249 Opcode::Div => "Div",
250 Opcode::Mod => "Mod",
251 Opcode::Neg => "Neg",
252 Opcode::Eq => "Eq",
253 Opcode::Lt => "Lt",
254 Opcode::Le => "Le",
255 Opcode::Jump => "Jump",
256 Opcode::Branch => "Branch",
257 Opcode::Call => "Call",
258 Opcode::Return => "Return",
259 Opcode::CallNative => "CallNative",
260 Opcode::Spawn => "Spawn",
261 Opcode::Yield => "Yield",
262 Opcode::Sleep => "Sleep",
263 Opcode::Exit => "Exit",
264 Opcode::SelfPid => "SelfPid",
265 Opcode::Send => "Send",
266 Opcode::Receive => "Receive",
267 Opcode::ReceiveTimeout => "ReceiveTimeout",
268 Opcode::ReceiveMatch => "ReceiveMatch",
269 Opcode::ReceiveMatchImm => "ReceiveMatchImm",
270 Opcode::Ask => "Ask",
271 Opcode::Monitor => "Monitor",
272 Opcode::Demonitor => "Demonitor",
273 Opcode::Link => "Link",
274 Opcode::Unlink => "Unlink",
275 Opcode::AskTimeout => "AskTimeout",
276 Opcode::Delegate => "Delegate",
277 Opcode::FreshRequestId => "FreshRequestId",
278 Opcode::ReceiveMatchCorr => "ReceiveMatchCorr",
279 Opcode::ReceiveMatchCorrImm => "ReceiveMatchCorrImm",
280 Opcode::RegisterName => "RegisterName",
281 Opcode::Whereis => "Whereis",
282 Opcode::Trap => "Trap",
283 Opcode::Nop => "Nop",
284 };
285 f.write_str(name)
286 }
287}