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 Cuint32s, 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
evalinside of a macro. That’s bad form: macros ought to manipulate expressions at parse time. - When I tried to use
@ccallviaExpr(:macrocall, Symbol("@ccall"), ...)it’d give me errors for not passing aLineNumberNodeas 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.