Create a custom @ccall

I am trying to create a macro that replaces the @ccall in my code.
My idea is to create something like this:

macro my_ccall(arg)
     custom_function_before_ccall()
     @ccall arg
end

I am having trouble with the type of the argument “arg”. I want it essentially to do just syntax replacement (like the define in C). Is it possible ?
Thanks in advance !

Not sure this is the type of answer you’re looking for, but you could simply adapt the implementation of @ccall, leaving the difficult parts to the existing methods ccall_macro_lower and ccall_macro_parse:

julia> import Base: ccall_macro_lower, ccall_macro_parse

julia> macro my_ccall(expr)
           println("Hello from my custom ccall!")
           return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...)
       end
@my_ccall (macro with 1 method)

julia> @my_ccall clock()::Int32
Hello from my custom ccall!
274138

EDIT: Like in Patrick’s approach below, it’s in general probably better to put the println (custom_function_before_ccall()) inside the returned expression:

julia> import Base: ccall_macro_lower, ccall_macro_parse

julia> custom_function_before_ccall() = println("Hello")
custom_function_before_ccall (generic function with 1 method)

julia> macro my_ccall(expr)
            ret_expr = ccall_macro_lower(:ccall, ccall_macro_parse(expr)...)
            return :(custom_function_before_ccall(); $ret_expr)
       end
@my_ccall (macro with 1 method)

julia> @my_ccall clock()::Int32
Hello
67218

Patrick’s approach is also a bit simpler, so you should probably just use that :slight_smile: .

1 Like

A macro typically should return an Expr. Depending on what you want to do exactly, something like

macro my_ccall(arg)
    quote
        custom_function_before_ccall()
        @ccall $arg
    end |> esc
end

should work. You probably want to apply a more nuanced escaping than that, but that depends on your specific situation.

Edit: Incorrect parentheses removed

1 Like

Thanks for your answers !
Actually although Patrick’s solution is more elegant, it did not work for me. I should have been more precise in my initial post: I also want to run function after the ccall. I think that maybe using @ccall that expands to return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) will cause the lines following the ccall not to be executed ? Does this make any sense ?

What worked for me is the following:

 macro my_ccall(expr)
            custom_function_before_ccall()
            ret_expr = ccall_macro_lower(:ccall, ccall_macro_parse(expr)...)
            custom_function_after_ccall()
            return ret_expr
end

I also tried

 macro my_ccall(expr)
       quote
            custom_function_before_ccall()
            ret_expr = @ccall($expr)
            custom_function_after_ccall()
            return ret_expr
     end |> esc
end

and

 macro my_ccall(expr)
       quote
            custom_function_before_ccall()
            ret_expr = ccall_macro_lower(:ccall, ccall_macro_parse($expr)...)
            custom_function_after_ccall()
            return ret_expr
     end |> esc
end

with no success

This works for me:

macro my_ccall(arg)
    result = gensym() # Do not conflict with identifiers from the call site even with the brute-force escaping used below
    return quote
        custom_function_before_ccall()
        $result = @ccall $arg
        custom_function_after_ccall()
        $result # Set return value of quote block
    end |> esc
end
custom_function_before_ccall() = println("before")
custom_function_after_ccall() = println("after")
@my_ccall clock()::Int

Note that on the REPL after is print before the result of clock() because the REPL does the printing of the return value when the macro is done. This is only an “observer” problem, the execution order is correct.

I had an additional pair of parentheses in the @ccall, because I forgot, that @ccall is special syntax and not a usual macro. I am sorry about that.

You had a mix of expr and arg which probably did not work.

Please note, that the solutions are totally different, which you will not observe in trivial cases.

In the solution proposed by me, everything is returned by the macro, so the macro is just rewriting code. The function custom_function_*_ccall are returned by the macro as code (Expr) and run during runtime. So if you put @my_ccall clock()::Int into a function, the functions custom_function_*_ccall are called whenever your function is called.

In your solution, the functions are executed during macro execution time. As your macro only returns a value, custom_function_*_ccall are not called when your function containing @my_ccall clock()::Int is called.

When working with macros, you have the chance to sneak something into the early startup phase of your program (depending on when your file is included, imported or used, this can be also quite late). You always need to think whether you really want to do something now or whether you just want to change the code at the macro’s caller to do something later.

So the question really is: What do you want? If this is a one-time initialization, go ahead with your approach executing the functions during macro execution (but keep in mind that macro execution is generally done during module precompilation).
If you just want to save typing and call the functions whenever your function is called, you should switch to the approach suggested by me (or something similar returning the function calls inside an Expr).

1 Like