Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | # Capítulo 08: Tensores, Memória Compilada RMM (`RMM1`/`RMM2`), Regiões e Sistemas |
| 2 | |
| 3 | Até aqui vimos o lado mais amável, elegante e matemático do Ridgun: funções puras, tipos algébricos, *pattern matching*, árvores de decisão com `|` / `|.` e grafos paralelos `||` com `->` que aproveitam todos os núcleos do seu processador sem lhe pedir um único `Mutex`. |
| 4 | |
| 5 | Chegados a este ponto, o programador veterano de sistemas costuma levantar a mão no fundo da sala com um meio sorriso e perguntar: |
| 6 | |
| 7 | > *"Tudo isto é lindo para calcular faturas ou compilar ASTs, mas o que acontece quando preciso de multiplicar matrizes de milhões de números em vírgula flutuante a uma velocidade vertiginosa? O que acontece com a memória? Há um coletor de lixo (Garbage Collector) escondido debaixo do tapete à espera do pior momento para congelar o meu programa durante 200 milissegundos? E como falo com o sistema operativo Unix a sério?"* |
| 8 | |
| 9 | Excelentes perguntas. Vamos levantar o capô do Ridgun para ver como ele se comporta quando toca a descer ao terreno dos sistemas, do cálculo numérico e da memória determinística. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## 8.1. O dilema da memória: nem Garbage Collector nem lutas com o Borrow Checker (`RMM1` e `RMM2`) |
| 14 | |
| 15 | No mundo das linguagens modernas costuma haver dois campos irreconciliáveis: |
| 16 | |
| 17 | 1. **O campo do Garbage Collector (Java, Go, Python, JS, Haskell, OCaml):** pede memória alegremente e, de vez em quando, um camião do lixo invisível pára o trânsito do seu programa para rastrear que objetos continuam vivos e quais não. É comodíssimo... até estar a fazer uma ferramenta de sistema ultrarrápida ou um servidor com latências estritas e o camião decidir estacionar em segunda fila. |
| 18 | 2. **O campo do controlo manual / Borrow Checker (C, C++, Rust):** não há coletor de lixo, mas em troca tem de gerir ponteiros à mão (arriscando um `Segmentation fault` ou um *use-after-free*) ou passar meia tarde a negociar com o *borrow checker* porque é que `'a` não vive tanto tempo como `'b`. |
| 19 | |
| 20 | O Ridgun resolve este dilema pela raiz com o **Ridgun Memory Management (`RMM`)**, um sistema determinístico de duas políticas complementares (**`RMM1`** e **`RMM2`**) combinado com **regiões léxicas (`with region`)**: |
| 21 | |
| 22 | ### Política `RMM1` — Gestão de Memória Compilada (CMM Linear e Condicional) |
| 23 | Quando o compilador conhece estaticamente o fluxo das variáveis locais dentro de uma função: |
| 24 | - **CMM Linear**: o `ridgunc` calcula em tempo de compilação qual é a **última linha** em que se lê cada variável local (`a`, `b`, ...) e insere logo a seguir a sua libertação determinística (`rg_cmm_drop`), ou transfere a sua propriedade sem cópias (`rg_cmm_move`) quando a variável é devolvida como resultado. |
| 25 | - **CMM Condicional (`ptr(a) != ptr(c)`)**: e se uma função como `escolher cond a b` devolver `a` ou `b` segundo uma condição em tempo de execução? O `ridgunc` constrói a tabela de ancestrais e emite uma guarda de identidade de ponteiros de **1 ciclo de CPU**: se `b` deixar de ser usada enquanto `c` continua viva, verifica `if ptr(b) != ptr(c) { free(b) }`. O ramo não escolhido é libertado no instante e o escolhido sobrevive intacto. |
| 26 | |
| 27 | ### Política `RMM2` — Contagem por Atribuição e Cascata Estrutural |
| 28 | E o que acontece quando coloca estruturas dentro de outras estruturas (`xs = [a, b]`), reatribui variáveis mutáveis (`mut x = ...`) ou encadeia chamadas complexas? Aí entra a **`RMM2`**, que governa todo o ciclo de vida com quatro regras matemáticas sem falhas: |
| 29 | |
| 30 | 1. **Os argumentos de entrada de uma função não são tocados (`+0` ao entrar, `-0` ao sair)**: passar uma lista ou um registo a uma função jamais incrementa nem decrementa contadores em vão. |
| 31 | 2. **Toda a atribuição a uma variável local interna ou célula de contentor soma `+1`**: |
| 32 | - `a = [10, 20]` $\rightarrow$ o bloco `M1` nasce com contador `1`. |
| 33 | - `xs = [a, b]` $\rightarrow$ a lista `xs` nasce com `1`, e como guarda referências a `a` e `b`, ambos sobem para `2`. |
| 34 | - Se reatribuir uma variável mutável (`mut x = ...` e depois `x = novo`), a `RMM2` aplica primeiro **`+1`** ao valor novo antes de aplicar **`-1`** ao antigo. |
| 35 | 3. **Ao sair da função, as variáveis locais que NÃO são devolvidas recebem `-1`**: se o contador de uma estrutura chegar a `0`, liberta-se a sua memória na hora e **propaga `-1` em cascata, um por um, a cada um dos seus filhos diretos**. |
| 36 | 4. **O valor devolvido pela função fica isento em trânsito**: recebe o seu `-1` de variável local mas não é destruído ao cruzar o retorno, de modo que quando quem chamou faz `c = minha funcao ()`, esse `+1` da atribuição deixa-o exatamente em `1`. |
| 37 | |
| 38 | ```ridgun |
| 39 | escolher Bool [Int] [Int] => [Int] |
| 40 | escolher 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 = escolher cond a b -- `c` recebe +1; o não escolhido morre aqui mesmo (rc -> 0) |
| 49 | (contar a) + (contar c) -- Ao terminar, `a` e `c` recebem -1 e tudo fica com 0 blocos vivos |
| 50 | ``` |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## 8.2. Regiões Léxicas com Quota (`with region`): Corta-fogos de memória em $O(1)$ |
| 55 | |
| 56 | Além da `RMM1` e da `RMM2`, quando está a processar um pacote de rede externo, um ficheiro gigante ou um lote de dados não confiáveis e quer impor um **teto estrito de memória RAM**, envolve o cálculo num bloco **`with region`**: |
| 57 | |
| 58 | ```ridgun |
| 59 | processar trama String => Result |
| 60 | processar trama bruto = |
| 61 | with region 64_KB do |
| 62 | limpas = map limpar linha (dividir linhas bruto) |
| 63 | contar validos limpas |
| 64 | ``` |
| 65 | |
| 66 | 1. **Quota garantida em hardware/runtime**: se dentro do bloco se tentar reservar nem que seja um único byte acima de `64_KB` (`65.536` bytes), o runtime aborta de forma limpa o bloco, liberta toda a arena temporária em tempo constante $O(1)$ e devolve `Err OutOfMemory` sem derrubar o processo. |
| 67 | 2. **Libertação instantânea ao sair**: assim que termina o bloco `with region`, toda a memória temporária da arena desaparece de uma só vez sem deixar fragmentação. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## 8.3. Tensores Nativos, Contração de Einstein (`@`) e Dimensões Simbólicas |
| 72 | |
| 73 | Se alguma vez programou em Python com NumPy ou PyTorch, de certeza que já sofreu o clássico erro ao minuto 41 de execução: |
| 74 | |
| 75 | ```text |
| 76 | RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x64 and 32x128) |
| 77 | ``` |
| 78 | |
| 79 | Em Ridgun, os **`Tensor`** são cidadãos nativos de primeira classe e as suas dimensões fazem parte da assinatura de tipos: |
| 80 | |
| 81 | ```ridgun |
| 82 | -- Se entra (N × K) e (K × M), obrigatoriamente sai (N × M)! |
| 83 | multiplicar matrizes Tensor[N, K] Tensor[K, M] => Tensor[N, M] |
| 84 | multiplicar matrizes a b = a_ik @ b_kj |
| 85 | ``` |
| 86 | |
| 87 | Repare em dois detalhes espetaculares: |
| 88 | 1. **Verificação estática de dimensões (`Tensor[N, K]`)**: o eixo interno `K` da primeira matriz tem de coincidir com o primeiro eixo `K` da segunda. Se tentar passar dimensões incompatíveis, o `ridgunc check` detém-no antes de executar o que quer que seja. |
| 89 | 2. **Notação de subscritos de Einstein (`a_ik @ b_kj`) e Hadamard (`a_ij * b_j`)**: |
| 90 | - Com **`@`**, os subscritos repetidos em todos os operandos (`k`) contraem-se (somam-se) e os únicos (`i`, `j`) sobrevivem no tensor resultado. |
| 91 | - Com **`+`, `-`, `*`, `/`**, os subscritos alinham dimensões e fazem *broadcasting* automático (`a_ij * b_j` multiplica cada linha `i` de `a` pelo vetor `b_j` sem necessidade de escrever `unsqueeze` nem `reshape`). |
| 92 | 3. **Tensores tipados de alto desempenho (`Tensor[U64 * N]`) e mutação *in-place* com `t_(i, j)`**: |
| 93 | Em sistemas criptográficos, simulações físicas ou álgebra linear pode declarar tensores planos sobre um bloco contíguo de inteiros sem sinal de 64 bits (`Tensor[U64 * 4]`, `Tensor[U64 * 8 * 16]`), operar em paralelo com `^` (XOR nativo de bloco) e ler ou escrever elementos em tempo $O(1)$ com a sintaxe de subscrito explícito `t_(i)` ou `t_(i, j)`: |
| 94 | ```ridgun |
| 95 | criar bloco estado => Tensor[U64 * 2 * 2] |
| 96 | criar bloco 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. Acesso a Hardware (`hardware ::`) e Primitivas POSIX de Sistemas |
| 105 | |
| 106 | O próprio compilador `ridgunc`, o gestor `stable` e utilitários de sistemas Unix estão escritos em Ridgun. Para tal, a linguagem inclui blocos de registos mapeados em memória (`hardware ::`) e primitivas POSIX diretas: |
| 107 | |
| 108 | ```ridgun |
| 109 | hardware :: UART0 @ 0x1000_0000 |
| 110 | tx_data : Int @ 0x00 |
| 111 | status : Int @ 0x04 |
| 112 | ``` |
| 113 | |
| 114 | E para trabalhar com o sistema operativo: |
| 115 | |
| 116 | | Primitiva | Assinatura | O que faz | |
| 117 | |---|---|---| |
| 118 | | `cli args` | `=> [String]` | Argumentos de linha de comandos (`argv`). | |
| 119 | | `read file` / `write file` | `String => String` / `String String => ()` | Leitura e escrita integral de ficheiros em disco. | |
| 120 | | `read bytes` / `write bytes` | `String => [Int]` / `String [Int] => ()` | Leitura e escrita binária exata byte a byte (`0..255`). | |
| 121 | | `list dir` / `file lstat` | `String => [String]` / `String => ...` | Listagem de diretórios e inspeção de metadados POSIX (`lstat`). | |
| 122 | | `sha256 file` / `sha256 str` / `sha256 raw` | `String => String` | Hash criptográfico SHA-256 nativo (em hexadecimal de 64 caracteres ou resumo binário bruto de 32 bytes com `sha256 raw` / `sha256 bytes`). | |
| 123 | | `sys exec` / `shell exec` | `... => Int` | Execução de processos do sistema operativo. | |
| 124 | | `sys getcwd` / `sys chdir` | `=> String` / `String => Int` | Consultar ou alterar o diretório de trabalho. | |
| 125 | | `sys getenv` / `sys setenv` | `String => String` / `String String => Int` | Variáveis de ambiente do processo. | |
| 126 | | `sys getuid` / `sys getgid` | `=> Int` | UID e GID reais do processo (`doas`, ferramentas de segurança). | |
| 127 | | `sys exit` | `Int => ()` | Terminar o processo com código de saída. | |
| 128 | |
| 129 | --- |
| 130 | |
| 131 | ## 8.5. O Ecossistema de Bibliotecas Padrão (`ridgun-libs`) |
| 132 | |
| 133 | Além das primitivas do compilador, o ecossistema oficial do Ridgun inclui **32 bibliotecas puras (`.rglib`)** escritas inteiramente em Ridgun que pode importar com `import <nome>` em qualquer projeto: |
| 134 | |
| 135 | | Categoria | Bibliotecas disponíveis | O que oferecem | |
| 136 | |---|---|---| |
| 137 | | **Coleções, Texto e Dados** | `collections`, `set`, `string`, `regex`, `utf8`, `fmt`, `csv`, `json`, `toml`, `yaml`, `xml`, `html`, `markdown`, `base64`, `hex`, `compress`, `sql` | Mapas, conjuntos, expressões regulares, parsers/serializadores estruturados, codificação e motores de consultas. | |
| 138 | | **Matemática, Tempo e Aleatoriedade** | `math`, `random`, `datetime`, `uuid`, `log`, `cli`, `bench`, `test` | Funções matemáticas, PRNG determinístico e de sistema, datas ISO-8601, UUIDs, frameworks de testes e benchmarks com `clock_gettime`. | |
| 139 | | **Sistemas, Rede e Web** | `fs`, `path`, `process`, `env`, `net`, `http`, `uri` | Sistema de ficheiros de alto nível, manipulação de caminhos, subprocessos, sockets TCP/UDP e cliente/servidor HTTP. | |
| 140 | | **Criptografia e Segurança (`crypto`)** | `crypto` (`sha256`, `hmac`, `hkdf`, `pbkdf2`, `argon2id`, `chacha20`, `aes`, `x25519`, `ed25519`, `csprng`) | Suite criptográfica verificada com vetores oficiais RFC/NIST, implementada sobre `U64`, `Tensor[U64 * ...]` e `sha256 raw` com otimização de inlining de tuplos sem alocações no heap. | |
| 141 | |
| 142 | --- |
| 143 | |
| 144 | ## 8.6. Caso de Estudo Real: Construindo `rg-wc`, um utilitário Unix paralelo |
| 145 | |
| 146 | Vamos unir tudo o que aprendemos desde o Capítulo 01 até aqui num programa real de sistemas: uma versão paralela da ferramenta Unix `wc` (contador de linhas e bytes para múltiplos ficheiros) escrita em Ridgun puro: |
| 147 | |
| 148 | ```ridgun |
| 149 | -- 1. Modelamos as métricas de um ficheiro com um registo ADT (Capítulo 06) |
| 150 | type Metricas = ficheiro: String, linhas: Int, bytes: Int |
| 151 | |
| 152 | -- 2. Contamos quebras de linha (código ASCII 10) com recursão de cauda TCO (Capítulo 04) |
| 153 | fn :: contar quebras String Int Int Int => Int |
| 154 | contar quebras texto pos fim acum = |
| 155 | | pos >= fim = acum |
| 156 | | (str char at texto pos) == 10 = contar quebras texto (pos + 1) fim (acum + 1) |
| 157 | | otherwise = contar quebras texto (pos + 1) fim acum |
| 158 | |
| 159 | fn :: numero de linhas String => Int |
| 160 | numero de linhas texto = contar quebras texto 0 (str len texto) 0 |
| 161 | |
| 162 | -- 3. Analisamos um ficheiro e devolvemos a sua estrutura Metricas |
| 163 | fn :: analisar ficheiro String => Metricas |
| 164 | analisar ficheiro caminho = |
| 165 | conteudo = read file caminho |
| 166 | || numero de linhas conteudo -> n_linhas |
| 167 | || str len conteudo -> n_bytes ||. |
| 168 | Metricas caminho n_linhas n_bytes |
| 169 | |
| 170 | -- 4. Formatamos cada linha e somamos totais com `fold` e `||` (Capítulo 07) |
| 171 | fn :: formatar metrica Metricas => String |
| 172 | formatar metrica m = "{linhas m}\t{bytes m}\t{ficheiro m}" |
| 173 | |
| 174 | fn :: somar linhas Int Metricas => Int |
| 175 | somar linhas acc m = acc + (linhas m) |
| 176 | |
| 177 | fn :: somar bytes Int Metricas => Int |
| 178 | somar bytes acc m = acc + (bytes m) |
| 179 | |
| 180 | fn :: gerar relatorio [String] => String |
| 181 | gerar relatorio caminhos = |
| 182 | metricas = ||map analisar ficheiro caminhos |
| 183 | || map formatar metrica metricas -> linhas_fmt |
| 184 | || fold somar linhas 0 metricas -> total_l |
| 185 | || fold somar bytes 0 metricas -> total_b ||. |
| 186 | "{linhas_fmt}\nTotal: {total_l} linhas, {total_b} bytes" |
| 187 | |
| 188 | fn :: main => () |
| 189 | main = |
| 190 | echo (gerar relatorio ["README.md", "AGENTS.md"]) |
| 191 | ``` |
| 192 | |
| 193 | Pare um segundo para olhar para essas ~40 linhas de código: |
| 194 | - Não há um único `null`, nem uma exceção descontrolada, nem um `Mutex`. |
| 195 | - `contar quebras` é compilada para um `loop` nativo com 0 bytes de pilha graças ao TCO automático. |
| 196 | - `||map analisar ficheiro caminhos` processa os ficheiros em paralelo em todos os núcleos do seu CPU, e o bloco `||` calcula simultaneamente as linhas formatadas, o total de linhas e o total de bytes. |
| 197 | - A memória temporária é libertada de forma determinística mediante **`RMM1`/`RMM2`**. |
| 198 | - E quando executa `ridgunc build rg_wc.rg -o rg-wc`, obtém um binário nativo autónomo de ~350 KB pronto para copiar para `/usr/local/bin`. |
| 199 | |
| 200 | --- |
| 201 | |
| 202 | ## 8.7. Roteiro Final: As 10 Regras de Ouro do Programador Ridgun |
| 203 | |
| 204 | 1. **Escreva sempre primeiro o contrato (`=>`) e depois as equações (`=`):** pensar nos tipos primeiro faz com que o compilador trabalhe para si. |
| 205 | 2. **Deixe que o `stable format` pagine as suas tabelas:** escreva com um espaço e deixe que a ferramenta alinhe colunas, `=>`, `=` e `->`. |
| 206 | 3. **Lembre-se da regra universal do `=`:** atrás de qualquer `=` pode abrir um bloco sequencial, uma árvore de guardas `|` ou um grafo paralelo `||`. |
| 207 | 4. **Use `|.` e `||.` apenas quando fechar um bloco aninhado para voltar a um nível superior:** mantenha as árvores de decisão profundas compactas e sem ambiguidade. |
| 208 | 5. **Imutável por omissão (`x = ...`), mutável explícito (`mut x = ...`):** use `mut` unicamente quando um acumulador local o exigir de verdade. |
| 209 | 6. **Use operadores booleanos claros em minúsculas (`and`, `or`, `xor`, `not`) e feche sempre as suas guardas com `| otherwise =`.** |
| 210 | 7. **Modele o seu domínio com ADTs (`type`):** use registos com `with campo: valor` e tipos soma (`Option`, `Result`) em vez de `null`. |
| 211 | 8. **Paralelize declarando dependências com `|| expr -> var` e `||map` / `||filter`:** deixe que o compilador verifique que não há ciclos e distribua o trabalho pelos núcleos. |
| 212 | 9. **Ponha dimensões simbólicas nos seus Tensores (`Tensor[N, K]`) e subscritos nas suas contrações (`a_ik @ b_kj`).** |
| 213 | 10. **Confie no `RMM` (`RMM1`/`RMM2`), limite entradas externas com `with region` e verifique sempre com `stable check` e `stable test`.** |
| 214 | |
| 215 | Boa viagem a programar em Ridgun! |