SPIR-V, ClangIR and the future of Vcc

October 15, 2024

SPIR-V is going places and we’re hitching a ride.


Late last year, I announced Vcc, to a very positive reception. At the time, we had an impressive working prototype, but not a “production-ready” compiler. I have been very busy this last year figuring out where to go from there and I think it’s now time to share my plans.

Intro

For those that missed it, I gave a presentation at Vulkanised 2024 where I discussed the state of the shader ecosystem, discussed the tech and expressed my wish to ultimately see the tech become redundant. This might come off as weird, but really, I didn’t set out to build this when I became a PhD student, I wanted to make AnyDSL work with Vulkan, instead of a bunch of bespoke, brittle vendor-specific backends. The reality is that there is a big gap between SPIR-V dialects and the one Vulkan uses is basically unusable unless you’re a legacy shading language, which I wanted to change.

I still do.

Thankfully I’m in a pretty decent position to do something about it, as thanks to my employment situation, and more specifically my PhD supervisor, I was able to become a member of the Khronos Group, the organization in charge of writing the Vulkan and SPIR-V standards. The exact process of what goes on within Khronos is naturally not something I can discuss publicly, but what I can say is that I’m happy about our yearly membership renewal1. Still, to have the best chance of pushing standards to evolve in the right direction, I need to provide a solid use-case for an extended SPIR-V dialect.

And this is what Shady and Vcc are for: they’re here to (hopefully) bootstrap the next generation of the SPIR-V ecosytem by making things work today, that would otherwise take many more years of slow evolution. In Vcc’s case, it furthers my case that shading languages are awful and that we should copy the way the compute folks do it already. Shady’s job is to support Vcc, and possibly other things2 by providing something approximating that easier to target SPIR-V dialect.

Still, in the end both are meant to be transitional tools: Vcc’s functionality should be built into Clang proper, and Shady’s into the core SPIR-V spec and GPU drivers.

Doing Things Right

The current (legacy) Vcc pipeline, note the potential mismatch between libLLVMs

The way Vcc works right now is an elaborate hack: We invoke clang on the command line, provide it with our special headers, some obscure options so it leaves the IR as untouched as possible, and let it dump out the LLVM bitcode on the disk. We then parse back that bitcode using libLLVM and using the C API, we convert the LLVM module into a shady one. This approach got us started but it’s ultimately doomed, for a few reasons:

  • We either ship our own clang or face potential version mismatches between it and the version of LLVM we linked against
  • We can’t define our own custom attributes and are stuck abusing clang::annotate which limits what we can do
  • LLVM IR is an unstructured control-flow graph, which means we can’t implement maximal reconvergence properly

It’s not “the right way” to go about this problem… But what would be the right way ? What we’d like is a nicer way to interface between Clang and Shady, possibly eliminating the need for vcc entirely. Turns out there is already something out there that was designed to solve precisely the problem we’re facing:

Oh my, It’s SPIR-V again !

  • It is a binary format that is designed to be exchanged between different tools
  • It is built to be extensible and we can easily create our own dialect
  • It already supports structured control flow

The plan moving forward is to sunset the legacy vcc frontend and instead move to a custom fork of Clang. This fork would emit a new SPIR-V dialect (a superset of the one used by Vulkan), that we’d lower to the Vulkan one via shady still. It feels like a far more correct solution: shady now both ingests and outputs SPIR-V, acting as a dialect translator and allowing us to delete all that hacky vcc LLVM IR wrangling code.

The future (clangir-based) Vcc pipeline

We could even make it transparent: Use Vulkan’s layer mechanism to implement extensions that allow to directly consume the SPIR-V obtained from Clang. Drivers could then choose to implement some of those extensions at their leisure, while providing a smooth transition for users.

Contributing Shader support to ClangIR

There are however a number of other things that I don’t think shady should be dealing with: For example, transforming struct layout (i.e. std140, std430 etc) is quite problematic to do at the IR level, since assumptions about type layout might be used in LLVM code optimizations and newer versions of the IR have dropped pointee types from pointers, making it impractical to patch offsets in address calculations. Instead we should really implement these things in Clang, which sounds quite daunting given the AST nature of that representation.

Thankfully, the ClangIR project looks set to provide a much better way to do things: it provides an MLIR dialect that represents a program in a form that’s close to the AST, but which is then cleanly lowered in multiple passes in a way that is far more approachable, modular and hackable. For our layout example, we can simply find struct definitions that have a layout annotation, and insert appropriate padding.

Crucially, ClangIR starts out structured, which makes me hopeful we can avoid ever lowering to a CFG and instead stay within the scf dialect - keeping the structured control flow semantics needed for maximal reconvergence. We’re currently exploring doing our own fork of ClangIR, and we hope that we can ultimately contribute back these things into mainline.

Doing the Right Thing

Open standards and Free Software are an incredible resource, and in our niche of graphics and GPUs I think it’s fair to say they’re just about to revolutionize the field. While previous generations have learned to cope with a landscape of proprietary APIs and formats, it’s becoming increasingly viable and attractive to embrace open standards instead. We sure hope we’re doing our part.

Recently, Microsoft announced they would switch to SPIR-V for future versions of Direct3D, which means ultimately they’ll benefit from the growing SPIR-V ecosystem, and Apple will stand alone outside of it. Time will tell how both Microsoft’s adoption and Apple’s future stance go, but I feel optimistic about this. While SPIR-V does not by itself provide immediate solutions to portability woes, it does provide the framework to build these solutions.

Appeal to Contributors

It’s becoming quite clear that we don’t have the human resources internally to pursue all of this in a timely manner, so we’re looking to build out our own community to host these efforts. If you have experience with compilers, SPIR-V or the LLVM project, and would like to contribute (or just to stand by the side and help with testing!) you can find us on Discord and GitHub.


  1. Given the relatively affordable price of membership for universities and small businesses, I would recommend those who have the time to get themselves involved within Khronos! ↩︎

  2. wink @eddyb and rust-gpu :) ↩︎