@@ -131,7 +131,9 @@ proc newAbiReg(types: seq[FFITypeMeta], procs: seq[FFIProcMeta]): AbiReg =
131131 for t in types:
132132 reg.typeTable[t.name] = t
133133 for p in procs:
134- if p.kind != FFIKind .DTOR :
134+ # A scalar-fast-path proc has no Req envelope: its export takes the scalar
135+ # args inline (see emitScalarMethod).
136+ if p.kind != FFIKind .DTOR and not p.scalarFastPath:
135137 let rt = reqTypeMeta (p)
136138 reg.typeTable[rt.name] = rt
137139 reg
@@ -200,38 +202,69 @@ proc emitReplyTypedefs(
200202 " reply, const char* err_msg, void* user_data);"
201203 )
202204
205+ func scalarRawFnName (libType: string ): string =
206+ # # The `FFICallBack`-shaped raw callback typedef a scalar-fast-path export
207+ # # takes: the dylib replies with raw bytes (no CBOR, no flat struct) that the
208+ # # per-method trampoline converts into the typed reply.
209+ libType & " ScalarRawFn"
210+
211+ proc scalarArgParams (m: FFIProcMeta ): seq [string ] =
212+ # # C parameters for a scalar method's args — passed inline by value, in both
213+ # # the raw export and the high-level wrapper (no Req struct).
214+ var params: seq [string ] = @ []
215+ for ep in m.extraParams:
216+ params.add (leafCTypeAbi (ep.typeName.strip ()).cType & " " & ep.name)
217+ params
218+
203219proc emitExternDecls (
204220 lines: var seq [string ],
205221 reg: var AbiReg ,
206222 libName, libType: string ,
207223 procs: seq [FFIProcMeta ],
208224) =
209225 let createRawFn = libType & " CreateRawFn"
210- var haveCtor = false
226+ var haveCtor, haveScalar = false
211227 for p in procs:
212228 if p.kind == FFIKind .CTOR :
213229 haveCtor = true
230+ if p.scalarFastPath:
231+ haveScalar = true
214232 if haveCtor:
215233 lines.add (
216234 " typedef void (*" & createRawFn &
217235 " )(int err_code, const char* ctx_addr, const char* err_msg, void* user_data);"
218236 )
237+ if haveScalar:
238+ lines.add (" /* Raw reply of a scalar-fast-path export: `msg`/`len` are raw bytes (a" )
239+ lines.add (
240+ " string return's UTF-8, or the 8-byte native-endian scalar image), not"
241+ )
242+ lines.add (" NUL-terminated and valid only for the duration of the call. */" )
243+ lines.add (
244+ " typedef void (*" & scalarRawFnName (libType) &
245+ " )(int caller_ret, char* msg, size_t len, void* user_data);"
246+ )
219247 lines.add (" #ifdef __cplusplus" )
220248 lines.add (" extern \" C\" {" )
221249 lines.add (" #endif" )
222250 lines.add (" " )
223251 for p in procs:
224- let reqStruct = reqStructName (p)
225252 case p.kind
226253 of FFIKind .FFI :
227- let info = methodReplyInfo (reg, libType, p)
228- lines.add (
229- " int " & p.procName & " (void* ctx, " & info.fnType &
230- " on_reply, void* user_data, const " & reqStruct & " * req);"
231- )
254+ if p.scalarFastPath:
255+ var params =
256+ @ [" void* ctx" , scalarRawFnName (libType) & " callback" , " void* user_data" ]
257+ params.add (scalarArgParams (p))
258+ lines.add (" int " & p.procName & " (" & params.join (" , " ) & " );" )
259+ else :
260+ let info = methodReplyInfo (reg, libType, p)
261+ lines.add (
262+ " int " & p.procName & " (void* ctx, " & info.fnType &
263+ " on_reply, void* user_data, const " & reqStructName (p) & " * req);"
264+ )
232265 of FFIKind .CTOR :
233266 lines.add (
234- " void* " & p.procName & " (const " & reqStruct & " * req, " & createRawFn &
267+ " void* " & p.procName & " (const " & reqStructName (p) & " * req, " & createRawFn &
235268 " on_created, void* user_data);"
236269 )
237270 of FFIKind .DTOR :
@@ -368,6 +401,130 @@ proc emitMethod(
368401 lines.add (" }" )
369402 lines.add (" " )
370403
404+ func scalarOkLines (m: FFIProcMeta , fnType: string ): seq [string ] =
405+ # # Trampoline RET_OK branch: convert the raw reply bytes into the typed
406+ # # reply. A string return rides as its own UTF-8 bytes (copied and
407+ # # NUL-terminated here); every other scalar is the 8-byte native-endian image
408+ # # of the Nim-side pack (signed ints sign-extended to 64 bits, floats widened
409+ # # to double, bool as 0/1 — see `ffiScalarRetBytes`).
410+ let rt = m.returnTypeName.strip ()
411+ if rt == " string" or rt == " cstring" :
412+ return @ [
413+ " char* reply = (char*)malloc(len + 1);" , " if (!reply) {" ,
414+ " fn(NIMFFI_RET_ERR, \"\" , \" out of memory\" , user_data);" ,
415+ " return;" , " }" , " if (len > 0) memcpy(reply, msg, len);" ,
416+ " reply[len] = '\\ 0';" , " fn(NIMFFI_RET_OK, reply, \"\" , user_data);" ,
417+ " free(reply);" ,
418+ ]
419+ var lines = @ [
420+ " uint64_t slot = 0;" , " if (!msg || len != sizeof(slot)) {" ,
421+ " fn(NIMFFI_RET_ERR, NULL, \" scalar reply: unexpected payload size\" , user_data);" ,
422+ " return;" , " }" , " memcpy(&slot, msg, sizeof(slot));" ,
423+ ]
424+ let cType = leafCTypeAbi (rt).cType
425+ case rt
426+ of " int" , " int64" :
427+ lines.add (" int64_t reply;" )
428+ lines.add (" memcpy(&reply, &slot, sizeof(reply));" )
429+ of " int8" , " int16" , " int32" :
430+ lines.add (" int64_t wide;" )
431+ lines.add (" memcpy(&wide, &slot, sizeof(wide));" )
432+ lines.add (" " & cType & " reply = (" & cType & " )wide;" )
433+ of " uint" , " uint64" :
434+ lines.add (" uint64_t reply = slot;" )
435+ of " uint8" , " uint16" , " uint32" , " byte" :
436+ lines.add (" " & cType & " reply = (" & cType & " )slot;" )
437+ of " bool" :
438+ lines.add (" bool reply = slot != 0;" )
439+ of " float" , " float64" :
440+ lines.add (" double reply;" )
441+ lines.add (" memcpy(&reply, &slot, sizeof(reply));" )
442+ of " float32" :
443+ lines.add (" double wide;" )
444+ lines.add (" memcpy(&wide, &slot, sizeof(wide));" )
445+ lines.add (" float reply = (float)wide;" )
446+ else :
447+ raise newException (
448+ ValueError , " abi = c: unexpected scalar-fast-path return type: " & rt
449+ )
450+ lines.add (" fn(NIMFFI_RET_OK, &reply, \"\" , user_data);" )
451+ lines
452+
453+ proc emitScalarMethod (
454+ lines: var seq [string ],
455+ reg: var AbiReg ,
456+ ctxType, libName, libType: string ,
457+ m: FFIProcMeta ,
458+ ) =
459+ # # A scalar-fast-path method: no Req struct crosses — the wrapper hands the
460+ # # scalar args straight to the raw export and a per-method trampoline adapts
461+ # # the raw-bytes reply into the same typed `ReplyFn` surface the flat-struct
462+ # # methods use. The heap box carrying the caller's callback is freed by the
463+ # # trampoline, which the dylib invokes exactly once on every path (the ctx
464+ # # guard and enqueue failures reply synchronously; success replies from the
465+ # # FFI thread).
466+ let stripped = stripLibPrefix (m.procName, m.libName)
467+ let pascal = snakeToPascalCase (stripped)
468+ let info = methodReplyInfo (reg, libType, m)
469+ let boxType = libType & pascal & " ScalarBox"
470+ let tramp = m.procName & " _scalar_reply"
471+ let isStr = m.returnTypeName.strip () in [" string" , " cstring" ]
472+ let errReply = if isStr: " \"\" " else : " NULL"
473+ lines.add (
474+ " typedef struct { " & info.fnType & " fn; void* user_data; } " & boxType & " ;"
475+ )
476+ lines.add (
477+ " static void " & tramp & " (int caller_ret, char* msg, size_t len, void* ud) {"
478+ )
479+ lines.add (" " & boxType & " * box = (" & boxType & " *)ud;" )
480+ lines.add (" if (!box) return;" )
481+ lines.add (" " & info.fnType & " fn = box->fn;" )
482+ lines.add (" void* user_data = box->user_data;" )
483+ lines.add (" free(box);" )
484+ lines.add (" if (!fn) return;" )
485+ lines.add (" if (caller_ret != NIMFFI_RET_OK) {" )
486+ lines.add (" char* em = (char*)malloc(len + 1);" )
487+ lines.add (" if (em) {" )
488+ lines.add (" if (len > 0) memcpy(em, msg, len);" )
489+ lines.add (" em[len] = '\\ 0';" )
490+ lines.add (" }" )
491+ lines.add (
492+ " fn(caller_ret, " & errReply & " , em ? em : \" FFI call failed\" , user_data);"
493+ )
494+ lines.add (" free(em);" )
495+ lines.add (" return;" )
496+ lines.add (" }" )
497+ for l in scalarOkLines (m, info.fnType):
498+ lines.add (l)
499+ lines.add (" }" )
500+ lines.add (" " )
501+ let params = scalarArgParams (m)
502+ let head =
503+ " static inline int " & libName & " _ctx_" & stripped & " (const " & ctxType & " * ctx, "
504+ let sig =
505+ if params.len > 0 :
506+ head & params.join (" , " ) & " , " & info.fnType & " on_reply, void* user_data) {"
507+ else :
508+ head & info.fnType & " on_reply, void* user_data) {"
509+ lines.add (sig)
510+ lines.add (
511+ " " & boxType & " * box = (" & boxType & " *)malloc(sizeof(" & boxType & " ));"
512+ )
513+ lines.add (" if (!box) {" )
514+ lines.add (
515+ " if (on_reply) on_reply(-1, " & errReply & " , \" out of memory\" , user_data);"
516+ )
517+ lines.add (" return -1;" )
518+ lines.add (" }" )
519+ lines.add (" box->fn = on_reply;" )
520+ lines.add (" box->user_data = user_data;" )
521+ var callArgs = @ [" ctx->ptr" , tramp, " box" ]
522+ for ep in m.extraParams:
523+ callArgs.add (ep.name)
524+ lines.add (" return " & m.procName & " (" & callArgs.join (" , " ) & " );" )
525+ lines.add (" }" )
526+ lines.add (" " )
527+
371528proc generateCAbiLibHeader * (
372529 procs: seq [FFIProcMeta ],
373530 types: seq [FFITypeMeta ],
@@ -386,7 +543,7 @@ proc generateCAbiLibHeader*(
386543 for t in types:
387544 ensureAbiStruct (reg, t.name)
388545 for p in procs:
389- if p.kind != FFIKind .DTOR :
546+ if p.kind != FFIKind .DTOR and not p.scalarFastPath :
390547 ensureAbiStruct (reg, reqStructName (p))
391548
392549 let guard = " NIM_FFI_LIB_" & libName.toUpperAscii () & " _C_ABI_H_INCLUDED"
@@ -417,7 +574,10 @@ proc generateCAbiLibHeader*(
417574 emitCtxAndCtor (lines, reg, libName, libType, ctxType, classified.ctors)
418575 emitDestructor (lines, ctxType, libName, classified.dtorProcName)
419576 for m in classified.methods:
420- emitMethod (lines, reg, ctxType, libName, libType, m)
577+ if m.scalarFastPath:
578+ emitScalarMethod (lines, reg, ctxType, libName, libType, m)
579+ else :
580+ emitMethod (lines, reg, ctxType, libName, libType, m)
421581
422582 lines.add (" #endif /* " & guard & " */" )
423583 lines.join (" \n " ) & " \n "
0 commit comments