- Wild est un linker qui vise des liens très rapides en développement itératif ; le lien incrémental n’est pas encore implémenté, mais même en lien non incrémental il est déjà rapide
- Le point de départ de Wild est que mold, un linker rapide existant, ne fait pas de lien incrémental et n’en prévoit pas ; Wild est écrit en Rust avec l’objectif de gérer la complexité du lien incrémental
- Les plateformes actuellement prises en charge sont x86-64, ARM64, RISC-V, LoongArch64 et PPC64LE sous Linux ; il prend en charge les binaires statiques, static-PIE, les binaires liés dynamiquement, les objets partagés, les proc-macros Rust, les informations de débogage, etc.
- Il peut être utilisé comme drop-in replacement, appelé comme un linker existant depuis GCC ou Clang, et s’intègre à Cargo, aux systèmes de build C/C++ et à wild-action pour la CI
- Le lien incrémental, les linker scripts plus complexes, Mach-O et la prise en charge de Windows ne sont pas encore implémentés ; pour vérifier qu’un binaire a bien été lié avec Wild, il faut rechercher la chaîne
Linker: Wild version... avec readelf ou strings
Objectifs et positionnement de Wild
- Wild est un linker qui vise des liens très rapides en développement itératif
- Le plan à long terme est d’implémenter le lien incrémental, mais ce n’est pas encore le cas aujourd’hui
- Le projet indique que, même sans lien incrémental, ses performances en lien non incrémental sont déjà rapides
- mold est déjà très rapide, mais ne fait pas de lien incrémental, et son auteur a indiqué ne pas prévoir de l’ajouter ; c’est la raison pour laquelle Wild a été créé comme linker distinct
- Wild est écrit en Rust, avec l’espoir de pouvoir gérer en Rust la complexité du lien incrémental
Installation
- Les tarballs de release sont disponibles sur la page des releases ; il suffit de les décompresser et de copier le binaire
wild vers un emplacement présent dans le PATH
- Si
cargo-binstall est disponible, l’installation peut se faire avec la commande suivante
cargo binstall wild-linker
- La commande d’installation Homebrew est la suivante
brew install wild-linker/wild/wild
- Pour compiler la dernière release publiée sur crates.io, utilisez la commande suivante
cargo install --locked wild-linker
- Pour installer le dernier code Git non publié, utilisez la commande suivante
cargo install --locked --bin wild --git https://github.com/wild-linker/wild.git wild-linker
- La version stable de Wild dans Nixpkgs peut être utilisée via
pkgs.useWildLinker pkgs.stdenv
- Pour la dernière révision Git instable, il faut consulter le document
nix/nix.md
Utilisation comme linker par défaut
- Wild est conçu comme un drop-in replacement et peut être utilisé de la même manière que les linkers appelés par GCC ou Clang
- Les options sont les suivantes
- L’option propre à Clang
--ld-path=wild
-fuse-ld=wild avec GCC 16.1 ou supérieur et Clang
- Clang nécessite un binaire ou un lien symbolique
ld.wild
- L’option généralement prise en charge
-B <path>
<path> est le répertoire contenant un ld qui pointe vers wild
-
Rust et Cargo
- Dans
~/.cargo/config.toml, il est possible d’utiliser Clang avec --ld-path=wild
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=--ld-path=wild"]
- Il est aussi possible d’utiliser
-fuse-ld=wild
[target.x86_64-unknown-linux-gnu]
# linker = "clang" # Uncomment this line if your GCC is older than version 16.
rustflags = ["-Clink-arg=-fuse-ld=wild"]
-
Systèmes de build C/C++
- Avec autotools, CMake, meson, etc., définir
LDFLAGS suffit généralement
export LDFLAGS="${LDFLAGS} -fuse-ld=wild"
- Avec les anciennes versions de GCC, il est possible de créer un lien symbolique
ld pointant vers wild et de passer ce répertoire avec -B
ln -s /usr/bin/wild /tmp/ld
export CFLAGS="${CFLAGS} -B/tmp"
export CXXFLAGS="${CXXFLAGS} -B/tmp"
export LDFLAGS="${LDFLAGS} -B/tmp"
- Comme ces systèmes de build peuvent être complexes, il est recommandé de vérifier avec
readelf que Wild a bien été utilisé pour lier le binaire final
-
Configuration Cargo pour Illumos
- Sous Illumos, il faut indiquer le chemin absolu de Clang et passer également Wild avec un chemin absolu
- Si le chemin absolu de Wild n’est pas fourni, l’appel peut être délégué silencieusement à GNU ld ou à Sun ld
[target.x86_64-unknown-illumos]
# Absolute path to clang - on OmniOS this is likely something like /opt/ooce/bin/clang.
linker = "/usr/bin/clang"
rustflags = [
# Will silently delegate to GNU ld or Sun ld unless the absolute path to Wild is provided.
"-Clink-arg=-fuse-ld=/absolute/path/to/wild"
]
État de la prise en charge et éléments non pris en charge
- Les plateformes et architectures actuellement prises en charge sont les suivantes
-
x86-64 sous Linux
-
ARM64 sous Linux
-
RISC-V(riscv64gc) sous Linux
- LoongArch64 sous Linux
- Prise en charge initiale
- PPC64LE sous Linux
- Prise en charge initiale
- Les fonctionnalités opérationnelles sont les suivantes, avec la réserve qu’il peut encore y avoir des bugs
- Sortie de binaires non relocalisables liés statiquement
- Sortie de binaires statiques indépendants de la position, c’est-à-dire static-PIE
- Sortie de binaires liés dynamiquement
- Sortie de fichiers d’objets partagés
.so
- Proc-macros Rust liées avec Wild
- Réussite des tests pour la plupart des crates les plus téléchargées sur crates.io
- Informations de débogage
- Prise en charge de GNU jobserver
- Prise en charge partielle des linker scripts
- Pour plus de détails, consulter la matrice de prise en charge des linker scripts
- Linker plugin LTO
- Il existe des problèmes connus
- Les grands éléments qui ne sont pas encore implémentés sont les suivants, dans un ordre proche de leur priorité actuelle
-
Lien incrémental
- Linker scripts plus complexes
- Prise en charge de Mach-O
- Prise en charge de Windows
Vérifier si Wild est utilisé
readelf peut être installé via le paquet binutils ; la commande suivante permet de consulter les chaînes de la section .comment
readelf --string-dump .comment my-executable
- Si une ligne comme celle-ci est présente, le binaire a été lié avec Wild
Linker: Wild version 0.1.0
strings est également disponible via le paquet binutils, et la vérification peut aussi se faire avec la commande suivante
strings my-executable | grep 'Linker:'
Benchmarks et cibles de test
- L’objectif de Wild est de devenir à terme un linker très rapide grâce au lien incrémental
- Le projet vise aussi à rendre aussi rapides que possible le lien non incrémental et le lien initial lorsque le lien incrémental est activé
- Tous les benchmarks sont exécutés avec une sortie sur tmpfs
- La méthode d’exécution des benchmarks est décrite dans BENCHMARKING.md
- Les systèmes cibles des benchmarks sont les suivants
- Les exemples de benchmarks incluent le temps de link et l’utilisation mémoire pour Chromium, le temps de link de
librustc-driver, le temps de link de Wild lui-même, ainsi que le temps de link de rust-analyzer sur Raspberry Pi 5
Exemple de link de code Rust et informations sur le projet
- Voici un exemple de build et de test d’une crate avec Wild dans
cargo test
- Cette commande a été exécutée avec succès sur plusieurs crates populaires comme
ripgrep, serde, tokio, rand et bitflags
- Le binaire
wild doit être dans le PATH, et Clang doit être installé car GCC n’autorise pas l’utilisation d’un linker arbitraire
RUSTFLAGS="-Clinker=clang -Clink-args=--ld-path=wild" cargo test
- Si un lien symbolique
ld.wild pointe vers wild, la méthode suivante est également possible
RUSTFLAGS="-Clinker=clang -Clink-args=-fuse-ld=wild" cargo test
- Pour utiliser Wild comme linker de code Rust en CI, consulter wild-action
- Les informations de contribution se trouvent dans CONTRIBUTING.md, et la vue d’ensemble de conception haut niveau dans DESIGN.md
- Les échanges autour de Wild ont lieu sur le serveur Zulip
- Le blog de David contient de nombreux articles couvrant différents aspects du linker Wild
- La licence est au choix Apache License 2.0 ou MIT license
1 commentaires
Avis sur Hacker News
Après le changement de licence de mold de l’AGPL vers MIT (dans le cadre de la sortie de mold 2.0), je pensais que le besoin de créer un autre linker rapide avait fortement diminué à l’échelle mondiale, donc je ne m’attendais pas à voir arriver un tel projet
En plus, le fait qu’il soit déjà 2 fois plus rapide que mold dans certains cas est encore plus inattendu ; je compte suivre son évolution et je souhaite bonne chance à l’auteur
Le linker de Microsoft fait du linking incrémental par défaut depuis des décennies ; c’est un peu embarrassant que Linux n’ait toujours pas de linker incrémental prêt pour la production
À en juger par les issues GitHub, l’auteur a d’abord essayé de la prendre en charge, puis a déplacé le support Windows vers le linker sold ; mais sold a récemment été archivé, donc je ne sais pas s’il n’y a finalement pas de support Windows, ou si j’ai mal compris la chronologie
J’y avais jeté un œil, mais je ne sais toujours pas si c’est prêt pour la production
D’après le README, on dirait que non, donc je continue à utiliser mold
Si vous êtes utilisateur de macOS, Apple a sorti un nouveau linker il y a environ 1 ou 2 ans, et l’auteur de mold a donc arrêté de travailler sur la version macOS
Pour l’utiliser avec Rust, il faut mettre ceci dans
config.toml[target.aarch64-apple-darwin]rustflags = ["-C","link-arg=-fuse-ld=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld","-C","link-arg=-ld_new",]J’ai installé les outils en ligne de commande, mais je n’ai que
/usr/bin/ldet/usr/bin/ld-classicCe serait rafraîchissant si les compilateurs C/C++ supprimaient l’étape intermédiaire du linking et construisaient tout le programme comme une seule unité
Si le compilateur pouvait voir tout le programme dès le départ, LTO lui-même ne serait plus nécessaire
Pour les builds incrémentaux, il faudrait quand même stocker certains artefacts de build, mais pas sous forme de fichiers objets : il faudrait des métadonnées indiquant l’origine du code généré et ses dépendances, afin de pouvoir remplacer uniquement les bonnes parties
De nos jours, les bibliothèques externes sont généralement liées dynamiquement, donc il n’est pas nécessaire de les construire depuis les sources, et supprimer le linker ne poserait pas non plus de problème pour les dépendances propriétaires
Si cela ne suffit toujours pas, le compilateur pourrait aussi accepter des fichiers objets, afin de gérer les cas legacy ou particuliers qui doivent être liés statiquement dans le binaire
SQLite3 concatène tout pour en faire une seule unité de compilation
Plus de gens utilisent probablement déjà cette approche qu’on ne le pense
https://sqlite.org/amalgamation.html
L’intérêt pour les linkers rapides a fortement augmenté à cause de l’adoption et de la popularité de Rust
Même un binaire Rust lié statiquement de taille raisonnable peut prendre plusieurs minutes à l’étape de linking d’une compilation en mode release (avec mold)
Ce n’est pas un problème propre à Rust : c’est plutôt le résultat de la combinaison d’un linking statique strict, d’optimisations avancées au moment du linking comme LTO et BOLT fournies par LLVM, et des plaintes de la communauté Rust sur les temps de compilation
En raison de son lien fort avec LLVM, et en pratique de sa dépendance envers lui, Rust est devenu le langage où la magie de LLVM au moment du linking est la plus largement adoptée ; on peut rencontrer le même problème en C++, mais dans ce cas on l’imputera probablement davantage à la chaîne d’outils qu’au langage
J’ai suivi wild pendant un moment parce qu’il pourrait devenir un linker incrémental optimisant, mais honnêtement, tant qu’il ne peut pas réellement faire de linking incrémental, je n’ai aucune motivation à l’essayer
De plus, l’écosystème C/C++ accepte culturellement les bibliothèques binaires ; dans bien des cas, quand on clone un dépôt ou qu’on change de branche de développement, on n’a pas à recompiler le monde entier, seulement sa propre application
Si le shell applicatif externe appelle la logique métier en Wasm, il suffit de recompiler la logique interne, et le shell de l’application externe n’a même pas besoin de redémarrer
2008 : gold, un nouveau linker conçu pour être plus rapide que GNU ld
Vers 2015 : lld, un linker de remplacement drop-in au moins 2 fois plus rapide que gold
2021 : mold, un nouveau linker plusieurs fois plus rapide que lld
2025 : wild, un nouveau linker...
À ce sujet, il y a un vieux mais bon livre de John Levine, Linkers and Loaders
C’est le dernier livre de la liste ci-dessous
https://www.johnlevine.com/books.phtml
Je l’ai lu il y a quelques années, il était assez intéressant, et c’est le livre de référence du domaine
Comme on le voit sur le lien, il a aussi écrit d’autres livres d’informatique populaires
Ça a l’air assez prometteur
C’est écrit en Rust dès le départ, et l’objectif est d’être rapide tout en prenant en charge le linking incrémental
Pour l’utiliser avec Rust, il devrait probablement être possible d’utiliser gcc comme pilote de linker
Dans le
.cargo/config.tomldu projet :[target.x86_64-unknown-linux-gnu]rustflags = ["-C", "link-arg=-fuse-ld=wild"]À part ça, pourquoi Rust doit-il s’accrocher ici à gcc ou clang ? Il manque une fonctionnalité ?
Malheureusement, gcc n’accepte pas de linker arbitraire via le flag
-fuse-ld=Les linkers acceptés sont seulement bfd, gold, lld et mold
On peut faire en sorte que gcc appelle wild comme linker, mais pour l’instant il faut créer un répertoire contenant le linker wild, renommer le binaire ou le lien symbolique en
"ld", puis passer-B/path/to/directory/containing/wildà gccLa raison pour laquelle Rust n’appelle pas directement le linker mais passe par gcc ou clang, c’est que le compilateur C connaît les flags de linking nécessaires, sur la plateforme actuelle, pour lier avec libc et le runtime C
Par exemple des éléments comme
Scrt1.o,crti.o,crtbeginS.o,crtendS.o,crtn.oParce que le compilateur Rust génère du bytecode IR, pas du code machine
Je me demande quelle pourrait être la raison théorique pour laquelle, dans le cas non incrémental, il serait plus rapide que mold ?
« Parce que c’est en Rust » est une bonne explication pour pas mal de choses, mais ça n’explique pas l’avantage de performance attendu
Si c’est « parce qu’il existe des opportunités de parallélisation faciles que Rust rend simples à exploiter », ce serait intéressant, mais ce n’est ni explicitement dit ni même sous-entendu
Un indice, c’est que j’ai entendu dire que Mold devient environ 10 % plus rapide quand il utilise l’allocateur plus rapide mimalloc
J’ai essayé mimalloc avec Wild, mais je n’ai constaté aucun gain de vitesse mesurable
Ça suggère que Mold utilise davantage l’allocateur que Wild, et dans Wild on a clairement fait des efforts pour optimiser le nombre d’allocations sur le tas
Globalement, je pense que c’est une différence de choix de conception
Quant au rapport avec Rust, je suis convaincu que si l’on portait Wild de Rust vers C ou C++, les performances seraient à peu près les mêmes
En revanche, grâce au borrow checker, certains patrons de code acceptables en Rust pourraient devenir des pièges en C ou C++, ce qui compliquerait la maintenance
À l’époque où je codais en C++, j’écrivais de façon plus défensive, même au prix d’un petit coût en performances ; avec Rust, sachant que le compilateur me couvre, je peux être beaucoup plus audacieux
Amusant comme coïncidence
Il y a une heure, j’ai comparé les performances de wild, mold et ld sur un projet C sur lequel je travaille
23 000 lignes, 172 fichiers ; avec
gcc+ld, le temps utilisateur était d’environ 23,4 s, avecgcc+mold22,5 s, et avecgcc+wild21,8 sJ’en conclus que, sur un projet bien structuré, le temps de linking n’est peut-être pas un si gros problème
Dans ce cas, l’essentiel du temps est passé à compiler le code, pas à linker
L’intérêt d’un linker rapide est maximal dans le développement itératif
Autrement dit, après une petite modification du code puis une recompilation et l’exécution du résultat, le compilateur n’a généralement presque rien à faire, mais le linking est toujours refait depuis le début et devient donc facilement le temps dominant
ld.lld, ça donne quoi ?« Ce benchmark a été exécuté sur l’ordinateur portable de David Lattimore, un System76 Lemur Pro de 2020, avec 4 cœurs (8 threads) et 42 Go de RAM »
https://news.ycombinator.com/item?id=33330499
Pour être clair, je ne suis pas en train de dire que wild est boursouflé
S’il y a un problème, il se situe plutôt du côté des logiciels développés avec, et des ordinateurs des personnes qui utiliseront ces logiciels
https://news.ycombinator.com/item?id=42896619
« ... la RAM est de 16 Go, et elle ne peut pas être mise à niveau... »
À moitié pour plaisanter, mais j’imagine que quelqu’un qui code en Rust a déjà 32 Go de RAM
Personnellement, sacrifier littéralement tout le reste pour passer mon portable à 64 Go a été une décision presque excellente
Je dis presque, parce que j’aurais dû mettre de l’argent à la fois dans la RAM et dans l’écran, au lieu de tout miser sur la RAM
Le seul inconvénient, c’est que faire le ménage dans les onglets ouverts une fois par semaine est devenu une tâche qui prend toute une soirée