427 lines
8.8 KiB
Markdown
427 lines
8.8 KiB
Markdown
< [Back](chapter-1.md) | [Summary](table-of-contents.md) | [Next](chapter-3.md) >
|
||
|
||
# **Prometeu Virtual Machine (PVM)**
|
||
|
||
This chapter defines the execution model, value system, calling convention, memory model, and host interface of the Prometeu Virtual Machine.
|
||
|
||
The PVM is a **deterministic, stack-based VM** designed for a 2D fantasy console environment. Its primary goal is to provide predictable performance, safe memory access, and a stable execution contract suitable for real-time games running at a fixed frame rate. fileciteturn2file0
|
||
|
||
---
|
||
|
||
## 1 Core Design Principles
|
||
|
||
The PVM is designed around the following constraints:
|
||
|
||
1. **Deterministic execution**: no hidden threads or asynchronous callbacks.
|
||
2. **Frame-based timing**: execution is bounded by frame time.
|
||
3. **Safe memory model**: all heap objects are accessed through handles.
|
||
4. **Simple compilation target**: stack-based bytecode with verified control flow.
|
||
5. **Stable ABI**: multi-value returns with fixed slot semantics.
|
||
6. **First-class functions**: functions can be passed, stored, and returned.
|
||
|
||
---
|
||
|
||
## 2 Execution Model
|
||
|
||
The PVM executes bytecode in a **frame loop**. Each frame:
|
||
|
||
1. The firmware enters the VM.
|
||
2. The VM runs until:
|
||
|
||
* the frame budget is consumed, or
|
||
* a `FRAME_SYNC` instruction is reached.
|
||
3. At `FRAME_SYNC`:
|
||
|
||
* events are delivered
|
||
* input is sampled
|
||
* optional GC may run
|
||
4. Control returns to the firmware.
|
||
|
||
`FRAME_SYNC` is the **primary safepoint** in the system.
|
||
|
||
---
|
||
|
||
## 3 Value Types
|
||
|
||
All runtime values are stored in VM slots as a `Value`.
|
||
|
||
### Primitive value types (stack values)
|
||
|
||
| Type | Description |
|
||
| ------- | --------------------- |
|
||
| `int` | 64-bit signed integer |
|
||
| `bool` | Boolean value |
|
||
| `float` | 64-bit floating point |
|
||
|
||
### Built-in vector and graphics types (stack values)
|
||
|
||
These are treated as VM primitives with dedicated opcodes:
|
||
|
||
| Type | Description |
|
||
| ------- | --------------------------------- |
|
||
| `vec2` | 2D vector (x, y) |
|
||
| `color` | Packed color value |
|
||
| `pixel` | Combination of position and color |
|
||
|
||
These types:
|
||
|
||
* live entirely on the stack
|
||
* are copied by value
|
||
* never allocate on the heap
|
||
|
||
### Heap values
|
||
|
||
All user-defined objects live on the heap and are accessed via **handles**.
|
||
|
||
| Type | Description |
|
||
| -------- | -------------------------- |
|
||
| `handle` | Reference to a heap object |
|
||
| `null` | Null handle |
|
||
|
||
Handles may refer to:
|
||
|
||
* user objects
|
||
* arrays
|
||
* strings
|
||
* closures
|
||
|
||
---
|
||
|
||
## 4 Handles and Gate Table
|
||
|
||
Heap objects are accessed through **handles**. A handle is a pair:
|
||
|
||
```
|
||
handle = { index, generation }
|
||
```
|
||
|
||
The VM maintains a **gate table**:
|
||
|
||
```
|
||
GateEntry {
|
||
alive: bool
|
||
generation: u32
|
||
base: usize
|
||
slots: u32
|
||
type_id: u32
|
||
}
|
||
```
|
||
|
||
When an object is freed:
|
||
|
||
* its gate entry is marked dead
|
||
* its generation is incremented
|
||
|
||
If a handle’s generation does not match the gate entry, the VM traps.
|
||
|
||
This prevents use-after-free bugs.
|
||
|
||
---
|
||
|
||
## 5 Heap Model
|
||
|
||
* All user objects live in the heap.
|
||
* Objects are fixed-layout blocks of slots.
|
||
* No inheritance at the memory level.
|
||
* Traits/interfaces are resolved by the compiler or via vtables.
|
||
|
||
Built-in types remain stack-only.
|
||
|
||
Heap objects include:
|
||
|
||
* user structs/classes
|
||
* strings
|
||
* arrays
|
||
* closures
|
||
|
||
---
|
||
|
||
## 6 Tuples and Multi-Return ABI
|
||
|
||
The PVM supports **multi-value returns**.
|
||
|
||
### Tuple rules
|
||
|
||
* Tuples are **stack-only**.
|
||
* Maximum tuple arity is **N = 6**.
|
||
* Tuples are not heap objects by default.
|
||
* To persist a tuple, it must be explicitly boxed.
|
||
|
||
### Call convention
|
||
|
||
Each function declares a fixed `ret_slots` value.
|
||
|
||
At call time:
|
||
|
||
1. Caller prepares arguments.
|
||
2. `CALL` transfers control.
|
||
3. Callee executes.
|
||
4. `RET` leaves exactly `ret_slots` values on the stack.
|
||
|
||
The verifier ensures that:
|
||
|
||
* all control paths produce the same `ret_slots`
|
||
* stack depth is consistent.
|
||
|
||
---
|
||
|
||
## 7 Call Stack and Frames
|
||
|
||
The VM uses a **call stack**.
|
||
|
||
Each frame contains:
|
||
|
||
```
|
||
Frame {
|
||
return_pc
|
||
base_pointer
|
||
ret_slots
|
||
}
|
||
```
|
||
|
||
Execution uses only the following call instructions:
|
||
|
||
| Opcode | Description |
|
||
| ------ | ---------------------- |
|
||
| `CALL` | Calls a function by id |
|
||
| `RET` | Returns from function |
|
||
|
||
There is no separate `PUSH_FRAME` or `POP_FRAME` instruction in the public ISA.
|
||
|
||
---
|
||
|
||
## 8 Closures and First-Class Functions
|
||
|
||
Closures are heap objects and represent **function values**.
|
||
|
||
The PVM treats functions as **first-class values**. This means:
|
||
|
||
* Functions can be stored in variables.
|
||
* Functions can be passed as arguments.
|
||
* Functions can be returned from other functions.
|
||
* All function values are represented as closures.
|
||
|
||
Even functions without captures are represented as closures with an empty capture set.
|
||
|
||
### Closure layout
|
||
|
||
```
|
||
Closure {
|
||
func_id
|
||
captures[]
|
||
}
|
||
```
|
||
|
||
Captures are stored as handles or value copies.
|
||
|
||
All closure environments are part of the GC root set.
|
||
|
||
### Direct and indirect calls
|
||
|
||
The PVM supports two forms of function invocation:
|
||
|
||
| Opcode | Description |
|
||
| -------------- | -------------------------------------- |
|
||
| `CALL` | Direct call by function id |
|
||
| `CALL_CLOSURE` | Indirect call through a closure handle |
|
||
|
||
For `CALL_CLOSURE`:
|
||
|
||
1. The closure handle is read from the stack.
|
||
2. The VM extracts the `func_id` from the closure.
|
||
3. The function is invoked using the closure’s captures as its environment.
|
||
|
||
The verifier ensures that:
|
||
|
||
* The closure handle is valid.
|
||
* The target function’s arity matches the call site.
|
||
* The `ret_slots` contract is respected.
|
||
|
||
---
|
||
|
||
## 9 Coroutines (Deterministic)
|
||
|
||
The PVM supports **cooperative coroutines**.
|
||
|
||
Characteristics:
|
||
|
||
* Coroutines are scheduled deterministically.
|
||
* No preemption.
|
||
* No parallel execution.
|
||
* All scheduling happens at safepoints.
|
||
|
||
Each coroutine contains:
|
||
|
||
```
|
||
Coroutine {
|
||
call_stack
|
||
operand_stack
|
||
state
|
||
}
|
||
```
|
||
|
||
### Coroutine instructions
|
||
|
||
| Opcode | Description |
|
||
| ------- | -------------------------- |
|
||
| `SPAWN` | Creates a coroutine |
|
||
| `YIELD` | Suspends current coroutine |
|
||
| `SLEEP` | Suspends for N frames |
|
||
|
||
Scheduling is:
|
||
|
||
* round-robin
|
||
* deterministic
|
||
* bounded by frame budget
|
||
|
||
Coroutine stacks are part of the GC root set.
|
||
|
||
---
|
||
|
||
## 10 Garbage Collection
|
||
|
||
The PVM uses a **mark-sweep collector**.
|
||
|
||
### GC rules
|
||
|
||
* GC runs only at **safepoints**.
|
||
* The primary safepoint is `FRAME_SYNC`.
|
||
* GC is triggered by:
|
||
|
||
* heap thresholds, or
|
||
* allocation pressure.
|
||
|
||
### Root set
|
||
|
||
The GC marks from:
|
||
|
||
* operand stack
|
||
* call stack frames
|
||
* global variables
|
||
* coroutine stacks
|
||
* closure environments
|
||
* host-held handles
|
||
|
||
The collector:
|
||
|
||
* does not compact memory (v1)
|
||
* uses free lists for reuse
|
||
|
||
---
|
||
|
||
## 11 Event and Interrupt Model
|
||
|
||
The PVM does not allow asynchronous callbacks.
|
||
|
||
All events are:
|
||
|
||
* queued by the firmware
|
||
* delivered at `FRAME_SYNC`
|
||
|
||
This ensures:
|
||
|
||
* deterministic execution
|
||
* predictable frame timing
|
||
|
||
Coroutines are the only supported concurrency mechanism.
|
||
|
||
---
|
||
|
||
## 12 Host Interface (Syscalls)
|
||
|
||
All hardware access occurs through syscalls.
|
||
|
||
Syscalls are:
|
||
|
||
* synchronous
|
||
* deterministic
|
||
* capability-checked
|
||
|
||
They operate on the following subsystems:
|
||
|
||
### Graphics
|
||
|
||
* tilebanks
|
||
* layers
|
||
* sprites
|
||
* palette control
|
||
* fade registers
|
||
* frame present
|
||
|
||
### Audio
|
||
|
||
* voice allocation
|
||
* play/stop
|
||
* volume/pan/pitch
|
||
* steal policy
|
||
|
||
### Input
|
||
|
||
* sampled once per frame
|
||
* exposed as frame state
|
||
|
||
### Assets
|
||
|
||
Asset banks are **host-owned memory**.
|
||
|
||
The VM interacts through handles:
|
||
|
||
| Syscall | Description |
|
||
| -------------- | ---------------------------- |
|
||
| `asset.load` | Request asset load into slot |
|
||
| `asset.status` | Query load state |
|
||
| `asset.commit` | Activate loaded asset |
|
||
|
||
Asset memory:
|
||
|
||
* is not part of the VM heap
|
||
* is not scanned by GC
|
||
|
||
### Save Memory (MEMCARD)
|
||
|
||
| Syscall | Description |
|
||
| --------------- | --------------- |
|
||
| `mem.read_all` | Read save data |
|
||
| `mem.write_all` | Write save data |
|
||
| `mem.commit` | Persist save |
|
||
| `mem.size` | Query capacity |
|
||
|
||
---
|
||
|
||
## 13 Verifier Requirements
|
||
|
||
Before execution, bytecode must pass the verifier.
|
||
|
||
The verifier ensures:
|
||
|
||
1. Valid jump targets
|
||
2. Stack depth consistency
|
||
3. Correct `ret_slots` across all paths
|
||
4. Handle safety rules
|
||
5. Closure call safety
|
||
6. No invalid opcode sequences
|
||
|
||
Invalid bytecode is rejected.
|
||
|
||
---
|
||
|
||
## 14 Summary
|
||
|
||
The Prometeu VM is:
|
||
|
||
* stack-based
|
||
* deterministic
|
||
* frame-synchronized
|
||
* handle-based for heap access
|
||
* multi-return capable
|
||
* first-class function capable
|
||
* coroutine-driven for concurrency
|
||
|
||
This design balances:
|
||
|
||
* ease of compilation
|
||
* predictable performance
|
||
* safety and debuggability
|
||
* suitability for real-time 2D games.
|
||
|
||
< [Back](chapter-1.md) | [Summary](table-of-contents.md) | [Next](chapter-3.md) > |