prometeu-runtime/docs/specs/runtime/16-host-abi-and-syscalls.md
2026-04-19 08:40:22 +01:00

8.4 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:

  1. Stack-based ABI: arguments and return values are passed through VM slots.
  2. Canonical identity: host services are named by stable (module, name, version).
  3. Load-time resolution: symbolic host bindings are resolved before execution.
  4. Metadata-driven execution: arity and result shape come from syscall metadata.
  5. Not first-class: syscalls are callable but not ordinary function values.
  6. Status-first operational policy: across syscall domains, operationally observable failure must surface through explicit status values, while only structural violations fault as Trap and only internal invariant breaks escalate as Panic.

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.

3 Syscall Resolution

The host maintains a registry:

(module, name, version) -> syscall_id

At load time:

  1. the cartridge declares required syscalls by canonical identity;
  2. bytecode encodes host-backed call sites as HOSTCALL <sysc_index>;
  3. the loader validates and resolves those identities;
  4. the loader rewrites HOSTCALL <sysc_index> into SYSCALL <id>;
  5. 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:

  1. the VM looks up syscall metadata by <id>;
  2. the VM ensures the argument contract is satisfiable;
  3. the syscall executes in the host environment;
  4. 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;
  • Trap is reserved for structural ABI misuse or invalid call shape;
  • Panic is 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:int is the canonical first return slot;
  • operations with observable operational failure paths must expose that explicit status:int return;
  • 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).

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) -> status
  • mem.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) -> status
  • asset.commit(handle) -> status
  • asset.cancel(handle) -> status

For asset.load:

  • asset_id is a signed 32-bit runtime identity;
  • slot is the target slot index;
  • bank kind is resolved from asset_table by asset_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.info exists only as a cheap deterministic operational summary aligned with the slot-first bank telemetry contract from 15-asset-management.md;
  • bank.slot_info is 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.info returns stack values, not textual structured payloads.

Composition surface (composer, v1)

The canonical frame-orchestration public ABI uses module composer.

Canonical operations in v1 are:

  • composer.bind_scene(bank_id) -> (status)
  • composer.unbind_scene() -> (status)
  • composer.set_camera(x, y) -> void
  • composer.emit_sprite(glyph_id, palette_id, x, y, layer, bank_id, flip_x, flip_y, priority) -> (status)

For mutating composer operations:

  • status is a ComposerOpStatus value;
  • bind_scene, unbind_scene, and emit_sprite are status-returning;
  • set_camera remains void in v1;
  • no caller-provided sprite index or active flag is part of the canonical contract.

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