ADR-050 — Spécification de la cible *-linux-air (phase 1 du programme runtime)
Statut : Accepté (2026-06-28). Companion d’ADR-049
(runtime TCB/TLS — document parent, qui définit le programme en 5 phases) et
d’ADR-046/ADR-047
(libc). Réalise la phase 1 : spec de la cible rustc + squelette air-rt +
preuve de faisabilité build-std. Décision de mise en œuvre sous le chapeau
d’ADR-049 (déjà ratifié) ; gravée en ADR car elle fixe des contrats durables
(triple, mécanique de build, frontière de gate) que les phases 2–5 et tout
contributeur devront connaître. Toute évolution structurante = RFC (ADR-015).
Catégorie : Architecture (chaîne d’outils / cible de compilation).
Contexte
ADR-049 a ratifié l’option (a) : produire la cible *-linux-air complète
(runtime + std) comme un tout cohérent, parce que le TCB/TLS d’Air ne peut
pas être validé en isolation dans un binaire lié à glibc (mêler le TLS d’Air et
celui de glibc-std est unsound, cf. #151). La phase 1 doit poser le socle
matériel de ce programme sans engager encore l’écriture d’asm (TLS/_start/
spawn = phase 2) : il faut une spécification de cible que rustc accepte et la
preuve qu’on peut reconstruire core+alloc pour elle.
Contrainte structurante : le workspace stable racine est pinné en Rust
stable 1.96.0, cibles *-linux-gnu, gardé par la barrière pré-merge (ADR-037)
- CI ARM. Une cible JSON custom reconstruite avec
-Z build-stdexige nightly. Les deux mondes ne peuvent pas cohabiter sous un mêmerust-toolchain.tomlni un même gate.
Décision
D1 — Triples {x86_64,aarch64}-unknown-linux-air, env = "air"
⚠️ AMENDÉ par ADR-076 (2026-07-08). Le champ
envdu spec JSON passe de"air"à"musl"(Air est ABI-musl, cf. D-1.3) et l’identité Air passe parvendor = "air"— pour quelibc/std/ l’écosystème compilent sans patch (chantier B, option A :stdsur la libc d’Air). Le code Air utilise désormaiscfg(target_vendor = "air")au lieu decfg(target_env = "air"). La cible porteos = linux(Air est un userland/libc sur le kernel Linux, pas un kernel — l’ABI syscall reste celle de Linux) etenv = "air": la 3ᵉ saveur de libc Linux, à côté degnuetmusl. Vérifié : rustc dérive bientarget_env="air",target_os="linux",target_family="unix",unix,target_thread_local,target_object_format="elf". Les deux arches (x86_64 ET aarch64) sont définies dès le jour 1 (ADR-014).
D2 — llvm-target découplé du nom Rust (musl)
LLVM ne connaît pas l’env air. Le champ llvm-target vaut donc
{arch}-unknown-linux-musl : un triple LLVM connu, statique, sans hypothèse
glibc ni interpréteur dynamique, pour un codegen éprouvé. Le nom de la cible
Rust (-air) et le llvm-target (-musl) sont des champs indépendants
(pattern standard des cibles custom). Sans impact sur l’ABI syscall (os=linux
gouverne). Provisoire : bascule possible si un jour LLVM connaît air.
D3 — Champs de codegen repris verbatim de la base musl
data-layout, cpu (x86-64), features aarch64 (+v8a,+outline-atomics),
max-atomic-width (64 x86 / 128 aarch64), frame-pointer (aarch64 non-leaf),
stack-probes: inline sont copiés de rustc --print target-spec-json (musl).
Les inventer casserait l’accord avec llvm-target et le codegen. (Principe :
kernel/outil = bible re-présentée, on n’invente pas les invariants bas niveau.)
D4 — Cible statique, panic = abort, TLS activé
dynamic-linking = false, crt-static-default, crt-static-respected (binaire
natif sans glibc/musl, par construction ; modèle de relocation/PIE = D5) ;
panic-strategy = "abort" (cohérent ADR-045 et ADR-049 D9 — pas de dérouleur) ;
has-thread-local = true (requis pour #[thread_local] → errno-in-TCB, D3
d’ADR-049). Linker provisoire rust-lld/gnu-lld (aucune dépendance à un
compilateur C externe pour la cible native) non exercé en phase 1 : on ne
construit que des rlibs (pas d’édition de liens).
D5 — Cible PIE (static-PIE) affirmée ; codegen PIC dès la phase 1
La cible est position-independent : relocation-model = "pic",
position-independent-executables, static-position-independent-executables,
relro-level = "full" (posture de durcissement de la base musl-static, dont on
était parti). Arbitrage BDFL (2026-06-28) : « la cible est PIE » → on aligne la
spec sur la cible réelle dès la phase 1 plutôt que de partir non-PIE.
Conséquence exercée dès la phase 1 : build-std compile core/alloc/air-rt
en PIC (vérifié sur les 2 arches : relocation_model="pic"), donc aucun
re-build à prévoir pour entrer ensuite dans un PIE, et l’ASLR du binaire
principal est préservé (sur-sécuriser d’abord, Principe 5).
Seul morceau différé en phase 2 : le _start/crt0 auto-relocalisant.
Le binaire étant 100 % statique (pas de ld.so), c’est notre démarrage qui
applique les relocations R_*_RELATIVE (ajout de la base de chargement réelle)
avant main. Contrainte de bootstrap à respecter : ce code ne doit référencer
aucune globale non encore relocalisée (modèle rcrt1/startup du std Rust).
PIE n’est pas « reportée » : un PIE est un exécutable, et aucun exécutable
n’existe avant la phase 2 — le premier binaire PIE possible naît avec _start.
D6 — Développement hors-arbre (rt/), workspace + toolchain séparés
Le programme *-linux-air vit dans rt/ : un workspace cargo indépendant,
pinné nightly (rt/rust-toolchain.toml, pin daté pour rejouabilité — esprit
ADR-025), construit avec -Z build-std=core,alloc + -Z json-target-spec
(rt/.cargo/config.toml). Le Cargo.toml racine liste rt dans exclude.
Aucune contamination du workspace stable (vérifié : cargo metadata racine
n’expose pas air-rt, cargo check --workspace stable reste vert).
D7 — rt/ exclu de la barrière stable — carve-out, lane CI nightly différée
rt/ ne peut pas passer la barrière ADR-037 (stable + gnu) : c’est nightly +
cible custom. Il en est donc exclu, ainsi que de la mesure de couverture —
carve-out de même nature que fuzz/ (esprit ADR-030 ;
fuzz/ est déjà hors-workspace, nightly, hors couverture). Une lane CI nightly
dédiée à rt/ (build 2 arches + fmt/clippy) sera branchée quand le tree se
stabilisera (≥ phase 4). L’upstream Tier-3 de la cible n’aura lieu qu’après
une démo qui tourne (phase 4), conformément à la stratégie ratifiée (ADR-049).
D8 — Squelette air-rt aligné sur D1–D9 d’ADR-049
Le crate air-rt (no_std + alloc) expose cinq modules — tcb (D1/D3/D9),
tls (D2), start (D4), env (D5/D6), spawn (D7) — documentés mais sans
asm/unsafe en phase 1 : il compile pour la cible (point d’ancrage), fige la
structure et renvoie chaque zone à sa décision parente. L’asm (registre TLS,
_start, clone3+CLONE_SETTLS) relève de la phase 2, validé sur les 2
arches avec // SAFETY: (ADR-021 ; rappels : x19 réservé LLVM aarch64,
c_char i8/u8 selon arche).
Conséquences
- Programme
*-linux-airdémarré sur une base prouvée, sans toucher au socle stable scellé (couches 0/1) ni à son gate. - Preuve de faisabilité acquise :
compiler_builtins+core+alloc+air-rtse reconstruisent pourx86_64-etaarch64-unknown-linux-air(cross depuis l’hôte x86) ;fmt --checketclippy -D warningsverts surair-rt. - Dette de gate explicite :
rt/n’est pas (encore) gardé automatiquement → discipline de vérification manuelle 2 arches en attendant la lane CI nightly. - Entrée de phase 2 cadrée (TCB/TLS/
_start/spawn) et question ASLR consignée pour ne pas être oubliée.
Alternatives écartées
- Mettre
air-rtdans le workspace stable : casserait la barrière (nightly + cible custom incompatibles avec stable 1.96.0/gnu) ou imposerait de déclasser le gate. Rejeté. - Inventer un
llvm-target = *-linux-air: LLVM ne connaît pasair→ codegen non supporté. Rejeté (D2). - Cible dynamique / liée à une libc système : contraire à la doctrine « zéro glibc/musl, natif par construction » (ADR-046, vision libc). Rejeté.
- Upstreamer la cible (Tier-3) tout de suite : prématuré sans démo qui tourne
ni
stdporté ; on développe hors-arbre d’abord (ADR-049, stratégie de timing).
Questions ouvertes (phases ultérieures)
_startauto-relocalisant (static-PIE) : implémentation des relocationsR_*_RELATIVEau démarrage, avec la contrainte de bootstrap (aucune globale non relocalisée) — phase 2. (La cible PIE est, elle, déjà fixée, D5 ; ce n’est plus un arbitrage mais une tâche d’ingénierie.)- Frontière
air-rt↔air-thread: le primitif de spawn migre-t-il dansair-rt? (question ADR-049 §2) — phase 2/3. - Édition de liens d’un exécutable (
-nostartfiles/-nostdlib, CRT Air,build-std-features = compiler-builtins-mempour fournirmemcpy/memset) — phase 4. - Pin nightly : politique de mise à jour du nightly de
rt/(sécurité vs stabilité) — à instruire quand la lane CI nightly sera branchée.