Intel® C++ Compiler 16.0 User and Reference Guide

Exception Handling

Intel® Cilk™ Plus attempts to reproduce, as closely as possible, the semantics of C++ exception handling. This generally requires limiting parallelism while exceptions are pending, and programs should not depend on parallelism during exception handling. The exception handling logic is as follows:

Throwing an exception does not abort existing children or siblings of the strand in which the exception is thrown; these strands will run normally to completion.

If a try block contains a cilk_spawn and/or a cilk_sync, then there is an implicit cilk_sync just before entering the try block and an implicit cilk_sync at the end of the try block (after destructors are invoked). A sync is executed automatically before exiting a try block, function block, or cilk_for body via an exception. (Note that the scope of a sync within a cilk_for is limited to spawns within the same loop.) A function has no active children when it begins execution of a catch block. These implicit syncs exist to ensure that the same catch clauses run as would have run in a serial execution of the program.

Implicit syncs may limit the parallelism of your program. The sync before a try block, in particular, may prematurely sync a spawn that occurs before the try block as in the following example:

void func() {
   cilk_spawn f();
   try { // oops! implicit sync prevents parallel execution of f()
     cilk_spawn g();
     h();
   }
   catch (...) {
     // Handle exceptions from g() or h(), but not f()
   }
}

If this is a problem for your program, there are two ways to prevent the implicit sync. One way is to move the body of your try block into a separate function. By moving the cilk_spawn lexically out of the try block, you eliminate the automatic generation of cilk_sync at both the start and end of the try block. The previous example could be rewritten as follows:

void gh()
{
   cilk_spawn g();
   h();
}

void func() {
   cilk_spawn f();
   try { // no cilk_spawn in body, so no implicit sync
     gh();
   }
   catch (...) {
     // Handle exceptions from gh(), but not f()
   }
}

Another way to prevent an implicit sync is to enclose the body of the try block or the entire try/catch statement in a cilk_for loop that executes only once. The body of a cilk_for loop is an isolated context such that spawns and syncs do not interact with the surrounding function. If you do this a lot, a macro can come in handy. The above example could be rewritten as follows:

#define CILK_TRY cilk_for (int _temp = 0; _temp < 1; ++_temp) try

void func() {
   cilk_spawn f();
   CILK_TRY { // try is within cilk_for, so does not sync f()
     cilk_spawn g();
     h();
   }
   catch (...) {
     // Handle exceptions from g() or h(), but not f()
   }
}

Windows* Structured Exception Handling

There are restrictions when using Microsoft Windows* structured exception handling (specifically, the /EHa compiler option and the __try, __except, __finally and __leave extensions to C/C++). Structured exception handling (SEH) will fail if an SEH exception is thrown after a steal occurs and before the corresponding cilk_sync. The runtime recognizes this condition and terminates the program with an appropriate error code.