From a0c54a125263df82705d4939940c870b3c05fd0d Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 21:55:42 +0100 Subject: [PATCH] docs: the engineers' guide describes the compiler written in Finch Rewritten (EN+PL): architecture, source tree, front end, code generation, C import and ABI, driver, bootstrapping and the language server. The type, ownership and semantics sections are kept; the C++ function reference is gone (branch before-bootstrap has that version). Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX --- README.md | 2 +- README.pl.md | 2 +- docs/en/for-engineers.md | 992 ++++++------------------------------ docs/pl/dla-inzynierow.md | 1005 ++++++------------------------------- 4 files changed, 297 insertions(+), 1704 deletions(-) diff --git a/README.md b/README.md index f626514..db0e813 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Pick the guide that fits you: | [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: list the GPUs, their memory and queues, create a device | | [`examples/raylib.fch`](examples/raylib.fch) | raylib with C structs by value | | [`examples/llvm.fch`](examples/llvm.fch) | Finch building LLVM IR through the LLVM-C API | -| [`boot/`](boot/) | the Finch compiler, written in Finch | +| [`src/`](src/) | the compiler, written in Finch (the seed to build it is in [`bootstrap/`](bootstrap/)) | ## Roadmap diff --git a/README.pl.md b/README.pl.md index 9422f8a..7931879 100644 --- a/README.pl.md +++ b/README.pl.md @@ -125,7 +125,7 @@ Wybierz przewodnik dla siebie: | [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: lista kart graficznych, ich pamięć i kolejki, tworzenie urządzenia | | [`examples/raylib.fch`](examples/raylib.fch) | raylib ze strukturami C przez wartość | | [`examples/llvm.fch`](examples/llvm.fch) | Finch budujący LLVM IR przez API LLVM-C | -| [`boot/`](boot/) | kompilator Fincha napisany w Finchu | +| [`src/`](src/) | kompilator napisany w Finchu (ziarno do jego zbudowania jest w [`bootstrap/`](bootstrap/)) | ## Plan rozwoju diff --git a/docs/en/for-engineers.md b/docs/en/for-engineers.md index 5d3d628..faf00dd 100644 --- a/docs/en/for-engineers.md +++ b/docs/en/for-engineers.md @@ -1,14 +1,13 @@ # Finch for Engineers -**The complete internals of Finch 2.0.** How source becomes tokens, tokens an AST, and the AST -LLVM IR, which is then optimized, emitted and linked against a small C runtime. The exact type -rules; the ownership model and how it lowers to copies and drops; how every construct becomes IR -(with real compiler output); how C headers, C structs and the System V ABI are handled; modules, -debug info, and the self-hosted compiler in `boot/`. Every function in the compiler is listed with -what it does and how it differs from its neighbours. +**The internals of Finch 2.4, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*, +which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how +the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a +seed; and the language server. -Assumed: you know C/C++ and have at least seen LLVM IR. -For the language from a user's point of view, see the [technicians' guide](for-technicians.md). +Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) and have at least seen LLVM IR. +The compiler's first version, in C++ on LLVM's C++ API, is kept on the `before-bootstrap` branch; this guide describes +the Finch one. > 🇵🇱 Polska wersja: [dla-inzynierow.md](../pl/dla-inzynierow.md) @@ -18,71 +17,51 @@ For the language from a user's point of view, see the [technicians' guide](for-t 1. [Architecture](#1-architecture) 2. [Source tree](#2-source-tree) -3. [Lexer](#3-lexer) -4. [Grammar and parser](#4-grammar-and-parser) -5. [AST](#5-ast) -6. [Loading: files and modules](#6-loading-files-and-modules) -7. [Semantic model](#7-semantic-model) -8. [Type system](#8-type-system) -9. [Ownership and memory](#9-ownership-and-memory) -10. [Code generation](#10-code-generation) -11. [Runtime checks](#11-runtime-checks) -12. [The C runtime](#12-the-c-runtime) -13. [C interop: headers, structs, ABI](#13-c-interop-headers-structs-abi) -14. [Optimization, emission, linking](#14-optimization-emission-linking) -15. [Debug info](#15-debug-info) -16. [Diagnostics](#16-diagnostics) -17. [The self-hosted compiler (boot/)](#17-the-self-hosted-compiler-boot) -18. [Build system](#18-build-system) -19. [Testing](#19-testing) -20. [Semantics vs. C](#20-semantics-vs-c) -21. [Function reference](#21-function-reference) -22. [Extending the compiler](#22-extending-the-compiler) -23. [Targets: Windows, macOS, ARM64](#23-targets-windows-macos-arm64) -24. [The language server](#24-the-language-server) -25. [Editor integrations](#25-editor-integrations) -26. [Known limitations](#26-known-limitations) +3. [Front end: lexer, parser, modules](#3-front-end-lexer-parser-modules) +4. [Code generation](#4-code-generation) +5. [C import and the C ABI](#5-c-import-and-the-c-abi) +6. [Driver, linking and targets](#6-driver-linking-and-targets) +7. [Type system](#7-type-system) +8. [Ownership and memory](#8-ownership-and-memory) +9. [Generics, enums and match](#9-generics-enums-and-match) +10. [Bootstrapping](#10-bootstrapping) +11. [The language server](#11-the-language-server) +12. [Semantics vs. C](#12-semantics-vs-c) +13. [Known limitations](#13-known-limitations) --- ## 1. Architecture -Finch is a single-pass front end in front of LLVM: type checking and IR generation are one walk -over the AST, with no intermediate representation of its own. +The compiler has no LLVM library inside it. It writes LLVM IR as **text** and lets `clang` do optimisation, code +generation and linking. That is what makes it small (about 8k lines) and what lets it be written in Finch itself. ``` - main.fch ──► Loader::load() ── lex() ── parse() ──► Program (+ each imported module, recursively) - │ - all `import "x.h"` ──► importHeaders() (libclang) ──► CImports - │ - hostMachine() ──► TargetMachine (triple + data layout)│ - ▼ - generate(progs, cimports, tm, debug) ──► Codegen::run() - declareStructs() all Finch + C structs, LLVM struct bodies, cycle check - declareFns() every function's signature (+ "borrow" analysis of parameters) - define() each body: type check + IRBuilder, scopes, drops, runtime checks - defineMainWrapper the C `main(argc, argv)` - DIBuilder::finalize (with -g) ; verifyModule - ▼ - optimize() PassBuilder default (skipped with -O0) - emitObject() legacy PM + addPassesToEmitFile ──► prog.o - link() cc prog.o ~/.cache/finch/rt-.o [your .c/.o/.a] -lm [pkg-config/-l…] - ▼ - executable (finch run: executed from a temp file with the remaining arguments, then deleted) + main.fch ─► loader ── lex ── parse ──► Program (+ every imported module, recursively) + │ + `import "x.h"` ─► cimport.fch (libclang, called from Finch) ─► C functions, structs, constants + │ + ▼ + gen.fch ─► generate(): declare structs, declare functions, define bodies + type check and emit IR in one walk; ABI classification for C calls + │ (IR text) + ▼ + linker.fch ─► clang -c prog.ll ─► clang prog.o rt-.o [your .c/.o/.a] -lm [pkg-config / -l] + │ + ▼ + executable (finch run: run from a temp file, then delete it) ``` Design decisions that shape the rest: -- **Fail fast.** The first error prints a diagnostic and exits (`failAt()` in `src/error.h`). No error - recovery, so later phases never see half-valid input. -- **One walk.** `Codegen` carries scopes, loops, the current function and module. Each expression - returns its IR value, its Finch type and two flags (`Value_`, §8). -- **Let LLVM do the work.** Locals are `alloca`s promoted by `mem2reg`/SROA. Finch builds φ-nodes by - hand only for `&&`/`||`. -- **Values, not references.** Arrays, strings and structs have value semantics: assignment copies, the - end of a block drops. Ownership is decided at compile time with no reference counting (§9). -- **A tiny C runtime** (`runtime/finch_rt.c`, ~430 lines) does what would be tedious in IR: string - operations, array growth, input, files, panics. It is embedded in the compiler and cached. +- **Fail fast.** The first error prints `file:line:col: error: …` with the source line and a caret, and the compiler stops. + No error recovery, so later phases never see half-valid input. +- **One walk.** `gen.fch` type checks and emits IR in one pass over the AST. Each expression yields an IR operand, its + Finch type (a string kind: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) and flags. +- **Let clang do the work.** Locals are `alloca`s promoted by `mem2reg`. Optimisation is clang's `-O2` (or `-O0` with `-g`). +- **Values, not references.** Arrays, strings and structs have value semantics; ownership is decided at compile time (§8). +- **A tiny C runtime** (`runtime/finch_rt.c`, ~760 lines) does what is tedious in IR: strings, array growth, maps, input, + files, panics. It is compiled once and cached in `~/.cache/finch`. --- @@ -90,221 +69,96 @@ Design decisions that shape the rest: | File | Lines | Responsibility | |---|---|---| -| `src/error.h` | 50 | `g_files` (all sources), `failAt(file, line, col, msg)`, `fail(line, col, msg)`, `FinchError` | -| `src/lexer.h/.cpp` | 250 | `Tok`, `Token`, `lex(fileIndex)`, `tokName()` | -| `src/ast.h` | 360 | `Type` (incl. Array, Map, Fixed, Struct, Named), all `Expr`/`Stmt` nodes, `FnDecl`, `StructDecl`, `Import`, `Link`, `Program` | -| `src/parser.h/.cpp` | 610 | Recursive-descent `Parser`, `typeFromName()` | -| `src/loader.h/.cpp` | 90 | Reading files and modules (`Loader`), `FINCH_PATH` | -| `src/target.h/.cpp` | 210 | `g_target`: triple, C compiler, processes, paths; backend initialisation | -| `src/cimport.h/.cpp` | 430 | libclang import: functions, C structs (fields, offsets), constants, globals | -| `src/codegen.h` | 15 | `generate()` | -| `src/codegen_impl.h` | 310 | The `Codegen` class and its helper structs, shared by the files below | -| `src/codegen.cpp` | 1460 | Program, declarations (functions, methods), scopes and cleanups, statements, expressions, places (`ref`), operators, errors as values, mutation analysis | -| `src/codegen_types.cpp` | 580 | LLVM types, type resolution, struct layout, coercions, ownership (copy/drop helpers), text formatting, debug types | -| `src/codegen_builtins.cpp` | 890 | Calls, methods, constructors, built-ins, conversions, array and str methods, runtime declarations, panics | -| `src/codegen_map.cpp` | 240 | Maps: indexing, literals, methods, iteration | -| `src/abi.cpp` | 370 | C calling conventions (System V x86-64, Microsoft x64, AAPCS64), C calls with structs by value, C-callable thunks | -| `src/lsp.cpp`, `src/index.h`, `src/builtins_doc.h` | 700 | The language server (§24) | -| `src/main.cpp` | 350 | Driver: CLI, target machine, O2, object emission, runtime cache, linking, link-error advice, `run` | -| `runtime/finch_rt.c` | 740 | The runtime library (§12) | -| `boot/*.fch` | 3100 | The self-hosted compiler (§17) | +| `src/lexer.fch` | 220 | Tokens, positions, indentation-free layout | +| `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) | +| `src/parser.fch` | 780 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link` | +| `src/gen.fch` | 5200 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, `-g` debug metadata, the `index` mode | +| `src/cimport.fch` | 560 | libclang: functions, structs (fields, offsets, padding), enums, constants, globals, macros | +| `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system | +| `src/linker.fch` | 300 | The cache, runtime object, libraries (`link`, pkg-config), link-error advice | +| `src/main.fch` | 310 | Driver: CLI, loader with module lookup and overlays, `build/run/ir/index/lsp` | +| `src/lsp.fch` | 875 | The language server (§11) | +| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion | +| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | +| `runtime/finch_rt.c` | 760 | The C runtime | +| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) | --- -## 3. Lexer +## 3. Front end: lexer, parser, modules -`lex(file)` makes one pass over `g_files[file].text` and returns `std::vector` ending in `Tok::End`. +**Lexer.** Newlines end statements; there is no indentation rule. Tokens carry file, line and column, which every error uses. -```cpp -struct Token { Tok kind; std::string text; int line, col; bool newlineBefore; }; -``` +**Parser.** Recursive descent, one function per construct. Notable points: types are parsed into a name string +(`[]str`, `map[str]int`, `ptr[char]`, `Stack[int]`); `fn Type.method` declares a method; `fn name[T]` and `struct Name[T]` +declare templates; `enum` and `match` have their own nodes; `export fn` marks a C-visible function; a block records +its `endLine` (used by the language server). The old `fn name(...) type` order is a parse error with a hint. -- **Newlines are not tokens.** Each token records `newlineBefore`; the parser decides what it means (§4). - This keeps newline handling out of every list rule in the grammar. -- **Positions:** 1-based; `col` counts characters (UTF-8 continuation bytes don't advance it), so - carets line up under `"Błąd"`. A stray non-ASCII character is reported whole, with a hint that names are ASCII-only. -- **Identifiers / keywords:** `[A-Za-z_][A-Za-z0-9_]*`. Keywords: - `fn return if else while for in break continue true false null import link struct defer or try`. - (`self` and `map` are ordinary identifiers with a meaning in context: inside a method, before `[`.) - **Type names are not keywords**: `int`, `u8`, `ptr`… are `Ident`s recognised by the parser through - `typeFromName()`, so `u8(x)` is an ordinary call and the lexer doesn't depend on the type list. -- **Numbers:** decimal or `0x` hex with `_` separators. No octal (`010` is 10). A `.` belongs to a number - only when a digit follows, so `0..10` is `Int DotDot Int`. -- **Strings / chars:** escapes `\n \t \r \0 \\ \" \'`. No newline inside a literal. -- **Operators:** maximal munch (`->` before `-`, `<<`/`<=` before `<`, `&&` before `&`, `..` before `.`, - `:=` before `:`). `:` alone is a token (named arguments). -- **Comments:** `//` and non-nesting `/* */`. +**Modules.** `import name` looks, in order, at `name_.fch` next to the importing file (`windows`, `linux`, `macos`), +`name.fch` there, the folders of `FINCH_PATH`, then the standard library `std/`. Each module is parsed once; its names +are used as `module.name`. `import "x.h"` goes to the C importer instead. --- -## 4. Grammar and parser +## 4. Code generation -`Parser` (`src/parser.cpp`) is hand-written recursive descent with one token of lookahead, plus up to -three in `atDeclaration()`. +`gen.fch` is one big `Gen` struct plus functions over it. The passes are: -```ebnf -program = { import | link | struct | function } ; -import = "import" ( STRING | IDENT ) ; (* "x.h" = C header, name = Finch module *) -link = "link" STRING ; (* "glfw" | "file.c" | "file.o" | "lib.a" *) -struct = "struct" IDENT "{" { type IDENT [ "=" expr ] NEWLINE } "}" ; -function = "fn" IDENT [ "." IDENT ] "(" [ param { "," param } ] ")" [ "->" ( type [ "!" ] | "!" ) ] block ; - (* fn Point.move: a method; "!": can fail *) -param = type IDENT ; -type = "[" "]" type | "map" "[" type "]" type | "ptr" [ "[" type "]" ] - | TYPENAME | IDENT [ "." IDENT ] ; +1. `declareStructs`: every Finch and C struct gets an LLVM type (C structs with their real padding); recursive-by-value + structs are refused. +2. `declareFns`: signatures, with a *borrow analysis* for parameters (a parameter that is never stored is passed without + a copy). +3. `define`: each body, type checked and emitted. Scopes track what must be dropped when they end (`defer` runs first). +4. `defineMainWrapper`: the C `main(argc, argv)`, and `finch_on_fatal` for embedders. +5. With `-g`, DWARF metadata is attached as the IR is written (use `-O0`). -block = "{" { statement TERMINATOR } "}" ; -statement = if | while | for | return | "break" | "continue" | "defer" statement | block - | type IDENT [ "=" expr ] (* declaration, see atDeclaration *) - | IDENT ":=" expr - | target assignop expr (* target: var, field, element, p.value *) - | call | method | try | orelse ; -for = "for" IDENT [ "," IDENT ] "in" expr ( ".." expr block | block ) ; (* range | for-each *) -assignop = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ; +Generics are **monomorphised**: a call `max(1, 2)` infers `T`, then the template's AST is generated again with `T` bound, +under a mangled name (`max$int`). `match` over an enum is exhaustive (a missing variant is an error, `else` covers the +rest); on a value it is a chain of comparisons. Enum values are `i32`; `Color(n)` is range checked; printing uses a +generated name table. -expr = or [ "or" ( block | expr ) ] ; (* f(x) or 0, f(x) or { ... }: lowest, right to left *) -or = and { "||" and } ; and = cmp { "&&" cmp } ; -cmp = add { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) add } ; -add = mul { ( "+" | "-" | "|" | "^" ) mul } ; -mul = unary { ( "*" | "/" | "%" | "<<" | ">>" | "&" ) unary } ; -unary = ( "-" | "!" | "~" ) unary | "try" postfix | postfix ; -postfix = primary { "." IDENT [ args ] | "[" expr "]" } ; -primary = INT | FLOAT | STRING | CHAR | "true" | "false" | "null" - | IDENT [ args ] | "(" expr ")" | "[" [ expr { "," expr } [","] ] "]" - | "[" ":" "]" | "[" expr ":" expr { "," expr ":" expr } [","] "]" ; (* maps *) -args = "(" [ arg { "," arg } [","] ] ")" ; arg = [ IDENT ":" ] expr ; -``` - -**Statement termination.** After a statement, `endOfStatement()` requires `}`, end of file, or a token -with `newlineBefore`. Inside expressions `sameLine()` (`parenDepth > 0 || !newlineBefore`) decides whether -an operator, `.`, `[` or a call's `(` continues the expression. `parenDepth` grows inside `( )` and `[ ]` -and is saved/reset to 0 when entering `{ }`. - -**Precedence is Go's**, not C's: `&` binds like `*`, and `|`/`^` like `+`, all above comparisons, so -`x & MASK == 0` means `(x & MASK) == 0`. - -**Recognising statements.** `atDeclaration()` is true for `[` `]` (an array type), `TYPE IDENT`, -`ptr [`, and `IDENT . IDENT IDENT` (a module's type). Otherwise the parser **parses a whole expression, -then looks at the next token**: an assignment operator makes it a target (it must be a `Var`, `Member` or -`Index`), otherwise it must be a call or method call, or it's `this value is computed but never used`. - -**Struct literals are calls**: `Point(1, 2)` or `Point(x: 1, y: 2)`. Brace-based literals (`Point{…}`) -would be ambiguous with blocks (`if done {`); a call needs no special grammar, and named arguments -(`IDENT ":"` in `args()`) are only accepted by constructors at code generation time. +Errors as values: `-> T!` returns a pair (value, error text); `try` returns early on error, `or` handles it, `error("…")` +makes one. --- -## 5. AST +## 5. C import and the C ABI -All nodes are in `src/ast.h`: small class hierarchies with an explicit `kind`, dispatched with -`switch` + `static_cast`. +`cimport.fch` calls **libclang** directly from Finch (it is itself written with `import "clang-c/Index.h"`), parses the +header with the target's flags, and walks the cursors. Types map as: `const char *` parameters → `str`, other +`char *` → `ptr[char]`, a `char *` result → `str`; integer types by size and signedness (`char` is unsigned on ARM64 +Linux); structs and enums by layout. -| ExprKind | Node | Fields | -|---|---|---| -| `Int` `Float` `Bool` `Char` `Str` | literals | `value` | -| `Null` | `NullExpr` | | -| `Var` | `VarExpr` | `name` | -| `Unary` | `UnaryExpr` | `op` (`- ! ~`), `operand` | -| `Binary` | `BinaryExpr` | `BinOp op`, `lhs`, `rhs` | -| `Call` | `CallExpr` | `callee` (a name), `args`, `argNames` | -| `Member` | `MemberExpr` | `obj`, `field` (`.x`, `.len`, `.ptr`, `.value`) | -| `Index` | `IndexExpr` | `obj`, `index` | -| `ArrayLit` | `ArrayLitExpr` | `elems` | -| `Method` | `MethodExpr` | `obj`, `name`, `args`, `argNames`; also `module.fn(...)` and `p.method(...)` | -| `MapLit` | `MapLitExpr` | `keys`, `values` (`[:]` has none) | -| `Try` | `TryExpr` | `call` | -| `OrElse` | `OrElseExpr` | `call`, and either `fallback` (an expression) or `block` | +LLVM IR does not do the C calling convention for you, so `gen.fch` reproduces it: -| StmtKind | Node | -|---|---| -| `Block` `VarDecl` `Assign` `Expr` `If` `While` `For` `ForEach` `Return` `Break` `Continue` `Defer` | as named; `ForEachStmt` has `var`, `var2` (`for i, x` / `for k, v`), `list`, `body`; `DeferStmt` holds one statement | +- `classify` – System V x86-64: a struct up to 16 bytes goes in registers by eightbyte class (INTEGER/SSE), larger in memory; +- `classifyArm64` – AAPCS64, including homogeneous float aggregates (HFA); +- the Microsoft x64 rules (1, 2, 4, 8-byte structs by value in a register, the rest by pointer); +- `makePlan` turns the classification into a call plan; `cThunk` makes `addr(fn)` callable from C (callbacks). -`FnDecl` has `recv` (the struct of a method, `""` otherwise) and `fallible` (`-> T!`). - -`math.sqrt(x)` parses as a `MethodExpr` on `VarExpr("math")`; whether `math` is a module or a variable -is decided during code generation. Calls name their callee instead of holding an expression: Finch has -no first-class functions, and resolving the name late lets one `CallExpr` be a built-in, a conversion -(`u8(x)`), a struct constructor, a Finch function or a C function. - -There is no "load" node: whether `x` means its address or its value depends on context (`ref()` vs -`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, Map, Fixed, Struct, Named }; - Kind kind; - std::shared_ptr elem; // Ptr (null = untyped `ptr`), Array, Fixed; Map: the value - std::shared_ptr key; // Map - long long count; // Fixed - std::string name, module; // Named (as written) / Struct (resolved) - StructInfo *info; // Struct -}; -``` - -The parser produces `Named` for struct names; `Codegen::resolve()` turns them into `Struct` with an -`info` pointer. `Fixed` (`[N]T`) only comes from C struct fields. `int` is `I64` and `float` is `F64`. -Equality is structural; two `Struct`s are equal iff they share `info`. +The same code is what lets the compiler call libclang, whose API passes `CXCursor`, `CXType` and `CXString` by value. --- -## 6. Loading: files and modules +## 6. Driver, linking and targets -`Loader::load(path, module, from)` in `main.cpp` reads a file, appends it to `g_files` (positions carry -the file index in `Pos::file`), lexes and parses it, then loads each `import name` that wasn't seen yet: -`name.fch` next to the importing file, or in a `FINCH_PATH` folder. The result is a `std::vector`, -main file first. Cycles are fine: a module is marked loaded before its own imports are followed. +`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--shared] [--overlay path=file]`. +`ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the +`export fn`s. -All files become **one LLVM module**. Each Finch module has a `ModuleScope` (functions, structs, the set -of modules it imports). Names are looked up in the current module; another module's names are reachable -only qualified (`geometry.length`, `geometry.Vec`) and only if imported there. Functions are emitted as -`finch..` (`finch.` in the main file), struct types as `finch..`. +`--target windows|linux|arm64|macos|` changes the triple and data layout, the C `char` signedness, the ABI rules +above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking +asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing +`link "…"` is suggested. The runtime object is keyed by a hash of its source and the target. + +Environment: `FINCH_PATH` (module folders), `FINCH_RT` (runtime source), `FINCH_CC` (C compiler/linker; the IR is always +compiled by `clang`, never by `CC`), `FINCH_LIBCLANG` (for `bootstrap/build.sh`). + +The compiler finds `runtime/` and `std/` next to itself (`../`, `./`) or under `../share/finch/` (`bootstrap/install.sh`). --- -## 7. Semantic model - -### Declaration order - -`Codegen::run()` first declares **all structs** of all modules plus all imported C structs, resolves -every field type and sets the LLVM bodies (`declareStructs` → `resolveStruct`), then declares **all -function signatures** (`declare`), then defines bodies. So order inside a file and across modules never -matters, and recursion (also mutual, also across modules) works. - -`resolveStruct` detects by-value cycles with a three-state flag (`struct A { A inner }` is an error with -the advice to use `ptr[A]` or `[]A`). Pointers and arrays of a struct don't need its layout, so -`struct Node { []Node kids; ptr[Node] parent }` is fine: they resolve the name "shallowly" (`resolveT(…, deep=false)`). - -### Scopes and variables - -```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 vars; vector cleanups; }; -``` - -A scope is pushed for the function's parameters, each `{ }`, each `for` (holds `i`), each `for … in` -(the hidden list temporary) and each iteration of it (holds `x`). - -- **No shadowing:** `addVar()` rejects a name found in *any* enclosing scope, a function of the module, - an imported module name or a built-in. -- **Read-only:** range and for-each variables (`readonlyWhy` gives the specific message). -- `order` numbers declarations; `defer` uses it to hide variables declared after it (§9.5). - -### Name resolution - -Value position `x`: local variable → C constant → C global → (module name: error with a hint) → error. -Call `n(...)`: built-ins (`print addr input new free exit shell read_file write_file file_exists`) → -type names (conversion) → function of the current module → struct of the current module / C struct -(constructor) → C function → error (with a hint if the function exists in another module, or a -`headerHint` for well-known libc names). - ---- - -## 8. Type system +## 7. Type system ### LLVM representation @@ -314,13 +168,13 @@ type names (conversion) → function of the current module → struct of the cur | `char` `i8` `u8` / `i16` `u16` / `i32` `u32` / `int` `u64` | `i8` / `i16` / `i32` / `i64` | | `f32` / `float` | `float` / `double` | | `str`, `[]T` | `{ ptr, i64, i64 }` (pointer, length, capacity) | -| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; §10.12) | +| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; see src/gen.fch) | | `ptr`, `ptr[T]`, `null` | `ptr` (opaque) | | `[N]T` | `[N x T]` | | Finch struct | named struct type, natural layout | | a method's `self` | `ptr` (the first parameter) | | the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) | -| C struct | named **packed** struct with explicit padding bytes (§13.2) | +| C struct | named **packed** struct with explicit padding bytes (§5) | Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, `sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`. @@ -335,7 +189,7 @@ struct Value_ { Value *v; FType type; bool literal; bool fresh; }; it fits (`fits()`), or `f32`. It survives unary minus and literal-only arithmetic that folds to a constant; explicit conversions clear it (`u8(250)` is a typed constant, so `u8(250) + 10` wraps to 4). - **`fresh`**: an owning value (str/array/owning struct) that nobody holds yet: the result of a call, - a literal, a concatenation, a constructor. It must be stored (moved) or released (§9). + a literal, a concatenation, a constructor. It must be stored (moved) or released (§8). ### Implicit conversions: `coerce(v, want)` @@ -374,7 +228,9 @@ signedness (char unsigned). Bool: equality only. Arrays/structs: error. --- -## 9. Ownership and memory +--- + +## 8. Ownership and memory ### 9.1 The model @@ -494,375 +350,54 @@ iteration, nested arrays of structs, and C struct passing. --- -## 10. Code generation +--- -### 10.1 Variables +## 9. Generics, enums and match -Every variable, including parameters, gets an `alloca` at the top of the entry block (`slot()`; -C structs get their C alignment). `addVar()` stores the initial value, registers the variable, adds a -drop cleanup if it owns its value, and (with `-g`) declares it to the debugger. +See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the +[technicians' guide](for-technicians.md). The tests are `tests/new/generics.fch`, `tests/new/enums.fch` and their refusals +in `tests/new/fail/`. -### 10.2 Places: `ref()`, `place()`, `expr()` +--- -```cpp -struct LRef { bool isPlace; Place pl; Value_ val; }; // an address, or (if none) a value +## 10. Bootstrapping + +The compiler is built from a **seed**: its own IR, committed as `bootstrap/.ll` (see +[bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh`: + +``` +seed .ll ──clang──► finch0 ──builds src/──► finch1 ──builds src/──► build/finch ``` -`ref(e, forWrite)` walks a `Var`/`Member`/`Index` chain and returns an **address** when one exists: +and stops unless `finch1` and `finch` produce byte-identical IR (a fixpoint: the compiler compiles itself the same way +whatever built it). `bootstrap/build.sh seed` rewrites the seed from the current compiler; do it whenever the compiler's +own source starts to use a feature the committed seed lacks. -- `Var` → its `alloca` (or a C global). -- `.field` on a struct place → `getelementptr` to the field (`llvmIndex`; C structs skip padding elements). -- through a pointer (`p.value`, `p.field`, `p[i]`) → load the pointer, check it isn't null, address the target. - `p.value` on a pointer to a struct that has its own `value` field means that field. -- `a[i]` on an array/str place → load `{ptr,len,cap}`, `boundsCheck(i, len)`, `getelementptr`. For - `forWrite` on a `str`, `finch_str_own` first (copy-on-write for literals). `[N]T` checks against `N`. -- `.len` / `.ptr` → values. -- a temporary (call result…) has no address: its field/element is extracted, copied if owning, and the - temporary released. +A seed is per system because the C calling convention for the libclang calls is baked into it (§5). +Linux x86-64 and Linux ARM64 have seeds. macOS and Windows would need seeds made with Apple's and Microsoft's headers. -`expr()` on these kinds calls `ref()` and loads from the address: this is where every `load` of a -variable, field or element comes from. `place()` demands an address (assignment targets, `addr()`). +--- -### 10.3 Assignment +## 11. The language server -`x = v`: evaluate `v` **first** (it may reallocate memory the target lives in: `a[0] = f()` where `f` -pushes to `a`), then `place(target, forWrite)`, `coerce`, `own`, load the old value, store, drop the old -one. Compound `x += v`: evaluate `v`, load the target, `arith()`, store, drop the old value (for `str +=`). +`finch lsp` speaks JSON-RPC over stdin/stdout (`Content-Length` framing, `std/json`). It does not type check; it runs +`finch index file.fch --overlay path=copy …` for every change, where the overlays are the editor's unsaved text. In `index` +mode the compiler prints, besides IR-free output, tab-separated records: -### 10.4 Functions and `main` - -Functions are `internal` with their natural LLVM signature (aggregates by value). Owning parameters are -copied at entry only if `borrowParam` is false. Methods (§10.10) get `self`'s address as an extra first -parameter; fallible functions (§10.11) return `{ i1, T, str }`. `fn main() -> !` / `-> int!` makes the -wrapper print `error: ` to stderr and exit with 1 on failure. `fn main` is the internal `finch.main`; a separate -external `i32 main(i32, ptr)` (`defineMainWrapper`) builds `[]str args` with `finch_args` if requested, -calls it, drops the arguments and returns the exit code. A function that falls off its end gets the -scope cleanups and `ret void`, or the error `can reach its end without returning T`. - -### 10.5 Control flow - -The builder's current block is the source of truth for reachability (`terminated()`): statements after -a terminator are errors (`this code can never run`). `continueAt()` deletes a merge block nobody jumps -to. `if`/`while`/`for` are the classic block layouts; `while true` without `break` leaves the function -terminated (no `return` needed after it). `for i in a..b` evaluates both ends once; `continue` goes to -`for.step`. `for x in list` reads the list's length from its address **every round** (pushing inside the -loop is safe) and keeps the index in a stack slot. `break`/`continue` emit the cleanups of the scopes -opened inside the loop (`Loop::scopeDepth`) before jumping. - -### 10.6 Short-circuit `&&` / `||` - -The only hand-built φ: the left side ends in block `from`, the right side in `rhsEnd`; -`phi i1 [!isAnd, from], [rhs, rhsEnd]`. - -### 10.7 Calls - -`callFinch()` coerces arguments to parameter types (array literals via `exprWant`), passes owning ones -shallowly, calls, then releases fresh arguments. Results of owning type are fresh. -`construct()` builds a struct from `zeroinitializer` with `insertvalue`: positional (all fields) or named -(missing ones take the field's default expression, evaluated in the struct's module with the caller's -scopes swapped out, or zero). C calls are in §13. - -### 10.8 Printing - -`print` emits `printf` per scalar argument with a format chosen by type (`%lld`, `%llu`, `%g`, `%c`, -`%.*s` for `str` with its length), separated by spaces, then `\n`. Everything else (pointers, arrays, -fixed arrays, maps, structs) is first **formatted** into a heap `str` buffer by `emitFormat()` and -printed with `finch_buf_print`. Composite types get a generated helper `finch.format.(buf, value*)` -that appends `[1, 2]` / `{"a": 1}` / `Point(x: 1, y: 2)`, quoting strings and chars inside. `str(x)` of -an array, map or struct runs the same helper, so `str(x)` is always what `print(x)` shows. - -### 10.9 Array and str methods - -Arrays: `push` (`finch_arr_reserve` + store + len++), `pop`/`remove` (move out, `remove_gap`), -`insert` (`insert_gap`), `clear`/`resize` (drop the elements that go away, `finch_arr_resize`), -`find`/`contains` (a loop with `compare(Eq)`), `slice` (new array, `copyAt` per element), `reverse` -(swap loop), `sort` (libc `qsort` with a generated comparator `finch.cmp.`), `join` (runtime). -Methods that change the array require an address (a variable/field/element). -Strings: `sub find contains starts_with ends_with split trim upper lower replace repeat bytes`, all in the runtime. -`for i, x in list` adds a read-only `int` index variable next to the element. - -### 10.10 Methods - -`fn Point.move(int dx)` is declared like a function named `finch.[module.]Point.move` with a leading -`ptr` parameter and stored in `Codegen::methods[StructInfo*]` (so methods follow the struct into every -module that uses it, and C structs can have methods too). In `define()`, `self` is a `Var` whose `slot` -**is that pointer argument**: `self.x` is a GEP from the caller's own struct, and `self = …` writes the -caller's value. No copy happens, ever. - -At a call `obj.m(args)` (`structMethod()`), `ref(obj)` gives an address when `obj` is a place (a variable, -field, element, `m[key]`), the loaded pointer for a `ptr[T]` (null-checked), or a stack temporary for a -value (`Point(1, 2).length()`), which is dropped after the call. Two safety rules come from the mutation -analysis: a method that changes `self` can't be called on a read-only loop variable, and if such a method -is given an argument that lives inside the receiver (`c.merge(c)`, `p.add(p.items)`), the argument is -copied first, so `self` can't free memory the argument still points to. - -### 10.11 Errors as values - -A fallible function (`-> T!`) returns `{ i1 ok, T value, str error }`; on failure `value` is zero, so the -caller never has to drop it. `return v` wraps `{1, v, zero}` (`retValue()`); `return error(msg)` evaluates -the message, runs the cleanups and returns `{0, zero, msg}` (`retError()`). - -Calling a fallible function is only allowed as the operand of `or` / `try`. `fallible(call)` sets -`fallibleTarget` to the call expression; `callFinch()` (and the fallible built-ins) compare their own -`CallExpr`/`MethodExpr` against it, and if it matches, leave `fallibleFailed` (`i1`) and `fallibleMsg` -(`str`) for the caller; if a fallible call doesn't match, it's the compile error *'f' can fail, so say what -happens then*. Nested handlers save and restore the three fields. - -| Form | Lowering | +| Record | Meaning | |---|---| -| `f(x) or fallback` | `br failed, or.failed, or.ok`; the failed block drops the message and evaluates the fallback (only there); a φ joins the two values | -| `f(x) or { … }` | the failed block declares `err` (owning the message) and runs the block. If the value is used, the block must end in a terminator; as a statement it may fall through, and the success path releases the unused value | -| `try f(x)` | the failed block runs `emitCleanups(0)` and returns `{0, zero, msg}` from the current function, which must be fallible | +| `@R file line col len defFile defLine defCol hover` | a reference: what hover shows and where the definition is | +| `@V name type file line col fnFile fnLine fnEnd` | a variable and the function it lives in | +| `@F module name recv file line col endLine signature params` | a function or method | +| `@S` / `@SF`, `@E`, `@I` | structs (and fields), enums, imports | -Built-ins with a failure mode check `&call == fallibleTarget` and switch to `finch_str_try_int`, -`finch_str_try_float`, `finch_try_read_file`, `finch_try_write_file`, `finch_try_delete_file`, which -return 1 / 0 and fill an error `str` instead of panicking. The cost on success is one predictable branch; -nothing is allocated unless a failure happens. - -### 10.12 Maps - -The runtime (§12) keeps an insertion-ordered hash table, in the style of CPython's `dict`: an `entries` -array of `{ i64 hash, K key, V value }` slots in insertion order (hash 0 = removed), plus an `index` of -`icap` slots (power of two, ≥ 2 × cap) with linear probing (−1 empty, −2 removed, else an entry number). -The runtime only hashes (FNV-1a + a 64-bit mix; text keys by content, others by bytes), finds and places -entries; the compiler stores, copies and drops keys and values, exactly like array elements. - -| Code | Lowering | -|---|---| -| `m[k]` read | `finch_map_find`; −1 → `finch_map_missing` (noreturn, with the key as text); else a place at `entry.value` | -| `m[k]` write (`=`, `+=`, a changing method, `addr`) | `mapSlot()`: `finch_map_slot` returns the entry, adding it if missing; a new entry gets a copy of the key and the value type's default | -| `["a": 1, …]` | a zeroed map + `mapSlot` per entry (the first entry decides the types; number values are all `float` if one has a dot) | -| `has`, `get`, `remove`, `clear`, `keys`, `values` | `codegen_map.cpp`; `get`'s default is evaluated only when the key is missing | -| `for k, v in m` | a loop over `0..used` that skips removed slots, re-reading `entries` and `used` every round | -| copy / drop / format | helpers like arrays': `finch_map_clone_raw` then deep copies of owning keys and values; drop each live entry, then `finch_map_free` | - -Removing marks the slot (and the index entry) removed; slots are packed when the table would grow while -at least a quarter of them are removed. Removing the last key resets the table. +The first error is printed as `path:line:col: error: message` and becomes a diagnostic. When code does not compile, the +server keeps answering completion and signature help from the last version that did. `tests/lsp_test.py` is the acceptance +test (22 checks). --- -## 11. Runtime checks - -All runtime errors go through `finch_panic(file, line, msg)` / `finch_panic_index(file, line, i, len)` -(declared `noreturn cold`), so the failing branch is laid out cold and the check costs a compare and a -well-predicted branch. LLVM removes checks it can prove (constant indexes in range, loop-hoisted bounds). - -| Check | Where | -|---|---| -| division/remainder by zero (constant zero: compile error) | `checkDivisor()` | -| `MIN / -1` for signed types | `checkDivisor()` | -| array/str/fixed-array index out of range (`icmp uge`, so negative indexes too) | `boundsCheck()` | -| `.value`, `.field`, `[i]` through null | `member()`, `index()` | -| `pop()` on empty, `slice()`/`sub()` out of range | methods, runtime | -| bad text in `int(s)`/`float(s)`, unreadable file in `read_file` (without `or` / `try`) | runtime | -| `m[key]` read with a missing key | `mapIndex()` | -| a method called through a null `ptr[T]` | `structMethod()` | - -A 20-million-element sieve with a bounds check on every access runs as fast as the same C (≈0.09 s): -the checks are hoisted or folded. - ---- - -## 12. The C runtime - -`runtime/finch_rt.c` is plain C with no global state. CMake embeds it into the compiler as a raw string -(`rt_source.inc`); on first use `runtimeObject()` compiles it with `$CC -O2 -fPIC -c` into -`~/.cache/finch/rt-.o`, where the hash covers the source, the Finch version and the C compiler. -It is linked statically into every program. - -The shared layout: - -```c -typedef struct { char *ptr; int64_t len; int64_t cap; } FStr; // ptr always NUL-terminated -typedef struct { void *ptr; int64_t len; int64_t cap; } FArr; -// str cap: > 0 heap (owned), 0 literal (static), -1 borrowed from C (copy before keeping) -``` - -Functions take and return these **by pointer**, so their C ABI is trivial (no struct classification): -string building (`concat`, `from_int/uint/float/char/bool`, `sub`, `trim`, `upper`, `lower`, `replace`, -`split`, `join`, `repeat`, `from_bytes`), comparison (`eq`, `cmp`, `find`, `starts`, `ends`), ownership -(`copy`, `own`, `drop`, `from_c`), arrays (`reserve`, `make`, `resize`, `clone_raw`, `free`, -`insert_gap`, `remove_gap`), maps (`map_find`, `map_slot`, `map_remove_at`, `map_clear`, `map_free`, -`map_clone_raw`, `map_missing`; §10.12), text buffers for `print`/`str` (`buf_add`, `buf_int`, …, -`buf_print`), the fallible variants (`str_try_int`, `str_try_float`, `try_read_file`, `try_write_file`, -`try_delete_file`, `report_error`), `args`, `input`, files, `shell`, and the panics. Allocation failure -prints `out of memory` and exits. - ---- - -## 13. C interop: headers, structs, ABI - -### 13.1 Importing headers - -`importHeaders(imports, dirs)` parses each `import "x.h"` as its own translation unit from an in-memory -`#include "x.h"`, with `-x c -std=gnu11`, `-I` for every `.fch` file's folder, and `-isystem` for each -directory the system C compiler searches (read from `$CC -E -v -x c /dev/null`, which is what makes it work -on NixOS). `gnu11` because glibc hides `M_PI` and friends in strict ISO mode. - -The `Importer` visits top-level cursors: - -| Cursor | Result | -|---|---| -| `FunctionDecl` | `CFunc` (signature mapped in **signature context**; `static` and K&R functions recorded as unsupported) | -| `TypedefDecl` of a record | names the record (`typedef struct {…} CXCursor` → `CXCursor`) | -| `StructDecl` (definition) | names the record by its tag | -| `VarDecl` `extern` | `CGlobal` (mapped in **data context**) | -| `EnumConstantDecl`, numeric `MacroDefinition` | `CConst` (literal) | - -`mapType()` works on canonical types and maps integers by size. The context decides `char *`: a `str` -in function signatures (Finch converts at the call), but `ptr[char]` inside structs, globals and behind -pointers (where the memory layout must stay a single pointer). Records by value become -`Named{name, "C"}` and are defined with `defineRecord()`: every field (`clang_Type_visitFields`) with its -byte offset, size and alignment, nested records, constant arrays (`Fixed`). Unions and bit fields are kept -as hidden bytes and mark the struct unsupported for by-value calls; pointers to anything else (void, -functions, incomplete structs) become `ptr`. - -### 13.2 Laying out C structs - -`resolveStruct()` builds a **packed** LLVM struct that mirrors C byte for byte: an `[n x i8]` pad before -each field whose offset is ahead of the current position, the field itself (only if its LLVM size matches -C's), and tail padding up to C's `sizeof`. Field accesses use the recorded `llvmIndex`. Packing makes the -layout exact regardless of LLVM's alignment rules; allocas and temporaries get the C alignment explicitly. - -### 13.3 The System V x86-64 calling convention - -`classify(T)` (in `abi.cpp`) flattens a struct into scalar leaves with byte offsets (through nested -structs and fixed arrays) and: - -- **MEMORY** if it is larger than 16 bytes or has a misaligned leaf: passed as a pointer with - `byval(T) align ≥8`, returned through a hidden first `sret(T)` pointer. -- otherwise each eightbyte is **INTEGER** if any leaf in it is an integer/pointer, else **SSE**. Parts: - INTEGER → `iN` for the bytes that are left (`i32` for a 4-byte `Color`), SSE → `double`, `<2 x float>` - or `float`. One part is passed as is; two parts as two arguments, or returned as `{a, b}`. - -`makePlan()` walks the parameters counting the 6 integer and 8 SSE argument registers; a struct whose -parts don't fit any more goes to the stack (MEMORY) as a whole, like clang does. Small integer -parameters get `signext`/`zeroext`. `emitCCall()` stores each struct argument into a slot big enough for -both the struct and its register form, loads the parts at offsets 0 and 8, calls with the planned function -type and attributes, and rebuilds struct results the same way. - -`cThunk(fn)` is the reverse for `addr(fn)`: an internal function with the C signature that reassembles -struct parameters from registers or `byval` memory, calls the Finch function and lowers its result. Only -C-compatible signatures are accepted. - -`tests/run/c_structs.fch` exercises `{float,float}`, `{float,float,float}`, `{u8×4}`, `{double,int}`, -`{i64,i64}`, a 24-byte struct, a struct with a `char[8]`, nested structs, register exhaustion with 13 -struct arguments, and callbacks taking and returning structs, against a C library compiled with -`link "abi.c"`. The results match the same calls made from C. - -### 13.4 Linking C code - -`link "name"` tries `pkg-config --libs name`, then `-lname`. `link "file.c"` compiles the file (relative -to the `.fch` file) with `$CC -O2 -fPIC -c` into a temporary object; `.o` and `.a` files are passed -through. When linking fails, `linkFailed()` maps undefined symbols back to the header that declared them -and prints the `link` line to add. - ---- - -## 14. Optimization, emission, linking - -- **Target:** `sys::getDefaultTargetTriple()`, CPU `generic`, `Reloc::PIC_`. The data layout is set on the - module before any IR is built (alignment of loads/stores depends on it). -- **Optimization:** `PassBuilder::buildPerModuleDefaultPipeline(O2)`: clang's `-O2` middle end. `-O0` - skips it (`finch ir file.fch -O0` shows `Codegen`'s raw output). -- **Emission:** legacy `PassManager` + `addPassesToEmitFile(ObjectFile)`. -- **Linking:** `$CC prog.o rt.o [user objects] -lm [libs]`. On Nix the cc wrapper turns `-L` paths into - `RPATH`, so programs run without `LD_LIBRARY_PATH`. -- **`run`:** a temporary executable, run with the remaining command-line arguments, deleted afterwards; - a signal is reported as `the program crashed: ` with exit status `128 + n`. - -Performance notes: user functions are `internal`, so LLVM inlines and specializes them freely -(recursive `fib(40)`: ≈0.19 s vs. ≈0.23 s for `clang -O2` on the same C on the author's machine); -bounds-checked array code matches C on a sieve. - ---- - -## 15. Debug info - -With `-g`, `Codegen` creates a `DIBuilder`, a compile unit (`DW_LANG_C`, so gdb's C expression syntax -works for `print p.x`), a `DIFile` per source file, a `DISubprogram` per function, parameter variables -(`createParameterVariable`, so `bt` shows `length (v=…)`) and local variables (`insertDeclare` on their -allocas), and a `DILocation` per statement and call (`setLoc`). Types: basic types with Finch names -(`int`, `u8`, `float`), `str`/arrays as `{ptr, len, cap}` structs, pointers, fixed arrays, and structs with -member offsets from the LLVM `StructLayout`. Recursive structs use a replaceable forward declaration. -Generated helpers carry no locations. Use `-g -O0` for the best experience; with O2 some variables are optimized out. - ---- - -## 16. Diagnostics - -`failAt(file, line, col, msg)` prints `path:line:col: error: msg` and the source line with a caret -(tabs preserved, columns counted in characters), then exits with 1. Conventions: binary operators report -the operator, calls the callee, argument errors the argument. Messages say what is wrong and, when -there's an obvious fix, how to fix it. There are no warnings. An LLVM verifier failure prints -`internal compiler error (please report)` and exits with 2. - ---- - -## 17. The self-hosted compiler (`boot/`) - -`boot/` is a second Finch compiler written in Finch (≈3,100 lines): - -| File | Contents | -|---|---| -| `boot/lexer.fch` | the lexer (tokens are `{kind, text, line, col, nl}`; kinds are words, keywords and operators are their own kind) | -| `boot/ast.fch` | `Type` (`kind`, `elem` as a 0/1-element array, `name`, `module`), one generic `Node` for everything, `Program` | -| `boot/parser.fch` | the same grammar and newline rules as the C++ parser, precedence climbing for binary operators | -| `boot/gen.fch` | type checker + **textual LLVM IR** generator: scopes, places, coercions, copies, helpers, printing, runtime checks, array/str methods | -| `boot/main.fch` | loader for modules and the driver: writes `.ll`, runs `clang -O2 file.ll runtime/finch_rt.c` | - -It supports the core language: `int float bool char str`, `[]T`, structs (defaults, named/positional -constructors), `ptr[T]`/`addr`/`new`/`free`/`null`, all statements except `defer`, modules, and the -built-ins and methods the C++ compiler has, minus C imports, sized integers and `defer`. -It implements the same value semantics and borrow analysis (copies where the main compiler copies) but -**never frees**: as a bootstrap compiler it leaves memory to the operating system. - -The compiler state is one `Gen` struct passed as `ptr[Gen]`: Finch has no globals, and passing a -pointer is how a function changes its caller's value. Without `defer` or a map type, scopes are arrays of -arrays searched linearly; helper functions (copy/print/compare per type) are generated by temporarily -swapping the output buffers (`beginHelper`/`endHelper`). - -**The fixpoint.** `tests/boot.sh`: - -1. `build/finch` (C++) compiles `boot/` → **stage 1**, -2. stage 1 compiles `boot/` → **stage 2** (and its IR), -3. stage 2 compiles `boot/` → **stage 3** IR, - -and requires the stage 2 and stage 3 IR to be **byte-identical** (≈32,000 lines). It then compiles every -test in `tests/run` that stays within the subset with the self-hosted compiler and compares the output -with the expected one. It takes about 2.3 s for the self-hosted compiler to compile itself. - ---- - -## 18. Build system - -- `find_package(LLVM CONFIG)`; **if the shared `LLVM` target exists Finch links that single library.** - `libclang.so` links `libLLVM.so` itself; linking the static component libraries as well put two copies of - LLVM in one process, and the duplicated global objects crashed at exit (a double free in a global - destructor). It also made the binary 82 MB instead of ~20 MB. -- libclang: `find_path(clang-c/Index.h)` + `find_library(clang)`. -- `runtime/finch_rt.c` is read at configure time into `build/rt_source.inc` as a raw string literal; - `CMAKE_CONFIGURE_DEPENDS` re-runs the configure step when it changes. -- `FINCH_VERSION` from `project(VERSION 2.4.0)`. C++17, `-Wall -Wextra` (MSVC: `/W3`), warning-free. -- `shell.nix` lists `llvmPackages.clang` before `llvmPackages.libclang`: the latter also ships an - unwrapped `clang` that can't find the system headers. - ---- - -## 19. Testing - -- `tests/run.sh`: every `tests/run/*.fch` with `fn main` must print exactly its `.out` (stdin from `.in` - if present); files without `main` are modules or helpers. Every `tests/fail/*.fch` must fail with the text - from its `// expect:` line. Currently **74 passed, 0 failed**. -- `MEMCHECK=1 tests/run.sh`: the same, plus valgrind on every program (no leaks, no invalid access). -- `tests/boot.sh`: the self-hosting fixpoint and the subset run (§17). -- `tests/cross.sh windows|arm64`: every test built with `--target` and run under Wine / QEMU. -- `python3 tests/lsp_test.py`: the language server, driven like an editor. -- CI (`.github/workflows/ci.yml`) runs all of it on Linux x86-64, Linux ARM64, macOS ARM64 and Windows. - ---- - -## 20. Semantics vs. C +## 12. Semantics vs. C | Situation | C | Finch 2.0 | |---|---|---| @@ -882,242 +417,15 @@ with the expected one. It takes about 2.3 s for the self-hosted compiler to comp --- -## 21. Function reference - -### Driver (`main.cpp`) - -| Function | Does | -|---|---| -| `Loader::load` / `findModule` | read, lex, parse a file and recursively its modules / locate `name.fch` | -| `hostMachine`, `optimize`, `emitObject` | target machine, O2 pipeline, object file | -| `runtimeObject` | compile and cache the runtime | -| `capture`, `libFlags`, `link` | run a command / pkg-config or `-l` / the link step, compiling `link "x.c"` files | -| `linkFailed`, `guessLib` | turn linker output into advice | - -### Lexer and parser - -| Function | Does | Differs from | -|---|---|---| -| `lex` / `tokName` | text → tokens / names for messages | | -| `accept` / `expect` / `unexpected` | consume if it matches / consume or fail / fail with context | | -| `sameLine` / `endOfStatement` | may this token continue the expression? / did the statement end? | | -| `atDeclaration` / `type` | lookahead for a declaration / parse a type | | -| `args` | `( [name:] expr, … )` for calls, methods and constructors | | -| `postfix` | `.field`, `.method(…)`, `[i]` chains | `primary` parses the start of a chain | - -### Codegen: program and statements (`codegen.cpp`) - -| Function | Does | Differs from | -|---|---|---| -| `declareStructs` / `declareFns` / `declare` | all types / all signatures / one signature + borrow analysis | | -| `define` / `defineMainWrapper` | one body / the C entry point | | -| `pushScope` / `popScope` | open / close a scope (`popScope` emits its cleanups if reachable) | | -| `emitCleanups(downTo)` / `emitScopeCleanups(i)` | cleanups of several scopes without popping (return/break) / of one scope | | -| `slot` / `tmpOf` / `tmp` | a variable's alloca / an anonymous one / an anonymous one holding a value | | -| `lookup` / `addVar` | find a visible variable (respects `deferLimit`) / declare one | | -| `stmt`, `varDecl`, `assign`, `ifStmt`, `whileStmt`, `forStmt`, `forEachStmt`, `returnStmt`, `jump` | statements | | -| `rootVar` / `mutates` / `mutatesExpr` | the variable an l-value starts from / does a statement or expression change it? | | - -### Codegen: expressions - -| Function | Does | Differs from | -|---|---|---| -| `expr` / `exprWant` | an r-value / the same, with an expected type for array literals | | -| `ref` / `place` | an address if any, else a value / an address or an error | `expr` loads from `ref` | -| `member` / `index` | `.x`, `.len`, `.ptr`, `.value`, auto-deref / `[i]` with bounds checks and copy-on-write | | -| `arrayLit` | `[…]` → heap array | | -| `unary`, `binary`, `arith`/`arithOp`, `compare`, `logic` | operators (`arith` adds the literal flag) | | -| `unify` / `coerce` / `convert` | two operands toward each other / one value into a slot / explicit `T(x)` | | - -### Codegen: types and ownership (`codegen_types.cpp`) - -| Function | Does | -|---|---| -| `ty`, `resolve`/`resolveT`, `findStruct`, `resolveStruct` | LLVM type; Named → Struct (deep or shallow); struct lookup; layout + cycle check | -| `owning`, `zero`, `defaultValue` | does a type own memory; zero constant; zero or the struct's defaults | -| `own` / `release` | something storable (move or copy) / drop an unkept fresh value | -| `copyValue`, `dropValue`, `copyAt`, `dropAt`, `copyFn`, `dropFn`, `typeKey` | copying and dropping by value, by address, and the per-type helpers | -| `forN` | an internal `for i in 0..n` loop used by helpers | -| `strConst`, `cstr`, `strFromC`, `elemSize` | literal str constant; str → `char*`; `char*` → borrowed str; element size | -| `emitPrint`, `printFn`, `printf_` | printing | -| `diType`, `setLoc` | debug info | - -### Codegen: calls (`codegen_builtins.cpp`) and ABI (`abi.cpp`) - -| Function | Does | -|---|---| -| `call` / `method` | resolve a call / a method or module-qualified call | -| `callFinch` / `construct` / `callC` | Finch function or method / struct constructor / C function | -| `structMethod` | `obj.m(...)`: the receiver's address, safety checks, the call | -| `print`, `addrOf`, `convert`, `arrayMethod` | built-ins | -| `fallible`, `tryExpr`, `orElse`, `retValue`, `retError` (`codegen.cpp`) | errors as values (§10.11) | -| `mapIndex`, `mapLit`, `mapMethod`, `mapSlot`, `forEachEntry` (`codegen_map.cpp`) | maps (§10.12) | -| `rt` / `libc` | declare a runtime / libc function | -| `fileName`, `panicIf`, `boundsCheck`, `checkDivisor` | runtime checks | -| `classify`, `makePlan`, `declareC`, `emitCCall`, `cThunk` | ABI classification (System V, Microsoft x64, AAPCS64), register plan, declaration, call, callback thunk | - --- -## 22. Extending the compiler +## 13. Known limitations -**A built-in function:** add the name to `isBuiltin()`, handle it in `call()`, implement it (in IR, or as -a runtime function declared in `rt()`'s table), add `tests/run/` and `tests/fail/` cases. If it should -exist in the self-hosted compiler too, add it to `boot/gen.fch`'s `call()` and `runtimeDecls()`. - -**An array or str method:** add a branch in `arrayMethod()` or the str part of `method()`; if it changes -the array, add its name to the `changing` sets in `method()` and `mutatesExpr()`, so borrowing stays correct. - -**An operator:** lexer token + `tokName`, `BinOp`, the right precedence function in the parser, `opName`, -emission in `arithOp`/`compare`, an explanation in `badOperands`. - -**A statement:** `StmtKind` + node, `Parser::statement`, `Codegen::stmt`; if it transfers control, respect -`terminated()`, use `continueAt()` for merge blocks, and emit cleanups (`emitCleanups`) before jumping out of scopes. - -**A new owning type:** extend `owning()`, `dropAt`/`copyAt` (and their helpers), `emitPrint`, `diType`. - ---- - -## 23. Targets: Windows, macOS, ARM64 - -`src/target.h` holds `g_target`: the LLVM triple, `windows` / `msvc` / `darwin` / `cross` flags, the C -compiler (`cc`) and the executable suffix. `setTarget()` maps `windows`, `linux`, `arm64`, `macos` (or a full -triple) to a triple and picks the C compiler: `FINCH_CC`, else `CC` (not when cross-compiling), else -`x86_64-w64-mingw32-gcc` / `aarch64-linux-gnu-gcc` for Windows / ARM64 Linux from another system, -`clang --target=` for other cross targets, `clang` on Windows, `cc` on Unix. `initTargets()` -registers the X86 and AArch64 backends (whichever this LLVM has; CMake defines `FINCH_TARGET_X86` / -`FINCH_TARGET_AARCH64` and links their libraries). Everything platform-dependent goes through it: - -- **Headers:** libclang parses with `--target=` (so `long` is 32 bits on Windows and the right - headers are used) and, on Windows, `-D_USE_MATH_DEFINES`. Include folders come from ` -E -v`. -- **Processes:** `capture()` / `runCommand()` wrap `popen` / `system` (`_popen` on Windows) and decode exit - statuses (POSIX `WIFEXITED`/signals, or Windows exit codes and NTSTATUS crashes like `0xC0000005`). - `shellQuote()` quotes for `sh` (`'…'`) or `cmd.exe` (`"…"`); a command line handed to `cmd /c` that - starts with a quote gets one extra pair of quotes. -- **Linking:** no `-lm` with MSVC; `.exe` names; the runtime cache key includes the triple and the C compiler. - MSVC linker messages (`LNK2019 unresolved external symbol`, `LNK1104`/`LNK1181 cannot open file`) are - translated like GNU ld's. -- **Running:** `runPrefix()`: a Windows program built on Linux runs with `wine`, an ARM64 Linux one - elsewhere with `qemu-aarch64`. -- **macOS:** ` -E -v` lists `(framework directory)` entries; they become `-iframework` for libclang, - so `import "OpenGL/gl.h"` works. `link "Cocoa.framework"` becomes `-framework Cocoa`. Apple's linker - messages (`Undefined symbols for architecture arm64: "_f"`, `ld: library 'x' not found`, - `framework 'X' not found`) are translated like the others, without Mach-O's leading `_`. -- **Debug info:** CodeView for MSVC targets, DWARF elsewhere. -- **Runtime:** no `getline` or `sys/wait.h` on Windows; `stdin`/`stdout`/`stderr` are macros there, so - `finch_std_stream(i)` gives them to Finch when a header has no `extern` variable for them. -- **Printing pointers** is the same everywhere: `null` or `0x…`. - -### The Microsoft x64 calling convention - -`classify()` switches on `g_target.windows`. A struct of exactly 1, 2, 4 or 8 bytes travels as **one -integer** of that size, even if it holds floats (`{float, float}` → `i64`). Any other struct is -**Indirect**: the caller copies it to its own stack and passes the copy's address (no `byval`: the callee -receives a plain pointer). Results of 1/2/4/8 bytes come back in `rax` as an integer; anything else -through a hidden `sret` pointer. Each argument takes one slot, so there is no register counting. -`cThunk()` mirrors this for callbacks. - -`tests/cross.sh windows` builds every test with `--target windows`, runs it with Wine and compares the output: -all of them pass, including `c_structs` (which then exercises the Microsoft rules against a C library built -with MinGW). CI additionally builds `finch.exe` with MSVC on Windows Server and runs `tests/run.sh` there. - -### AAPCS64 (ARM64 Linux and Apple) - -`classify()` mirrors clang's AArch64 lowering: - -- A **homogeneous floating-point aggregate** (1–4 members, all `float` or all `double`, nested structs - and arrays flattened) goes in vector registers: passed as `[n x float]` / `[n x double]`, returned as - `{ float, … }`. On Linux the argument also gets `alignstack(8)`, as clang does; Apple doesn't. -- Any other struct of up to 16 bytes goes in general registers: `i64` (≤ 8 bytes) or `[2 x i64]` - (`i128` if its alignment is 16); it is returned as an integer of its exact size or `[2 x i64]`. -- Bigger structs are **Indirect**: a copy on the caller's stack, passed by address (no `byval`); returned - through `sret`. -- There is no register counting: the backend places each part. -- C's plain `char` is **unsigned** on ARM64 Linux and signed on Apple and x86, which matters for the - sign/zero extension of `char` arguments and results (`charIsSigned()`). -- Apple passes the variadic part of `printf(...)` on the stack; LLVM does that from the `...` in the - function type. - -`tests/cross.sh arm64` runs the whole suite (including the struct ABI test against a C library built by -GCC) under QEMU, and CI runs it natively on ARM64 Linux and Apple Silicon. - -### Building finch.exe - -CI uses Visual Studio 2022, Ninja and the official `clang+llvm-21.x-x86_64-pc-windows-msvc` package -(static libraries, `/MT`, so `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`). Two Windows details in -`CMakeLists.txt`: that package names the DIA SDK library at the path of the machine LLVM was built on, so -it is redirected to the installed Visual Studio; and the embedded runtime is written as several raw -string pieces, because MSVC limits one literal to 16 KB. LLVM is initialized with the native target only, -so the static build doesn't pull in every backend. - ---- - -## 24. The language server - -`finch lsp` (`src/lsp.cpp`) speaks LSP over stdin/stdout with `llvm::json`. On `didOpen`/`didChange` -it runs the real front end on the document: `Loader` (with the open documents' text as overrides), -`importHeaders` (cached per set of imports, since reading headers is the slow part), and `generate()` -without optimizing or emitting. Two switches make the compiler usable inside a server: - -- `g_throwErrors`: `failAt()` throws `FinchError{file, line, col, msg}` instead of printing and exiting. - The server publishes it as a diagnostic (for the right file, also inside an imported module) and clears - diagnostics that disappeared. -- `g_index`: while generating code, `Codegen::note()` records a `SymRef` for every name it resolves - (variables, parameters, fields, functions, constructors, C functions and constants, built-ins, methods, - module members): its span, a markdown hover, and the definition position. `define()` and - `declareStructs()` add `FnInfo` / `StructIndex`; `addVar()` adds `VarInfo` with the enclosing function's - line range. Positions of names come from `namePos` fields the parser records. - -Requests use that index: - -| Request | From | -|---|---| -| hover, definition | the `SymRef` under the cursor | -| completion | keywords, types, built-ins, variables of the enclosing function, functions, structs, imported modules, C names matching the typed prefix; after `name.`: struct fields (also through `ptr[T]`), array/str methods, or a module's members | -| signatureHelp | scans back to the unmatched `(`, counts top-level commas, looks up the Finch / C / built-in signature | -| documentSymbol | functions and structs (with fields) of the file | - -Half-typed code (`p.`, `add(1, `) doesn't compile, so the server keeps the **last successful** analysis of -each document and merges its functions, structs and variables in for completion and signature help. -`tests/lsp_test.py` drives the server like an editor and checks every feature. - -`src/builtins_doc.h` is the single description of built-ins and methods, used for hover, completion and -signature help (and mirrored by the cheat sheets). - ---- - -## 25. Editor integrations - -### Kate - -`editors/kate/finch.xml` is a KSyntaxHighlighting definition (the engine of Kate, KWrite and KDevelop): -keyword lists that match `builtins_doc.h`, a context for `fn Type.method`, strings with escapes, and -folding regions for `{ }` and block comments. `lspclient.json` is the entry for Kate's LSP -client (`highlightingModeRegex: ^Finch$`); `install.sh` copies the definition and merges the entry into -`~/.config/kate/lspclient/settings.json`. Kate sends more requests than VS Code (`documentHighlight`, -`foldingRange`, …); the server answers every request it doesn't implement with `MethodNotFound`, so no -client waits. - -### VS Code - -`editors/vscode` is a plain JavaScript extension: `extension.js` starts `finch lsp` through -`vscode-languageclient` (path from the `finch.path` setting) and adds **Run** / **Build** as -`ProcessExecution` tasks (no shell quoting, the `$finch` problem matcher turns `file:line:col: error:` -lines into Problems). `syntaxes/finch.tmLanguage.json` is the TextMate grammar, tested by `npm test` with -`vscode-textmate` + `vscode-oniguruma` (the engine VS Code itself uses); -`language-configuration.json` gives comments, brackets, auto-closing and indentation; `snippets/` the -templates. CI packages it with `vsce` into `finch-lang.vsix`. - -## 26. Known limitations - -- No generics, interfaces, `match`, closures or Finch-side enums. -- Strings are bytes: `.len`, `s[i]`, `upper()` are not Unicode-aware. -- Changing a map's keys inside a `for` over the same map is not detected (it is memory-safe, but entries - may be skipped or seen twice). -- A `try` / `or` failure in the middle of an expression doesn't release fresh temporaries computed - earlier in that expression (a small leak on the failure path only). -- Dangling pointers (`addr` of a local that went away, use after `free`) are not detected. -- Shift amounts and float→int conversions are not range-checked (LLVM poison, as in C). -- C: unions and bit-field structs by value, function-like macros, `long double`. -- Targets: x86-64 and ARM64 (Linux, macOS, Windows on x86-64). No Windows on ARM64, 32-bit, or WebAssembly. -- The self-hosted compiler has no C imports, sized integers, `defer`, maps, methods or errors as values, - and doesn't free memory. +- The compiler runs on Linux x86-64 and ARM64 only (no seed for macOS or Windows yet). Programs can be built for all three. +- macOS output is only checked as IR; Windows and ARM64 are run under Wine and qemu in CI. +- One error per run, no recovery. +- `-g` debug info is best with `-O0`. +- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell. +- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error + inside the instantiated body. +- No payload enums yet (variants carry no data). diff --git a/docs/pl/dla-inzynierow.md b/docs/pl/dla-inzynierow.md index 0c17234..5af730b 100644 --- a/docs/pl/dla-inzynierow.md +++ b/docs/pl/dla-inzynierow.md @@ -1,14 +1,12 @@ # 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. +**Wnętrze Fincha 2.4, którego kompilator jest napisany w Finchu.** Jak kod źródłowy staje się tokenami, drzewem AST i +LLVM IR *jako tekstem*, który `clang` kompiluje i linkuje z małym runtime'em w C; jak nagłówki C czyta libclang wywoływany +z samego Fincha i jak ręcznie odtwarzane są konwencje wywołań C; reguły typów i własności; jak kompilator buduje sam siebie +z ziarna; oraz serwer języka. -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). +Zakładam, że czytasz Fincha (zobacz [przewodnik dla techników](dla-technikow.md)) i widziałeś już LLVM IR. +Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-bootstrap`; ten przewodnik opisuje wersję w Finchu. > 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md) @@ -17,297 +15,143 @@ Język z perspektywy użytkownika opisuje [przewodnik dla techników](dla-techni ## 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. [Platformy: Windows, macOS, ARM64](#23-platformy-windows-macos-arm64) -24. [Serwer języka](#24-serwer-języka) -25. [Integracje z edytorami](#25-integracje-z-edytorami) -26. [Znane ograniczenia](#26-znane-ograniczenia) +2. [Drzewo źródeł](#2-drzewo-źródeł) +3. [Front end: lekser, parser, moduły](#3-front-end-lekser-parser-moduły) +4. [Generowanie kodu](#4-generowanie-kodu) +5. [Import C i ABI C](#5-import-c-i-abi-c) +6. [Sterownik, linkowanie i cele](#6-sterownik-linkowanie-i-cele) +7. [System typów](#7-system-typów) +8. [Własność i pamięć](#8-własność-i-pamięć) +9. [Generyki, enumy i match](#9-generyki-enumy-i-match) +10. [Bootstrap](#10-bootstrap) +11. [Serwer języka](#11-serwer-języka) +12. [Semantyka a C](#12-semantyka-a-c) +13. [Znane ograniczenia](#13-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. +W kompilatorze nie ma biblioteki LLVM. Pisze LLVM IR jako **tekst**, a optymalizację, generowanie kodu i linkowanie zostawia +`clangowi`. Dzięki temu jest mały (ok. 8 tys. linii) i może być napisany w samym Finchu. ``` - 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 (pomijane przy -O0) - emitObject() legacy PM + addPassesToEmitFile ──► prog.o - link() cc prog.o ~/.cache/finch/rt-.o [twoje .c/.o/.a] -lm [pkg-config/-l…] - ▼ - plik wykonywalny (finch run: uruchamiany z pliku tymczasowego z resztą argumentów, potem usuwany) + main.fch ─► loader ── lex ── parse ──► Program (+ każdy importowany moduł, rekurencyjnie) + │ + `import "x.h"` ─► cimport.fch (libclang wołany z Fincha) ─► funkcje, struktury, stałe C + │ + ▼ + gen.fch ─► generate(): struktury, sygnatury, ciała funkcji + kontrola typów i IR w jednym przejściu; klasyfikacja ABI wywołań C + │ (tekst IR) + ▼ + linker.fch ─► clang -c prog.ll ─► clang prog.o rt-.o [twoje .c/.o/.a] -lm [pkg-config / -l] + │ + ▼ + plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany) ``` -Decyzje, które kształtują resztę: +Decyzje projektowe: -- **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. +- **Szybko się przerywa.** Pierwszy błąd wypisuje `plik:linia:kol: error: …` z linią źródła i strzałką, i kompilator kończy. + Bez odzyskiwania po błędach, więc kolejne fazy nie widzą półpoprawnych danych. +- **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR jednym przejściem po AST. Wyrażenie daje operand IR, typ Fincha + (napis: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) i flagi. +- **Niech clang pracuje.** Zmienne lokalne to `alloca`, zamieniane przez `mem2reg`. Optymalizacja to `-O2` clanga (albo `-O0` z `-g`). +- **Wartości, nie referencje.** Tablice, teksty i struktury mają semantykę wartości; własność ustala się w czasie kompilacji (§8). +- **Malutki runtime w C** (`runtime/finch_rt.c`, ok. 760 linii) robi to, co w IR jest uciążliwe: teksty, rośnięcie tablic, mapy, + wejście, pliki, paniki. Kompilowany raz i trzymany w `~/.cache/finch`. --- -## 2. Pliki źródłowe +## 2. Drzewo źródeł -| Plik | Linie | Odpowiedzialność | +| Plik | Linie | Zadanie | |---|---|---| -| `src/error.h` | 50 | `g_files` (wszystkie źródła), `failAt(file, line, col, msg)`, `fail(line, col, msg)`, `FinchError` | -| `src/lexer.h/.cpp` | 250 | `Tok`, `Token`, `lex(indeksPliku)`, `tokName()` | -| `src/ast.h` | 360 | `Type` (z Array, Map, Fixed, Struct, Named), wszystkie węzły `Expr`/`Stmt`, `FnDecl`, `StructDecl`, `Import`, `Link`, `Program` | -| `src/parser.h/.cpp` | 610 | Parser zstępujący `Parser`, `typeFromName()` | -| `src/loader.h/.cpp` | 90 | Wczytywanie plików i modułów (`Loader`), `FINCH_PATH` | -| `src/target.h/.cpp` | 210 | `g_target`: triple, kompilator C, procesy, ścieżki; inicjalizacja backendów | -| `src/cimport.h/.cpp` | 430 | Import przez libclang: funkcje, struktury C (pola, przesunięcia), stałe, zmienne globalne | -| `src/codegen.h` | 15 | `generate()` | -| `src/codegen_impl.h` | 310 | Klasa `Codegen` i jej struktury pomocnicze, wspólne dla plików poniżej | -| `src/codegen.cpp` | 1460 | Program, deklaracje (funkcje, metody), zasięgi i sprzątanie, instrukcje, wyrażenia, miejsca (`ref`), operatory, błędy jako wartości, analiza modyfikacji | -| `src/codegen_types.cpp` | 580 | Typy LLVM, rozwiązywanie typów, układ struktur, konwersje, własność (helpery kopiowania/zwalniania), zamiana na tekst, typy dla debuggera | -| `src/codegen_builtins.cpp` | 890 | Wywołania, metody, konstruktory, funkcje wbudowane, konwersje, metody tablic i tekstów, deklaracje runtime'u, panic | -| `src/codegen_map.cpp` | 240 | Mapy: indeksowanie, literały, metody, przechodzenie | -| `src/abi.cpp` | 370 | Konwencje wywołań C (System V x86-64, Microsoft x64, AAPCS64), wywołania C ze strukturami przez wartość, wrappery wywoływalne z C | -| `src/lsp.cpp`, `src/index.h`, `src/builtins_doc.h` | 700 | Serwer języka (§24) | -| `src/main.cpp` | 350 | Sterownik: CLI, maszyna docelowa, O2, emisja, cache runtime'u, linkowanie, porady przy błędach linkera, `run` | -| `runtime/finch_rt.c` | 740 | Biblioteka uruchomieniowa (§12) | -| `boot/*.fch` | 3100 | Kompilator samohostujący (§17) | +| `src/lexer.fch` | 220 | Tokeny i pozycje | +| `src/ast.fch` | 120 | Jedna struktura `Node` z napisem `kind`, dziećmi, nazwami typów, pozycjami (`line`, `endLine`) | +| `src/parser.fch` | 780 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link` | +| `src/gen.fch` | 5200 | Kontrola typów + IR: struktury, funkcje, metody, zakresy i zwolnienia, instrukcje, wyrażenia, wbudowane, mapy, błędy jako wartości, generyki (monomorfizacja), enumy i `match`, metadane `-g`, tryb `index` | +| `src/cimport.fch` | 560 | libclang: funkcje, struktury (pola, offsety, wypełnienie), enumy, stałe, globalne, makra | +| `src/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu | +| `src/linker.fch` | 300 | Pamięć podręczna, obiekt runtime'u, biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera | +| `src/main.fch` | 310 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/ir/index/lsp` | +| `src/lsp.fch` | 875 | Serwer języka (§11) | +| `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania | +| `std/*.fch` | 900 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`) | +| `runtime/finch_rt.c` | 760 | Runtime w C | +| `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) | --- -## 3. Lekser +## 3. Front end: lekser, parser, moduły -`lex(file)` przechodzi raz przez `g_files[file].text` i zwraca `std::vector` zakończony `Tok::End`. +**Lekser.** Koniec linii kończy instrukcję; wcięcia nic nie znaczą. Tokeny niosą plik, linię i kolumnę, z których korzysta każdy błąd. -```cpp -struct Token { Tok kind; std::string text; int line, col; bool newlineBefore; }; -``` +**Parser.** Zejście rekurencyjne, jedna funkcja na konstrukcję. Typy są parsowane do napisu (`[]str`, `map[str]int`, +`ptr[char]`, `Stack[int]`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]` i `struct Nazwa[T]` to szablony; `enum` i `match` +mają własne węzły; `export fn` oznacza funkcję widoczną dla C; blok zapamiętuje `endLine` (dla serwera języka). Stara +kolejność `fn nazwa(...) typ` to błąd składni z podpowiedzią. -- **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 or try`. - (`self` i `map` to zwykłe identyfikatory ze znaczeniem zależnym od miejsca: w metodzie, przed `[`.) - **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 `/* */`. +**Moduły.** `import nazwa` szuka kolejno `nazwa_.fch` obok importującego pliku (`windows`, `linux`, `macos`), `nazwa.fch`, +katalogów z `FINCH_PATH`, potem biblioteki standardowej `std/`. Każdy moduł jest parsowany raz; jego nazwy są używane jako +`modul.nazwa`. `import "x.h"` trafia do importera C. --- -## 4. Gramatyka i parser +## 4. Generowanie kodu -`Parser` (`src/parser.cpp`) to ręcznie pisany parser zstępujący z jednym tokenem wyprzedzenia, a w -`atDeclaration()` z maksymalnie trzema. +`gen.fch` to jedna duża struktura `Gen` i funkcje na niej. Przejścia: -```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 [ "." IDENT ] "(" [ param { "," param } ] ")" [ "->" ( type [ "!" ] | "!" ) ] block ; - (* fn Punkt.przesun: metoda; "!": może się nie udać *) -param = type IDENT ; -type = "[" "]" type | "map" "[" type "]" type | "ptr" [ "[" type "]" ] - | TYPENAME | IDENT [ "." IDENT ] ; +1. `declareStructs`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wypełnieniem); rekurencja przez wartość jest odrzucana. +2. `declareFns`: sygnatury, z *analizą pożyczania* parametrów (parametr, który nigdzie nie jest zapisywany, jest przekazywany bez kopii). +3. `define`: każde ciało, sprawdzone i wyemitowane. Zakresy pamiętają, co zwolnić na końcu (`defer` działa pierwszy). +4. `defineMainWrapper`: C-owe `main(argc, argv)` i `finch_on_fatal` dla osadzających. +5. Z `-g` metadane DWARF dokładane są w trakcie pisania IR (używaj `-O0`). -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 | try | orelse ; -for = "for" IDENT [ "," IDENT ] "in" expr ( ".." expr block | block ) ; (* zakres | po elementach *) -assignop = "=" | "+=" | "-=" | "*=" | "/=" | "%=" ; +Generyki są **monomorfizowane**: wywołanie `max(1, 2)` wnioskuje `T`, potem AST szablonu generowane jest ponownie z +przypisanym `T`, pod zmangowaną nazwą (`max$int`). `match` na enumie jest wyczerpujący (brak wariantu to błąd, `else` łapie +resztę); na wartości to ciąg porównań. Wartości enuma to `i32`; `Color(n)` jest sprawdzane; wypisywanie używa wygenerowanej tabeli nazw. -expr = or [ "or" ( block | expr ) ] ; (* f(x) or 0, f(x) or { ... }: najniższy, od prawej *) -or = and { "||" and } ; and = cmp { "&&" cmp } ; -cmp = add { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) add } ; -add = mul { ( "+" | "-" | "|" | "^" ) mul } ; -mul = unary { ( "*" | "/" | "%" | "<<" | ">>" | "&" ) unary } ; -unary = ( "-" | "!" | "~" ) unary | "try" postfix | postfix ; -postfix = primary { "." IDENT [ args ] | "[" expr "]" } ; -primary = INT | FLOAT | STRING | CHAR | "true" | "false" | "null" - | IDENT [ args ] | "(" expr ")" | "[" [ expr { "," expr } [","] ] "]" - | "[" ":" "]" | "[" expr ":" expr { "," expr ":" expr } [","] "]" ; (* mapy *) -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. +Błędy jako wartości: `-> T!` zwraca parę (wartość, tekst błędu); `try` wraca wcześniej przy błędzie, `or` go obsługuje, `error("…")` go tworzy. --- -## 5. AST +## 5. Import C i ABI C -Wszystkie węzły są w `src/ast.h`: małe hierarchie klas z jawnym `kind`, rozgałęziane przez -`switch` + `static_cast`. +`cimport.fch` woła **libclang** wprost z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek z flagami +celu i przechodzi po kursorach. Typy: parametr `const char *` → `str`, inny `char *` → `ptr[char]`, wynik `char *` → `str`; +liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na ARM64 Linux); struktury i enumy według układu. -| 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.fn(...)` i `p.metoda(...)` | -| `MapLit` | `MapLitExpr` | `keys`, `values` (`[:]` nie ma żadnych) | -| `Try` | `TryExpr` | `call` | -| `OrElse` | `OrElseExpr` | `call` oraz `fallback` (wyrażenie) albo `block` | +LLVM IR nie realizuje za ciebie konwencji wywołań C, więc `gen.fch` ją odtwarza: -| StmtKind | Węzeł | -|---|---| -| `Block` `VarDecl` `Assign` `Expr` `If` `While` `For` `ForEach` `Return` `Break` `Continue` `Defer` | jak w nazwie; `ForEachStmt` ma `var`, `var2` (`for i, x` / `for k, v`), `list`, `body`; `DeferStmt` trzyma jedną instrukcję | +- `classify`: System V x86-64: struktura do 16 bajtów idzie w rejestrach według klas ośmiobajtów (INTEGER/SSE), większa w pamięci; +- `classifyArm64`: AAPCS64, z jednorodnymi agregatami zmiennoprzecinkowymi (HFA); +- reguły Microsoft x64 (struktury 1, 2, 4, 8 bajtów w rejestrze, reszta przez wskaźnik); +- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` robi z `addr(fn)` coś, co C może wywołać (callbacki). -`FnDecl` ma `recv` (struktura metody, w innych przypadkach `""`) i `fallible` (`-> T!`). - -`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, Map, Fixed, Struct, Named }; - Kind kind; - std::shared_ptr elem; // Ptr (null = `ptr` bez typu), Array, Fixed; Map: wartość - std::shared_ptr key; // Map - 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`. +Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXCursor`, `CXType` i `CXString` przez wartość. --- -## 6. Wczytywanie: pliki i moduły +## 6. Sterownik, linkowanie i cele -`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`, -z głównym plikiem na początku. Cykle nie przeszkadzają: moduł jest oznaczany jako wczytany, zanim -zaczniemy podążać za jego importami. +`finch build|run|ir|index plik.fch [-o wyjście] [-O0] [-g] [-l biblioteka] [-I katalog] [--target cel] [--shared] [--overlay ścieżka=plik]`. +`ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` robi bibliotekę współdzieloną z `export fn`. -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..` (`finch.` w głównym pliku), -typy struktur jako `finch..`. +`--target windows|linux|arm64|macos|` zmienia triple i układ danych, znak `char`, reguły ABI powyżej, wariant OS +modułów (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`. Przy linkowaniu `pkg-config` podaje flagi +dla `link "biblioteka"`; gdy linker zawiedzie, wynik jest analizowany i podpowiadane jest brakujące `link "…"`. Obiekt +runtime'u jest kluczowany skrótem jego źródła i celu. + +Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator/linker C; IR +zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`). + +Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`). --- -## 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 vars; vector 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 +## 7. System typów ### Reprezentacja w LLVM @@ -317,13 +161,13 @@ nazwy typów (konwersja) → funkcja bieżącego modułu → struktura bieżące | `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ść) | -| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; §10.12) | +| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64 }` (entries, len, used, cap, index, icap; zob. src/gen.fch) | | `ptr`, `ptr[T]`, `null` | `ptr` (nieprzezroczysty) | | `[N]T` | `[N x T]` | | struktura Fincha | nazwany typ struct, naturalny układ | | `self` metody | `ptr` (pierwszy parametr) | | wynik `-> T!` | `{ i1 ok, T value, str error }` (`i8` w miejscu `T` dla `-> !`) | -| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§13.2) | +| struktura C | nazwany **upakowany** (packed) struct z jawnymi bajtami wypełnienia (§5) | Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, `sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`. @@ -338,7 +182,7 @@ struct Value_ { Value *v; FType type; bool literal; bool fresh; }; 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). + trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8). ### Konwersje niejawne: `coerce(v, want)` @@ -378,7 +222,9 @@ Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tabl --- -## 9. Własność i pamięć +--- + +## 8. Własność i pamięć ### 9.1 Model @@ -499,382 +345,52 @@ podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C --- -## 10. Generowanie kodu +--- -### 10.1 Zmienne +## 9. Generyki, enumy i match -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. +Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika: sekcje 23 i 24 [przewodnika dla techników](dla-technikow.md). +Testy: `tests/new/generics.fch`, `tests/new/enums.fch` i odmowy w `tests/new/fail/`. -### 10.2 Miejsca: `ref()`, `place()`, `expr()` +--- -```cpp -struct LRef { bool isPlace; Place pl; Value_ val; }; // adres albo (jeśli go nie ma) wartość +## 10. Bootstrap + +Kompilator budowany jest z **ziarna**: własnego IR zapisanego jako `bootstrap/.ll` (zobacz +[bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh`: + +``` +ziarno .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch ``` -`ref(e, forWrite)` przechodzi łańcuch `Var`/`Member`/`Index` i zwraca **adres**, jeśli istnieje: +i przerywa, jeśli `finch1` i `finch` nie dają identycznego IR (punkt stały: kompilator kompiluje siebie tak samo, niezależnie +od tego, kto go zbudował). `bootstrap/build.sh seed` przepisuje ziarno z bieżącego kompilatora; rób to, gdy źródło +kompilatora zacznie używać czegoś, czego zatwierdzone ziarno nie ma. -- `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. +Ziarno jest na system, bo zapieczono w nim konwencję wywołań C dla wywołań libclang (§5). Linux x86-64 i Linux ARM64 mają +ziarna. Dla macOS i Windowsa trzeba by ziaren zrobionych z nagłówkami Apple i Microsoftu. -`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 +## 11. Serwer języka -`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 +=`). +`finch lsp` mówi JSON-RPC przez stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów: przy każdej zmianie +uruchamia `finch index plik.fch --overlay ścieżka=kopia …`, gdzie nakładki to niezapisany tekst edytora. W trybie `index` +kompilator wypisuje rekordy rozdzielone tabulatorami: -### 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. Metody (§10.10) dostają adres -`self` jako dodatkowy pierwszy parametr; funkcje, które mogą się nie udać (§10.11), zwracają `{ i1, T, str }`. -`fn main() -> !` / `-> int!` sprawia, że wrapper przy porażce wypisuje `error: ` na stderr -i kończy z kodem 1. `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 skalarnego z formatem wybranym wg typu (`%lld`, `%llu`, -`%g`, `%c`, `%.*s` dla `str` z jego długością), oddzielając spacjami, a na końcu `\n`. Wszystko inne -(wskaźniki, tablice, tablice o stałym rozmiarze, mapy, struktury) jest najpierw **zamieniane na tekst** -w buforze `str` na stercie przez `emitFormat()` i wypisywane przez `finch_buf_print`. Typy złożone dostają -generowany helper `finch.format.(buf, wartość*)`, który dopisuje `[1, 2]` / `{"a": 1}` / -`Punkt(x: 1, y: 2)`, biorąc teksty i znaki w środku w cudzysłów. `str(x)` tablicy, mapy albo struktury -używa tego samego helpera, więc `str(x)` to zawsze to, co pokazuje `print(x)`. - -### 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.`), `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. -`for i, x in lista` dodaje obok elementu zmienną-indeks typu `int` tylko do odczytu. - -### 10.10 Metody - -`fn Punkt.przesun(int dx)` jest deklarowana jak funkcja `finch.[modul.]Punkt.przesun` z dodatkowym -pierwszym parametrem `ptr` i trzymana w `Codegen::methods[StructInfo*]` (więc metody idą za strukturą do -każdego modułu, który jej używa, a struktury z C też mogą mieć metody). W `define()` `self` to `Var`, którego -`slot` **jest tym argumentem-wskaźnikiem**: `self.x` to GEP od struktury samego wywołującego, a `self = …` -zapisuje wartość wywołującego. Nigdy nie ma kopii. - -Przy wywołaniu `obj.m(args)` (`structMethod()`) `ref(obj)` daje adres, gdy `obj` jest miejscem (zmienna, -pole, element, `m[klucz]`), wczytany wskaźnik dla `ptr[T]` (sprawdzany na null) albo tymczasowe miejsce na -stosie dla wartości (`Punkt(1, 2).dlugosc()`), zwalniane po wywołaniu. Z analizy modyfikacji wynikają dwie -reguły bezpieczeństwa: metody zmieniającej `self` nie można wywołać na zmiennej pętli (tylko do odczytu), -a jeśli taka metoda dostaje argument żyjący wewnątrz odbiorcy (`c.merge(c)`, `p.add(p.items)`), argument -jest najpierw kopiowany, żeby `self` nie mógł zwolnić pamięci, na którą argument wciąż wskazuje. - -### 10.11 Błędy jako wartości - -Funkcja, która może się nie udać (`-> T!`), zwraca `{ i1 ok, T value, str error }`; przy porażce `value` -jest zerem, więc wywołujący nigdy nie musi go zwalniać. `return v` opakowuje `{1, v, zero}` (`retValue()`); -`return error(msg)` liczy komunikat, wykonuje sprzątanie i zwraca `{0, zero, msg}` (`retError()`). - -Wywołanie takiej funkcji jest dozwolone tylko jako argument `or` / `try`. `fallible(wywołanie)` ustawia -`fallibleTarget` na wyrażenie wywołania; `callFinch()` (i funkcje wbudowane, które mogą się nie udać) -porównują z nim swoje `CallExpr`/`MethodExpr` i jeśli pasuje, zostawiają `fallibleFailed` (`i1`) -i `fallibleMsg` (`str`) dla wywołującego; jeśli wywołanie takiej funkcji nie pasuje, to błąd kompilacji -*'f' can fail, so say what happens then*. Zagnieżdżone obsługi zapisują i przywracają te trzy pola. - -| Forma | Tłumaczenie | +| Rekord | Znaczenie | |---|---| -| `f(x) or zapasowa` | `br failed, or.failed, or.ok`; blok porażki zwalnia komunikat i liczy wartość zapasową (tylko tam); φ łączy obie wartości | -| `f(x) or { … }` | blok porażki deklaruje `err` (właściciel komunikatu) i wykonuje blok. Jeśli wartość jest używana, blok musi kończyć się skokiem; jako instrukcja może przejść dalej, a ścieżka sukcesu zwalnia nieużytą wartość | -| `try f(x)` | blok porażki wykonuje `emitCleanups(0)` i zwraca `{0, zero, msg}` z bieżącej funkcji, która też musi móc się nie udać | +| `@R plik linia kol dł plikDef liniaDef kolDef podpowiedź` | odwołanie: co pokazuje hover i gdzie jest definicja | +| `@V nazwa typ plik linia kol plikFn liniaFn koniecFn` | zmienna i funkcja, w której żyje | +| `@F moduł nazwa odb plik linia kol koniec sygnatura parametry` | funkcja lub metoda | +| `@S` / `@SF`, `@E`, `@I` | struktury (i pola), enumy, importy | -Funkcje wbudowane z trybem porażki sprawdzają `&wywołanie == fallibleTarget` i przełączają się na -`finch_str_try_int`, `finch_str_try_float`, `finch_try_read_file`, `finch_try_write_file`, -`finch_try_delete_file`, które zwracają 1 / 0 i wypełniają `str` z błędem zamiast robić panic. Koszt przy -sukcesie to jedna przewidywalna gałąź; nic nie jest alokowane, dopóki nie zdarzy się porażka. - -### 10.12 Mapy - -Runtime (§12) trzyma tablicę haszującą zachowującą kolejność wstawiania, w stylu `dict` z CPythona: -tablica `entries` z miejscami `{ i64 hash, K key, V value }` w kolejności wstawiania (hash 0 = usunięte) -oraz `index` z `icap` miejscami (potęga dwójki, ≥ 2 × cap) z sondowaniem liniowym (−1 puste, −2 usunięte, -w innym razie numer wpisu). Runtime tylko haszuje (FNV-1a + 64-bitowe mieszanie; klucze tekstowe po treści, -pozostałe po bajtach), znajduje i umieszcza wpisy; kompilator zapisuje, kopiuje i zwalnia klucze i wartości, -dokładnie tak jak elementy tablic. - -| Kod | Tłumaczenie | -|---|---| -| odczyt `m[k]` | `finch_map_find`; −1 → `finch_map_missing` (noreturn, z kluczem jako tekst); w innym razie miejsce `entry.value` | -| zapis `m[k]` (`=`, `+=`, metoda zmieniająca, `addr`) | `mapSlot()`: `finch_map_slot` zwraca wpis, dodając go, jeśli go nie ma; nowy wpis dostaje kopię klucza i wartość domyślną typu | -| `["a": 1, …]` | wyzerowana mapa + `mapSlot` dla każdego wpisu (pierwszy wpis ustala typy; liczbowe wartości są wszystkie `float`, jeśli któraś ma kropkę) | -| `has`, `get`, `remove`, `clear`, `keys`, `values` | `codegen_map.cpp`; wartość domyślna `get` jest liczona tylko wtedy, gdy klucza brak | -| `for k, v in m` | pętla po `0..used` pomijająca usunięte miejsca, wczytująca `entries` i `used` w każdym obrocie | -| kopia / zwolnienie / tekst | helpery jak dla tablic: `finch_map_clone_raw`, a potem głębokie kopie posiadających kluczy i wartości; zwolnienie każdego żywego wpisu, potem `finch_map_free` | - -Usunięcie oznacza miejsce (i wpis w indeksie) jako usunięte; miejsca są upychane, gdy tablica miałaby -urosnąć, a co najmniej ćwierć z nich jest usunięta. Usunięcie ostatniego klucza resetuje tablicę. +Pierwszy błąd wypisywany jest jako `ścieżka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie kompiluje, +serwer podpowiada dalej z ostatniej wersji, która się skompilowała. Test akceptacyjny: `tests/lsp_test.py` (22 sprawdzenia). --- -## 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` (bez `or` / `try`) | runtime | -| odczyt `m[klucz]` z brakującym kluczem | `mapIndex()` | -| metoda wywołana przez `null` w `ptr[T]` | `structMethod()` | - -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-.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`), mapy (`map_find`, `map_slot`, `map_remove_at`, `map_clear`, `map_free`, -`map_clone_raw`, `map_missing`; §10.12), bufory tekstu dla `print`/`str` (`buf_add`, `buf_int`, …, -`buf_print`), warianty mogące się nie udać (`str_try_int`, `str_try_float`, `try_read_file`, -`try_write_file`, `try_delete_file`, `report_error`), `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: ` 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.4.0)`. C++17, `-Wall -Wextra` (MSVC: `/W3`), 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 **74 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). -- `tests/cross.sh windows|arm64`: każdy test zbudowany z `--target` i uruchomiony w Wine / QEMU. -- `python3 tests/lsp_test.py`: serwer języka, sterowany jak przez edytor. -- CI (`.github/workflows/ci.yml`) uruchamia to wszystko na Linuksie x86-64, Linuksie ARM64, macOS ARM64 i Windowsie. - ---- - -## 20. Semantyka a C +## 12. Semantyka a C | Sytuacja | C | Finch 2.0 | |---|---|---| @@ -894,246 +410,15 @@ wynik z oczekiwanym. Kompilacja samego siebie zajmuje kompilatorowi samohostują --- -## 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 lub metoda Fincha / konstruktor struktury / funkcja C | -| `structMethod` | `obj.m(...)`: adres odbiorcy, kontrole bezpieczeństwa, wywołanie | -| `print`, `addrOf`, `convert`, `arrayMethod` | funkcje wbudowane | -| `fallible`, `tryExpr`, `orElse`, `retValue`, `retError` (`codegen.cpp`) | błędy jako wartości (§10.11) | -| `mapIndex`, `mapLit`, `mapMethod`, `mapSlot`, `forEachEntry` (`codegen_map.cpp`) | mapy (§10.12) | -| `rt` / `libc` | deklaruje funkcję runtime'u / libc | -| `fileName`, `panicIf`, `boundsCheck`, `checkDivisor` | kontrole w czasie działania | -| `classify`, `makePlan`, `declareC`, `emitCCall`, `cThunk` | klasyfikacja ABI (System V, Microsoft x64, AAPCS64), plan rejestrów, deklaracja, wywołanie, wrapper dla callbacków | - --- -## 22. Rozbudowa kompilatora +## 13. Znane ograniczenia -**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. Platformy: Windows, macOS, ARM64 - -`src/target.h` trzyma `g_target`: triple LLVM, flagi `windows` / `msvc` / `darwin` / `cross`, kompilator C -(`cc`) i rozszerzenie plików wykonywalnych. `setTarget()` zamienia `windows`, `linux`, `arm64`, `macos` -(albo pełny triple) na triple i wybiera kompilator C: `FINCH_CC`, potem `CC` (nie przy kompilacji -krzyżowej), potem `x86_64-w64-mingw32-gcc` / `aarch64-linux-gnu-gcc` dla Windowsa / Linuksa ARM64 z innego -systemu, `clang --target=` dla pozostałych celów krzyżowych, `clang` na Windowsie, `cc` na Uniksie. -`initTargets()` rejestruje backendy X86 i AArch64 (te, które ma dany LLVM; CMake definiuje -`FINCH_TARGET_X86` / `FINCH_TARGET_AARCH64` i linkuje ich biblioteki). Przez tę warstwę idzie wszystko, -co zależy od platformy: - -- **Nagłówki:** libclang parsuje z `--target=` (więc `long` ma 32 bity na Windowsie i używane są - właściwe nagłówki), a na Windowsie z `-D_USE_MATH_DEFINES`. Katalogi nagłówków pochodzą z ` -E -v`. -- **Procesy:** `capture()` / `runCommand()` opakowują `popen` / `system` (`_popen` na Windowsie) i dekodują - kody wyjścia (POSIX `WIFEXITED`/sygnały albo kody Windowsa i wyjątki NTSTATUS, np. `0xC0000005`). - `shellQuote()` cytuje dla `sh` (`'…'`) albo `cmd.exe` (`"…"`); linia polecenia dla `cmd /c`, która - zaczyna się od cudzysłowu, dostaje dodatkową parę cudzysłowów. -- **Linkowanie:** bez `-lm` przy MSVC; nazwy `.exe`; klucz cache runtime'u obejmuje triple i kompilator C. - Komunikaty linkera MSVC (`LNK2019 unresolved external symbol`, `LNK1104`/`LNK1181 cannot open file`) są - tłumaczone tak jak komunikaty GNU ld. -- **Uruchamianie:** `runPrefix()`: program dla Windowsa zbudowany na Linuksie uruchamia się przez `wine`, - program dla Linuksa ARM64 na innym procesorze przez `qemu-aarch64`. -- **macOS:** ` -E -v` wypisuje wpisy `(framework directory)`; zamieniają się w `-iframework` dla - libclang, więc `import "OpenGL/gl.h"` działa. `link "Cocoa.framework"` daje `-framework Cocoa`. Komunikaty - linkera Apple (`Undefined symbols for architecture arm64: "_f"`, `ld: library 'x' not found`, - `framework 'X' not found`) są tłumaczone jak pozostałe, bez wiodącego `_` z Mach-O. -- **Informacje dla debuggera:** CodeView dla celów MSVC, DWARF w pozostałych. -- **Runtime:** na Windowsie nie ma `getline` ani `sys/wait.h`; `stdin`/`stdout`/`stderr` są tam makrami, - więc `finch_std_stream(i)` udostępnia je Finchowi, gdy nagłówek nie ma dla nich zmiennej `extern`. -- **Wypisywanie wskaźników** jest wszędzie takie samo: `null` albo `0x…`. - -### Konwencja wywołań Microsoft x64 - -`classify()` rozgałęzia się po `g_target.windows`. Struktura o rozmiarze dokładnie 1, 2, 4 albo 8 bajtów -idzie jako **jedna liczba całkowita** tego rozmiaru, nawet jeśli zawiera floaty (`{float, float}` → `i64`). -Każda inna struktura jest **pośrednia (Indirect)**: wywołujący kopiuje ją na swój stos i przekazuje adres -kopii (bez `byval`: funkcja dostaje zwykły wskaźnik). Wyniki 1/2/4/8-bajtowe wracają w `rax` jako liczba; -wszystko inne przez ukryty wskaźnik `sret`. Każdy argument zajmuje jedno miejsce, więc nie ma liczenia -rejestrów. `cThunk()` robi to samo w drugą stronę dla callbacków. - -`tests/cross.sh windows` buduje każdy test z `--target windows`, uruchamia go w Wine i porównuje wynik: przechodzą -wszystkie, łącznie z `c_structs` (który wtedy sprawdza reguły Microsoftu na bibliotece C zbudowanej MinGW). -CI dodatkowo buduje `finch.exe` przez MSVC na Windows Server i uruchamia tam `tests/run.sh`. - -### AAPCS64 (Linux ARM64 i Apple) - -`classify()` odwzorowuje to, jak clang tłumaczy wywołania na AArch64: - -- **Jednorodny agregat zmiennoprzecinkowy** (1–4 elementy, wszystkie `float` albo wszystkie `double`, - zagnieżdżone struktury i tablice spłaszczone) idzie w rejestrach wektorowych: przekazywany jako - `[n x float]` / `[n x double]`, zwracany jako `{ float, … }`. Na Linuksie argument dostaje też - `alignstack(8)`, tak jak u clanga; Apple tego nie robi. -- Każda inna struktura do 16 bajtów idzie w rejestrach ogólnych: `i64` (≤ 8 bajtów) albo `[2 x i64]` - (`i128`, jeśli jej wyrównanie to 16); zwracana jest jako liczba całkowita dokładnie jej rozmiaru albo `[2 x i64]`. -- Większe struktury są **pośrednie (Indirect)**: kopia na stosie wywołującego, przekazana przez adres - (bez `byval`); zwracane przez `sret`. -- Nie ma liczenia rejestrów: każdą część rozmieszcza backend. -- Zwykły `char` w C jest **bez znaku** na Linuksie ARM64, a ze znakiem na Apple i x86, co ma znaczenie przy - rozszerzaniu argumentów i wyników typu `char` (`charIsSigned()`). -- Apple przekazuje zmienną część `printf(...)` przez stos; LLVM robi to na podstawie `...` w typie funkcji. - -`tests/cross.sh arm64` uruchamia cały zestaw testów (łącznie z testem ABI struktur na bibliotece C -zbudowanej GCC) w QEMU, a CI robi to natywnie na Linuksie ARM64 i Apple Silicon. - -### Budowanie finch.exe - -CI używa Visual Studio 2022, Ninja i oficjalnego pakietu `clang+llvm-21.x-x86_64-pc-windows-msvc` -(biblioteki statyczne, `/MT`, więc `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`). Dwa szczegóły Windowsa -w `CMakeLists.txt`: ten pakiet podaje ścieżkę do biblioteki DIA SDK z maszyny, na której zbudowano LLVM, -więc jest ona przekierowywana na zainstalowane Visual Studio; a osadzony runtime jest zapisywany jako -kilka surowych napisów, bo MSVC ogranicza jeden literał do 16 KB. LLVM jest inicjalizowany tylko z -natywnym backendem, żeby budowanie statyczne nie wciągało wszystkich architektur. - ---- - -## 24. Serwer języka - -`finch lsp` (`src/lsp.cpp`) mówi protokołem LSP przez stdin/stdout, używając `llvm::json`. Przy -`didOpen`/`didChange` uruchamia na dokumencie prawdziwy front-end: `Loader` (z tekstem otwartych -dokumentów jako nadpisaniem plików), `importHeaders` (w cache na zestaw importów, bo czytanie nagłówków -jest najwolniejsze) i `generate()` bez optymalizacji i emisji. Dwa przełączniki pozwalają użyć -kompilatora wewnątrz serwera: - -- `g_throwErrors`: `failAt()` rzuca `FinchError{file, line, col, msg}` zamiast wypisywać i kończyć proces. - Serwer publikuje to jako diagnostykę (dla właściwego pliku, także wewnątrz importowanego modułu) i czyści - diagnostyki, które zniknęły. -- `g_index`: podczas generowania kodu `Codegen::note()` zapisuje `SymRef` dla każdej rozwiązywanej nazwy - (zmienne, parametry, pola, funkcje, konstruktory, funkcje i stałe C, funkcje wbudowane, metody, elementy - modułów): jej zakres, opis w markdownie i pozycję definicji. `define()` i `declareStructs()` dokładają - `FnInfo` / `StructIndex`; `addVar()` dokłada `VarInfo` z zakresem linii otaczającej funkcji. Pozycje nazw - pochodzą z pól `namePos`, które zapisuje parser. - -Zapytania korzystają z tego indeksu: - -| Zapytanie | Źródło | -|---|---| -| hover, definition | `SymRef` pod kursorem | -| completion | słowa kluczowe, typy, funkcje wbudowane, zmienne otaczającej funkcji, funkcje, struktury, importowane moduły, nazwy z C pasujące do wpisanego początku; po `nazwa.`: pola struktury (także przez `ptr[T]`), metody tablic/tekstów albo zawartość modułu | -| signatureHelp | cofa się do niezamkniętego `(`, liczy przecinki na najwyższym poziomie i szuka sygnatury Fincha / C / wbudowanej | -| documentSymbol | funkcje i struktury (z polami) z pliku | - -Kod w trakcie pisania (`p.`, `add(1, `) się nie kompiluje, więc serwer trzyma **ostatnią udaną** analizę -każdego dokumentu i dołącza z niej funkcje, struktury i zmienne do podpowiedzi. `tests/lsp_test.py` -steruje serwerem tak jak edytor i sprawdza każdą funkcję. - -`src/builtins_doc.h` to jedyny opis funkcji wbudowanych i metod, używany przy hover, podpowiedziach -i podpowiedziach parametrów (i odwzorowany w ściągach). - ---- - -## 25. Integracje z edytorami - -### Kate - -`editors/kate/finch.xml` to definicja KSyntaxHighlighting (silnika Kate, KWrite i KDevelop): listy słów -kluczowych zgodne z `builtins_doc.h`, kontekst dla `fn Typ.metoda`, teksty ze znakami specjalnymi oraz -regiony zwijania dla `{ }` i komentarzy blokowych. `lspclient.json` to wpis dla klienta LSP w Kate -(`highlightingModeRegex: ^Finch$`); `install.sh` kopiuje definicję i dołącza wpis do -`~/.config/kate/lspclient/settings.json`. Kate wysyła więcej zapytań niż VS Code (`documentHighlight`, -`foldingRange`, …); serwer odpowiada na każde nieobsługiwane zapytanie błędem `MethodNotFound`, więc żaden -klient nie czeka. - -### VS Code - -`editors/vscode` to zwykłe rozszerzenie w JavaScripcie: `extension.js` uruchamia `finch lsp` przez -`vscode-languageclient` (ścieżka z ustawienia `finch.path`) i dodaje **Run** / **Build** jako zadania -`ProcessExecution` (bez cytowania dla powłoki; matcher `$finch` zamienia linie `plik:linia:kol: error:` -w wpisy w Problems). `syntaxes/finch.tmLanguage.json` to gramatyka TextMate, testowana przez `npm test` -z `vscode-textmate` + `vscode-oniguruma` (tym samym silnikiem, którego używa VS Code); -`language-configuration.json` daje komentarze, nawiasy, automatyczne domykanie i wcięcia; `snippets/` -szablony. CI pakuje je przez `vsce` do `finch-lang.vsix`. - -## 26. Znane ograniczenia - -- Brak typów generycznych, interfejsów, `match`, domknięć i enumów pisanych w Finchu. -- Teksty to bajty: `.len`, `s[i]`, `upper()` nie znają Unicode. -- Zmiana kluczy mapy wewnątrz `for` po tej samej mapie nie jest wykrywana (jest bezpieczna dla pamięci, - ale wpisy mogą zostać pominięte albo odwiedzone dwa razy). -- Porażka `try` / `or` w środku wyrażenia nie zwalnia świeżych wartości tymczasowych policzonych wcześniej - w tym wyrażeniu (mały wyciek, tylko na ścieżce porażki). -- 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`. -- Cele: x86-64 i ARM64 (Linux, macOS; Windows na x86-64). Bez Windowsa na ARM64, systemów 32-bitowych i WebAssembly. -- Kompilator samohostujący nie ma importu C, liczb z rozmiarem, `defer`, map, metod ani błędów jako - wartości i nie zwalnia pamięci. +- Kompilator działa tylko na Linuksie x86-64 i ARM64 (brak ziarna dla macOS i Windowsa). Programy można budować na wszystkie trzy. +- Wynik dla macOS jest sprawdzany tylko jako IR; Windows i ARM64 uruchamiane są w CI pod Wine i qemu. +- Jeden błąd na uruchomienie, bez odzyskiwania. +- Informacje `-g` najlepiej z `-O0`. +- Sterownik używa narzędzi `sh` (`rm`, `mkdir`), więc wymaga powłoki POSIX. +- Generyki są monomorfizowane (kod rośnie z liczbą instancji); nie ma ograniczeń na typy, błędne użycie to błąd wewnątrz + wygenerowanego ciała. +- Jeszcze nie ma enumów z danymi (warianty nie niosą wartości).