wired_bench_shim.hc · 165 linesall
/*
 * C ABI of the foreign-engine shims for the wired runtime JSON bench.
 *
 * One opaque context per engine, created once per engine run (untimed). The
 * context owns every reusable resource: parser state, the most recent
 * document, the most recent serialize buffer, and the last error message —
 * no allocation ever crosses the FFI boundary.
 *
 * Conventions:
 *   - int-returning calls: 0 = success, nonzero = failure; the message is
 *     available via jb_<engine>_error() until the next call on the context.
 *   - jb_<engine>_parse takes caller-owned, immutable, unpadded input; any
 *     copy or padding the engine needs happens inside (the bench times it).
 *   - jb_<engine>_serialize returns a context-owned minified UTF-8 buffer,
 *     valid until the next call on the context.
 *   - No C++ exception and no Rust panic may unwind across this boundary.
 *
 * The header is consumed by D via ImportC: plain C99, no includes beyond
 * <stddef.h>/<stdint.h>.
 */
#ifndef WIRED_BENCH_SHIM_H
#define WIRED_BENCH_SHIM_H

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Mirrors the D-side Fingerprint (fingerprint.d); the adapters copy it
 * field by field, so only the field set — not the layout — must match. */
typedef struct jb_fingerprint {
    uint64_t nulls;
    uint64_t trues;
    uint64_t falses;
    uint64_t numbers;        /* ints + floats merged: engines classify differently */
    uint64_t strings;        /* value-position strings only */
    uint64_t arrays;
    uint64_t objects;
    uint64_t array_elems;
    uint64_t object_members;
    uint64_t string_bytes;   /* decoded (unescaped) bytes of value strings */
    uint64_t key_bytes;      /* decoded bytes of object keys */
    double   number_sum;     /* compared with relative tolerance */
} jb_fingerprint;

/* Cross-engine checksum of the typed `decode` op (the partial Twitter
 * struct; see the D-side twitter.d for the canonical definition). All sums
 * are 64-bit and WRAP on overflow — part of the contract. */
typedef struct jb_twitter_stats {
    uint64_t status_count;
    uint64_t id_sum;
    uint64_t user_id_sum;
    uint64_t followers_sum;
    uint64_t retweet_sum;
    uint64_t favorite_sum;
    uint64_t text_bytes;
    uint64_t screen_name_bytes;
    uint64_t created_at_bytes;
} jb_twitter_stats;

/* ---- simdjson, DOM front-end (dom::parser tape, reused across parses) --- */
typedef struct jb_sj_dom_ctx jb_sj_dom_ctx;
jb_sj_dom_ctx *jb_sj_dom_new(void);
void           jb_sj_dom_free(jb_sj_dom_ctx *ctx);
int            jb_sj_dom_parse(jb_sj_dom_ctx *ctx, const char *data, size_t len);
void           jb_sj_dom_doc_free(jb_sj_dom_ctx *ctx);
int            jb_sj_dom_fingerprint(jb_sj_dom_ctx *ctx, jb_fingerprint *out);
const char    *jb_sj_dom_serialize(jb_sj_dom_ctx *ctx, size_t *len);
const char    *jb_sj_dom_error(const jb_sj_dom_ctx *ctx);

/* ---- simdjson, On-Demand front-end -------------------------------------
 * A lazy forward-only cursor: a bare "parse" would only run stage 1, so the
 * timed op is parse + a full-document walk, and the walk *is* the
 * fingerprint computation (strings and numbers are materialized). */
typedef struct jb_sj_od_ctx jb_sj_od_ctx;
jb_sj_od_ctx  *jb_sj_od_new(void);
void           jb_sj_od_free(jb_sj_od_ctx *ctx);
int            jb_sj_od_parse_walk(jb_sj_od_ctx *ctx, const char *data,
    size_t len, jb_fingerprint *out);
/* Structural skip: containers are traversed, scalars are never decoded —
 * the cost profile of a lazy parser over untouched fields (weaker than a
 * full well-formedness check: numbers/strings stay unvalidated). */
int            jb_sj_od_validate(jb_sj_od_ctx *ctx, const char *data, size_t len);
/* Typed decode: hand-written On-Demand extraction into the partial Twitter
 * structs, held by the context (unread fields are lazily skipped). */
int            jb_sj_od_decode(jb_sj_od_ctx *ctx, const char *data, size_t len);
int            jb_sj_od_twitter_stats(jb_sj_od_ctx *ctx, jb_twitter_stats *out);
const char    *jb_sj_od_error(const jb_sj_od_ctx *ctx);

/* ---- rapidjson (kParseFullPrecisionFlag always, for cross-engine number
 * comparability; SIMD level fixed at build time per ISA preset) ---------- */
typedef struct jb_rj_ctx jb_rj_ctx;
jb_rj_ctx     *jb_rj_new(void);
void           jb_rj_free(jb_rj_ctx *ctx);
int            jb_rj_parse(jb_rj_ctx *ctx, const char *data, size_t len);
/* Copies into a context scratch buffer (timed), then ParseInsitu. */
int            jb_rj_parse_insitu(jb_rj_ctx *ctx, const char *data, size_t len);
/* SAX Reader driving a null handler: full validation, nothing built. */
int            jb_rj_validate(jb_rj_ctx *ctx, const char *data, size_t len);
void           jb_rj_doc_free(jb_rj_ctx *ctx);
int            jb_rj_fingerprint(jb_rj_ctx *ctx, jb_fingerprint *out);
const char    *jb_rj_serialize(jb_rj_ctx *ctx, size_t *len);
const char    *jb_rj_error(const jb_rj_ctx *ctx);

/* ---- Rust engines (libwired_bench_rs.a; shims/rust) ---------------------
 * Same conventions. Engine specifics:
 *   jb_serde_*  — serde_json: parse = from_slice::<Value>, held in the ctx.
 *   jb_simdj_*  — simd-json: parse = copy into a reused scratch Vec (timed;
 *                 the library requires &mut [u8] for in-situ de-escaping) +
 *                 to_borrowed_value with reused Buffers, then dropped. The
 *                 document for fingerprint/serialize is re-parsed lazily as
 *                 an OwnedValue from a pristine copy, outside any timed op.
 *   jb_sonic_*  — sonic-rs: parse = from_slice::<sonic_rs::Value>.        */
typedef struct jb_serde_ctx jb_serde_ctx;
jb_serde_ctx  *jb_serde_new(void);
void           jb_serde_free(jb_serde_ctx *ctx);
int            jb_serde_parse(jb_serde_ctx *ctx, const char *data, size_t len);
/* from_slice::<IgnoredAny>: full validation, nothing built. */
int            jb_serde_validate(jb_serde_ctx *ctx, const char *data, size_t len);
void           jb_serde_doc_free(jb_serde_ctx *ctx);
int            jb_serde_fingerprint(jb_serde_ctx *ctx, jb_fingerprint *out);
const char    *jb_serde_serialize(jb_serde_ctx *ctx, size_t *len);
int            jb_serde_decode(jb_serde_ctx *ctx, const char *data, size_t len);
int            jb_serde_twitter_stats(jb_serde_ctx *ctx, jb_twitter_stats *out);
const char    *jb_serde_error(const jb_serde_ctx *ctx);

typedef struct jb_simdj_ctx jb_simdj_ctx;
jb_simdj_ctx  *jb_simdj_new(void);
void           jb_simdj_free(jb_simdj_ctx *ctx);
int            jb_simdj_parse(jb_simdj_ctx *ctx, const char *data, size_t len);
/* to_tape over the scratch copy (timed): stage 1 + 2, no value building. */
int            jb_simdj_validate(jb_simdj_ctx *ctx, const char *data, size_t len);
void           jb_simdj_doc_free(jb_simdj_ctx *ctx);
int            jb_simdj_fingerprint(jb_simdj_ctx *ctx, jb_fingerprint *out);
const char    *jb_simdj_serialize(jb_simdj_ctx *ctx, size_t *len);
/* Typed decode via simd-json's serde front-end (mutable copy included). */
int            jb_simdj_decode(jb_simdj_ctx *ctx, const char *data, size_t len);
int            jb_simdj_twitter_stats(jb_simdj_ctx *ctx, jb_twitter_stats *out);
const char    *jb_simdj_error(const jb_simdj_ctx *ctx);

typedef struct jb_sonic_ctx jb_sonic_ctx;
jb_sonic_ctx  *jb_sonic_new(void);
void           jb_sonic_free(jb_sonic_ctx *ctx);
int            jb_sonic_parse(jb_sonic_ctx *ctx, const char *data, size_t len);
/* from_slice::<IgnoredAny>: SIMD-skip validation, nothing built. */
int            jb_sonic_validate(jb_sonic_ctx *ctx, const char *data, size_t len);
void           jb_sonic_doc_free(jb_sonic_ctx *ctx);
int            jb_sonic_fingerprint(jb_sonic_ctx *ctx, jb_fingerprint *out);
const char    *jb_sonic_serialize(jb_sonic_ctx *ctx, size_t *len);
int            jb_sonic_decode(jb_sonic_ctx *ctx, const char *data, size_t len);
int            jb_sonic_twitter_stats(jb_sonic_ctx *ctx, jb_twitter_stats *out);
const char    *jb_sonic_error(const jb_sonic_ctx *ctx);

/* Engine/version provenance for the report header, e.g.
 * "simdjson 4.2.2; rapidjson 1.1.0". Static storage. */
const char *jb_cpp_versions(void);
const char *jb_rs_versions(void);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* WIRED_BENCH_SHIM_H */