Índice

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

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")
    }
}

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:

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:

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

  1. Escribe un programa que use volatile_write y volatile_read para implementar un spin-lock simple (candado con espera activa).
  1. Usa atomic_store y atomic_load para implementar una bandera booleana thread-safe que un thread establece y otro thread consulta.
  1. Escribe un programa usando tipos con tamaño fijo (i8, i16, i32) que empaque tres valores en un solo i64 usando desplazamiento de bits.
  1. 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.
  1. 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

Siguiente capítulo: Caso de estudio — Cómo se construyó nyx-kv →

← Anterior: Async y event loop Siguiente: Caso de estudio — Cómo se construyó nyx-kv →