Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # Capítol 08: Tensors, Memòria Compilada RMM (`RMM1`/`RMM2`), Regions i Sistemes |
| 2 | |
| 3 | Fins aquí hem vist el costat més amable, elegant i matemàtic de Ridgun: funcions pures, tipus algebraics, *pattern matching*, arbres de decisió amb `|` / `|.` i grafs paral·lels `||` amb `->` que aprofiten tots els nuclis del teu processador sense demanar-te ni un trist `Mutex`. |
| 4 | |
| 5 | Arribats a aquest punt, el programador veterà de sistemes sol aixecar la mà al fons de l'aula amb mig somriure i preguntar: |
| 6 | |
| 7 | > *"Tot això és preciós per calcular factures o compilar ASTs, però què passa quan necessito multiplicar matrius de milions de flotants a velocitat de vertigen? Què passa amb la memòria? Hi ha un recol·lector de brossa (Garbage Collector) amagat sota la catifa esperant el pitjor moment per congelar el meu programa durant 200 mil·lisegons? I com parlo amb el sistema operatiu Unix de debò?"* |
| 8 | |
| 9 | Excel·lents preguntes. Aixequem el capó de Ridgun per veure com se les gasta quan toca baixar al fang dels sistemes, el càlcul numèric i la memòria determinista. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 8.1. El dilema de la memòria: ni Garbage Collector ni baralles amb el Borrow Checker (`RMM1` i `RMM2`) |
| 14 | |
| 15 | Al món dels llenguatges moderns sol haver-hi dos bàndols irreconciliables: |
| 16 | |
| 17 | 1. **El bàndol del Garbage Collector (Java, Go, Python, JS, Haskell, OCaml):** tu demanes memòria alegrement i, de tant en tant, un camió de la brossa invisible atura el trànsit del teu programa per rastrejar quins objectes continuen vius i quins no. És comodíssim... fins que estàs fent una eina de sistema ultraràpida o un servidor amb latències estrictes i el camió decideix aparcar en doble fila. |
| 18 | 2. **El bàndol del control manual / Borrow Checker (C, C++, Rust):** no hi ha recol·lector de brossa, però a canvi has de gestionar punters a mà (jugant-te un `Segmentation fault` o un *use-after-free*) o passar-te mitja tarda negociant amb el *borrow checker* per què `'a` no viu tant com `'b`. |
| 19 | |
| 20 | Ridgun resol aquest dilema de soca-rel amb **Ridgun Memory Management (`RMM`)**, un sistema determinista de dues polítiques complementàries (**`RMM1`** i **`RMM2`**) combinat amb **regions lèxiques (`with region`)**: |
| 21 | |
| 22 | ### Política `RMM1` — Gestió de Memòria Compilada (CMM Lineal i Condicional) |
| 23 | Quan el compilador coneix estàticament el flux de les variables locals dins d'una funció: |
| 24 | - **CMM Lineal**: `ridgunc` calcula en temps de compilació quina és l'**última línia** en què es llegeix cada variable local (`a`, `b`, ...) i insereix just després el seu alliberament determinista (`rg_cmm_drop`), o transfereix la seva propietat sense còpies (`rg_cmm_move`) quan la variable es retorna com a resultat. |
| 25 | - **CMM Condicional (`ptr(a) != ptr(c)`)**: i si una funció com `triar cond a b` retorna `a` o `b` segons una condició en temps d'execució? `ridgunc` construeix la taula d'ancestres i emet una guarda d'identitat de punters d'**1 cicle de CPU**: si `b` deixa de fer-se servir mentre `c` continua viva, comprova `if ptr(b) != ptr(c) { free(b) }`. La branca no triada s'allibera a l'instant i la triada sobreviu intacta. |
| 26 | |
| 27 | ### Política `RMM2` — Recompte per Assignació i Cascada Estructural |
| 28 | I què passa quan fiques estructures dins d'altres estructures (`xs = [a, b]`), reassignes variables mutables (`mut x = ...`) o encadenes crides complexes? Aquí entra **`RMM2`**, que governa tot el cicle de vida amb quatre regles matemàtiques sense fissures: |
| 29 | |
| 30 | 1. **Els arguments d'entrada d'una funció no es toquen (`+0` en entrar, `-0` en sortir)**: passar una llista o un registre a una funció mai no incrementa ni decrementa comptadors en va. |
| 31 | 2. **Tota assignació a una variable local interna o cel·la de contenidor suma `+1`**: |
| 32 | - `a = [10, 20]` $\rightarrow$ el bloc `M1` neix amb comptador `1`. |
| 33 | - `xs = [a, b]` $\rightarrow$ la llista `xs` neix amb `1`, i com que desa referències a `a` i `b`, tots dos pugen a `2`. |
| 34 | - Si reassignes una variable mutable (`mut x = ...` i després `x = nou`), `RMM2` aplica primer **`+1`** al valor nou abans d'aplicar **`-1`** a l'antic. |
| 35 | 3. **En sortir de la funció, les variables locals que NO es retornen reben `-1`**: si el comptador d'una estructura arriba a `0`, s'allibera la seva memòria a l'instant i **propaga `-1` en cascada, un per un, a cadascun dels seus fills directes**. |
| 36 | 4. **El valor retornat per la funció s'eximeix en trànsit**: rep el seu `-1` de variable local però no es destrueix en creuar el `return`, de manera que quan qui ha cridat fa `c = la meva funcio ()`, aquell `+1` de l'assignació el deixa exactament en `1`. |
| 37 | |
| 38 | ```ridgun |
| 39 | triar Bool [Int] [Int] => [Int] |
| 40 | triar cond a b = |
| 41 | | cond = a |
| 42 | | otherwise = b |
| 43 | |
| 44 | exemple rmm2 Bool => Int |
| 45 | exemple rmm2 cond = |
| 46 | a = [10, 20, 30] -- M1 (rc = 1) |
| 47 | b = [40, 50, 60] -- M2 (rc = 1) |
| 48 | c = triar cond a b -- `c` rep +1; el no triat mor aquí mateix (rc -> 0) |
| 49 | (comptar a) + (comptar c) -- En acabar, `a` i `c` reben -1 i tot queda en 0 blocs vius |
| 50 | ``` |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## 8.2. Regions Lèxiques amb Quota (`with region`): Tallafocs de memòria en $O(1)$ |
| 55 | |
| 56 | A més d'`RMM1` i `RMM2`, quan estàs processant un paquet de xarxa extern, un fitxer gegant o un lot de dades no confiables i vols posar un **sostre estricte de memòria RAM**, envoltes el càlcul en un bloc **`with region`**: |
| 57 | |
| 58 | ```ridgun |
| 59 | processar trama String => Result |
| 60 | processar trama cru = |
| 61 | with region 64_KB do |
| 62 | netes = map netejar linia (dividir linies cru) |
| 63 | comptar valids netes |
| 64 | ``` |
| 65 | |
| 66 | 1. **Quota garantida en maquinari/runtime**: si dins del bloc s'intenta reservar ni un sol byte per sobre de `64_KB` (`65.536` bytes), el runtime avorta netament el bloc, allibera tota l'arena temporal en temps constant $O(1)$ i retorna `Err OutOfMemory` sense tombar el procés. |
| 67 | 2. **Alliberament instantani en sortir**: tan bon punt acaba el bloc `with region`, tota la memòria temporal de l'arena desapareix de cop sense deixar fragmentació. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## 8.3. Tensors Natius, Contracció d'Einstein (`@`) i Dimensions Simbòliques |
| 72 | |
| 73 | Si alguna vegada has programat en Python amb NumPy o PyTorch, segur que has patit el clàssic error al minut 41 d'execució: |
| 74 | |
| 75 | ```text |
| 76 | RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128) |
| 77 | ``` |
| 78 | |
| 79 | En Ridgun, els **`Tensor`** són ciutadans natius de primera classe i les seves dimensions formen part de la signatura de tipus: |
| 80 | |
| 81 | ```ridgun |
| 82 | -- Si entra (N × K) i (K × M), per nassos surt (N × M)! |
| 83 | multiplicar matrius Tensor[N, K] Tensor[K, M] => Tensor[N, M] |
| 84 | multiplicar matrius a b = a_ik @ b_kj |
| 85 | ``` |
| 86 | |
| 87 | Fixa't en tres detalls espectaculars: |
| 88 | 1. **Verificació estàtica de dimensions (`Tensor[N, K]`)**: l'eix intern `K` de la primera matriu ha de coincidir amb el primer eix `K` de la segona. Si intentes passar dimensions incompatibles, `ridgunc check` t'atura abans d'executar res. |
| 89 | 2. **Notació de subíndexs d'Einstein (`a_ik @ b_kj`) i Hadamard (`a_ij * b_j`)**: |
| 90 | - Amb **`@`**, els subíndexs repetits en tots els operands (`k`) es contrauen (es sumen) i els únics (`i`, `j`) sobreviuen al tensor resultat. |
| 91 | - Amb **`+`, `-`, `*`, `/`**, els subíndexs alineen dimensions i fan *broadcasting* automàtic (`a_ij * b_j` multiplica cada fila `i` d'`a` pel vector `b_j` sense necessitat d'escriure `unsqueeze` ni `reshape`). |
| 92 | 3. **Tensors tipats d'alt rendiment (`Tensor[U64 * N]`) i mutació *in-place* amb `t_(i, j)`**: |
| 93 | En sistemes criptogràfics, simulacions físiques o àlgebra lineal pots declarar tensors plans sobre un bloc contigu d'enters sense signe de 64 bits (`Tensor[U64 * 4]`, `Tensor[U64 * 8 * 16]`), operar en paral·lel amb `^` (XOR natiu de bloc) i llegir o escriure elements en temps $O(1)$ amb la sintaxi de subíndex explícit `t_(i)` o `t_(i, j)`: |
| 94 | ```ridgun |
| 95 | crear bloc estat => Tensor[U64 * 2 * 2] |
| 96 | crear bloc estat = |
| 97 | mut m = tensor fill [2, 2] (u64 0) |
| 98 | m_(0, 1) = u64 0x6a09e667f3bcc908 |
| 99 | m |
| 100 | ``` |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## 8.4. Accés a Maquinari (`hardware ::`) i Primitives POSIX de Sistemes |
| 105 | |
| 106 | El mateix compilador `ridgunc`, el gestor `stable` i utilitats de sistemes Unix estan escrits en Ridgun. Per a això, el llenguatge inclou blocs de registres mapejats en memòria (`hardware ::`) i primitives POSIX directes: |
| 107 | |
| 108 | ```ridgun |
| 109 | hardware :: UART0 @ 0x1000_0000 |
| 110 | tx_data : Int @ 0x00 |
| 111 | status : Int @ 0x04 |
| 112 | ``` |
| 113 | |
| 114 | I per treballar amb el sistema operatiu: |
| 115 | |
| 116 | | Primitiva | Signatura | Què fa | |
| 117 | |---|---|---| |
| 118 | | `cli args` | `=> [String]` | Arguments de línia d'ordres (`argv`). | |
| 119 | | `read file` / `write file` | `String => String` / `String String => ()` | Lectura i escriptura íntegra de fitxers en disc. | |
| 120 | | `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Lectura i escriptura binària exacta byte a byte (`0..255`). | |
| 121 | | `list dir` / `file lstat` | `String => [String]` / `String => ...` | Llistat de directoris i inspecció de metadades POSIX (`lstat`). | |
| 122 | | `sha256 file` / `sha256 str` / `sha256 raw` | `String => String` | Resum criptogràfic SHA-256 natiu (en hexadecimal de 64 caràcters o digest binari cru de 32 bytes amb `sha256 raw` / `sha256 bytes`). | |
| 123 | | `sys exec` / `shell exec` | `... => Int` | Execució de processos del sistema operatiu. | |
| 124 | | `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultar o canviar el directori de treball. | |
| 125 | | `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variables d'entorn del procés. | |
| 126 | | `sys getuid` / `sys getgid` | `=> Int` | UID i GID reals del procés (`doas`, eines de seguretat). | |
| 127 | | `sys exit` | `Int => ()` | Acabar el procés amb codi de sortida. | |
| 128 | |
| 129 | --- |
| 130 | |
| 131 | ## 8.5. L'Ecosistema de Llibreries Estàndard (`ridgun-libs`) |
| 132 | |
| 133 | A més de les primitives del compilador, l'ecosistema oficial de Ridgun inclou **32 llibreries pures (`.rglib`)** escrites íntegrament en Ridgun que pots importar amb `import <nom>` en qualsevol projecte: |
| 134 | |
| 135 | | Categoria | Llibreries disponibles | Què aporten | |
| 136 | |---|---|---| |
| 137 | | **Col·leccions, Text i Dades** | `collections`, `set`, `string`, `regex`, `utf8`, `fmt`, `csv`, `json`, `toml`, `yaml`, `xml`, `html`, `markdown`, `base64`, `hex`, `compress`, `sql` | Mapes, conjunts, expressions regulars, parsers/serialitzadors estructurats, codificació i motors de consultes. | |
| 138 | | **Matemàtiques, Temps i Aleatorietat** | `math`, `random`, `datetime`, `uuid`, `log`, `cli`, `bench`, `test` | Funcions matemàtiques, PRNG determinista i de sistema, dates ISO-8601, UUIDs, marcs de tests i benchmarks amb `clock_gettime`. | |
| 139 | | **Sistemes, Xarxa i Web** | `fs`, `path`, `process`, `env`, `net`, `http`, `uri` | Sistema de fitxers d'alt nivell, manipulació de rutes, sub-processos, sockets TCP/UDP i client/servidor HTTP. | |
| 140 | | **Criptografia i Seguretat (`crypto`)** | `crypto` (`sha256`, `hmac`, `hkdf`, `pbkdf2`, `argon2id`, `chacha20`, `aes`, `x25519`, `ed25519`, `csprng`) | Suite criptogràfica verificada amb vectors oficials RFC/NIST, implementada sobre `U64`, `Tensor[U64 * ...]` i `sha256 raw` amb optimització d'inlining de tuples sense reserves al heap. | |
| 141 | |
| 142 | --- |
| 143 | |
| 144 | ## 8.6. Cas d'Estudi Real: Construint `rg-wc`, una utilitat Unix paral·lela |
| 145 | |
| 146 | Unirem tot el que hem après des del Capítol 01 fins aquí en un programa real de sistemes: una versió paral·lela de l'eina Unix `wc` (comptador de línies i bytes per a múltiples fitxers) escrita en Ridgun pur: |
| 147 | |
| 148 | ```ridgun |
| 149 | -- 1. Modelem les mètriques d'un fitxer amb un registre ADT (Capítol 06) |
| 150 | type Metriques = fitxer: String, linies: Int, bytes: Int |
| 151 | |
| 152 | -- 2. Comptem salts de línia (codi ASCII 10) amb recursió de cua TCO (Capítol 04) |
| 153 | fn :: comptar salts String Int Int Int => Int |
| 154 | comptar salts text pos fi acum = |
| 155 | | pos >= fi = acum |
| 156 | | (str char at text pos) == 10 = comptar salts text (pos + 1) fi (acum + 1) |
| 157 | | otherwise = comptar salts text (pos + 1) fi acum |
| 158 | |
| 159 | fn :: nombre de linies String => Int |
| 160 | nombre de linies text = comptar salts text 0 (str len text) 0 |
| 161 | |
| 162 | -- 3. Analitzem un fitxer i retornem la seva estructura Metriques |
| 163 | fn :: analitzar fitxer String => Metriques |
| 164 | analitzar fitxer ruta = |
| 165 | contingut = read file ruta |
| 166 | || nombre de linies contingut -> n_linies |
| 167 | || str len contingut -> n_bytes ||. |
| 168 | Metriques ruta n_linies n_bytes |
| 169 | |
| 170 | -- 4. Formatem cada fila i sumem totals amb `fold` i `||` (Capítol 07) |
| 171 | fn :: formatar metrica Metriques => String |
| 172 | formatar metrica m = "{linies m}\t{bytes m}\t{fitxer m}" |
| 173 | |
| 174 | fn :: sumar linies Int Metriques => Int |
| 175 | sumar linies acc m = acc + (linies m) |
| 176 | |
| 177 | fn :: sumar bytes Int Metriques => Int |
| 178 | sumar bytes acc m = acc + (bytes m) |
| 179 | |
| 180 | fn :: generar informe [String] => String |
| 181 | generar informe rutes = |
| 182 | metriques = ||map analitzar fitxer rutes |
| 183 | || map formatar metrica metriques -> files |
| 184 | || fold sumar linies 0 metriques -> total_l |
| 185 | || fold sumar bytes 0 metriques -> total_b ||. |
| 186 | "{files}\nTotal: {total_l} línies, {total_b} bytes" |
| 187 | |
| 188 | fn :: main => () |
| 189 | main = |
| 190 | echo (generar informe ["README.md", "AGENTS.md"]) |
| 191 | ``` |
| 192 | |
| 193 | Atura't un segon a mirar aquestes ~40 línies de codi: |
| 194 | - No hi ha ni un sol `null`, ni una excepció descontrolada, ni un `Mutex`. |
| 195 | - `comptar salts` es compila a un `loop` natiu amb 0 bytes de pila gràcies al TCO automàtic. |
| 196 | - `||map analitzar fitxer rutes` processa els fitxers en paral·lel en tots els nuclis de la teva CPU, i el bloc `||` calcula simultàniament les files formatades, el total de línies i el total de bytes. |
| 197 | - La memòria temporal s'allibera determinísticament mitjançant **`RMM1`/`RMM2`**. |
| 198 | - I quan executes `ridgunc build rg_wc.rg -o rg-wc`, obtens un binari natiu autònom de ~350 KB a punt per copiar a `/usr/local/bin`. |
| 199 | |
| 200 | --- |
| 201 | |
| 202 | ## 8.7. Full de Ruta Final: Les 10 Regles d'Or del Programador Ridgun |
| 203 | |
| 204 | 1. **Escriu sempre primer el contracte (`=>`) i després les equacions (`=`):** pensar en els tipus primer fa que el compilador treballi per a tu. |
| 205 | 2. **Deixa que `stable format` maqueti les teves taules:** escriu amb un espai i deixa que l'eina alineï columnes, `=>`, `=` i `->`. |
| 206 | 3. **Recorda la regla universal de l'`=`:** darrere de qualsevol `=` pots obrir un bloc seqüencial, un arbre de guardes `|` o un graf paral·lel `||`. |
| 207 | 4. **Fes servir `|.` i `||.` només quan tanquis un bloc imbricat per tornar a un nivell superior:** mantén els arbres de decisió profunds compactes i sense ambigüitat. |
| 208 | 5. **Immutable per defecte (`x = ...`), mutable explícit (`mut x = ...`):** fes servir `mut` únicament quan un acumulador local ho demani de debò. |
| 209 | 6. **Fes servir operadors booleans clars en minúscules (`and`, `or`, `xor`, `not`) i tanca sempre les teves guardes amb `| otherwise =`.** |
| 210 | 7. **Modela el teu domini amb ADTs (`type`):** fes servir registres amb `with camp: valor` i tipus suma (`Option`, `Result`) en lloc de `null`. |
| 211 | 8. **Paral·lelitza declarant dependències amb `|| expr -> var` i `||map` / `||filter`:** deixa que el compilador verifiqui que no hi ha cicles i reparteixi la feina entre els nuclis. |
| 212 | 9. **Posa dimensions simbòliques als teus Tensors (`Tensor[N, K]`) i subíndexs a les teves contraccions (`a_ik @ b_kj`).** |
| 213 | 10. **Confia en `RMM` (`RMM1`/`RMM2`), afita entrades externes amb `with region` i verifica sempre amb `stable check` i `stable test`.** |
| 214 | |
| 215 | Bon viatge programant en Ridgun! |