Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # 08. kapitulua: Tentsoreak, RMM Memoria Konpilatua (`RMM1`/`RMM2`), Eskualdeak eta Sistemak |
| 2 | |
| 3 | Honaino Ridgunen alderdirik atseginena, dotoreena eta matematikoena ikusi dugu: funtzio puruak, datu-mota aljebraikoak, *pattern matching*-a, `|` / `|.` bidezko erabaki-zuhaitzak eta zure prozesadorearen nukleo guztiak `Mutex` triste bat ere eskatu gabe aprobetxatzen dituzten `->` gezidun `||` grafo paraleloak. |
| 4 | |
| 5 | Puntu honetara iritsita, sistemetako programatzaile beteranoak eskua altxatu ohi du ikasgelaren atzealdean irribarre erdiarekin eta honako hau galdetu: |
| 6 | |
| 7 | > *"Hau guztia ederra da fakturak kalkulatzeko edo ASTak konpilatzeko, baina zer gertatzen da milioika koma higikorreko matrizeak abiadura bizian biderkatu behar ditudanean? Zer gertatzen da memoriarekin? Ba al dago alfonbra azpian ezkutatutako zabor-biltzaile (Garbage Collector) bat unerik txarrenaren zain nire programa 200 milisegundoz izozteko? Eta nola hitz egiten dut benetako Unix sistema eragilearekin?"* |
| 8 | |
| 9 | Galdera bikainak. Altxa dezagun Ridgunen kapota sistemen, zenbakizko kalkuluaren eta memoria deterministaren lokatzetara jaistea tokatzen denean nola moldatzen den ikusteko. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 8.1. Memoriaren dilema: ez Garbage Collector-ik, ez Borrow Checker-arekiko borrokarik (`RMM1` eta `RMM2`) |
| 14 | |
| 15 | Lengoaia modernoen munduan bi alde adiskidetu ezinak egon ohi dira: |
| 16 | |
| 17 | 1. **Garbage Collector-aren aldea (Java, Go, Python, JS, Haskell, OCaml):** zuk memoria alai eskatzen duzu eta, noizean behin, zabor-kamioi ikusezin batek zure programaren trafikoa gelditzen du zein objektuk jarraitzen duten bizirik eta zeintzuek ez arakatzeko. Oso erosoa da... sistemako tresna ultra-azkar bat edo latentzia zorrotzeko zerbitzari bat egiten ari zaren arte eta kamioiak bigarren ilaran aparkatzea erabakitzen duen arte. |
| 18 | 2. **Eskuzko kontrolaren / Borrow Checker-aren aldea (C, C++, Rust):** ez dago zabor-biltzailerik, baina trukean erakusleak eskuz kudeatu behar dituzu (`Segmentation fault` bat edo *use-after-free* bat jokatuz) edo arratsalde erdia *borrow checker*-arekin negoziatzen eman behar duzu zergatik `'a` ez den `'b` bezainbeste bizi azaltzeko. |
| 19 | |
| 20 | Ridgunek dilema hori errotik ebazten du **Ridgun Memory Management (`RMM`)** bidez, bi politika osagarriz (**`RMM1`** eta **`RMM2`**) osatutako sistema determinista bat, **eskualde lexikoekin (`with region`)** konbinatua: |
| 21 | |
| 22 | ### `RMM1` politika — Memoria Kudeaketa Konpilatua (CMM Lineala eta Baldintzazkoa) |
| 23 | Konpiladoreak funtzio baten barruko aldagai lokalen fluxua estatikoki ezagutzen duenean: |
| 24 | - **CMM Lineala**: `ridgunc`-ek konpilazio-garaian kalkulatzen du zein den aldagai lokal bakoitza (`a`, `b`, ...) irakurtzen den **azken lerroa** eta berehala bere askapen determinista (`rg_cmm_drop`) txertatzen du, edo bere jabetza kopiarik gabe transferitzen du (`rg_cmm_move`) aldagaia emaitza gisa itzultzen denean. |
| 25 | - **CMM Baldintzazkoa (`ptr(a) != ptr(c)`)**: eta `aukeratu cond a b` bezalako funtzio batek exekuzio-garaiko baldintza baten arabera `a` edo `b` itzultzen badu? `ridgunc`-ek arbasoen taula eraikitzen du eta **PUZ ziklo 1eko** erakusle-identitatearen zain bat igortzen du: `b` erabiltzeari uzten bazaio `c` bizirik dagoen bitartean, `if ptr(b) != ptr(c) { free(b) }` egiaztatzen du. Aukeratu ez den adarra berehala askatzen da eta aukeratutakoak osorik irauten du. |
| 26 | |
| 27 | ### `RMM2` politika — Esleipen bidezko Kontaketa eta Kaskada Estrukturala |
| 28 | Eta zer gertatzen da egiturak beste egitura batzuen barruan sartzen dituzunean (`xs = [a, b]`), aldagai aldakorrak berresleitzen dituzunean (`mut x = ...`) edo dei konplexuak kateatzen dituzunean? Hor sartzen da **`RMM2`**, bizi-ziklo osoa pitzadurarik gabeko lau arau matematikorekin gobernatzen duena: |
| 29 | |
| 30 | 1. **Funtzio baten sarrerako argumentuak ez dira ukitzen (`+0` sartzean, `-0` irtetean)**: zerrenda edo erregistro bat funtzio bati pasatzeak ez ditu inoiz kontagailuak alferrik handitzen ez txikitzen. |
| 31 | 2. **Barneko aldagai lokal batera edo edukiontzi-gelaxka batera egindako esleipen orok `+1` batzen du**: |
| 32 | - `a = [10, 20]` $\rightarrow$ `M1` blokea `1` kontagailuarekin jaiotzen da. |
| 33 | - `xs = [a, b]` $\rightarrow$ `xs` zerrenda `1`-ekin jaiotzen da, eta `a` zein `b`-rako erreferentziak gordetzen dituenez, biak `2`-ra igotzen dira. |
| 34 | - Aldagai aldakor bat berresleitzen baduzu (`mut x = ...` eta gero `x = berria`), `RMM2`-k lehenengo **`+1`** aplikatzen dio balio berriari zaharrari **`-1`** aplikatu aurretik. |
| 35 | 3. **Funtziotik irtetean, itzultzen EZ diren aldagai lokalek `-1` jasotzen dute**: egitura baten kontagailua `0`-ra iristen bada, bere memoria berehala askatzen da eta **`-1` kaskadan hedatzen dio, banan-banan, bere seme-alaba zuzen bakoitzari**. |
| 36 | 4. **Funtzioak itzulitako balioa iraganean salbuesten da**: aldagai lokaleko bere `-1` jasotzen du baina ez da suntsitzen `return`-a gurutzatzean; horrela, deitu duenak `c = nire funtzioa ()` egiten duenean, esleipenaren `+1` horrek zehazki `1`-ean uzten du. |
| 37 | |
| 38 | ```ridgun |
| 39 | aukeratu Bool [Int] [Int] => [Int] |
| 40 | aukeratu cond a b = |
| 41 | | cond = a |
| 42 | | otherwise = b |
| 43 | |
| 44 | adibidea rmm2 Bool => Int |
| 45 | adibidea rmm2 cond = |
| 46 | a = [10, 20, 30] -- M1 (rc = 1) |
| 47 | b = [40, 50, 60] -- M2 (rc = 1) |
| 48 | c = aukeratu cond a b -- `c`-k +1 jasotzen du; aukeratu ez dena hemen bertan hiltzen da (rc -> 0) |
| 49 | (zenbatu a) + (zenbatu c) -- Amaitzean, `a`-k eta `c`-k -1 jasotzen dute eta dena 0 bloke bizitan geratzen da |
| 50 | ``` |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## 8.2. Kuotadun Eskualde Lexikoak (`with region`): Memoriako suhesiak $O(1)$-ean |
| 55 | |
| 56 | `RMM1` eta `RMM2`-z gain, kanpoko sare-pakete bat, fitxategi erraldoi bat edo fidagarriak ez diren datu-sorta bat prozesatzen ari zarenean eta **RAM memoriaren sabai zorrotz bat** jarri nahi duzunean, kalkulua **`with region`** bloke batean biltzen duzu: |
| 57 | |
| 58 | ```ridgun |
| 59 | prozesatu bilbea String => Result |
| 60 | prozesatu bilbea gordina = |
| 61 | with region 64_KB do |
| 62 | garbiak = map garbitu lerroa (banatu lerroak gordina) |
| 63 | zenbatu baliozkoak garbiak |
| 64 | ``` |
| 65 | |
| 66 | 1. **Hardwarean/runtimean bermatutako kuota**: blokearen barruan `64_KB`-tik (`65.536` byte) gora byte bakar bat ere erreserbatzen saiatzen bada, runtime-ak blokea garbi abortatzen du, aldi baterako arena osoa $O(1)$ denbora konstantean askatzen du eta `Err OutOfMemory` itzultzen du prozesua bota gabe. |
| 67 | 2. **Berehalako askapena irtetean**: `with region` blokea amaitzen den bezain laster, arenako aldi baterako memoria guztia kolpe batez desagertzen da zatikatzarik utzi gabe. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## 8.3. Tentsore Natiboak, Einsteinen Uzkurdura (`@`) eta Dimentsio Sinbolikoak |
| 72 | |
| 73 | Inoiz Pythonen NumPy edo PyTorch-ekin programatu baduzu, ziur exekuzioko 41. minutuko errore klasikoa pairatu duzula: |
| 74 | |
| 75 | ```text |
| 76 | RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128) |
| 77 | ``` |
| 78 | |
| 79 | Ridgunen, **`Tensor`**-ak lehen mailako herritar natiboak dira eta haien dimentsioak mota-sinaduraren parte dira: |
| 80 | |
| 81 | ```ridgun |
| 82 | -- (N × K) eta (K × M) sartzen badira, nahitaez (N × M) ateratzen da! |
| 83 | biderkatu matrizeak Tensor[N, K] Tensor[K, M] => Tensor[N, M] |
| 84 | biderkatu matrizeak a b = a_ik @ b_kj |
| 85 | ``` |
| 86 | |
| 87 | Erreparatu bi xehetasun ikusgarri hauei: |
| 88 | 1. **Dimentsioen egiaztapen estatikoa (`Tensor[N, K]`)**: lehen matrizearen barneko `K` ardatzak bigarrenaren lehenengo `K` ardatzarekin bat etorri behar du. Bateraezinak diren dimentsioak pasatzen saiatzen bazara, `ridgunc check`-ek ezer exekutatu aurretik gelditzen zaitu. |
| 89 | 2. **Einsteinen azpiindizeen notazioa (`a_ik @ b_kj`) eta Hadamard (`a_ij * b_j`)**: |
| 90 | - **`@`**-rekin, eragile guztietan errepikatzen diren azpiindizeak (`k`) uzkurtu egiten dira (batu egiten dira) eta bakarrak direnak (`i`, `j`) emaitza-tentsorean bizirik ateratzen dira. |
| 91 | - **`+`, `-`, `*`, `/`**-rekin, azpiindizeek dimentsioak lerrokatzen dituzte eta *broadcasting* automatikoa egiten dute (`a_ij * b_j`-k `a`-ren `i` errenkada bakoitza `b_j` bektorearekin biderkatzen du `unsqueeze` edo `reshape` idatzi beharrik gabe). |
| 92 | 3. **Errendimendu handiko tentsore tipatuak (`Tensor[U64 * N]`) eta *in-place* mutazioa `t_(i, j)`-rekin**: |
| 93 | Sistema kriptografikoetan, simulazio fisikoetan edo aljebra linealean zeinurik gabeko 64 biteko osokoen bloke jarraitu baten gaineko tentsore lauak deklara ditzakezu (`Tensor[U64 * 4]`, `Tensor[U64 * 8 * 16]`), paraleloan eragin `^`-rekin (blokeko XOR natiboa) eta elementuak $O(1)$ denboran irakurri edo idatzi `t_(i)` edo `t_(i, j)` azpiindize esplizituaren sintaxiarekin: |
| 94 | ```ridgun |
| 95 | sortu egoera blokea => Tensor[U64 * 2 * 2] |
| 96 | sortu egoera blokea = |
| 97 | mut m = tensor fill [2, 2] (u64 0) |
| 98 | m_(0, 1) = u64 0x6a09e667f3bcc908 |
| 99 | m |
| 100 | ``` |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## 8.4. Hardwarerako Sarbidea (`hardware ::`) eta Sistemen POSIX Primitibak |
| 105 | |
| 106 | `ridgunc` konpiladorea bera, `stable` kudeatzailea eta Unix sistemetako tresnak Ridgunen idatzita daude. Horretarako, lengoaiak memorian mapatutako erregistro-blokeak (`hardware ::`) eta POSIX primitiba zuzenak barne hartzen ditu: |
| 107 | |
| 108 | ```ridgun |
| 109 | hardware :: UART0 @ 0x1000_0000 |
| 110 | tx_data : Int @ 0x00 |
| 111 | status : Int @ 0x04 |
| 112 | ``` |
| 113 | |
| 114 | Eta sistema eragilearekin lan egiteko: |
| 115 | |
| 116 | | Primitiba | Sinadura | Zer egiten duen | |
| 117 | |---|---|---| |
| 118 | | `cli args` | `=> [String]` | Komando-lerroko argumentuak (`argv`). | |
| 119 | | `read file` / `write file` | `String => String` / `String String => ()` | Diskoko fitxategien irakurketa eta idazketa osoa. | |
| 120 | | `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Bytez byteko (`0..255`) irakurketa eta idazketa bitar zehatza. | |
| 121 | | `list dir` / `file lstat` | `String => [String]` / `String => ...` | Direktorioen zerrendatzea eta POSIX metadatuen ikuskapena (`lstat`). | |
| 122 | | `sha256 file` / `sha256 str` / `sha256 raw` | `String => String` | SHA-256 hash kriptografiko natiboa (64 karaktereko hamaseitarrean edo 32 byteko laburpen bitar gordinean `sha256 raw` / `sha256 bytes`-ekin). | |
| 123 | | `sys exec` / `shell exec` | `... => Int` | Sistema eragileko prozesuen exekuzioa. | |
| 124 | | `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Laneko direktorioa kontsultatu edo aldatu. | |
| 125 | | `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Prozesuaren ingurune-aldagaiak. | |
| 126 | | `sys getuid` / `sys getgid` | `=> Int` | Prozesuaren benetako UID eta GIDa (`doas`, segurtasun-tresnak). | |
| 127 | | `sys exit` | `Int => ()` | Prozesua irteera-kodearekin amaitu. | |
| 128 | |
| 129 | --- |
| 130 | |
| 131 | ## 8.5. Liburutegi Estandarren Ekosistema (`ridgun-libs`) |
| 132 | |
| 133 | Konpiladorearen primitibez gain, Ridgunen ekosistema ofizialak Ridgunen bertan osorik idatzitako **32 liburutegi puru (`.rglib`)** biltzen ditu, edozein proiektutan `import <izena>` bidez inporta ditzakezunak: |
| 134 | |
| 135 | | Kategoria | Eskuragarri dauden liburutegiak | Zer eskaintzen duten | |
| 136 | |---|---|---| |
| 137 | | **Bildumak, Testua eta Datuak** | `collections`, `set`, `string`, `regex`, `utf8`, `fmt`, `csv`, `json`, `toml`, `yaml`, `xml`, `html`, `markdown`, `base64`, `hex`, `compress`, `sql` | Mapak, multzoak, adierazpen erregularrak, egituratutako analizatzaile/serializatzaileak, kodetzea eta kontsulta-motorrak. | |
| 138 | | **Matematika, Denbora eta Ausazkotasuna** | `math`, `random`, `datetime`, `uuid`, `log`, `cli`, `bench`, `test` | Funtzio matematikoak, PRNG determinista eta sistemakoa, ISO-8601 datak, UUIDak, test-markoak eta `clock_gettime` bidezko erreferentzia-probak. | |
| 139 | | **Sistemak, Sarea eta Weba** | `fs`, `path`, `process`, `env`, `net`, `http`, `uri` | Goi-mailako fitxategi-sistema, bideen manipulazioa, azpiprozesuak, TCP/UDP socketak eta HTTP bezero/zerbitzaria. | |
| 140 | | **Kriptografia eta Segurtasuna (`crypto`)** | `crypto` (`sha256`, `hmac`, `hkdf`, `pbkdf2`, `argon2id`, `chacha20`, `aes`, `x25519`, `ed25519`, `csprng`) | RFC/NIST bektore ofizialekin egiaztatutako suite kriptografikoa, `U64`, `Tensor[U64 * ...]` eta `sha256 raw` gainean inplementatua, heap-ean erreserbarik egiten ez duen tuplen inlining optimizazioarekin. | |
| 141 | |
| 142 | --- |
| 143 | |
| 144 | ## 8.6. Benetako Kasu-Azterketa: `rg-wc` eraikitzen, Unix tresna paralelo bat |
| 145 | |
| 146 | Lotu dezagun 01. kapitulutik honaino ikasitako guztia sistemetako benetako programa batean: Unixeko `wc` tresnaren bertsio paralelo bat (fitxategi anitzetarako lerro- eta byte-kontagailua) Ridgun puruan idatzia: |
| 147 | |
| 148 | ```ridgun |
| 149 | -- 1. Fitxategi baten metrikak ADT erregistro batekin modelatzen ditugu (06. kapitulua) |
| 150 | type Metrikak = fitxategia: String, lerroak: Int, byteak: Int |
| 151 | |
| 152 | -- 2. Lerro-jauziak (ASCII 10 kodea) TCO isats-errekurtsioarekin zenbatzen ditugu (04. kapitulua) |
| 153 | fn :: zenbatu jauziak String Int Int Int => Int |
| 154 | zenbatu jauziak testua pos ama met = |
| 155 | | pos >= ama = met |
| 156 | | (str char at testua pos) == 10 = zenbatu jauziak testua (pos + 1) ama (met + 1) |
| 157 | | otherwise = zenbatu jauziak testua (pos + 1) ama met |
| 158 | |
| 159 | fn :: lerro kopurua String => Int |
| 160 | lerro kopurua testua = zenbatu jauziak testua 0 (str len testua) 0 |
| 161 | |
| 162 | -- 3. Fitxategi bat aztertzen dugu eta bere Metrikak egitura itzultzen dugu |
| 163 | fn :: aztertu fitxategia String => Metrikak |
| 164 | aztertu fitxategia bidea = |
| 165 | edukia = read file bidea |
| 166 | || lerro kopurua edukia -> n_lerro |
| 167 | || str len edukia -> n_byte ||. |
| 168 | Metrikak bidea n_lerro n_byte |
| 169 | |
| 170 | -- 4. Errenkada bakoitza formateatzen dugu eta guztizkoak `fold` eta `||`-rekin batzen ditugu (07. kapitulua) |
| 171 | fn :: formateatu metrika Metrikak => String |
| 172 | formateatu metrika m = "{lerroak m}\t{byteak m}\t{fitxategia m}" |
| 173 | |
| 174 | fn :: batu lerroak Int Metrikak => Int |
| 175 | batu lerroak acc m = acc + (lerroak m) |
| 176 | |
| 177 | fn :: batu byteak Int Metrikak => Int |
| 178 | batu byteak acc m = acc + (byteak m) |
| 179 | |
| 180 | fn :: sortu txostena [String] => String |
| 181 | sortu txostena bideak = |
| 182 | metrikak = ||map aztertu fitxategia bideak |
| 183 | || map formateatu metrika metrikak -> errenkadak |
| 184 | || fold batu lerroak 0 metrikak -> guztira_l |
| 185 | || fold batu byteak 0 metrikak -> guztira_b ||. |
| 186 | "{errenkadak}\nGuztira: {guztira_l} lerro, {guztira_b} byte" |
| 187 | |
| 188 | fn :: main => () |
| 189 | main = |
| 190 | echo (sortu txostena ["README.md", "AGENTS.md"]) |
| 191 | ``` |
| 192 | |
| 193 | Gelditu segundo batez ~40 kode-lerro horiei begiratzeko: |
| 194 | - Ez dago `null` bakar bat ere, ez kontrolik gabeko salbuespenik, ez `Mutex`-ik. |
| 195 | - `zenbatu jauziak` 0 pila-byteko `loop` natibo batera konpilatzen da TCO automatikoari esker. |
| 196 | - `||map aztertu fitxategia bideak` deiak fitxategiak paraleloan prozesatzen ditu zure PUZaren nukleo guztietan, eta `||` blokeak aldi berean kalkulatzen ditu formateatutako errenkadak, lerroen guztizkoa eta byteen guztizkoa. |
| 197 | - Aldi baterako memoria modu deterministan askatzen da **`RMM1`/`RMM2`** bidez. |
| 198 | - Eta `ridgunc build rg_wc.rg -o rg-wc` exekutatzen duzunean, `/usr/local/bin`-era kopiatzeko prest dagoen ~350 KB-ko bitar natibo autonomo bat lortzen duzu. |
| 199 | |
| 200 | --- |
| 201 | |
| 202 | ## 8.7. Azken Ibilbide-Orria: Ridgun Programatzailearen Urrezko 10 Arauak |
| 203 | |
| 204 | 1. **Idatzi beti lehenengo kontratua (`=>`) eta ondoren ekuazioak (`=`):** motetan lehenengo pentsatzeak konpiladorea zure alde lan egitera bultzatzen du. |
| 205 | 2. **Utzi `stable format`-i zure taulak maketatzen:** idatzi zuriune batekin eta utzi tresnari zutabeak, `=>`, `=` eta `->` lerrokatzen. |
| 206 | 3. **Gogoratu `=`-ren arau unibertsala:** edozein `=`-ren atzean bloke sekuentzial bat, `|` zain-zuhaitz bat edo `||` grafo paralelo bat ireki dezakezu. |
| 207 | 4. **Erabili `|.` eta `||.` habiaratutako bloke bat goiko maila batera itzultzeko ixten duzunean soilik:** mantendu erabaki-zuhaitz sakonak trinko eta anbiguotasunik gabe. |
| 208 | 5. **Aldaezina lehenespenez (`x = ...`), aldakor esplizitua (`mut x = ...`):** erabili `mut` metatzaile lokal batek benetan eskatzen duenean soilik. |
| 209 | 6. **Erabili eragile boolear argiak letra xehez (`and`, `or`, `xor`, `not`) eta itxi beti zure zainak `| otherwise =`-rekin.** |
| 210 | 7. **Modelatu zure domeinua ADTekin (`type`):** erabili erregistroak `with eremua: balioa`-rekin eta batuketa-motak (`Option`, `Result`) `null`-en ordez. |
| 211 | 8. **Paralelizatu mendekotasunak `|| adier -> ald` eta `||map` / `||filter`-ekin deklaratuz:** utzi konpiladoreari ziklorik ez dagoela egiaztatzen eta lana nukleoen artean banatzen. |
| 212 | 9. **Jarri dimentsio sinbolikoak zure Tentsoreei (`Tensor[N, K]`) eta azpiindizeak zure uzkurdurei (`a_ik @ b_kj`).** |
| 213 | 10. **Fido izan `RMM`-z (`RMM1`/`RMM2`), mugatu kanpoko sarrerak `with region`-ekin eta egiaztatu beti `stable check` eta `stable test`-ekin.** |
| 214 | |
| 215 | Bidaia zoriontsua Ridgunen programatzen! |