Introducing Vcc: the Vulkan Clang Compiler

January 09, 2024

Vcc, the Vulkan Clang Compiler1, is the result of about 3 years of research at the computer graphics chair from Saarland University. It’s exactly what the name implies: a clang-based compiler that outputs code that runs on Vulkan.

Vcc can be thought of as a GLSL and HLSL competitor, but the true intent of this project is to retire the concept of shading languages entirely. Unlike existing shading languages, Vcc makes a honest attempt to bring the entire C/C++ language family to Vulkan, which means implementing a number of previously unseen features in Vulkan shaders:

  • Physical Pointers: Pointer arithmetic and observing the memory layout of types.
  • Generic Pointers: Eliminate the address space from pointers, like OpenCL can.
  • Real Function Calls: Including supporting recursion and function pointers.
  • Control Flow: We support all control-flow statements, including goto2.

Crucially: the support for these constructs is not best effort but instead is central the project. In plainer words: we intend to support every legal use of these constructs per the relevant language standards. The fundamental compiler work to make all of this possible is already done and has been working internally for many months.

Why ?

The ecosystem case

On one hand, shading languages from graphics APIs suffer from reduced expressivity against their compute cousins. Most if not all of the aforementionned features are staples of programming in CUDA, ROCm as well as modern versions of OpenCL. The gap between compute and graphics is hard to justify, and seems to have a number of historical reasons behind it. However, there are precious little justifications for this status-quo persisting.

On the other, GLSL and HLSL are facing significant evolutionary challenges. They started life as C-ish dialects but for various reasons have diverged from it in significant ways. This divergence is a well-known problem and it significantly gets in the way of sharing code between host and device, because while it is possible to write code in a common subset between say, GLSL and C++, doing so is severely restrictive.

Making Vulkan able to sit at the same table as dedicated compute APIs has been a recurring topic over the last few years, for obvious reasons: Vulkan has seen fantastic adoptions and implementations are rather high-quality. The tooling around Vulkan is of high quality, but one of the blind spots has been the shading language situation. By aligning Vulkan with other GPU compute APIs we can kill two birds with one stone and further break down the software gap between graphics and compute APIs.

The case for expressivity

As my earlier post on function calls argued, there is a case to be made for strong, effective abstractions. I believe that lacking higher-level abstractions doesn’t make a language “faster” but instead discourages experimentation.

The current restrictiveness found in shading languages makes Vulkan programming harder as a net result. Crucially, the current lack of the “dynamic” abstractions like function calls, in favour of only offering static ones (macros and templates), hinders scaling by making code size an issue, and also pushes complexity back to the host.

Not all of the industry is against more dynamic abstractions in shaders, the recent M3 GPU architecture from Apple makes great strides to eliminate the traditional drawbacks of larger shaders. The new work graphs extensions from AMD also treads similar ground. Vcc and Shady aim to be part of this movement towards richer, more expressive shading languages.

How ?

Vcc is merely a front-end for Shady, which is the more important artifact here. Shady is an IR and compiler designed to extend SPIR-V with support for the aforementioned constructs. Shady presents as a relatively conventional IR, and includes support for parsing LLVM IR. It handles the lowering and emulation of all the extra features not found in current versions of SPIR-V 3.

There are of course a number of unique features found only in shaders. These are exposed in Vcc using intrinsics and annotations, allowing to write code that interfaces with the various features of the Vulkan pipeline. To make this nicer, it’s wrapped in macros provided in shady.h, see this sample:

#include <shady.h>

descriptor_set(0) descriptor_binding(1) uniform sampler2D texSampler;

location(0) input vec3 vertexColor;
location(1) input vec2 texCoord;

location(0) output vec4 outColor;

fragment_shader void main() {
    vec4 fragColor; = vertexColor;
    fragColor.w = 1.0f;
    outColor = texture2D(texSampler, texCoord) * fragColor;

The internals of Shady are open-source for all to read, but we are looking to publish about it this year. There is a number of novel approaches used in this work which will be explored together with their applications to other compilers, in particular when it comes to our handling of divergence and control flow.

When ?

You can play with Vcc right now. But be warned, it’s early days. There is a website and the code is available here, build it and install it somewhere. You will need LLVM development files and Clang installed on the machine for the Vcc target to build and work correctly.

There are a number of samples bundles in the repository directly, but more are available in this fork of VulkanTutorial including a port of aobench, a typical C microbenchmark.

If you’re so inclined, you can attend my introductory talk on Vcc at Vulkanised 2024 on Feb. 7 17:00 PST, where I will go into more detail about the design and transformations performed in Shady, as well my thoughts on the implications for the future of shading languages.

  1. Or perhaps Vulkan C Compiler, or even Vulkan Compiler Collection. It’s up to you really. ↩︎

  2. We do so while offering well defined non-uniform semantics for all of it ! This is a headline topic for our research work. ↩︎

  3. To be accurate, many of the features are present in SPIR-V, but are explicitly banned from the Vulkan dialect of SPIR-V, therefore making this a moot point. Some, but not all of the features are more implemented in OpenCL which has it’s own flavour of SPIR-V. See my older post on SPIR-V ↩︎