Mach VM Model

Overview

Mach is the register-based execution form for streamlined Mcode IR. The compiler lowers source to mcode, the optimizer simplifies it, and the Mach serializer turns the result into compact bytecode for the VM.

Mcode remains the authoritative instruction-set reference. This page describes what the VM model means at runtime: registers, frames, values, heap objects, calls, tail calls, disruption handlers, immutability, and garbage collection boundaries.

Instruction Words

Mach bytecode uses fixed-width instruction words. The opcode selects the operation; the remaining fields are interpreted as register indexes, constant indexes, or jump offsets.

The ordinary form is one 32-bit word with 8-bit A/B/C fields. When an A, B, or C register index does not fit, the instruction is preceded by one wide word. The prefix’s A/B/C bytes are the high bytes of the following word’s fields, so each register index is then 16 bits. For iABx, the prefix’s Bx is the high 16 bits of the following word’s Bx, producing a 32-bit unsigned index. The prefix does not widen signed jump offsets: iAsBx keeps its signed 16-bit offset and isJ keeps its signed 24-bit offset.

The prefix and its following word are one logical instruction. Branches and debug locations name the prefix, never the second word. A narrow instruction has no prefix, remains byte-for-byte unchanged, and the VM executes it with the same one indexed instruction fetch as before. Wide call-argument payload words use the same prefix rule.

Constant operands are pool-global VALUES indices. Lowering records every constant-bearing logical instruction and branch target in its fragment; after pool-wide interning, the Pit linker rewrites those operands, inserts wide at the u16 boundary, and rebases branches, entry/disruption PCs, PGO sites, and origin locations. REGEXP literals are normalized through two LOADK operations into scratch registers, so REGEXP’s B/C fields remain register operands.

Four logical formats are used:

iABC - Three Small Operands

Used for operations such as dest = left + right, property access, or setting call arguments.

op, A, B, C

iABx - Register and Constant Index

Used when an instruction needs one register plus a larger constant-pool index.

op, A, Bx

iAsBx - Register and Signed Offset

Used for conditional jumps.

op, A, signed_offset

isJ - Signed Jump

Used for unconditional jumps.

op, signed_offset

The exact packed bit layout is a current-runtime bytecode detail. Artifact records say when a payload is Mach bytecode; portable interchange should use the higher-level executable and mcode records instead.

Registers and Frames

Mach is a register VM. Each function runs with a fixed set of register slots computed by the compiler.

  • Argument slots hold the function inputs.
  • Local slots hold var and def values.
  • Closure slots hold captured values from outer functions.
  • Temporary slots hold intermediate expression results.

A frame is the live execution state for one function call. Conceptually it contains the function being executed, the caller to resume when it returns, the current instruction position, and the register slots for that function.

Frames are also heap-managed values because closures can keep outer variables alive after the creating function has returned. The VM treats frames as managed runtime objects, not raw stack memory exposed to user code.

The frame budget

A frame carries at most 65535 register slots. The FUNCTIONS row already stores arity, close_slots, and frame_slots as u16; the wide prefix lets instructions address the same range. Slot index 0xffff remains unavailable to a frame of that size because the count’s valid indexes end at 0xfffe; the runtime can therefore continue using 0xffff as its no-return-slot sentinel.

Modules with more than 255 live slots are ordinary wide functions. A module lowers as a root function, and every live top-level binding holds one of that function’s slots for the whole body, but crossing the old 8-bit boundary adds prefix words only to instructions that actually name the high slots. The compiler still refuses a function whose frame count exceeds the u16 descriptor, by name and as an ordinary compile error.

Values in Registers

Every register slot holds a Pit value. Small integers, logical values, null, and some short text values can be immediate. Arrays, records, longer text, blobs, functions, code, and frames are heap-managed values referenced from the slot.

The VM does not expose the representation to programs. User code observes the language types and operations documented in the language reference. The representation matters to Mach because it determines whether an operation can run directly, needs a runtime helper, or must disrupt.

See Values for the current value model.

Heap Objects

Mach works with a small set of heap object categories:

ObjectVM role
ArrayOrdered, integer-indexed storage used by array literals, push, pop, and indexed access
RecordKey/value storage used by records, modules, runtime contexts, and messages
TextImmutable script-visible text, plus temporary mutable text during optimized concatenation
BlobBit-addressed binary data used by binary modules and I/O
FunctionCallable code, whether implemented by Mach bytecode or by C
CodeCompiled function bodies and constant pools
FrameLive or captured function execution state

Forwarding objects can appear internally while the copying collector is moving objects, but they are a GC implementation detail rather than a script-visible category.

The exact in-memory layout, table sizes, and allocation formulas are runtime implementation details. The VM contract is higher level: slots hold managed values, heap objects can move during GC, and every operation must keep live values reachable through VM slots or runtime roots.

Object Operations

The compiler emits mcode guard sequences before memory operations. After streamlining, Mach should receive canonical operations whose runtime shape is already known:

  • load reads from a valid array or record.
  • store writes to a valid mutable array or record.
  • Assigning null to a record field removes that field.
  • Invalid array indexes and writes to immutable objects disrupt.
  • length reads the logical length of arrays, text, blobs, records, or functions as defined by the language.
  • push and pop operate on arrays.

This split keeps type dispatch mostly in mcode, leaving Mach to execute compact operations against checked values.

Text Construction

Script-visible text is immutable, but repeated concatenation needs an efficient path. The compiler lowers text concatenation to a temporary mutable text object:

  1. Create mutable text with enough capacity.
  2. Append the left text.
  3. Append the right text.
  4. Stone the text before it escapes the defining slot.

The streamline optimizer inserts stone slot, slot when mutable text would escape into a return value, call argument, closure, record, array, or any other observable location. See Stone Values for the immutability model.

Calls

Normal function calls are decomposed into:

  1. Create a call frame for the callee.
  2. Copy arguments into that frame.
  3. Invoke the frame and write the result to a destination register.

For Mach functions, invocation switches to the callee frame and later resumes the caller. For C or other native functions, invocation crosses into the host runtime and writes the returned value back into the caller frame.

Explicit Tail Calls

The source-level go statement is an explicit tail call. It is lowered to a tail-call frame followed by a tail invoke.

Conceptually:

  • The current function does not resume after go.
  • The callee receives the supplied arguments.
  • The callee’s result becomes the current function’s result.
  • The caller chain is preserved as if the current frame had been replaced by the callee frame.

This is different from an ordinary call followed by return: go states the tail-call intent directly in the source language and lets Mach reuse or replace the current frame.

Disruption Handlers

Functions may carry a disruption handler entry point. If a disrupting operation occurs and the current function has a handler, control transfers to that handler with the same managed frame context. When the call chain reaches its end with the disruption still unhandled, the actor crashes.

Because disruption handling needs a resumable current frame, the compiler rejects some combinations that would make explicit tail calls ambiguous, such as go inside a function with a disruption clause.

Arithmetic and Comparisons

Mcode emits explicit type checks and dispatch branches before arithmetic when types are not statically known. Once Mach executes arithmetic instructions, it can use fast numeric paths because mcode has already established the expected shape: number or null at the arithmetic seam.

Arithmetic is total. A non-number operand yields null rather than disrupting, so the checks in front of an arithmetic instruction select between the numeric path and producing null. Null is absorbing.

Comparisons preserve language semantics: equality is defined over every pair of values, while ordering is defined for two numbers or two texts and disrupts on any other pairing. Concatenation converts number operands to text and disrupts when an operand has no text form.

Immutability and GC Boundary

Mach runs inside one actor heap at a time. Actor heaps are collected by a copying collector, so any heap object reference may move during allocation. The VM and native helpers must keep live values in managed slots or explicit runtime roots.

Stone values are immutable. Attempts to write through Mach operations into a stoned array, record, blob, or text disrupt. Stoned heap values still belong to the actor heap unless they are runtime constants; the collector can move them like other heap objects.

Relationship to Other Specs

  • Mcode IR lists the instruction set that Mach encodes.
  • Streamline simplifies mcode before it is lowered to mach.
  • Values describes the current value model.
  • Stone Values describes immutability.
  • Artifact Formats describes where Mach bytecode appears in executable and boot artifacts.