Files
cubelinux-2/cubecode/src/opcode.rs
T
CUBELinux-2 4484eba83d feat(cubelinux-2): Package 4 — cubecode (cubevm): code/data mapping + safe cube-addressed bytecode VM over CZYX
Implements the PDF's Package 4 (cubevm/cubecode): the cube as a substrate for
storing and introspecting code + AI artifacts.

- opcode.rs: decode/encode codec for a deterministic, safe bytecode (28 ops:
  stack arithmetic/logic/shift, comparisons, jumps, CALL_LINK, RET, SYSCALL,
  DUP/DROP). Body is always decoded through this codec before execution, so
  the cube never runs code that didn't survive decode.
- cell.rs: CodeCell + Kind (Fn/Kernel/Layer/Checkpoint/Variant/Other). Kind
  rides in the record doc_type; the call graph is the cube's linked_records,
  so CALL_LINK n executes linked_records[n].
- vm.rs: stack-based interpreter over CubeStore. CALL_LINK follows cube edges;
  call depth is bounded (no infinite cube loops); deterministic + no unsafe +
  no float, so runs are reproducible (prereq for 'navigate and reconstruct
  experiments'). Host syscalls introspect the cube (degree/link-exists/trace).

Design decision (the PDF's interesting tension): it says body may be
'bytecode, machine code, or serialized model weights' but the load-bearing
clause is a DSL/runtime that 'walks cube links to load and dispatch functions'
= addressable code-as-data. On this hardware a foreign-machine-code JIT is
neither safe nor needed, so we built the safe bytecode VM. cubetrace/
cubedbt/cubeai (execution capture + ML over traces) are the next layer and are
excluded here.

Verified: ./check (fmt+tests+clippy -D warnings) green; cubecode: 13 tests
(arith, div-by-zero fault, CALL_LINK edge-follow, bad-link fault, recursion
depth bound, syscall trace/degree). Gate: cde1e62 -> +Package4.
2026-08-10 19:55:08 -04:00

256 lines
7.4 KiB
Rust

//! Bytecode opcode set and wire codec for the cube VM (PDF Package 4).
//!
//! The VM is a safe, deterministic, stack-based interpreter. Code lives as
//! records in the CZYX cube (each record's body is a `Vec<Op>`), and the
//! call graph *is* the cube's association graph: `CALL_LINK` executes a
//! record that the current record links to. No foreign machine code is ever
//! executed — the body is always decoded through this codec first.
//!
//! Every op is one byte; ops that carry an operand append it as a second
//! byte. The encoding is self-delimiting so a truncated program fails
//! cleanly instead of mis-decoding.
/// A single VM instruction.
///
/// Arithmetic and logic are on `u8` with wrapping semantics (the cube's
/// cells are bytes). Jumps address the *decoded* instruction stream by
/// index, not raw byte offsets, so a program can be re-encoded without
/// invalidating jump targets.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Op {
/// Do nothing.
Nop,
/// Stop execution (pops the call stack; ends the program at the root).
Halt,
/// Push an immediate byte onto the data stack.
Const(u8),
/// Push local slot `i` (0..16) onto the data stack.
Load(u8),
/// Pop the data stack into local slot `i` (0..16).
Store(u8),
/// `a + b` (wrapping).
Add,
/// `a - b` (wrapping).
Sub,
/// `a * b` (wrapping).
Mul,
/// `a / b`; faults on divide-by-zero.
Div,
/// `a % b`; faults on modulo-by-zero.
Mod,
/// `a & b`.
And,
/// `a | b`.
Or,
/// `a ^ b`.
Xor,
/// `a << (b & 7)`.
Shl,
/// `a >> (b & 7)`.
Shr,
/// Push `1` if `a == b` else `0`.
Eq,
/// Push `1` if `a != b` else `0`.
Ne,
/// Push `1` if `a < b` (unsigned) else `0`.
Lt,
/// Push `1` if `a > b` (unsigned) else `0`.
Gt,
/// Push `1` if `a <= b` (unsigned) else `0`.
Le,
/// Push `1` if `a >= b` (unsigned) else `0`.
Ge,
/// Unconditional jump to instruction index `t`.
Jmp(u8),
/// Pop `v`; jump to `t` if `v == 0`, else fall through.
Jz(u8),
/// Pop `v`; jump to `t` if `v != 0`, else fall through.
Jnz(u8),
/// Call the record at `linked_records[link]` (the cube edge). Pushes a
/// return frame; the callee runs on the *shared* data stack, so callers
/// pass arguments by leaving them on the stack and read results after
/// `RET` returns.
CallLink(u8),
/// Return to the caller (pop the call frame). At the root, ends the run.
Ret,
/// Invoke host syscall `id` (see [`crate::vm::Vm::syscall`]).
Syscall(u8),
/// Duplicate the top of the data stack.
Dup,
/// Discard the top of the data stack.
Drop,
}
/// Error decoding a bytecode stream.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CodeError {
/// An operand byte was missing at the end of the stream.
Truncated,
/// An unknown opcode byte was encountered.
UnknownOp(u8),
}
const OP_CONST: u8 = 2;
const OP_LOAD: u8 = 3;
const OP_STORE: u8 = 4;
const OP_JMP: u8 = 21;
const OP_JZ: u8 = 22;
const OP_JNZ: u8 = 23;
const OP_CALLLINK: u8 = 24;
const OP_SYSCALL: u8 = 26;
/// Encode a sequence of ops into the wire format.
///
/// Single-byte ops map to their byte; operand ops append the operand. The
/// result is deterministic and reversible by [`decode`].
pub fn encode(ops: &[Op]) -> Vec<u8> {
let mut out = Vec::with_capacity(ops.len() * 2);
for op in ops {
match op {
Op::Nop => out.push(0),
Op::Halt => out.push(1),
Op::Const(v) => {
out.push(OP_CONST);
out.push(*v);
}
Op::Load(i) => {
out.push(OP_LOAD);
out.push(*i);
}
Op::Store(i) => {
out.push(OP_STORE);
out.push(*i);
}
Op::Add => out.push(5),
Op::Sub => out.push(6),
Op::Mul => out.push(7),
Op::Div => out.push(8),
Op::Mod => out.push(9),
Op::And => out.push(10),
Op::Or => out.push(11),
Op::Xor => out.push(12),
Op::Shl => out.push(13),
Op::Shr => out.push(14),
Op::Eq => out.push(15),
Op::Ne => out.push(16),
Op::Lt => out.push(17),
Op::Gt => out.push(18),
Op::Le => out.push(19),
Op::Ge => out.push(20),
Op::Jmp(t) => {
out.push(OP_JMP);
out.push(*t);
}
Op::Jz(t) => {
out.push(OP_JZ);
out.push(*t);
}
Op::Jnz(t) => {
out.push(OP_JNZ);
out.push(*t);
}
Op::CallLink(l) => {
out.push(OP_CALLLINK);
out.push(*l);
}
Op::Ret => out.push(25),
Op::Syscall(id) => {
out.push(OP_SYSCALL);
out.push(*id);
}
Op::Dup => out.push(27),
Op::Drop => out.push(28),
}
}
out
}
fn next(b: &[u8], i: &mut usize) -> Result<u8, CodeError> {
if *i >= b.len() {
return Err(CodeError::Truncated);
}
let v = b[*i];
*i += 1;
Ok(v)
}
/// Decode a wire-format bytecode stream back into ops.
///
/// Returns [`CodeError::Truncated`] if an operand is missing and
/// [`CodeError::UnknownOp`] for unrecognized opcode bytes.
pub fn decode(b: &[u8]) -> Result<Vec<Op>, CodeError> {
let mut ops = Vec::new();
let mut i = 0;
while i < b.len() {
let byte = b[i];
i += 1;
let op = match byte {
0 => Op::Nop,
1 => Op::Halt,
OP_CONST => Op::Const(next(b, &mut i)?),
OP_LOAD => Op::Load(next(b, &mut i)?),
OP_STORE => Op::Store(next(b, &mut i)?),
5 => Op::Add,
6 => Op::Sub,
7 => Op::Mul,
8 => Op::Div,
9 => Op::Mod,
10 => Op::And,
11 => Op::Or,
12 => Op::Xor,
13 => Op::Shl,
14 => Op::Shr,
15 => Op::Eq,
16 => Op::Ne,
17 => Op::Lt,
18 => Op::Gt,
19 => Op::Le,
20 => Op::Ge,
OP_JMP => Op::Jmp(next(b, &mut i)?),
OP_JZ => Op::Jz(next(b, &mut i)?),
OP_JNZ => Op::Jnz(next(b, &mut i)?),
OP_CALLLINK => Op::CallLink(next(b, &mut i)?),
25 => Op::Ret,
OP_SYSCALL => Op::Syscall(next(b, &mut i)?),
27 => Op::Dup,
28 => Op::Drop,
other => return Err(CodeError::UnknownOp(other)),
};
ops.push(op);
}
Ok(ops)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode_roundtrip() {
let prog = vec![
Op::Const(42),
Op::Store(3),
Op::Load(3),
Op::Const(2),
Op::Mul,
Op::Jz(0),
Op::CallLink(1),
Op::Syscall(0),
Op::Halt,
];
let bytes = encode(&prog);
assert_eq!(decode(&bytes).unwrap(), prog);
}
#[test]
fn truncated_operand_is_error() {
// Op::Const (2) with no following byte.
assert_eq!(decode(&[2]).unwrap_err(), CodeError::Truncated);
}
#[test]
fn unknown_op_is_error() {
assert_eq!(decode(&[0xFF]).unwrap_err(), CodeError::UnknownOp(0xFF));
}
}