ADR-049 — Runtime Air : TCB, TLS, démarrage (crt0), errno, environ — fondation *-linux-air
Statut : Accepté (2026-06-28, ratifié par le BDFL ; option (a) *-linux-air complète, cf. amendement en fin). Document fondateur du runtime userland d’Air.
⚠️ CORRECTION D’ARCHITECTURE (2026-06-29, ADR-052). Le placement d’
air-rtposé par cet ADR est OBSOLÈTE : D8 (« nouveau crate runtime reposant sur la couche 0 ») est faux au regard de la doctrine de layering d’Air — seule la couche 1 consomme la couche 0.air-rtest en réalité un objet couche 1 (AirRuntime) composé de briques couche 1 (ThreadControlBlock,ThreadLocalStorage, outils de relocation). Voir ADR-052, qui fait autorité. Le fond technique des décisions D1–D9 (TCB, TLS Variant I/II,crt0,errno-in-TCB, self-relocation static-PIE, spawnstd-free,panic=abort,no_std) reste valide ; seul l’étage (couche 0 → couche 1) et le consommateur des syscalls changent. Companion d’ADR-046/ADR-047 (libc) et d’ADR-048 (couche 0 std-free). Réalise l’étape « produire TCB/TLS » de la feuille de route BDFL (couche 1 → TCB/TLS → libc). Aboutissement des bloqueurs convergents : spawn std-free (volet 2 air-thread),environ/OsString(air-process/filesystem/config), état par-thread de la libc.
Catégorie : Architecture (fondateur, runtime). Toute évolution structurante = RFC (ADR-015).
Contexte
Plusieurs chantiers ont buté sur le même socle manquant, et tous le désignent :
air-threadspawn std-free (volet 2, #151) : faire tourner une closure Rust dans un thread spawné exige un TCB correct (errno, canari%fs:0x28, DTV, état par-thread) — impossible via l’ABI interne glibc.air-process/air-filesystem/air-configno_std (#153) : besoin deenviron(capturé au démarrage) et d’un équivalentOsString.- La libc (ADR-046/047) :
errnothread-local, état par-thread, démarrage des programmes C.
Le déblocage commun = le runtime userland d’Air : notre TCB, notre TLS, notre crt0/démarrage. C’est la
fondation de la cible *-linux-air, construite par nous (pas d’ABI glibc).
Décision (proposée — à arbitrer/ratifier)
D1 — TCB propre à Air (au registre TLS)
Air définit son Thread Control Block placé au registre TLS (%fs x86_64 / tpidr_el0 aarch64). Champs :
self-pointer, errno (le canal POSIX, thread-local, ADR-046 D4), canari de pile, DTV (Dynamic Thread
Vector), métadonnées du thread (tid, base/taille de pile, mot futex de join), crochet d’état par-thread (futur
cache malloc). Aucune compat ABI glibc : on possède le layout.
D2 — TLS ELF standard
Variant II (x86_64) / Variant I (aarch64). Image .tdata/.tbss copiée/zéroée à la création de thread ;
#[thread_local] résout via le registre TLS. Mise en place : crt0 (thread principal) / clone3 + CLONE_SETTLS
(threads spawnés, via air-thread).
D3 — errno dans le TCB
errno = champ du TCB, thread-local, lu/écrit par la libc via le registre TLS. Zéro globale (ADR-046 D4).
D4 — Démarrage (crt0/_start)
L’entrée capture argc/argv/envp/auxv depuis la pile initiale (ABI System V), met en place le TCB/TLS du
thread principal, exécute l’init, appelle l’entrée du programme. auxv (AT_RANDOM pour le canari, AT_PAGESZ, AT_HWCAP…).
D5 — Accès environ
Le envp capturé en D4 est exposé par une API runtime (équivalent getenv/environ) → débloque le no_std
d’air-process/air-filesystem/air-config.
D6 — Type chaîne-OS (octets)
Type octets possédé (réutiliser air-base-lib::AirPath/un newtype) en remplacement d’OsString dans les API couche 1
concernées (air-config) — cohérent avec la doctrine « chemins/env = octets ».
D7 — Spawn std-free (déblocage volet 2 air-thread)
air-thread::spawn : alloue pile + image TLS + TCB, câble CLONE_SETTLS, l’entrée enfant initialise son contexte
(registre TLS → son TCB) puis exécute la closure, nettoie (munmap), exit ; join via CHILD_CLEARTID/futex (couche 0).
D8 — Emplacement & dépendances ⚠️ (corrigé par ADR-052)
Nouveau crate runtime ( → OBSOLÈTE. air-rt ?) reposant sur la couche 0air-rt
est un objet couche 1 (AirRuntime) qui ne consomme que la couche 1 (air-thread pour
clone3/futex/join, air-memory pour mmap, air-process pour exit_group, et les nouveaux
objets couche 1 ThreadControlBlock/ThreadLocalStorage/relocation). Il ne dépend jamais de
air-sys-syscall (la surface kernel) ; air-sys-types (types transverses : AirError/Errno,
Pid/Tid… — ADR-052 D6) reste autorisé. air-thread (spawn), la libc et le futur std de
*-linux-air reposent sur AirRuntime. no_std + alloc conservé. Cf. ADR-052 D1–D6.
D9 — Modèle panic / destructeurs TLS
panic = abort partout (cohérent ADR-045) ; pas d’unwind cross-thread. Destructeurs thread_local! (Drop en fin de
thread) : supportés via une liste enregistrée dans le TCB, exécutée à la sortie du thread.
Conséquences
- Débloque tout le reste de la couche 1 no_std (les 3 crates env-gated) et le spawn std-free d’air-thread.
- Pose la fondation
*-linux-air: lestdde cette cible reposera sur ce runtime + la libc. - Introduit de l’asm (entrée
_start, mise en place TLS par arch) — chaque bloc// SAFETY:, audité, minimal (ADR-021).
Alternatives écartées
- Réutiliser le TCB glibc/musl : rejeté (ABI interne non maîtrisée, contraire à « pas de C non maîtrisé »).
- Bricoler
#[thread_local]nightly : insuffisant pour l’interop runtime complet ; on veut stable. - Rester lié à glibc pour le spawn : rejeté (unsound, cf. #151).
Questions ouvertes (décisions BDFL)
- Relation avec
stdRust : (a) cible*-linux-airavec unstdportant sur ce runtime (gros, propre), ou (b) resterno_std+libc pour les userlands C/C++ d’abord,std-Rust-sur-Air plus tard ? Recommandation : (b) d’abord. - Nom/forme du crate runtime (
air-rt) et frontière avecair-thread(le spawn migre-t-il dansair-rt?). set_tid/threads multiples au démarrage, gestionat_exit/init-array C (pour les userlands C).- Canari : source (
AT_RANDOMde l’auxv) et politique.
Amendement / ratification — 2026-06-28 (BDFL)
Statut : Accepté. Question ouverte n°1 tranchée : option (a) — cible *-linux-air complète avec std complet sur air-rt, construite comme un tout cohérent.
Justification technique décisive : le TCB/TLS d’air-rt 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, volet 2 air-thread). Le runtime n’a de sens que sur la cible *-linux-air entière (zéro glibc). Conséquence : air-rt, la spec rustc-target et le portage du sys/pal de std forment un seul programme cohérent (pas des pièces testées séparément sur glibc).
Programme *-linux-air (phases) : (1) spec rustc-target *-linux-air (sans glibc, +crt-static ou crt Air) ; (2) air-rt (TCB/TLS/crt0/environ/spawn, ADR-049 D1–D9) ; (3) portage std::sys (pal) sur air-rt + couche 0/1 (thread/TLS/fs/net/env/time/process) ; (4) bootstrap d’un binaire Rust *-linux-air (hello-world → tests) ; (5) libc C-ABI (ADR-046/047) sur la même fondation, pour les userlands C (OpenSSH). C’est un programme multi-sessions.