Aligns the C++ generators with the C generator and the symbol naming: the
native (zero-serialization, same-process) wrapper is the bare `<lib>.hpp` and
the CBOR (inter-process) wrapper carries the `_cbor` suffix — mirroring the C
headers (`<lib>.h` / `<lib>_cbor.h`) and the `<name>` / `<name>_cbor` exports.
Previously native was `<lib>_native.hpp` and CBOR was the bare `<lib>.hpp`,
which is backwards from the symbol convention and would collide on the native
`<lib>.h` when both ABIs emit into one dir (ffiMode=both). With the flip, a
single `genbindings_cpp` run now drops `<lib>.hpp` + `<lib>_cbor.hpp` side by
side, exactly like c_bindings holds both `.h` headers.
Consumers updated to match: the CBOR cpp_bindings driver and the C++ e2e suite
include `*_cbor.hpp`; the native example includes the bare `<lib>.hpp`.
Validated: native example runs on `my_timer.hpp`; C++ e2e suite 19/19 on the
`_cbor.hpp` headers; check_bindings_cpp regen is deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the ergonomic native event surface to the C++ generator:
`node.On<Event>(std::function<void(const <Payload>&)>)` registers a native
listener; a per-event extern "C" trampoline reads the typed POD
(`fromC(*reinterpret_cast<const ::<Payload>*>(msg))`) and invokes the handler —
no CBOR. The handler is owned by the node (a `std::map` of `ListenerBase`) so
its address stays valid until `removeEventListener`.
The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo
fires it. Verified end-to-end and ASAN-clean.
With this the native C++ generator covers the full surface: requests
(scalar/string/bool/seq/Option/nested), typed struct returns, and typed events.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the native C++ marshalling to sequences and optionals: a C++ field maps
to std::vector<T> / std::optional<T>, and `toC` returns a holder that owns the
C-array backing (move/NRVO-safe std::vectors) while string pointers borrow the
C++ argument — valid for the call, which the library deep-copies. `fromC` reads
seq/Option back out of the C-POD.
Unblocks the timer's complex (seq-of-structs / seq-of-strings / two optionals)
and schedule (three struct params) methods — they now generate and round-trip
typed results. Verified end-to-end and ASAN-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A native C++ binding generator (`cpp_native.nim`), the C++ counterpart of the C
and Go native paths and companion to the CBOR `cpp.nim`. It emits
`<lib>_native.hpp`: an idiomatic C++ struct + `toC`/`fromC` per `{.ffi.}` type,
and a `<Lib>Node` class whose methods marshal typed args into / read typed
struct returns out of the native ABI (`<name>` entry points + flat C structs in
`<lib>.h`) — zero serialization. Wired into genBindings under
`targetLang=cpp` + `-d:ffiMode=native`; emits the native C header alongside so
the binding is self-contained. Task: `genbindings_cpp_native`.
First cut covers scalar/string/bool/nested-struct fields (create/version/echo);
seq/Option params are `// SKIPPED`, and native typed events are next. Filename
is `_native.hpp` for now to coexist with the CBOR `.hpp` (rename is a follow-up).
Verified end-to-end: the generated example builds and round-trips a typed
EchoResponse (`make run`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add the `-d:ffiMode=native|cbor|both` strdefine (default both) with
`ffiEmitNative`/`ffiEmitCbor` helpers; the C generator now emits only the
selected header(s) (`<lib>.h` and/or `<lib>_cbor.h`).
- Native C events: the native header documents each event's payload type
(`"on_echo_fired" -> const EchoEvent *`) so consumers cast the callback's msg
to the typed struct — the bare native listener already delivers it.
- nimble tasks: `genbindings_c` (both), `genbindings_c_native`,
`genbindings_c_cbor`.
Verified: native mode emits only my_timer.h, cbor only my_timer_cbor.h, both
emits both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the ergonomic native event surface for Go: `node.On<Event>(func(<Payload>))`
registers a native listener for that event and the library delivers the typed
`<Payload>` POD, which an exported Go callback reads into a Go struct (reusing
the generated `fromC`) and hands to the user's handler — no CBOR parsing. Sits
beside the existing CBOR `SetEventHandler` (wildcard / inter-process).
The example registers `OnEchoFired` and receives a typed `EchoEvent` when Echo
fires it. Verified end-to-end with `go run -race`.
C/C++/Rust get the same per-event typed handler + router next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Events now mirror the native/CBOR split already in place for requests, with the
same symbol-naming convention:
- `<lib>_add_event_listener` -> NATIVE listener (typed `<T>Pod` by pointer)
- `<lib>_add_event_listener_cbor` -> CBOR listener (EventEnvelope bytes)
Framework: `FFIEventListener` gains a `native` flag; `addEventListener` a
`native` param; a new `dispatchFFIEventDual` template builds the `<T>Pod` once
for native listeners (`nimToPod`/`freePod`) and the CBOR envelope once for the
rest, fanning each out — so a single `{.ffiEvent.}` dispatch serves both kinds.
`declareLibrary` exports both registration entry points.
Generators: the bare `<lib>_add_event_listener` is the native symbol; every
CBOR consumer (C/C++/Go/Rust) now targets `<lib>_add_event_listener_cbor`. The
rename and the generator updates ship together so the bare name is never briefly
broken. Bindings regenerated.
Validated: native-event unit test (typed POD to native + CBOR to cbor listener,
orc/refc/ASAN); full unit suite; C++ e2e 19/19; Go example; existing event
tests unchanged. The per-event *typed* native callback + wildcard router (the
ergonomic consumer surface) is a follow-up.
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>