15 KiB
Changelog
All notable changes to this project are documented in this file.
[0.3.0] - 2026-07-24
Breaking release. declareLibrary is now required before any FFI annotation;
the per-request handler timeout and its {.ffi: "timeout = <ms>".} override are
replaced by the non-terminal RET_STALE_WARN progress callback; reaching an
enum from an abi = c type or proc is a compile error; and the generated C
<lib>_ctx_destroy() returns int instead of void.
New surface: {.ffiStatic.} for context-independent procs, {.ffiConst.},
{.ffi.} enums, doc-comment propagation into the generated bindings, a C
binding generator (-d:targetLang=c), and a CBOR-free abi = c path in both
directions. Internally the watchdog thread is gone — the heartbeat now runs on
the dedicated event thread that also isolates user callbacks from the FFI thread.
Changed
abi = cnon-scalar procs no longer marshal through CBOR. The foreign surface is unchanged — the generated headers and exported symbols are byte-identical — but the hop between the caller and the FFI thread now carries the packed_CWirestruct itself instead of CBOR-encoding it and decoding it back. A request is packed into amalloc'd owned copy on the calling thread and unpacked (then freed) on the FFI thread; an object reply rides back as its_CWireimage and astringreply as raw UTF-8, so the round trip throughcborEncodeShared/cborDecodePtris gone from both directions. Only the scalar fast path was CBOR-free before (#131)._CWireseq/Option payload buffers are allocated with libcmalloc(cwireAllocBuf) rather thanallocShared, so a wire packed on the calling thread can be freed on the FFI thread — the cross-thread ownership the CBOR-free request path relies on, and consistent with the libc-backed request envelope.- The generated C
<lib>_ctx_destroy()now returnsintinstead ofvoid, propagating the exported<lib>_destroy()status code (NIMFFI_RET_OKon success,RET_ERRon a null/invalid context or a failed context teardown) instead of discarding it, so a host can observe a failed teardown. Existing callers that invoke it as a statement are unaffected (#133). - A failed
<lib>_ctx_destroy()no longer frees the event-listener boxes. A non-NIMFFI_RET_OKteardown leaves the worker threads live, and they still hold each box as callbackuser_data; the boxes are now leaked rather than freed out from under a running event thread. The context struct and the listener array are still freed unconditionally. - User event callbacks now run on a dedicated event thread fed by a
bounded SPSC queue (default capacity 1024), so a slow listener can no
longer block the FFI thread or concurrent
add_event_listener/remove_event_listenercalls (#6). - Replaced the dedicated watchdog thread with a heartbeat check that
runs on the event thread. The FFI thread advances an atomic heartbeat
each loop iteration; if it stalls for more than 1s past the start-up
grace window, the event thread emits the
not_respondingevent. declareLibraryno longer emits the shared-librarysoname/install_namelinker flags when building as an executable (--app:libguard), so FFI code can be unit-tested as a plain binary — fatal on macOS, where-install_namerequires-dynamiclib.
Added
{.ffiStatic.}: exports a context-independent proc — no library param, and noctxin its wrapper, so a host can call a stateless utility (key generation, parsing, a version string) without constructing the library (#134). Wired for both thecborandcABIs across all four backends: the C header emits<lib>_static_<proc>(...), C++ and Rust an associated function on the ctx type taking thetimeouta method reads from its ctx. Handlers run on the library's static context, created on the first such call and held for the rest of the process, so that call starts a thread pair nothing tears down —destroyFFIContextrefuses it;destroyStaticFFIContextis the Nim-side teardown for process shutdown and tests, with no foreign equivalent. An{.ffiHandle.}parameter or return is rejected at macro time: a handle belongs to the context that created it, which a static proc cannot reach.{.ffi.}now accepts anenumtype, emitting a native enum in every target (Cenum, C++enum class, Rust enum, CDDL string choice). Values cross the wire as the text$valueyields — the associated string if declared, else the symbol name — matching whatcbor_serializationwrites. Enums are supported on the CBOR wire only; reaching one from anabi = ctype or proc is now a compile error naming the type, where it previously registered as a fieldless struct and silently dropped the value.{.ffiConst.}exposes a Nimconstto every generated binding as a native constant (static constin C,constexprin C++,pub constin Rust). Integer, float,boolandstringvalues are supported, computed expressions arrive folded, and names are re-cased toUPPER_SNAKE.{.ffiEvent.}no longer requires an explicit wire-name string: when omitted it is derived from the proc name viacamelToSnakeCase(onPeerConnected→on_peer_connected), matching how{.ffi.}derives its C export symbol. Pass a string literal only to override it.- Doc comments (
##) on{.ffi.}/{.ffiCtor.}/{.ffiDtor.}procs are now propagated to the generated bindings —/** ... */on the C declarations,///on the C++ class methods and Rustpub fns, and;comments in the CDDL schema — so the exported API is documented once, in the Nim source (#127). Editing a##comment now changes the generated bindings, sonimble check_bindingsflags them stale until regenerated; an undocumented proc still generates byte-identical output. - FFI annotations (
{.ffi.},{.ffiStatic.},{.ffiCtor.},{.ffiDtor.},{.ffiEvent.},{.ffiHandle.},{.ffiRaw.}) that expand aftergenBindings()now produce a loud compile error instead of being silently dropped from the generated bindings. - C binding generator (
-d:targetLang=c): emits a header-only C binding (<lib>.h) plus aCMakeLists.txt, alongside the existing Rust / C++ / CDDL backends. Requests/responses travel as CBOR using the same vendored TinyCBOR the C++ backend uses. C has no generics or overloading, so eachseq[T]/Option[T]is monomorphised into its own struct + encode/decode/free triple. The high-level<lib>_ctx_*API is asynchronous: each method/constructor takes a typed result callback and the binding owns and reclaims all reply data and error strings (valid only for the duration of the callback), so the caller never frees anything — there is no blocking wait and no manual-free contract. Shared codegen helpers were extracted intoffi/codegen/common.nim(used by both the C and C++ backends). Newnimble genbindings_c/genbindings_c_echo/check_bindings_c/test_c_e2etasks, atests/e2e/cctest harness, and atests/unit/test_c_codegen.nimunit suite. - Non-terminal
RET_STALE_WARN(3) progress callback in place of a handler timeout: nim-ffi never times a handler out (a hard-cancel mid-call into the underlying library can leave it half-applied). Instead, while a request is still in flight its result callback receives aRET_STALE_WARNevery 5s (Android's ANR interval; override with-d:ffiStaleWarnIntervalMs=<ms>), with the payload carrying the elapsed milliseconds as a decimal string. The request always ends with exactly one terminalRET_OK/RET_ERR; the dev decides what to do with a slow one. Replaces the never-released per-proc{.ffi: "timeout = <ms>".}override and thedefaultRequestTimeoutcontext field (#126, supersedes #93). - Per-interaction ABI-format annotations:
declareLibrarynow takes an optionaldefaultABIFormat("cbor"default, or"c") that every{.ffi.}/{.ffiCtor.}/{.ffiDtor.}/{.ffiRaw.}/{.ffiEvent.}inherits, and each annotation can override it with an"abi = c"/"abi = cbor"spec (e.g.{.ffi: "abi = cbor".}).declareLibraryis now required before any FFI annotation (#78). c(abi = cC-struct) ABI codec: every{.ffi: "abi = c".}type gets a<T>_CWirecompanion pluscwirePack/cwireUnpack/cwireFree. This first slice covers theabi = cpath — POD scalars andstring(ascstring); composite fields follow. (cevents remain CBOR-only.)- CBOR-free (
abi = c) C bindings, emitted by the singlectarget (-d:targetLang=c): the onecgenerator now picks its output from the library's ABI format — theabi = cheader or the CBOR header. Theabi = cheader is a single self-contained<lib>.hwhose_CWirestructs are the C ABI, so the C consumer passes native structs and links no CBOR at all. Thecproc-dispatch path is wired end-to-end: the generated exported wrapperscwireUnpackthe request into a Nim object, reuse the existing CBOR thread transport internally, and a Nim reply trampolinecwirePacks the response back into a_CWirestruct for the caller's typed callback.abiCodegenImplementedacceptscfor proc/ctor/dtor annotations (events remain CBOR-only). Newexamples/echo/c_abi_bindings/(checked in beside the CBORc_bindings/for comparison),nimble genbindings_c_abi_echo/check_bindings_c_abi/test_c_abi_e2e/test_c_abi_e2e_sanitizedtasks, and atests/e2e/c_abictest harness (#105). tests/bench/bench_codec.nim(+nimble bench_codec): a single-process microbenchmark comparing thecborandccodecs across payload shapes, isolating codec cost from the (identical) thread/callback round-trip.- Queue-overflow handling: when the bounded event queue is full, the
library sets a sticky "stuck" flag, logs an error, fires
not_respondingfrom the event thread, and rejects subsequentsendRequestToFFIThreadcalls withevent queue stuck - library cannot accept new requests.
[0.2.0] - 2026-06-04
Major release introducing the CBOR-based wire format, CBOR-backed FFI events with a multi-listener registry, multi-language binding generation (C++, Rust, CDDL), CI hardening with sanitizers, and several robustness fixes around context lifetime and memory safety.
Added
- CBOR serialization as the FFI wire format, replacing the previous
JSON/string-based
serial.nim(#23). - CBOR-backed FFI events: event payloads are now serialized with CBOR (#39).
- Multi-listener event registry (
FFIEventRegistry) and its wiring intoFFIContext(#45, #49). - Event-listener ABI with per-event typed listeners (#50).
- C++ typed per-event listeners in the generated bindings (#51).
- Rust per-event typed listeners (
add_on_<x>_listener+ wildcardadd_event_listener) (#52) and Rust event example bindings/clients (#53). - C++ binding generator with end-to-end tests driven by CMake/CTest (#27), later expanded with multi-context, cross-library, pipeline, and stress tests (#42).
- CDDL schema generator for the FFI types (#24).
- CI pipeline: parallel test execution (#26), AddressSanitizer / UndefinedBehaviorSanitizer / ThreadSanitizer jobs (#34), and a cross-platform OS matrix for the C++ e2e suite (#38).
- CBOR type-coverage tests (#41).
Changed
- Removed the redundant
ffiTypemacro; theffimacro is now the single authoring entry point (#22). - Generated C++ avoids move constructors and assignment operators (#36) and no longer throws exceptions across the binding boundary (#46).
- Removed the wildcard event listener; event dispatch is now strictly per-event (#70).
Fixed
- Use-after-free in the event/context lifetime path (#47).
[0.1.4] - 2026-05-13
Added
- Simplified FFI authoring with auto-generated C++ and Rust language bindings,
including new
ffi/codegen/cpp.nim,ffi/codegen/rust.nimand sharedffi/codegen/meta.nimhelpers (#15). - Rust example bindings and clients under
examples/nim_timer/(rust_bindingsandrust_client, the latter with a Tokio async variant) (#15). - JSON/string-based FFI (de)serialization via
ffi/serial.nim(ffiSerialize/ffiDeserialize), withtests/test_serial.nimcoverage. (CBOR replaced this layer later, in 0.2.0.) - FFI context pool (
ffi/ffi_context_pool.nim) using a fixed array of contexts. - Test suite expansion:
test_alloc.nim,test_ctx_validation.nim,test_ffi_context.nim,test_gc_compat.nim. - Continuous integration pipeline (#12).
Fixed
- Context buffer overflow (#21).
- Use a fixed array of contexts to avoid consuming all file descriptors (#14).
- Memory leaks (#11).
- Add
install_namefor macOS shared libraries (#8).
Changed
- Run tests with the
refcgarbage collector (#20). - Remove
CatchableErrorusage (#19). - Update license files to comply with Logos licensing requirements.
[0.1.3] - 2026-01-23
Fixed
- Properly import and re-export
chroniclesso downstream packages get the logging macros transitively.
[0.1.2] - 2026-01-23
Fixed
- Re-export
chroniclesandstd/tableswhen theffimodule is imported, so generated code resolves these symbols at the call site.
[0.1.1] - 2026-01-23
Initial tagged release.
Added
- Core
ffimacro for declaring procs exposed across the FFI boundary. FFIContextwith a dedicated worker thread, request dispatch, and a watchdog with configurable timeout (#7).- License files updated to comply with Logos licensing requirements.