Bytecode Compilation
Quartz includes a stack-based bytecode virtual machine for faster execution. This document describes the bytecode format, instruction set, and how to work with compiled code.
Overview
The bytecode VM provides:
- Faster execution — 2-5x faster than interpreter for compute-heavy code
- Ahead-of-time compilation — Compile once, run many times
- Smaller memory footprint — Bytecode is more compact than AST
- Optimization opportunities — Constant folding, dead code elimination
Using Bytecode Mode
Compile and Run
# Compile to bytecode and run immediately
quartz -c script.qz
Ahead-of-Time Compilation
# Compile to .qzb file
quartz -b script.qz
# This creates script.qzb
# Run the compiled bytecode
quartz script.qzb
Disassemble Bytecode
# View bytecode instructions
python3 tools/qzb_disasm.py script.qzb
File Format (.qzb)
The .qzb file format:
┌─────────────────────────────────┐
│ Magic: "QZB\0" (4 bytes) │
├─────────────────────────────────┤
│ Version: u16 │
├─────────────────────────────────┤
│ Flags: u32 │
├─────────────────────────────────┤
│ Constant Pool │
│ ├─ Count: u32 │
│ └─ Constants: Value[] │
├─────────────────────────────────┤
│ String Table │
│ ├─ Count: u32 │
│ └─ Strings: String[] │
├─────────────────────────────────┤
│ Function Table │
│ ├─ Count: u32 │
│ └─ Functions: FuncDef[] │
├─────────────────────────────────┤
│ Bytecode │
│ ├─ Size: u32 │
│ └─ Instructions: u8[] │
└─────────────────────────────────┘
VM Architecture
Stack-Based Execution
The VM uses an operand stack for computations:
let x = 1 + 2
Execution:
LOAD_CONST 1 # Stack: [1]
LOAD_CONST 2 # Stack: [1, 2]
ADD # Stack: [3]
STORE_LOCAL 0 # Stack: [], x = 3
Call Frame Stack
Function calls use a separate call frame stack:
┌───────────────────┐
│ Frame 2: inner() │ ← Current
│ - Locals: [...] │
│ - Return addr │
├───────────────────┤
│ Frame 1: outer() │
│ - Locals: [...] │
│ - Return addr │
├───────────────────┤
│ Frame 0: main │
│ - Globals: [...] │
└───────────────────┘
Instruction Set
Stack Operations
| Opcode | Args | Description |
|---|---|---|
| `LOAD_CONST` | index | Push constant from pool |
| `LOAD_LOCAL` | index | Push local variable |
| `STORE_LOCAL` | index | Pop and store to local |
| `LOAD_GLOBAL` | index | Push global variable |
| `STORE_GLOBAL` | index | Pop and store to global |
| `POP` | — | Discard top of stack |
| `DUP` | — | Duplicate top of stack |
Arithmetic Operations
| Opcode | Description | Stack Effect |
|---|---|---|
| `ADD` | Addition | [a, b] → [a+b] |
| `SUB` | Subtraction | [a, b] → [a-b] |
| `MUL` | Multiplication | [a, b] → [a*b] |
| `DIV` | Division | [a, b] → [a/b] |
| `MOD` | Modulo | [a, b] → [a%b] |
| `NEG` | Negate | [a] → [-a] |
Comparison Operations
| Opcode | Description | Stack Effect |
|---|---|---|
| `EQ` | Equal | [a, b] → [a==b] |
| `NE` | Not equal | [a, b] → [a!=b] |
| `LT` | Less than | [a, b] → [a |
| `LE` | Less or equal | [a, b] → [a<=b] |
| `GT` | Greater than | [a, b] → [a>b] |
| `GE` | Greater or equal | [a, b] → [a>=b] |
Logical Operations
| Opcode | Description | Stack Effect |
|---|---|---|
| `AND` | Logical AND | [a, b] → [a&&b] |
| `OR` | Logical OR | [a, b] → [a||b] |
| `NOT` | Logical NOT | [a] → [!a] |
Control Flow
| Opcode | Args | Description |
|---|---|---|
| `JUMP` | offset | Unconditional jump |
| `JUMP_IF_FALSE` | offset | Jump if top is false |
| `JUMP_IF_TRUE` | offset | Jump if top is true |
| `LOOP` | offset | Jump backward (for loops) |
Function Operations
| Opcode | Args | Description |
|---|---|---|
| `CALL` | argc | Call function with argc args |
| `CALL_NATIVE` | index, argc | Call native function |
| `RETURN` | — | Return from function |
| `MAKE_CLOSURE` | index | Create closure |
Object Operations
| Opcode | Args | Description |
|---|---|---|
| `MAKE_ARRAY` | size | Create array from stack |
| `MAKE_DICT` | size | Create dict from stack |
| `GET_INDEX` | — | Array/dict index access |
| `SET_INDEX` | — | Array/dict index assignment |
| `GET_PROP` | index | Object property access |
| `SET_PROP` | index | Object property assignment |
| `NEW_INSTANCE` | index | Create class instance |
Example: Compilation
Source Code
fn factorial(n) {
if (n <= 1) {
return 1
}
return n * factorial(n - 1)
}
io.println(factorial(5))
Compiled Bytecode
; Function: factorial
; Locals: [n]
0000: LOAD_LOCAL 0 ; load n
0002: LOAD_CONST 0 ; load 1
0004: LE ; n <= 1
0005: JUMP_IF_FALSE 0011 ; skip if false
0008: LOAD_CONST 0 ; load 1
000A: RETURN ; return 1
000B: LOAD_LOCAL 0 ; load n
000D: LOAD_LOCAL 0 ; load n
000F: LOAD_CONST 0 ; load 1
0011: SUB ; n - 1
0012: CALL 1 ; factorial(n-1)
0014: MUL ; n * factorial(n-1)
0015: RETURN ; return result
; Main
0016: LOAD_CONST 1 ; load 5
0018: CALL 1 ; factorial(5)
001A: CALL_NATIVE 0, 1 ; io.println
001D: POP
001E: HALT
Optimizations
Constant Folding
let x = 2 + 3 * 4 // Compiled as: let x = 14
Dead Code Elimination
fn example() {
return 42
io.println("unreachable") // Removed
}
Inline Caching
Property lookups are cached for repeated access patterns.
Peephole Optimization
; Before
LOAD_CONST 0
POP
; After (removed entirely)
Performance Comparison
| Benchmark | Interpreter | Bytecode VM | Speedup |
|---|---|---|---|
| Fibonacci(30) | 450ms | 180ms | 2.5x |
| Tight loop 1M | 120ms | 35ms | 3.4x |
| Array operations | 85ms | 42ms | 2.0x |
| String concat | 95ms | 78ms | 1.2x |
💡 When to Use Bytecode Mode
- Use bytecode for production, benchmarks, compute-heavy code
- Use interpreter for development, debugging, small scripts
Development Tools
Disassembler
python3 tools/qzb_disasm.py script.qzb
Output:
=== Quartz Bytecode Disassembly ===
File: script.qzb
Version: 1
Constant Pool: 5 entries
0: (int) 1
1: (int) 5
...
Functions: 1
factorial(1 args, 1 locals)
Code:
0000: LOAD_LOCAL 0
0002: LOAD_CONST 0
...
Inspector
python3 tools/qzb_inspect.py script.qzb
Shows file structure, metadata, and statistics.
Bytecode Verifier
python3 tools/verify_bytecode.py script.qzb
Validates bytecode for correctness.
See Also: Architecture Overview →