mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-21 16:59:30 +00:00
Records the user-chosen direction for telling the compiler a proc's wire
format, applicable across the whole pragma family ({.ffi.}, {.ffiHost.}, …):
- per-proc pragma argument: {.ffi: raw.} / {.ffi: cbor.} (A1), default native;
- library-wide override declareLibrary(defaultAbiFormat = raw) (C2);
- the global -d:ffiMode compile flag is discarded.
'raw' (not 'native') names the zero-serialization C-POD format. Design only;
captures open questions (symbol emission, resolution order, codegen impact) for
further discussion before implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.3 KiB
3.3 KiB
Design: ABI format selection (raw vs cbor)
Status: direction decided, open for refinement. How a library author tells the
compiler which wire format a boundary-crossing proc uses. Applies to the whole
pragma family ({.ffi.}, {.ffiHost.}, {.ffiCtor.}, {.ffiDtor.}).
The two formats
raw— native, zero-serialization C-POD ABI. Same-process; the request/ result is a C struct passed/cast directly under the deep-copy + callback-lifetime ownership rule. The common path.cbor— CBOR-encoded buffer. For IPC / generic / cross-language callers where serialization is required anyway.
Decision
A1 — per-proc pragma argument (chosen). The format is an argument on the pragma, read by the macro at compile time:
proc echo(req: EchoReq): Future[Result[EchoResp, string]] {.ffi: raw.}
proc echo(req: EchoReq): Future[Result[EchoResp, string]] {.ffi: cbor.}
proc fetchProfile(id: string): Future[Result[Profile, string]] {.ffiHost: cbor.}
Matches the existing {.ffiEvent: "wire_name".} precedent (pragmas already take
args in this codebase). Local, granular, lets one library mix both formats.
C2 — library-wide default (chosen, layered on top). Set the default once at
declareLibrary; a per-proc pragma arg overrides it:
declareLibrary("my_app", MyApp, defaultAbiFormat = raw)
# every {.ffi.}/{.ffiHost.}/… is `raw` unless it says otherwise
So the common case stays terse, with per-proc control when needed.
Rejected:
- Global compile flag (
-d:ffiFormat=…) — discarded. All-or-nothing, action-at-a-distance, can't mix formats in one library.defaultAbiFormatreplaces its only virtue (a single default) without the downsides. - Distinct pragma names (
{.ffiCbor.},{.ffiHostCbor.}) — combinatorial explosion across the pragma family. - Format on the data type (
type T {.ffi: cbor.}) — misattributes a transport property to the data; breaks when one type is used by both a raw and a cbor proc.
Sketch
type AbiFormat* = enum
raw # native zero-serialization C-POD (same-process)
cbor # CBOR buffer (IPC / generic)
# two overloads, like ffiEvent: no-arg form defaults to the library default
macro ffi*(prc: untyped): untyped = ... # uses defaultAbiFormat
macro ffi*(fmt: static[AbiFormat], prc: untyped): untyped = ... # explicit override
Open questions (for further discussion)
- Enum name / spelling.
AbiFormatwith valuesraw/cbor? (rawovernativeper current preference.) - Resolution order. proc pragma arg →
defaultAbiFormat→ built-in fallback (raw?). Confirm the fallback whendeclareLibrarysets no default. - Symbol emission. Today
{.ffi.}emits BOTH<name>and<name>_cbor. Does a per-proc format mean we emit only the chosen symbol, or keep emitting both and let the format arg pick the primary? (Leaning: emit only the chosen one;<name>_cborsuffix stays only when both are explicitly wanted.) - Codegen impact. Each generator (c/go/cpp/rust/swift/kotlin) must honour the per-proc format when emitting wrappers + registration symbols.
- Future A3. Whether to later expose an orthogonal
{.ffi, wire: cbor.}transport pragma that composes across the family (deferred; A1+C2 first).