Files
dev/crates/ffi/include/dev.h
T
zeekayandHanzo Dev 11abc2a33f fix(ffi): a refused allocation answers null instead of ending the process
dev_alloc built its buffer with vec!, which allocates on the path that cannot
fail: a valid layout the allocator refuses goes to handle_alloc_error, and that
aborts. An abort is not a panic, so the catch_unwind around it never ran.
dev_alloc(isize::MAX) died on SIGABRT, taking every other session in the
process with it, and the same call into dev.wasm trapped on unreachable — under
a doc and a header that both promised null.

Ask the allocator directly. Layout::from_size_align refuses a length with no
layout, alloc_zeroed answers null when there is no memory, and nothing left in
there can panic, so the catch_unwind goes. dev_release frees the buffer as the
boxed [u8] whose layout this is.

The module doc and dev.h now say how far "the process stays up" reaches: a
panic, and no further. Memory the library needs for itself still aborts when
refused — with the module's memory lent out, a dev_step on a 512 MiB input
traps — and dev_alloc is the one place a refusal comes back.

The test asks for isize::MAX bytes and the two lengths past it; against the old
body it kills the test binary with SIGABRT, and so does the C fixture asking
for PTRDIFF_MAX through dev.h. The wasm import list is unchanged.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-18 22:41:50 -07:00

89 lines
3.5 KiB
C

/* The C ABI of the dev agent loop (HIP-1330).
*
* Written by hand and kept beside crates/ffi/src/lib.rs, which the C ABI test
* in crates/ffi/tests/abi.c compiles against the staticlib. There is no
* generator step to forget to run.
*
* Ownership:
* - Every pointer you pass in is copied before the call returns.
* - Every dev_buf handed back is a Rust allocation; release it with
* dev_free and never with free(3).
* - A session handle is opaque and generational: dev_drop retires it, and a
* retired handle answers DEV_HANDLE instead of naming a live session. A
* dev_drop that lands while a call on that session is running makes the
* call answer DEV_HANDLE and hand back no dev_buf: there is no session
* left for its answer to belong to.
* - A call that panics answers DEV_PANIC and poisons that session; its later
* calls answer DEV_POISON until dev_drop. Other sessions are untouched.
* - A refused allocation is not a panic. Memory the library needs for itself
* and is refused ends the process, or in wasm traps the instance; dev_alloc
* is the one place a refusal comes back, as a null.
* - A dev_buf is written only on DEV_OK. On any other status the out
* parameter is left as the caller passed it.
*
* Payloads are dev-protocol messages (Config, Event, Action list, snapshot) in
* the encoding dev_abi() names.
*/
#ifndef HANZO_DEV_H
#define HANZO_DEV_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define DEV_OK 0 /* the call did what it was asked */
#define DEV_HANDLE (-1) /* the handle names no live session */
#define DEV_MALFORMED (-2) /* the payload is not a message of this protocol */
#define DEV_POISON (-3) /* a previous call on this session panicked */
#define DEV_PANIC (-4) /* this call panicked; the session is now poisoned */
#define DEV_NULL (-5) /* a pointer the caller must supply was null */
#define DEV_BUSY (-6) /* the session is inside another call */
#define DEV_FULL (-7) /* no slot is left for another session */
/* A byte string Rust owns until dev_free. */
typedef struct dev_buf {
uint8_t *ptr;
size_t len;
size_t cap;
} dev_buf;
/* Version of this ABI and of the payload encoding behind it. */
uint32_t dev_abi(void);
/* Start a session from an encoded Config, or from the default when len is 0.
* Writes the handle to out. */
int32_t dev_new(const uint8_t *cfg, size_t len, uint64_t *out);
/* Feed one encoded Event; writes the encoded action list to out. */
int32_t dev_step(uint64_t session, const uint8_t *event, size_t len, dev_buf *out);
/* Write the session's snapshot to out. */
int32_t dev_snapshot(uint64_t session, dev_buf *out);
/* Rebuild a session from a snapshot; writes its new handle to out. */
int32_t dev_restore(const uint8_t *state, size_t len, uint64_t *out);
/* End a session and retire its handle. */
void dev_drop(uint64_t session);
/* Release a dev_buf this library handed out. */
void dev_free(dev_buf buf);
/* Lend the host len zeroed bytes inside this library's memory; null for a zero
* len or a refused allocation. Compiled to wasm the library's memory is its own,
* so a host there has no other way to produce an input pointer or an
* out-parameter. A native host shares an address space and never needs it. */
uint8_t *dev_alloc(size_t len);
/* Take back what dev_alloc lent. ptr and len are that call's answer and its
* argument. */
void dev_release(uint8_t *ptr, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* HANZO_DEV_H */