Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # Capítulo 08: Tensores, Memoria Compilada RMM (`RMM1`/`RMM2`), Rexións e Sistemas |
| 2 | |
| 3 | Ata aquí vimos o lado máis amable, elegante e matemático de Ridgun: funcións puras, tipos alxébricos, *pattern matching*, árbores de decisión con `|` / `|.` e grafos paralelos `||` con `->` que aproveitan todos os núcleos do teu procesador sen pedirche nin un triste `Mutex`. |
| 4 | |
| 5 | Chegados a este punto, o programador veterano de sistemas adoita erguer a man ao fondo da aula con medio sorriso e preguntar: |
| 6 | |
| 7 | > *"Todo isto é precioso para calcular facturas ou compilar ASTs, pero que pasa cando preciso multiplicar matrices de millóns de flotantes a velocidade de vertixe? Que pasa coa memoria? Hai un colector de lixo (Garbage Collector) agochado debaixo da alfombra agardando o peor momento para conxelar o meu programa durante 200 milisegundos? E como falo co sistema operativo Unix de verdade?"* |
| 8 | |
| 9 | Excelentes preguntas. Imos levantar o capó de Ridgun para ver como se las gasta cando toca baixar ao barro dos sistemas, o cálculo numérico e a memoria determinista. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 8.1. O dilema da memoria: nin Garbage Collector nin pelexas co Borrow Checker (`RMM1` e `RMM2`) |
| 14 | |
| 15 | No mundo das linguaxes modernas adoita haber dous bandos irreconciliables: |
| 16 | |
| 17 | 1. **O bando do Garbage Collector (Java, Go, Python, JS, Haskell, OCaml):** ti pides memoria alegremente e, de cando en vez, un camión do lixo invisible detén o tráfico do teu programa para rastrexar que obxectos seguen vivos e cales non. É comodísimo... ata que estás facendo unha ferramenta de sistema ultrarrápida ou un servidor con latencias estritas e o camión decide aparcar en dobre fila. |
| 18 | 2. **O bando do control manual / Borrow Checker (C, C++, Rust):** non hai colector de lixo, pero a cambio tes que xestionar punteiros á man (xogándote un `Segmentation fault` ou un *use-after-free*) ou pasar media tarde negociando co *borrow checker* por que `'a` non vive tanto coma `'b`. |
| 19 | |
| 20 | Ridgun resolve este dilema de raíz con **Ridgun Memory Management (`RMM`)**, un sistema determinista de dúas políticas complementarias (**`RMM1`** e **`RMM2`**) combinado con **rexións léxicas (`with region`)**: |
| 21 | |
| 22 | ### Política `RMM1` — Xestión de Memoria Compilada (CMM Lineal e Condicional) |
| 23 | Cando o compilador coñece estaticamente o fluxo das variables locais dentro dunha función: |
| 24 | - **CMM Lineal**: `ridgunc` calcula en tempo de compilación cal é a **última liña** na que se le cada variable local (`a`, `b`, ...) e insire xusto despois a súa liberación determinista (`rg_cmm_drop`), ou transfire a súa propiedade sen copias (`rg_cmm_move`) cando a variable se devolve como resultado. |
| 25 | - **CMM Condicional (`ptr(a) != ptr(c)`)**: e se unha función como `escoller cond a b` devolve `a` ou `b` segundo unha condición en tempo de execución? `ridgunc` constrúe a táboa de devanceiros e emite unha garda de identidade de punteiros de **1 ciclo de CPU**: se `b` deixa de usarse mentres `c` segue viva, comproba `if ptr(b) != ptr(c) { free(b) }`. A rama non escollida libérase ao instante e a escollida sobrevive intacta. |
| 26 | |
| 27 | ### Política `RMM2` — Conta por Asignación e Fervenza Estrutural |
| 28 | E que ocorre cando metes estruturas dentro doutras estruturas (`xs = [a, b]`), reasignas variables mutables (`mut x = ...`) ou encadeas chamadas complexas? Aí entra **`RMM2`**, que goberna todo o ciclo de vida con catro regras matemáticas sen fisuras: |
| 29 | |
| 30 | 1. **Os argumentos de entrada dunha función non se tocan (`+0` ao entrar, `-0` ao saír)**: pasar unha lista ou un rexistro a unha función xamais incrementa nin decrementa contadores en balde. |
| 31 | 2. **Toda asignación a unha variable local interna ou cela de contedor suma `+1`**: |
| 32 | - `a = [10, 20]` $\rightarrow$ o bloque `M1` nace con contador `1`. |
| 33 | - `xs = [a, b]` $\rightarrow$ a lista `xs` nace con `1`, e como garda referencias a `a` e `b`, ambos soben a `2`. |
| 34 | - Se reasignas unha variable mutable (`mut x = ...` e logo `x = novo`), `RMM2` aplica primeiro **`+1`** ao valor novo antes de aplicar **`-1`** ao antigo. |
| 35 | 3. **Ao saír da función, as variables locais que NON se devolven reciben `-1`**: se o contador dunha estrutura chega a `0`, libérase a súa memoria no acto e **propaga `-1` en fervenza, un por un, a cada un dos seus fillos directos**. |
| 36 | 4. **O valor devolto pola función exímese en tránsito**: recibe o seu `-1` de variable local pero non se destrúe ao cruzar o `return`, de xeito que cando quen chamou fai `c = mina funcion ()`, ese `+1` da asignación déixao exactamente en `1`. |
| 37 | |
| 38 | ```ridgun |
| 39 | escoller Bool [Int] [Int] => [Int] |
| 40 | escoller cond a b = |
| 41 | | cond = a |
| 42 | | otherwise = b |
| 43 | |
| 44 | exemplo rmm2 Bool => Int |
| 45 | exemplo rmm2 cond = |
| 46 | a = [10, 20, 30] -- M1 (rc = 1) |
| 47 | b = [40, 50, 60] -- M2 (rc = 1) |
| 48 | c = escoller cond a b -- `c` recibe +1; o non escollido morre aquí mesmo (rc -> 0) |
| 49 | (contar a) + (contar c) -- Ao rematar, `a` e `c` reciben -1 e todo queda en 0 bloques vivos |
| 50 | ``` |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## 8.2. Rexións Léxicas con Cota (`with region`): Devasas de memoria en $O(1)$ |
| 55 | |
| 56 | Ademais de `RMM1` e `RMM2`, cando estás procesando un paquete de rede externo, un ficheiro xigante ou un lote de datos non confiables e queres poñer un **teito estrito de memoria RAM**, envolves o cálculo nun bloque **`with region`**: |
| 57 | |
| 58 | ```ridgun |
| 59 | procesar trama String => Result |
| 60 | procesar trama cru = |
| 61 | with region 64_KB do |
| 62 | limpas = map limpar lina (dividir linas cru) |
| 63 | contar validos limpas |
| 64 | ``` |
| 65 | |
| 66 | 1. **Cota garantida en hardware/runtime**: se dentro do bloque se intenta reservar nin un só byte por riba de `64_KB` (`65.536` bytes), o runtime aborta limpamente o bloque, libera toda a area temporal en tempo constante $O(1)$ e devolve `Err OutOfMemory` sen tombar o proceso. |
| 67 | 2. **Liberación instantánea ao saír**: en canto remata o bloque `with region`, toda a memoria temporal da area desaparece de golpe sen deixar fragmentación. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## 8.3. Tensores Nativos, Contracción de Einstein (`@`) e Dimensións Simbólicas |
| 72 | |
| 73 | Se algunha vez programaches en Python con NumPy ou PyTorch, seguro que sufriches o clásico erro no minuto 41 de execución: |
| 74 | |
| 75 | ```text |
| 76 | RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128) |
| 77 | ``` |
| 78 | |
| 79 | En Ridgun, os **`Tensor`** son cidadáns nativos de primeira clase e as súas dimensións forman parte da sinatura de tipos: |
| 80 | |
| 81 | ```ridgun |
| 82 | -- Se entra (N × K) e (K × M), por forza sae (N × M)! |
| 83 | multiplicar matrices Tensor[N, K] Tensor[K, M] => Tensor[N, M] |
| 84 | multiplicar matrices a b = a_ik @ b_kj |
| 85 | ``` |
| 86 | |
| 87 | Fíxate en dous detalles espectaculares: |
| 88 | 1. **Verificación estática de dimensións (`Tensor[N, K]`)**: o eixe interno `K` da primeira matriz ten que coincidir co primeiro eixe `K` da segunda. Se intentas pasar dimensións incompatibles, `ridgunc check` deténte antes de executar nada. |
| 89 | 2. **Notación de subíndices de Einstein (`a_ik @ b_kj`) e Hadamard (`a_ij * b_j`)**: |
| 90 | - Con **`@`**, os subíndices repetidos en todos os operandos (`k`) contráense (súmanse) e os únicos (`i`, `j`) sobreviven no tensor resultado. |
| 91 | - Con **`+`, `-`, `*`, `/`**, os subíndices aliñan dimensións e fan *broadcasting* automático (`a_ij * b_j` multiplica cada fila `i` de `a` polo vector `b_j` sen necesidade de escribir `unsqueeze` nin `reshape`). |
| 92 | 3. **Tensores tipados de alto rendemento (`Tensor[U64 * N]`) e mutación *in-place* con `t_(i, j)`**: |
| 93 | En sistemas criptográficos, simulacións físicas ou álxebra linear podes declarar tensores planos sobre un bloque contiguo de enteiros sen signo de 64 bits (`Tensor[U64 * 4]`, `Tensor[U64 * 8 * 16]`), operar en paralelo con `^` (XOR nativo de bloque) e ler ou escribir elementos en tempo $O(1)$ coa sintaxe de subíndice explícito `t_(i)` ou `t_(i, j)`: |
| 94 | ```ridgun |
| 95 | crear bloque estado => Tensor[U64 * 2 * 2] |
| 96 | crear bloque estado = |
| 97 | mut m = tensor fill [2, 2] (u64 0) |
| 98 | m_(0, 1) = u64 0x6a09e667f3bcc908 |
| 99 | m |
| 100 | ``` |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## 8.4. Acceso a Hardware (`hardware ::`) e Primitivas POSIX de Sistemas |
| 105 | |
| 106 | O propio compilador `ridgunc`, o xestor `stable` e utilidades de sistemas Unix están escritos en Ridgun. Para iso, a linguaxe inclúe bloques de rexistros mapeados en memoria (`hardware ::`) e primitivas POSIX directas: |
| 107 | |
| 108 | ```ridgun |
| 109 | hardware :: UART0 @ 0x1000_0000 |
| 110 | tx_data : Int @ 0x00 |
| 111 | status : Int @ 0x04 |
| 112 | ``` |
| 113 | |
| 114 | E para traballar co sistema operativo: |
| 115 | |
| 116 | | Primitiva | Sinatura | Que fai | |
| 117 | |---|---|---| |
| 118 | | `cli args` | `=> [String]` | Argumentos de liña de comandos (`argv`). | |
| 119 | | `read file` / `write file` | `String => String` / `String String => ()` | Lectura e escritura íntegra de ficheiros en disco. | |
| 120 | | `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Lectura e escritura binaria exacta byte a byte (`0..255`). | |
| 121 | | `list dir` / `file lstat` | `String => [String]` / `String => ...` | Listaxe de directorios e inspección de metadatos POSIX (`lstat`). | |
| 122 | | `sha256 file` / `sha256 str` / `sha256 raw` | `String => String` | Hash criptográfico SHA-256 nativo (en hexadecimal de 64 caracteres ou resumo binario cru de 32 bytes con `sha256 raw` / `sha256 bytes`). | |
| 123 | | `sys exec` / `shell exec` | `... => Int` | Execución de procesos do sistema operativo. | |
| 124 | | `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultar ou mudar o directorio de traballo. | |
| 125 | | `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variables de contorno do proceso. | |
| 126 | | `sys getuid` / `sys getgid` | `=> Int` | UID e GID reais do proceso (`doas`, ferramentas de seguridade). | |
| 127 | | `sys exit` | `Int => ()` | Rematar o proceso con código de saída. | |
| 128 | |
| 129 | --- |
| 130 | |
| 131 | ## 8.5. O Ecosistema de Bibliotecas Estándar (`ridgun-libs`) |
| 132 | |
| 133 | Ademais das primitivas do compilador, o ecosistema oficial de Ridgun inclúe **32 bibliotecas puras (`.rglib`)** escritas integramente en Ridgun que podes importar con `import <nome>` en calquera proxecto: |
| 134 | |
| 135 | | Categoría | Bibliotecas dispoñibles | Que achegan | |
| 136 | |---|---|---| |
| 137 | | **Coleccións, Texto e Datos** | `collections`, `set`, `string`, `regex`, `utf8`, `fmt`, `csv`, `json`, `toml`, `yaml`, `xml`, `html`, `markdown`, `base64`, `hex`, `compress`, `sql` | Mapas, conxuntos, expresións regulares, parsers/serializadores estruturados, codificación e motores de consultas. | |
| 138 | | **Matemáticas, Tempo e Aleatoriedade** | `math`, `random`, `datetime`, `uuid`, `log`, `cli`, `bench`, `test` | Funcións matemáticas, PRNG determinista e de sistema, datas ISO-8601, UUIDs, marcos de probas e benchmarks con `clock_gettime`. | |
| 139 | | **Sistemas, Rede e Web** | `fs`, `path`, `process`, `env`, `net`, `http`, `uri` | Sistema de ficheiros de alto nivel, manipulación de rutas, sub-procesos, sockets TCP/UDP e cliente/servidor HTTP. | |
| 140 | | **Criptografía e Seguridade (`crypto`)** | `crypto` (`sha256`, `hmac`, `hkdf`, `pbkdf2`, `argon2id`, `chacha20`, `aes`, `x25519`, `ed25519`, `csprng`) | Suite criptográfica verificada con vectores oficiais RFC/NIST, implementada sobre `U64`, `Tensor[U64 * ...]` e `sha256 raw` con optimización de inlining de tuplas sen reservas no heap. | |
| 141 | |
| 142 | --- |
| 143 | |
| 144 | ## 8.6. Caso de Estudo Real: Construíndo `rg-wc`, unha utilidade Unix paralela |
| 145 | |
| 146 | Imos unir todo o aprendido desde o Capítulo 01 ata aquí nun programa real de sistemas: unha versión paralela da ferramenta Unix `wc` (contador de liñas e bytes para múltiples ficheiros) escrita en Ridgun puro: |
| 147 | |
| 148 | ```ridgun |
| 149 | -- 1. Modelamos as métricas dun ficheiro cun rexistro ADT (Capítulo 06) |
| 150 | type Metricas = ficheiro: String, linas: Int, bytes: Int |
| 151 | |
| 152 | -- 2. Contamos saltos de liña (código ASCII 10) con recursión de cola TCO (Capítulo 04) |
| 153 | fn :: contar saltos String Int Int Int => Int |
| 154 | contar saltos texto pos fin acum = |
| 155 | | pos >= fin = acum |
| 156 | | (str char at texto pos) == 10 = contar saltos texto (pos + 1) fin (acum + 1) |
| 157 | | otherwise = contar saltos texto (pos + 1) fin acum |
| 158 | |
| 159 | fn :: numero de linas String => Int |
| 160 | numero de linas texto = contar saltos texto 0 (str len texto) 0 |
| 161 | |
| 162 | -- 3. Analizamos un ficheiro e devolvemos a súa estrutura Metricas |
| 163 | fn :: analizar ficheiro String => Metricas |
| 164 | analizar ficheiro ruta = |
| 165 | contido = read file ruta |
| 166 | || numero de linas contido -> n_linas |
| 167 | || str len contido -> n_bytes ||. |
| 168 | Metricas ruta n_linas n_bytes |
| 169 | |
| 170 | -- 4. Formatamos cada fila e sumamos totais con `fold` e `||` (Capítulo 07) |
| 171 | fn :: formatar metrica Metricas => String |
| 172 | formatar metrica m = "{linas m}\t{bytes m}\t{ficheiro m}" |
| 173 | |
| 174 | fn :: sumar linas Int Metricas => Int |
| 175 | sumar linas acc m = acc + (linas m) |
| 176 | |
| 177 | fn :: sumar bytes Int Metricas => Int |
| 178 | sumar bytes acc m = acc + (bytes m) |
| 179 | |
| 180 | fn :: xerar informe [String] => String |
| 181 | xerar informe rutas = |
| 182 | metricas = ||map analizar ficheiro rutas |
| 183 | || map formatar metrica metricas -> filas |
| 184 | || fold sumar linas 0 metricas -> total_l |
| 185 | || fold sumar bytes 0 metricas -> total_b ||. |
| 186 | "{filas}\nTotal: {total_l} liñas, {total_b} bytes" |
| 187 | |
| 188 | fn :: main => () |
| 189 | main = |
| 190 | echo (xerar informe ["README.md", "AGENTS.md"]) |
| 191 | ``` |
| 192 | |
| 193 | Párate un segundo a mirar esas ~40 liñas de código: |
| 194 | - Non hai nin un só `null`, nin unha excepción descontrolada, nin un `Mutex`. |
| 195 | - `contar saltos` compílase a un `loop` nativo con 0 bytes de pila grazas ao TCO automático. |
| 196 | - `||map analizar ficheiro rutas` procesa os ficheiros en paralelo en todos os núcleos da túa CPU, e o bloque `||` calcula simultaneamente as filas formatadas, o total de liñas e o total de bytes. |
| 197 | - A memoria temporal libérase deterministicamente mediante **`RMM1`/`RMM2`**. |
| 198 | - E cando executas `ridgunc build rg_wc.rg -o rg-wc`, obtés un binario nativo autónomo de ~350 KB listo para copiar a `/usr/local/bin`. |
| 199 | |
| 200 | --- |
| 201 | |
| 202 | ## 8.7. Folla de Ruta Final: As 10 Regras de Ouro do Programador Ridgun |
| 203 | |
| 204 | 1. **Escribe sempre primeiro o contrato (`=>`) e despois as ecuacións (`=`):** pensar nos tipos primeiro fai que o compilador traballe para ti. |
| 205 | 2. **Deixa que `stable format` maquete as túas táboas:** escribe cun espazo e deixa que a ferramenta aliñe columnas, `=>`, `=` e `->`. |
| 206 | 3. **Lembra a regra universal do `=`:** detrás de calquera `=` podes abrir un bloque secuencial, unha árbore de gardas `|` ou un grafo paralelo `||`. |
| 207 | 4. **Usa `|.` e `||.` só cando peches un bloque aniñado para volver a un nivel superior:** mantén as árbores de decisión profundas compactas e sen ambigüidade. |
| 208 | 5. **Inmutable por defecto (`x = ...`), mutable explícito (`mut x = ...`):** usa `mut` unicamente cando un acumulador local o pida de verdade. |
| 209 | 6. **Usa operadores booleanos claros en minúsculas (`and`, `or`, `xor`, `not`) e pecha sempre as túas gardas con `| otherwise =`.** |
| 210 | 7. **Modela o teu dominio con ADTs (`type`):** usa rexistros con `with campo: valor` e tipos suma (`Option`, `Result`) en lugar de `null`. |
| 211 | 8. **Paraleliza declarando dependencias con `|| expr -> var` e `||map` / `||filter`:** deixa que o compilador verifique que non hai ciclos e reparta o traballo entre os núcleos. |
| 212 | 9. **Ponlle dimensións simbólicas aos teus Tensores (`Tensor[N, K]`) e subíndices ás túas contraccións (`a_ik @ b_kj`).** |
| 213 | 10. **Confía en `RMM` (`RMM1`/`RMM2`), acouta entradas externas con `with region` e verifica sempre con `stable check` e `stable test`.** |
| 214 | |
| 215 | Feliz viaxe programando en Ridgun! |