Skip to content

Commit f5055b6

Browse files
committed
chore: reduce walls of comments
1 parent 4981a1b commit f5055b6

51 files changed

Lines changed: 575 additions & 1816 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/echo/echo.nim

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import ffi, chronos, strutils
66
type Echo = object
77
prefix: string
88

9-
# `-d:ffiEchoAbiC` builds the `abi = c` variant (`_CWire` structs on the wire);
10-
# the default is the CBOR ABI. The same source drives both the `c_bindings/`
11-
# (CBOR) and `c_abi_bindings/` example outputs.
9+
# `-d:ffiEchoAbiC` builds the `abi = c` variant; default is the CBOR ABI.
1210
when defined(ffiEchoAbiC):
1311
declareLibrary("echo", Echo, defaultABIFormat = "c")
1412
else:

examples/timer/timer.nim

Lines changed: 10 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@ import ffi, chronos, options
22

33
type Maybe[T] = Option[T]
44

5-
# The library's main state type. The FFI context owns one instance.
6-
# Named `MyTimer` (not `Timer`) so the C-exported symbols are
7-
# `my_timer_create` / `my_timer_destroy` / ... — `timer_create` would
8-
# collide with POSIX `<time.h>`'s `int timer_create(clockid_t, ...)` which
9-
# `<pthread.h>` transitively drags in on Linux.
5+
# Named `MyTimer` (not `Timer`) so C symbols like `my_timer_create` don't collide with POSIX `<time.h>`'s `timer_create`.
106
type MyTimer = object
117
name: string # set at creation time, read back in each response
128

13-
# `defaultABIFormat` selects the wire format every {.ffi.} / {.ffiEvent.} / ...
14-
# in this library inherits; "cbor" is the default and can be overridden per
15-
# annotation with an `"abi = ..."` spec.
9+
# `defaultABIFormat` is the wire format every annotation inherits (override per-annotation with "abi = ...").
1610
declareLibrary("my_timer", MyTimer, defaultABIFormat = "cbor")
1711

1812
type TimerConfig {.ffi.} = object
@@ -37,37 +31,27 @@ type ComplexResponse {.ffi.} = object
3731
itemCount: int
3832
hasNote: bool
3933

40-
# --- Library-initiated event ----------------------------------------------
41-
# Demonstrates the {.ffiEvent.} macro: a typed event the library can fire
42-
# from any {.ffi.} handler, dispatched to the foreign side's registered
43-
# callback as CBOR. Per-target codegens emit a typed handler-struct +
44-
# dispatcher so the foreign caller decodes nothing by hand.
34+
# {.ffiEvent.}: a typed event any {.ffi.} handler can fire to the foreign callback.
4535
type EchoEvent {.ffi.} = object
4636
message: string
4737
echoCount: int
4838

4939
proc onEchoFired*(evt: EchoEvent) {.ffiEvent: "on_echo_fired".}
5040

51-
# --- Constructor -----------------------------------------------------------
52-
# Called once from Rust. Creates the FFIContext + MyTimer.
53-
# Uses chronos (await sleepAsync) so the body is async.
41+
# Constructor: creates the FFIContext + MyTimer; async via chronos.
5442
proc myTimerCreate*(config: TimerConfig): Future[Result[MyTimer, string]] {.ffiCtor.} =
5543
await sleepAsync(1.milliseconds) # proves chronos is live on the FFI thread
5644
return ok(MyTimer(name: config.name))
5745

58-
# --- Async method ----------------------------------------------------------
59-
# Waits `delayMs` milliseconds (non-blocking, on the chronos event loop)
60-
# then echoes the message back with a request counter.
46+
# Async method: sleeps `delayMs` then echoes the message back.
6147
proc myTimerEcho*(
6248
timer: MyTimer, req: EchoRequest
6349
): Future[Result[EchoResponse, string]] {.ffi.} =
6450
await sleepAsync(req.delayMs.milliseconds)
6551
onEchoFired(EchoEvent(message: req.message, echoCount: 1))
6652
return ok(EchoResponse(echoed: req.message, timerName: timer.name))
6753

68-
# --- Sync method -----------------------------------------------------------
69-
# No await — the macro detects this and fires the callback inline,
70-
# without going through the request channel.
54+
# Sync method: no await, so the macro fires the callback inline.
7155
proc myTimerVersion*(timer: MyTimer): Future[Result[string, string]] {.ffi.} =
7256
return ok("nim-timer v0.1.0")
7357

@@ -82,12 +66,7 @@ proc myTimerComplex*(
8266
return
8367
ok(ComplexResponse(summary: summary, itemCount: count, hasNote: req.note.isSome))
8468

85-
# --- Multiple complex parameters -------------------------------------------
86-
# Demonstrates how a {.ffi.} proc handles several object-typed parameters at
87-
# once. Each parameter is its own {.ffi.} type, so it lands in the generated
88-
# foreign-side bindings as a first-class struct/class, and the per-proc Req
89-
# envelope (MyTimerScheduleReq on the wire) carries all three under field
90-
# names that match the Nim params.
69+
# Multiple object-typed params: each is its own {.ffi.} type, all carried in one per-proc Req envelope.
9170
type JobSpec {.ffi.} = object
9271
name: string
9372
payload: seq[string]
@@ -112,10 +91,7 @@ type ScheduleResult {.ffi.} = object
11291
proc myTimerSchedule*(
11392
timer: MyTimer, job: JobSpec, retry: RetryPolicy, schedule: ScheduleConfig
11493
): Future[Result[ScheduleResult, string]] {.ffi.} =
115-
## Composes three independent object-typed parameters (`job`, `retry`,
116-
## `schedule`) into a single scheduling decision. The macro packs them into
117-
## one CBOR-encoded request envelope on the wire and unpacks them back into
118-
## the named locals before this body runs.
94+
## Three object-typed params (`job`, `retry`, `schedule`) packed into one CBOR envelope.
11995
await sleepAsync(1.milliseconds)
12096
if job.name.len == 0:
12197
return err("job name must not be empty")
@@ -137,22 +113,8 @@ proc myTimerSchedule*(
137113
)
138114

139115
proc my_timer_destroy*(timer: MyTimer) {.ffiDtor.} =
140-
## Tears down the FFI context created by my_timer_create.
141-
## Blocks until the FFI thread and watchdog thread have joined.
116+
## Tears down the FFI context; blocks until FFI + watchdog threads join.
142117
discard
143118

144-
# genBindings() must be the LAST top-level call in the FFI root file —
145-
# after every {.ffi.}, {.ffiCtor.} and {.ffiDtor.} pragma. Each pragma
146-
# fires at compile time and registers its proc into the compile-time
147-
# ffiProcRegistry / ffiTypeRegistry; genBindings() then reads those
148-
# registries to emit the language bindings. If genBindings() runs before
149-
# a pragma, that proc is silently absent from the generated bindings.
150-
#
151-
# Multi-file libraries: keep all .ffi./.ffiCtor./.ffiDtor. pragmas in
152-
# imported sub-modules and call genBindings() once at the bottom of the
153-
# top-level file that imports them — Nim resolves imports before the
154-
# importing file's body runs, so the registries are fully populated by
155-
# the time genBindings() executes.
156-
#
157-
# genBindings() is a compile-time no-op unless -d:ffiGenBindings is set.
119+
# genBindings() must be the LAST top-level call, after every pragma has registered its proc; it reads those registries to emit bindings (no-op unless -d:ffiGenBindings).
158120
genBindings()

ffi/alloc.nim

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,13 @@
11
## Cross-thread allocation helpers backed by libc `malloc`/`free`.
2-
##
3-
## We deliberately avoid Nim's `allocShared`/`deallocShared` here. Under
4-
## `--mm:orc` they delegate to the per-thread `allocator` MemRegion stored
5-
## in TLS; freeing such a buffer from a different thread later walks
6-
## `chunk.owner` back to that MemRegion. If the original thread has exited
7-
## by then (e.g. a `std::async` worker that produced the FFI request and
8-
## was destroyed before the FFI thread ran `deleteRequest`), `chunk.owner`
9-
## dangles into reclaimed TLS and `addToSharedFreeList` segfaults — TSan on
10-
## ARM reproduces this from `TimerE2E.ThreadedHammer`. `malloc`/`free` are
11-
## process-global and thread-lifetime-independent, so freeing on a different
12-
## thread is safe.
2+
## Avoids Nim `allocShared` whose TLS-owned MemRegion segfaults when freed from a
3+
## thread other than the one that allocated (and may have since exited); libc is process-global.
134

145
import system/ansi_c
156

16-
## Can be shared safely between threads
177
type SharedSeq*[T] = tuple[data: ptr UncheckedArray[T], len: int]
188

199
proc alloc*(str: cstring): cstring =
20-
## Allocates a fresh null-terminated copy of `str` via `c_malloc`. The
21-
## returned pointer must be released with `dealloc(cstring)`.
10+
## Fresh null-terminated `c_malloc` copy of `str`; free with `dealloc(cstring)`.
2211
if str.isNil():
2312
var ret = cast[cstring](c_malloc(1))
2413
ret[0] = '\0'
@@ -29,8 +18,6 @@ proc alloc*(str: cstring): cstring =
2918
return ret
3019

3120
proc alloc*(str: string): cstring =
32-
## Allocates a fresh null-terminated copy of `str` via `c_malloc`. The
33-
## returned pointer must be released with `dealloc(cstring)`.
3421
var ret = cast[cstring](c_malloc(csize_t(str.len + 1)))
3522
let s = cast[seq[char]](str)
3623
for i in 0 ..< str.len:
@@ -39,20 +26,15 @@ proc alloc*(str: string): cstring =
3926
return ret
4027

4128
proc dealloc*(p: cstring) {.inline.} =
42-
## Frees a buffer obtained from one of the `alloc(...)` overloads above.
43-
## Nil-safe.
29+
## Frees an `alloc(...)` buffer. Nil-safe.
4430
if not p.isNil():
4531
c_free(cast[pointer](p))
4632

4733
proc allocBox*(size: int): pointer =
48-
## `c_malloc` block for a cross-thread callback box (allocated on the foreign
49-
## caller thread, freed on the FFI thread). Uses libc for the same
50-
## thread-lifetime safety reason as the rest of this module. Free with
51-
## `freeBox`.
34+
## `c_malloc` block for a cross-thread callback box; free with `freeBox`.
5235
c_malloc(csize_t(size))
5336

5437
proc freeBox*(p: pointer) =
55-
## Releases a block from `allocBox`. Nil-safe.
5638
if not p.isNil():
5739
c_free(p)
5840

@@ -70,8 +52,6 @@ proc deallocSharedSeq*[T](s: var SharedSeq[T]) =
7052
s.len = 0
7153

7254
proc toSeq*[T](s: SharedSeq[T]): seq[T] =
73-
## Creates a seq[T] from a SharedSeq[T]. No explicit dealloc is required
74-
## as req[T] is a GC managed type.
7555
var ret = newSeq[T]()
7656
for i in 0 ..< s.len:
7757
ret.add(s.data[i])

ffi/cbor_serial.nim

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
1-
## Thin wrapper around `cbor_serialization` (vacp2p/nim-cbor-serialization) that
2-
## adapts the library's exception-based API to the `Result[T, string]` shape the
3-
## FFI plumbing expects, and adds the few transport-only details the FFI layer
4-
## needs on top:
5-
##
6-
## - `cborEncodeShared` writes into a `c_malloc` buffer so the FFI thread
7-
## can take ownership of the bytes without a second copy. `c_malloc`
8-
## (not `allocShared`) because the buffer must be freeable from the FFI
9-
## thread after the producing thread may have exited — see the note in
10-
## `ffi/ffi_thread_request.nim`.
11-
## - `CborNullByte` is the canonical "successful but no value" wire sentinel.
12-
##
13-
## `cborEncode` / `cborDecode` are the public API the macros and tests use.
14-
##
15-
## Type contract for `.ffi.` payloads:
16-
##
17-
## - Plain `object` types flow as value copies — fields are serialized and
18-
## the foreign side reconstructs an independent value.
19-
## - `ref T` is *also* a value copy: `cbor_serialization`'s default `ref T`
20-
## writer dereferences and encodes the pointee, so the receiving side
21-
## allocates a fresh `ref` local to its own GC heap. No object identity
22-
## is preserved across the boundary — the two sides own independent
23-
## copies after decode.
24-
## - Raw `pointer` / `ptr T` are rejected at macro-expansion time (see
25-
## `rejectRawPtrType` in `internal/ffi_macro.nim`). The only address that
26-
## legitimately crosses the boundary is the opaque ctx handle returned by
27-
## `.ffiCtor.`, which is validated against `FFIContextPool` on every
28-
## re-entry. Arbitrary user pointers would lack that validation.
1+
## `cbor_serialization` wrapper adapting its exception API to `Result[T, string]` for the FFI layer.
2+
## `.ffi.` payloads (plain `object` and `ref T`) cross as value copies; raw `pointer`/`ptr T` are
3+
## rejected at macro-expansion time (see `rejectRawPtrType`).
294

305
import system/ansi_c
316
import cbor_serialization, cbor_serialization/std/options, results
327

338
export cbor_serialization, options, results
349

3510
const CborNullByte*: byte = 0xf6'u8
36-
## CBOR encoding of `null` — used as the wire sentinel for empty OK payloads.
11+
## CBOR `null` — wire sentinel for empty OK payloads.
3712

3813
proc cborEncode*[T](x: T): seq[byte] =
39-
## CBOR-encode any cbor_serialization-supported type (plus `pointer` / `ptr T`
40-
## via our custom writers) into a fresh `seq[byte]`.
4114
return Cbor.encode(x)
4215

4316
proc cborEncodeShared*[T](x: T): tuple[data: ptr UncheckedArray[byte], len: int] =
44-
## Encodes `x` into a `c_malloc` buffer.
45-
##
46-
## The returned `data` is owned by the caller and must be freed exactly
47-
## once via `cborFreeShared`. The
48-
## `FFIThreadRequest deleteRequest` path frees adopted buffers
49-
## automatically. Empty payloads return `(nil, 0)` without allocating.
17+
## Encodes `x` into a caller-owned `c_malloc` buffer (free via `cborFreeShared`).
18+
## Empty payloads return `(nil, 0)` without allocating.
5019
let bytes = Cbor.encode(x)
5120
if bytes.len == 0:
5221
return (nil, 0)
@@ -55,16 +24,13 @@ proc cborEncodeShared*[T](x: T): tuple[data: ptr UncheckedArray[byte], len: int]
5524
return (buf, bytes.len)
5625

5726
proc cborFreeShared*(data: var ptr UncheckedArray[byte]) =
58-
## Releases a buffer previously returned by `cborEncodeShared` and nils
59-
## the caller's pointer so a stale reference can't be reused after free.
60-
## Safe to call with `nil` (the `(nil, 0)` empty-payload contract).
27+
## Frees a `cborEncodeShared` buffer and nils the pointer. Nil-safe.
6128
if not data.isNil():
6229
c_free(data)
6330
data = nil
6431

6532
proc cborDecode*[T](data: openArray[byte], _: typedesc[T]): Result[T, string] =
66-
## Decode `data` into a `T`, converting any cbor_serialization exception
67-
## into a `Result.err` carrying the exception message.
33+
## Decode `data` into a `T`, mapping any exception to `Result.err`.
6834
try:
6935
let v = Cbor.decode(data, T)
7036
return ok(v)
@@ -74,8 +40,7 @@ proc cborDecode*[T](data: openArray[byte], _: typedesc[T]): Result[T, string] =
7440
proc cborDecodePtr*[T](
7541
data: ptr UncheckedArray[byte], dataLen: int, _: typedesc[T]
7642
): Result[T, string] =
77-
## Convenience for ptr+len buffers (used by the macro to avoid binding an
78-
## openArray to a `let`).
43+
## Convenience for ptr+len buffers.
7944
if dataLen <= 0:
8045
return cborDecode(default(seq[byte]), T)
8146
cborDecode(toOpenArray(data, 0, dataLen - 1), T)

0 commit comments

Comments
 (0)