October 1, 2021·4 min read·engineeringcclangopenclcompilers

Clang 13 makes OpenCL declarations part of the default compilation

Clang 13 changes OpenCL's default language version and builtin declaration handling. Its guaranteed tail-call attribute also gives C code an enforceable stack-growth requirement.

A compiler command can depend on defaults that never appear in its build description. Changing those defaults changes how the same source is interpreted. Clang 13 makes two such changes for OpenCL compilation. It supplies builtin declarations by default and chooses OpenCL C 1.2 when the command line does not explicitly select a language version.

The LLVM 13.0.0 release is published on October 1. Its versioned Clang release notes describe the OpenCL changes and a new guaranteed tail-call attribute available to C. Both concern what the compiler promises about a translation, though one changes the environment supplied to kernel source and the other adds an explicit requirement to a call.

I am interested in the distinction because C's value at a platform boundary depends on understanding the actual compilation contract. Source text alone does not establish that contract. Language selection and compiler extensions can change what declarations exist and what code generation must guarantee.

Builtins no longer require a separate enabling flag

Clang 13 adds builtin OpenCL types, macros, and function declarations by default. The release provides -cl-no-stdinc to suppress default declarations that are not native to the compiler. A build that previously arranged those declarations itself therefore needs to understand what it now receives automatically.

The effect is broader than finding a header file. The release notes distinguish compiler-native declarations from the supplied declarations the new flag suppresses. A custom compilation environment may intentionally control that latter set. The flag gives such an environment an explicit choice instead of forcing it to rely on an earlier absence of default declarations.

The default language version also becomes OpenCL C 1.2. A command that requires another version can select it explicitly. This matters when diagnosing a build difference because the absence of a version flag is now a positive choice of 1.2 under Clang 13. It should not be interpreted as a request for whichever newest language facilities the compiler happens to recognize.

The release adds explicit version selection for C++ for OpenCL 1.0 through -cl-std=clc++1.0 and recognizes the .clcpp source extension. Those are separate interfaces from the OpenCL C default. A source extension and language flag are part of explaining which dialect reaches the front end.

Optional facilities need distinct handling

The OpenCL C 3.0 work includes parsing support for optional facilities such as generic address spaces and images. It also adds optionality support in builtin function headers for generic address spaces and C11 atomics. A compiler recognizing a language version still needs to model which facilities are optional within that version.

This is relevant to the new default declarations. Automatically providing a set of declarations does not mean every possible target supports every OpenCL operation. The front end's declaration environment and the language's capability model have to agree. The release's explicit optionality work addresses that distinction instead of treating a version number as a complete description of target functionality.

The notes also report corrected OpenCL C language-version and SPIR address-space information in DWARF. Debug information is another consumer of the compiler's understanding of the source dialect. If the emitted description misstates the address space or language version, inspection tools can receive an inaccurate account even when the source compiled.

For an engineer reading a build command, dialect selection is one source of compilation behavior. The origin of declarations supplies another part of the explanation, and optional language facilities constrain which operations are available. These causes should remain distinguishable in the build configuration. An explicit language version records the intended dialect even if a later toolchain changes its default again. A deliberate choice about supplied declarations similarly records whether the build expects Clang's environment or provides its own. Neither choice should be inferred solely from a successful translation of one kernel source file.

C can require a tail call

The same release introduces guaranteed tail calls using a statement attribute. In C, the GNU spelling is __attribute__((musttail)). The attribute belongs to a return statement whose value is a function call. It does not annotate the function declaration as a whole.

The versioned attribute reference states that the compiler must generate a tail call even when optimization is disabled. If a recursive cycle depends on that guarantee to avoid unbounded stack growth, an ordinary optimization opportunity is insufficient. The compiler has to diagnose a call for which it cannot provide the required transformation.

An illustrative C wrapper has matching caller and callee signatures:

int next_step(int value);

int current_step(int value)
{
    __attribute__((musttail)) return next_step(value);
}

The signatures matter. The reference requires the same number of arguments and compatible types under its stated similarity rules. Calling conventions must match between the caller and the target function. Variadic functions and old-style K&R declarations do not qualify. Those restrictions are part of making a code-generation guarantee enforceable, rather than merely encouraging the optimizer to try.

I prefer a compilation error when the algorithm depends on this property. A stack bound that disappears when optimization settings change is a hidden precondition of the program. musttail lets source state the requirement at the call that needs it, with an implementation check for the supported conditions.

The attribute does not make an arbitrary recursive function bounded in stack usage. It applies to the annotated return call, and a cycle must have the needed guarantees along its relevant calls. Operations that require work after the callee returns also conflict with the intended transfer. The documented restrictions identify the cases the compiler can commit to supporting.

Clang 13 separately adds -fstack-usage, which generates a .su file containing function frame-size information. That output describes individual frames. A tail-call requirement concerns whether repeated calls add frames, so the two facilities answer different questions. Knowing a frame's size cannot by itself bound a recursion depth, while a guaranteed tail transfer changes how that call contributes to stack growth.