Files
improvise/crates/zset-ffi/include/zset_ffi.h
T

172 lines
9.3 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, but movable. A ZsCircuit, a ZsRebuild and a ZsZSet may each be handed to
* another thread; use each from one thread at a time (a zs_rebuild_step call uses both its
* job and its circuit). Callbacks run on whichever thread calls zs_circuit_step or
* zs_rebuild_step, so a ctx must be usable from every thread that may step. 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_ERR_TRANSACTION 10
#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, and zs_circuit_rollback of a transaction
* the failed step ran in. */
int32_t zs_circuit_step(ZsCircuit *c);
/* Transactions: undo a failed command. zs_circuit_begin opens one; zs_circuit_commit keeps
* what its steps did; zs_circuit_rollback puts the circuit's state back to what it was at
* begin, and revives a circuit that a failed step in the transaction poisoned. The cost of a
* transaction is the keys its steps touch (plus a delay's held input), not the circuit's size.
* - At most one is open. begin on an open one, and commit or rollback on none, is
* ZS_ERR_TRANSACTION. begin on a poisoned circuit is ZS_ERR_POISONED; so is commit after a
* failed step (roll back instead). A poison from outside a transaction is permanent.
* - Pushes made after begin are dropped by a rollback; what was pending at begin is pending
* again. zs_circuit_take is ZS_ERR_TRANSACTION while one is open: take after commit.
* - Operators built inside a transaction stay, and their state goes back to empty.
* - What is outside the circuit is not undone: a callback's own side effects, and a
* ZsRebuild stepped in the transaction (it reports ZS_ERR_INTERRUPTED afterwards; start
* another job). */
int32_t zs_circuit_begin(ZsCircuit *c);
int32_t zs_circuit_commit(ZsCircuit *c);
int32_t zs_circuit_rollback(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