Passing function type parameters to a macro

Hey all,

I would like to use macros to generate functions with different bodies based on the function’s type parameters. @generated is not an option since ultimately these need to work with CUDA.jl (as far as I can tell, those are incompatible).

Consider this example, where I have a statement/block returned by my_body and I want it to be inline repeated.


macro my_body()
    return :(println("~"))
end

macro repeat_inline(N, expr)
    # Yes, this is terrible but it works
    tail(expr, N) = N <= 0 ? :() : :($expr; $(tail(expr, N - 1)))
    return esc(tail(expr, N))
end

function g()
    @repeat_inline 3 @my_body
end

println("parsed")
g()
println("run")

which prints

parsed
~
~
~
run

Macros can take arguments that are literals, exprs, and symbols. But if a value is known at compile time, such as a Val arg, it seems reasonable that we may use that, too.

Is it possible write a macro that could be invoked something like this?


# This is what fails
function f(v :: Val{N}) where {N}
    @repeat_inline N @my_body
end

f(Val(5))

I won’t bother posting all the weird iterations I’ve tried, but I can’t seem to pass to repeat_inline an expression where N has been evaluated from the scope of the function.

If it’s possible, any suggestions?

1 Like

Nope, sorry :slightly_smiling_face:

Macros operate on syntax, never on values. Even if those values are types and are known at compile time, they’re still values. The macro receives the symbol :N with no meaning or value attached to it.

Can you tell us a little more about what you’re actually trying to do and why it requires metaprogramming at all? Your minimal example is so minimal that it can (and definitely should) use a for loop instead of any macro trickery.

5 Likes

Darn! Ok, thanks @rdeits

Mostly this was an ‘educational’ question to better understand macros. I’m writing kernels using CUDA.jl, specifically warp reductions. Since the warp size of mine and most other hardware is 32, I can safely hard-code 5 = log2(32). Explicit iteration costs precious registers. Putting that block in a function doesn’t seem like an option since the iteration mutates multiple variables, and I can’t return tuples on a gpu. This seemed nicer than trying to use pointers, if that is even possible.

The macro thing would be helpful for the next level of reductions, which span multiple warps, because then the number of times I could repeat might be limited if I’m trying to move larger objects through shared memory (and each iteration needs twice as much shared memory as the previous).

I think I might go the way of generating my own functions like:

julia> begin
       macro make(name, arg, body)
           esc(:($name($arg) = $body))
       end
       @make f x x*2
       end
f (generic function with 1 method)

julia> println(f(1))
2

Except less very minimal :wink: