5.9. Why macros do not work in a #pragma?

OpenMP compilers usually expand macros inside #pragma omp, so why macros do not work in Mercurium?

The reason is that when Mercurium compiles an OmpSs/OpenMP program we do not enable OpenMP in the native compiler to reduce the chance of unwanted interferences. This implies that the #pragma omp is not known to the native preprocessor, which leaves the #pragma omp as is. As a consequence, macros are not expanded and they become unusable inside pragmas.

5.9.1. Alternatives

Although macros are not expanded in clauses, most of the cases they are used for integer constants:

#define N 100

#pragma omp task out([N]a)
void f(int *a);

void g()
{
  int a[N];

  f(a);
#pragma omp taskwait
}

The example shown above will not work but fortunately in these cases one can avoid macros. A first approach replaces the macro with an enumerator. This is the most portable approach:

enum { N = 100 };
// Rest of the code

Another approach uses an const integer:

const int N = 100;
// Rest of the code

Note

In C a const qualified variable is not a constant expression whereas in C++ it may be (more information). Mercurium, though, allows this case as a constant expression in both languages.

In case you need to change the value at compile time, you can use the following strategy. Here assume that N_ is a macro that you change through the invocation of the compiler (typically with a compiler flag like -DN_=100):

enum { N = N_ };
// Rest of the code

Another feasible scenario where macros and pragmas are combined is when we want to generate the pragma itself via macros. To do that, we could use the _Pragma operator introduced in C99 and C++11:

#define STRINGIFY(X) #X
#define MY_PRAGMA(X) _Pragma(STRINGIFY(X))
#define MY_OPENMP_PARALLEL_LOOP omp parallel for

int main() {
    MY_PRAGMA(MY_OPENMP_PARALLEL_LOOP)
    for(int i = 0; i < 10; ++i) {
        // ...
    }
}

5.9.2. Why rely on the native preprocessor, then?

We could provide our own preprocessor, but this adds a lot of complication just to support this case (and as shown in Alternatives the typical use case can be worked around).

Complications include, but do not limit to:

  • it is not trivial to know where are the system headers. While gcc provides good support to know which paths it is using, not all compiler vendors provide this information. Even worse, some parameters may change the used paths between invocations of the native compiler
  • subtleties among preprocessor implementations. Not all preprocessors behave the same way and sometimes these differences are relevant for the native compiler headers