52 Commits

Author SHA1 Message Date
Ivan FB
88c7c8b8b7
feat(codegen): native typed events for the Rust generator
Adds native (zero-CBOR) event support to the Rust generator, mirroring the
cpp_native / Go event path: a per-event `add_<wire>_listener` registrar takes a
closure, boxes it, and registers it through the bare `<lib>_add_event_listener`
(native) entry point. The extern "C" trampoline reads the payload as the raw
C-POD struct and hands the consumer a borrowed idiomatic value via from_c — no
serialization on the hot path.

The node owns the boxed closures in a Mutex<HashMap<id, Box<dyn Any>>> keyed by
listener id so they outlive the call, and `remove_event_listener` drops them and
calls the bare remove entry point. Event externs are only emitted when the
library declares events, so event-free crates stay minimal.

Verified end-to-end: the demo registers a listener, echo fires on_echo_fired
inline, the typed EchoEvent reaches the closure, and removal returns true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:05:38 +02:00
Ivan FB
99fdcfdff7
feat(codegen): native Rust generator handles seq / Option / nested
Extends the native Rust marshalling to sequences and optionals (the Rust
counterpart of the cpp_native increment): a field maps to Vec<T> / Option<T>,
the repr(C) mirror gains the matching `{ ptr, len }` / `{ present, value }`
fields, and `to_c` now returns a holder owning the CStrings + C-array backing
(Vec/CString live on the heap, so the C struct's raw pointers survive the move
and the call). `from_c` reads seq/Option back out via slice + present-flag.

Unblocks the timer's complex (seq-of-structs / seq-of-strings / two optionals)
and schedule (three struct params). Verified end-to-end — the demo round-trips
typed ComplexResponse / ScheduleResult.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:59:57 +02:00
Ivan FB
23152d4fe7
feat(codegen): native (non-CBOR) Rust generator + rust.nim -> rust_cbor.nim split
Splits the Rust codegen the way C++ is split: rename `rust.nim` -> `rust_cbor.nim`
(CBOR) and add `rust_native.nim` (native). Dispatch on `targetLang=rust` now
honours `-d:ffiMode` (native/cbor); the crates share file names so each mode
writes its own dir (rust_bindings vs rust_native_bindings).

`rust_native.nim` emits a `<lib>_native` crate — the Rust analogue of
`cpp_native`: `#[repr(C)]` POD mirrors + `extern "C"` native entry points
(ffi.rs); idiomatic structs with `to_c`/`from_c`, a holder owning the CStrings
for the call (types.rs); and a `<Lib>Node` whose methods marshal typed args in /
read typed struct returns out, blocking via std mpsc (api.rs).

First cut: scalar/string/bool/float/nested-struct fields (create/version/echo);
seq/Option params are SKIPPED, native events next. Verified end-to-end — the
generated crate builds and the demo round-trips a typed EchoResponse. Tasks:
genbindings_rust (CBOR), genbindings_rust_native.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:51:06 +02:00
Ivan FB
f08cb7971d
feat(codegen): C native event payloads + -d:ffiMode (native/cbor/both) + tasks
- 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>
2026-05-31 18:37:27 +02:00
Ivan FB
ceccc7bef3
feat(codegen): Go per-event typed native event handlers
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>
2026-05-31 18:37:27 +02:00
Ivan FB
918dd72390
feat(ffi): native event delivery + dual-ABI event symbol naming
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>
2026-05-31 18:37:27 +02:00
Ivan FB
0a8b53a06d
feat(codegen): Go bindings return typed structs natively
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>
2026-05-31 18:37:19 +02:00
Ivan FB
9cf4bf0127
feat(ffi): native typed struct returns for the C ABI
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>
2026-05-31 18:37:18 +02:00
Ivan FB
965fd68785
feat(codegen): Go bindings support struct/seq/Option params
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>
2026-05-31 18:37:18 +02:00
Ivan FB
ad493d6f9d
feat(ffi): cross struct/seq/Option params natively via POD
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>
2026-05-31 18:37:05 +02:00
Ivan FB
4af031c421
fix(codegen): Rust bindings target the *_cbor request exports
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>
2026-05-31 18:34:30 +02:00
Ivan FB
914c70a131
fix(codegen): C++ bindings target the *_cbor exports
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>
2026-05-31 16:31:12 +02:00
Ivan FB
f3206c30b8
feat(ffi): emit a native (zero-serialization) C ABI alongside CBOR
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>
2026-05-31 02:02:39 +02:00
Ivan FB
028dbb56e6
feat(codegen): add C and Go (cgo) binding generators
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>
2026-05-31 00:06:46 +02:00
Ivan FB
c43563f82f
rust codegen: per-event typed add_on_<x>_listener + wildcard add_event_listener (#52) 2026-05-29 20:40:28 +02:00
Gabriel Cruz
7ccf34591d
chore: avoid throwing exceptions in C++ bindings (#46) 2026-05-29 12:35:49 -03:00
Ivan FB
e394166c46
Cpp typed event listeners (#51) 2026-05-28 22:40:33 +02:00
Ivan FB
3f19411684
Event listener abi (#50) 2026-05-28 16:00:28 +02:00
Ivan FB
496a341466
Event registry wiring (#49) 2026-05-27 22:26:39 +02:00
Ivan FB
e99220a3e4
fix use-after-free concern (#47)
Co-authored-by: Gabriel Cruz <8129788+gmelodie@users.noreply.github.com>
2026-05-26 23:46:27 +02:00
Ivan FB
216316826c
add FFIEventRegistry: multi-listener data structure for FFI events (#45) 2026-05-26 21:42:01 +02:00
Gabriel Cruz
436c0d760b
test(cpp-e2e): add multi-context, cross-library, pipeline, stress tests (#30) (#42) 2026-05-26 09:18:12 -03:00
Ivan FB
6a7e4616fd
Adjust events to cbor (#39) 2026-05-25 15:51:56 +02:00
Gabriel Cruz
31d0ebfa51
chore(ci): extend cpp-e2e to OS matrix (#38) 2026-05-22 11:43:37 -03:00
Ivan FB
c7cf46bdea
avoid move ctor and assing operator in cpp generated code (#36) 2026-05-21 16:38:13 +02:00
Gabriel Cruz
ee472f05ad
chore(ci): fsanitize tests (#34) 2026-05-20 14:14:42 -03:00
Ivan FB
e12745e85c
Add cddl generator (#24) 2026-05-18 20:00:57 +02:00
Ivan FB
ac303a707e
Start using CBOR (#23)
Co-authored-by: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com>
Co-authored-by: Gabriel Cruz <8129788+gmelodie@users.noreply.github.com>
2026-05-16 01:08:42 +02:00
Ivan FB
e48c9f9edf
remove ffiType macro because it is duplicated by ffi macro (#22) 2026-05-13 14:48:54 +02:00
Ivan FB
6d31fa30bd
use fixed array of ctx to avoid consuming all fds (#14) 2026-05-13 00:02:23 +02:00
Gabriel Cruz
81c62c263e
fix: context buffer overflow (#21) 2026-05-11 19:21:40 -03:00
Ivan FB
a52c4facd9
Simplified FFI authoring with auto-generated C++ and Rust bindings (#15) 2026-05-11 23:28:17 +02:00
Gabriel Cruz
e99eb60fd1
chore: run tests with refc (#20)
* chore: run tests with refc

* chore: split ci jobs

* chore: fix tests
2026-05-09 10:47:37 -03:00
Gabriel Cruz
37d19c0687
chore: remove CatchableError (#19) 2026-05-08 14:14:16 -03:00
Ivan FB
df2277e726
Fix memleaks (#11)
* 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
2026-04-27 21:22:45 +02:00
Pablo Lopez
bb8a3e7e22
fix: add install_name for mac (#8)
not to hardcode the paths
2026-02-20 17:37:13 +02:00
Ivan Folgueira Bande
bb8ed28ab0
add require chronicles taskpools and import std tables 2026-01-23 15:01:08 +01:00
Ivan FB
9fff004b19
add explicit timeout when sending watchdog requests (#7) 2026-01-16 15:48:35 +01:00
Ivan Folgueira Bande
d7a5492121
avoid use gc ed types in FFIContext and better macro documentation 2025-12-13 23:53:59 +01:00
Ivan Folgueira Bande
6811c8675f
make registeredRequests no threadvar
This gives better results in logos-messaging-go-bindings
Can be shared across threads because is populated at compile-time
and only read at run-time.
2025-12-12 14:45:59 +01:00
Ivan Folgueira Bande
803744dd29
rm some Waku leftover references 2025-12-11 23:50:00 +01:00
Ivan Folgueira Bande
5964e287a9
rm useless comments 2025-12-11 17:21:47 +01:00
Ivan Folgueira Bande
e0cf0c8842
fixes for proper proc exposure to c 2025-12-11 17:11:59 +01:00
Ivan Folgueira Bande
142a57f543
set logger in main thread body 2025-12-11 17:10:24 +01:00
Ivan Folgueira Bande
da3251aa1a
evolving more 2025-12-10 17:43:26 +01:00
Ivan Folgueira Bande
86dc58e7c2
add ffi macro 2025-12-09 18:51:50 +01:00
Ivan Folgueira Bande
b974eab39c
comment echo repr lines 2025-09-17 14:38:01 +02:00
Ivan Folgueira Bande
3c7f7f9a1c
remove comment 2025-09-17 14:37:53 +02:00
Ivan Folgueira Bande
a1a6536b3c
general ffi increments 2025-09-17 14:37:45 +02:00
Ivan Folgueira Bande
356f0ccc1b
working simplification 2025-09-17 14:37:36 +02:00