372 lines
8.3 KiB
Markdown
372 lines
8.3 KiB
Markdown
< [Back](chapter-15.md) | [Summary](table-of-contents.md) >
|
|
|
|
# **Host ABI and Syscalls**
|
|
|
|
This chapter defines the Application Binary Interface (ABI) between the Prometeu Virtual Machine (PVM) and the host environment. It specifies how syscalls are identified, resolved, invoked, verified, and accounted for.
|
|
|
|
Syscalls provide controlled access to host-managed subsystems such as graphics, audio, input, asset banks, and persistent storage.
|
|
|
|
This chapter defines the **contract**. Individual subsystems (GFX, AUDIO, MEMCARD, ASSETS, etc.) define their own syscall tables that conform to this ABI.
|
|
|
|
---
|
|
|
|
## 1 Design Principles
|
|
|
|
The syscall system follows these rules:
|
|
|
|
1. **Deterministic**: Syscalls must behave deterministically for the same inputs and frame state.
|
|
2. **Synchronous**: Syscalls execute to completion within the current VM slice.
|
|
3. **Non-blocking**: Long operations must be modeled as request + status polling.
|
|
4. **Capability-gated**: Each syscall requires a declared capability.
|
|
5. **Stack-based ABI**: Arguments and return values are passed via VM slots.
|
|
6. **Not first-class**: Syscalls are callable but cannot be stored as values.
|
|
7. **Language-agnostic identity**: Syscalls are identified by canonical names, not language syntax.
|
|
|
|
---
|
|
|
|
## 2 Canonical Syscall Identity
|
|
|
|
Syscalls are identified by a **canonical triple**:
|
|
|
|
```
|
|
(module, name, version)
|
|
```
|
|
|
|
Example:
|
|
|
|
```
|
|
("gfx", "present", 1)
|
|
("input", "state", 1)
|
|
("audio", "play", 2)
|
|
```
|
|
|
|
This identity is:
|
|
|
|
* Independent of language syntax.
|
|
* Stable across languages and toolchains.
|
|
* Used for capability checks and linking.
|
|
|
|
### Language independence
|
|
|
|
Different languages may reference the same syscall using different syntax:
|
|
|
|
| Language style | Reference form |
|
|
| -------------- | --------------- |
|
|
| Dot-based | `gfx.present` |
|
|
| Namespace | `gfx::present` |
|
|
| Object-style | `Gfx.present()` |
|
|
| Functional | `present(gfx)` |
|
|
|
|
All of these map to the same canonical identity:
|
|
|
|
```
|
|
("gfx", "present", 1)
|
|
```
|
|
|
|
The compiler is responsible for this mapping.
|
|
|
|
---
|
|
|
|
## 3 Syscall Resolution
|
|
|
|
The host maintains a **Syscall Registry**:
|
|
|
|
```
|
|
(module, name, version) -> syscall_id
|
|
```
|
|
|
|
Example:
|
|
|
|
```
|
|
("gfx", "present", 1) -> 0x0101
|
|
("gfx", "submit", 1) -> 0x0102
|
|
("audio", "play", 1) -> 0x0201
|
|
```
|
|
|
|
### Resolution process
|
|
|
|
At cartridge load time:
|
|
|
|
1. The cartridge declares required syscalls using canonical identities.
|
|
2. The host verifies capabilities.
|
|
3. The host resolves each canonical identity to a numeric `syscall_id`.
|
|
4. The VM stores the resolved table for fast execution.
|
|
|
|
At runtime, the VM executes:
|
|
|
|
```
|
|
SYSCALL <id>
|
|
```
|
|
|
|
Only numeric IDs are used during execution.
|
|
|
|
---
|
|
|
|
## 4 Syscall Instruction Semantics
|
|
|
|
The VM provides a single instruction:
|
|
|
|
```
|
|
SYSCALL <id>
|
|
```
|
|
|
|
Where:
|
|
|
|
* `<id>` is a 32-bit integer identifying the syscall.
|
|
|
|
Execution steps:
|
|
|
|
1. The VM looks up syscall metadata using `<id>`.
|
|
2. The VM verifies that enough arguments exist on the stack.
|
|
3. The VM checks capability requirements.
|
|
4. The syscall executes in the host environment.
|
|
5. The syscall leaves exactly `ret_slots` values on the stack.
|
|
|
|
If any contract rule is violated, the VM traps.
|
|
|
|
---
|
|
|
|
## 5 Syscall Metadata Table
|
|
|
|
Each syscall is defined by a metadata entry.
|
|
|
|
### SyscallMeta structure
|
|
|
|
```
|
|
SyscallMeta {
|
|
id: u32
|
|
module: string
|
|
name: string
|
|
version: u16
|
|
arg_slots: u8
|
|
ret_slots: u8
|
|
capability: CapabilityId
|
|
may_allocate: bool
|
|
cost_hint: u32
|
|
}
|
|
```
|
|
|
|
Fields:
|
|
|
|
| Field | Description |
|
|
| -------------- | ------------------------------------------------ |
|
|
| `id` | Unique numeric syscall identifier |
|
|
| `module` | Canonical module name |
|
|
| `name` | Canonical syscall name |
|
|
| `version` | ABI version of the syscall |
|
|
| `arg_slots` | Number of input stack slots |
|
|
| `ret_slots` | Number of return stack slots |
|
|
| `capability` | Required capability |
|
|
| `may_allocate` | Whether the syscall may allocate VM heap objects |
|
|
| `cost_hint` | Expected cycle cost |
|
|
|
|
The verifier uses this table to validate stack effects.
|
|
|
|
---
|
|
|
|
## 6 Arguments and Return Values
|
|
|
|
Syscalls use the same slot-based ABI as functions.
|
|
|
|
### Argument passing
|
|
|
|
Arguments are pushed onto the stack before the syscall.
|
|
|
|
Example:
|
|
|
|
```
|
|
push a
|
|
push b
|
|
SYSCALL X // expects 2 arguments
|
|
```
|
|
|
|
### Return values
|
|
|
|
After execution, the syscall leaves exactly `ret_slots` values on the stack.
|
|
|
|
Example:
|
|
|
|
```
|
|
// before: []
|
|
SYSCALL input_state
|
|
// after: [held, pressed, released]
|
|
```
|
|
|
|
Composite return values are represented as multiple slots (stack tuples).
|
|
|
|
---
|
|
|
|
## 7 Syscalls as Callable Entities (Not First-Class)
|
|
|
|
Syscalls behave like functions in terms of arguments and return values, but they are **not first-class values**.
|
|
|
|
This means:
|
|
|
|
* Syscalls can be invoked.
|
|
* Syscalls cannot be stored in variables.
|
|
* Syscalls cannot be passed as arguments.
|
|
* Syscalls cannot be returned from functions.
|
|
|
|
Only user-defined functions and closures are first-class.
|
|
|
|
### Conceptual declaration
|
|
|
|
```
|
|
host fn input_state() -> (int, int, int)
|
|
```
|
|
|
|
This represents a syscall with three return values, but it cannot be treated as a function value.
|
|
|
|
---
|
|
|
|
## 8 Error Model: Traps vs Status Codes
|
|
|
|
Syscalls use a hybrid error model.
|
|
|
|
### Trap conditions (contract violations)
|
|
|
|
The VM traps when:
|
|
|
|
* The syscall id is invalid.
|
|
* The canonical identity cannot be resolved.
|
|
* The required capability is missing.
|
|
* The stack does not contain enough arguments.
|
|
* A handle is invalid or dead.
|
|
|
|
These are fatal contract violations.
|
|
|
|
### Status returns (domain conditions)
|
|
|
|
Normal operational states are returned as values.
|
|
|
|
Examples:
|
|
|
|
* asset not yet loaded
|
|
* audio voice unavailable
|
|
* memcard full
|
|
|
|
These are represented by status codes in return slots.
|
|
|
|
---
|
|
|
|
## 9 Capability System
|
|
|
|
Each syscall requires a capability.
|
|
|
|
Capabilities are declared by the cartridge manifest.
|
|
|
|
Example capability groups:
|
|
|
|
* `gfx`
|
|
* `audio`
|
|
* `input`
|
|
* `asset`
|
|
* `memcard`
|
|
|
|
If a syscall is invoked without the required capability:
|
|
|
|
* The VM traps.
|
|
|
|
---
|
|
|
|
## 10 Interaction with the Garbage Collector
|
|
|
|
The VM heap is managed by the GC. Host-managed memory is separate.
|
|
|
|
### Heap vs host memory
|
|
|
|
| Memory | Managed by | GC scanned |
|
|
| --------------- | ---------- | ---------- |
|
|
| VM heap objects | VM GC | Yes |
|
|
| Asset banks | Host | No |
|
|
| Audio buffers | Host | No |
|
|
| Framebuffers | Host | No |
|
|
|
|
Assets are addressed by identifiers, not VM heap handles.
|
|
|
|
### Host root rule
|
|
|
|
If a syscall stores a handle to a VM heap object beyond the duration of the call, it must register that handle as a **host root**.
|
|
|
|
This prevents the GC from collecting objects still in use by the host.
|
|
|
|
This rule applies only to VM heap objects (such as closures or user objects), not to asset identifiers or primitive values.
|
|
|
|
---
|
|
|
|
## 11 Determinism Rules
|
|
|
|
Syscalls must obey deterministic execution rules.
|
|
|
|
Forbidden behaviors:
|
|
|
|
* reading real-time clocks
|
|
* accessing non-deterministic OS APIs
|
|
* performing blocking I/O
|
|
|
|
Allowed patterns:
|
|
|
|
* frame-based timers
|
|
* request + poll status models
|
|
* event delivery at frame boundaries
|
|
|
|
---
|
|
|
|
## 12 Cost Model and Budgeting
|
|
|
|
Each syscall contributes to frame cost.
|
|
|
|
The VM tracks:
|
|
|
|
* cycles spent in syscalls
|
|
* syscall counts
|
|
* allocation cost (if any)
|
|
|
|
Example telemetry:
|
|
|
|
```
|
|
Frame 10231:
|
|
Syscalls: 12
|
|
Cycles (syscalls): 380
|
|
Allocations via syscalls: 2
|
|
```
|
|
|
|
Nothing is free.
|
|
|
|
---
|
|
|
|
## 13 Blocking and Long Operations
|
|
|
|
Syscalls must not block.
|
|
|
|
Long operations must use a two-phase model:
|
|
|
|
1. Request
|
|
2. Status polling or event notification
|
|
|
|
Example pattern:
|
|
|
|
```
|
|
asset.load(id)
|
|
...
|
|
status, progress = asset.status(id)
|
|
```
|
|
|
|
---
|
|
|
|
## 14 Summary
|
|
|
|
* Syscalls are deterministic, synchronous, and non-blocking.
|
|
* They use the same slot-based ABI as functions.
|
|
* They are callable but not first-class.
|
|
* They are identified canonically by `(module, name, version)`.
|
|
* Language syntax does not affect the ABI.
|
|
* The host resolves canonical identities to numeric syscall IDs.
|
|
* Capabilities control access to host subsystems.
|
|
* GC only manages VM heap objects.
|
|
* Host-held heap objects must be registered as roots.
|
|
* All syscall costs are tracked per frame.
|
|
|
|
|
|
|
|
< [Back](chapter-15.md) | [Summary](table-of-contents.md) > |