This document defines the stable ABI used by native codegen/runtime boundaries.
TONIC_RUNTIME_ABI_VERSION = 1- ABI version is carried by
TCallContext.abi_version. - Mismatched versions return
TCallStatus::InvalidAbi.
TValue is #[repr(C)] and has fixed v1 layout:
- size: 16 bytes
- alignment: 8 bytes
- fields:
tag: u8(TValueTag)ownership: u8(TOwnership)reserved: u16(must be0)payload: u64
Immediate payloads:
Int=> two's-complementi64inpayloadBool=>0or1Nil=>0
Heap payloads (refcounted handle IDs):
Float,String,Atom,List,Map,Keyword,Tuple2,ResultOk,ResultErr,Closure,Range
Closure handle representation (Closure tag):
- payload references a refcounted runtime closure cell
- closure cell contains parameter names, lowered body ops, and captured lexical environment snapshot
retain_tvalue/release_tvaluegovern closure lifetime across call boundaries
v1 uses deterministic handle-based reference counting:
- heap values are stored in an internal handle table
retain_tvalueincrements refcountrelease_tvaluedecrements refcount and frees at zero- releasing an unknown/already-freed handle returns deterministic
AbiErrorCode::InvalidHandle - ownership/tag misuse returns deterministic
AbiErrorCode::OwnershipViolationorTagHandleMismatch
native_abi::memory_stats_snapshot() exposes deterministic heap counters:
allocations_totalreclaims_totalactive_handlesactive_handles_high_water
These counters are monotonic where applicable and are intended for roadmap baseline capture before collector behavior changes.
TCallContext:
abi_versionargcargv: *const TValue
TCallResult:
status: TCallStatus(Ok | Err | Panic | InvalidAbi)value: TValue(valid forOk)error: TValue(string payload forErr/Panic/InvalidAbi)
invoke_runtime_boundary(...) behavior:
- validates ABI version + frame shape
- decodes args via
tvalue_to_runtime - catches panics (
catch_unwind) and returnsPanicstatus instead of unwinding across boundary - maps helper/runtime ABI failures to deterministic
Errresult
Host interop introduces a separate compatibility marker for host-function contracts:
TONIC_HOST_INTEROP_ABI_VERSION = 1
Boundary entrypoints for host interop continue to use the same core call ABI:
tonic_rt_host_call: extern "C" fn(TCallContext) -> TCallResulttonic_rt_protocol_dispatch: extern "C" fn(TCallContext) -> TCallResult
Errors from unknown host functions, key-type mismatch, and arity mismatch are returned as
TCallStatus::Err with deterministic string payloads in TCallResult.error.
The HostRegistry (see src/interop.rs) holds all registered host functions. The following table lists currently registered keys and their arity:
| Key | Arity | Returns |
|---|---|---|
sys_path_exists |
1 | Bool |
sys_list_files_recursive |
1 | List[String] — sorted relative paths; symlinks skipped (lstat); errors on missing/empty path |
sys_ensure_dir |
1 | Bool |
sys_remove_tree |
1 | Bool — true removed, false not found; symlinks removed as files (lstat); errors on empty path |
sys_write_text |
2 | Bool |
sys_append_text |
2 | Bool |
sys_write_text_atomic |
2 | Bool |
sys_lock_acquire |
1 | Bool (true when lock file is acquired, false when already held) |
sys_lock_release |
1 | Bool (true when released, false when missing) |
sys_read_text |
1 | String |
sys_read_stdin |
0 | String |
sys_append_text appends bytes to the target file and creates parent directories on demand.
sys_write_text_atomic writes via a temporary sibling file and rename replacement.
sys_lock_acquire/sys_lock_release provide advisory lock-file semantics for persistence workflows.
| Key | Arity | Returns |
|---|---|---|
sys_run |
1 | Map {exit_code, output} |
sys_sleep_ms |
1 | Bool |
sys_retry_plan |
7 | Map {retry, delay_ms, source} |
sys_log |
3 | Bool |
sys_argv |
0 | List[String] |
sys_env |
1 | String | Nil |
sys_which |
1 | String | Nil |
sys_cwd |
0 | String |
sys_log writes newline-delimited JSON payloads to an append sink. If TONIC_SYSTEM_LOG_PATH
is set, the runtime appends to that file (creating parent directories when needed); otherwise
it emits the JSON line to stderr.
| Key | Arity | Returns |
|---|---|---|
sys_random_token |
1 | String |
sys_hmac_sha256_hex |
2 | String |
sys_constant_time_eq |
2 | Bool |
sys_discord_ed25519_verify |
4 | Bool |
| Key | Arity | Returns |
|---|---|---|
sys_http_request |
5 | Map {status, headers, body, final_url} |
HTTP server primitives use process-scoped opaque handle strings ("listener:N", "conn:N") as the primary state management mechanism. Handles are allocated from a global AtomicU64 counter and stored in LazyLock<Mutex<HashMap>> maps. Handle state is not transferred across process boundaries.
| Key | Arity | Returns |
|---|---|---|
sys_http_listen |
2 | Map {status: :ok, listener_id} |
sys_http_accept |
2 | Map {status: :ok, connection_id, client_ip, client_port} |
sys_http_read_request |
1 | Map {status: :ok, method, path, query_string, headers, body} |
sys_http_write_response |
4 | Bool |
Handle lifecycle
sys_http_listen→ allocates aTcpListener, returnslistener:Nsys_http_accept→ clones the listener fd, blocks/polls for a connection, allocates aTcpStream, returnsconn:Msys_http_read_request→ clones the stream fd, reads the HTTP/1.1 requestsys_http_write_response→ removes the stream from the map, writes the response, drops the stream (closes connection)
After sys_http_write_response the connection_id is no longer valid. The listener_id remains valid until the process exits (no explicit close API in v1).
See docs/system-stdlib.md for full API reference, error contracts, and examples.
runtime_to_tvalue(RuntimeValue) -> Result<TValue, AbiError>tvalue_to_runtime(TValue) -> Result<RuntimeValue, AbiError>validate_tvalue(TValue) -> Result<(), AbiError>
These helpers are used for differential testing between interpreter values and native ABI values.