wandres.dev
DIOS · PhD: construye lo imposible

PhD: construye lo imposible

El nivel Dios: escribe tu propia libc mínima y tu propio allocator, asómate a cómo funciona un compilador por dentro, y descubre que el camino de sistemas no termina nunca.

⏱ 18 min

Has recorrido todo el mapa: del primer printf al Undefined Behavior. Este último nivel no enseña un tema nuevo — te reta a demostrar que lo entiendes construyendo lo que antes usabas sin pensar. Si puedes escribir tu propia libc y tu propio allocator, y entender cómo un compilador convierte tu C en máquina, eres, de verdad, nivel dios.

🎯 Al terminar esta lección sabrás
  • Escribir una libc mínima desde cero.
  • Implementar un allocator propio.
  • Entender cómo funciona un compilador por dentro.
  • Ver por qué el camino no termina.

Reto 1: tu propia libc mínima

En el nivel 23 corriste código freestanding. Ahora conviértelo en una mini-libc: implementa, desde syscalls crudas, las funciones que dabas por sentadas.

// escribir a la salida estándar, sin la libc: solo la syscall write
long sys_write(int fd, const void *buf, unsigned long n) {
    long r;
    __asm__ volatile ("syscall" : "=a"(r)
        : "a"(1), "D"(fd), "S"(buf), "d"(n) : "rcx", "r11", "memory");
    return r;
}

size_t mi_strlen(const char *s) {          // tu strlen
    const char *p = s;
    while (*p) p++;
    return (size_t)(p - s);
}

int mi_puts(const char *s) {                // tu puts, sobre tu strlen y sys_write
    sys_write(1, s, mi_strlen(s));
    sys_write(1, "\n", 1);
    return 0;
}
Reimplementar es la prueba definitiva de comprensión

Hay una diferencia abismal entre usar strlen y escribir strlen. Usarla requiere saber qué hace; escribirla requiere entender punteros, el terminador nulo, size_t, la aritmética de direcciones — todo junto y correcto. Cuando reimplementas una pieza de la libc desde syscalls, demuestras que no hay magia debajo que no comprendas: sabes exactamente qué hace la máquina en cada paso, porque lo has escrito. Esta es la razón por la que los grandes programadores de sistemas reconstruyen las cosas al menos una vez. No para usar su versión en producción, sino porque después de hacerlo, la caja negra se vuelve transparente para siempre.

Reto 2: tu propio allocator

Implementa malloc y free desde cero, pidiendo memoria al sistema con mmap o sbrk y gestionándola tú. Empieza simple —una lista libre de bloques— y evoluciona:

// esqueleto: pide memoria al kernel y gestiona una lista de bloques libres.
// cada bloque lleva una cabecera con su tamaño y si está libre.
// malloc: busca un hueco suficiente (o pide más al kernel);
// free: marca el bloque libre y fusiona con vecinos libres (coalescing).
void *mi_malloc(size_t n);
void  mi_free(void *p);

Al hacerlo entenderás en carne propia la fragmentación, la alineación (nivel 14), las cabeceras de bloque, y por qué las arenas y pools existen. Habrás cerrado el círculo del nivel 13.

Cómo funciona un compilador por dentro

Has usado gcc y clang cientos de veces. ¿Qué hacen realmente? Un compilador convierte tu C en máquina por fases:

flowchart LR
S[código C] --> L[lexer: tokens]
L --> P[parser: AST]
P --> I[IR intermedio]
I --> O[optimización: pasadas]
O --> C[codegen: ensamblador]
style S fill:#89b4fa,color:#11111b
style I fill:#cba6f7,color:#11111b
style O fill:#fab387,color:#11111b
style C fill:#a6e3a1,color:#11111b
  • El lexer parte el texto en tokens (int, x, =, 5).
  • El parser los organiza en un árbol de sintaxis (AST) según la gramática.
  • Se traduce a una representación intermedia (IR) — en LLVM, el famoso “LLVM IR”.
  • Las pasadas de optimización transforman el IR (es aquí donde se explota el UB del nivel 29, se hace inlining, LTO, vectorización).
  • El codegen emite ensamblador para tu arquitectura, respetando la ABI (nivel 21).
El compilador es el sistema por antonomasia

Entender esta tubería cierra tu modelo mental de todo el track. Ahora ves por qué el UB permite optimizaciones (ocurren en las pasadas sobre el IR), por qué la ABI importa (el codegen la respeta), por qué LTO ve el programa entero (optimiza el IR combinado), y por qué los sanitizers instrumentan (insertan código en el IR). El compilador es, a la vez, el programa de sistemas más complejo que usarás y la lente que unifica todo lo que has aprendido. LLVM y GCC son proyectos abiertos: puedes leer su código, ver el IR de tu programa (clang -S -emit-llvm), y hasta contribuir. Ahí está la frontera.

El camino no termina

💡
Nivel dios no es un final, es una licencia

Llegar aquí no significa “saberlo todo” — significa tener los cimientos para aprender cualquier cosa del mundo de los sistemas sin que nada te resulte magia. Desde aquí, las fronteras: contribuir a LLVM, GCC o musl; escribir un kernel de juguete (o el de verdad, en el siguiente track); explorar la verificación formal (demostrar matemáticamente que el código es correcto); el rendimiento extremo del HPC; o los lenguajes que intentan dar la seguridad de la que C carece — como Rust, que te espera en su propio track. Has aprendido a leer el estándar, a pensar en memoria, a no temer al metal. Eso no caduca y se transfiere a todo. El programador de sistemas nunca termina de aprender; simplemente, deja de tenerle miedo a lo desconocido. Bienvenido al nivel dios.

⚔️ Demuestra que eres dios
  1. Escribe una mini-libc: sys_write, tu strlen, tu puts, y un “hola mundo” que no enlace la libc real.
  2. Implementa un malloc/free propio con una lista libre y coalescing. Pruébalo bajo AddressSanitizer.
  3. Mira el LLVM IR de un programa tuyo con clang -S -emit-llvm -O2 y reconoce lo que hace el optimizador.
  4. Elige tu siguiente frontera: ¿el kernel de Linux? ¿Rust? ¿un compilador? El mapa del nivel 0 ahora es tuyo entero.