374 lines
8.6 KiB
Markdown
374 lines
8.6 KiB
Markdown
## PR-03 — Frame model v0: locals, operand stack, and function metadata
|
||
|
||
**Why:** `let x: int = 1` failing usually means locals/frames are not modeled correctly.
|
||
|
||
### Scope
|
||
|
||
* Define `FunctionMeta`:
|
||
|
||
* `code_offset`, `code_len`
|
||
* `param_slots`, `local_slots`, `return_slots`
|
||
* `max_stack_slots` (computed by verifier or compiler)
|
||
* Define `Frame`:
|
||
|
||
* `base` (stack base index)
|
||
* `locals_base` (or equivalent)
|
||
* `return_slots`
|
||
* `pc_return`
|
||
* Decide representation:
|
||
|
||
* Option A (recommended v0): **single VM stack** with fixed layout per frame:
|
||
|
||
* `[args][locals][operand_stack...]`
|
||
* Use `base + local_index` addressing.
|
||
|
||
### Deliverables
|
||
|
||
* `CallStack` with `Vec<Frame>`
|
||
* `enter_frame(meta)` allocates locals area (zero-init)
|
||
* `leave_frame()` reclaims to previous base
|
||
|
||
### Tests
|
||
|
||
* locals are isolated per call
|
||
* locals are zero-initialized
|
||
* stack is restored exactly after return
|
||
|
||
### Acceptance
|
||
|
||
* Locals are deterministic and independent from operand stack usage.
|
||
|
||
---
|
||
|
||
## PR-04 — Locals opcodes: GET_LOCAL / SET_LOCAL / INIT_LOCAL
|
||
|
||
**Why:** PBS `let` and parameters need first-class support.
|
||
|
||
### Scope
|
||
|
||
* Implement opcodes:
|
||
|
||
* `GET_LOCAL <u16 slot>` pushes value slots
|
||
* `SET_LOCAL <u16 slot>` pops value slots and writes
|
||
* `INIT_LOCAL <u16 slot>` (optional) for explicit initialization semantics
|
||
* Enforce bounds: local slot index must be within `[0..param+local_slots)`
|
||
* Enforce slot width: if types are multi-slot, compiler emits multiple GET/SET or uses `*_N` variants.
|
||
|
||
### Deliverables
|
||
|
||
* `LocalAddressing` utilities
|
||
* Deterministic trap codes:
|
||
|
||
* `TRAP_INVALID_LOCAL`
|
||
* `TRAP_LOCAL_WIDTH_MISMATCH` (if enforced)
|
||
|
||
### Tests
|
||
|
||
* `let x: int = 1; return x;` works
|
||
* invalid local index traps
|
||
|
||
### Acceptance
|
||
|
||
* `let` works reliably; no stack side effects beyond specified pops/pushes.
|
||
|
||
---
|
||
|
||
## PR-05 — Core arithmetic + comparisons in VM (int/bounded/bool)
|
||
|
||
**Why:** The minimal executable PBS needs arithmetic that doesn’t corrupt stack.
|
||
|
||
### Scope
|
||
|
||
* Implement v0 numeric opcodes (slot-safe):
|
||
|
||
* `IADD, ISUB, IMUL, IDIV, IMOD`
|
||
* `ICMP_EQ, ICMP_NE, ICMP_LT, ICMP_LE, ICMP_GT, ICMP_GE`
|
||
* `BADD, BSUB, ...` (or unify with tagged values)
|
||
* Define conversion opcodes if lowering expects them:
|
||
|
||
* `BOUND_TO_INT`, `INT_TO_BOUND_CHECKED` (trap OOB)
|
||
|
||
### Deliverables
|
||
|
||
* Deterministic traps:
|
||
|
||
* `TRAP_DIV_ZERO`
|
||
* `TRAP_OOB` (bounded checks)
|
||
|
||
### Tests
|
||
|
||
* simple arithmetic chain
|
||
* div by zero traps
|
||
* bounded conversions trap on overflow
|
||
|
||
### Acceptance
|
||
|
||
* Arithmetic and comparisons are closed and verified.
|
||
|
||
---
|
||
|
||
## PR-06 — Control flow opcodes: jumps, conditional branches, structured “if”
|
||
|
||
**Why:** `if` must be predictable and verifier-safe.
|
||
|
||
### Scope
|
||
|
||
* Implement opcodes:
|
||
|
||
* `JMP <i32 rel>`
|
||
* `JMP_IF_TRUE <i32 rel>`
|
||
* `JMP_IF_FALSE <i32 rel>`
|
||
* Verifier rules:
|
||
|
||
* targets must be valid instruction boundaries
|
||
* stack height at join points must match
|
||
|
||
### Tests
|
||
|
||
* nested if
|
||
* if with empty branches
|
||
* branch join mismatch rejected
|
||
|
||
### Acceptance
|
||
|
||
* Control flow is safe; no implicit stack juggling.
|
||
|
||
---
|
||
|
||
## PR-07 — Calling convention v0: CALL / RET / multi-slot returns
|
||
|
||
**Why:** Without a correct call model, PBS isn’t executable.
|
||
|
||
### Scope
|
||
|
||
* Introduce `CALL <u16 func_id>`
|
||
|
||
* caller pushes args (slots)
|
||
* callee frame allocates locals
|
||
* Introduce `RET`
|
||
|
||
* callee must leave exactly `return_slots` on operand stack at `RET`
|
||
* VM pops frame and transfers return slots to caller
|
||
* Define return mechanics for `void` (`return_slots=0`)
|
||
|
||
### Deliverables
|
||
|
||
* `FunctionTable` indexing and bounds checks
|
||
* Deterministic traps:
|
||
|
||
* `TRAP_INVALID_FUNC`
|
||
* `TRAP_BAD_RET_SLOTS`
|
||
|
||
### Tests
|
||
|
||
* `fn add(a:int,b:int):int { return a+b; }`
|
||
* multi-slot return (e.g., `Pad` flattened)
|
||
* void call
|
||
|
||
### Acceptance
|
||
|
||
* Calls are stable and stack-clean.
|
||
|
||
---
|
||
|
||
## PR-08 — Host syscalls v0: stable ABI, multi-slot args/returns
|
||
|
||
**Why:** PBS relies on deterministic syscalls; ABI must be frozen and enforced.
|
||
|
||
### Scope
|
||
|
||
* Unify syscall invocation opcode:
|
||
|
||
* `SYSCALL <u16 id> <u8 arg_slots> <u8 ret_slots>`
|
||
* Runtime validates:
|
||
|
||
* pops `arg_slots`
|
||
* pushes `ret_slots`
|
||
* Implement/confirm:
|
||
|
||
* `GfxClear565 (0x1010)`
|
||
* `InputPadSnapshot (0x2010)`
|
||
* `InputTouchSnapshot (0x2011)`
|
||
|
||
### Deliverables
|
||
|
||
* A `SyscallRegistry` mapping id -> handler + signature
|
||
* Deterministic traps:
|
||
|
||
* `TRAP_INVALID_SYSCALL`
|
||
* `TRAP_SYSCALL_SIG_MISMATCH`
|
||
|
||
### Tests
|
||
|
||
* syscall isolated tests
|
||
* wrong signature traps
|
||
|
||
### Acceptance
|
||
|
||
* Syscalls are “industrial”: typed by signature, deterministic, no host surprises.
|
||
|
||
---
|
||
|
||
## PR-09 — Debug info v0: spans, symbols, and traceable traps
|
||
|
||
**Why:** Industrial debugging requires actionable failures.
|
||
|
||
### Scope
|
||
|
||
* Add optional debug section:
|
||
|
||
* per-instruction span table (`pc -> (file_id, start, end)`)
|
||
* function names
|
||
* Enhance trap payload with debug span (if present)
|
||
|
||
### Tests
|
||
|
||
* trap includes span when debug present
|
||
* trap still works without debug
|
||
|
||
### Acceptance
|
||
|
||
* You can pinpoint “where” a trap happened reliably.
|
||
|
||
---
|
||
|
||
## PR-10 — Program image + linker: imports/exports resolved before VM run
|
||
|
||
**Why:** Imports are compile-time, but we need an industrial linking model for multi-module PBS.
|
||
|
||
### Scope
|
||
|
||
* Define in bytecode:
|
||
|
||
* `exports`: symbol -> func_id/service entry (as needed)
|
||
* `imports`: symbol refs -> relocation slots
|
||
* Implement a **linker** that:
|
||
|
||
* builds a `ProgramImage` from N modules
|
||
* resolves imports to exports
|
||
* produces a single final `FunctionTable` and code blob
|
||
|
||
### Notes
|
||
|
||
* VM **does not** do name lookup at runtime.
|
||
* Linking errors are deterministic: `LINK_UNRESOLVED_SYMBOL`, `LINK_DUP_EXPORT`, etc.
|
||
|
||
### Tests
|
||
|
||
* two-module link success
|
||
* unresolved import fails
|
||
* duplicate export fails
|
||
|
||
### Acceptance
|
||
|
||
* Multi-module PBS works; “import” is operationalized correctly.
|
||
|
||
---
|
||
|
||
## PR-11 — Canonical integration cartridge + golden bytecode snapshots
|
||
|
||
**Why:** One cartridge must be the unbreakable reference.
|
||
|
||
### Scope
|
||
|
||
* Create `CartridgeCanonical.pbs` that covers:
|
||
|
||
* locals
|
||
* arithmetic
|
||
* if
|
||
* function call
|
||
* syscall clear
|
||
* input snapshot
|
||
* Add `golden` artifacts:
|
||
|
||
* canonical AST JSON (frontend)
|
||
* IR Core (optional)
|
||
* IR VM / bytecode dump
|
||
* expected VM trace (optional)
|
||
|
||
### Tests
|
||
|
||
* CI runs cartridge and checks:
|
||
|
||
* no traps
|
||
* deterministic output state
|
||
|
||
### Acceptance
|
||
|
||
* This cartridge is the “VM heartbeat test”.
|
||
|
||
---
|
||
|
||
## PR-12 — VM test harness: stepper, trace, and property tests
|
||
|
||
**Why:** Industrial quality means test tooling, not just “it runs”.
|
||
|
||
### Scope
|
||
|
||
* Add `VmRunner` test harness:
|
||
|
||
* step limit
|
||
* deterministic trace of stack deltas
|
||
* snapshot of locals
|
||
* Add property tests (lightweight):
|
||
|
||
* stack never underflows in verified programs
|
||
* verified programs never jump out of bounds
|
||
|
||
### Acceptance
|
||
|
||
* Debugging is fast, and regressions are caught.
|
||
|
||
---
|
||
|
||
## PR-13 — Optional: Refactor Value representation (tagged slots) for clarity
|
||
|
||
**Why:** If current `Value` representation is the source of complexity/bugs, refactor now.
|
||
|
||
### Scope (only if needed)
|
||
|
||
* Make `Slot` explicit:
|
||
|
||
* `Slot::I32`, `Slot::I64`, `Slot::U32`, `Slot::Bool`, `Slot::ConstId`, `Slot::GateId`, `Slot::Unit`
|
||
* Multi-slot types become sequences of slots.
|
||
|
||
### Acceptance
|
||
|
||
* Simpler, more verifiable runtime.
|
||
|
||
---
|
||
|
||
# Work split (what can be parallel later)
|
||
|
||
* VM core correctness: PR-01..PR-08 (sequential, contract-first)
|
||
* Debug + tooling: PR-09, PR-12 (parallel after PR-03)
|
||
* Linking/imports: PR-10 (parallel after PR-01)
|
||
* Canonical cartridge: PR-11 (parallel after PR-05)
|
||
|
||
---
|
||
|
||
# “Stop the line” rules
|
||
|
||
1. If a PR introduces an opcode without stack spec + verifier integration, it’s rejected.
|
||
2. If a PR changes bytecode layout without bumping version, it’s rejected.
|
||
3. If a PR adds a feature before the canonical cartridge passes, it’s rejected.
|
||
|
||
---
|
||
|
||
# First implementation target (tomorrow morning, start here)
|
||
|
||
**Start with PR-02 (Opcode spec + verifier)** even if you think you already know the bug.
|
||
Once the verifier exists, the rest becomes mechanical: every failure becomes *actionable*.
|
||
|
||
## Definition of Done (DoD) for PBS v0 “minimum executable”
|
||
|
||
A single canonical cartridge runs end-to-end:
|
||
|
||
* `let` declarations (locals)
|
||
* arithmetic (+, -, *, /, %, comparisons)
|
||
* `if/else` control flow
|
||
* `when` expression (if present in lowering)
|
||
* function calls with params + returns (including `void`)
|
||
* multiple return slots (flattened structs / hardware value types)
|
||
* host syscalls (e.g., `GfxClear565`, `InputPadSnapshot`, `InputTouchSnapshot`)
|
||
* deterministic traps (OOB bounded, invalid local, invalid call target, stack underflow) |