1 points par GN⁺ 2025-04-14 | 1 commentaires | Partager sur WhatsApp
  • rustc_codegen_clr est un projet qui génère du code Rust vers C et .NET, et se prépare à une présentation à la Rust Week tout en améliorant son taux de réussite aux tests
  • Le taux de réussite des tests Rust core est passé de 92 % il y a deux mois à 95,9 %, et les quelque 65 tests restants semblent avoir des causes similaires
  • Les correctifs récents se sont concentrés sur les intrinsics d’entiers 128 bits, l’arithmétique vérifiée et le subslicing, tandis que la partie .NET peut désormais exécuter 96,3 % des tests Rust core
  • Le projet vise une sortie proche du C99 ou de l’ANSI C et l’usage des API POSIX standard afin de cibler davantage de compilateurs C, et a déjà réussi à exécuter des programmes Rust très simples sur certains compilateurs ANSI C
  • Des optimisations pour réduire la taille des fichiers lors de la génération de gros volumes de code C, ainsi qu’un refactoring de l’IR interne, sont en cours et posent les bases pour élargir l’exécution de Rust à des plateformes anciennes ou spécialisées

Présentation à la Rust Week et état d’avancement du projet

  • Une présentation du projet rustc_codegen_clr est prévue à la Rust Week organisée à Utrecht, aux Pays-Bas
  • La présentation cherche un équilibre entre accessibilité pour les débutants et traitement d’un sujet avancé comme la compilation de Rust vers C
  • Ces derniers mois, le travail a progressé en parallèle sur les correctifs de tests, la compatibilité avec les compilateurs C, les performances et le refactoring interne

Hausse du taux de réussite des tests Rust core

  • Le taux de réussite des tests Rust core est passé de 92 % il y a deux mois à 95,9 %
  • Il reste encore environ 65 tests, mais leurs causes semblent similaires, ce qui réduit relativement le champ des correctifs à apporter
  • La partie .NET bénéficie elle aussi de ces correctifs et peut désormais exécuter 96,3 % des tests Rust core

Correctifs sur les entiers 128 bits et l’arithmétique vérifiée

  • Une grande partie des progrès vient des correctifs sur les intrinsics 128 bits, l’arithmétique vérifiée et le subslicing
  • En C, il existe trois intrinsics popcount : __builtin_popcount, __builtin_popcountl et __builtin_popcountll
    • __builtin_popcountll, contrairement à ce que son nom laisse penser, fonctionne sur unsigned long long et non sur __int128_t
    • Sur x86_64 Linux avec GCC, unsigned long et unsigned long long font tous deux 64 bits
  • L’implémentation précédente tronquait silencieusement les entiers 128 bits à 64 bits dans l’intrinsic de comptage de bits, produisant ainsi des résultats erronés
  • Le popcount 128 bits est émulé en comptant séparément les bits à 1 des 64 bits de poids faible et des 64 bits de poids fort, puis en additionnant les deux résultats
  • La vérification d’overflow pour la multiplication 128 bits est traitée de manière simple, faute d’avoir trouvé une méthode plus efficace
    • si b n’est pas 0 et que (a * b) / b == a, alors on considère qu’il n’y a pas d’overflow
    • ce n’est pas une méthode révolutionnaire, mais elle suffit à faire passer quelques tests supplémentaires

Bug de subslicing et intrinsics de fallback

  • Le bug de subslicing venait d’un oubli de sizeof, qui décalait le pointeur de données d’un slice en octets au lieu de le faire par élément
  • Ce bug ne cassait le comportement que lors d’un subslicing depuis la fin du slice et non depuis le début, une forme surtout utilisée dans le pattern matching, ce qui explique qu’il soit resté longtemps inaperçu
  • Pour les byte slices et les string slices, la taille d’un octet et d’une unité de code UTF-8 étant de 1 octet, les tests internes passaient, et le problème n’est apparu que dans la suite complète de tests du compilateur Rust
  • Certains intrinsics peuvent utiliser directement l’implémentation de fallback du compilateur Rust au lieu d’être réimplémentés
  • carrying_mul_add doit effectuer la multiplication sur un entier deux fois plus large que les entrées
    • jusqu’à 64 bits, c’est gérable, mais avec des entrées 128 bits il faut des entiers 256 bits
    • LLVM prend en charge les entiers 256 bits, contrairement à C et à .NET
  • L’implémentation de fallback du compilateur Rust réalise une multiplication et une addition 256 bits à l’aide d’entiers 128 bits, ce qui peut servir de référence pour émuler des opérations 128 bits avec seulement des entiers 64 bits

Prise en charge de davantage de compilateurs C

  • Le projet cherche à accroître les chances que du code Rust puisse fonctionner avec davantage de compilateurs C spécialisés
  • Il existe déjà des exemples où du code Rust s’exécute sur Game Boy, se compile avec de simples commandes move, ou tourne avec le Holly C de Temple OS
  • Il n’est pas possible de prendre directement en charge des compilateurs C propriétaires inaccessibles, mais élargir la compatibilité avec des compilateurs C obscurs augmente les chances que du code Rust puisse aussi tourner dans ces environnements
  • Beaucoup de plateformes ne sont pas prises en charge faute de documentation ou d’accessibilité
  • Dans le cadre des discussions sur l’écriture d’une partie de Git en Rust, des plateformes propriétaires comme NonStop ne prennent en charge ni Rust, ni LLVM, ni GCC, ce qui peut entraîner une dégradation voire une disparition du support de Git
  • Compiler Rust vers C signifie qu’en théorie, Rust pourrait tourner partout où il existe un compilateur C
    • il reste toutefois incertain de savoir si tous les problèmes pourront être contournés sur toutes les plateformes
    • obtenir légalement un compilateur pour ces plateformes aurait nécessité l’achat d’un serveur, bien au-delà du budget disponible
    • il semble donc peu probable que Rust y fonctionne prochainement

Stratégie actuelle de sortie C

  • Le plan actuel consiste à générer du code aussi proche que possible d’un C99 conforme au standard ou de l’ANSI C
  • Une certaine prise en charge du threading est nécessaire pour l’initialisation thread-local, d’où l’orientation vers l’usage exclusif des API POSIX standard
  • Le projet dispose de ses propres implémentations de fallback pour certains intrinsics, et cette liste s’allonge progressivement
  • L’exécution de programmes Rust très simples a déjà réussi sur certains compilateurs ANSI C
  • L’objectif est d’atteindre un état où même les plateformes difficiles à prendre en charge aujourd’hui pourront être ajoutées assez facilement si le besoin se présente

Petites améliorations de performances et de taille de fichiers

  • Pour l’affichage des littéraux entiers, il a été pris en compte que les entiers inférieurs à 2^32 sont aussi courts, voire plus courts, en décimal qu’en hexadécimal
    • 255 est plus court d’un octet que 0xFF
    • 65536 est aussi plus court que 0xFFFF
    • à cause du préfixe 0x, la représentation hexadécimale ne devient pas plus compacte avant 2^32
  • Dans le cas extrême de la conversion du compilateur Rust complet en C, des fichiers source C allant jusqu’à 1 Go ont déjà été générés, si bien qu’une petite réduction relative de la taille a un impact réel
  • La directive #line, utilisée pour insérer les debug info, a également été rendue plus intelligente
    • le nom du fichier source n’est inclus que lorsqu’il change
    • cela peut réduire fortement la taille des fichiers lors de l’utilisation des debug info

Refactoring interne et nettoyage de l’IR

  • Certaines fonctionnalités internes de rustc_codegen_clr ont été séparées dans une crate distincte afin d’accélérer les builds incrémentaux
  • Une migration vers un IR interné plus économe en mémoire est également en cours
  • L’ancien IR contenait des rvalue/lvalue atypiques qui se mappaient mal vers C, et les problèmes devenaient plus marqués avec des fonctionnalités Rust comme les types de taille dynamique
  • Par exemple, avec un type dynamique personnalisé comme MyStr, &self.s paraît simple mais implique en réalité la gestion des métadonnées d’un fat pointer
    • en C99, un compound literal permet de créer un struct FatPtr_str sur une seule ligne
    • en ANSI C, il faut placer séparément data et meta dans des variables temporaires avant de retourner le résultat
  • Comme une seule ligne de Rust et de MIR peut se transformer en plusieurs lignes de C, l’ancien IR gérait cela au moyen de scopes internes avec des variables locales temporaires et des sous-instructions
  • La nouvelle approche rend l’étape de préparation plus complexe, mais simplifie l’IR dans son ensemble ; une fois les derniers cas difficiles résolus, la fonctionnalité correspondante de l’ancien IR pourra être supprimée

Prochaines étapes

  • Le travail sur le projet dure depuis environ 1,5 an, et plus le nombre de bugs diminue, plus il faut de temps pour trouver ceux qui restent
  • Un deuxième volet de “Rust panics under the hood” est en préparation afin d’expliquer pas à pas le fonctionnement des panic en Rust
  • La seule explication de la génération des panic messages représente déjà dix minutes de contenu, ce qui amène à envisager de scinder l’article en deux
  • Un profileur mémoire petit et précis pour Rust est également en cours de développement
    • le code représente environ 2K LOC
    • le calendrier est serré, mais l’auteur espère écrire à ce sujet dans les prochaines semaines

1 commentaires

 
GN⁺ 2025-04-14
Avis sur Hacker News
  • J’attends toujours qu’un des front-ends ou back-ends Rust alternatifs permette de bootstrapper Rust sur alpha, hppa, m68k, sh4
    À l’origine, le projet rustc_codegen_gcc l’avait promis, mais il n’a finalement jamais tenu cette promesse
    • Je me demande si ces quatre plateformes sont toutes réellement utilisées, ou si c’est juste un critère arbitraire pour considérer que la prise en charge des plateformes est complète
    • m68k-unknown-linux-gnu n’a-t-il pas été fusionné comme cible Tier-3 de Rust ? [0]
      [0] https://github.com/rust-lang/compiler-team/issues/458
    • Ont-ils abandonné cet objectif ? La dernière fois que j’en ai entendu parler, c’était encore en développement
    • Rust ne prend même pas encore correctement en charge OpenBSD on x86_64
  • fractalfir est vraiment quelqu’un de talentueux. Je le vois souvent sur le Reddit Rust ; je ne m’y connais pas tant que ça en compilateurs, mais les autres semblent beaucoup apprécier son travail
    • Je crois qu’il est assez jeune. J’espère qu’il a un bel avenir devant lui
  • Dans « la plupart des composants de std fonctionnent à environ 95 % sur .NET et 80 % en C », le titre HN omet que le taux de réussite de 95 % ne concerne que .NET
    Côté GCC/Clang, ce n’est « que » 80 %
    • Le README était obsolète. Ces chiffres dataient du début de l’année ; maintenant, c’est plutôt ceci
      | .NET Core tests | 1764 | 48 | 20 | 96.29% |
      | C Core tests | 1712 | 71 | 8 | 95.59% |
  • Il y a beaucoup d’usages intéressants à imaginer avec ça. Le premier qui me vient à l’esprit est une meilleure interopérabilité avec d’autres langages comme Python
    • Avec PyO3, l’interopérabilité est déjà excellente. L’exception, c’est quand les gens veulent compiler la partie Rust depuis les sources et trouvent pénible d’installer le compilateur Rust
      Ce hack est un back-end de compilateur Rust. Comme un back-end prend en entrée des instructions propres à la plateforme, le code C non trivial généré ne sera pas portable
      Les utilisateurs devront recevoir des sources spécifiques à leur plateforme déjà générées, ou installer le compilateur Rust et ce back-end pour les générer eux-mêmes
    • Quel avantage cela apporte-t-il qu’on ne puisse pas déjà obtenir avec des fonctions Rust extern "c" ?
  • Est-ce que ça passe de LLVM IR vers C ? Ou de l’AST Rust vers C ?
    • J’ai trouvé la réponse dans le README du projet
      « Ma représentation IR .NET se mappe bien vers C, ce qui m’a permis d’ajouter la prise en charge de la compilation de Rust vers C avec 2 à 3 milliers de lignes de code. Je réutilise presque toute la base de code ; le code spécifique à C et à .NET n’existe qu’à la toute dernière étape de la compilation »
    • C’est un back-end rustc, donc une alternative aux back-ends LLVM, GCC et Cranelift
      Au départ, c’était un back-end .NET, mais il s’est avéré que l’approche pouvait aussi facilement prendre en charge la génération de code C, donc cela a été ajouté. Ce que rustc lui transmet est converti en une représentation intermédiaire (IR) propre au projet, puis traité
  • Le compilateur Nim vers C a un taux de réussite aux tests de 100 %
  • Mais est-ce que ça conserve aussi les garanties propres à Rust ?
    • S’il n’y a pas de bug dans la transformation elle-même, il n’y a pas de raison que non. Les garanties statiques ont déjà été vérifiées et existent déjà, puisqu’on part du principe qu’on transforme du code Rust qui compile déjà avec le compilateur Rust normal
      Les garanties dynamiques comme les vérifications de bornes peuvent aussi être implémentées sans problème dans le runtime C
    • Pourquoi est-ce que ça ne marcherait pas ?
  • Très chouette. L’inverse, passer de C vers Rust, serait aussi excellent
    • Pour un programme non trivial, il faudrait probablement tout mettre dans un seul gros bloc unsafe
      C ne transmet pas toutes les informations qu’il faudrait connaître explicitement pour que ça compile en Rust
    • Mark Russinovich a récemment donné une présentation à une conférence Rust au Royaume-Uni où il a évoqué une tentative à grande échelle de conversion C→Rust en interne chez Microsoft
      https://www.youtube.com/watch?v=1VgptLwP588
    • De tels outils existent déjà. Le problème, c’est qu’ils utilisent beaucoup de blocs unsafe et que le code obtenu n’est généralement pas très idiomatique en Rust
      Par exemple, transformer une machine à états basée sur des variables globales en une machine à états plus idiomatique en Rust, avec quelque chose comme des énumérations nommées, serait très difficile
      Avec l’aide d’une IA suffisamment puissante, ce serait peut-être possible, mais l’IA est encore loin de réellement faire ce qu’on attend d’elle, donc il est difficile de dire que c’est prêt. Il faudrait aussi assez de mémoire pour faire tenir l’ensemble des bases de code C et Rust dans la fenêtre de contexte ; dès que le code dépasse une certaine taille, il faudrait vite du matériel très coûteux. Sinon, comme avec beaucoup de LLM d’aide au code, on finit par générer indépendamment du code incompatible d’un morceau à l’autre
      Cela dit, si vous voulez étendre un projet C avec Rust ou le réécrire progressivement, https://c2rust.com/ est utilisable dès maintenant
    • Voir https://github.com/immunant/c2rust
    • Quel avantage espère-t-on en tirer ?
  • Rust vers C ? Pourquoi faire ça. Autant écrire directement du C. Si on comprend Rust, on peut sûrement comprendre et utiliser C efficacement
    • Pour obtenir à la fois les avantages de Rust et la large compatibilité ainsi que la prise en charge des architectures de gcc et des compilateurs C en général
      Rust est un langage moderne avec une gestion de paquets, des outils intégrés de build/test simplifiés, beaucoup moins de fardeau historique, et des fonctionnalités et une syntaxe de haut niveau que les gens apprécient vraiment
      C est aussi propre, mais les bases de code complexes bénéficient d’un langage moderne qui aide à créer des abstractions robustes tout en conservant la vitesse du C. Sans oublier bien sûr le borrow checker et la sécurité mémoire
    • Pour bénéficier des avantages de Rust sur des plateformes que rustc ne prend pas en charge. C’est assez évident
    • Il y a sans doute aussi une part de « parce qu’on peut le faire », mais c’est aussi parce qu’il existe beaucoup de plateformes que Rust ne prend pas en charge
      Un transpileur Rust→C qui génère du code C suffisamment standard pourrait combler cet écart