mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-01-07 16:43:10 +00:00
add ffi macro
This commit is contained in:
parent
b974eab39c
commit
86dc58e7c2
@ -4,12 +4,14 @@
|
|||||||
|
|
||||||
import std/[options, atomics, os, net, locks, json]
|
import std/[options, atomics, os, net, locks, json]
|
||||||
import chronicles, chronos, chronos/threadsync, taskpools/channels_spsc_single, results
|
import chronicles, chronos, chronos/threadsync, taskpools/channels_spsc_single, results
|
||||||
import ./ffi_types, ./ffi_thread_request, ./internal/ffi_macro
|
import ./ffi_types, ./ffi_thread_request, ./ffi_watchdog_req, ./internal/ffi_macro
|
||||||
|
|
||||||
type FFIContext* = object
|
type FFIContext*[T] = object
|
||||||
ffiThread: Thread[(ptr FFIContext)]
|
myLib*: T
|
||||||
|
# main library object (e.g., Waku, LibP2P, SDS, the one to be exposed as a library)
|
||||||
|
ffiThread: Thread[(ptr FFIContext[T])]
|
||||||
# represents the main FFI thread in charge of attending API consumer actions
|
# represents the main FFI thread in charge of attending API consumer actions
|
||||||
watchdogThread: Thread[(ptr FFIContext)]
|
watchdogThread: Thread[(ptr FFIContext[T])]
|
||||||
# monitors the FFI thread and notifies the FFI API consumer if it hangs
|
# monitors the FFI thread and notifies the FFI API consumer if it hangs
|
||||||
lock: Lock
|
lock: Lock
|
||||||
reqChannel: ChannelSPSCSingle[ptr FFIThreadRequest]
|
reqChannel: ChannelSPSCSingle[ptr FFIThreadRequest]
|
||||||
@ -21,6 +23,7 @@ type FFIContext* = object
|
|||||||
eventUserdata*: pointer
|
eventUserdata*: pointer
|
||||||
running: Atomic[bool] # To control when the threads are running
|
running: Atomic[bool] # To control when the threads are running
|
||||||
registeredRequests: ptr Table[cstring, FFIRequestProc]
|
registeredRequests: ptr Table[cstring, FFIRequestProc]
|
||||||
|
# Pointer to with the registered requests at compile time
|
||||||
|
|
||||||
const git_version* {.strdefine.} = "n/a"
|
const git_version* {.strdefine.} = "n/a"
|
||||||
|
|
||||||
@ -80,18 +83,18 @@ registerReqFFI(WatchdogReq, foo: ptr Foo):
|
|||||||
proc(): Future[Result[string, string]] {.async.} =
|
proc(): Future[Result[string, string]] {.async.} =
|
||||||
return ok("waku thread is not blocked")
|
return ok("waku thread is not blocked")
|
||||||
|
|
||||||
type JsonWakuNotRespondingEvent = object
|
type JsonNotRespondingEvent = object
|
||||||
eventType: string
|
eventType: string
|
||||||
|
|
||||||
proc init(T: type JsonWakuNotRespondingEvent): T =
|
proc init(T: type JsonNotRespondingEvent): T =
|
||||||
return JsonWakuNotRespondingEvent(eventType: "not_responding")
|
return JsonNotRespondingEvent(eventType: "not_responding")
|
||||||
|
|
||||||
proc `$`(event: JsonWakuNotRespondingEvent): string =
|
proc `$`(event: JsonNotRespondingEvent): string =
|
||||||
$(%*event)
|
$(%*event)
|
||||||
|
|
||||||
proc onWakuNotResponding*(ctx: ptr FFIContext) =
|
proc onNotResponding*(ctx: ptr FFIContext) =
|
||||||
callEventCallback(ctx, "onWakuNotResponsive"):
|
callEventCallback(ctx, "onWakuNotResponsive"):
|
||||||
$JsonWakuNotRespondingEvent.init()
|
$JsonNotRespondingEvent.init()
|
||||||
|
|
||||||
proc watchdogThreadBody(ctx: ptr FFIContext) {.thread.} =
|
proc watchdogThreadBody(ctx: ptr FFIContext) {.thread.} =
|
||||||
## Watchdog thread that monitors the Waku thread and notifies the library user if it hangs.
|
## Watchdog thread that monitors the Waku thread and notifies the library user if it hangs.
|
||||||
@ -120,15 +123,14 @@ proc watchdogThreadBody(ctx: ptr FFIContext) {.thread.} =
|
|||||||
|
|
||||||
sendRequestToFFIThread(ctx, WatchdogReq.ffiNewReq(wakuCallback, nilUserData)).isOkOr:
|
sendRequestToFFIThread(ctx, WatchdogReq.ffiNewReq(wakuCallback, nilUserData)).isOkOr:
|
||||||
error "Failed to send watchdog request to FFI thread", error = $error
|
error "Failed to send watchdog request to FFI thread", error = $error
|
||||||
onWakuNotResponding(ctx)
|
onNotResponding(ctx)
|
||||||
|
|
||||||
waitFor watchdogRun(ctx)
|
waitFor watchdogRun(ctx)
|
||||||
|
|
||||||
proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
|
proc ffiThreadBody[T](ctx: ptr FFIContext[T]) {.thread.} =
|
||||||
## FFI thread that attends library user API requests
|
## FFI thread that attends library user API requests
|
||||||
|
|
||||||
let ffiRun = proc(ctx: ptr FFIContext) {.async.} =
|
let ffiRun = proc(ctx: ptr FFIContext[T]) {.async.} =
|
||||||
var ffiHandler: TT
|
|
||||||
while true:
|
while true:
|
||||||
await ctx.reqSignal.wait()
|
await ctx.reqSignal.wait()
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
|
|||||||
|
|
||||||
## Handle the request
|
## Handle the request
|
||||||
asyncSpawn FFIThreadRequest.process(
|
asyncSpawn FFIThreadRequest.process(
|
||||||
request, addr ffiHandler, ctx.registeredRequests
|
request, addr ctx.myLib, ctx.registeredRequests
|
||||||
)
|
)
|
||||||
|
|
||||||
let fireRes = ctx.reqReceivedSignal.fireSync()
|
let fireRes = ctx.reqReceivedSignal.fireSync()
|
||||||
@ -153,21 +155,21 @@ proc ffiThreadBody[TT](ctx: ptr FFIContext) {.thread.} =
|
|||||||
|
|
||||||
waitFor ffiRun(ctx)
|
waitFor ffiRun(ctx)
|
||||||
|
|
||||||
proc createFFIContext*[T](tt: typedesc[T]): Result[ptr FFIContext, string] =
|
proc createFFIContext*[T](): Result[ptr FFIContext[T], string] =
|
||||||
## This proc is called from the main thread and it creates
|
## This proc is called from the main thread and it creates
|
||||||
## the FFI working thread.
|
## the FFI working thread.
|
||||||
var ctx = createShared(FFIContext, 1)
|
var ctx = createShared(FFIContext[T], 1)
|
||||||
ctx.reqSignal = ThreadSignalPtr.new().valueOr:
|
ctx.reqSignal = ThreadSignalPtr.new().valueOr:
|
||||||
return err("couldn't create reqSignal ThreadSignalPtr")
|
return err("couldn't create reqSignal ThreadSignalPtr")
|
||||||
ctx.reqReceivedSignal = ThreadSignalPtr.new().valueOr:
|
ctx.reqReceivedSignal = ThreadSignalPtr.new().valueOr:
|
||||||
return err("couldn't create reqReceivedSignal ThreadSignalPtr")
|
return err("couldn't create reqReceivedSignal ThreadSignalPtr")
|
||||||
ctx.lock.initLock()
|
ctx.lock.initLock()
|
||||||
ctx.registeredRequests = addr ffi_macro.registeredRequests
|
ctx.registeredRequests = addr ffi_types.registeredRequests
|
||||||
|
|
||||||
ctx.running.store(true)
|
ctx.running.store(true)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
createThread(ctx.ffiThread, ffiThreadBody[tt], ctx)
|
createThread(ctx.ffiThread, ffiThreadBody[T], ctx)
|
||||||
except ValueError, ResourceExhaustedError:
|
except ValueError, ResourceExhaustedError:
|
||||||
freeShared(ctx)
|
freeShared(ctx)
|
||||||
return err("failed to create the Waku thread: " & getCurrentExceptionMsg())
|
return err("failed to create the Waku thread: " & getCurrentExceptionMsg())
|
||||||
@ -180,7 +182,7 @@ proc createFFIContext*[T](tt: typedesc[T]): Result[ptr FFIContext, string] =
|
|||||||
|
|
||||||
return ok(ctx)
|
return ok(ctx)
|
||||||
|
|
||||||
proc destroyFFIContext*(ctx: ptr FFIContext): Result[void, string] =
|
proc destroyFFIContext*[T](ctx: ptr FFIContext): Result[void, string] =
|
||||||
ctx.running.store(false)
|
ctx.running.store(false)
|
||||||
|
|
||||||
let signaledOnTime = ctx.reqSignal.fireSync().valueOr:
|
let signaledOnTime = ctx.reqSignal.fireSync().valueOr:
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import std/tables
|
||||||
import chronos
|
import chronos
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@ -31,5 +32,8 @@ template foreignThreadGc*(body: untyped) =
|
|||||||
|
|
||||||
type onDone* = proc()
|
type onDone* = proc()
|
||||||
|
|
||||||
|
## Registered requests table populated at compile time
|
||||||
|
var registeredRequests* {.threadvar.}: Table[cstring, FFIRequestProc]
|
||||||
|
|
||||||
### End of FFI utils
|
### End of FFI utils
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@ -2,8 +2,6 @@ import std/[macros, tables]
|
|||||||
import chronos
|
import chronos
|
||||||
import ../ffi_types
|
import ../ffi_types
|
||||||
|
|
||||||
var registeredRequests* {.threadvar.}: Table[cstring, FFIRequestProc]
|
|
||||||
|
|
||||||
proc extractFieldsFromLambda(body: NimNode): seq[NimNode] =
|
proc extractFieldsFromLambda(body: NimNode): seq[NimNode] =
|
||||||
## Extracts the fields (params) from the given lambda body.
|
## Extracts the fields (params) from the given lambda body.
|
||||||
var procNode = body
|
var procNode = body
|
||||||
@ -319,22 +317,19 @@ macro registerReqFFI*(reqTypeName, reqHandler, body: untyped): untyped =
|
|||||||
|
|
||||||
# echo "Registered FFI request: " & result.repr
|
# echo "Registered FFI request: " & result.repr
|
||||||
|
|
||||||
macro processReq*(reqType: typed, args: varargs[untyped]): untyped =
|
macro processReq*(
|
||||||
## Expands T.processReq(a,b,...) into the sendRequest boilerplate.
|
reqType, ctx, callback, userData: untyped, args: varargs[untyped]
|
||||||
|
): untyped =
|
||||||
# Collect the passed arguments as NimNodes
|
## Expands T.processReq(ctx, callback, userData, a, b, ...)
|
||||||
var callArgs = @[reqType, ident("callback"), ident("userData")]
|
var callArgs = @[reqType, callback, userData]
|
||||||
for a in args:
|
for a in args:
|
||||||
callArgs.add a
|
callArgs.add a
|
||||||
|
|
||||||
# Build: ffiNewReq(reqType, callback, userData, arg1, arg2, ...)
|
|
||||||
let newReqCall = newCall(ident("ffiNewReq"), callArgs)
|
let newReqCall = newCall(ident("ffiNewReq"), callArgs)
|
||||||
|
|
||||||
# Build: ffi_context.sendRequestToFFIThread(ctx, <newReqCall>)
|
# CORRECT: use actual ctx symbol
|
||||||
let sendCall = newCall(
|
let sendCall = newCall(
|
||||||
newDotExpr(ident("ffi_context"), ident("sendRequestToFFIThread")),
|
newDotExpr(ident("ffi_context"), ident("sendRequestToFFIThread")), ctx, newReqCall
|
||||||
ident("ctx"),
|
|
||||||
newReqCall,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = quote:
|
result = quote:
|
||||||
@ -342,6 +337,69 @@ macro processReq*(reqType: typed, args: varargs[untyped]): untyped =
|
|||||||
let res = `sendCall`
|
let res = `sendCall`
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
let msg = "error in sendRequestToFFIThread: " & res.error
|
let msg = "error in sendRequestToFFIThread: " & res.error
|
||||||
callback(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), userData)
|
`callback`(RET_ERR, unsafeAddr msg[0], cast[csize_t](msg.len), `userData`)
|
||||||
return RET_ERR
|
return RET_ERR
|
||||||
return RET_OK
|
return RET_OK
|
||||||
|
|
||||||
|
macro ffi*(prc: untyped): untyped =
|
||||||
|
let procName = prc[0]
|
||||||
|
let formalParams = prc[3]
|
||||||
|
let bodyNode = prc[^1]
|
||||||
|
|
||||||
|
if formalParams.len < 2:
|
||||||
|
error("`.ffi.` procs require at least 1 parameter")
|
||||||
|
|
||||||
|
let firstParam = formalParams[1]
|
||||||
|
let paramIdent = firstParam[0]
|
||||||
|
let paramType = firstParam[1]
|
||||||
|
|
||||||
|
let reqName = ident($procName & "Req")
|
||||||
|
let returnType = ident("cint")
|
||||||
|
|
||||||
|
# Build parameter list (skip return type)
|
||||||
|
var newParams = newSeq[NimNode]()
|
||||||
|
newParams.add(returnType)
|
||||||
|
for i in 1 ..< formalParams.len:
|
||||||
|
newParams.add(newIdentDefs(formalParams[i][0], formalParams[i][1]))
|
||||||
|
|
||||||
|
# Build argument list for processReq
|
||||||
|
var argsList = newSeq[NimNode]()
|
||||||
|
for i in 1 ..< formalParams.len:
|
||||||
|
argsList.add(formalParams[i][0])
|
||||||
|
|
||||||
|
# 1. Build the dot expression. e.g.: waku_is_onlineReq.processReq
|
||||||
|
let dotExpr = newTree(nnkDotExpr, reqName, ident"processReq")
|
||||||
|
|
||||||
|
# 2. Build the call node with dotExpr as callee
|
||||||
|
let callNode = newTree(nnkCall, dotExpr)
|
||||||
|
for arg in argsList:
|
||||||
|
callNode.add(arg)
|
||||||
|
|
||||||
|
# Proc body
|
||||||
|
let ffiBody = newStmtList(
|
||||||
|
quote do:
|
||||||
|
initializeLibrary()
|
||||||
|
if not isNil(ctx):
|
||||||
|
ctx[].userData = userData
|
||||||
|
if isNil(callback):
|
||||||
|
return RET_MISSING_CALLBACK
|
||||||
|
)
|
||||||
|
|
||||||
|
ffiBody.add(callNode)
|
||||||
|
|
||||||
|
let ffiProc = newProc(
|
||||||
|
name = procName,
|
||||||
|
params = newParams,
|
||||||
|
body = ffiBody,
|
||||||
|
pragmas = newTree(nnkPragma, ident "dynlib", ident "exportc"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# registerReqFFI wrapper
|
||||||
|
let registerReq = quote:
|
||||||
|
registerReqFFI(`reqName`, `paramIdent`: `paramType`):
|
||||||
|
proc(): Future[Result[string, string]] {.async.} =
|
||||||
|
return `bodyNode`
|
||||||
|
|
||||||
|
# Final macro result
|
||||||
|
result = newStmtList(registerReq, ffiProc)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user