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-048 — Descellement unique de la couche 0 : std-free certifié + famille futex(2) (re-sceau couche-0-v1.6)

Statut : Accepté (2026-06-28, validé par le BDFL). Companion d’ADR-044 (méthode de re-sceau de la couche 0), d’ADR-046 et d’ADR-047 (la libc — pourquoi on exige std-free + un futex(2)), et de l’ADR-021 (conventions couche 0). Décline le Principe 4 (no_std-future, errno sans alloc). S’appuie sur l’audit docs/notes/audits/audit-couche0-std-free-fr.md.

Catégorie : Architecture — couche 0 (re-sceau). Évolution structurante de la couche scellée → RFC (ADR-015).

Contexte

Deux constats convergents :

  1. La couche 0 devait être std-free (Principe 4) — elle ne l’est pas. L’audit montre que air-sys-types et air-sys-syscall sont std par défaut (pas de #![no_std]) et dépendent de std en code lib (std::os::fd, std::ffi::CString/OsString, std::sync::{Arc,Mutex}).
  2. La libc Air (ADR-046/047) exige un substrat threading std-free (air-thread réimplémenté sur la couche 0), ce qui requiert un futex(2) classique (FUTEX_WAIT/WAKE sur &AtomicU32) absent de la couche 0 (le seul futex présent est via io_uring, trop lourd pour un Mutex basique). Or la couche 0 a elle-même besoin de ce futex(2) pour remplacer le std::sync::Mutex de son état io_uring partagé.

Desceller une couche scellée a un coût (re-sceau, validation 2-arches, ABI). On regroupe donc TOUT en un seul descellement pour ne re-sceller qu’une fois, certifié std-free.

Décision

D1 — Descellement unique, re-sceau couche-0-v1.6

Un seul descellement (depuis couche-0-v1.5) couvrant tout le périmètre ci-dessous. Le découpage en tâches/PR est autorisé, mais le re-sceau couche-0-v1.6 ne se referme que lorsque TOUT est fait et certifié std-free.

D2 — Conversion std-free (no_std + alloc)

  • #![no_std] + extern crate alloc dans les deux lib.rs.
  • std::os::fd::*core::os::fd::* (mécanique, ~22 modules).
  • std::ffi::CStringalloc::ffi::CString ; std::ffi::CStrcore::ffi::CStr.
  • std::sync::Arcalloc::sync::Arc.
  • std::ffi::OsString/OsStr → type octets (Unix : nom de fichier = octets, peut être non-UTF-8) — possédé alloc::vec::Vec<u8> / emprunté &[u8], cohérent avec « chemins = octets ».
  • alloc est toléré pour les types possédés exigés (CString, nom de DirEntry…) ; le happy path reste sans alloc et errno reste NonZeroI32 sans alloc (Principe 4 / ADR-021 inchangés).

D3 — Nouvelle famille futex(2)

Famille couche 0 dédiée : futex_wait / futex_wake (et requeue/cmp_requeue si justifiés), sur &core::sync::atomic::AtomicU32. Une fonction typée par opération (pas de multiplexeur futex(op,…), ADR-021 §3). EINTR remonté à l’appelant, sans retry (ADR-021 §2). Sert air-thread (couche 1, libc) et le mutex de la couche 0 (D5).

D4 — clone3 : support du spawn de thread

Vérifier et, si besoin, étendre le wrapper clone3 (couche 0) pour exprimer les arguments de création de thread : flags CLONE_VM|CLONE_THREAD|CLONE_SETTLS|CLONE_CHILD_CLEARTID|CLONE_PARENT_SETTID, pile enfant, pointeur TLS, child_tid (pour le join via CHILD_CLEARTID/futex). (Le bootstrap TLS + trampoline d’entrée du thread enfant relèvent de la couche 1 air-thread — hors de ce re-sceau ; D4 garantit seulement que la couche 0 expose ce qu’il faut.)

D5 — Mutex io_uring sur futex(2)

Remplacer le std::sync::Mutex/PoisonError de io_uring/shared.rs par un mutex futex-maison (au-dessus de D3). Supprime la dernière dépendance std::sync du code lib.

D6 — Critère d’acceptation du re-sceau (porte de sortie)

Le re-sceau couche-0-v1.6 n’est prononcé que si TOUT est vert :

  1. cargo build avec #![no_std] réussit (les deux crates) ⇒ preuve par le compilateur qu’il ne reste aucun std en code lib ;
  2. couverture 100 % (lignes + branches) maintenue (couche 0) ;
  3. barrière 2-arches (x86_64 + aarch64) + CI ARM vertes ;
  4. les nouveaux symboles (futex(2), éventuels ajouts clone3) ont leurs tests + property/fuzz au standard couche 0, et leurs commentaires // SAFETY:.

Conséquences

  • air-thread (couche 1) pourra être réimplémenté std-free sur la couche 0 (clone3 + futex), ce qui débloque le harnais no_std de la méthode libc (ADR-047 : thread Rust-pur-sur-couche-1 ∥ thread chemin-C, même behaviour).
  • La couche 0 redevient conforme au Principe 4, durablement (gardé par le critère #![no_std]).
  • ABI : ajout de symboles (futex(2)) ; la migration std::os::fdcore::os::fd est sans impact ABI (mêmes types, même layout — core::os::fd est la définition que std ré-exporte).

Alternatives écartées

  • Desceller plusieurs fois (std-free puis futex séparément) : rejeté — coût/risque de re-sceaux multiples ; D1 impose un descellement unique.
  • Mutex via le futex io_uring existant : rejeté — un Mutex basique ne doit pas dépendre d’un anneau io_uring (poids, cycle de vie).
  • Spinlock au lieu de futex : rejeté — pas de blocage, gaspillage CPU, inversion de priorité.
  • Garder std en couche 0 : rejeté — viole le Principe 4 et contaminerait la libc (std tire la glibc), rendant impossible le harnais no_std (ADR-047).

Questions ouvertes

  1. Opérations futex exactes à exposer (WAIT/WAKE suffisent-ils, ou REQUEUE/CMP_REQUEUE/PI dès v1.6 ?).
  2. Forme précise du type octets remplaçant OsString (newtype dédié vs Vec<u8> nu).
  3. Le wrapper clone3 actuel exprime-t-il déjà tout D4, ou faut-il l’étendre (impact ABI à vérifier) ?

Amendement 1 — 2026-06-28 (ratifié BDFL) : core::os::fd n’existe pas → internalisation des types FD

Constat empirique (rustc 1.96 stable) : use core::os::fd::OwnedFd; produit error[E0432]: could not find fd in os. core::os::fd n’existe PAS en stable ; les types FD (OwnedFd/BorrowedFd/RawFd + traits AsFd/AsRawFd/FromRawFd/IntoRawFd) ne vivent que dans std::os::fd.

Correction de D2 et de la Conséquence « sans impact ABI » — les deux étaient FAUX pour les FD :

  • La migration std::os::fdcore::os::fd n’est ni possible ni mécanique.
  • Pour atteindre #![no_std], Air doit internaliser ses propres types FD : RawFd, BorrowedFd<'fd>, OwnedFd (Drop → close(2)) + traits, comme la spec air-sys-types.md (l.74-90) le prévoit déjà — le pub use std::os::fd actuel est un raccourci/dette qui dévie de la spec.
  • C’est une introduction de type air-stable (Principe 8, ADR-012, ABI 10 ans) → AVEC impact ABI (baseline air-abi-check à poser), PAS mécanique. ~88 sites lib + interop de tests à reprendre.
  • Point d’archi : OwnedFd::drop exige un close(2) minimal dans le crate air-sys-types (un seul syscall, conforme à la spec).

Tâche AJOUTÉE au cycle v1.6, AVANT le reste de D2 :

  • Tâche 2a — Internalisation des types FD (air-stable) : définir RawFd/BorrowedFd/OwnedFd + traits + close(2), migrer les ~88 sites, interop tests, baseline air-abi-check. Miroir exact de la sémantique std (interop triviale via RawFd + layout ABI-stable).
  • Une fois 2a fait, le reste de D2 (CString→alloc, CStr→core, Arc→alloc, OsString→octets, #![no_std]+alloc) redevient mécanique (piloté par le compilateur).

Déjà acquis : Tâche 1 futex(2) (#147, mergé) ; D5 mutex FutexMutex (5ca6fe8, livré). Ordre v1.6 : futex(2) ✅ → FD (2a) → reste de D2 → #![no_std] → re-sceau v1.6 (D6 inchangé) → puis clone3 spawn-thread (D4 / Tâche 3).