Un langage système à l'assaut du GPU : les ambitions du backend SPIR-V de Zig

Un langage système à l'assaut du GPU : les ambitions du backend SPIR-V de Zig

ZigSPIR-VGPUCompilateurShaderProgrammation Système

Sources:Lobsters

Le 26 juin 2026, le journal de développement de Zig affichait un titre : « SPIR-V Backend Progress ». L’auteur est Ali Cheraghi, contributeur principal du backend SPIR-V du compilateur Zig. Ce n’est pas un article annonçant que « le backend SPIR-V est maintenant prêt » — au contraire, il consacre de longs paragraphes à reconnaître le bitrot (pourrissement du code), la limitation single-thread et un taux de réussite aux tests de comportement de seulement 49 %. Pourtant, le même jour, ce billet s’est retrouvé en page d’accueil de Lobsters à 28 points, avec trois commentaires, tous exprimant de l’enthousiasme.

J’ai essayé de comprendre d’où venait cet enthousiasme. Un backend de compilateur auto-hébergé, avec moins de la moitié des tests de comportement qui passent, cassé à plusieurs endroits après la fusion dans la branche principale et nécessitant des semaines de réparations — selon n’importe quel critère traditionnel de livraison logicielle, cela relève de l’« expérimentation précoce ». La communauté y a pourtant lu un signal radicalement différent : un langage de programmation système commence à établir une tête de pont sur le territoire du GPU.

Où se situe SPIR-V

Pour comprendre ce signal, il faut revenir à la place de SPIR-V dans l’écosystème GPU.

SPIR-V est la représentation intermédiaire binaire (IR) définie par le groupe Khronos, utilisée par Vulkan, OpenCL, OpenGL et, dans un futur proche, par DirectX. Son objectif de conception central est simple : sortir la compilation des shaders et des noyaux de calcul du pilote graphique pour la placer du côté de l’application. Avant SPIR-V, le chemin standard de la programmation GPU était — écrire du code source en GLSL ou HLSL, et le confier au pilote pour une compilation à l’exécution. La qualité du compilateur intégré au pilote variait considérablement d’un fabricant à l’autre, d’une version de pilote à l’autre. SPIR-V a changé cette division du travail : le frontend du langage se charge de générer du binaire SPIR-V conforme à la spécification, le pilote se charge uniquement de le traduire en ISA GPU. La responsabilité de la compilation est passée du pilote à la chaîne d’outils du langage.

Ce déplacement signifie une chose : n’importe quel frontend de compilateur capable de produire du SPIR-V valide peut devenir un point d’entrée pour la programmation GPU. Plus besoin de GLSL. Plus besoin de HLSL. La spécification Vulkan elle-même ne se préoccupe pas de savoir si votre binaire SPIR-V a été transposé depuis GLSL ou compilé directement depuis C++, Rust, Julia — ou Zig.

C’est pourquoi il est si important qu’un backend de compilateur puisse produire du SPIR-V. Un compilateur de langage de programmation généraliste peut générer directement du code GPU — la frontière entre langage de shader et langage généraliste commence à s’estomper.

Jusqu’où le backend de Zig est-il allé ?

Le journal de développement du 26 juin 2026 couvre cinq dimensions de progrès. Je les classe par ordre d’importance d’ingénierie :

Premièrement, l’instruction builtin @SpirvType. SPIR-V possède des types qui n’ont pas de correspondance directe dans le système de types de Zig — sampler, image, sampled image, runtime array. Jusqu’ici, ces types ne pouvaient s’exprimer qu’en écrivant manuellement des instructions SPIR-V en assembleur inline, ce qui était depuis longtemps catalogué comme « le plus gros obstacle à l’écriture de shaders ». @SpirvType élève les types spécifiques au GPU au rang de concept de première classe reconnu par le compilateur : vous pouvez déclarer un sampler en syntaxe Zig, le lier à un descriptor set et à un point de binding — c’est le saut crucial entre « pouvoir générer des instructions SPIR-V » et « pouvoir écrire naturellement un shader en Zig ».

Deuxièmement, les modes d’exécution portés par la convention d’appel. Taille du workgroup, origine des fragments, paramètres de mesh shader — ces informations de mode d’exécution étaient auparavant insérées manuellement via l’assembleur inline OpExecutionMode. Dans la nouvelle conception, vous déclarez la convention d’appel d’une fonction comme callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }), et le compilateur déduit automatiquement le mode d’exécution correct. Deux nouvelles conventions d’appel, spirv_task et spirv_mesh, ont également été ajoutées pour supporter le pipeline de mesh shading. Côté utilisateur, déclarer le point d’entrée d’un compute shader devient aussi naturel que déclarer une fonction Zig exportée ordinaire.

Troisièmement, la génération de code multithread. Le backend SPIR-V tournait depuis le premier jour en single-thread à l’intérieur du thread de l’éditeur de liens. Cette refonte l’intègre au pipeline unifié MIR → génération de code du compilateur ; chaque tâche de génération de code est désormais dispatchée sur le pool de threads, comme n’importe quel autre backend auto-hébergé. Deux passes ISel font également leur retour : dedup_types (fusion des instructions de type en double) et prune_unused (élimination du code mort) — toutes deux avaient été supprimées lors de la précédente refonte single-thread et sont restaurées grâce à la mise à niveau architecturale. Pour l’utilisateur, l’impact concret est la vitesse de compilation ; du point de vue du jugement d’ingénierie, cela signifie que le backend SPIR-V est sorti, au niveau architectural, du statut de « cas particulier » pour devenir une unité de compilation de plain-pied avec les autres cibles.

Quatrièmement, l’édition de liens de fichiers objets. Les fichiers .spv sont désormais reconnus comme un format de fichier objet. Plusieurs fichiers .zig (ou objets .spv externes) peuvent être compilés puis assemblés en un module unique par l’éditeur de liens SPIR-V. Cela signifie que les grands projets de shaders peuvent être découpés en plusieurs unités de compilation, ce qui permet théoriquement la compilation incrémentielle et la distribution de bibliothèques — même si ces workflows avancés ne sont pas encore prêts, les fondations au niveau du format sont posées.

Cinquièmement, les capacités et extensions pilotées par le jeu de fonctionnalités CPU. Auparavant, OpCapability et OpExtension étaient insérés ad hoc par la génération de code ou l’assembleur inline ; ils sont désormais extraits de SPIRV-Headers selon leur chaîne de dépendances et gérés de manière unifiée par le jeu de fonctionnalités CPU. L’assembleur rejette toute tentative d’insertion manuelle de ces instructions — le compilateur commence à assumer une responsabilité systémique sur la correction de sa sortie, au lieu de déléguer la validation à l’outil spirv-val en aval.

Par rapport à il y a quatre semaines, le taux de réussite aux tests de comportement est passé d’environ 39 % à 49 % (cible spirv64-vulkan), des dizaines de bugs ont été corrigés, std.gpu a été renommé std.spirv. Cheraghi reste sobre dans son expression : « Le backend SPIR-V est significativement plus utilisable qu’il y a un mois, mais il reste loin du but. »

Positionné sur la carte des concurrents

Zig n’est pas le seul projet à tenter de connecter un langage généraliste au GPU. En plaçant côte à côte les principaux concurrents, on peut positionner plus précisément le backend SPIR-V de Zig.

Rust GPU (rust-gpu) est le point de référence le plus direct. Basé sur rustc_codegen_spirv, il compile Rust en shaders SPIR-V. Le projet a démarré vers 2019, a bénéficié du soutien d’Embark Studios avant d’être transféré à la communauté. Il dispose actuellement d’un sous-ensemble de bibliothèque standard fonctionnel (spirv-std), de démos jouables dans le navigateur avec SHADERed, et d’un framework expérimental SPIR-T pour les optimisations au moment de l’édition de liens. Mais d’après les issues GitHub et les discussions communautaires, les mises à jour du compilateur Rust imposent fréquemment au plugin codegen des adaptations de suivi ; une version stable n’a pas encore vu le jour.

Circle C++ Shader Compiler permet d’écrire des shaders en C++ standard, avec des attributs pour marquer les points d’entrée GPU, et produit directement du SPIR-V. Au niveau syntaxique, c’est proche de CUDA — source unique, sur-ensemble du C++. Mais Circle est un compilateur propriétaire, maintenu par Sean Baxter seul, ce qui limite sa portée écosystémique.

Julia GPU fournit des capacités de programmation GPU via CUDA.jl et AMDGPU.jl, en contournant SPIR-V pour générer directement des instructions PTX ou AMDGCN. L’avantage est le développement interactif — écrire un noyau dans le REPL et l’exécuter immédiatement. L’inconvénient est clair : la portabilité inter-fabricants dépend de l’écosystème de paquets plutôt que d’un IR standard.

Le backend SPIR-V de Zig occupe une position très spécifique sur cette carte : c’est le seul langage de programmation système à traiter SPIR-V comme un backend auto-hébergé du compilateur — rust-gpu est un plugin codegen externe au compilateur Rust, pas un composant de première classe du projet Rust ; Circle est un projet personnel propriétaire ; Julia contourne SPIR-V. Le backend SPIR-V de Zig est maintenu dans le même dépôt, avec le même système de build, et examiné par le même groupe de contributeurs principaux que les backends x86, ARM et RISC-V.

C’est une arme à double tranchant. Être dans le même dépôt que la ligne principale du compilateur signifie que le backend SPIR-V évolue passivement à chaque ajustement architectural du compilateur Zig — le bitrot est le prix de ce couplage étroit. Mais cela signifie aussi que toute amélioration de l’infrastructure du compilateur (système de types, pipeline de génération de code, éditeur de liens) peut automatiquement bénéficier au backend SPIR-V. Le rétablissement de la génération de code multithread dans le journal du 26 juin est un exemple concret de ce mécanisme : la décision architecturale d’unifier le pipeline MIR a « gratuitement » offert au backend SPIR-V la capacité de dispatch sur le pool de threads.

Le vrai obstacle n’est pas dans le compilateur

Du point de vue de la feuille de route technique, les plus grands obstacles qui attendent le backend SPIR-V de Zig ne se situent pas entièrement à l’intérieur du compilateur.

Le premier obstacle, ce sont les espaces d’adressage. Le modèle mémoire du GPU distingue plusieurs espaces d’adressage — global, local, private, constant — alors que les pointeurs de Zig supposent par défaut un espace d’adressage générique. Le blog de Cheraghi mentionne que Vulkan ne supporte pas OpPtrCastToGeneric — l’implémentation actuelle suppose donc, comme solution temporaire, que tous les pointeurs appartiennent à la classe de stockage Function. Cela signifie que les manipulations complexes de pointeurs (comme passer des références entre espaces d’adressage) seront limitées sur la cible Vulkan. La situation est meilleure sur la cible OpenCL, car l’environnement de base OpenCL garantit davantage de capacités, et le taux de réussite aux tests de comportement y est plus élevé (environ 75 %).

Le deuxième obstacle, ce sont les divergences de sémantique numérique. Dans l’environnement Vulkan, les instructions fma, sqrt, exp, log ne garantissent pas un arrondi correct — ce qui entre en conflit avec les hypothèses de sémantique numérique par défaut du compilateur Zig. Zig est plus exigeant en matière de déterminisme que ne l’est la tolérance typique des langages de shaders vis-à-vis de la précision numérique. Ce n’est pas nécessairement un problème insoluble — Rust GPU et les compilateurs GLSL ont tous affronté le même fossé sémantique — mais cela exige des décisions de conception explicites et une documentation, ce qui est encore en cours.

Le troisième obstacle est d’ordre écosystémique : l’adaptation de la bibliothèque standard. Sur un GPU, il n’y a pas de système d’exploitation, pas de système de fichiers, pas d’allocateur de tas (du moins pas au sens traditionnel). Une grande partie du code de la bibliothèque standard de Zig repose sur ces hypothèses. Porter std.math, std.sort, les structures de données et algorithmes courants vers un sous-ensemble compatible GPU est un travail d’une ampleur comparable à celle du backend du compilateur lui-même. Dans sa liste de « prochaines étapes », Cheraghi mentionne les sommes préfixes, les réductions, la multiplication matricielle et d’autres algorithmes fondamentaux — ce sont les briques de base des charges de travail HPC et ML, ce qui indique que la priorisation est correcte, mais aussi que la progression en est encore au stade précoce.

Pourquoi ce journal a-t-il attiré l’attention ?

Revenons au post Lobsters à 28 points. Au-delà des détails techniques, l’enthousiasme a deux sources.

L’une est un marqueur temporel. Le même jour, le journal de développement de Zig contenait une autre mise à jour — « New @bitCast Semantics and LLVM Backend Improvements » par Matthew Lugg — qui a généré 16 commentaires à elle seule sur Lobsters. Deux avancées du compilateur Zig en une journée sur la page d’accueil de Lobsters, ce n’est pas la norme dans ce type de communauté. Ce que cela signale, c’est la vitalité de l’écosystème du langage : le compilateur Zig progresse sur plusieurs dimensions en parallèle — rétrogradation d’entiers, sémantique de bitCast, optimisation du backend LLVM, corrections du backend SPIR-V — ce n’est pas un projet qui itère sur un seul chemin.

L’autre source est un signal directionnel. L’existence même du backend SPIR-V dit ceci : les mainteneurs de Zig considèrent qu’un langage de programmation système devrait pouvoir compiler du code GPU. C’est une déclaration selon laquelle le GPU est un territoire légitime de la programmation système — un taux de 49 % ne permet pas encore de dire « nous aussi, on supporte le GPU ».

Cette direction diffère du récit GPU de Rust. La philosophie de sécurité de Rust a une valeur différenciante claire sur GPU — le système de propriété peut prévenir les data races à la compilation, ce qui est un avantage naturel dans le modèle de programmation GPU hautement parallèle. La proposition de valeur de Zig est différente : pas d’allocation implicite, le calcul à la compilation parle le même langage, une gestion explicite du flux de contrôle. Sur GPU, l’absence d’allocation implicite signifie que vous ne déclencherez pas accidentellement un appel à un allocateur de tas inexistant. Comptime signifie que la stratégie de distribution des workgroups, la disposition mémoire, les facteurs de déroulage peuvent être décidés dynamiquement à la compilation en fonction des caractéristiques du GPU — pas besoin de macros, pas besoin de scripts de génération de code.

Lequel est le mieux adapté à la programmation GPU ? Je n’ai pas de position à défendre. Les deux langages en sont à un stade bien trop précoce dans leurs tentatives GPU ; la pauvreté des données ne permet aucune conclusion comparative. Mais du point de vue de la diversité de l’écosystème, avoir deux langages système aux philosophies différentes qui s’attaquent simultanément à SPIR-V, c’est mieux que d’en avoir un seul.

Déclaration d’humilité

L’analyse de cet article se fonde sur le journal de développement de Zig du 26 juin 2026, l’article de blog « Zig and GPUs » d’Ali Cheraghi, les discussions de la communauté Lobsters, ainsi que la documentation publique de la spécification Khronos SPIR-V. Je n’ai pas contribué au compilateur Zig, ni personnellement compilé et exécuté des shaders sous la cible spirv64-vulkan. Les données citées, comme le taux de réussite de 49 % aux tests de comportement, proviennent du témoignage de l’auteur du journal de développement et n’ont pas été vérifiées indépendamment. Les descriptions de l’état actuel de Rust GPU, Circle et Julia GPU se fondent sur les dépôts publics, les discussions communautaires et les articles académiques — l’utilisabilité réelle de chaque projet peut varier significativement selon le cas d’usage. Si vous avez une expérience d’ingénierie directe dans l’un de ces domaines, n’hésitez pas à signaler les limites de cet article.