I have a bunch of C functions, that I want to write Julia wrappers for. Here’s an example:
const LIBSPARSE = "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/"*
"vecLib.framework/libSparse.dylib"
# ...definitions of cconvert and unsafe convert...
SparseMultiply(arg1::SparseMatrix{Cfloat},
arg2::StridedMatrix{Cfloat},
arg3::StridedMatrix{Cfloat}) =
@ccall LIBSPARSE._Z14SparseMultiply18SparseMatrix_Float17DenseMatrix_FloatS0_(arg1::SparseMatrix{Cfloat},
arg2::DenseMatrix{Cfloat},
arg3::DenseMatrix{Cfloat})::Cvoid
I realize this isn’t quite self contained: copy-paste the first 200ish lines from here for the definition of SparseMatrix
and cconvert
. Many of these functions have the same Julia name, and only differ by their arguments. The types passed to the c function are mostly the same as the Julia types, with a few exceptions: there’s a couple enums that become Cuint32
s, and Strided(Vector/Matrix){Cfloat/Cdouble}
become Dense(Vector/Matrix){Cfloat/Cdouble}
. (See previous link for definitions of these structs: their internals aren’t important for the question.) Here’s my best attempt at writing a macro that does this for me:
macro generateDemangled(jlName, cName, retType, jlArgTypes...)
local jlArgExprs = map(enumerate(jlArgTypes)) do (i, T)
Expr(:(::), esc(Symbol("arg$i")), esc(T))
end
local jlCall = Expr(:(::), Expr(:call, esc(jlName), jlArgExprs...), esc(retType))
local cArgTypesList = Vector{Type}()
local ALL_ENUMS = [:(SparseFactorization_t), :(SparseOrder_t), :(SparseScaling_t),
:(SparseStatus_t), :(SparseControl_t)]
for jlType in jlArgTypes
if jlType in ALL_ENUMS
push!(cArgTypesList, Cuint)
elseif jlType == :(StridedVector{Cfloat})
push!(cArgTypesList, DenseVector{Cfloat})
elseif jlType == :(StridedVector{Cdouble})
push!(cArgTypesList, DenseVector{Cdouble})
elseif jlType == :(StridedMatrix{Cfloat})
push!(cArgTypesList, DenseMatrix{Cfloat})
elseif jlType == :(StridedMatrix{Cdouble})
push!(cArgTypesList, DenseMatrix{Cdouble})
else
push!(cArgTypesList, eval(jlType)) # Issue #1
end
end
local cArgTypesExpr = Expr(:tuple, cArgTypesList...)
local functionAndLibrary = Expr(:tuple, esc(cName), LIBSPARSE)
local autoNamedArgs = [esc(Symbol("arg$i")) for i in 1:length(jlArgTypes)]
# Issue #2
local ccallFunction = Expr(:call, :ccall, functionAndLibrary, esc(retType), cArgTypesExpr, autoNamedArgs...)
return Expr(Symbol("="), jlCall, ccallFunction)
end
Running
@macroexpand @generateDemangled(SparseMultiply,
:_Z14SparseMultiply18SparseMatrix_Float17DenseMatrix_FloatS0_,
Cvoid,
SparseMatrix{Cfloat}, StridedMatrix{Cfloat}, StridedMatrix{Cfloat})
in the REPL generates something that looks pretty close to what I want. But there’s still 2 issues with this:
- I’m calling
eval
inside of a macro. That’s bad form: macros ought to manipulate expressions at parse time. - When I tried to use
@ccall
viaExpr(:macrocall, Symbol("@ccall"), ...)
it’d give me errors for not passing aLineNumberNode
as the 2nd argument. I reverted toccall
, but now this means I ought to explicitly invokeGC.@preserve
,cconvert
, andunsafe_convert
. (These structs have pointers as fields.) Ick.
Is there a way to resolve these 2 issues? Or is there a better approach that avoids macros entirely? Mostly I’m just trying to cut down on code repetition. I guess I could provide the C types as well, which would eliminate issue #1…but then that brings back a good portion of the code repetition.
I do care about performance, though: are there benefits that come with using a macro, versus other ways of generating code? I’m currently using @eval
to generate multiple functions, by looping over 2 possible type parameters, to cut the amount of code duplication in half. Code snippet here. But I don’t expect that to impact performance: both types are hard-coded.