10 KiB
Host ABI and Syscalls
Domain: host ABI structure Function: normative
This chapter defines the structural ABI between the PVM and the host environment.
It focuses on:
- syscall identity;
- load-time resolution;
- instruction-level call semantics;
- metadata shape;
- argument and return-slot contract.
Operational policies such as capabilities, fault classes, determinism, GC interaction, budgeting, and blocking are split into a companion chapter.
Per DEC-0009, debug tooling and certification are host-owned concerns. This chapter therefore excludes general-purpose guest-visible debug syscalls from the canonical public ABI.
1 Design Principles
The syscall ABI follows these rules:
- Stack-based ABI: arguments and return values are passed through VM slots.
- Canonical identity: host services are named by stable
(module, name, version). - Load-time resolution: symbolic host bindings are resolved before execution.
- Metadata-driven execution: arity and result shape come from syscall metadata.
- Not first-class: syscalls are callable but not ordinary function values.
- Status-first operational policy: across syscall domains, operationally observable failure must surface through explicit
statusvalues, while only structural violations fault asTrapand only internal invariant breaks escalate asPanic.
2 Canonical Syscall Identity
Syscalls are identified by a canonical triple:
(module, name, version)
Example:
("gfx", "present", 1)
("audio", "play", 2)
("composer", "emit_sprite", 1)
This identity is:
- language-independent;
- toolchain-stable;
- used for linking and capability gating.
Input queries are VM-owned intrinsic calls in v1 and are outside the syscall identity space.
Syscall identity is transport, not the conceptual boundary for runtime
profiles. Game and System may both use syscall transport internally or at
their ABI edge, but that does not make their public authoring surfaces the same
ABI. System APIs, when exposed to PBS or other guest languages, belong to a
dedicated System profile ABI and stdlib/framework.
3 Syscall Resolution
The host maintains a registry:
(module, name, version) -> syscall_id
At load time:
- the cartridge declares required syscalls by canonical identity;
- bytecode encodes host-backed call sites as
HOSTCALL <sysc_index>; - the loader validates and resolves those identities;
- the loader rewrites
HOSTCALL <sysc_index>intoSYSCALL <id>; - the executable image uses only numeric syscall ids at runtime.
Raw SYSCALL <id> is not valid in a PBX pre-load artifact and must be rejected there.
4 Syscall Instruction Semantics
Pre-load artifact form:
HOSTCALL <sysc_index>
Final executable form:
SYSCALL <id>
Where:
<sysc_index>indexes the program-declared syscall table;<id>is the final numeric host syscall id.
Execution steps:
- the VM looks up syscall metadata by
<id>; - the VM ensures the argument contract is satisfiable;
- the syscall executes in the host environment;
- the syscall produces exactly the declared
ret_slots.
The execution result must respect the canonical runtime boundary defined with 16a-syscall-policies.md:
- successful or operationally rejected execution returns values in the declared stack shape;
Trapis reserved for structural ABI misuse or invalid call shape;Panicis reserved for runtime/host invariant failure.
5 Syscall Metadata Table
Each syscall is defined by metadata.
Conceptual 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 loader uses this table to resolve identities and validate declared ABI shape.
6 Arguments and Return Values
Syscalls use the same slot-oriented argument/return philosophy as ordinary calls.
Argument passing
Arguments are pushed before the syscall.
Example:
push a
push b
SYSCALL X
Return values
After execution, the syscall leaves exactly ret_slots values on the stack.
Composite results use multiple stack slots rather than implicit hidden structures.
Return shape must also follow the transversal status-first policy in 16a-syscall-policies.md:
- when a syscall exposes operational failure,
status:intis the canonical first return slot; - operations with observable operational failure paths must expose that explicit
status:intreturn; - operations with no real operational error path may remain
void(ret_slots = 0); - if extra payload is returned together with
status, the payload must follow the leading status slot in the declared stack shape; - stack shape remains strict in both cases and must match syscall metadata exactly.
MEMCARD game surface (mem, v1)
The game memcard profile uses module mem with status-first return shapes.
mem is a domain layer backed by runtime fs (it is not a separate storage backend).
The mem module is a game profile surface. It is not the public storage model
for System profile apps.
Canonical operations in v1 are:
mem.slot_count() -> (status, count)mem.slot_stat(slot) -> (status, state, used_bytes, generation, checksum)mem.slot_read(slot, offset, max_bytes) -> (status, payload_hex, bytes_read)mem.slot_write(slot, offset, payload_hex) -> (status, bytes_written)mem.slot_commit(slot) -> statusmem.slot_clear(slot) -> status
Semantics and domain status catalog are defined by 08-save-memory-and-memcard.md.
Asset surface (asset, v1)
The asset runtime surface also follows the status-first ABI shape.
Canonical operations in v1 are:
asset.load(asset_id, slot) -> (status, handle)asset.status(handle) -> statusasset.commit(handle) -> statusasset.cancel(handle) -> status
For asset.load:
asset_idis a signed 32-bit runtime identity;slotis the target slot index;- bank kind is resolved from
asset_tablebyasset_id, not supplied by the caller.
Bank diagnostics surface (bank, v1)
DEC-0009 narrows the public bank contract:
bank.info(bank_type) -> (used_slots, total_slots)is the only surviving public bank diagnostic shape in v1;bank.infoexists only as a cheap deterministic operational summary aligned with the slot-first bank telemetry contract from15-asset-management.md;bank.slot_infois host/debug tooling surface and is not part of the canonical public guest ABI;- JSON-on-the-wire bank inspection payloads are not valid public ABI;
bank.inforeturns stack values, not textual structured payloads.
GFX surface (gfx, v1)
The public GFX ABI uses format-neutral operation names. API and syscall names MUST NOT encode RGB565 or any other pixel format suffix.
Canonical operations in v1 include:
gfx.clear(color) -> voidgfx.fill_rect(x, y, w, h, color) -> voidgfx.draw_line(x0, y0, x1, y1, color) -> voidgfx.draw_circle(x, y, radius, color) -> voidgfx.draw_disc(x, y, radius, color) -> voidgfx.draw_square(x, y, size, color) -> voidgfx.draw_text(x, y, text, color) -> void
Rules:
colorarguments are packed RGBA8888 values using RGBA channel order.- RGB565 raw values are not a public ABI contract.
Gfx*565syscall names andgfx.*_565canonical identities are not valid current ABI.- The runtime framebuffer exposed across the host boundary is RGBA8888, not
u16RGB565.
Composition surface (composer, v1)
The canonical frame-orchestration public ABI uses module composer.
The composer module is a game profile surface. It is not inherited by
System profile apps as their public UI/composition API.
Canonical operations in v1 are:
composer.bind_scene(bank_id) -> (status)composer.unbind_scene() -> (status)composer.set_camera(x, y) -> voidcomposer.emit_sprite(glyph_id, palette_id, x, y, layer, bank_id, flip_x, flip_y, priority) -> (status)
For mutating composer operations:
statusis aComposerOpStatusvalue;bind_scene,unbind_scene, andemit_spriteare status-returning;set_cameraremainsvoidin v1;- no caller-provided sprite index or
activeflag is part of the canonical contract.
Fatal boundary clarification for bind_scene:
bind_sceneremains status-returning for ordinary orchestration-domain rejection such as unresolved target scene slot;- missing glyph dependencies inside a resident scene are not accepted as passive
ComposerOpStatusoutcomes; - if scene activation or later composition detects an unresolved scene glyph dependency, runtime MUST escalate that condition as a fatal machine error with clear logging.
7 Syscalls as Callable Entities (Not First-Class)
Syscalls behave like call sites, not like first-class guest values.
This means:
- syscalls can be invoked;
- syscalls cannot be stored in variables;
- syscalls cannot be passed as function values;
- syscalls cannot be returned as closures.
Only user-defined functions and closures are first-class.
8 Relationship to Other Specs
16a-syscall-policies.mddefines operational policies layered on top of this ABI.15-asset-management.mddefines asset-domain semantics behind some syscall families.08-save-memory-and-memcard.mddefines persistent save-domain semantics.02a-vm-values-and-calling-convention.mddefines the slot/call philosophy reused by the syscall ABI.