Avoiding Potential Problems - the __MIC__ macro and offload code blocks

The __MIC__ macro is used to protect source code which is intended to run only on the coprocessor. For example:

__declspec( target (mic))

void is_it_on_coprocessor()

{

  #ifdef __MIC__

    printf("yes, it is\n");

  #else

    printf("no, it is not\n");

  #endif

}

The __declspec(target(mic)) declaration tells the Intel® C++ compiler to generate two versions of the object code for this routine. In one, the target architecture is the Intel® Xeon® processor - the host system - and the __MIC__ macro is undefined. In the other, the target architecture is the Intel® Xeon Phi™ coprocessor and __MIC__ is defined .

It is tempting to use this macro directly inside an offload code block rather than relegating it to a lower level function. For example:

void is_it_on_coprocessor()

{

  #pragma offload target(mic)

  {

    #ifdef __MIC__

      printf("yes, it is\n");

    #else

      printf("no, it is not\n");

    #endif

  }

}

At first, this source code seems quite reasonable and, when it is executed, appears to do what we want. However, if we change the source code by adding a variable inside the #ifdef __MIC__ block we find things are not as we might have hoped. For example:

void is_it_on_coprocessor()

{

  int print_this = 0;

  #pragma offload target(mic)

  {

    #ifdef __MIC__

      printf("yes, it is: %d\n", print_this);

    #else

      printf("no, it is not\n");

    #endif

  }

}

With this source code, we now get the rather cryptic runtime error message:

offload error: unexpected number of variable descriptors 

What is going on here -

The problem comes about because the preprocessor must be executed before the compiler can be called. The first time the preprocessor is called, it is not yet known that the function contains code which must also be compiled for the coprocessor. Therefore, on this pass through the preprocessor, the __MIC__ macro is not defined. Once it is known that object code must also be generated for the coprocessor, the source code is preprocessed again with __MIC__ defined.

In the first example, the entire function is preprocessed and compiled "as is", first with __MIC__ undefined, then with __MIC__ defined. So, in this first example there is no problem.

But look at the third example again:

void is_it_on_coprocessor()

{

  int print_this = 0;

  #pragma offload target(mic)

  {

    #ifdef __MIC__

      printf("yes, it is: %d\n", print_this);

    #else

      printf("no, it is not\n");

    #endif

  }

}

When this source code is run through the preprocessor with __MIC__ undefined, it reduces to:

void is_it_on_coprocessor()

{

  int print_this = 0;

  #pragma offload target(mic)

  {

      printf("no, it is not\n");

  }

}

The compiler looks at this and generates object code that does something like:

if a coprocessor is available

  call the offload routines (no local variables need to be passed)

else

  print "no, it is not"

The compiler also determines that object code for the coprocessor must be generated. The source code is run through the preprocessor again, this time with __MIC__ defined:

void is_it_on_coprocessor()

{

  int print_this = 0;

  #pragma offload target(mic)

  {

         printf("yes, it is: %d\n", print_this);

   }

}

From this, the compiler now generates object code for the coprocessor which does something like:

grab a copy of the variable print_this that was passed in

print "yes, it is: ", print_this

Normally, when an offload directive is encountered, the compiler is able to determine which local variables are required inside the offload block. In this case, the object code generated for the host does not detect that there is a local variable which needs to be passed. The object code generated for the coprocessor, however is expecting a variable.

Just don't do it -

There are ways to modify the offload statement in the third example in order to force the offload to include the "print_this" variable. However, this only addresses one possible error. Depending on the code, other errors are possible. For example:

void is_it_on_coprocessor()

{

  #pragma offload target(mic)

  {

    #ifdef __MIC__

      printf("yes, it is\n");

    #endif

  }

}

In this case the program runs to completion but nothing prints out. We now have a silent error.

In these simple examples, it is fairly easy to see what went wrong. In actual production code, it generally is not. You can safely use the __MIC__ macro in functions that are called from an offload block but never use it directly in the offload block itself. Just don't do it.

For more complete information about compiler optimizations, see our Optimization Notice.