1# SDD 31 — El compilador de Rust: de «lo presta el lab» a «es nuestro»
2
3> «sin compilador de rust, takana no tiene sentido» — el usuario, 2026-09-14.
4>
5> Tiene razón por una vía que no es de gusto, y conviene escribirla entera antes de discutir cómo.
6
7## 1. El hueco, medido
8
9| pregunta | respuesta (2026-09-14) |
10|---|---|
11| ¿cuánto de takana es Rust? | **12 crates, 42 555 líneas** — todo el instrumental |
12| recetas `cargo-*` en el corpus | **44** |
13| recetas de `rustc` o `cargo` | **0** |
14| ¿de dónde sale el `rustc` que construye todo eso? | del **LAB** (`.dev-fs/alpine`), un rootfs ajeno |
15
16Y el lab **entra en el `ArtifactHash`**. O sea: la distro depende de bytes de otro para poder
17reconstruir su propio instrumental. No se ve porque funciona — es la misma forma de
18`subcomando-sin-driver`, a escala de toolchain: 44 subcomandos de `cargo` y ningún `cargo`.
19
20## 2. Los tres escalones
21
221. **`rust-toolchain-bin`** — el tarball oficial de rust-lang, verificado por su sha256 y sellado
23 tal cual con `foreign = true` (bytes ajenos: clase `ajeno`, fuera del recuento del corpus).
24 **Hecho y probado en la caja**: `rustc 1.97.0`, `cargo 1.97.0`, y un binario compilado que corre.
252. **`rust` desde fuente** — con `llvm21` del corpus y el stage0 del lab. *En curso.*
263. **La cadena `mrustc → 1.90 → 1.91.0 → 1.91.1`** del selfhost (`docs/10-roadmap.md`,
27 `docs/11-bootstrap.md`), que quita hasta el escalón 1. Pendiente.
28
29## 3. Lo que costó el escalón 1, y no estaba en el grafo
30
31El artefacto sella (799 M) y **no ejecuta** hasta tener dos cosas que la distro publicaba por
32separado — las dos `sealed`, las dos en el perfil equivocado:
33
34- **El cargador musl.** `rustc` es PIE dinámico (`NEEDED librustc_driver-*.so`, `NEEDED libc.so`).
35 En el worker ni arranca (`cannot execute: required file not found`, que es como se ve la falta del
36 intérprete); en la caja sí, porque ahí está `musl-shared`.
37- **`libgcc_s.so.1`** (`_Unwind_Resume: symbol not found`). Lo publica `gcc-libs`, que estaba
38 declarado **sólo en los cuatro perfiles de escritorio**.
39
40**Y no hace falta un `cc`.** El primer `rustc hola.rs` falla con `linker \`cc\` not found`, y la
41respuesta no es traer un compilador de C: el toolchain **trae su propio `lld`**
42(`rustlib/<target>/bin/rust-lld`). Con `-C linker-flavor=ld.lld -C link-self-contained=yes` enlaza y
43el binario corre. Una distro sin compilador de C puede compilar Rust igual.
44
45## 4. Lo que costó el escalón 2 — cinco muros, todos con su mensaje engañoso
46
471. **`llvm18` no sirve.** El bootstrap de rustc lo dice en una línea:
48 `panic!("bad LLVM version: {version}, need >=21")`. Mínimos medidos leyendo
49 `src/bootstrap/src/core/build_steps/llvm.rs`: **1.87 → ≥18 · 1.90 → ≥19 · 1.93/1.95 → ≥20 ·
50 1.97 → ≥21**. Y bajar de rustc no es salida: el stage0 de rustc *N* es *N-1* o *N*, y lo que
51 tenemos es 1.97. Por eso existe `llvm21` — y `llvm18` se queda, porque lo usa `mesa-llvmpipe` y
52 mesa 24.0.9 no soporta 21. Dos consumidores con rangos incompatibles ⇒ dos artefactos.
532. **El toolchain musl oficial NO puede producir proc-macros.** Está compilado con
54 `crt-static = true`, y los proc-macros son dylibs:
55
56 error: cannot produce proc-macro for `clap_derive` as the target
57 `x86_64-unknown-linux-musl` does not support these crate types
58
59 …compilando el **propio bootstrap de rustc**, que usa `clap` con derive. O sea: el prebuilt
60 sirve para compilar programas y **no** para arrancar el build de rustc. El stage0 es el `rustc`
61 del LAB, que Alpine construye con soporte de dylib. (Y por eso el rustc que salga de acá lleva
62 `crt-static = false`: con `true` heredaría el mismo defecto y no podría compilar ninguna de las
63 44 recetas `cargo-*` que usan `derive`.)
643. **x.py DESCARGA el stage0 si no le nombras el local.** Sin `build.rustc`/`build.cargo`, en un
65 sandbox sin red muere con `RuntimeError: failed verification` en `download_toolchain()` — un
66 mensaje que no menciona ni la red ni el stage0.
674. **El triple de Alpine no es el canónico.** Sin fijar `build`, x.py detecta
68 `x86_64-alpine-linux-musl` y entonces una sección `[target.x86_64-unknown-linux-musl]` **no
69 aplica a nada**: ignora el `llvm-config` externo y se pone a construir LLVM él solo. Y al forzar
70 el canónico, el stage0 del lab **no tiene la std de ese triple** (`can't find crate for std`).
71 Se usa el de Alpine en todo, que además es con el que ya se compila el corpus entero.
725. **cmake + zig = «compiler broken».** Los build scripts nativos (jemalloc, compiler-rt) usan
73 cmake, y con el wrapper de zig la prueba de ABI falla. `CC=gcc CXX=g++`, igual que en `llvm21`
74 y `cmake`.
75
76## 5. Lo que falta decidir
77
78- **Dónde vive el toolchain.** Hoy `rust-toolchain-bin` está en `perfil.servidor` (799 M; un
79 escritorio no compila nada). El de fuente irá donde se decida — y probablemente sustituya al
80 prebuilt en ese perfil en vez de sumarse.
81- **La config del sitio**: que las flags del linker (`-C linker-flavor=ld.lld`) sean el default en
82 un `/etc/cargo/config.toml`, para que `cargo build` funcione sin recordarlas.
83- **El escalón 3**: cuándo se conecta la cadena del selfhost al corpus. Es lo único que quita la
84 dependencia de bytes ajenos, y es el trabajo más caro de los tres.
31-compilador-de-rust.md
SDD 31 — El compilador de Rust: de «lo presta el lab» a «es nuestro»
«sin compilador de rust, takana no tiene sentido» — el usuario, 2026-09-14.
Tiene razón por una vía que no es de gusto, y conviene escribirla entera antes de discutir cómo.
1. El hueco, medido
| pregunta | respuesta (2026-09-14) |
| ¿cuánto de takana es Rust? | 12 crates, 42 555 líneas — todo el instrumental |
recetas cargo-* en el corpus | 44 |
recetas de rustc o cargo | 0 |
¿de dónde sale el rustc que construye todo eso? | del LAB (.dev-fs/alpine), un rootfs ajeno |
Y el lab entra en el ArtifactHash. O sea: la distro depende de bytes de otro para poder
reconstruir su propio instrumental. No se ve porque funciona — es la misma forma de
subcomando-sin-driver, a escala de toolchain: 44 subcomandos de cargo y ningún cargo.
2. Los tres escalones
rust-toolchain-bin — el tarball oficial de rust-lang, verificado por su sha256 y sellado
tal cual con foreign = true (bytes ajenos: clase ajeno, fuera del recuento del corpus).
Hecho y probado en la caja: rustc 1.97.0, cargo 1.97.0, y un binario compilado que corre.
rust desde fuente — con llvm21 del corpus y el stage0 del lab. En curso.
- La cadena
mrustc → 1.90 → 1.91.0 → 1.91.1 del selfhost (docs/10-roadmap.md,
docs/11-bootstrap.md), que quita hasta el escalón 1. Pendiente.
3. Lo que costó el escalón 1, y no estaba en el grafo
El artefacto sella (799 M) y no ejecuta hasta tener dos cosas que la distro publicaba por
separado — las dos sealed, las dos en el perfil equivocado:
- El cargador musl.
rustc es PIE dinámico (NEEDED librustc_driver-*.so, NEEDED libc.so).
En el worker ni arranca (cannot execute: required file not found, que es como se ve la falta del
intérprete); en la caja sí, porque ahí está musl-shared.
libgcc_s.so.1 (_Unwind_Resume: symbol not found). Lo publica gcc-libs, que estaba
declarado sólo en los cuatro perfiles de escritorio.
Y no hace falta un cc. El primer rustc hola.rs falla con linker \cc` not found, y la respuesta no es traer un compilador de C: el toolchain **trae su propio lld** (rustlib/<target>/bin/rust-lld). Con -C linker-flavor=ld.lld -C link-self-contained=yes` enlaza y
el binario corre. Una distro sin compilador de C puede compilar Rust igual.
4. Lo que costó el escalón 2 — cinco muros, todos con su mensaje engañoso
-
llvm18 no sirve. El bootstrap de rustc lo dice en una línea:
panic!("bad LLVM version: {version}, need >=21"). Mínimos medidos leyendo
src/bootstrap/src/core/build_steps/llvm.rs: 1.87 → ≥18 · 1.90 → ≥19 · 1.93/1.95 → ≥20 ·
1.97 → ≥21. Y bajar de rustc no es salida: el stage0 de rustc N es N-1 o N, y lo que
tenemos es 1.97. Por eso existe llvm21 — y llvm18 se queda, porque lo usa mesa-llvmpipe y
mesa 24.0.9 no soporta 21. Dos consumidores con rangos incompatibles ⇒ dos artefactos.
-
El toolchain musl oficial NO puede producir proc-macros. Está compilado con
crt-static = true, y los proc-macros son dylibs:
error: cannot produce proc-macro for `clap_derive` as the target
`x86_64-unknown-linux-musl` does not support these crate types
…compilando el propio bootstrap de rustc, que usa clap con derive. O sea: el prebuilt
sirve para compilar programas y no para arrancar el build de rustc. El stage0 es el rustc
del LAB, que Alpine construye con soporte de dylib. (Y por eso el rustc que salga de acá lleva
crt-static = false: con true heredaría el mismo defecto y no podría compilar ninguna de las
44 recetas cargo-* que usan derive.)
-
x.py DESCARGA el stage0 si no le nombras el local. Sin build.rustc/build.cargo, en un
sandbox sin red muere con RuntimeError: failed verification en download_toolchain() — un
mensaje que no menciona ni la red ni el stage0.
-
El triple de Alpine no es el canónico. Sin fijar build, x.py detecta
x86_64-alpine-linux-musl y entonces una sección [target.x86_64-unknown-linux-musl] no
aplica a nada: ignora el llvm-config externo y se pone a construir LLVM él solo. Y al forzar
el canónico, el stage0 del lab no tiene la std de ese triple (can't find crate for std).
Se usa el de Alpine en todo, que además es con el que ya se compila el corpus entero.
-
cmake + zig = «compiler broken». Los build scripts nativos (jemalloc, compiler-rt) usan
cmake, y con el wrapper de zig la prueba de ABI falla. CC=gcc CXX=g++, igual que en llvm21
y cmake.
5. Lo que falta decidir
- Dónde vive el toolchain. Hoy
rust-toolchain-bin está en perfil.servidor (799 M; un
escritorio no compila nada). El de fuente irá donde se decida — y probablemente sustituya al
prebuilt en ese perfil en vez de sumarse.
- La config del sitio: que las flags del linker (
-C linker-flavor=ld.lld) sean el default en
un /etc/cargo/config.toml, para que cargo build funcione sin recordarlas.
- El escalón 3: cuándo se conecta la cadena del selfhost al corpus. Es lo único que quita la
dependencia de bytes ajenos, y es el trabajo más caro de los tres.