Sistemas — Assembly inline, volatile, atomic
Nyx como lenguaje de sistemas
La mayoría de los libros de programación se detienen en "construir un servidor web." Pero Nyx está diseñado para ir más allá — puede reemplazar a C para programación de sistemas. Este capítulo cubre las herramientas de bajo nivel que lo hacen posible.
Inline assembly
Cuando necesitas ejecutar una instrucción específica de CPU, Nyx te permite escribir assembly directamente:
fn main() { unsafe { asm("nop") // no-operation: no hace nada, toma un ciclo } }
El inline assembly usa sintaxis AT&T estilo GCC y se compila a inline assembly de LLVM.
Manipulación de registros
fn main() { unsafe { asm("mov $42, %rax") // mover 42 al registro RAX asm("add $8, %rax") // sumar 8 a RAX (ahora 50) } }
Cuándo usar inline assembly
- Instrucciones específicas de CPU: CPUID, RDTSC (leer contador de timestamp), control de caché
- Manejadores de interrupciones:
cli/stipara deshabilitar/habilitar interrupciones - Programación bare-metal: bootloaders, kernels de SO
- Secuencias críticas de rendimiento: cuando el optimizador de LLVM no genera código óptimo
Para casi todo el código a nivel de aplicación, nunca necesitas inline assembly. Existe para los casos raros donde necesitas control exacto sobre lo que hace la CPU.
Atributos de funciones
Nyx soporta atributos de funciones para programación de sistemas:
#[naked] fn manejador_interrupcion() { unsafe { asm("push %rax") asm("push %rbx") // ... manejar interrupción ... asm("pop %rbx") asm("pop %rax") asm("iretq") } }
#[naked]— sin prólogo/epílogo de función (sin setup de stack frame)#[interrupt]— marca como manejador de interrupción de hardware#[link_section(".text.boot")]— coloca la función en una sección específica del binario#[export_name("_start")]— controla el nombre del símbolo en el binario
Acceso volátil a memoria
El acceso normal a memoria puede ser reordenado o eliminado por el compilador. El acceso volátil no:
fn leer_registro_hardware() -> int { unsafe { let mmio_addr: *int = 0x40000000 as *int return volatile_read(mmio_addr) } } fn escribir_registro_hardware(valor: int) { unsafe { let mmio_addr: *int = 0x40000000 as *int volatile_write(mmio_addr, valor) } }
Por qué importa volatile
Sin volatile, el compilador podría:
- Leer un registro de hardware una vez y cachear el valor (perdiendo actualizaciones)
- Eliminar una escritura a un registro porque "nadie lo lee" (¡el hardware sí!)
- Reordenar lecturas y escrituras (el hardware depende del orden exacto)
Volatile garantiza: cada lectura lee de memoria, cada escritura escribe a memoria, en exactamente el orden que especificaste.
Operaciones atómicas
Las operaciones atómicas son la base de la programación lock-free. Garantizan que un ciclo de lectura-modificación-escritura se complete sin interrupción:
fn main() { unsafe { let bandera: *int = alloc(8) atomic_store(bandera, 0) // En un thread: atomic_store(bandera, 1) // señal de "listo" // En otro thread: let listo: int = atomic_load(bandera) if listo == 1 { print("El otro thread señaló listo") } free(bandera) } }
Atomic vs mutex
| Atomic | Mutex |
|---|---|
| Solo operaciones individuales | Protege bloques de código |
| Sin espera (lock-free) | Puede bloquear (threads esperan) |
| Muy rápido (~1 nanosegundo) | Más lento (~50 nanosegundos) |
| Difícil de usar correctamente | Fácil de razonar |
Usa atomics para flags simples, contadores y máquinas de estado. Usa mutexes para secciones críticas complejas.
Tipos con tamaño fijo para hardware
Cuando interactúas con hardware o protocolos binarios, necesitas tipos de tamaño exacto:
var byte: i8 = 0xFF var word: i16 = 0x1234 var dword: i32 = 0xDEADBEEF var qword: i64 = 0 var ubyte: u8 = 255 var uword: u16 = 65535 var udword: u32 = 4294967295 var uqword: u64 = 0 var single: f32 = 3.14
Estos tipos se mapean directamente a anchos de registros de hardware y son esenciales para drivers de dispositivos y parsing de protocolos binarios.
Modo sin GC
Para programación de sistemas, a menudo no puedes tener un recolector de basura. Nyx soporta compilación sin el GC:
make run-no-gc FILE=programa.nx
En modo sin GC:
- No se enlaza Boehm GC
- Toda la gestión de memoria es manual (
alloc/free) - Binarios más pequeños
- Rendimiento determinístico (sin pausas de GC)
- Debes liberar todo — las fugas son tu responsabilidad
Compilación cruzada
Nyx puede compilar para diferentes arquitecturas:
make cross FILE=programa.nx TARGET=aarch64-linux-gnu
Esto genera un binario para ARM64 Linux desde una máquina x86. Combinado con modo sin GC, puedes apuntar a sistemas embebidos y microcontroladores.
Target WebAssembly
Nyx también puede compilar a WebAssembly para ejecutarse en navegadores:
make wasm FILE=programa.nx
Esto genera un archivo .wasm que puede ser cargado por una página web.
Ejemplo práctico: leer el timestamp de CPU
fn rdtsc() -> int { var resultado: int = 0 unsafe { asm("rdtsc") asm("shl $32, %rdx") asm("or %rdx, %rax") } return resultado } fn main() { let inicio: int = time_us() // ... hacer algún trabajo ... var i: int = 0 while i < 1000000 { i += 1 } let fin: int = time_us() print("Transcurrido: " + int_to_string(fin - inicio) + " microsegundos") }
Ejercicios
- Escribe un programa que use
volatile_writeyvolatile_readpara implementar un spin-lock simple (candado con espera activa).
- Usa
atomic_storeyatomic_loadpara implementar una bandera booleana thread-safe que un thread establece y otro thread consulta.
- Escribe un programa usando tipos con tamaño fijo (
i8,i16,i32) que empaque tres valores en un soloi64usando desplazamiento de bits.
- Compila un programa simple "hola mundo" en modo sin GC y compara el tamaño del binario con la versión normal con GC habilitado.
- Escribe una función que use inline assembly para ejecutar un sled de nops (100 nops seguidos) y mide su tiempo de ejecución.
Resumen
unsafe { asm("...") }ejecuta inline assembly (sintaxis AT&T).- Atributos de función:
#[naked],#[interrupt],#[link_section],#[export_name]. volatile_read/volatile_writeprevienen reordenamiento del compilador en acceso a memoria.atomic_load/atomic_storeproporcionan sincronización lock-free entre threads.- Tipos con tamaño fijo (
i8,i16,i32,u8,u16,u32,f32) coinciden con anchos de hardware. - El modo sin GC habilita gestión de memoria manual y determinística.
- Compilación cruzada y targets WebAssembly extienden Nyx más allá de Linux de escritorio.
Siguiente capítulo: Caso de estudio — Cómo se construyó nyx-kv →