@@ -20,17 +20,53 @@ const EmptyErrorMarker = "unknown error"
2020 # # the callback's msg ptr non-nil and gives the foreign side a recognizable
2121 # # fallback to log.
2222
23+ const MaxScalarArgs * = 8
24+ # # Inline capacity for the scalar fast path. A `.ffi.` method with more than
25+ # # this many scalar params can't use the fast path (checked at compile time).
26+
2327type FFIThreadRequest * = object
2428 callback* : FFICallBack
2529 userData* : pointer
2630 reqId* : cstring # # Per-proc Req type name used to look up the handler.
2731 data* : ptr UncheckedArray [byte ] # # Owned CBOR-encoded request payload.
2832 dataLen* : int
33+ isScalar* : bool
34+ # # Set by `initScalar`: the payload rode inline in `scalarArgs` (no CBOR,
35+ # # no `data` buffer), so `deleteRequest` has nothing extra to free.
36+ scalarArgs* : array [MaxScalarArgs , uint64 ]
37+ # # Scalar-fast-path args inlined in the envelope (one `ffiPackScalar` value
38+ # # per slot) so there's no per-call `c_malloc`. A plain array rather than a
39+ # # `union` with `data`: costs a fixed 64 bytes per request but keeps the
40+ # # `next` link and `deleteRequest` unaliased and branch-free.
2941 next* : ptr FFIThreadRequest
3042 # # Intrusive ingress-queue link (see `ffi_request_queue.nim`). Touched only
3143 # # under the queue's lock; the request doubles as its own node, so no
3244 # # separate node alloc lands on the per-thread ORC MemRegion.
3345
46+ func ffiPackScalar * [T](x: T): uint64 =
47+ # # Bit-cast one scalar into a `uint64` request slot. Signed ints sign-extend
48+ # # to 64 bits; `float32` widens to `float64` (exactly representable, so the
49+ # # value round-trips); `bool` becomes 0/1. Reverse with `ffiUnpackScalar`.
50+ when T is SomeFloat :
51+ cast [uint64 ](float64 (x))
52+ elif T is bool :
53+ uint64 (ord (x))
54+ elif T is SomeSignedInt :
55+ cast [uint64 ](int64 (x))
56+ else :
57+ uint64 (x)
58+
59+ func ffiUnpackScalar * [T](u: uint64 , _: typedesc [T]): T =
60+ # # Inverse of `ffiPackScalar`: reinterpret a request slot back into `T`.
61+ when T is SomeFloat :
62+ T (cast [float64 ](u))
63+ elif T is bool :
64+ u != 0 'u64
65+ elif T is SomeSignedInt :
66+ T (cast [int64 ](u))
67+ else :
68+ T (u)
69+
3470proc allocBaseRequest (
3571 callback: FFICallBack , userData: pointer , reqId: cstring
3672): ptr FFIThreadRequest =
@@ -43,6 +79,7 @@ proc allocBaseRequest(
4379 ret[].reqId = reqId.alloc ()
4480 ret[].data = nil
4581 ret[].dataLen = 0
82+ ret[].isScalar = false
4683 ret[].next = nil
4784 return ret
4885
@@ -118,6 +155,49 @@ proc initFromOwnedShared*(
118155 adoptOwnedSharedPayload (ret, data, dataLen)
119156 return ret
120157
158+ proc initScalar * (
159+ T: typedesc [FFIThreadRequest ],
160+ callback: FFICallBack ,
161+ userData: pointer ,
162+ reqId: cstring ,
163+ args: varargs [uint64 ],
164+ ): ptr type T =
165+ # # Builds a scalar-fast-path request: the packed scalar args ride inline in
166+ # # `scalarArgs` with no payload `c_malloc`. `args` come from `ffiPackScalar`.
167+ # # Only the routing `reqId` cstring is heap-allocated, same as the CBOR path.
168+ doAssert args.len <= MaxScalarArgs ,
169+ " initScalar: " & $ args.len & " scalar args exceed MaxScalarArgs (" & $ MaxScalarArgs &
170+ " )"
171+ var ret = allocBaseRequest (callback, userData, reqId)
172+ ret[].isScalar = true
173+ for i in 0 ..< args.len:
174+ ret[].scalarArgs[i] = args[i]
175+ ret
176+
177+ func ffiScalarRetBytes * [T](x: T): seq [byte ] =
178+ # # Serializes a scalar handler result into the raw response payload the
179+ # # callback carries — no CBOR envelope. A `string`/`cstring` rides as its
180+ # # own UTF-8 bytes (like the error path); every other scalar rides as the
181+ # # 8-byte native-endian image of `ffiPackScalar(x)`. Note: an empty string
182+ # # yields a 0-length payload, which `handleRes` sends as the CBOR-null
183+ # # sentinel — the foreign scalar reader (a follow-up) must special-case it.
184+ when T is string :
185+ var b = newSeq [byte ](x.len)
186+ if x.len > 0 :
187+ copyMem (addr b[0 ], unsafeAddr x[0 ], x.len)
188+ b
189+ elif T is cstring :
190+ let n = x.len
191+ var b = newSeq [byte ](n)
192+ if n > 0 :
193+ copyMem (addr b [0 ], cast [pointer ](x), n)
194+ b
195+ else :
196+ let u = ffiPackScalar (x)
197+ var b = newSeq [byte ](sizeof (uint64 ))
198+ copyMem (addr b[0 ], unsafeAddr u, sizeof (uint64 ))
199+ b
200+
121201proc deleteRequest * (request: ptr FFIThreadRequest ) =
122202 if not request[].data.isNil:
123203 c_free (request[].data)
0 commit comments