Skip to content

Instantly share code, notes, and snippets.

@mratsim
Created January 7, 2023 15:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mratsim/054ac5ad518a27f21c076c6041e21001 to your computer and use it in GitHub Desktop.
Save mratsim/054ac5ad518a27f21c076c6041e21001 to your computer and use it in GitHub Desktop.
Dealing with push/pop and C openarray wrapper
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/macros
# ############################################################
#
# Binding utilities
#
# ############################################################
# Flag parameters
# ------------------------------------------------------------
type Flag*[E: enum] = distinct cint
func flag*[E: enum](e: varargs[E]): Flag[E] {.inline.} =
## Enum should only have power of 2 fields
# static:
# for val in E:
# assert (ord(val) and (ord(val) - 1)) == 0, "Enum values should all be power of 2, found " &
# $val & " with value " & $ord(val) & "."
var flags = 0
for val in e:
flags = flags or ord(val)
result = Flag[E](flags)
# Macros
# ------------------------------------------------------------
proc replaceSymsByIdents*(ast: NimNode): NimNode =
proc inspect(node: NimNode): NimNode =
case node.kind:
of {nnkIdent, nnkSym}:
return ident($node)
of nnkEmpty:
return node
of nnkLiterals:
return node
of nnkHiddenStdConv:
if node[1].kind == nnkIntLit:
return node[1]
else:
expectKind(node[1], nnkSym)
return ident($node[1])
of nnkConv: # type conversion needs to be replaced by a function call in untyped AST
var rTree = nnkCall.newTree()
for child in node:
rTree.add inspect(child)
return rTree
else:
var rTree = node.kind.newTree()
for child in node:
rTree.add inspect(child)
return rTree
result = inspect(ast)
macro replacePragmasByInline(procAst: typed): untyped =
## Replace pragmas by the inline pragma
## We need a separate "typed" macro
## so that it is executed after the {.push mypragma.} calls
var params: seq[NimNode]
for i in 0 ..< procAst.params.len:
params.add procAst.params[i]
result = newStmtList()
# The push cdecl is applied multiple times :/, so fight push with push
result.add nnkPragma.newTree(ident"push", ident"nimcall", ident"inline")
result.add newProc(
name = procAst.name,
params = params,
body = procAst.body.replaceSymsByIdents(),
procType = nnkProcDef,
pragmas = nnkPragma.newTree(ident"inline", ident"nimcall")
)
result.add nnkPragma.newTree(ident"pop")
macro wrapOpenArrayLenType*(ty: typedesc, procAst: untyped): untyped =
## Wraps pointer+len library calls in properly typed and converted openArray calls
##
## ```
## {.push cdecl.}
## proc foo*(r: int, a: openArray[CustomType], b: int) {.wrapOpenArrayLenType: uint32, importc: "foo", dynlib: "libfoo.so".}
## {.pop.}
## ```
##
## is transformed into
##
## ```
## proc foo(r: int, a: ptr CustomType, aLen: uint32, b: int) {.cdecl, importc: "foo", dynlib: "libfoo.so".}
##
## proc foo*(r: int, a: openArray[CustomType], b: int) {.inline.} =
## foo(r, a[0].unsafeAddr, a.len.uint32, b)
## ```
procAst.expectKind(nnkProcDef)
let suffix = "Lib"
var
wrappeeParams = @[procAst.params[0]]
wrapperParams = @[procAst.params[0]]
wrapperBody = newCall(ident($procAst.name & suffix))
for i in 1 ..< procAst.params.len:
if procAst.params[i][^2].kind == nnkBracketExpr and procAst.params[i][^2][0].eqident"openarray":
procAst.params[i].expectLen(3) # prevent `proc foo(a, b: openArray[int])`
wrappeeParams.add newIdentDefs(
ident($procAst.params[i][0] & "Ptr"),
nnkPtrTy.newTree(procAst.params[i][^2][1]),
newEmptyNode()
)
wrappeeParams.add newIdentDefs(
ident($procAst.params[i][0] & "Len"),
ty,
newEmptyNode()
)
wrapperParams.add procAst.params[i]
wrapperBody.add newCall(
ident"unsafeAddr",
nnkBracketExpr.newTree(
ident($procAst.params[i][0]),
newLit 0
))
wrapperBody.add newCall(ty, newCall(bindSym"len", ident($procAst.params[i][0])))
else:
wrappeeParams.add procAst.params[i]
wrapperParams.add procAst.params[i]
# Handle "a, b: int"
for j in 0 ..< procAst.params[i].len - 2:
wrapperBody.add ident($procAst.params[i][j])
let wrappee = newProc(
name = ident($procAst.name & suffix), # Remove export marker if any
params = wrappeeParams,
body = procAst.body.copyNimTree(),
procType = nnkProcDef,
pragmas = procAst.pragma
)
let wrapper = newProc(
name = procAst[0], # keep export marker if any
params = wrapperParams,
body = newStmtList(procAst.body.copyNimTree(), wrapperBody), # original procAst body can contain comments that we copy
procType = nnkProcDef,
pragmas = nnkPragma.newTree(bindSym"replacePragmasByInline") # pragmas are for the wrappee
)
result = newStmtList(wrappee, wrapper)
when isMainModule:
expandMacros:
{.push cdecl.}
proc foo(x: int, a: openArray[uint32], name: cstring) {.wrapOpenArrayLenType: cuint.} =
discard
{.pop.}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment