Finch/docs/pl/dla-inzynierow.md
Nexoniarz f897fb3140 Rename the Finch file extension from .fn to .fch
.fn is already used by other tools (e.g. Fennel). Modules are now looked up as name.fch;
a leftover name.fn gets a clear message telling you to rename it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:27:42 +02:00

896 lines
50 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Finch dla inżynierów
**Kompletny opis wnętrza Fincha 2.0.** Jak kod źródłowy staje się tokenami, tokeny drzewem AST,
a AST kodem LLVM IR, który potem jest optymalizowany, zapisywany i linkowany z małym runtime'em w C.
Dokładne reguły typów; model własności i to, jak tłumaczy się na kopie i zwolnienia; jak każda
konstrukcja staje się IR (z prawdziwym wyjściem kompilatora); obsługa nagłówków C, struktur C i ABI
System V; moduły, informacje dla debuggera i kompilator samohostujący z `boot/`. Każda funkcja
kompilatora jest opisana razem z tym, czym różni się od sąsiednich.
Zakładam, że znasz C/C++ i widziałeś już LLVM IR.
Język z perspektywy użytkownika opisuje [przewodnik dla techników](dla-technikow.md).
> 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md)
---
## Spis treści
1. [Architektura](#1-architektura)
2. [Pliki źródłowe](#2-pliki-źródłowe)
3. [Lekser](#3-lekser)
4. [Gramatyka i parser](#4-gramatyka-i-parser)
5. [AST](#5-ast)
6. [Wczytywanie: pliki i moduły](#6-wczytywanie-pliki-i-moduły)
7. [Model semantyczny](#7-model-semantyczny)
8. [System typów](#8-system-typów)
9. [Własność i pamięć](#9-własność-i-pamięć)
10. [Generowanie kodu](#10-generowanie-kodu)
11. [Kontrole w czasie działania](#11-kontrole-w-czasie-działania)
12. [Runtime w C](#12-runtime-w-c)
13. [Współpraca z C: nagłówki, struktury, ABI](#13-współpraca-z-c-nagłówki-struktury-abi)
14. [Optymalizacja, emisja, linkowanie](#14-optymalizacja-emisja-linkowanie)
15. [Informacje dla debuggera](#15-informacje-dla-debuggera)
16. [Diagnostyka](#16-diagnostyka)
17. [Kompilator samohostujący (boot/)](#17-kompilator-samohostujący-boot)
18. [System budowania](#18-system-budowania)
19. [Testy](#19-testy)
20. [Semantyka a C](#20-semantyka-a-c)
21. [Spis funkcji](#21-spis-funkcji)
22. [Rozbudowa kompilatora](#22-rozbudowa-kompilatora)
23. [Znane ograniczenia](#23-znane-ograniczenia)
---
## 1. Architektura
Finch to jednoprzebiegowy front-end przed LLVM: sprawdzanie typów i generowanie IR to jedno przejście
po AST, bez własnej reprezentacji pośredniej.
```
main.fch ──► Loader::load() ── lex() ── parse() ──► Program (+ każdy importowany moduł, rekurencyjnie)
│
wszystkie `import "x.h"` ──► importHeaders() (libclang) ──► CImports
│
hostMachine() ──► TargetMachine (triple + data layout)│
▼
generate(progs, cimports, tm, debug) ──► Codegen::run()
declareStructs() wszystkie struktury Fincha i C, ciała typów LLVM, kontrola cykli
declareFns() sygnatura każdej funkcji (+ analiza „pożyczania” parametrów)
define() każde ciało: typy + IRBuilder, zasięgi, zwalnianie, kontrole
defineMainWrapper C-owe `main(argc, argv)`
DIBuilder::finalize (z -g) ; verifyModule
▼
optimize() PassBuilder default<O2> (pomijane przy -O0)
emitObject() legacy PM + addPassesToEmitFile ──► prog.o
link() cc prog.o ~/.cache/finch/rt-<hash>.o [twoje .c/.o/.a] -lm [pkg-config/-l…]
▼
plik wykonywalny (finch run: uruchamiany z pliku tymczasowego z resztą argumentów, potem usuwany)
```
Decyzje, które kształtują resztę:
- **Szybka porażka.** Pierwszy błąd wypisuje diagnostykę i kończy proces (`failAt()` w `src/error.h`).
Bez odtwarzania po błędach, więc dalsze fazy nigdy nie widzą częściowo poprawnego wejścia.
- **Jedno przejście.** `Codegen` niesie zasięgi, pętle, bieżącą funkcję i moduł. Każde wyrażenie zwraca
wartość IR, typ Fincha i dwie flagi (`Value_`, §8).
- **Niech LLVM robi robotę.** Zmienne lokalne to `alloca`, które `mem2reg`/SROA zamieniają na rejestry.
Węzły φ Finch buduje ręcznie tylko dla `&&`/`||`.
- **Wartości, nie referencje.** Tablice, teksty i struktury mają semantykę wartości: przypisanie kopiuje,
koniec bloku zwalnia. Własność rozstrzyga się w czasie kompilacji, bez liczenia referencji (§9).
- **Malutki runtime w C** (`runtime/finch_rt.c`, ok. 430 linii) robi to, co w IR byłoby żmudne: operacje na
tekstach, wzrost tablic, wejście, pliki, panic. Jest wbudowany w kompilator i trzymany w cache.
---
## 2. Pliki źródłowe
| Plik | Linie | Odpowiedzialność |
|---|---|---|
| `src/error.h` | 40 | `g_files` (wszystkie źródła), `failAt(file, line, col, msg)`, `fail(line, col, msg)` |
| `src/lexer.h/.cpp` | 250 | `Tok`, `Token`, `lex(indeksPliku)`, `tokName()` |
| `src/ast.h` | 310 | `Type` (z Array, Fixed, Struct, Named), wszystkie węzły `Expr`/`Stmt`, `FnDecl`, `StructDecl`, `Import`, `Link`, `Program` |
| `src/parser.h/.cpp` | 540 | Parser zstępujący `Parser`, `typeFromName()` |
| `src/cimport.h/.cpp` | 420 | Import przez libclang: funkcje, struktury C (pola, przesunięcia), stałe, zmienne globalne |
| `src/codegen.h` | 15 | `generate()` |
| `src/codegen_impl.h` | 265 | Klasa `Codegen` i jej struktury pomocnicze, wspólne dla czterech plików poniżej |
| `src/codegen.cpp` | 1090 | Program, deklaracje, zasięgi i sprzątanie, instrukcje, wyrażenia, miejsca (`ref`), operatory, analiza modyfikacji |
| `src/codegen_types.cpp` | 500 | Typy LLVM, rozwiązywanie typów, układ struktur, konwersje, własność (helpery kopiowania/zwalniania), wypisywanie, typy DWARF |
| `src/codegen_builtins.cpp` | 710 | Wywołania, konstruktory, funkcje wbudowane, konwersje, metody tablic i tekstów, deklaracje runtime'u, panic |
| `src/abi.cpp` | 310 | Klasyfikacja System V x86-64, wywołania C ze strukturami przez wartość, wrappery wywoływalne z C |
| `src/main.cpp` | 390 | Sterownik: CLI, ładowanie, maszyna docelowa, O2, emisja, cache runtime'u, linkowanie, porady przy błędach linkera, `run` |
| `runtime/finch_rt.c` | 440 | Biblioteka uruchomieniowa (§12) |
| `boot/*.fch` | 3080 | Kompilator samohostujący (§17) |
---
## 3. Lekser
`lex(file)` przechodzi raz przez `g_files[file].text` i zwraca `std::vector<Token>` zakończony `Tok::End`.
```cpp
struct Token { Tok kind; std::string text; int line, col; bool newlineBefore; };
```
- **Nowe linie nie są tokenami.** Każdy token zapamiętuje `newlineBefore`, a o znaczeniu decyduje
parser (§4). Dzięki temu obsługa nowych linii nie zaśmieca każdej reguły listowej w gramatyce.
- **Pozycje:** od 1; `col` liczy znaki (bajty kontynuacji UTF-8 jej nie zwiększają), więc `^` trafia
pod `"Błąd"`. Zabłąkany znak spoza ASCII jest pokazywany w całości z podpowiedzią, że nazwy są tylko ASCII.
- **Identyfikatory / słowa kluczowe:** `[A-Za-z_][A-Za-z0-9_]*`. Słowa kluczowe:
`fn return if else while for in break continue true false null import link struct defer`.
**Nazwy typów nie są słowami kluczowymi**: `int`, `u8`, `ptr`… to `Ident`, rozpoznawane przez parser
za pomocą `typeFromName()`, więc `u8(x)` jest zwykłym wywołaniem, a lekser nie zależy od listy typów.
- **Liczby:** dziesiętne albo `0x` szesnastkowe z separatorami `_`. Bez ósemkowych (`010` to 10).
`.` należy do liczby tylko wtedy, gdy po niej jest cyfra, więc `0..10` to `Int DotDot Int`.
- **Teksty / znaki:** sekwencje `\n \t \r \0 \\ \" \'`. Bez nowej linii w literale.
- **Operatory:** najdłuższe dopasowanie (`->` przed `-`, `<<`/`<=` przed `<`, `&&` przed `&`, `..` przed `.`,
`:=` przed `:`). Samotny `:` to token (argumenty nazwane).
- **Komentarze:** `//` i niezagnieżdżane `/* */`.
---
## 4. Gramatyka i parser
`Parser` (`src/parser.cpp`) to ręcznie pisany parser zstępujący z jednym tokenem wyprzedzenia, a w
`atDeclaration()` z maksymalnie trzema.
```ebnf
program = { import | link | struct | function } ;
import = "import" ( STRING | IDENT ) ; (* "x.h" = nagłówek C, nazwa = moduł Fincha *)
link = "link" STRING ; (* "glfw" | "plik.c" | "plik.o" | "lib.a" *)
struct = "struct" IDENT "{" { type IDENT [ "=" expr ] NOWA_LINIA } "}" ;
function = "fn" IDENT "(" [ param { "," param } ] ")" [ "->" type ] block ;
param = type IDENT ;
type = "[" "]" type | "ptr" [ "[" type "]" ] | TYPENAME | IDENT [ "." IDENT ] ;
block = "{" { statement TERMINATOR } "}" ;
statement = if | while | for | return | "break" | "continue" | "defer" statement | block
| type IDENT [ "=" expr ] (* deklaracja, zobacz atDeclaration *)
| IDENT ":=" expr
| target assignop expr (* cel: zmienna, pole, element, p.value *)
| call | method ;
for = "for" IDENT "in" expr ( ".." expr block | block ) ; (* zakres | po elementach *)
assignop = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ;
expr = or ; or = and { "||" and } ; and = cmp { "&&" cmp } ;
cmp = add { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) add } ;
add = mul { ( "+" | "-" | "|" | "^" ) mul } ;
mul = unary { ( "*" | "/" | "%" | "<<" | ">>" | "&" ) unary } ;
unary = ( "-" | "!" | "~" ) unary | postfix ;
postfix = primary { "." IDENT [ args ] | "[" expr "]" } ;
primary = INT | FLOAT | STRING | CHAR | "true" | "false" | "null"
| IDENT [ args ] | "(" expr ")" | "[" [ expr { "," expr } [","] ] "]" ;
args = "(" [ arg { "," arg } ] ")" ; arg = [ IDENT ":" ] expr ;
```
**Koniec instrukcji.** Po instrukcji `endOfStatement()` wymaga `}`, końca pliku albo tokenu
z `newlineBefore`. Wewnątrz wyrażeń `sameLine()` (`parenDepth > 0 || !newlineBefore`) decyduje, czy
operator, `.`, `[` albo `(` wywołania kontynuuje wyrażenie. `parenDepth` rośnie w `( )` i `[ ]`,
a przy wejściu do `{ }` jest zapamiętywany i zerowany.
**Priorytety są jak w Go**, nie jak w C: `&` wiąże jak `*`, a `|`/`^` jak `+`, wszystkie powyżej porównań,
więc `x & MASKA == 0` znaczy `(x & MASKA) == 0`.
**Rozpoznawanie instrukcji.** `atDeclaration()` zwraca prawdę dla `[` `]` (typ tablicowy), `TYP IDENT`,
`ptr [` i `IDENT . IDENT IDENT` (typ z modułu). W pozostałych przypadkach parser **parsuje całe
wyrażenie, a potem patrzy na następny token**: operator przypisania robi z niego cel (musi być `Var`,
`Member` albo `Index`), a inaczej musi to być wywołanie funkcji lub metody, w przeciwnym razie
`this value is computed but never used`.
**Literały struktur to wywołania**: `Punkt(1, 2)` albo `Punkt(x: 1, y: 2)`. Literały w klamrach
(`Punkt{…}`) byłyby niejednoznaczne z blokami (`if gotowe {`); wywołanie nie potrzebuje specjalnej
gramatyki, a argumenty nazwane (`IDENT ":"` w `args()`) są akceptowane tylko przez konstruktory na etapie generowania kodu.
---
## 5. AST
Wszystkie węzły są w `src/ast.h`: małe hierarchie klas z jawnym `kind`, rozgałęziane przez
`switch` + `static_cast`.
| ExprKind | Węzeł | Pola |
|---|---|---|
| `Int` `Float` `Bool` `Char` `Str` | literały | `value` |
| `Null` | `NullExpr` | |
| `Var` | `VarExpr` | `name` |
| `Unary` | `UnaryExpr` | `op` (`- ! ~`), `operand` |
| `Binary` | `BinaryExpr` | `BinOp op`, `lhs`, `rhs` |
| `Call` | `CallExpr` | `callee` (nazwa), `args`, `argNames` |
| `Member` | `MemberExpr` | `obj`, `field` (`.x`, `.len`, `.ptr`, `.value`) |
| `Index` | `IndexExpr` | `obj`, `index` |
| `ArrayLit` | `ArrayLitExpr` | `elems` |
| `Method` | `MethodExpr` | `obj`, `name`, `args`, `argNames`; także `modul.fch(...)` |
| StmtKind | Węzeł |
|---|---|
| `Block` `VarDecl` `Assign` `Expr` `If` `While` `For` `ForEach` `Return` `Break` `Continue` `Defer` | jak w nazwie; `ForEachStmt` ma `var`, `list`, `body`; `DeferStmt` trzyma jedną instrukcję |
`math.sqrt(x)` parsuje się jako `MethodExpr` na `VarExpr("math")`; to, czy `math` jest modułem, czy zmienną,
rozstrzyga się przy generowaniu kodu. Wywołania nazywają funkcję, zamiast trzymać wyrażenie: Finch nie ma
funkcji jako wartości, a późne rozwiązywanie nazwy pozwala, by jeden `CallExpr` był funkcją wbudowaną,
konwersją (`u8(x)`), konstruktorem struktury, funkcją Fincha albo funkcją C.
Nie ma węzła „load”: to, czy `x` znaczy adres, czy wartość, zależy od kontekstu (`ref()` kontra `expr()`, §10.2).
### `Type`
```cpp
struct Type {
enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
Str, Ptr, Null, Array, Fixed, Struct, Named };
Kind kind;
std::shared_ptr<Type> elem; // Ptr (null = `ptr` bez typu), Array, Fixed
long long count; // Fixed
std::string name, module; // Named (jak napisano) / Struct (rozwiązany)
StructInfo *info; // Struct
};
```
Parser tworzy `Named` dla nazw struktur; `Codegen::resolve()` zamienia je na `Struct` ze wskaźnikiem
`info`. `Fixed` (`[N]T`) pochodzi tylko z pól struktur C. `int` to `I64`, a `float` to `F64`.
Równość jest strukturalna; dwa `Struct` są równe, gdy mają to samo `info`.
---
## 6. Wczytywanie: pliki i moduły
`Loader::load(path, module, from)` w `main.cpp` czyta plik, dopisuje go do `g_files` (pozycje niosą
indeks pliku w `Pos::file`), leksuje go i parsuje, a potem wczytuje każdy jeszcze niewidziany `import nazwa`:
`nazwa.fch` obok importującego pliku albo w folderze z `FINCH_PATH`. Wynik to `std::vector<Program>`,
z głównym plikiem na początku. Cykle nie przeszkadzają: moduł jest oznaczany jako wczytany, zanim
zaczniemy podążać za jego importami.
Wszystkie pliki trafiają do **jednego modułu LLVM**. Każdy moduł Fincha ma `ModuleScope` (funkcje,
struktury, zbiór modułów, które importuje). Nazwy szuka się w bieżącym module; nazwy innego modułu
są dostępne tylko z kwalifikatorem (`geometry.length`, `geometry.Vec`) i tylko tam, gdzie moduł
zaimportowano. Funkcje są emitowane jako `finch.<moduł>.<nazwa>` (`finch.<nazwa>` w głównym pliku),
typy struktur jako `finch.<moduł>.<Nazwa>`.
---
## 7. Model semantyczny
### Kolejność deklaracji
`Codegen::run()` najpierw deklaruje **wszystkie struktury** wszystkich modułów i zaimportowane struktury C,
rozwiązuje typy pól i ustawia ciała typów LLVM (`declareStructs` → `resolveStruct`), potem deklaruje
**wszystkie sygnatury funkcji** (`declare`), a dopiero potem definiuje ciała. Kolejność w pliku i między
modułami nigdy więc nie ma znaczenia, a rekurencja (także wzajemna i między modułami) działa.
`resolveStruct` wykrywa cykle przez wartość trójstanową flagą (`struct A { A inner }` to błąd z radą,
by użyć `ptr[A]` albo `[]A`). Wskaźniki i tablice struktury nie potrzebują jej układu, więc
`struct Wezel { []Wezel dzieci; ptr[Wezel] rodzic }` jest w porządku: rozwiązują nazwę „płytko”
(`resolveT(…, deep=false)`).
### Zasięgi i zmienne
```cpp
struct Var { Value *slot; FType type; bool readonly; std::string readonlyWhy;
bool owned; int order; bool moved; };
struct Cleanup { bool isDefer; std::string var; const Stmt *body; int order; };
struct Scope { unordered_map<string, Var> vars; vector<Cleanup> cleanups; };
```
Zasięg jest otwierany dla parametrów funkcji, każdego `{ }`, każdego `for` (trzyma `i`), każdego `for … in`
(ukryta tymczasowa lista) i każdej jego iteracji (trzyma `x`).
- **Bez przesłaniania:** `addVar()` odrzuca nazwę znalezioną w *dowolnym* zewnętrznym zasięgu,
funkcję modułu, nazwę zaimportowanego modułu i funkcję wbudowaną.
- **Tylko do odczytu:** zmienne pętli zakresowej i pętli po elementach (`readonlyWhy` daje konkretny komunikat).
- `order` numeruje deklaracje; `defer` używa go, by ukryć zmienne zadeklarowane po nim (§9.5).
### Rozwiązywanie nazw
`x` w pozycji wartości: zmienna lokalna → stała C → zmienna globalna C → (nazwa modułu: błąd z podpowiedzią) → błąd.
Wywołanie `n(...)`: funkcje wbudowane (`print addr input new free exit shell read_file write_file file_exists`) →
nazwy typów (konwersja) → funkcja bieżącego modułu → struktura bieżącego modułu / struktura C
(konstruktor) → funkcja C → błąd (z podpowiedzią, jeśli funkcja istnieje w innym module, albo z
`headerHint` dla znanych nazw z libc).
---
## 8. System typów
### Reprezentacja w LLVM
| Finch | LLVM |
|---|---|
| `bool` | `i1` |
| `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` |
| `f32` / `float` | `float` / `double` |
| `str`, `[]T` | `{ ptr, i64, i64 }` (wskaźnik, długość, pojemność) |
| `ptr`, `ptr[T]`, `null` | `ptr` (nieprzezroczysty) |
| `[N]T` | `[N x T]` |
| struktura Fincha | nazwany typ struct, naturalny układ |
| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§13.2) |
Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`,
`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`.
### Wartości
```cpp
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```
- **`literal`**: liczba wpisana w kod albo zaimportowana stała C. Może stać się dowolnym typem całkowitym,
w którym się mieści (`fits()`), albo `f32`. Przechodzi przez minus jednoargumentowy i arytmetykę samych
literałów zwijaną do stałej; jawne konwersje ją kasują (`u8(250)` to stała z typem, więc `u8(250) + 10` zawija się do 4).
- **`fresh`**: wartość posiadająca pamięć (str/tablica/struktura z takimi polami), której nikt jeszcze nie
trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§9).
### Konwersje niejawne: `coerce(v, want)`
Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; całkowita → całkowita,
jeśli wartość to mieszczący się literał albo typ się rozszerza (`widens()`: ta sama znakowość i więcej bitów
albo bez znaku → ściśle większa ze znakiem); całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`.
Nic więcej: błąd podpowiada jawną konwersję. Typy posiadające pamięć konwertują się tylko na siebie.
Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
### Argumenty operatorów: `unify(l, r)`
Strona-literał dopasowuje się do drugiej; w przeciwnym razie rośnie strona, która się rozszerza;
`i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64.
Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`.
### Konwersje jawne: `convert()`
| Z → Na | IR |
|---|---|
| int/char ↔ int/char | `trunc`/`sext`/`zext` wg znakowości źródła |
| bool → int | `zext` |
| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
| float ↔ float | `fpext`/`fptrunc` |
| int → bool | `icmp ne 0` |
| liczba/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) |
| str → int/float | `finch_str_to_int/float` (panic przy złym tekście) |
| `[]u8`/`[]char` → str | `finch_str_from_bytes` |
| ptr → str | `finch_str_from_c` (pożyczony, `cap = -1`) |
| ptr ↔ ptr | nic |
### Porównania
Wskaźniki/null: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
`finch_str_cmp` dla porządku. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` i `une` dla `!=`.
Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tablice/struktury: błąd.
---
## 9. Własność i pamięć
### 9.1 Model
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma
dokładnie jednego właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`.
Właściciel zwalnia wartość, gdy sam znika. Nie ma liczenia referencji ani garbage collectora;
każda decyzja zapada statycznie.
| Sytuacja | Co się dzieje |
|---|---|
| `x := expr` / `T x = expr` / `x = expr` / pole, element, `push` | **`own(v)`**: wartość fresh jest przenoszona; pożyczona jest kopiowana w głąb |
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, stara zwalniana po zapisie |
| koniec bloku, `return`, `break`, `continue` | posiadane zmienne każdego opuszczanego zasięgu są zwalniane (od najnowszej), uruchamiają się defery |
| `return lokalna` | zmienna jest **przenoszona na zewnątrz** (oznaczana jako `moved` na czas sprzątania tego returna) |
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; argumenty fresh zwalnia wywołujący po wywołaniu |
| parametr, który funkcja zmienia | kopiowany na wejściu i posiadany przez funkcję (decyduje `mutates()`, niżej) |
| wartość tymczasowa, której nikt nie trzyma (`print(a + b)`, `f(g())`, instrukcja-wyrażenie) | **`release(v)`**: zwalniana zaraz po użyciu |
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (fresh) |
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało może zmieniać `lista`, elementy są kopiowane |
Literał tekstowy to wartość fresh z `cap = 0`: przeniesienie nic nie kosztuje, zwalnianie jest pomijane
(`release()` ignoruje stałe), a zapis do niego (`s[i] = c`) najpierw robi kopię na stercie (`finch_str_own`).
Teksty z C są pożyczane z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
### 9.2 Pożyczanie parametrów: `mutates()`
Przy deklarowaniu funkcji każdy parametr posiadający pamięć jest sprawdzany przez `mutates(nazwa, ciało)`:
przypisanie, którego zmienna-korzeń (`rootVar()`: przez `.pole` i `[i]`) jest tym parametrem, metoda
zmieniająca (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane
parametry są pożyczane (`Fn::borrowParam`), zmieniane kopiowane raz na wejściu. Analiza jest składniowa
i zachowawcza: wszystko, co *może* zmienić parametr, powoduje kopię, co zawsze jest poprawne.
Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
### 9.3 Kopiowanie i zwalnianie
`dropAt(addr, T)` / `copyAt(dst, src, T)` działają na adresach:
- `str`: `finch_str_drop` (zwalnia, gdy `cap > 0`) / `finch_str_copy` (literały współdzieli, resztę duplikuje).
- `[]T` i struktury posiadające: generowane helpery `finch.drop.<klucz>` i `finch.copy.<klucz>`, tworzone
raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Gracz`). Tablice zwalniają każdy posiadający
element, a potem `finch_arr_free`; kopie wołają `finch_arr_clone_raw`, a potem kopiują w głąb posiadające
elementy na surowe bity. Struktury zwalniają/kopiują tylko pola posiadające.
Helpery są emitowane we własnych funkcjach (`HelperScope` zapamiętuje pozycję buildera i wyłącza lokacje
debugowe), mogą wołać się rekurencyjnie (`struct Wezel { []Wezel dzieci }`) i są wstawiane (inline) przez O2.
### 9.4 Jak to wygląda
```c
fn greet(str name) -> str {
msg := "Hi, " + name
return msg
}
fn main() {
a := ["x"]
b := a
b.push(greet("Ola"))
print(b[1])
}
```
IR funkcji `main` z `-O0` (skrócony):
```llvm
%3 = call ptr @finch_alloc(i64 24) ; ["x"]: jeden element na stercie
store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; literał, cap 0
...
store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (fresh)
%a1 = load { ptr, i64, i64 }, ptr %a
store { ptr, i64, i64 } %a1, ptr %1
call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a kopiuje
%8 = load { ptr, i64, i64 }, ptr %2
store { ptr, i64, i64 } %8, ptr %b
%9 = call { ptr, i64, i64 } @finch.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
...
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększ, jeśli trzeba
store { ptr, i64, i64 } %9, ptr %15 ; wynik greet przeniesiony, bez kopii
...
%20 = icmp uge i64 1, %19 ; b[1]: kontrola zakresu
br i1 %20, label %out.of.range, label %in.range
...
call void @finch.drop.arr_str(ptr %b) ; koniec main: od najnowszej
call void @finch.drop.arr_str(ptr %a)
ret void
```
a `greet` zwraca `msg` bez kopiowania (`ret … %5` po wczytaniu slotu: zmienna została przeniesiona).
### 9.5 defer
`defer instrukcja` dokłada do bieżącego zasięgu `Cleanup{isDefer, body, order = bieżące varOrder}`
i niczego w tym miejscu nie emituje. Za każdym razem, gdy emitowane jest sprzątanie zasięgu (dojście do
końca bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **w tym wyjściu**, z
`deferLimit = order`, żeby `lookup()` ukrył zmienne zadeklarowane po defer: te są wtedy już zwolnione
(sprzątanie idzie w odwrotnej kolejności). `return`/`break`/`continue` w instrukcji defer to błędy (`inDefer`).
Ciała defer są więc powielane na każdą ścieżkę wyjścia, przez co na zwykłej ścieżce nic nie kosztują.
### 9.6 Pamięć ręczna
`new(v)` alokuje `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia
wskazywaną wartość, jeśli `T` posiada pamięć (pomijając null), i woła `free` z libc. Wskaźniki nigdy
niczego nie posiadają: `addr(x)` może wisieć po końcu bloku `x`; nie jest to sprawdzane.
### 9.7 Weryfikacja
`MEMCHECK=1 tests/run.sh` buduje każdy program testowy i uruchamia go pod valgrindem z
`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero nieprawidłowych dostępów we wszystkich,
w tym przy returnach z wnętrza pętli, `continue`/`break` z posiadanymi zmiennymi, deferach, modyfikacji
podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
---
## 10. Generowanie kodu
### 10.1 Zmienne
Każda zmienna, łącznie z parametrami, dostaje `alloca` na początku bloku wejściowego (`slot()`;
struktury C dostają wyrównanie z C). `addVar()` zapisuje wartość początkową, rejestruje zmienną,
dokłada sprzątanie, jeśli zmienna posiada pamięć, i (z `-g`) zgłasza ją debuggerowi.
### 10.2 Miejsca: `ref()`, `place()`, `expr()`
```cpp
struct LRef { bool isPlace; Place pl; Value_ val; }; // adres albo (jeśli go nie ma) wartość
```
`ref(e, forWrite)` przechodzi łańcuch `Var`/`Member`/`Index` i zwraca **adres**, jeśli istnieje:
- `Var` → jej `alloca` (albo zmienna globalna C).
- `.pole` na miejscu-strukturze → `getelementptr` do pola (`llvmIndex`; struktury C pomijają elementy wypełnienia).
- przez wskaźnik (`p.value`, `p.pole`, `p[i]`) → wczytanie wskaźnika, kontrola null, adres celu.
`p.value` na wskaźniku do struktury z własnym polem `value` oznacza to pole.
- `a[i]` na miejscu-tablicy/tekście → wczytanie `{ptr,len,cap}`, `boundsCheck(i, len)`, `getelementptr`.
Przy `forWrite` na `str` najpierw `finch_str_own` (kopiowanie przy zapisie dla literałów). `[N]T` sprawdza względem `N`.
- `.len` / `.ptr` → wartości.
- wartość tymczasowa (wynik wywołania…) nie ma adresu: jej pole/element jest wyciągany, kopiowany
(jeśli posiada pamięć), a sama wartość zwalniana.
`expr()` dla tych rodzajów woła `ref()` i wczytuje spod adresu: stąd bierze się każdy `load` zmiennej,
pola albo elementu. `place()` wymaga adresu (cele przypisań, `addr()`).
### 10.3 Przypisanie
`x = v`: **najpierw** liczone jest `v` (może przenieść pamięć, w której żyje cel: `a[0] = f()`, gdzie `f`
robi `push` do `a`), potem `place(cel, forWrite)`, `coerce`, `own`, wczytanie starej wartości, zapis i
zwolnienie starej. Złożone `x += v`: liczenie `v`, wczytanie celu, `arith()`, zapis, zwolnienie starej
wartości (dla `str +=`).
### 10.4 Funkcje i `main`
Funkcje są `internal` z naturalną sygnaturą LLVM (agregaty przez wartość). Parametry posiadające
pamięć są kopiowane na wejściu tylko wtedy, gdy `borrowParam` jest fałszywe. `fn main` to wewnętrzna
`finch.main`; osobna zewnętrzna `i32 main(i32, ptr)` (`defineMainWrapper`) buduje `[]str args` przez
`finch_args`, jeśli trzeba, woła ją, zwalnia argumenty i zwraca kod wyjścia. Funkcja, która dochodzi do
swojego końca, dostaje sprzątanie zasięgów i `ret void` albo błąd `can reach its end without returning T`.
### 10.5 Sterowanie przebiegiem
Bieżący blok buildera jest źródłem prawdy o osiągalności (`terminated()`): instrukcje po terminatorze to
błąd (`this code can never run`). `continueAt()` usuwa blok łączący, do którego nikt nie skacze.
`if`/`while`/`for` mają klasyczny układ bloków; `while true` bez `break` zostawia funkcję zakończoną
(nie trzeba po nim `return`). `for i in a..b` liczy oba końce raz; `continue` idzie do `for.step`.
`for x in lista` czyta długość listy z jej adresu **w każdej rundzie** (`push` w środku pętli jest bezpieczny)
i trzyma indeks w slocie na stosie. `break`/`continue` emitują sprzątanie zasięgów otwartych wewnątrz pętli
(`Loop::scopeDepth`) przed skokiem.
### 10.6 Skrócone `&&` / `||`
Jedyne ręcznie budowane φ: lewa strona kończy się w bloku `from`, prawa w `rhsEnd`;
`phi i1 [!isAnd, from], [rhs, rhsEnd]`.
### 10.7 Wywołania
`callFinch()` konwertuje argumenty na typy parametrów (literały tablic przez `exprWant`), przekazuje
posiadające płytko, woła, a potem zwalnia argumenty fresh. Wyniki typów posiadających są fresh.
`construct()` buduje strukturę od `zeroinitializer` przez `insertvalue`: pozycyjnie (wszystkie pola)
albo po nazwie (brakujące biorą wyrażenie domyślne pola, liczone w module struktury z podmienionymi
zasięgami wywołującego, albo zero). Wywołania C opisuje §13.
### 10.8 Wypisywanie
`print` emituje `printf` dla każdego argumentu z formatem wybranym wg typu (`%lld`, `%llu`, `%g`, `%c`,
`%.*s` dla `str` z jego długością, `%p`), oddzielając spacjami, a na końcu `\n`. Tablice, tablice o stałym
rozmiarze i struktury wołają generowane helpery `finch.print.<klucz>`, które wypisują `[1, 2]` /
`Punkt(x: 1, y: 2)`, biorąc teksty i znaki w środku w cudzysłów.
### 10.9 Metody tablic i tekstów
Tablice: `push` (`finch_arr_reserve` + zapis + len++), `pop`/`remove` (przeniesienie na zewnątrz,
`remove_gap`), `insert` (`insert_gap`), `clear`/`resize` (zwolnienie znikających elementów,
`finch_arr_resize`), `find`/`contains` (pętla z `compare(Eq)`), `slice` (nowa tablica, `copyAt` na element),
`reverse` (pętla zamian), `sort` (`qsort` z libc z generowaną porównywarką `finch.cmp.<klucz>`), `join` (runtime).
Metody zmieniające tablicę wymagają adresu (zmienna/pole/element).
Teksty: `sub find contains starts_with ends_with split trim upper lower replace repeat bytes`, wszystkie w runtime.
---
## 11. Kontrole w czasie działania
Wszystkie błędy w czasie działania idą przez `finch_panic(plik, linia, msg)` /
`finch_panic_index(plik, linia, i, len)` (zadeklarowane `noreturn cold`), więc gałąź błędu jest układana
jako zimna, a kontrola kosztuje porównanie i dobrze przewidywany skok. LLVM usuwa kontrole, które potrafi
udowodnić (stałe indeksy w zakresie, kontrole wyciągnięte z pętli).
| Kontrola | Gdzie |
|---|---|
| dzielenie/reszta przez zero (stałe zero: błąd kompilacji) | `checkDivisor()` |
| `MIN / -1` dla typów ze znakiem | `checkDivisor()` |
| indeks tablicy/tekstu/tablicy stałej poza zakresem (`icmp uge`, więc także ujemne) | `boundsCheck()` |
| `.value`, `.pole`, `[i]` przez null | `member()`, `index()` |
| `pop()` na pustej, `slice()`/`sub()` poza zakresem | metody, runtime |
| zły tekst w `int(s)`/`float(s)`, nieczytelny plik w `read_file` | runtime |
Sito na 20 milionów elementów z kontrolą zakresu przy każdym dostępie działa tak szybko jak to samo w C
(ok. 0,09 s): kontrole są wyciągane z pętli albo zwijane.
---
## 12. Runtime w C
`runtime/finch_rt.c` to zwykłe C bez stanu globalnego. CMake wbudowuje go w kompilator jako surowy
napis (`rt_source.inc`); przy pierwszym użyciu `runtimeObject()` kompiluje go przez `$CC -O2 -fPIC -c`
do `~/.cache/finch/rt-<hash>.o`, gdzie hash obejmuje źródło, wersję Fincha i kompilator C.
Jest linkowany statycznie do każdego programu.
Wspólny układ:
```c
typedef struct { char *ptr; int64_t len; int64_t cap; } FStr; // ptr zawsze zakończony zerem
typedef struct { void *ptr; int64_t len; int64_t cap; } FArr;
// cap tekstu: > 0 sterta (posiadany), 0 literał (statyczny), -1 pożyczony z C (skopiuj, zanim zatrzymasz)
```
Funkcje przyjmują i zwracają te struktury **przez wskaźnik**, więc ich ABI jest trywialne (bez klasyfikacji
struktur): budowanie tekstów (`concat`, `from_int/uint/float/char/bool`, `sub`, `trim`, `upper`, `lower`,
`replace`, `split`, `join`, `repeat`, `from_bytes`), porównania (`eq`, `cmp`, `find`, `starts`, `ends`),
własność (`copy`, `own`, `drop`, `from_c`), tablice (`reserve`, `make`, `resize`, `clone_raw`, `free`,
`insert_gap`, `remove_gap`), `args`, `input`, pliki, `shell` i panic. Brak pamięci wypisuje
`out of memory` i kończy proces.
---
## 13. Współpraca z C: nagłówki, struktury, ABI
### 13.1 Import nagłówków
`importHeaders(imports, dirs)` parsuje każdy `import "x.h"` jako osobną jednostkę translacji z pliku
w pamięci `#include "x.h"`, z `-x c -std=gnu11`, `-I` dla folderu każdego pliku `.fch` i `-isystem` dla
każdego katalogu przeszukiwanego przez systemowy kompilator C (odczytanego z `$CC -E -v -x c /dev/null`,
dzięki czemu działa to na NixOS). `gnu11`, bo glibc w trybie ścisłego ISO ukrywa `M_PI` i podobne.
`Importer` odwiedza kursory najwyższego poziomu:
| Kursor | Wynik |
|---|---|
| `FunctionDecl` | `CFunc` (sygnatura mapowana w **kontekście sygnatury**; funkcje `static` i K&R zapisywane jako nieobsługiwane) |
| `TypedefDecl` rekordu | nazywa rekord (`typedef struct {…} CXCursor` → `CXCursor`) |
| `StructDecl` (definicja) | nazywa rekord jego tagiem |
| `VarDecl` `extern` | `CGlobal` (mapowany w **kontekście danych**) |
| `EnumConstantDecl`, liczbowe `MacroDefinition` | `CConst` (literał) |
`mapType()` działa na typach kanonicznych i mapuje liczby całkowite wg rozmiaru. Kontekst decyduje o
`char *`: w sygnaturach funkcji to `str` (Finch konwertuje przy wywołaniu), ale w strukturach, zmiennych
globalnych i za wskaźnikami to `ptr[char]` (tam układ pamięci musi zostać pojedynczym wskaźnikiem).
Rekordy przez wartość stają się `Named{nazwa, "C"}` i są definiowane przez `defineRecord()`: każde pole
(`clang_Type_visitFields`) z przesunięciem w bajtach, rozmiarem i wyrównaniem, zagnieżdżone rekordy,
tablice stałe (`Fixed`). Unie i pola bitowe zostają jako ukryte bajty i oznaczają strukturę jako
nieobsługiwaną przy wywołaniach przez wartość; wskaźniki na wszystko inne (void, funkcje, niekompletne
struktury) stają się `ptr`.
### 13.2 Układ struktur C
`resolveStruct()` buduje **upakowany** struct LLVM, który odwzorowuje C bajt w bajt: wypełnienie
`[n x i8]` przed każdym polem, którego przesunięcie wyprzedza bieżącą pozycję, samo pole (tylko jeśli jego
rozmiar w LLVM zgadza się z C) i wypełnienie końcowe do `sizeof` z C. Dostęp do pól używa zapisanego
`llvmIndex`. Upakowanie czyni układ dokładnym niezależnie od reguł wyrównania LLVM; alokacje i wartości
tymczasowe dostają wyrównanie z C jawnie.
### 13.3 Konwencja wywołań System V x86-64
`classify(T)` (w `abi.cpp`) spłaszcza strukturę do skalarnych liści z przesunięciami w bajtach (przez
zagnieżdżone struktury i tablice stałe) i:
- **MEMORY**, jeśli jest większa niż 16 bajtów albo ma niewyrównany liść: przekazywana jako wskaźnik z
`byval(T) align ≥8`, zwracana przez ukryty pierwszy wskaźnik `sret(T)`.
- w przeciwnym razie każdy eightbyte jest **INTEGER**, jeśli którykolwiek liść w nim jest liczbą
całkowitą/wskaźnikiem, a inaczej **SSE**. Części: INTEGER → `iN` dla pozostałych bajtów (`i32` dla
4-bajtowego `Color`), SSE → `double`, `<2 x float>` albo `float`. Jedna część idzie wprost; dwie jako
dwa argumenty albo zwracane jako `{a, b}`.
`makePlan()` przechodzi parametry, licząc 6 rejestrów całkowitych i 8 SSE; struktura, której części już się
nie mieszczą, idzie w całości na stos (MEMORY), tak jak robi to clang. Małe parametry całkowite dostają
`signext`/`zeroext`. `emitCCall()` zapisuje każdy argument-strukturę do slotu wystarczająco dużego dla
struktury i jej postaci rejestrowej, wczytuje części z przesunięć 0 i 8, woła z zaplanowanym typem funkcji
i atrybutami, a wyniki-struktury odbudowuje w ten sam sposób.
`cThunk(fn)` to odwrotność dla `addr(fn)`: wewnętrzna funkcja z sygnaturą C, która składa parametry-struktury
z rejestrów albo pamięci `byval`, woła funkcję Fincha i obniża jej wynik. Akceptowane są tylko sygnatury zgodne z C.
`tests/run/c_structs.fch` sprawdza `{float,float}`, `{float,float,float}`, `{u8×4}`, `{double,int}`,
`{i64,i64}`, strukturę 24-bajtową, strukturę z `char[8]`, zagnieżdżone struktury, wyczerpanie rejestrów
przy 13 argumentach-strukturach oraz wywołania zwrotne przyjmujące i zwracające struktury, na bibliotece C
kompilowanej przez `link "abi.c"`. Wyniki zgadzają się z tymi samymi wywołaniami zrobionymi z C.
### 13.4 Linkowanie kodu C
`link "nazwa"` próbuje `pkg-config --libs nazwa`, a potem `-lnazwa`. `link "plik.c"` kompiluje plik
(względem pliku `.fch`) przez `$CC -O2 -fPIC -c` do tymczasowego obiektu; pliki `.o` i `.a` przechodzą
bez zmian. Gdy linkowanie się nie uda, `linkFailed()` mapuje niezdefiniowane symbole na nagłówek, który
je zadeklarował, i wypisuje linijkę `link` do dopisania.
---
## 14. Optymalizacja, emisja, linkowanie
- **Cel:** `sys::getDefaultTargetTriple()`, CPU `generic`, `Reloc::PIC_`. Data layout jest ustawiany na
module, zanim powstanie jakikolwiek IR (od niego zależy wyrównanie loadów i store'ów).
- **Optymalizacja:** `PassBuilder::buildPerModuleDefaultPipeline(O2)`: środkowa część `-O2` z clanga.
`-O0` ją pomija (`finch ir plik.fch -O0` pokazuje surowe wyjście `Codegen`).
- **Emisja:** stary `PassManager` + `addPassesToEmitFile(ObjectFile)`.
- **Linkowanie:** `$CC prog.o rt.o [obiekty użytkownika] -lm [biblioteki]`. Na Niksie wrapper cc zamienia
ścieżki `-L` na `RPATH`, więc programy działają bez `LD_LIBRARY_PATH`.
- **`run`:** tymczasowy plik wykonywalny uruchamiany z resztą argumentów i potem usuwany; sygnał jest
zgłaszany jako `the program crashed: <nazwa>` z kodem wyjścia `128 + n`.
Uwagi o wydajności: funkcje użytkownika są `internal`, więc LLVM swobodnie je wstawia i specjalizuje
(rekurencyjne `fib(40)`: ok. 0,19 s wobec ok. 0,23 s dla `clang -O2` na tym samym kodzie C na maszynie
autora); kod tablicowy z kontrolą zakresu dorównuje C na sicie.
---
## 15. Informacje dla debuggera
Z `-g` `Codegen` tworzy `DIBuilder`, jednostkę kompilacji (`DW_LANG_C`, więc składnia wyrażeń C w gdb
działa dla `print p.x`), `DIFile` na plik źródłowy, `DISubprogram` na funkcję, zmienne-parametry
(`createParameterVariable`, więc `bt` pokazuje `length (v=…)`), zmienne lokalne (`insertDeclare` na ich
`alloca`) i `DILocation` na każdą instrukcję i wywołanie (`setLoc`). Typy: podstawowe z nazwami Fincha
(`int`, `u8`, `float`), `str`/tablice jako struktury `{ptr, len, cap}`, wskaźniki, tablice stałe i struktury
z przesunięciami pól z `StructLayout` LLVM. Struktury rekurencyjne używają zastępowalnej deklaracji
wyprzedzającej. Generowane helpery nie mają lokacji. Najlepiej działa `-g -O0`; przy O2 część zmiennych jest usuwana.
---
## 16. Diagnostyka
`failAt(plik, linia, kol, msg)` wypisuje `ścieżka:linia:kol: error: msg` i linijkę źródła ze znakiem `^`
(tabulatory zachowane, kolumny liczone w znakach), a potem kończy proces z kodem 1. Konwencje: operatory
binarne raportują operator, wywołania nazwę funkcji, błędy argumentów pozycję argumentu. Komunikaty mówią,
co jest źle, a gdy naprawa jest oczywista, także jak to naprawić. Ostrzeżeń nie ma. Błąd weryfikatora LLVM
wypisuje `internal compiler error (please report)` i kończy proces z kodem 2.
---
## 17. Kompilator samohostujący (`boot/`)
`boot/` to drugi kompilator Fincha napisany w Finchu (ok. 3100 linii):
| Plik | Zawartość |
|---|---|
| `boot/lexer.fch` | lekser (tokeny to `{kind, text, line, col, nl}`; rodzaje to słowa, a słowa kluczowe i operatory są swoim własnym rodzajem) |
| `boot/ast.fch` | `Type` (`kind`, `elem` jako tablica 0/1 elementów, `name`, `module`), jeden ogólny `Node` na wszystko, `Program` |
| `boot/parser.fch` | ta sama gramatyka i reguły nowych linii co parser C++, wspinanie się po priorytetach dla operatorów binarnych |
| `boot/gen.fch` | sprawdzanie typów + generator **tekstowego LLVM IR**: zasięgi, miejsca, konwersje, kopie, helpery, wypisywanie, kontrole, metody tablic i tekstów |
| `boot/main.fch` | ładowanie modułów i sterownik: zapisuje `.ll`, uruchamia `clang -O2 plik.ll runtime/finch_rt.c` |
Obsługuje rdzeń języka: `int float bool char str`, `[]T`, struktury (wartości domyślne, konstruktory po
nazwie i po kolei), `ptr[T]`/`addr`/`new`/`free`/`null`, wszystkie instrukcje poza `defer`, moduły oraz
funkcje wbudowane i metody kompilatora C++, bez importu C, liczb z rozmiarem i `defer`. Ma tę samą
semantykę wartości i analizę pożyczania (kopiuje tam, gdzie główny kompilator), ale **nigdy nie zwalnia**:
jako kompilator bootstrapowy zostawia pamięć systemowi operacyjnemu.
Stan kompilatora to jedna struktura `Gen` przekazywana jako `ptr[Gen]`: Finch nie ma zmiennych globalnych,
a przekazanie wskaźnika to sposób, w jaki funkcja zmienia wartość wywołującego. Bez `defer` i typu map zasięgi
to tablice tablic przeszukiwane liniowo; funkcje pomocnicze (kopiowanie/wypisywanie/porównywanie na typ)
są generowane przez tymczasową podmianę buforów wyjścia (`beginHelper`/`endHelper`).
**Punkt stały.** `tests/boot.sh`:
1. `build/finch` (C++) kompiluje `boot/` → **stage 1**,
2. stage 1 kompiluje `boot/` → **stage 2** (i jego IR),
3. stage 2 kompiluje `boot/` → IR **stage 3**,
i wymaga, żeby IR ze stage 2 i stage 3 był **identyczny co do bajtu** (ok. 32 000 linii). Potem kompiluje
kompilatorem samohostującym każdy test z `tests/run`, który mieści się w podzbiorze języka, i porównuje
wynik z oczekiwanym. Kompilacja samego siebie zajmuje kompilatorowi samohostującemu ok. 2,3 s.
---
## 18. System budowania
- `find_package(LLVM CONFIG)`; **jeśli istnieje współdzielony cel `LLVM`, Finch linkuje tę jedną bibliotekę.**
`libclang.so` sam linkuje `libLLVM.so`; linkowanie dodatkowo statycznych bibliotek komponentów wprowadzało
do procesu dwie kopie LLVM, a zduplikowane obiekty globalne wywracały program przy wyjściu (podwójne
zwolnienie w globalnym destruktorze). Do tego binarka miała 82 MB zamiast ok. 20 MB.
- libclang: `find_path(clang-c/Index.h)` + `find_library(clang)`.
- `runtime/finch_rt.c` jest wczytywany przy konfiguracji do `build/rt_source.inc` jako surowy literał
napisowy; `CMAKE_CONFIGURE_DEPENDS` ponawia konfigurację, gdy plik się zmieni.
- `FINCH_VERSION` z `project(VERSION 2.0.0)`. C++17, `-Wall -Wextra`, bez ostrzeżeń.
- `shell.nix` wymienia `llvmPackages.clang` przed `llvmPackages.libclang`: ten drugi dostarcza też
„gołego” `clang`, który nie widzi nagłówków systemowych.
---
## 19. Testy
- `tests/run.sh`: każdy `tests/run/*.fch` z `fn main` musi wypisać dokładnie swój `.out` (stdin z `.in`, jeśli
jest); pliki bez `main` to moduły albo pliki pomocnicze. Każdy `tests/fail/*.fch` musi się nie udać z tekstem
z linijki `// expect:`. Obecnie **59 passed, 0 failed**.
- `MEMCHECK=1 tests/run.sh`: to samo plus valgrind na każdym programie (bez wycieków i złych dostępów).
- `tests/boot.sh`: punkt stały samohostowania i przebieg na podzbiorze (§17).
---
## 20. Semantyka a C
| Sytuacja | C | Finch 2.0 |
|---|---|---|
| Przepełnienie liczby ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli stałe, inaczej błąd w czasie działania |
| Indeks tablicy poza zakresem | UB | błąd w czasie działania (także dla pól `[N]T` struktur C) |
| Dereferencja null | UB | błąd w czasie działania dla `.value`, `.pole`, `[i]` |
| Użycie po zwolnieniu / podwójne zwolnienie tablic, tekstów, struktur | częste błędy | niemożliwe: tych nie zwalnia się ręcznie |
| Wyciek tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
| `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` |
| Niejawne zwężanie, mieszanie ze znakiem / bez znaku | po cichu | błędy kompilacji |
| Niezainicjalizowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
| Brak return | UB, jeśli użyty | błąd kompilacji |
| Kolejność obliczeń | w większości nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
| Przesunięcie ≥ szerokość, float→int poza zakresem | UB | nadal poison (jeszcze niesprawdzane) |
| Wiszący wskaźnik z `addr()` / `new` + `free` | UB | nadal UB |
---
## 21. Spis funkcji
### Sterownik (`main.cpp`)
| Funkcja | Robi |
|---|---|
| `Loader::load` / `findModule` | czyta, leksuje i parsuje plik oraz rekurencyjnie jego moduły / szuka `nazwa.fch` |
| `hostMachine`, `optimize`, `emitObject` | maszyna docelowa, potok O2, plik obiektowy |
| `runtimeObject` | kompiluje runtime i trzyma go w cache |
| `capture`, `libFlags`, `link` | uruchamia polecenie / pkg-config albo `-l` / linkowanie, z kompilacją plików z `link "x.c"` |
| `linkFailed`, `guessLib` | zamieniają wyjście linkera na porady |
### Lekser i parser
| Funkcja | Robi | Różni się od |
|---|---|---|
| `lex` / `tokName` | tekst → tokeny / nazwy do komunikatów | |
| `accept` / `expect` / `unexpected` | konsumuje, jeśli pasuje / konsumuje albo zgłasza błąd / błąd z kontekstem | |
| `sameLine` / `endOfStatement` | czy ten token może kontynuować wyrażenie? / czy instrukcja się skończyła? | |
| `atDeclaration` / `type` | podgląd deklaracji / parsowanie typu | |
| `args` | `( [nazwa:] expr, … )` dla wywołań, metod i konstruktorów | |
| `postfix` | łańcuchy `.pole`, `.metoda(…)`, `[i]` | `primary` parsuje początek łańcucha |
### Codegen: program i instrukcje (`codegen.cpp`)
| Funkcja | Robi | Różni się od |
|---|---|---|
| `declareStructs` / `declareFns` / `declare` | wszystkie typy / wszystkie sygnatury / jedna sygnatura + analiza pożyczania | |
| `define` / `defineMainWrapper` | jedno ciało / punkt wejścia C | |
| `pushScope` / `popScope` | otwiera / zamyka zasięg (`popScope` emituje jego sprzątanie, jeśli osiągalne) | |
| `emitCleanups(downTo)` / `emitScopeCleanups(i)` | sprzątanie kilku zasięgów bez zamykania (return/break) / jednego zasięgu | |
| `slot` / `tmpOf` / `tmp` | `alloca` zmiennej / anonimowa / anonimowa z wartością | |
| `lookup` / `addVar` | szuka widocznej zmiennej (respektuje `deferLimit`) / deklaruje zmienną | |
| `stmt`, `varDecl`, `assign`, `ifStmt`, `whileStmt`, `forStmt`, `forEachStmt`, `returnStmt`, `jump` | instrukcje | |
| `rootVar` / `mutates` / `mutatesExpr` | zmienna, od której zaczyna się l-wartość / czy instrukcja albo wyrażenie ją zmienia? | |
### Codegen: wyrażenia
| Funkcja | Robi | Różni się od |
|---|---|---|
| `expr` / `exprWant` | r-wartość / to samo z oczekiwanym typem dla literałów tablic | |
| `ref` / `place` | adres, jeśli jest, inaczej wartość / adres albo błąd | `expr` wczytuje z `ref` |
| `member` / `index` | `.x`, `.len`, `.ptr`, `.value`, auto-dereferencja / `[i]` z kontrolą zakresu i kopiowaniem przy zapisie | |
| `arrayLit` | `[…]` → tablica na stercie | |
| `unary`, `binary`, `arith`/`arithOp`, `compare`, `logic` | operatory (`arith` dokłada flagę literału) | |
| `unify` / `coerce` / `convert` | dwa argumenty do siebie / jedna wartość do miejsca / jawne `T(x)` | |
### Codegen: typy i własność (`codegen_types.cpp`)
| Funkcja | Robi |
|---|---|
| `ty`, `resolve`/`resolveT`, `findStruct`, `resolveStruct` | typ LLVM; Named → Struct (głęboko albo płytko); szukanie struktury; układ + kontrola cykli |
| `owning`, `zero`, `defaultValue` | czy typ posiada pamięć; stała zerowa; zero albo wartości domyślne struktury |
| `own` / `release` | coś do zapisania (przeniesienie albo kopia) / zwolnienie niezatrzymanej wartości fresh |
| `copyValue`, `dropValue`, `copyAt`, `dropAt`, `copyFn`, `dropFn`, `typeKey` | kopiowanie i zwalnianie przez wartość, przez adres i helpery na typ |
| `forN` | wewnętrzna pętla `for i in 0..n` używana przez helpery |
| `strConst`, `cstr`, `strFromC`, `elemSize` | stała-literał str; str → `char*`; `char*` → pożyczony str; rozmiar elementu |
| `emitPrint`, `printFn`, `printf_` | wypisywanie |
| `diType`, `setLoc` | informacje dla debuggera |
### Codegen: wywołania (`codegen_builtins.cpp`) i ABI (`abi.cpp`)
| Funkcja | Robi |
|---|---|
| `call` / `method` | rozwiązuje wywołanie / metodę albo wywołanie z modułu |
| `callFinch` / `construct` / `callC` | funkcja Fincha / konstruktor struktury / funkcja C |
| `print`, `addrOf`, `convert`, `arrayMethod` | funkcje wbudowane |
| `rt` / `libc` | deklaruje funkcję runtime'u / libc |
| `fileName`, `panicIf`, `boundsCheck`, `checkDivisor` | kontrole w czasie działania |
| `classify`, `makePlan`, `declareC`, `emitCCall`, `cThunk` | klasyfikacja System V, plan rejestrów, deklaracja, wywołanie, wrapper dla callbacków |
---
## 22. Rozbudowa kompilatora
**Funkcja wbudowana:** dodaj nazwę do `isBuiltin()`, obsłuż ją w `call()`, zaimplementuj (w IR albo jako
funkcję runtime'u zadeklarowaną w tabeli `rt()`), dodaj przypadki w `tests/run/` i `tests/fail/`. Jeśli ma
istnieć też w kompilatorze samohostującym, dodaj ją do `call()` i `runtimeDecls()` w `boot/gen.fch`.
**Metoda tablicy albo tekstu:** dodaj gałąź w `arrayMethod()` albo w części `str` w `method()`; jeśli
zmienia tablicę, dopisz jej nazwę do zbiorów `changing` w `method()` i `mutatesExpr()`, żeby pożyczanie
dalej było poprawne.
**Operator:** token leksera + `tokName`, `BinOp`, właściwa funkcja priorytetu w parserze, `opName`, emisja
w `arithOp`/`compare` i wyjaśnienie w `badOperands`.
**Instrukcja:** `StmtKind` + węzeł, `Parser::statement`, `Codegen::stmt`; jeśli przenosi sterowanie,
respektuj `terminated()`, używaj `continueAt()` dla bloków łączących i emituj sprzątanie (`emitCleanups`)
przed wyskokiem z zasięgów.
**Nowy typ posiadający pamięć:** rozszerz `owning()`, `dropAt`/`copyAt` (i ich helpery), `emitPrint`, `diType`.
---
## 23. Znane ograniczenia
- Brak metod w strukturach, typów generycznych, map, `match`, domknięć i błędów jako wartości (`int("x")`, `read_file` robią panic).
- Teksty to bajty: `.len`, `s[i]`, `upper()` nie znają Unicode.
- Wiszące wskaźniki (`addr` zmiennej, która zniknęła, użycie po `free`) nie są wykrywane.
- Wielkość przesunięć i konwersje float→int nie są sprawdzane (poison w LLVM, jak w C).
- C: unie i struktury z polami bitowymi przez wartość, makra-funkcje, `long double`.
- Testowana jedna platforma: Linux x86-64 (kod ABI jest specyficzny dla System V x86-64).
- Kompilator samohostujący nie ma importu C, liczb z rozmiarem ani `defer` i nie zwalnia pamięci.