Simple question about macro arguments

Hello,

I would like to use a macro to compute derivative of several fields.
Concretly, in a simplified context, I have the following issue:
for the macro

macro testij(i,j) esc(:(a[$i]=b[$j] )) end

for a and b defined, if I do @macroexpand @testij(1+2,2) I get

:(a[1 + 2] = b[2])

I would like to obtain this :(a[3] = b[2]), how can I do it ?

In my particular case I do operation on several field:

macro compute_∇P(li,lj) esc( 
    quote
        ∂xPx = (P_shared[$li+1,$lj,1] - P_shared[$li-1,$lj,1])*0.5/Δ
        ∂zPx = (P_shared[$li,$lj+1,1] - P_shared[$li,$lj-1,1])*0.5/Δ
        ∂xPz = (P_shared[$li+1,$lj,2] - P_shared[$li-1,$lj,2])*0.5/Δ
        ∂zPz = (P_shared[$li,$lj+1,2] - P_shared[$li,$lj-1,2])*0.5/Δ
    end)
end

For boundary conditions I do

i_ = mod(i-1,1:N)
@compute_∇P(i_,j)

but I would like to write it this way @compute_∇P(mod(i-1,1:N),j) without computing the modulus several time.

Thank you for your help,
Best

If you write

macro compute_∇P(li, lj)
    quote
        let li = $li, lj = $lj
            lip, lim = li+1, li-1
            ljp, lim = lj+1, lj-1
            ∂xPx = (P_shared[lip, lj, 1] - P_shared[lim, lj, 1])*0.5/Δ
            ∂zPx = (P_shared[li, ljp, 1] - P_shared[li, ljm, 1])*0.5/Δ
            ∂xPz = (P_shared[lip, lj, 2] - P_shared[lim, lj, 2])*0.5/Δ
            ∂zPz = (P_shared[li, ljp, 2] - P_shared[li, ljm, 2])*0.5/Δ
        end
    end |> esc
end

then li and lj are evaluated only once:

julia> @macroexpand @compute_∇P(mod(i-1,1:N), j)
quote
    #= REPL[1]:3 =#
    let li = mod(i - 1, 1:N), lj = j
        #= REPL[1]:4 =#
        (lip, lim) = (li + 1, li - 1)
        #= REPL[1]:5 =#
        (ljp, lim) = (lj + 1, lj - 1)
        #= REPL[1]:6 =#
        ∂xPx = ((P_shared[lip, lj, 1] - P_shared[lim, lj, 1]) * 0.5) / Δ
        #= REPL[1]:7 =#
        ∂zPx = ((P_shared[li, ljp, 1] - P_shared[li, ljm, 1]) * 0.5) / Δ
        #= REPL[1]:8 =#
        ∂xPz = ((P_shared[lip, lj, 2] - P_shared[lim, lj, 2]) * 0.5) / Δ
        #= REPL[1]:9 =#
        ∂zPz = ((P_shared[li, ljp, 2] - P_shared[li, ljm, 2]) * 0.5) / Δ
    end
end

EDIT: Alternatively, why not use a function that returns the tuple (∂xPx, ∂zPx, ∂xPz, ∂zPz)?

2 Likes

Macro calls don’t act when function calls do, they transform source code, so they have to act before the code is evaluated. Consequently, their inputs are only a few inlined literals (Int, Float64, String), Symbol, and Expr that are initially parsed from source code. That means the macro actually takes i == :(1+2); it’s not like a function call where 1+2 evaluates beforehand. Instead, you need to generate code that evaluates 1+2 before its result is used for indexing. matthias314’s comment works off similar principles.

In the global scope, it is possible to normally eval-uate Expr interpolated with previously computed values of any type. Evaluation of arbitrary expressions can’t be done within local scopes after full parsing.

2 Likes

Thank you for your help,
It is amazing to have answers so fast !

I wouldn’t know exactly how to do it in an optimized way, this bloc of code is used in two CUDA.jl kernels.
My P field from the global memory is load into the shared memory, then I compute derivatives of P that I need for the next step in the same kernel.
As I don’t have enough space and as I need P each time I need ∇P, I don’t want to save ∇P in the global memory (I am trying to reduce the number of access to the global memory).
I need the value of ∇P in two different kernels and I prefer to not copy-past the code, that why I went for macro.

I am a physicist by training and know almost nothing about good practice for writing code. In fact I don’t even know exactly what I am allowed to write in a kernel. I am slowly learning by doing benchmark for my specific system.

Thank you again for your help !

EDIT: it is in fact not working with let but if I use

macro compute_∇P(li,lj)
    quote
        li=$li; lj=$lj
        lip, lim = li+1, li-1
        ljp, ljm = lj+1, lj-1
        ∂xPx = (P_shared[lip,lj,1] - P_shared[lim,lj,1])*0.5/Δ
        ∂zPx = (P_shared[li,ljp,1] - P_shared[li,ljm,1])*0.5/Δ
        ∂xPz = (P_shared[lip,lj,2] - P_shared[lim,lj,2])*0.5/Δ
        ∂zPz = (P_shared[li,ljp,2] - P_shared[li,ljm,2])*0.5/Δ
    end |> esc
end

it is enough to not compute the modulus multiple times

1 Like

The issue with this way is that I systematically do two operations while I might need to evaluate only one. For this one I might need to define 4 new variables while I might only need zero or one li=$li; lj=$lj; i=$i; j=$j if I have to do only one modulus.

macro load_ρμPv(li,lj,i,j) esc(
    quote
        v_shared[$li,$lj,1] = v[$i,$j,1]
        P_shared[$li,$lj,1] = P[$i,$j,1]
        v_shared[$li,$lj,2] = v[$i,$j,2]
        P_shared[$li,$lj,2] = P[$i,$j,2]
        μ_shared[$li,$lj] = μ[$i,$j]
        ρ_shared[$li,$lj] = ρ[$i,$j]
    end)
end

concretely I would like to call @load_ρμPv(li,T+2,i,mod(j+1,1:N)) without having to write

jp = mod(j+1,1:N)
ljp = T+2
@load_ρμPv(li,ljp,i,jp)

it is mainly to make it easier to read and more compact.

I forgot to declare the variables before the let. There was also a typo. This one should work:

macro compute_∇P(li, lj)
    quote
        ∂xPx, ∂zPx, ∂xPz, ∂zPz = let li = $li, lj = $lj
            lip, lim = li+1, li-1
            ljp, ljm = lj+1, lj-1
            ∂xPx = (P_shared[lip, lj, 1] - P_shared[lim, lj, 1])*0.5/Δ
            ∂zPx = (P_shared[li, ljp, 1] - P_shared[li, ljm, 1])*0.5/Δ
            ∂xPz = (P_shared[lip, lj, 2] - P_shared[lim, lj, 2])*0.5/Δ
            ∂zPz = (P_shared[li, ljp, 2] - P_shared[li, ljm, 2])*0.5/Δ
            ∂xPx, ∂zPx, ∂xPz, ∂zPz
        end
    end |> esc
end

I’m using let to avoid overwriting existing variables li and lj. This may not be a concern in your case. A different way to achieve this would be to use gensym to create new variable names.

1 Like

Thank you for your help, it is working well now !