Skip to content

Commit 75251fb

Browse files
Merge pull request #1 from logos-messaging/extend-more
Complete first ffi macro definition
2 parents b974eab + 5964e28 commit 75251fb

File tree

6 files changed

+228
-56
lines changed

6 files changed

+228
-56
lines changed

ffi.nim

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import std/atomics, chronos
12
import
23
ffi/internal/[ffi_library, ffi_macro],
34
ffi/[alloc, ffi_types, ffi_context, ffi_thread_request]
45

5-
export alloc, ffi_library, ffi_macro, ffi_types, ffi_context, ffi_thread_request
6+
export atomics, chronos
7+
export
8+
atomics, alloc, ffi_library, ffi_macro, ffi_types, ffi_context, ffi_thread_request

ffi/ffi_context.nim

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import std/[options, atomics, os, net, locks, json]
66
import chronicles, chronos, chronos/threadsync, taskpools/channels_spsc_single, results
7-
import ./ffi_types, ./ffi_thread_request, ./internal/ffi_macro
7+
import ./ffi_types, ./ffi_thread_request, ./internal/ffi_macro, ./logging
88

9-
type FFIContext* = object
10-
ffiThread: Thread[(ptr FFIContext)]
9+
type FFIContext*[T] = object
10+
myLib*: T
11+
# main library object (e.g., Waku, LibP2P, SDS, the one to be exposed as a library)
12+
ffiThread: Thread[(ptr FFIContext[T])]
1113
# represents the main FFI thread in charge of attending API consumer actions
12-
watchdogThread: Thread[(ptr FFIContext)]
14+
watchdogThread: Thread[(ptr FFIContext[T])]
1315
# monitors the FFI thread and notifies the FFI API consumer if it hangs
1416
lock: Lock
1517
reqChannel: ChannelSPSCSingle[ptr FFIThreadRequest]
@@ -21,6 +23,7 @@ type FFIContext* = object
2123
eventUserdata*: pointer
2224
running: Atomic[bool] # To control when the threads are running
2325
registeredRequests: ptr Table[cstring, FFIRequestProc]
26+
# Pointer to with the registered requests at compile time
2427

2528
const git_version* {.strdefine.} = "n/a"
2629

@@ -80,18 +83,18 @@ registerReqFFI(WatchdogReq, foo: ptr Foo):
8083
proc(): Future[Result[string, string]] {.async.} =
8184
return ok("waku thread is not blocked")
8285

83-
type JsonWakuNotRespondingEvent = object
86+
type JsonNotRespondingEvent = object
8487
eventType: string
8588

86-
proc init(T: type JsonWakuNotRespondingEvent): T =
87-
return JsonWakuNotRespondingEvent(eventType: "not_responding")
89+
proc init(T: type JsonNotRespondingEvent): T =
90+
return JsonNotRespondingEvent(eventType: "not_responding")
8891

89-
proc `$`(event: JsonWakuNotRespondingEvent): string =
92+
proc `$`(event: JsonNotRespondingEvent): string =
9093
$(%*event)
9194

92-
proc onWakuNotResponding*(ctx: ptr FFIContext) =
95+
proc onNotResponding*(ctx: ptr FFIContext) =
9396
callEventCallback(ctx, "onWakuNotResponsive"):
94-
$JsonWakuNotRespondingEvent.init()
97+
$JsonNotRespondingEvent.init()
9598

9699
proc watchdogThreadBody(ctx: ptr FFIContext) {.thread.} =
97100
## Watchdog thread that monitors the Waku thread and notifies the library user if it hangs.
@@ -120,15 +123,16 @@ proc watchdogThreadBody(ctx: ptr FFIContext) {.thread.} =
120123

121124
sendRequestToFFIThread(ctx, WatchdogReq.ffiNewReq(wakuCallback, nilUserData)).isOkOr:
122125
error "Failed to send watchdog request to FFI thread", error = $error
123-
onWakuNotResponding(ctx)
126+
onNotResponding(ctx)
124127

125128
waitFor watchdogRun(ctx)
126129

127-
proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
130+
proc ffiThreadBody[T](ctx: ptr FFIContext[T]) {.thread.} =
128131
## FFI thread that attends library user API requests
129132

130-
let ffiRun = proc(ctx: ptr FFIContext) {.async.} =
131-
var ffiHandler: TT
133+
logging.setupLog(logging.LogLevel.DEBUG, logging.LogFormat.TEXT)
134+
135+
let ffiRun = proc(ctx: ptr FFIContext[T]) {.async.} =
132136
while true:
133137
await ctx.reqSignal.wait()
134138

@@ -144,7 +148,7 @@ proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
144148

145149
## Handle the request
146150
asyncSpawn FFIThreadRequest.process(
147-
request, addr ffiHandler, ctx.registeredRequests
151+
request, addr ctx.myLib, ctx.registeredRequests
148152
)
149153

150154
let fireRes = ctx.reqReceivedSignal.fireSync()
@@ -153,21 +157,21 @@ proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
153157

154158
waitFor ffiRun(ctx)
155159

156-
proc createFFIContext*[T](tt: typedesc[T]): Result[ptr FFIContext, string] =
160+
proc createFFIContext*[T](): Result[ptr FFIContext[T], string] =
157161
## This proc is called from the main thread and it creates
158162
## the FFI working thread.
159-
var ctx = createShared(FFIContext, 1)
163+
var ctx = createShared(FFIContext[T], 1)
160164
ctx.reqSignal = ThreadSignalPtr.new().valueOr:
161165
return err("couldn't create reqSignal ThreadSignalPtr")
162166
ctx.reqReceivedSignal = ThreadSignalPtr.new().valueOr:
163167
return err("couldn't create reqReceivedSignal ThreadSignalPtr")
164168
ctx.lock.initLock()
165-
ctx.registeredRequests = addr ffi_macro.registeredRequests
169+
ctx.registeredRequests = addr ffi_types.registeredRequests
166170

167171
ctx.running.store(true)
168172

169173
try:
170-
createThread(ctx.ffiThread, ffiThreadBody[tt], ctx)
174+
createThread(ctx.ffiThread, ffiThreadBody[T], ctx)
171175
except ValueError, ResourceExhaustedError:
172176
freeShared(ctx)
173177
return err("failed to create the Waku thread: " & getCurrentExceptionMsg())
@@ -180,7 +184,7 @@ proc createFFIContext*[T](tt: typedesc[T]): Result[ptr FFIContext, string] =
180184

181185
return ok(ctx)
182186

183-
proc destroyFFIContext*(ctx: ptr FFIContext): Result[void, string] =
187+
proc destroyFFIContext*[T](ctx: ptr FFIContext[T]): Result[void, string] =
184188
ctx.running.store(false)
185189

186190
let signaledOnTime = ctx.reqSignal.fireSync().valueOr:

ffi/ffi_types.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import std/tables
12
import chronos
23

34
################################################################################
@@ -31,5 +32,8 @@ template foreignThreadGc*(body: untyped) =
3132

3233
type onDone* = proc()
3334

35+
## Registered requests table populated at compile time
36+
var registeredRequests* {.threadvar.}: Table[cstring, FFIRequestProc]
37+
3438
### End of FFI utils
3539
################################################################################

ffi/internal/ffi_library.nim

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ macro declareLibrary*(libraryName: static[string]): untyped =
6262
let nimMainName = ident("lib" & libraryName & "NimMain")
6363

6464
let initializeLibraryProc = quote:
65-
proc `procName`() {.exported.} =
65+
proc `procName`*() {.exported.} =
6666
if not initialized.exchange(true):
6767
## Every Nim library needs to call `<yourprefix>NimMain` once exactly,
6868
## to initialize the Nim runtime.
@@ -78,18 +78,4 @@ macro declareLibrary*(libraryName: static[string]): untyped =
7878

7979
res.add(initializeLibraryProc)
8080

81-
## Generate the exported C-callable callback setter
82-
let setCallbackProc = quote:
83-
proc set_event_callback(
84-
ctx: ptr FFIContext, callback: FFICallBack, userData: pointer
85-
) {.dynlib, exportc.} =
86-
initializeLibrary()
87-
ctx[].eventCallback = cast[pointer](callback)
88-
ctx[].eventUserData = userData
89-
90-
res.add(setCallbackProc)
91-
92-
# echo result.repr
9381
return res
94-
95-

ffi/internal/ffi_macro.nim

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ import std/[macros, tables]
22
import chronos
33
import ../ffi_types
44

5-
var registeredRequests* {.threadvar.}: Table[cstring, FFIRequestProc]
6-
75
proc extractFieldsFromLambda(body: NimNode): seq[NimNode] =
86
## Extracts the fields (params) from the given lambda body.
97
var procNode = body
108
if procNode.kind == nnkStmtList and procNode.len == 1:
119
procNode = procNode[0]
12-
if procNode.kind != nnkLambda:
10+
if procNode.kind != nnkLambda and procNode.kind != nnkProcDef:
1311
error "registerReqFFI expects a lambda proc, found: " & $procNode.kind
1412

1513
let params = procNode[3] # parameters list
@@ -26,7 +24,7 @@ proc buildRequestType(reqTypeName: NimNode, body: NimNode): NimNode =
2624
var procNode = body
2725
if procNode.kind == nnkStmtList and procNode.len == 1:
2826
procNode = procNode[0]
29-
if procNode.kind != nnkLambda:
27+
if procNode.kind != nnkLambda and procNode.kind != nnkProcDef:
3028
error "registerReqFFI expects a lambda proc, found: " & $procNode.kind
3129

3230
let params = procNode[3] # formal params of the lambda
@@ -62,7 +60,7 @@ proc buildFfiNewReqProc(reqTypeName, body: NimNode): NimNode =
6260
else:
6361
procNode = body
6462

65-
if procNode.kind != nnkLambda:
63+
if procNode.kind != nnkLambda and procNode.kind != nnkProcDef:
6664
error "registerReqFFI expects a lambda definition. Found: " & $procNode.kind
6765

6866
# T: typedesc[CreateNodeRequest]
@@ -188,7 +186,7 @@ proc buildProcessFFIRequestProc(reqTypeName, reqHandler, body: NimNode): NimNode
188186
var procNode = body
189187
if procNode.kind == nnkStmtList and procNode.len == 1:
190188
procNode = procNode[0]
191-
if procNode.kind != nnkLambda:
189+
if procNode.kind != nnkLambda and procNode.kind != nnkProcDef:
192190
error "registerReqFFI expects a lambda definition. Found: " & $procNode.kind
193191

194192
let typedescParam =
@@ -317,31 +315,102 @@ macro registerReqFFI*(reqTypeName, reqHandler, body: untyped): untyped =
317315
let deleteProc = buildFfiDeleteReqProc(reqTypeName, fields)
318316
result = newStmtList(typeDef, ffiNewReqProc, deleteProc, processProc, addNewReqToReg)
319317

320-
# echo "Registered FFI request: " & result.repr
321-
322-
macro processReq*(reqType: typed, args: varargs[untyped]): untyped =
323-
## Expands T.processReq(a,b,...) into the sendRequest boilerplate.
324-
325-
# Collect the passed arguments as NimNodes
326-
var callArgs = @[reqType, ident("callback"), ident("userData")]
318+
macro processReq*(
319+
reqType, ctx, callback, userData: untyped, args: varargs[untyped]
320+
): untyped =
321+
## Expands T.processReq(ctx, callback, userData, a, b, ...)
322+
var callArgs = @[reqType, callback, userData]
327323
for a in args:
328324
callArgs.add a
329325

330-
# Build: ffiNewReq(reqType, callback, userData, arg1, arg2, ...)
331326
let newReqCall = newCall(ident("ffiNewReq"), callArgs)
332327

333-
# Build: ffi_context.sendRequestToFFIThread(ctx, <newReqCall>)
334328
let sendCall = newCall(
335-
newDotExpr(ident("ffi_context"), ident("sendRequestToFFIThread")),
336-
ident("ctx"),
337-
newReqCall,
329+
newDotExpr(ident("ffi_context"), ident("sendRequestToFFIThread")), ctx, newReqCall
338330
)
339331

340332
result = quote:
341333
block:
342334
let res = `sendCall`
343-
if res.isErr:
335+
if res.isErr():
344336
let msg = "error in sendRequestToFFIThread: " & res.error
345-
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
337+
`callback`(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), `userData`)
346338
return RET_ERR
347339
return RET_OK
340+
341+
macro ffi*(prc: untyped): untyped =
342+
let procName = prc[0]
343+
let formalParams = prc[3]
344+
let bodyNode = prc[^1]
345+
346+
if formalParams.len < 2:
347+
error("`.ffi.` procs require at least 1 parameter")
348+
349+
let firstParam = formalParams[1]
350+
let paramIdent = firstParam[0]
351+
let paramType = firstParam[1]
352+
353+
let reqName = ident($procName & "Req")
354+
let returnType = ident("cint")
355+
356+
# Build parameter list (skip return type)
357+
var newParams = newSeq[NimNode]()
358+
newParams.add(returnType)
359+
for i in 1 ..< formalParams.len:
360+
newParams.add(newIdentDefs(formalParams[i][0], formalParams[i][1]))
361+
362+
# Build Future[Result[string, string]] return type
363+
let futReturnType = quote:
364+
Future[Result[string, string]]
365+
366+
var userParams = newSeq[NimNode]()
367+
userParams.add(futReturnType)
368+
if formalParams.len > 3:
369+
for i in 4 ..< formalParams.len:
370+
userParams.add(newIdentDefs(formalParams[i][0], formalParams[i][1]))
371+
372+
# Build argument list for processReq
373+
var argsList = newSeq[NimNode]()
374+
for i in 1 ..< formalParams.len:
375+
argsList.add(formalParams[i][0])
376+
377+
# 1. Build the dot expression. e.g.: waku_is_onlineReq.processReq
378+
let dotExpr = newTree(nnkDotExpr, reqName, ident"processReq")
379+
380+
# 2. Build the call node with dotExpr as callee
381+
let callNode = newTree(nnkCall, dotExpr)
382+
for arg in argsList:
383+
callNode.add(arg)
384+
385+
# Proc body
386+
let ffiBody = newStmtList(
387+
quote do:
388+
initializeLibrary()
389+
if not isNil(ctx):
390+
ctx[].userData = userData
391+
if isNil(callback):
392+
return RET_MISSING_CALLBACK
393+
)
394+
395+
ffiBody.add(callNode)
396+
397+
let ffiProc = newProc(
398+
name = procName,
399+
params = newParams,
400+
body = ffiBody,
401+
pragmas = newTree(nnkPragma, ident "dynlib", ident "exportc", ident "cdecl"),
402+
)
403+
404+
var anonymousProcNode = newProc(
405+
name = newEmptyNode(), # anonymous proc
406+
params = userParams,
407+
body = newStmtList(bodyNode),
408+
pragmas = newTree(nnkPragma, ident"async"),
409+
)
410+
411+
# registerReqFFI wrapper
412+
let registerReq = quote:
413+
registerReqFFI(`reqName`, `paramIdent`: `paramType`):
414+
`anonymousProcNode`
415+
416+
result = newStmtList(registerReq, ffiProc)

0 commit comments

Comments
 (0)