Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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-std exige nightly. Les deux mondes ne peuvent pas cohabiter sous un même rust-toolchain.toml ni 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 env du spec JSON passe de "air" à "musl" (Air est ABI-musl, cf. D-1.3) et l’identité Air passe par vendor = "air" — pour que libc/std/ l’écosystème compilent sans patch (chantier B, option A : std sur la libc d’Air). Le code Air utilise désormais cfg(target_vendor = "air") au lieu de cfg(target_env = "air"). La cible porte os = linux (Air est un userland/libc sur le kernel Linux, pas un kernel — l’ABI syscall reste celle de Linux) et env = "air" : la 3ᵉ saveur de libc Linux, à côté de gnu et musl. Vérifié : rustc dérive bien target_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-air dé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-rt se reconstruisent pour x86_64- et aarch64-unknown-linux-air (cross depuis l’hôte x86) ; fmt --check et clippy -D warnings verts sur air-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-rt dans 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 pas air → 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 std porté ; on développe hors-arbre d’abord (ADR-049, stratégie de timing).

Questions ouvertes (phases ultérieures)

  1. _start auto-relocalisant (static-PIE) : implémentation des relocations R_*_RELATIVE au 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.)
  2. Frontière air-rtair-thread : le primitif de spawn migre-t-il dans air-rt ? (question ADR-049 §2) — phase 2/3.
  3. Édition de liens d’un exécutable (-nostartfiles/-nostdlib, CRT Air, build-std-features = compiler-builtins-mem pour fournir memcpy/memset) — phase 4.
  4. Pin nightly : politique de mise à jour du nightly de rt/ (sécurité vs stabilité) — à instruire quand la lane CI nightly sera branchée.