Skip to content
Ridgun
English

Ridgun

The language itself: a compiler that compiles itself, the stable project tool, the specification and the book

08-tensori-rmm-regioni-e-sistemi.md210 lines · 14.4 KBRaw
1# Capitolo 08: Tensori, Memoria Compilata RMM (`RMM1`/`RMM2`), Regioni e Sistemi
2
3Fin qui abbiamo visto il lato più amichevole, elegante e matematico di Ridgun: funzioni pure, tipi algebrici, *pattern matching*, alberi decisionali con `|` / `|.` e grafi paralleli `||` con `->` che sfruttano tutti i core del tuo processore senza chiederti nemmeno un misero `Mutex`.
4
5Arrivati a questo punto, il programmatore veterano di sistemi di solito alza la mano in fondo all'aula con un mezzo sorriso e chiede:
6
7> *"Tutto questo è bellissimo per calcolare fatture o compilare AST, ma cosa succede quando devo moltiplicare matrici di milioni di numeri in virgola mobile a velocità vertiginosa? Che ne è della memoria? C'è un garbage collector nascosto sotto il tappeto che aspetta il momento peggiore per congelare il mio programma per 200 millisecondi? E come parlo davvero con il sistema operativo Unix?"*
8
9Domande eccellenti. Alziamo il cofano di Ridgun per vedere come si comporta quando tocca sporcarsi le mani con i sistemi, il calcolo numerico e la memoria deterministica.
10
11---
12
13## 8.1. Il dilemma della memoria: né Garbage Collector né litigi con il Borrow Checker (`RMM1` e `RMM2`)
14
15Nel mondo dei linguaggi moderni ci sono di solito due fazioni inconciliabili:
16
171. **La fazione del Garbage Collector (Java, Go, Python, JS, Haskell, OCaml):** tu chiedi memoria allegramente e, di tanto in tanto, un camion della spazzatura invisibile ferma il traffico del tuo programma per rintracciare quali oggetti sono ancora vivi e quali no. È comodissimo... finché non stai realizzando uno strumento di sistema ultraveloce o un server con latenze strette e il camion decide di parcheggiare in doppia fila.
182. **La fazione del controllo manuale / Borrow Checker (C, C++, Rust):** non c'è garbage collector, ma in cambio devi gestire i puntatori a mano (rischiando un `Segmentation fault` o uno *use-after-free*) o passare mezzo pomeriggio a negoziare con il *borrow checker* sul perché `'a` non viva quanto `'b`.
19
20Ridgun risolve questo dilemma alla radice con **Ridgun Memory Management (`RMM`)**, un sistema deterministico a due politiche complementari (**`RMM1`** e **`RMM2`**) combinato con **regioni lessicali (`with region`)**:
21
22### Politica `RMM1` — Gestione della Memoria Compilata (CMM Lineare e Condizionale)
23Quando il compilatore conosce staticamente il flusso delle variabili locali dentro una funzione:
24- **CMM Lineare**: `ridgunc` calcola a tempo di compilazione qual è l'**ultima riga** in cui viene letta ogni variabile locale (`a`, `b`, ...) e inserisce subito dopo la sua liberazione deterministica (`rg_cmm_drop`), oppure ne trasferisce la proprietà senza copie (`rg_cmm_move`) quando la variabile viene restituita come risultato.
25- **CMM Condizionale (`ptr(a) != ptr(c)`)**: e se una funzione come `scegli cond a b` restituisce `a` o `b` in base a una condizione a tempo di esecuzione? `ridgunc` costruisce la tabella degli antenati ed emette una guardia di identità dei puntatori da **1 ciclo di CPU**: se `b` smette di essere usata mentre `c` è ancora viva, controlla `if ptr(b) != ptr(c) { free(b) }`. Il ramo non scelto viene liberato all'istante e quello scelto sopravvive intatto.
26
27### Politica `RMM2` — Conteggio per Assegnazione e Cascata Strutturale
28E cosa succede quando metti strutture dentro altre strutture (`xs = [a, b]`), riassegni variabili mutabili (`mut x = ...`) o concateni chiamate complesse? Qui entra in gioco **`RMM2`**, che governa l'intero ciclo di vita con quattro regole matematiche senza falle:
29
301. **Gli argomenti in ingresso di una funzione non si toccano (`+0` all'entrata, `-0` all'uscita)**: passare una lista o un record a una funzione non incrementa né decrementa mai i contatori invano.
312. **Ogni assegnazione a una variabile locale interna o cella di contenitore somma `+1`**:
32 - `a = [10, 20]` $\rightarrow$ il blocco `M1` nasce con contatore `1`.
33 - `xs = [a, b]` $\rightarrow$ la lista `xs` nasce con `1`, e poiché conserva riferimenti ad `a` e `b`, entrambi salgono a `2`.
34 - Se riassegni una variabile mutabile (`mut x = ...` e poi `x = nuovo`), `RMM2` applica prima **`+1`** al valore nuovo prima di applicare **`-1`** al vecchio.
353. **All'uscita dalla funzione, le variabili locali che NON vengono restituite ricevono `-1`**: se il contatore di una struttura arriva a `0`, la sua memoria viene liberata all'istante e **propaga `-1` a cascata, uno per uno, a ciascuno dei suoi figli diretti**.
364. **Il valore restituito dalla funzione è esentato in transito**: riceve il suo `-1` di variabile locale ma non viene distrutto attraversando il `return`, in modo che quando il chiamante fa `c = mia funzione ()`, quel `+1` dell'assegnazione lo lascia esattamente a `1`.
37
38```ridgun
39scegli Bool [Int] [Int] => [Int]
40scegli cond a b =
41 | cond = a
42 | otherwise = b
43
44esempio rmm2 Bool => Int
45esempio rmm2 cond =
46 a = [10, 20, 30] -- M1 (rc = 1)
47 b = [40, 50, 60] -- M2 (rc = 1)
48 c = scegli cond a b -- `c` riceve +1; il non scelto muore proprio qui (rc -> 0)
49 (conta a) + (conta c) -- Al termine, `a` e `c` ricevono -1 e tutto resta a 0 blocchi vivi
50```
51
52---
53
54## 8.2. Regioni Lessicali con Quota (`with region`): Firewall di memoria in $O(1)$
55
56Oltre a `RMM1` e `RMM2`, quando stai elaborando un pacchetto di rete esterno, un file gigantesco o un lotto di dati non affidabili e vuoi imporre un **tetto stretto di memoria RAM**, avvolgi il calcolo in un blocco **`with region`**:
57
58```ridgun
59elabora trama String => Result
60elabora trama grezzo =
61 with region 64_KB do
62 pulite = map pulisci riga (dividi righe grezzo)
63 conta validi pulite
64```
65
661. **Quota garantita in hardware/runtime**: se dentro il blocco si tenta di riservare anche un solo byte oltre `64_KB` (`65.536` byte), il runtime interrompe in modo pulito il blocco, libera tutta l'arena temporanea in tempo costante $O(1)$ e restituisce `Err OutOfMemory` senza far cadere il processo.
672. **Liberazione istantanea all'uscita**: non appena termina il blocco `with region`, tutta la memoria temporanea dell'arena scompare in un colpo solo senza lasciare frammentazione.
68
69---
70
71## 8.3. Tensori Nativi, Contrazione di Einstein (`@`) e Dimensioni Simboliche
72
73Se hai mai programmato in Python con NumPy o PyTorch, sicuramente hai sofferto il classico errore al minuto 41 di esecuzione:
74
75```text
76RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128)
77```
78
79In Ridgun, i **`Tensor`** sono cittadini nativi di prima classe e le loro dimensioni fanno parte della firma dei tipi:
80
81```ridgun
82-- Se entra (N × K) e (K × M), per forza esce (N × M)!
83moltiplica matrici Tensor[N, K] Tensor[K, M] => Tensor[N, M]
84moltiplica matrici a b = a_ik @ b_kj
85```
86
87Nota due dettagli spettacolari:
881. **Verifica statica delle dimensioni (`Tensor[N, K]`)**: l'asse interno `K` della prima matrice deve coincidere con il primo asse `K` della seconda. Se provi a passare dimensioni incompatibili, `ridgunc check` ti ferma prima di eseguire alcunché.
892. **Notazione dei pedici di Einstein (`a_ik @ b_kj`) e Hadamard (`a_ij * b_j`)**:
90 - Con **`@`**, i pedici ripetuti in tutti gli operandi (`k`) si contraggono (si sommano) e quelli unici (`i`, `j`) sopravvivono nel tensore risultato.
91 - Con **`+`, `-`, `*`, `/`**, i pedici allineano le dimensioni e fanno *broadcasting* automatico (`a_ij * b_j` moltiplica ogni riga `i` di `a` per il vettore `b_j` senza bisogno di scrivere `unsqueeze` né `reshape`).
923. **Tensori tipizzati (`Tensor[T * d1 * d2]`), pedici matematici `t_(i, j)`, viste `t_(i,)` e mappatura *in-place* (`in`)**:
93 Puoi dichiarare tensori multidimensionali con tipo scalare esplicito (`U64`, `Int`, `Float`, `Byte`) inizializzati a zero in una sola riga, indicizzarli con pedici matematici `t_(i, j)`, estrarre viste di riga `t_(i,)` o colonna `t_(,j)` senza copiare memoria, destrutturare ritorni multipli direttamente sulle celle del tensore senza allocare tuple nell'heap, e applicare funzioni *in-place* lungo un asse con `map ... in` o `||map ... in`:
94 ```ridgun
95 fn :: blamka p16 Tensor[U64 * 16] => ()
96 blamka p16 v =
97 v_(0), v_(4), v_(8), v_(12) = blamka g v_(0) v_(4) v_(8) v_(12)
98
99 fn :: comprimi blocco Tensor[U64 * 8 * 16] Tensor[U64 * 8 * 16] => Tensor[U64 * 8 * 16]
100 comprimi blocco x y =
101 r_ij = x_ij xor y_ij
102 z_ij = r_ij
103 ||map blamka p16 in z_(i,)
104 ||map blamka p16 in z_(,j)
105 z_ij xor r_ij
106 ```
107
108---
109
110## 8.4. Accesso all'Hardware (`hardware ::`) e Primitive POSIX di Sistema
111
112Lo stesso compilatore `ridgunc`, il gestore `stable` e utilità di sistema Unix sono scritti in Ridgun. A tale scopo, il linguaggio include blocchi di registri mappati in memoria (`hardware ::`) e primitive POSIX dirette:
113
114```ridgun
115hardware :: UART0 @ 0x1000_0000
116 tx_data : Int @ 0x00
117 status : Int @ 0x04
118```
119
120E per lavorare con il sistema operativo:
121
122| Primitiva | Firma | Cosa fa |
123|---|---|---|
124| `cli args` | `=> [String]` | Argomenti della riga di comando (`argv`). |
125| `read file` / `write file` | `String => String` / `String String => ()` | Lettura e scrittura integrale di file su disco. |
126| `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Lettura e scrittura binaria esatta byte per byte (`0..255`). |
127| `list dir` / `file lstat` | `String => [String]` / `String => ...` | Elenco di directory e ispezione dei metadati POSIX (`lstat`). |
128| `sha256 file` / `sha256 str` | `String => String` | Hash crittografico SHA-256 nativo in esadecimale (64 caratteri). |
129| `sha256 raw` / `sha256 bytes` | `String => String` | Digest SHA-256 binario grezzo da 32 byte (con supporto completo per byte `0x00` intermedi e zero conversioni hex). |
130| `sys exec` / `shell exec` | `... => Int` | Esecuzione di processi del sistema operativo. |
131| `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultare o cambiare la directory di lavoro. |
132| `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variabili d'ambiente del processo. |
133| `sys getuid` / `sys getgid` | `=> Int` | UID e GID reali del processo (`doas`, strumenti di sicurezza). |
134| `sys open mode` / `sys close` / `sys poll` | `String String => Int` / `Int => Int` / `Int Int Int => Int` | Descrittori PAL multipiattaforma e `poll` limitato da `with timeout`. |
135| `sys exit` | `Int => ()` | Terminare il processo con codice di uscita. |
136
137---
138
139## 8.5. Caso di Studio Reale: Costruire `rg-wc`, un'utilità Unix parallela
140
141Uniamo tutto ciò che abbiamo imparato dal Capitolo 01 fino a qui in un vero programma di sistema: una versione parallela dello strumento Unix `wc` (contatore di righe e byte per più file) scritta in puro Ridgun:
142
143```ridgun
144-- 1. Modelliamo le metriche di un file con un record ADT (Capitolo 06)
145type Metriche = file: String, righe: Int, byte: Int
146
147-- 2. Contiamo le interruzioni di riga (codice ASCII 10) con ricorsione di coda TCO (Capitolo 04)
148fn :: conta ritorni String Int Int Int => Int
149 conta ritorni testo pos fine acum =
150 | pos >= fine = acum
151 | (str char at testo pos) == 10 = conta ritorni testo (pos + 1) fine (acum + 1)
152 | otherwise = conta ritorni testo (pos + 1) fine acum
153
154fn :: numero di righe String => Int
155 numero di righe testo = conta ritorni testo 0 (str len testo) 0
156
157-- 3. Analizziamo un file e restituiamo la sua struttura Metriche
158fn :: analizza file String => Metriche
159 analizza file percorso =
160 contenuto = read file percorso
161 || numero di righe contenuto -> n_righe
162 || str len contenuto -> n_byte ||.
163 Metriche percorso n_righe n_byte
164
165-- 4. Formattiamo ogni riga e sommiamo i totali con `fold` e `||` (Capitolo 07)
166fn :: formatta metrica Metriche => String
167 formatta metrica m = "{righe m}\t{byte m}\t{file m}"
168
169fn :: somma righe Int Metriche => Int
170 somma righe acc m = acc + (righe m)
171
172fn :: somma byte Int Metriche => Int
173 somma byte acc m = acc + (byte m)
174
175fn :: genera rapporto [String] => String
176 genera rapporto percorsi =
177 metriche = ||map analizza file percorsi
178 || map formatta metrica metriche -> righe_fmt
179 || fold somma righe 0 metriche -> totale_r
180 || fold somma byte 0 metriche -> totale_b ||.
181 "{righe_fmt}\nTotale: {totale_r} righe, {totale_b} byte"
182
183fn :: main => ()
184 main =
185 echo (genera rapporto ["README.md", "AGENTS.md"])
186```
187
188Fermati un secondo a guardare quelle ~40 righe di codice:
189- Non c'è nemmeno un singolo `null`, né un'eccezione incontrollata, né un `Mutex`.
190- `conta ritorni` viene compilata in un `loop` nativo con 0 byte di stack grazie alla TCO automatica.
191- `||map analizza file percorsi` elabora i file in parallelo su tutti i core della tua CPU, e il blocco `||` calcola simultaneamente le righe formattate, il totale delle righe e il totale dei byte.
192- La memoria temporanea viene liberata deterministicamente mediante **`RMM1`/`RMM2`**.
193- E quando esegui `ridgunc build rg_wc.rg -o rg-wc`, ottieni un binario nativo autonomo da ~350 KB pronto da copiare in `/usr/local/bin`.
194
195---
196
197## 8.6. Tabella di Marcia Finale: Le 10 Regole d'Oro del Programmatore Ridgun
198
1991. **Scrivi sempre prima il contratto (`=>`) e poi le equazioni (`=`):** pensare prima ai tipi fa sì che il compilatore lavori per te.
2002. **Lascia che `stable format` impagini le tue tabelle:** scrivi con uno spazio e lascia che lo strumento allinei colonne, `=>`, `=` e `->`.
2013. **Ricorda la regola universale dell'`=`:** dietro qualsiasi `=` puoi aprire un blocco sequenziale, un albero di guardie `|` o un grafo parallelo `||`.
2024. **Usa `|.` e `||.` solo quando chiudi un blocco annidato per tornare a un livello superiore:** mantieni gli alberi decisionali profondi compatti e senza ambiguità.
2035. **Immutabile per impostazione predefinita (`x = ...`), mutabile esplicito (`mut x = ...`):** usa `mut` unicamente quando un accumulatore locale lo richiede davvero.
2046. **Usa operatori booleani chiari in minuscolo (`and`, `or`, `xor`, `not`) e chiudi sempre le tue guardie con `| otherwise =`.**
2057. **Modella il tuo dominio con ADT (`type`):** usa record con `with campo: valore` e tipi somma (`Option`, `Result`) invece di `null`.
2068. **Parallelizza dichiarando le dipendenze con `|| expr -> var` e `||map` / `||filter`:** lascia che il compilatore verifichi l'assenza di cicli e distribuisca il lavoro tra i core.
2079. **Dai dimensioni simboliche ai tuoi Tensori (`Tensor[N, K]`) e pedici alle tue contrazioni (`a_ik @ b_kj`).**
20810. **Affidati a `RMM` (`RMM1`/`RMM2`), delimita gli input esterni con `with region` e verifica sempre con `stable check` e `stable test`.**
209
210Buon viaggio programmando in Ridgun!