Regenerates the wrapper (MyTimerNode.kt) and JNI shim (my_timer_jni.c) via
`nimble genbindings_kotlin`, removing the hand-written sources, and points
the instrumented test at the derived MyTimerNode class.
Validated by cross-compiling both ABIs with the NDK: arm64-v8a + x86_64 build
clean, the four Java_org_logos_mytimer_MyTimerNode_* symbols are exported, and
libmy_timer_jni.so correctly NEEDS libmy_timer.so. (The Kotlin/Gradle layer
still runs on a device/emulator.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds ffi/codegen/kotlin.nim, wired into the targetLang dispatch and exposed
as the `genbindings_kotlin` nimble task. From the same metadata it emits both
artifacts an Android consumer needs over the native (zero-serialization) C
ABI from c.nim: an idiomatic Kotlin AutoCloseable wrapper and the JNI shim
that bridges it.
Callback-shape selection is per proc (ack / string / struct), and the JNI
shim blocks on a condvar until the FFI-thread callback fires so the Kotlin
side stays synchronous. An all-string struct return crosses as a String[]
that the wrapper repacks into a data class — matching the validated example.
Procs needing seq/Option params, multiple struct params, or a non-string
struct return are skipped with a logged notice so the generated code always
compiles. That marshaling, events, and async are the next increments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An Android library module wrapping the timer library's native ABI behind an
idiomatic Kotlin `TimerNode` class via a JNI shim. `build-libs.sh` cross-compiles
two native libraries per ABI (arm64-v8a + x86_64) into src/main/jniLibs/:
libmy_timer.so (the Nim library) and libmy_timer_jni.so (the JNI bridge, which
NEEDs the former). The shim turns each Kotlin `external fun` into a blocking call
and reads the typed EchoResponse struct out of the result callback.
Gradle packages everything under jniLibs/ automatically; an instrumented test
covers create/version/echo on a device/emulator. Native build validated for
both ABIs (correct aarch64/x86_64 ELF, JNI symbols exported, libmy_timer.so
linked).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A "kitchen sink" {.ffi.} object spanning every supported field shape — all
integer widths, both floats, bool, string, sequences (scalars / strings /
floats / nested structs), Option/Maybe, and a nested struct by value — is sent
in as a C-POD and returned as a typed C-POD, then checked field-for-field
against the Nim-native result.
This is the native-path complement to the existing CBOR coverage (test_serial
for the codec, test_wire_compat for the bytes): it pins nimToPod ->
*NativeExport -> clonePod/podToNim of the typed return for the whole type
matrix. Compiling also proves the native-POD codegen accepts every type. Passes
under orc + refc and clean under ASAN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hammers one shared context from several threads, alternating the two ABIs of
the same library: the native path (EchoRequest struct in, typed EchoResponse*
back) and the CBOR path (encoded request in, CBOR map back), each verifying the
echoed message round-trips. Exercises the POD deep-copy/free on the way in, the
respPod deliver/free on the way out, and the request channel under contention.
Run plain or with SAN=address / SAN=thread. Clean at 6 threads x 1500 iters
(9000 calls per ABI) under both ASAN and TSAN — no leaks, use-after-free, or
data races.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every generated library exports both ABIs side by side; spell out the choice in
the example READMEs: the native (pure-C) ABI is the default for same-process /
local calls (flat C structs, zero serialization), while the CBOR ABI exists
solely for inter-process communication (different process or machine). In a
shared address space CBOR is pure overhead, so prefer native locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Echo/Complex/Schedule now return typed Go structs (EchoResponse,
ComplexResponse, ScheduleResult); print their fields instead of an "ok" line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Struct-returning methods now hand back a typed Go struct instead of the raw
CBOR/bytes. Since the native return POD is freed right after the callback, the
POD->Go conversion must happen in-callback: the generator emits a `fromC()`
reader per {.ffi.} type and, per struct-returning proc, an exported Go result
callback. The method calls the native entry point directly with that callback
and a `runtime/cgo.Handle` (boxed in a small C allocation so it travels through
the void* userData checkptr-safe), then blocks until the callback delivers the
typed value or error on the result slot.
String/raw-returning procs keep the existing C-bridge + condvar path. Validated
end-to-end (Echo/Complex/Schedule) including under `go run -race`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the native C example to cast each struct return's callback msg to its
`const <Type>*` and read it in-callback (EchoResponse, ComplexResponse), instead
of scanning opaque bytes. Regenerate the headers with the new return-shape note.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A `{.ffi.}` proc that returns a registered struct now delivers it natively
instead of CBOR-encoding it. The FFI-thread handler builds the return's
`<T>Pod` mirror on the heap (`nimToPod`) and stashes it on the request; the
callback receives it as a typed `const <T>*` (msg = pointer, len = sizeof), and
handleRes deep-frees it the instant the callback returns — callback-lifetime
ownership, the caller frees nothing.
Mechanics: FFIThreadRequest gains respPod/respPodLen/respPodFree fields that
handleRes honors ahead of the byte payload; the macro emits a per-proc
cdecl freer (`freePod` + `ffiCFree`) for the response POD. String and
seq[byte] returns still travel as raw bytes; the CBOR path (`<name>_cbor`) is a
separate handler and is unchanged. The C header documents the new return shape.
Validated end-to-end from C (EchoResponse, ComplexResponse with nested
seq/option graphs) including under ASAN — no UAF or double-free.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A runnable main.go that constructs the timer with a TimerConfig, then calls
Echo (struct param), Complex (slice-of-structs + slice + two optionals) and
Schedule (three struct params) with idiomatic Go values — the methods the Go
generator used to skip. The Makefile builds the dylib next to the package
(cgo's ${SRCDIR} rpath finds it at runtime); README documents the Nim->Go type
mapping. Verified end-to-end with `go run`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Go generator previously emitted a `// SKIPPED` stub for any proc with a
struct, sequence or optional parameter, leaving Echo/Complex/Schedule
uncallable. Now that the native ABI carries those as flat C-POD structs, the Go
side can marshal them: emit an idiomatic Go struct per {.ffi.} type plus a
`toC()` that builds the matching `C.<Type>` (C.CString for strings, a C array
for seqs, present-flags for options, recursively for nested structs) and
returns cleanup funcs run via defer once the call returns. The native path
deep-copies every argument, so releasing the C buffers immediately is safe.
The C bridge already accepted struct-by-value params via the pass-through type
mapping; only the Go-side conversion and the `allSupported` gate needed work.
Bare seq/Option *top-level* params (not wrapped in a struct) remain skipped, as
the native ABI does not expose them either.
The generated package is now self-contained: the native `<lib>.h` is emitted
beside the `.go`, and the cgo directives use ${SRCDIR} so the header and the
staged library resolve without extra env vars. genbindings_go runs gofmt to
finalize column alignment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native C ABI only works in-process. This example demonstrates the other
half — the CBOR ABI crossing a process (and machine) boundary — since the `ctx`
pointer is process-local and cannot travel over the wire.
A server links libmy_timer, owns one context, and serves method calls; a client
links nothing (it only needs the FfiCbor encoder/ffi_decode_text in
my_timer_cbor.h) and speaks the same CBOR over a socket. Both binaries accept
`--unix <path>` for two processes on one host and `--tcp <host> <port>` for two
machines — the only difference is the socket family, so one client/server pair
covers both scenarios. Framing is length-prefixed in network byte order so the
endpoints may differ in OS, arch, or endianness.
`proto.h` carries the shared framing, the CBOR request builders, and a small
CBOR map reader so the client can pull text fields out of a response without a
full CBOR library. Verified end-to-end over both AF_UNIX and TCP loopback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The C codegen already emitted `my_timer.h` / `my_timer_cbor.h`, but the example
had no runnable driver. Add `example.c` exercising the native ABI end-to-end
(ctor with a struct param, string-returning version, struct-param echo, and a
deeply nested ComplexRequest), plus a Makefile that builds the Nim dylib from
the repo root — where the vendored Nimble deps resolve — and links the driver.
Native is the same-process path; the companion CBOR headers are for crossing a
process/machine boundary (see the forthcoming ipc example).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The native (zero-serialization) path previously handed `{.ffi.}` struct
params to the FFI thread using the Nim object layout (GC'd `string` fields),
which does not match the C-POD layout the generated header declares — an ABI
mismatch that left struct-param procs uncallable from C and skipped by the Go
codegen.
Wire the generated POD machinery into both the `{.ffi.}` and `{.ffiCtor.}`
native paths: a registered `{.ffi.}` struct now travels as its `<T>Pod`
mirror — `clonePod` deep-copies it off the caller's buffers into shared
(`c_malloc`) memory on the caller thread, `podToNim` rebuilds the Nim value on
the FFI thread, and `freePod` releases it from the CArgs free proc. `string`
collapses to `cstring` (alloc/ffiCFree); scalars copy direct. New classifiers
(`nativeWireType` / `nativeArgCopyStmt` / `nativeArgUnpackStmt`) keep both
paths and the CArgs alloc/free in lockstep so ownership can't drift.
The load-bearing invariant: the `<T>Pod` `{.bycopy.}` layout is identical to
the C struct emitted by `codegen/c.emitCStructs`, so the `exportc` symbol's
ABI matches the header even though Nim's own struct name differs. Keep the two
emitters in sync.
Validated end-to-end from C (TimerConfig, EchoRequest, and a nested
ComplexRequest with seq-of-structs, seq-of-strings and two Options) and clean
under ASAN. Struct *returns* still travel as CBOR on the native path; that is
left for a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Rust wrapper speaks CBOR, but after the native/CBOR split it still declared
and called the bare `<name>` request symbols — which are now the *native*
(typed-args) entry points, so every Rust request hit the wrong ABI (struct/ptr
mismatch). This is the Rust counterpart of the C++ fix (914c70a), which was
missed at the time. Point the ffi.rs externs and the api.rs ctor/method calls at
`<name>_cbor`; the destructor has no CBOR variant and the event registration is
unchanged here.
Verified at runtime: the rust_client now creates a context and round-trips
version / echo / schedule over CBOR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The C++ wrapper speaks CBOR, but after the native/CBOR split it still called the
bare `<name>` symbols — which are now the *native* (typed-args) entry points, so
every C++ call hit the wrong ABI and the C++ e2e failed 19/19 (also reddening
the ASan/TSan jobs, which run the same suite). Point the generated extern
declarations and call sites at `<name>_cbor` for `{.ffi.}` procs and the ctor;
the destructor has no CBOR variant and stays bare. Regenerated the timer and
echo C++ bindings. C++ e2e back to 19/19.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Commit f3206c3 split each FFI export into two distinctly-named Nim procs
(`<name>CborExport` / `<name>NativeExport`, and the ctor variants), so the
bare user name now resolves only to the Nim-native helper. The C-shape
integration tests still invoked the CBOR entry points by the bare name and
no longer compiled. Point those call sites at the `*CborExport` /
`*CborCtorExport` procs; the Nim-native `waitFor <name>(lib, ...)` calls keep
the bare name on purpose.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A single {.ffi.} definition now produces BOTH interfaces, chosen by the
caller at link time rather than by a global compile flag:
- `<name>` — native typed-arg C export. Args travel to the FFI thread in
a c_malloc'd C-POD struct passed by pointer (no CBOR), and the
result is delivered to the callback as raw bytes. This is the
preferred path for same-process callers: no serialization on
either side.
- `<name>_cbor` — the existing CBOR-buffer dispatcher, kept for generic /
cross-language callers.
Both share the user's helper proc; they register distinct handlers keyed by
"<Camel>Req" (CBOR) and "<Camel>ReqNative". FFIThreadRequest gains a `cborMode`
flag and a `payloadFree` hook so the native C-POD payload (which owns duplicated
cstring fields) is released correctly and an empty native result is delivered as
a zero-length buffer instead of the CBOR null sentinel. alloc.nim gains
ffiCMalloc/ffiCFree (prefixed to avoid Nim's style-insensitive clash with
ansi_c.c_malloc/c_free).
Verified end-to-end on a scalar-param lib: native calls return raw strings
("calc v1", "sum=42"); the _cbor variant still returns CBOR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0.2.0 carries each request as a single CBOR buffer over the exported ABI,
which is awkward for hand-written host bindings (every consumer would have
to encode CBOR and decode responses by hand). These two generators emit
ergonomic, ready-to-use bindings from the same {.ffi.} registry the C++/Rust
generators already consume.
- c.nim (targetLang=c): a self-contained <lib>.h with a small CBOR encoder,
ffi_decode_text(), and `static inline <lib>_<proc>(ctx, cb, ud, args...)`
wrappers that CBOR-encode and forward to the real export. The wrapper keeps
the export's source name but is given a distinct symbol via an __asm__ label
so the raw export's asm alias doesn't bind back to the wrapper (which would
recurse). Scalar/string params only; others fall back to the raw CBOR decl.
- go.nim (targetLang=go): a single <lib>.go cgo package that #includes the
generated <lib>.h and adds a condvar-backed response capture. This is the
key bit: 0.2.0 removed the synchronous fast-path, so a caller can no longer
read a result right after the call — the generated bridges block on the
callback, turning each async export into a blocking Go method. Also emits a
go.mod for importability.
Wired both into genBindings dispatch (targetLang "c"/"go") and added
genbindings_c / genbindings_go tasks. Both verified end-to-end against a
scalar-param test lib (build + run) and the real libwaku surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* protect against mem leak in case of failures sending requests to ffi thread
* better cleanup if failures in createFFIContext
* avoid dangling cstring in handleRes under ARC/ORC
* better resource cleanup in destroyFFIContext
* invoke onNotResponding if failure in destroyFFIContext
* correct seq copy in alloc
* make sure the lock is init before cleanUpResources
* better possible exception handling in processReq
* guard allocSharedSeq if given seq is empty
* enhance error handling in ffi_context
* add new tests and some corrections