2

When an OpenMP pragma is used as part of an argument for a macro, it gets incorrectly substituted. In this code:

#define make_body( ... ) { __VA_ARGS__ }
extern foo( int );
int main(){
  make_body(
    #pragma omp parallel for
    for( int i = 0; i < 10; i += 1 ){
      foo( i );
    }
  )
}

I would expect that it would be expanded to:

extern foo( int )
int main(){
  {
    #pragma omp parallel for
    for( int i = 0; i < 10; i += 1 ){
      foo( i );
    }
  }
}

However, (according to gcc -E) it becomes expanded to:

extern foo( int );
int main(){
#pragma omp parallel for
  { 
    for( int i = 0; i < 10; i += 1 ){ 
      foo( i ); 
    } 
  }
}

Is this correct behavior? How can I get the expected behavior, preferably without changing the arguments to the macro? Does this happen with all pragmas? Is this an effect of the variadic macro? Do other compilers perform the same substitution?

Using gcc (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609

  • I'm curious: what's the real target use case? The example presented in the question cannot be it, because it would be simpler and less troublesome to put in literal `{}` like a normal person. – John Bollinger Nov 20 '18 at 22:47
  • 1
    The real use case is a code timing library that inserts timer stop, start, and time elapsed reporting code around the code section. It's been working quite well so far but this OpenMP example unfortunately breaks things. To get around this I'll add some macros for those individual components so the code section is not a macro argument. – memorableusername Nov 21 '18 at 02:47

3 Answers3

1

You are not allowed to put any preprocessing directive inside the arguments to a function-like macro. (C11 6.10.3p11, last sentence.)

In this case, you should be able to write _Pragma("omp parallel for") and get the effect you want. Experimentation indicates that this works correctly with both clang and gcc (versions 7 and 8 respectively) but only if you specify -fopenmp on the command line. Yes, even if using -E.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • _Pragma is a C99 feature, so nearly 20 years old! https://stackoverflow.com/questions/45477355/difference-between-pragma-and-pragma-in-c – Jim Cownie Nov 26 '18 at 09:13
  • I'm commenting to emphasize that last part because it just bit me really hard. For the openmp stuff, you absolutely must use `-fopenmp` for this to work: https://godbolt.org/z/h9NI-n – memorableusername Jun 21 '19 at 18:53
1

Is this correct behavior?

It would be most accurate to say that it is not incorrect behavior. The standard has this to say about macro arguments:

If there are sequences of preprocessing tokens within the list of arguments that would otherwise act as preprocessing directives, the behavior is undefined.

You have exercised that case, and accordingly reaped undefined behavior.

Does this happen with all pragmas? Is this an effect of the variadic macro? Do other compilers perform the same substitution?

The behavior is undefined, so it may vary among conforming implementations, and even within the same implementation, for any reason or none. I would guess that GCC is relatively consistent in this area, but in no way should you rely on that. It is not specifically tied to the macro involved being variadic.

How can I get the expected behavior, preferably without changing the arguments to the macro?

C11, which is supported by GCC 5, has a solution for the specific case of macros that emit pragmas: the _Pragma operator. It is more often used in literal macro replacement text, so that you can have a macro that represents a pragma, but it ought to work in a macro argument, too. You will, however, have to change the macro argument a bit:

  make_body(
    _Pragma("omp parallel for")
    for( int i = 0; i < 10; i += 1 ){
      foo( i );
    }
  )
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

Alternative solution this fixes a case we saw in real code which is way to big to be recreated here (not that we're exactly sure why it happens anyway). -fopenmp is also necessary.

#define EMPTY()
#define DELAY(x) x EMPTY()
#define STRINGIZE(x) STRINGIZE_NO_PREPROCESS(x)
#define STRINGIZE_NO_PREPROCESS(x) #x

#define PRAGMA(x) _Pragma( STRINGIZE(x) )
#define DELAYED_PRAGMA_assist(x) PRAGMA(x)
#define DELAYED_PRAGMA DELAY(DELAYED_PRAGMA_assist)

#define PRAGMA_IN_MACRO_ARG(x) DELAYED_PRAGMA(x)
#define PRAGMA_IN_MACRO_BODY(x) PRAGMA(x)

PRAGMA_IN_MACRO_ARG can be used to correctly place a the pragma when the code is being passed to another macro as an argument For example:

#define WRAP( body ) { body }

#define loop( i, N, body )\
if( N > 0 ) \
WRAP( \
  PRAGMA_IN_MACRO_ARG(omp parallel for private(i)) \
  for( i = 0; i < N; ++i ){ \
      body \
  } \
)

loop( i, 10, printf("%d\n", i); )

PRAGMA_IN_MACRO_BODY can be used to correctly place a the pragma when the code is simply part of a macro's body, and not as input to another macro For example:

#define loop( i, N, body )\
if( N > 0 ) \
{\
  PRAGMA_IN_MACRO_BODY(omp parallel for private(i)) \
  for( i = 0; i < N; ++i ){ \
      body \
  } \
}

loop( i, 10, printf("%d\n", i); )