166 lines
5.0 KiB
Markdown
166 lines
5.0 KiB
Markdown
---
|
|
title: "Register VM"
|
|
description: "Register-based virtual machine (Mach)"
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Mach VM is a register-based virtual machine using 32-bit instructions. It is modeled after Lua's register VM — operands are register indices rather than stack positions, reducing instruction count and improving performance.
|
|
|
|
## Instruction Formats
|
|
|
|
All instructions are 32 bits wide. Four encoding formats are used:
|
|
|
|
### iABC — Three-Register
|
|
|
|
```
|
|
[op: 8][A: 8][B: 8][C: 8]
|
|
```
|
|
|
|
Used for operations on three registers: `R(A) = R(B) op R(C)`.
|
|
|
|
### iABx — Register + Constant
|
|
|
|
```
|
|
[op: 8][A: 8][Bx: 16]
|
|
```
|
|
|
|
Used for loading constants: `R(A) = K(Bx)`.
|
|
|
|
### iAsBx — Register + Signed Offset
|
|
|
|
```
|
|
[op: 8][A: 8][sBx: 16]
|
|
```
|
|
|
|
Used for conditional jumps: if `R(A)` then jump by `sBx`.
|
|
|
|
### isJ — Signed Jump
|
|
|
|
```
|
|
[op: 8][sJ: 24]
|
|
```
|
|
|
|
Used for unconditional jumps with a 24-bit signed offset.
|
|
|
|
## Registers
|
|
|
|
Each function frame has a fixed number of register slots, determined at compile time. Registers hold:
|
|
|
|
- **R(0)** — `this` binding
|
|
- **R(1)..R(arity)** — function arguments
|
|
- **R(arity+1)..** — local variables and temporaries
|
|
|
|
## Instruction Set
|
|
|
|
### Loading
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `LOADK` | iABx | `R(A) = K(Bx)` — load from constant pool |
|
|
| `LOADI` | iAsBx | `R(A) = sBx` — load small integer |
|
|
| `LOADNULL` | iA | `R(A) = null` |
|
|
| `LOADTRUE` | iA | `R(A) = true` |
|
|
| `LOADFALSE` | iA | `R(A) = false` |
|
|
| `MOVE` | iABC | `R(A) = R(B)` — register copy |
|
|
|
|
### Arithmetic
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `ADD` | iABC | `R(A) = R(B) + R(C)` |
|
|
| `SUB` | iABC | `R(A) = R(B) - R(C)` |
|
|
| `MUL` | iABC | `R(A) = R(B) * R(C)` |
|
|
| `DIV` | iABC | `R(A) = R(B) / R(C)` |
|
|
| `MOD` | iABC | `R(A) = R(B) % R(C)` |
|
|
| `POW` | iABC | `R(A) = R(B) ^ R(C)` |
|
|
| `NEG` | iABC | `R(A) = -R(B)` |
|
|
| `INC` | iABC | `R(A) = R(B) + 1` |
|
|
| `DEC` | iABC | `R(A) = R(B) - 1` |
|
|
|
|
### Comparison
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `EQ` | iABC | `R(A) = R(B) == R(C)` |
|
|
| `NEQ` | iABC | `R(A) = R(B) != R(C)` |
|
|
| `LT` | iABC | `R(A) = R(B) < R(C)` |
|
|
| `LE` | iABC | `R(A) = R(B) <= R(C)` |
|
|
| `GT` | iABC | `R(A) = R(B) > R(C)` |
|
|
| `GE` | iABC | `R(A) = R(B) >= R(C)` |
|
|
|
|
### Property Access
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `GETFIELD` | iABC | `R(A) = R(B)[K(C)]` — named property |
|
|
| `SETFIELD` | iABC | `R(A)[K(B)] = R(C)` — set named property |
|
|
| `GETINDEX` | iABC | `R(A) = R(B)[R(C)]` — computed property |
|
|
| `SETINDEX` | iABC | `R(A)[R(B)] = R(C)` — set computed property |
|
|
|
|
### Variable Resolution
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `GETNAME` | iABx | Unresolved variable (compiler placeholder) |
|
|
| `GETINTRINSIC` | iABx | Global intrinsic / built-in |
|
|
| `GETENV` | iABx | Module environment variable |
|
|
| `GETUP` | iABC | `R(A) = UpFrame(B).slots[C]` — closure upvalue |
|
|
| `SETUP` | iABC | `UpFrame(A).slots[B] = R(C)` — set closure upvalue |
|
|
|
|
### Control Flow
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `JMP` | isJ | Unconditional jump |
|
|
| `JMPTRUE` | iAsBx | Jump if `R(A)` is true |
|
|
| `JMPFALSE` | iAsBx | Jump if `R(A)` is false |
|
|
| `JMPNULL` | iAsBx | Jump if `R(A)` is null |
|
|
|
|
### Function Calls
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `CALL` | iABC | Call `R(A)` with `B` args starting at `R(A+1)`, `C`=keep result |
|
|
| `RETURN` | iA | Return `R(A)` |
|
|
| `RETNIL` | — | Return null |
|
|
| `CLOSURE` | iABx | Create closure from function pool entry `Bx` |
|
|
|
|
### Object / Array
|
|
|
|
| Opcode | Format | Description |
|
|
|--------|--------|-------------|
|
|
| `NEWOBJECT` | iA | `R(A) = {}` |
|
|
| `NEWARRAY` | iABC | `R(A) = array(B)` |
|
|
| `PUSH` | iABC | Push `R(B)` to array `R(A)` |
|
|
|
|
## JSCodeRegister
|
|
|
|
The compiled output for a function:
|
|
|
|
```c
|
|
struct JSCodeRegister {
|
|
uint16_t arity; // argument count
|
|
uint16_t nr_slots; // total register count
|
|
uint32_t cpool_count; // constant pool size
|
|
JSValue *cpool; // constant pool
|
|
uint32_t instr_count; // instruction count
|
|
MachInstr32 *instructions; // 32-bit instruction array
|
|
uint32_t func_count; // nested function count
|
|
JSCodeRegister **functions; // nested function table
|
|
JSValue name; // function name
|
|
uint16_t disruption_pc; // exception handler offset
|
|
};
|
|
```
|
|
|
|
The constant pool holds all non-immediate values referenced by `LOADK` instructions: strings, large numbers, and other constants.
|
|
|
|
### Constant Pool Index Overflow
|
|
|
|
Named property instructions (`LOAD_FIELD`, `STORE_FIELD`, `DELETE`) use the iABC format where the constant pool key index occupies an 8-bit field (max 255). When a function references more than 256 unique property names, the serializer automatically falls back to a two-instruction sequence:
|
|
|
|
1. `LOADK tmp, key_index` — load the key string into a temporary register (iABx, 16-bit index)
|
|
2. `LOAD_DYNAMIC` / `STORE_DYNAMIC` / `DELETEINDEX` — use the register-based variant
|
|
|
|
This is transparent to the mcode compiler and streamline optimizer.
|