42d808732b
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
151 lines
7.9 KiB
C
151 lines
7.9 KiB
C
/* zset_ffi.h: the C ABI of crates/zset-ffi (libzset_ffi.{so,dylib,a}).
|
|
* Hand-written; tests/header.rs checks it against src/lib.rs.
|
|
* Reasoning: context/code/zset-ffi.md.
|
|
*
|
|
* Conventions
|
|
* - Every function but the frees, zs_abi_version and zs_last_error returns int32_t: 0 is
|
|
* success, anything else is a ZS_ERR_* code.
|
|
* Results come back through out-pointers, which are checked FIRST (a NULL out is
|
|
* ZS_ERR_NULL_ARGUMENT with no state changed) and written only on success.
|
|
* On failure zs_last_error() describes it (per thread, valid until the next failure).
|
|
* - Handles (ZsZSet, ZsCircuit, ZsRebuild) are opaque. Each is created by a function that
|
|
* takes ZsX **out and is released by exactly one zs_X_free. Freeing NULL is a no-op;
|
|
* freeing twice or using a freed handle is undefined.
|
|
* - Keys are byte strings: (pointer, length). The library copies them; the caller keeps
|
|
* ownership of every buffer it passes, and may reuse it after the call returns.
|
|
* A zero length accepts a NULL pointer.
|
|
* - The library never returns memory the caller must free, except handles.
|
|
* - Weights are int64_t. A weight that would leave int64_t is ZS_ERR_OVERFLOW, never a
|
|
* wrong value, and never an unwinding exception.
|
|
* - Not thread-safe. A ZsCircuit and every ZsRebuild made from it form ONE group (they share
|
|
* a reference count): keep the whole group on one thread at a time. Z-sets are separate
|
|
* handles. Streams, inputs and sinks are uint32_t ids local to their circuit; a sink id is
|
|
* never a stream id (they are refused as ZS_ERR_BAD_HANDLE), but an id from another circuit
|
|
* is not detected. A ZsRebuild is stepped only on the circuit that made it (else
|
|
* ZS_ERR_BAD_HANDLE), even if another circuit now lives at the old address.
|
|
* - Keys of 4 GiB or longer are refused with ZS_ERR_BAD_VALUE: the wire form has u32 lengths.
|
|
*/
|
|
#ifndef ZSET_FFI_H
|
|
#define ZSET_FFI_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define ZS_ABI_VERSION 1
|
|
|
|
#define ZS_OK 0
|
|
#define ZS_ERR_NULL_ARGUMENT 1
|
|
#define ZS_ERR_OVERFLOW 2
|
|
#define ZS_ERR_PANIC 3
|
|
#define ZS_ERR_BAD_HANDLE 4
|
|
#define ZS_ERR_BUFFER_TOO_SMALL 5
|
|
#define ZS_ERR_BAD_VALUE 6
|
|
#define ZS_ERR_POISONED 7
|
|
#define ZS_ERR_CALLBACK 8
|
|
#define ZS_ERR_INTERRUPTED 9
|
|
|
|
#define ZS_AGG_COUNT 0
|
|
#define ZS_AGG_SUM 1
|
|
#define ZS_AGG_MIN 2
|
|
#define ZS_AGG_MAX 3
|
|
|
|
typedef struct ZsZSet ZsZSet;
|
|
typedef struct ZsCircuit ZsCircuit;
|
|
typedef struct ZsRebuild ZsRebuild;
|
|
|
|
/* Callbacks run inside zs_circuit_step and zs_rebuild_step.
|
|
* - The function pointer and its ctx are kept by the circuit and must stay valid until
|
|
* zs_circuit_free. emit and emit_ctx are valid only during the callback that received them.
|
|
* - A callback emits images of a key by calling emit(emit_ctx, bytes, len); the bytes are
|
|
* copied during the call.
|
|
* - A callback reports failure by its return value: ZsFlatMapFn non-zero, ZsKeepFn and
|
|
* ZsExpiredFn negative. The step then fails with ZS_ERR_CALLBACK and the circuit is dead.
|
|
* - A callback MUST return normally: no longjmp, no C++ exception, no Lisp non-local exit
|
|
* through it (wrap the body in handler-case and return the failure value). If one does,
|
|
* the circuit is left marked mid-step and every later call on it is ZS_ERR_POISONED.
|
|
* - A callback that calls the library with its own circuit (or the rebuild job being
|
|
* stepped) gets ZS_ERR_POISONED and nothing changes. zs_circuit_free on a circuit that is
|
|
* mid-step, and zs_rebuild_free on a job being stepped, do nothing (the object is leaked
|
|
* rather than freed under the running frames); free after the call returns.
|
|
* A circuit whose step was left by a non-local exit stays mid-step, so its free leaks too. */
|
|
typedef void (*ZsEmit)(void *emit_ctx, const uint8_t *bytes, size_t len);
|
|
typedef int32_t (*ZsFlatMapFn)(void *ctx, const uint8_t *key, size_t len, ZsEmit emit, void *emit_ctx);
|
|
/* 1 keep, 0 drop, negative fail. */
|
|
typedef int32_t (*ZsKeepFn)(void *ctx, const uint8_t *key, size_t len);
|
|
/* 1 expired, 0 not yet, negative fail. */
|
|
typedef int32_t (*ZsExpiredFn)(void *ctx);
|
|
|
|
uint32_t zs_abi_version(void);
|
|
const char *zs_last_error(void);
|
|
|
|
/* Z-sets. add/sub/neg/scale/decode_add leave the set unchanged when they fail. add and sub
|
|
* accept the same handle as both arguments. */
|
|
int32_t zs_zset_new(ZsZSet **out);
|
|
void zs_zset_free(ZsZSet *z);
|
|
int32_t zs_zset_clone(const ZsZSet *z, ZsZSet **out);
|
|
int32_t zs_zset_insert(ZsZSet *z, const uint8_t *key, size_t len, int64_t weight);
|
|
int32_t zs_zset_weight(const ZsZSet *z, const uint8_t *key, size_t len, int64_t *out);
|
|
int32_t zs_zset_len(const ZsZSet *z, size_t *out);
|
|
int32_t zs_zset_add(ZsZSet *a, const ZsZSet *b);
|
|
int32_t zs_zset_sub(ZsZSet *a, const ZsZSet *b);
|
|
int32_t zs_zset_neg(ZsZSet *a);
|
|
int32_t zs_zset_scale(ZsZSet *a, int64_t n);
|
|
int32_t zs_zset_distinct(const ZsZSet *z, ZsZSet **out);
|
|
|
|
/* Encoding: entries ascending by key, each [u32 LE key length][key][i64 LE weight].
|
|
* *needed is set whenever z is valid; the bytes are written only if cap >= *needed, else
|
|
* ZS_ERR_BUFFER_TOO_SMALL (call again with a larger buffer). buf may be NULL only when
|
|
* *needed is zero or to probe with cap too small; otherwise ZS_ERR_NULL_ARGUMENT. */
|
|
int32_t zs_zset_encode(const ZsZSet *z, uint8_t *buf, size_t cap, size_t *needed);
|
|
int32_t zs_zset_decode_add(ZsZSet *z, const uint8_t *buf, size_t len);
|
|
|
|
/* Circuits. Every stream carries a Z-set of byte-string keys. */
|
|
int32_t zs_circuit_new(ZsCircuit **out);
|
|
void zs_circuit_free(ZsCircuit *c);
|
|
int32_t zs_circuit_input(ZsCircuit *c, uint32_t *out);
|
|
int32_t zs_circuit_output(ZsCircuit *c, uint32_t stream, uint32_t *out);
|
|
int32_t zs_circuit_delay(ZsCircuit *c, uint32_t stream, uint32_t *out);
|
|
int32_t zs_circuit_integrate(ZsCircuit *c, uint32_t stream, uint32_t *out);
|
|
int32_t zs_circuit_differentiate(ZsCircuit *c, uint32_t stream, uint32_t *out);
|
|
int32_t zs_circuit_distinct(ZsCircuit *c, uint32_t stream, uint32_t *out);
|
|
int32_t zs_circuit_plus(ZsCircuit *c, uint32_t a, uint32_t b, uint32_t *out);
|
|
int32_t zs_circuit_filter(ZsCircuit *c, uint32_t stream, ZsKeepFn keep, void *ctx, uint32_t *out);
|
|
int32_t zs_circuit_flat_map(ZsCircuit *c, uint32_t stream, ZsFlatMapFn f, void *ctx, uint32_t *out);
|
|
|
|
/* index_by: f emits exactly one group key g per element; the output element is
|
|
* pair(g, element), pair(k, v) = [u32 LE length of k][k][v]. */
|
|
int32_t zs_circuit_index_by(ZsCircuit *c, uint32_t stream, ZsFlatMapFn f, void *ctx, uint32_t *out);
|
|
|
|
/* join: both inputs carry pair(k, v); the output element is pair(k, pair(a, b)). */
|
|
int32_t zs_circuit_join(ZsCircuit *c, uint32_t a, uint32_t b, uint32_t *out);
|
|
|
|
/* aggregate: the input carries pair(k, v) with v an 8-byte little-endian int64 (any bytes
|
|
* for COUNT); the output is pair(k, 8-byte little-endian int64). COUNT is the total weight,
|
|
* SUM is the weighted sum (a non-empty group always has a row, even at zero), MIN and MAX read the values of positive weight. */
|
|
int32_t zs_circuit_aggregate(ZsCircuit *c, uint32_t stream, int32_t kind, uint32_t *out);
|
|
|
|
int32_t zs_circuit_push(ZsCircuit *c, uint32_t input, const ZsZSet *z);
|
|
|
|
/* One clock tick. After a failure in a step the circuit is poisoned: every later call that
|
|
* uses it returns ZS_ERR_POISONED, except the free. */
|
|
int32_t zs_circuit_step(ZsCircuit *c);
|
|
int32_t zs_circuit_take(ZsCircuit *c, uint32_t sink, ZsZSet **out);
|
|
|
|
/* Resumable snapshot load: chunk keys per piece (0 reads as 1). The whole load is one
|
|
* logical tick, and done is 1 as soon as the last chunk ran. While a job is pending it
|
|
* owns the clock: a zs_circuit_step in between makes the next zs_rebuild_step return
|
|
* ZS_ERR_INTERRUPTED (the circuit stays usable; drop the job). */
|
|
int32_t zs_rebuild_new(ZsCircuit *c, uint32_t input, const ZsZSet *snapshot, size_t chunk, ZsRebuild **out);
|
|
int32_t zs_rebuild_step(ZsRebuild *job, ZsCircuit *c, ZsExpiredFn expired, void *ctx, int32_t *done);
|
|
void zs_rebuild_free(ZsRebuild *job);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|