docs: ABI format selection decision (raw vs cbor)

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>
This commit is contained in:
Ivan FB 2026-06-13 23:01:16 +02:00
parent c90200de2b
commit 50b56c0cad
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
2 changed files with 82 additions and 0 deletions

75
docs/design-abi-format.md Normal file
View File

@ -0,0 +1,75 @@
# 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:
```nim
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:
```nim
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. `defaultAbiFormat`
replaces 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
```nim
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.** `AbiFormat` with values `raw` / `cbor`? (`raw` over
`native` per current preference.)
- **Resolution order.** proc pragma arg → `defaultAbiFormat` → built-in fallback
(`raw`?). Confirm the fallback when `declareLibrary` sets 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>_cbor` suffix 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).

View File

@ -46,6 +46,13 @@ Export `<lib>_abi_descriptor()` returning the schema (CBOR/JSON) so a host can
validate compatibility at load time. Addresses the deferred CBOR wire-versioning
concern.
## Cross-cutting decisions
- **ABI format selection** ([design-abi-format.md](design-abi-format.md)) — per-proc
pragma arg `{.ffi: raw.}` / `{.ffi: cbor.}` (default native/`raw`), with a
library-wide `declareLibrary(defaultAbiFormat = …)` override. Global compile
flag discarded. Applies to `{.ffiHost.}` too. Direction decided, design only.
## Adjacent / parallel tracks (already discussed elsewhere)
- **seq/Option + multi-struct param marshaling parity** for the Swift (#59) and