Bytecode

RustScript compiles source into a Program containing compact stack-machine bytecode. The interpreter, debugger, trace recorder, AOT compiler, and VMBC serializer share this representation.

Program layout

A Program carries the executable and its runtime metadata:

  • constants: Vec<Value>
  • code: Vec<u8>
  • local_count: usize
  • imports: Vec<HostImport>
  • debug: Option<DebugInfo>
  • type_map: Option<TypeMap>
  • script-function, callable-prototype, function-region, and root-binding tables

Program owns callable prototypes and callbacks derived from them. Replacing a program invalidates callbacks tied to the previous program.

TypeMap

TypeMap records typed lowering information used by the interpreter and native backends:

  • operand_types: HashMap<usize, (ValueType, ValueType)>
  • local_types: Vec<ValueType>

operand_types is keyed by bytecode offset and records inferred operand pairs for binary operations and comparisons. local_types is a post-lowering local-slot summary. Compiler-inserted null clears and hidden frame slots can widen entries to Unknown or Null, so it is not a source-level binding table.

Instruction encoding

Each instruction begins with one byte containing an OpCode. Operands follow in little-endian form as u8, u16, or u32 values. Branch operands contain absolute bytecode targets.

Core stack and control opcodes include:

OpcodeMnemonicOperandsStack or control effect
0x00nopNo change.
0x01retReturn from the active continuation or halt the root.
0x02ldcu32 indexPush a constant.
0x03add(a, b) → a + b.
0x04sub(a, b) → a - b.
0x05mul(a, b) → a * b.
0x06div(a, b) → a / b.
0x07nega → -a.
0x08ceq(a, b) → a == b.
0x09clt(a, b) → a < b.
0x0Acgt(a, b) → a > b.
0x0Bbru32 targetSet the instruction pointer to target.
0x0Cbrfalseu32 targetPop a boolean and branch when false.
0x0DpopDiscard the top value.
0x0EdupDuplicate the top value.
0x0Fldlocu8 indexPush a local.
0x10stlocu8 indexPop into a local.
0x11callu16 id, u8 argcDispatch a builtin or direct host import.
0x12shlShift left.
0x13shrShift right.
0x14modRemainder.
0x15andEager logical and.
0x16orEager logical or.

The current OpCode enum also covers collection operations, callable values, frame management, borrows, and compiler-generated lifetime operations. Treat the Rust enum and VMBC decoder as authoritative for the complete set.

Calls and continuations

Direct call dispatch remains separate from callable-value dispatch:

1. Builtins use fixed reserved indices from BuiltinFunction::call_index(). 2. Runtime host imports are remapped to dense per-program import slots and emitted as call <slot>, <argc>. 3. Host functions used as values, RustScript function items, and closures execute through callvalue <argc>.

callvalue validates the callable prototype, schema, arity, and frame layout before entering a script body or host callable. ret completes the active typed continuation: root halt, caller resume, or host return.

VMBC wire format

VMBC v9 serializes the Program needed by another runtime. It carries constants, bytecode, imports, debug/source information when included, type metadata, script functions, callable prototypes, function regions, and root bindings.

Use the released runner to emit and inspect artifacts:

pd-vm-run --emit-vmbc out/example.vmbc examples/example.rss
pd-vm-run --disasm-vmbc out/example.vmbc
pd-vm-run --disasm-vmbc out/example.vmbc --show-source

Assembler API

assemble() parses textual assembly into a Program. Assembler and BytecodeBuilder provide lower-level construction APIs.

Data declarations:

  • const NAME VALUE
  • string NAME "..."

Directives:

  • .data and .code switch sections.
  • .label NAME defines a jump label.
  • .local NAME [INDEX] defines a named local.
.data
const two 2
string greeting "hello"
.code
.local counter
.label loop
ldc two
stloc counter
ldloc counter
ldc 1
sub
dup
stloc counter
brfalse done
br loop
.label done
ldc greeting
ret

Embedded VMBC runtime

The pd-vm-nostd crate decodes VMBC v9 and executes it using only core and alloc. It supports direct bytecode execution, script frames and callable values, synchronous host callbacks, and instruction fuel. Source compilation, debugger transports, JIT/AOT, asynchronous host operations, and operating-system integration remain in pd-vm.

Compile .rss to VMBC on a host, then load that artifact through pd_vm_nostd on the target. The PlatformIO Arduino-Pico integration is maintained in micro-rustscript.