A feature-complete shader compiler library written in pure C99. Converts between WGSL, GLSL 450, MSL, PTX, HLSL, and SPIR-V through a shared intermediate representation.
WGSL ──┐ ┌── SPIR-V
GLSL ──┤ ├── WGSL
MSL ──┼── [SSIR] ────┼── GLSL 450
PTX ──┤ ├── MSL
SPIR-V ┘ ├── HLSL
└── V3D (Pi 5)
- Single header, zero dependencies -- include
simple_wgsl.hand link the static library. No runtime dependencies beyond the C standard library. - Eight languages, one pipeline -- parse WGSL, GLSL, MSL, or PTX source; ingest SPIR-V binaries; emit to any of six output formats. Every conversion flows through the same intermediate representation.
- ~46k lines of C99 -- no templates, no inheritance hierarchies, no build system complexity. Every compilation unit is a single
.cfile. Builds in under a second. - Embeddable -- all memory allocation goes through overridable macros (
NODE_MALLOC,SSIR_MALLOC, etc.), so you can plug in your own allocator for game engines, embedded systems, or WASM targets. - Fuzz-hardened -- eight libFuzzer targets with AddressSanitizer + UndefinedBehaviorSanitizer have been run continuously.
#include "simple_wgsl.h"
#include <stdio.h>
int main(void) {
// Parse WGSL source
const char *src =
"@vertex fn main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4f {\n"
" return vec4f(0.0, 0.0, 0.0, 1.0);\n"
"}\n";
WgslAstNode *ast = wgsl_parse(src);
WgslResolver *resolver = wgsl_resolver_build(ast);
// Compile to SPIR-V
WgslLowerOptions opts = {0};
opts.env = WGSL_LOWER_ENV_VULKAN_1_2;
opts.enable_debug_names = 1;
uint32_t *spirv;
size_t word_count;
WgslLowerResult res = wgsl_lower_emit_spirv(ast, resolver, &opts, &spirv, &word_count);
if (res == WGSL_LOWER_OK)
printf("Generated %zu SPIR-V words\n", word_count);
wgsl_lower_free(spirv);
wgsl_resolver_free(resolver);
wgsl_free_ast(ast);
}Compile directly without a build system (requires SPIR-V Headers installed):
cc -c *.c -I/path/to/SPIRV-Headers/include
ar rcs libsimple_wgsl.a *.oOr use CMake (3.16+), which fetches SPIR-V Headers automatically if not found on the system:
cmake -B build -G Ninja
ninja -C buildRun the tests (requires Google Test, fetched automatically):
cmake -B build -G Ninja -DWGSL_BUILD_TESTS=ON
ninja -C build
ctest --test-dir build --output-on-failureBuild options (All testing/debugging related):
| Option | Default | Description |
|---|---|---|
WGSL_BUILD_TESTS |
ON |
Build Google Test suite |
WGSL_BUILD_EXPRESSION_TESTS |
OFF |
Build golden-file expression tests (needs expressions/ data) |
WGSL_BUILD_FUZZ |
OFF |
Build libFuzzer fuzz targets |
WGSL_BUILD_EGL_TESTS |
OFF |
Build EGL/OpenGL compute validation tests |
WGSL_BUILD_METAL_EXAMPLES |
OFF |
Build Metal/MSL examples (macOS only) |
WGSL_USE_ASAN |
OFF |
Enable AddressSanitizer |
WGSL_USE_UBSAN |
OFF |
Enable UndefinedBehaviorSanitizer |
WGSL_USE_LSAN |
OFF |
Enable LeakSanitizer (standalone, not with ASan) |
WGSL_COVERAGE |
OFF |
Enable code coverage with llvm-cov (requires Clang) |
graph LR
WGSL["WGSL Source"] --> P1[wgsl_parse]
GLSL["GLSL 450 Source"] --> P2[glsl_parse]
MSL["MSL Source"] --> P3[msl_to_ssir]
PTX["PTX Assembly"] --> P4[ptx_to_ssir]
P1 --> AST[AST]
P2 --> AST
AST --> R[Resolver]
R --> L[Lowering]
L --> SSIR["SSIR Module"]
L --> SPIRV["SPIR-V Binary"]
SPIRV --> D[spirv_to_ssir]
P3 --> SSIR
P4 --> SSIR
D --> SSIR
SSIR --> E1[ssir_to_spirv]
SSIR --> E2[ssir_to_wgsl]
SSIR --> E3[ssir_to_glsl]
SSIR --> E4[ssir_to_msl]
SSIR --> E5[ssir_to_hlsl]
SSIR --> E6[ssir_to_v3d]
E1 --> SPIRV2["SPIR-V"]
E2 --> WGSL2["WGSL"]
E3 --> GLSL2["GLSL 450"]
E4 --> MSL2["MSL"]
E5 --> HLSL2["HLSL"]
E6 --> V3D2["V3D QPU"]
SSIR (Simple Shader Intermediate Representation) sits at the center. All source languages parse into it, all output formats emit from it. See TECHNICAL.md for the full architecture deep-dive.
| File | Lines | Purpose |
|---|---|---|
simple_wgsl.h |
2024 | Unified public API header |
wgsl_parser.c |
2432 | WGSL lexer + recursive-descent parser |
glsl_parser.c |
2635 | GLSL 450 lexer + recursive-descent parser |
msl_parser.c |
2405 | MSL lexer + parser (produces SSIR directly) |
ptx_parser.c |
1879 | PTX assembly lexer + recursive-descent parser |
ptx_lower.c |
3099 | PTX to SSIR lowering (register tracking, BDA support, texture/surface ops) |
wgsl_resolve.c |
1266 | Semantic analysis and symbol resolution |
wgsl_lower.c |
8103 | AST to SSIR to SPIR-V compilation |
wgsl_raise.c |
2148 | SPIR-V to WGSL decompilation |
ssir.c |
3430 | SSIR module, type system, and builder API |
ssir_to_spirv.c |
3056 | SSIR to SPIR-V serialization |
ssir_to_wgsl.c |
1603 | SSIR to WGSL text emission |
ssir_to_glsl.c |
1684 | SSIR to GLSL 450 text emission |
ssir_to_msl.c |
2068 | SSIR to Metal Shading Language emission |
ssir_to_hlsl.c |
1470 | SSIR to HLSL emission |
spirv_to_ssir.c |
2566 | SPIR-V to SSIR deserialization |
ssir_to_v3d.c |
2603 | SSIR to V3D QPU binary emission (Raspberry Pi 5) |
simple_wgsl supports opt-in language extensions via enable directives:
| Extension | What it enables |
|---|---|
immediate_address_space |
var<immediate> for push constants with scalar, vector, matrix, and struct types |
immediate_arrays |
Additionally allows arrays in var<immediate> (implies immediate_address_space) |
See the TUTORIAL.md immediates section for usage details and the TECHNICAL.md for the full specification.
- TECHNICAL.md -- Architecture, SSIR specification, full API reference, type system, instruction set, and internal design details.
- TUTORIAL.md -- Step-by-step guides for every major use case: parsing, compiling, decompiling, cross-compiling, immediates (push constants), building the SSIR programmatically, custom allocators, and more.
When the Vulkan SDK is available, the build also produces cuvk_runtime -- a drop-in libcuda.so.1 replacement that runs CUDA compute kernels on any Vulkan GPU. Cross-platform: Linux, macOS (MoltenVK / Kosmickrisp), Windows. CUDA binaries compiled with nvcc link against this library at runtime (via LD_PRELOAD or direct linking), and their PTX kernels are cross-compiled to SPIR-V on the fly.
CUDA fatbin --> extract PTX (zstd/lz4) --> ptx_to_ssir --> ssir_to_spirv --> VkComputePipeline
Key features:
- BDA mode (Vulkan 1.1+ with buffer device address): kernel parameters packed into push constants, pointers as 64-bit device addresses
- Descriptor mode (fallback): each pointer parameter becomes a storage buffer descriptor binding
- Stream-based dispatch: kernel launches and event timestamps are recorded into per-stream command buffers for correct ordering and accurate GPU-side timing
- No libvulkan link dependency: all Vulkan functions loaded at runtime via X-macro function pointer table (
dlsym/LoadLibraryAbootstrap)
Includes stub implementations for libcudart.so.1, libcublas.so.13, and libcufft.so.12. The cuFFT stub implements Cooley-Tukey and Stockham FFT via WGSL compute shaders. See cuvk_runtime/ for source (~12.7k lines).
| Dependency | Version | Source |
|---|---|---|
| SPIR-V Headers (Khronos) | vulkan-sdk-1.4.341 | System or FetchContent |
| Google Test | v1.14.0 | FetchContent (tests only) |
| Vulkan SDK | system | Optional, for GPU tests and cuvk_runtime |
| zstd | v1.5.7 | FetchContent (cuvk_runtime only) |
spirv-val / spirv-dis |
system | Optional, SPIRV-Tools for test validation |
See LICENSE.