Programming language
Cilk, Cilk++, Cilk Plus and OpenCilk are general-purpose programming languages designed for multithreaded parallel computing. They are based on the C and C++ programming languages, which they extend with constructs to express parallel loops and the fork–join idiom.
Originally developed in the 1990s at the Massachusetts Institute of Technology (MIT) in the group of Charles E. Leiserson, Cilk was later commercialized as Cilk++ by a spinoff company, Cilk Arts. That company was subsequently acquired by Intel, which increased compatibility with existing C and C++ code, calling the result Cilk Plus. After Intel stopped supporting Cilk Plus in 2017, MIT is again developing Cilk in the form of OpenCilk.
History.
MIT Cilk.
The Cilk programming language grew out of three separate projects at the MIT Laboratory for Computer Science:
In April 1994 the three projects were combined and christened "Cilk". The name Cilk is not an acronym, but an allusion to "nice threads" (silk) and the C programming language. The Cilk-1 compiler was released in September 1994.
The original Cilk language was based on ANSI C, with the addition of Cilk-specific keywords to signal parallelism. When the Cilk keywords are removed from Cilk source code, the result should always be a valid C program, called the "serial elision" (or "C elision") of the full Cilk program, with the same semantics as the Cilk program running on a single processor. Despite several similarities, Cilk is not directly related to AT&amp;T Bell Labs' Concurrent C.
Cilk was implemented as a translator to C, targeting the GNU C Compiler (GCC). The last version, Cilk 5.4.6, is available from the MIT Computer Science and Artificial Intelligence Laboratory (CSAIL), but is no longer supported.
A showcase for Cilk's capabilities was the Cilkchess parallel chess-playing program, which won several computer chess prizes in the 1990s, including the 1996 Open Dutch Computer Chess Championship.
Cilk Arts and Cilk++.
Prior to c. 2006, the market for Cilk was restricted to high-performance computing. The emergence of multicore processors in mainstream computing meant that hundreds of millions of new parallel computers were being shipped every year. Cilk Arts was formed to capitalize on that opportunity: in 2006, Leiserson launched Cilk Arts to create and bring to market a modern version of Cilk that supports the commercial needs of an upcoming generation of programmers. The company closed a Series A venture financing round in October 2007, and its product, Cilk++ 1.0, shipped in December, 2008.
Cilk++ differs from Cilk in several ways: support for C++, support for loops, and hyperobjects – a new construct designed to solve data race problems created by parallel accesses to global variables. Cilk++ was proprietary software. Like its predecessor, it was implemented as a Cilk-to-C++ compiler. It supported the Microsoft and GNU compilers.
Intel Cilk Plus.
On July 31, 2009, Cilk Arts announced on its web site that its products and engineering team were now part of Intel Corp. In early 2010, the Cilk website at codice_1 began redirecting to the Intel website (as of early 2017, the original Cilk website no longer resolves to a host). Intel and Cilk Arts integrated and advanced the technology further resulting in a September 2010 release of Intel Cilk Plus. Cilk Plus adopts simplifications, proposed by Cilk Arts in Cilk++, to eliminate the need for several of the original Cilk keywords while adding the ability to spawn functions and to deal with variables involved in reduction operations. Cilk Plus differs from Cilk and Cilk++ by adding array extensions, being incorporated in a commercial compiler (from Intel), and compatibility with existing debuggers.
Cilk Plus was first implemented in the Intel C++ Compiler with the release of the Intel compiler in Intel Composer XE 2010. An open source (BSD-licensed) implementation was contributed by Intel to the GNU Compiler Collection (GCC), which shipped Cilk Plus support in version 4.9, except for the _Cilk_for keyword, which was added in GCC 5.0. In February 2013, Intel announced a Clang fork with Cilk Plus support. The Intel Compiler, but not the open source implementations, comes with a race detector and a performance analyzer.
Intel later discontinued it, recommending its users switch to instead using either OpenMP or Intel's own TBB library for their parallel programming needs.
Differences between versions.
In the original MIT Cilk implementation, the first Cilk keyword is in fact codice_2, which identifies a function which is written in Cilk. Since Cilk procedures can call C procedures directly, but C procedures cannot directly call or spawn Cilk procedures, this keyword is needed to distinguish Cilk code from C code. Cilk Plus removes this restriction, as well as the codice_2 keyword, so C and C++ functions can call into Cilk Plus code and vice versa.
Deprecation of Cilk Plus.
In May, 2017, GCC 7.1 was released and marked Cilk Plus support as deprecated. Intel itself announced in September 2017 that they would deprecate Cilk Plus with the 2018 release of the Intel Software Development Tools. In May 2018, GCC 8.1 was released with Cilk Plus support removed.
OpenCilk.
After Cilk Plus support was deprecated by Intel, MIT has taken on the development of Cilk in the OpenCilk implementation, focusing on the LLVM/Clang fork now termed "Tapir". OpenCilk remains largely compatible with Intel Cilk Plus. Its first stable version was released in March 2021.
Language features.
The principle behind the design of the Cilk language is that the programmer should be responsible for "exposing" the parallelism, identifying elements that can safely be executed in parallel; it should then be left to the run-time environment, particularly the scheduler, to decide during execution how to actually divide the work between processors. It is because these responsibilities are separated that a Cilk program can run without rewriting on any number of processors, including one.
Task parallelism: spawn and sync.
Cilk's main addition to C are two keywords that together allow writing task-parallel programs.
Below is a recursive implementation of the Fibonacci function in Cilk, with parallel recursive calls, which demonstrates the spawn, and sync keywords. The original Cilk required any function using these to be annotated with the cilk keyword, which is gone as of Cilk Plus. (Cilk program code is not numbered; the numbers have been added only to make the discussion easier to follow.)
cilk int fib(int n) {
 if (n ; 2) {
 return n;
 else {
 int x, y;
 x = spawn fib(n - 1);
 y = spawn fib(n - 2);
 sync;
 return x + y;
If this code was executed by a "single" processor to determine the value of fib(2), that processor would create a frame for fib(2), and execute lines 1 through 5. On line 6, it would create spaces in the frame to hold the values of x and y. On line 8, the processor would have to suspend the current frame, create a new frame to execute the procedure fib(1), execute the code of that frame until reaching a return statement, and then resume the fib(2) frame with the value of fib(1) placed into fib(2)'s x variable. On the next line, it would need to suspend again to execute fib(0) and place the result in fib(2)'s y variable.
When the code is executed on a "multiprocessor" machine, however, execution proceeds differently. One processor starts the execution of fib(2); when it reaches line 8, however, the spawn keyword modifying the call to fib(n-1) tells the processor that it can safely give the job to a second processor: this second processor can create a frame for fib(1), execute its code, and store its result in fib(2)'s frame when it finishes; the first processor continues executing the code of fib(2) at the same time. A processor is not obligated to assign a spawned procedure elsewhere; if the machine only has two processors and the second is still busy on fib(1) when the processor executing fib(2) gets to the procedure call, the first processor will suspend fib(2) and execute fib(0) itself, as it would if it were the only processor. Of course, if another processor is available, then it will be called into service, and all three processors would be executing separate frames simultaneously.
If the processor executing fib(2) were to execute line 13 before both of the other processors had completed their frames, it would generate an incorrect result or an error; fib(2) would be trying to add the values stored in x and y, but one or both of those values would be missing. This is the purpose of the sync keyword, which we see in line 11: it tells the processor executing a frame that it must suspend its own execution until all the procedure calls it has spawned off have returned. When fib(2) is allowed to proceed past the sync statement in line 11, it can only be because fib(1) and fib(0) have completed and placed their results in x and y, making it safe to perform calculations on those results.
The code example above uses the syntax of Cilk-5. The original Cilk (Cilk-1) used a rather different syntax that required programming in an explicit continuation-passing style, and the Fibonacci examples looks as follows:
thread fib(cont int k, int n)
 if (n ; 2) {
 send_argument(k, n);
 else {
 cont int x, y;
 spawn_next sum(k, ?x, ?y);
 spawn fib(x, n - 1);
 spawn fib(y, n - 2);
thread sum(cont int k, int x, int y)
 send_argument(k, x + y);
Inside fib's recursive case, the spawn_next keyword indicates the creation of a "successor" thread (as opposed to the "child" threads created by spawn), which executes the sum subroutine after waiting for the "continuation variables" x and y to be filled in by the recursive calls. The base case and sum use a send_argument(k, n) operation to set their continuation variable k to the value of n, effectively "returning" the value to the successor thread.
Inlets.
The two remaining Cilk keywords are slightly more advanced, and concern the use of "inlets". Ordinarily, when a Cilk procedure is spawned, it can return its results to the parent procedure only by putting those results in a variable in the parent's frame, as we assigned the results of our spawned procedure calls in the example to codice_4 and codice_5.
The alternative is to use an inlet. An inlet is a function internal to a Cilk procedure which handles the results of a spawned procedure call as they return. One major reason to use inlets is that all the inlets of a procedure are guaranteed to operate atomically with regards to each other and to the parent procedure, thus avoiding the bugs that could occur if the multiple returning procedures tried to update the same variables in the parent frame at the same time.
Inlets were removed when Cilk became Cilk++, and are not present in Cilk Plus.
Parallel loops.
Cilk++ added an additional construct, the parallel loop, denoted cilk_for in Cilk Plus. These loops look like
void loop(int *a, int n)
 #pragma cilk grainsize = 100 // optional
 cilk_for (int i = 0; i ; n; i++) {
 a[i] = f(a[i]);
This implements the parallel map idiom: the body of the loop, here a call to f followed by an assignment to the array a, is executed for each value of i from zero to n in an indeterminate order. The optional "grain size" pragma determines the coarsening: any sub-array of one hundred or fewer elements is processed sequentially. Although the Cilk specification does not specify the exact behavior of the construct, the typical implementation is a divide-and-conquer recursion, as if the programmer had written
static void recursion(int *a, int start, int end)
 if (end - start ;= 100) { // The 100 here is the grainsize.
 for (int i = start; i ; end; i++) {
 a[i] = f(a[i]);
 else {
 int midpoint = start + (end - start) / 2;
 cilk_spawn recursion(a, start, midpoint);
 recursion(a, midpoint, end);
 cilk_sync;
void loop(int *a, int n)
 recursion(a, 0, n);
The reasons for generating a divide-and-conquer program rather than the obvious alternative, a loop that spawn-calls the loop body as a function, lie in both the grainsize handling and in efficiency: doing all the spawning in a single task makes load balancing a bottleneck.
A review of various parallel loop constructs on HPCwire found the cilk_for construct to be quite general, but noted that the Cilk Plus specification did not stipulate that its iterations need to be data-independent, so a compiler cannot automatically vectorize a cilk_for loop. The review also noted the fact that reductions (e.g., sums over arrays) need additional code.
Reducers and hyperobjects.
Cilk++ added a kind of