@cfunction and generated functions


#1

With the new @cfunction macro, there is now the option to use $ syntax to create a runtime closure over a callable, which (I think, please correct me if this is wrong) can be safely used as follows:

cfun = @cfunction $identity Int (Int,) # a CFunction
GC.@preserve cfun begin
    ptr = Base.unsafe_convert(Ptr{Cvoid}, Base.cconvert(Ptr{Cvoid}, cfun))
    ccall(ptr, Int, (Int,), 42)
end

So far so good, but now I’m trying to write a generated function that uses @cfunction. Here are two attempts:

@generated function makefun1(obj)
    quote
        @cfunction $obj Int (Int,)
    end
end
cfun = makefun1(identity) # a Ptr{Cvoid}, not a CFunction!
@generated function makefun2(obj)
    quote
        @cfunction $(Expr(:$, obj)) Int (Int,)
    end
end
cfun = makefun2(identity) # a CFunction, so far so good
GC.@preserve cfun begin
    ptr = Base.unsafe_convert(Ptr{Cvoid}, Base.cconvert(Ptr{Cvoid}, cfun))
    ccall(ptr, Int, (Int,), 42) # error!
end

The error I get with this last version is:

ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type typeof(identity)
Closest candidates are:
  convert(::Type{T}, ::T) where T at essentials.jl:154
Stacktrace:
 [1] typeof(identity)(::Int64) at ./deprecated.jl:473
 [2] top-level scope at ./gcutils.jl:87

which happens because the type of obj gets interpolated in the generated function.

How do I do this? I guess I’m just confused due to the two different meanings of $.

(Note: the application is FunctionWrappers.jl; my PR for 1.0 support currently passes tests but just reimplements the old jl_function_ptr call that was present in cfunction on 0.6. I’m trying to get this part working (@rdeits’ branch).


#2

As any other metaprogramming, check the expression you are generating.

Expr(:$, :obj).


#3

Ah OK, that wasn’t as hard as I thought it was.


#4

@yuyichao, what do you think about CallWrapper in 1.0? @cfunction is now capable of handling closures and that was one of CallWrapper's purposes (the other being conversion to the desired return type). I was thinking about simply turning CallWrapper into

struct CallWrapper{Ret, F}
    f::F
end

CallWrapper{Ret}(f::F) where {Ret, F} = CallWrapper{Ret, F}(f)

(wrapper::CallWrapper{Ret})(args...) where {Ret} = convert(Ret, wrapper.f(args...))

for nargs in 0:128
    @eval function (wrapper::CallWrapper{Ret})($((Symbol("arg", i) for i in 1:nargs)...)) where Ret
        convert(Ret, wrapper.f($((Symbol("arg", i) for i in 1:nargs)...)))
    end
end

Thoughts?


#5

Well, I got it working on 0.7 with @cfunction and the approach I described in my previous post. Probably easier to discuss over at https://github.com/yuyichao/FunctionWrappers.jl/pull/7.