Compare commits

...
Author SHA1 Message Date
Nexoniarz
881b70ba71
Merge pull request #2 from Nexoniarz/claude/charming-cerf-c5s1a1
Claude/charming cerf c5s1a1
2026-11-01 16:07:31 +00:00
Claude
1f45bd8a01
docs (en, pl) and editors: masks, tails, shuffles, lookup, dot_add, @cpu, cpu_has, Nix
- technicians' guides: section 31 rewritten around kernels (masks, the end of a list, moving lanes,
  int8 dot products, choosing instructions at build and at run time)
- cheat sheets and engineers' guides: the new operations and their IR; how features are found
- Kate and VS Code: @cpu, boolxN and the new built-ins highlighted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 15:46:04 +00:00
Claude
04f1f94a21
Vectors for kernels: masks and select, shuffles and byte lookup, dot_add, @cpu and cpu_has; --cpu native on Nix
- comparisons of vectors give masks (boolxN): select(m, a, b), m.any/all/count, & | ^ !;
  f32x8(list, i, fill) and v.store_part(list, i) for the end of a list (masked load/store)
- v.shuffle(constant lanes), v.lo(), v.hi(), concat(a, b), table.lookup(indices):
  pshufb / vpshufb on x86 (SSSE3 and up), tbl on ARM64 (checked in the assembly, LLVM 18 and 21)
- acc.dot_add(a, b): i32 lanes gain 4 byte products each, exactly; vpdpbusd with AVX-VNNI or AVX-512 VNNI,
  usdot/sdot/udot on ARM64, the sum written out elsewhere (pmaddwd); run here with all three paths agreeing
- @cpu("avx2,fma") fn: LLVM target-features for one function (+evex512 for AVX-512, which LLVM 18+ needs);
  cpu_has("avx2"): CPUID + XGETBV on x86, hwcaps on Linux ARM64, sysctl on macOS; a wide vector can't be passed
  between functions with different @cpu (its registers differ), and the compiler says so
- the compiler asks clang which features --cpu means (its predefined macros, cached), so dot_add picks the instruction
- Nix's clang wrapper dropped -march=native in nix-shell (reproduced): finch tells it NIX_ENFORCE_NO_NATIVE=0, shell.nix too,
  and warns if --cpu native still made no difference

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 15:41:47 +00:00
Claude
0ceaaa27c4
Floats as written, never fused; fma and muladd; f32/f64 bits, f16 and bf16
- a * b + c is two roundings on every processor and --cpu: the IR has no fast-math flags, and clang
  compiles it with -ffp-contract=on (checked in the assembly: never fused, also with FMA hardware)
- fma(a, b, c) / v.fma(b, c): one rounding, always (llvm.fma)
- muladd(a, b, c) / v.muladd(b, c): the fastest way here (llvm.fmuladd), what v.fma was before
- f32_bits / f32_from_bits / f64_bits / f64_from_bits: bitcasts, also of vectors
- f16_to_f32 / f32_to_f16 (LLVM half), bf16_to_f32 / f32_to_bf16 (integer ops, ties to even, NaN kept)
- docs (en, pl): what is guaranteed about floats, the new built-ins

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 15:25:40 +00:00
Nexoniarz
5393a32a47
Merge pull request #1 from Nexoniarz/claude/charming-cerf-c5s1a1
Claude/charming cerf c5s1a1
2026-11-01 15:04:23 +00:00
Claude
06bfe940fa
docs: the Polish technicians' and beginners' guides rewritten in natural Polish; vectors in every guide
- dla-technikow.md and dla-poczatkujacych.md written anew from the English, in plain Polish
  (no calques, the same words for the same things in every guide, addressing every reader alike)
- the technicians' guides (en, pl): "Variants that hold values" moved to the enums section;
  the examples list has wordcount and simd
- src/embedded.fch packs the runtime with its updated comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 14:09:58 +00:00
Claude
8495bc718d
docs: the Polish cheat sheet and engineers' guide rewritten in natural Polish; the engineers' guide matches the Finch compiler
- names from the C++ compiler (declareFns, HelperScope, Fn::borrowParam, mutatingMethods) replaced by the real ones
- section 8's subsections numbered 8.x; the map's LLVM type has 7 fields; @W in the index records

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 14:01:56 +00:00
Claude
0bf66740df
Compiler and docs on every system: macOS Intel in CI, releases made by CI, docs for vectors and --cpu
- CI: macOS on Apple Silicon and Intel (macos-15, macos-15-intel); a tag v* attaches every package
  (Linux x64/ARM64, macOS ARM64/x64, Windows x64, the VS Code extension) to its release
- vectors: fma is llvm.fmuladd (llvm.fma was a libm call per lane without FMA hardware: 3.5x slower)
- examples/simd.fch: a dot product eight at a time, timed against the plain loop
- README and the English guides: installing on Linux, macOS and Windows; vectors (SIMD); --cpu;
  -g is DWARF on Windows too (lldb from llvm-mingw); no more mention of the C++ compiler
- comments left from the C++ compiler (src/target.cpp, src/codegen.cpp, "never frees") brought up to date

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 13:57:28 +00:00
Claude
68a9493572
Vector types (SIMD): f32x8, i8x32, f64x4 and the rest; --cpu native
- fNxM / iNxM / uNxM up to 512 bits, as LLVM vectors (<8 x float>): LLVM picks SSE, AVX2,
  AVX-512 or NEON for them
- f32x8(x) in every lane, f32x8(a, ..., h), f32x8(list, i) loads, f32x8(i32x8 v) converts
- + - * / and the bit operators lane by lane, with a plain number on either side counting for every lane;
  v[i], v[i] = x, v.len, == / != (every lane), print and str
- methods: sum min max dot abs sqrt floor ceil round fma store; min/max/abs/clamp lane by lane
- wide vectors (and structs holding them) are loaded and stored with align 16, what malloc and the stack give
- --cpu <name> (and cpu = "..." in finch.toml): native, x86-64-v3, apple-m1, ...
- debug info shows vectors; the language server completes their methods; Kate and VS Code highlight them
- f32 x = -1.5 works: a minus in front of a float written in the code keeps it such a number

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 13:50:35 +00:00
Nexoniarz
a4bdeb5712 CI: package the macOS and Windows builds again (finch-macos-arm64.tar.gz, finch-windows-x64.zip)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
2026-10-29 12:22:56 +01:00
54 changed files with 4098 additions and 1369 deletions

View file

@ -1,8 +1,8 @@
name: CI
# The compiler is written in Finch. It is built from bootstrap/<system>.ll, its own LLVM IR (see bootstrap/README.md),
# then from itself. Seeds: Linux x86-64 and ARM64 (tested here), Windows x86-64 (tested under Wine, and here),
# macOS ARM64 and x86-64 (made with Apple's headers; only checked as IR until the macos job below passes).
# then from itself, and tested, on every system it runs on: Linux x86-64 and ARM64, macOS ARM64 and x86-64, Windows x86-64.
# A tag v* also makes the release: every package below is attached to it.
on:
push:
@ -75,7 +75,14 @@ jobs:
fi
macos:
runs-on: macos-15
strategy:
fail-fast: false
matrix:
include:
- { os: macos-15, arch: arm64 }
- { os: macos-15-intel, arch: x64 }
name: macos-${{ matrix.arch }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install LLVM 21 (libclang)
@ -89,6 +96,14 @@ jobs:
build/finch version
- name: Tests
run: tests/run.sh
- name: Package
run: |
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r runtime std examples dist/finch/
tar -czf finch-macos-${{ matrix.arch }}.tar.gz -C dist finch
- uses: actions/upload-artifact@v4
with:
name: finch-macos-${{ matrix.arch }}
path: finch-macos-${{ matrix.arch }}.tar.gz
windows:
runs-on: windows-2022
@ -116,6 +131,16 @@ jobs:
export PATH="/c/llvm-mingw-20251216-ucrt-x86_64/bin:$PATH"
export FINCH_LDFLAGS="-LC:/llvm/bin"
FINCH=../build/finch.exe tests/run.sh
- name: Package
run: |
mkdir -p dist/finch
cp build/finch.exe build/libclang.dll README.md LICENSE dist/finch/
cp -r runtime std examples dist/finch/
cd dist && 7z a -tzip finch-windows-x64.zip finch > /dev/null
- uses: actions/upload-artifact@v4
with:
name: finch-windows-x64
path: dist/finch-windows-x64.zip
vscode:
runs-on: ubuntu-24.04
@ -136,3 +161,27 @@ jobs:
with:
name: finch-vscode
path: editors/vscode/finch-lang.vsix
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [linux, cross, macos, windows, vscode]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Attach the packages to the release (made if it isn't there yet)
uses: softprops/action-gh-release@v2
with:
name: Finch ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/finch-linux-x64.tar.gz
dist/finch-linux-arm64.tar.gz
dist/finch-macos-arm64.tar.gz
dist/finch-macos-x64.tar.gz
dist/finch-windows-x64.zip
dist/finch-lang.vsix

View file

@ -40,6 +40,8 @@ fn main() {
Operator precedence that does not bite (`x & 1 == 0` means what it says).
- **Practical.** Maps, methods, errors as values (`int(s) or 0`, `try read_file(p)`), modules, any C
library, and a language server for your editor. Runs on Linux, macOS and Windows, on x86-64 and ARM64.
- **SIMD built in.** `f32x8`, `i8x32`, `f64x4`: numbers side by side, added and multiplied all at once. LLVM turns them
into SSE, AVX2, AVX-512 or NEON instructions, so one piece of code is fast on a desktop, a laptop and ARM.
- **Errors that talk like a person:**
```
game.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
@ -48,13 +50,17 @@ fn main() {
```
- **Any C library, directly.** `import "raylib.h"` + `link "raylib"`, and you can call it, structs by value
and callbacks included. Headers are read by libclang, so there is nothing to declare by hand.
- **Written in itself.** The compiler is Finch code, and nothing else: `src/*.fch`, about 8.6k lines. It is built from a
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical. Its first version, in C++,
is kept on the `before-bootstrap` branch.
- **Written in itself.** The compiler is Finch code, and nothing else: `src/*.fch`, about 12k lines. It is built from a
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical.
## Quick start
**Building it** (Linux x86-64 or ARM64; needs `clang` and libclang, LLVM 21):
**Ready builds** for Linux (x64, ARM64), macOS (Apple Silicon, Intel) and Windows (x64) are in the
[releases](https://github.com/Nexoniarz/Finch/releases). Unpack one and keep `runtime/` and `std/` next to `finch`. What
each system needs besides is in the [technicians' guide](docs/en/for-technicians.md#2-installing): clang and libclang
from LLVM 21 on Linux, `brew install llvm@21` on a Mac, llvm-mingw on Windows.
**Building it yourself** (on Linux, macOS, or Windows with Git Bash; needs `clang` and libclang, LLVM 21):
```sh
git clone https://github.com/Nexoniarz/Finch.git
@ -64,13 +70,12 @@ bootstrap/build.sh # the compiler is build/finch: built from th
./build/finch run examples/hello.fch
./build/finch build examples/tour.fch -o tour && ./tour
tests/run.sh # every program, every refusal, -g, a shared library, the language server
tests/run.sh # every program, every expected error, -g, a shared library, the language server
```
Ready builds of the compiler are in the [releases](https://github.com/Nexoniarz/Finch/releases) (Linux x64 and ARM64:
unpack, keep `runtime/` and `std/` next to `finch`). **Programs for Windows, ARM64 and macOS** are built from Linux with
`--target windows|arm64|macos` (see the [guide](docs/en/for-technicians.md#3-the-finch-command)); the compiler itself does not run on
macOS or Windows yet (it needs a seed made with that system's headers: see [bootstrap/README.md](bootstrap/README.md)).
The same `bootstrap/build.sh` works on a Mac (with Homebrew's `llvm@21`) and on Windows (with llvm-mingw): see
[bootstrap/README.md](bootstrap/README.md). Programs for another system are built with `--target windows|linux|arm64|macos`
(see the [guide](docs/en/for-technicians.md#building-for-another-system)).
Editors: **Kate** (`editors/kate/install.sh`), **VS Code** (the `finch-lang` `.vsix` from the release), or any
editor with LSP support (`finch lsp`).
@ -91,13 +96,15 @@ Pick the guide that fits you:
| | |
|---|---|
| Variables | `x := 5` (inferred) or `int x = 5`; `const W = 640` |
| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, arrays `[]T`, maps `map[K]V`, pointers `ptr[T]` |
| Types | `int` `float` `bool` `char` `str`, sized `i8`…`i64` `u8`…`u64` `f32` `f64`, arrays `[]T`, maps `map[K]V`, pointers `ptr[T]`, vectors `f32x8` `i8x32`… |
| Functions | `fn add(int a, int b) -> int { return a + b }` |
| Structs | `struct Point { … }` with one field per line (`int x`, `int y = 0`); create with `Point(1, 2)` or `Point(x: 1)` |
| Methods | `fn Point.move(int dx) { self.x += dx }` → `p.move(3)` |
| Arrays | `nums := [1, 2, 3]`, `nums.push(4)`, `nums[0]`, `nums.len`, `for i, n in nums { }` |
| Maps | `ages := ["ann": 31]`, `ages["bob"] = 25`, `ages.has("cy")`, `counts[w] += 1`, `for k, v in ages { }` |
| Errors | `fn parse(str s) -> int! { … return error("bad") }` → `parse(s) or 0`, `parse(s) or { … }`, `try parse(s)` |
| Vectors (SIMD) | `v := f32x8(list, i)`, `v * 2.0 + w`, `v.sum()`, `select(v > 0.0, v, 0.0)`, `t.lookup(idx)`, `acc.dot_add(a, b)`, `v.store(list, i)` |
| Processors | `--cpu native`; `@cpu("avx2") fn …` with `if cpu_has("avx2")`; floats computed as written (`fma`/`muladd` to fuse) |
| Text | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Control | `if` / `else if` / `else` (also as a value), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures |
@ -108,7 +115,7 @@ Pick the guide that fits you:
| Tests | `test "name" { assert(x == 3) }` next to the code, run with `finch test` |
| C interop | `import "stdio.h"`, `link "glfw"`, `link "mine.c"` / `"glue.cpp"`, unions, `#define`s (also `(Color){…}`), `addr(fn)` for callbacks |
| I/O | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Tools | `finch run/build/test/fmt/ir/lsp`, `finch.toml` for a project, a build cache, `--target windows/arm64/macos/android`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors |
| Tools | `finch run/build/test/fmt/ir/lsp`, `finch.toml` for a project, a build cache, `--target windows/arm64/macos/android`, `--cpu native`, `-g` for gdb/lldb, `-O0`, the chain of calls on runtime errors |
## Examples
@ -122,6 +129,7 @@ Pick the guide that fits you:
| [`examples/structs.fch`](examples/structs.fch) | structs, arrays of structs, a linked list with `new`/`free` |
| [`examples/types.fch`](examples/types.fch) | sized numbers and pointers |
| [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/simd.fch`](examples/simd.fch) | vectors: a dot product eight numbers at a time, and the time it saves |
| [`examples/window.fch`](examples/window.fch) | a GLFW + OpenGL window |
| [`examples/opengl.fch`](examples/opengl.fch) | modern OpenGL 3.3: shaders, buffers, a spinning triangle |
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: list the GPUs, their memory and queues, create a device |
@ -151,14 +159,17 @@ Pick the guide that fits you:
- [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` as a value, `"\(x)"` in text, `"""` text, C unions and `#define` expressions
- [x] `test` blocks and `finch test`, `finch fmt`, `finch.toml`, a build cache, the chain of calls on runtime errors
- [x] Threads (`import thread`) and atomics; C++ files; `--target android` (compiles; NDK linking and devices untried)
- [x] Threads (`import thread`) and atomics; C++ files; `--target android`
- [x] The compiler on every system it builds for: Linux, macOS (Apple Silicon and Intel), Windows; releases made by CI
- [x] Vector types (`f32x8`, `i8x32`, …) for SIMD, and `--cpu native`
- [x] Masks, shuffles, byte lookup and `dot_add` for kernels; `@cpu` and `cpu_has`; `fma`/`muladd`, float bits, f16 and bf16
## Layout
```
src/ the compiler, in Finch: lexer, parser, AST, code generation (to LLVM IR as text), C import (libclang) and
the C calling conventions, the language server, the driver
std/ the standard library, in Finch: json, path, time, proc, fs
std/ the standard library, in Finch: json, path, time, proc, fs, thread
runtime/ the small C runtime linked into every program
bootstrap/ build.sh and the seeds: the compiler's own LLVM IR, one per system
examples/ example programs

View file

@ -5,8 +5,8 @@
</picture>
</p>
**Mały, zwinny, skuteczny.** Język programowania na LLVM ze składnią podobną do C,
na tyle prosty, że nauczysz się go w godzinę, i tak szybki jak C.
**Mały, szybki, celny.** Język programowania oparty na LLVM, ze składnią w stylu C. Nauczysz się go w godzinę,
a programy działają tak szybko jak w C.
> 🇬🇧 [English version](README.md)
@ -19,7 +19,7 @@ fn przeciwprostokatna(float a, float b) -> float {
fn main() {
imie := "Finch"
print("Cześć od", imie, "!")
print("Cześć, tu", imie, "!")
for i in 1..4 {
print(i, "→", przeciwprostokatna(float(i), 1.0))
@ -29,51 +29,58 @@ fn main() {
## Dlaczego Finch
- **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez garbage collectora.
Rekurencyjne `fib(40)` trwa ok. 0,19 s (`clang -O2` na tym samym kodzie C: ok. 0,23 s), a kod na tablicach
z kontrolą zakresu dorównuje C.
- **Pamięć bez bólu.** Listy, teksty i struktury są wartościami: przypisanie kopiuje, a koniec bloku je zwalnia.
Nie ma `free`, o którym można zapomnieć, nie ma użycia po zwolnieniu, a każdy test jest czysty pod valgrindem.
- **Bezpieczniejszy niż C tam, gdzie to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
i bez znaku. Indeks poza zakresem, dzielenie przez zero i null zatrzymują program czytelnym błędem, zamiast
powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, każda
biblioteka C i serwer języka dla twojego edytora. Działa na Linuksie, macOS i Windowsie, na x86-64 i ARM64.
- **Błędy mówią po ludzku:**
- **Prosty.** Nie ma średników, plików nagłówkowych ani preprocesora. Na każdą rzecz jest jeden sposób.
- **Szybki.** Kod przechodzi przez optymalizacje LLVM (`-O2`) i staje się zwykłym programem, bez garbage collectora.
Rekurencyjne `fib(40)` liczy się ok. 0,19 s (ten sam kod w C z `clang -O2`: ok. 0,23 s), a pętle po tablicach
ze sprawdzaniem zakresu są tak samo szybkie jak w C.
- **Pamięć bez kłopotów.** Listy, teksty i struktury są wartościami: przypisanie robi kopię, a koniec bloku sprząta.
Nie ma `free`, o którym da się zapomnieć, ani odwołań do zwolnionej pamięci. Valgrind nie znajduje błędów w żadnym teście.
- **Bezpieczniejszy niż C tam, gdzie nic to nie kosztuje.** Liczby nie zmieniają po cichu typu, a liczb ze znakiem
i bez znaku nie da się przypadkiem pomieszać. Indeks poza tablicą, dzielenie przez zero czy `null` kończą program
jasnym komunikatem, a nie niezdefiniowanym zachowaniem. Kolejność działań jest taka, jakiej się spodziewasz
(`x & 1 == 0` znaczy dokładnie to, co widać).
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, dowolna biblioteka C
i serwer języka dla edytora. Działa na Linuksie, macOS i Windowsie, na procesorach x86-64 i ARM64.
- **SIMD w języku.** `f32x8`, `i8x32`, `f64x4`: kilka liczb obok siebie, dodawanych i mnożonych naraz. LLVM zamienia je
na instrukcje SSE, AVX2, AVX-512 albo NEON, więc ten sam kod jest szybki na komputerze stacjonarnym, laptopie i ARM-ie.
- **Komunikaty, które da się zrozumieć:**
```
gra.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
3 | i32 y = x
| ^
```
- **Każda biblioteka C, bezpośrednio.** Piszesz `import "raylib.h"` i `link "raylib"`, i możesz z niej korzystać,
także ze struktur przekazywanych przez wartość i z callbacków. Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
- **Napisany w sobie.** Kompilator to kod w Finchu i nic więcej: `src/*.fch`, ok. 8,6 tys. linii. Buduje się go z ziarna
(własny kod LLVM IR, `bootstrap/`), potem z samego siebie, a oba wyniki są identyczne. Pierwsza wersja, w C++,
jest w gałęzi `before-bootstrap`.
- **Biblioteki C bez pośredników.** Wystarczy `import "raylib.h"` i `link "raylib"`, żeby z niej korzystać, łącznie
ze strukturami przekazywanymi przez wartość i funkcjami zwrotnymi. Nagłówki czyta libclang, więc niczego nie trzeba
deklarować ręcznie.
- **Napisany w samym sobie.** Kompilator to wyłącznie kod w Finchu: `src/*.fch`, ok. 12 tys. linii. Buduje się go
z pliku startowego (jego własnego kodu LLVM IR, w `bootstrap/`), a potem jeszcze raz nim samym. Oba wyniki są identyczne.
## Szybki start
## Na start
**Budowanie** (Linux x86-64 lub ARM64; potrzebny `clang` i libclang, LLVM 21):
**Gotowe kompilatory** na Linuksa (x64, ARM64), macOS (Apple Silicon, Intel) i Windowsa (x64) znajdziesz w
[wydaniach](https://github.com/Nexoniarz/Finch/releases). Rozpakuj archiwum i trzymaj katalogi `runtime/` i `std/` obok
pliku `finch`. Co jeszcze trzeba doinstalować, opisuje [przewodnik dla techników](docs/pl/dla-technikow.md#2-instalacja):
na Linuksie clang i libclang z LLVM 21, na Macu `brew install llvm@21`, na Windowsie llvm-mingw.
**Samodzielne budowanie** (na Linuksie, macOS albo Windowsie w Git Bash; potrzebne są `clang` i libclang z LLVM 21):
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # albo zainstaluj samodzielnie clang + libclang (LLVM 21) + pkg-config
bootstrap/build.sh # kompilator to build/finch: z ziarna, potem z samego siebie
nix-shell # albo zainstaluj clang, libclang (LLVM 21) i pkg-config po swojemu
bootstrap/build.sh # gotowy kompilator to build/finch
./build/finch run examples/hello.fch
./build/finch build examples/tour.fch -o tour && ./tour
tests/run.sh # każdy program, każda odmowa, -g, biblioteka współdzielona, serwer języka
tests/run.sh # wszystkie programy testowe, oczekiwane błędy, -g, biblioteka współdzielona, serwer języka
```
Gotowe kompilatory są w [wydaniach](https://github.com/Nexoniarz/Finch/releases) (Linux x64 i ARM64: rozpakuj, zostaw
`runtime/` i `std/` obok `finch`). **Programy dla Windowsa, ARM64 i macOS** buduje się z Linuksa przez
`--target windows|arm64|macos`; sam kompilator nie działa jeszcze na macOS ani Windowsie (potrzebuje ziarna
zrobionego z nagłówkami tego systemu: patrz [bootstrap/README.md](bootstrap/README.md)).
Ten sam `bootstrap/build.sh` działa na Macu (z `llvm@21` z Homebrew) i na Windowsie (z llvm-mingw); szczegóły są w
[bootstrap/README.md](bootstrap/README.md). Program na inny system zbudujesz opcją `--target windows|linux|arm64|macos`
(zob. [przewodnik](docs/pl/dla-technikow.md#budowanie-na-inny-system)).
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo dowolny
edytor z obsługą LSP (`finch lsp`).
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo każdy edytor,
który obsługuje LSP (`finch lsp`).
## Dokumentacja
@ -81,90 +88,96 @@ Wybierz przewodnik dla siebie:
| Przewodnik | Dla kogo | Polski | English |
|---|---|---|---|
| **Początkujący** | Nigdy nie programowałeś: uczniowie, seniorzy, ciekawscy | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) | [for-beginners.md](docs/en/for-beginners.md) |
| **Technicy** | Znasz komputery, programowanie jest nowe: instalacja, cały język, biblioteki C, problemy | [dla-technikow.md](docs/pl/dla-technikow.md) | [for-technicians.md](docs/en/for-technicians.md) |
| **Inżynierowie** | Wszystko: lekser, gramatyka, AST, reguły typów, tłumaczenie na IR, import przez libclang, ABI, wnętrze | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) | [for-engineers.md](docs/en/for-engineers.md) |
| **Ściąga** | Każda funkcja, metoda, operator i polecenie na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.md) |
| **Dla początkujących** | Dla tych, którzy nigdy nie programowali: uczniów, seniorów, ciekawych świata | [dla-poczatkujacych.md](docs/pl/dla-poczatkujacych.md) | [for-beginners.md](docs/en/for-beginners.md) |
| **Dla techników** | Znasz komputery, ale programowanie jest dla ciebie nowe: instalacja, cały język, biblioteki C, rozwiązywanie problemów | [dla-technikow.md](docs/pl/dla-technikow.md) | [for-technicians.md](docs/en/for-technicians.md) |
| **Dla inżynierów** | Wszystko: lekser, gramatyka, AST, reguły typów, generowanie IR, import przez libclang, ABI, budowa kompilatora | [dla-inzynierow.md](docs/pl/dla-inzynierow.md) | [for-engineers.md](docs/en/for-engineers.md) |
| **Ściąga** | Wszystkie funkcje, metody, operatory i polecenia na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.md) |
## Język w pigułce
| | |
|---|---|
| Zmienne | `x := 5` (typ zgadnięty) albo `int x = 5`; `const W = 640` |
| Typy | `int` `float` `bool` `char` `str`, z rozmiarem `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, mapy `map[K]V`, wskaźniki `ptr[T]` |
| Funkcje | `fn add(int a, int b) -> int { return a + b }` |
| Struktury | `struct Punkt { … }` z jednym polem na linijkę (`int x`, `int y = 0`); tworzenie przez `Punkt(1, 2)` albo `Punkt(x: 1)` |
| Zmienne | `x := 5` (typ wynika z wartości) albo `int x = 5`; `const W = 640` |
| Typy | `int` `float` `bool` `char` `str`, o podanym rozmiarze: `i8`…`i64` `u8`…`u64` `f32` `f64`, tablice `[]T`, mapy `map[K]V`, wskaźniki `ptr[T]`, wektory `f32x8` `i8x32`… |
| Funkcje | `fn dodaj(int a, int b) -> int { return a + b }` |
| Struktury | `struct Punkt { … }`, jedno pole w linii (`int x`, `int y = 0`); tworzy się je przez `Punkt(1, 2)` albo `Punkt(x: 1)` |
| Metody | `fn Punkt.przesun(int dx) { self.x += dx }` → `p.przesun(3)` |
| Tablice | `liczby := [1, 2, 3]`, `liczby.push(4)`, `liczby[0]`, `liczby.len`, `for i, n in liczby { }` |
| Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cy")`, `ile[s] += 1`, `for k, v in wiek { }` |
| Błędy | `fn parsuj(str s) -> int! { … return error("zły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
| Tekst | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
| Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) |
| Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
| Mapy | `wiek := ["ania": 31]`, `wiek["bob"] = 25`, `wiek.has("cyryl")`, `ile[slowo] += 1`, `for k, v in wiek { }` |
| Błędy | `fn parsuj(str s) -> int! { … return error("zła liczba") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
| Wektory (SIMD) | `v := f32x8(lista, i)`, `v * 2.0 + w`, `v.sum()`, `select(v > 0.0, v, 0.0)`, `t.lookup(idx)`, `acc.dot_add(a, b)`, `v.store(lista, i)` |
| Procesory | `--cpu native`; `@cpu("avx2") fn …` razem z `if cpu_has("avx2")`; liczby zmiennoprzecinkowe liczone tak, jak zapisane (`fma`/`muladd` do łączenia) |
| Tekst | `"hp: \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
| Sterowanie | `if` / `else if` / `else` (również jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
| Pamięć | tablice, teksty i struktury zwalniają się same; `new(...)` / `free(...)` dla własnych struktur na stercie |
| Enumy i `match` | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (również jako wartość) |
| Typy generyczne | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
| Moduły | `import ksztalty` → `ksztalty.pole(p)`, `import ui/button as btn`; biblioteka standardowa: `json`, `path`, `time`, `proc`, `fs`, `thread` |
| Wątki | `thread.spawn(addr(praca), addr(dane))`, `t.join()`, `thread.mutex()`, `atomic_add(addr(n), 1)` |
| Testy | `test "nazwa" { assert(x == 3) }` obok kodu, uruchamiane przez `finch test` |
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (też `(Color){…}`), `addr(fn)` dla callbacków |
| Wejście/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Narzędzia | `finch run/build/test/fmt/ir/lsp`, `finch.toml` dla projektu, cache budowania, `--target windows/arm64/macos/android`, `-g` dla gdb/lldb, `-O0`, łańcuch wywołań przy błędach w czasie działania |
| Testy | `test "nazwa" { assert(x == 3) }` tuż obok kodu, uruchamiane przez `finch test` |
| Współpraca z C | `import "stdio.h"`, `link "glfw"`, `link "moje.c"` / `"glue.cpp"`, unie, `#define` (także `(Color){…}`), `addr(fn)` jako funkcja zwrotna |
| Wejście i wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
| Narzędzia | `finch run/build/test/fmt/ir/lsp`, `finch.toml` dla projektu, pamięć podręczna kompilacji, `--target windows/arm64/macos/android`, `--cpu native`, `-g` dla gdb i lldb, `-O0`, ścieżka wywołań przy błędzie w trakcie działania |
## Przykłady
| Plik | Pokazuje |
| Plik | Co pokazuje |
|---|---|
| [`examples/hello.fch`](examples/hello.fch) | najmniejszy program |
| [`examples/hello.fch`](examples/hello.fch) | najkrótszy program |
| [`examples/tour.fch`](examples/tour.fch) | zmienne, funkcje, warunki, pętle |
| [`examples/guess.fch`](examples/guess.fch) | gra w zgadywanie: wejście, pętle, `rand` z C |
| [`examples/guess.fch`](examples/guess.fch) | gra w zgadywanie liczby: wczytywanie, pętle, `rand` z C |
| [`examples/todo.fch`](examples/todo.fch) | struktury, tablice, teksty i pliki |
| [`examples/wordcount.fch`](examples/wordcount.fch) | mapy, metody i błędy jako wartości: najczęstsze słowa w pliku |
| [`examples/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista wiązana z `new`/`free` |
| [`examples/types.fch`](examples/types.fch) | liczby z rozmiarem i wskaźniki |
| [`examples/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista jednokierunkowa z `new`/`free` |
| [`examples/types.fch`](examples/types.fch) | liczby o podanym rozmiarze i wskaźniki |
| [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
| [`examples/window.fch`](examples/window.fch) | okno GLFW + OpenGL |
| [`examples/simd.fch`](examples/simd.fch) | wektory: iloczyn skalarny liczony po osiem liczb naraz i zysk na czasie |
| [`examples/window.fch`](examples/window.fch) | okno GLFW z OpenGL |
| [`examples/opengl.fch`](examples/opengl.fch) | nowoczesny OpenGL 3.3: shadery, bufory, obracający się trójkąt |
| [`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 |
| [`src/`](src/) | kompilator napisany w Finchu (ziarno do jego zbudowania jest w [`bootstrap/`](bootstrap/)) |
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: lista kart graficznych z ich pamięcią i kolejkami, utworzenie urządzenia |
| [`examples/raylib.fch`](examples/raylib.fch) | raylib i struktury C przekazywane przez wartość |
| [`examples/llvm.fch`](examples/llvm.fch) | Finch, który buduje LLVM IR przez API LLVM-C |
| [`src/`](src/) | kompilator napisany w Finchu (plik startowy do jego zbudowania leży w [`bootstrap/`](bootstrap/)) |
## Plan rozwoju
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
- [x] Typy, w tym liczby z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null, zakres)
- [x] Operatory bitowe z rozsądnymi priorytetami
- [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
- [x] `struct`, także struktury C przez wartość (ABI System V) i callbacki
- [x] Tablice z `.len`, kontrolą zakresu i metodami; `for x in lista`
- [x] Pamięć: zwalnianie na końcu bloku, `defer`, `new`/`free`
- [x] Typy, w tym liczby o podanym rozmiarze, wskaźniki i konwersje; funkcje; `if`/`while`/`for`
- [x] Czytelne błędy kompilacji; sprawdzanie w trakcie działania (dzielenie, `null`, zakres tablicy)
- [x] Operatory bitowe z rozsądną kolejnością działań
- [x] Nagłówki C przez libclang; `link` z pkg-config; podpowiedzi przy błędach linkera
- [x] `struct`, w tym struktury C przekazywane przez wartość (ABI System V) i funkcje zwrotne
- [x] Tablice z `.len`, sprawdzaniem zakresu i metodami; `for x in lista`
- [x] Pamięć: wartości zwalniane na końcu bloku, `defer`, `new`/`free`
- [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki
- [x] Moduły Fincha (`import nazwa`)
- [x] Informacje dla debuggera (`-g`)
- [x] Bootstrap: kompilator Fincha napisany w Finchu, który buduje sam siebie
- [x] Bootstrap: kompilator Fincha napisany w Finchu, który kompiluje sam siebie
- [x] Windows: `finch.exe`, programy `.exe`, ABI Microsoft x64; budowanie na Windowsa z Linuksa (`--target windows`)
- [x] Serwer języka (`finch lsp`) i rozszerzenie VS Code
- [x] Serwer języka (`finch lsp`) i rozszerzenie do VS Code
- [x] macOS i ARM64 (Linux i Apple Silicon) z ich konwencjami wywołań C; obsługa Kate
- [x] Metody w strukturach, mapy, błędy jako wartości (`!`, `or`, `try`)
- [x] Metody struktur, mapy, błędy jako wartości (`!`, `or`, `try`)
- [x] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy
- [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs`
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia `#define`
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, cache budowania, łańcuch wywołań przy błędach
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android` (kompiluje się; linkowanie z NDK i urządzenia niesprawdzone)
- [x] `const`, `if` jako wartość, `"\(x)"` w tekście, tekst `"""`, unie C i wyrażenia w `#define`
- [x] Bloki `test` i `finch test`, `finch fmt`, `finch.toml`, pamięć podręczna kompilacji, ścieżka wywołań przy błędach
- [x] Wątki (`import thread`) i operacje atomowe; pliki C++; `--target android`
- [x] Kompilator na każdym systemie, na który buduje: Linux, macOS (Apple Silicon i Intel), Windows; wydania robi CI
- [x] Typy wektorowe (`f32x8`, `i8x32`, …) dla SIMD i `--cpu native`
- [x] Maski, przestawianie, tabele bajtów i `dot_add` dla jąder obliczeniowych; `@cpu` i `cpu_has`; `fma`/`muladd`, bity liczb, f16 i bf16
## Struktura
## Co gdzie leży
```
src/ kompilator, w Finchu: lekser, parser, AST, generowanie kodu (do LLVM IR jako tekst), import C (libclang)
i konwencje wywołań C, serwer języka, sterownik
std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs
runtime/ mały runtime w C dołączany do każdego programu
bootstrap/ build.sh i ziarna: własny LLVM IR kompilatora, po jednym na system
src/ kompilator w Finchu: lekser, parser, AST, generowanie kodu (LLVM IR jako tekst), import C (libclang)
i konwencje wywołań C, serwer języka, polecenie finch
std/ biblioteka standardowa w Finchu: json, path, time, proc, fs, thread
runtime/ mały runtime w C, dołączany do każdego programu
bootstrap/ build.sh i pliki startowe: własny LLVM IR kompilatora, osobno dla każdego systemu
examples/ przykładowe programy
editors/ rozszerzenie VS Code, podświetlanie i konfiguracja LSP dla Kate
tests/ run/ new/ (wzorcowe wyjście), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py
docs/ przewodniki en/ i pl/ dla trzech grup odbiorców
editors/ rozszerzenie do VS Code, podświetlanie składni i konfiguracja LSP dla Kate
tests/ run/ new/ (wzorcowe wyniki), fail/ new/fail/ (oczekiwane błędy), run.sh, cross.sh, shared.sh, lsp_test.py
docs/ przewodniki po angielsku (en/) i po polsku (pl/) dla trzech grup czytelników
```
## Licencja

View file

@ -3,12 +3,16 @@
Finch's compiler is written in Finch. To build it you need something that already runs Finch: the **seed**, which is
the compiler's own LLVM IR, one file per system:
| File | System | Checked |
| File | System | Built and tested in CI on |
|---|---|---|
| `linux-x86_64.ll` | Linux x86-64 | built and tested here and in CI |
| `linux-aarch64.ll` | Linux ARM64 | made with the ARM64 cross compiler; CI builds and tests it on an ARM64 runner |
| `windows-x86_64.ll` | Windows x86-64 (MinGW: llvm-mingw's clang + libclang.dll) | built and run under Wine: it compiles itself, and every test program runs |
| `macos-arm64.ll`, `macos-x86_64.ll` | macOS | made on Linux with the macOS 11.3 SDK; checked as IR only (nobody has run them yet) |
| `linux-x86_64.ll` | Linux x86-64 | ubuntu-24.04 |
| `linux-aarch64.ll` | Linux ARM64 | ubuntu-24.04-arm |
| `macos-arm64.ll` | macOS, Apple Silicon | macos-15 |
| `macos-x86_64.ll` | macOS, Intel | macos-15-intel |
| `windows-x86_64.ll` | Windows x86-64 (MinGW: llvm-mingw's clang + libclang.dll) | windows-2022 |
Each CI job builds the compiler from its seed, then from itself, runs every test, and packs the result; a tag `v*` attaches
the packages to the release.
`bootstrap/build.sh` compiles the seed with `clang` (and the runtime, and libclang) into `build/finch0`; `finch0` builds
`src/` into `finch1`; `finch1` builds `src/` again into `build/finch`; the two must make the same IR, or the script stops.
@ -41,5 +45,6 @@ official LLVM for Windows); `bootstrap/winbuild.sh` turns it into `finch.exe` fo
`bootstrap/build.sh` does the same from Git Bash: put llvm-mingw's `bin` in PATH and set `FINCH_LIBCLANG` (the path of
`libclang.dll`), `FINCH_LLVM_INCLUDE` (its `include` folder) and `FINCH_LDFLAGS=-L<folder of libclang.dll>`.
macOS: the seeds were made with the macOS 11.3 SDK; `brew install llvm@21` gives clang and libclang; the CI job sets
`FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE`. If the macOS build fails, that job is where it shows first.
macOS: the seeds were made with the macOS 11.3 SDK. `brew install llvm@21`
gives clang and libclang; set `FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE` as the CI job does (the
[technicians' guide](../docs/en/for-technicians.md#option-b-build-it-yourself) has the lines).

View file

@ -21,8 +21,9 @@ Everything in Finch on one page. For explanations, see the [guides](../../README
| `finch version` | version |
| `-l lib` | link a C library (like `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) |
| `-g` | debug info (gdb, lldb, Visual Studio) |
| `-g` | debug info (gdb, lldb) |
| `-O0` | no optimizations |
| `--cpu native` / `x86-64-v3` / `apple-m1` … | use every instruction of that processor (AVX2, AVX-512, NEON); the program then needs one like it |
| `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) |
Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files).
@ -39,6 +40,7 @@ links = ["SDL3", "native/glue.cpp"] # libraries and your own .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,."]
cpu = "native" # the same as --cpu native
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
links = ["ws2_32"]
@ -96,6 +98,7 @@ One statement per line, no semicolons, `{` on the same line, no brackets around
| `[]T` | array: `[1, 2, 3]`, `[]str names` |
| `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` |
| `ptr[T]`, `ptr` | pointer, untyped pointer |
| `f32x8`, `i8x32`, `f64x4`, `u16x16` … | vector: N numbers worked on at once (SIMD), see below |
| `Name` / `module.Name` | a struct |
Escapes: `\n \t \r \0 \\ \" \'`.
@ -240,6 +243,34 @@ Fallible built-ins: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_fil
---
## Vectors `f32x8` (SIMD)
Types: `f32x4/8/16`, `f64x2/4/8`, `i8x16/32/64`, `i16x8/16/32`, `i32x4/8/16`, `i64x2/4/8`, and `u…` the same; at most 512 bits.
| Code | Gives |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, …, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` (a `[]f32` or `ptr[f32]`) |
| `f32x8(v)` | an 8-lane vector converted lane by lane (`f32x8(ints)`, `i32x8(floats)`) |
| `v + w`, `v * 2.0`, `-v` | lane by lane; a plain number counts for every lane (`/` only on floats) |
| `& \| ^ << >> ~` | lane by lane, whole-number lanes |
| `v < w`, `v == w`, … | a mask `boolx8`: `m.all()`, `m.any()`, `m.count()`, `m & n`, `!m`, `m[i]` |
| `select(m, a, b)` | lane by lane: `a` where `m` is true, else `b` |
| `v[i]`, `v[i] = x`, `v.len` | a lane; the number of lanes |
| `min(v, w)`, `max`, `abs(v)`, `clamp(v, lo, hi)` | lane by lane |
| `v.sum()`, `v.min()`, `v.max()`, `v.dot(w)` | one number from all the lanes |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | every lane (float lanes) |
| `v.fma(b, c)`, `v.muladd(b, c)` | `v * b + c`: one rounding always / the fastest way here |
| `v.store(list, i)` | lanes into `list[i]` … |
| `f32x8(list, i, fill)`, `v.store_part(list, i)` | the end of a list: only the lanes inside it (the others are `fill`) |
| `v.shuffle(3, 2, 1, 0)`, `v.lo()`, `v.hi()`, `concat(a, b)` | lanes rearranged (shuffle takes numbers written in the code) |
| `table.lookup(idx)` | `table[idx % table.len]` per lane (`pshufb`, `tbl`) |
| `acc.dot_add(a, b)` | `i32x8` sums gain 4 byte products each (`u8x32` × `i8x32`): `vpdpbusd`, `usdot` where there |
| `@cpu("avx2,fma") fn …`, `cpu_has("avx2")` | a function that may use these features; check at run time before calling it |
---
## Text `str`
| Code | Gives |
@ -300,6 +331,9 @@ Automatic only when nothing is lost: smaller → bigger number, any int → floa
| `shell(command)` | run a command, its exit code |
| `exit(code)` | stop the program now (also ends an `or { }` block) |
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | for any numbers (your own function of that name wins) |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` with one rounding / fused where the processor can (`a * b + c` written out is never fused) |
| `f32_bits(x)`, `f32_from_bits(u)`, `f64_bits`, `f64_from_bits` | a float's bits as `u32`/`u64` and back, unchanged |
| `f16_to_f32(h)`, `f32_to_f16(x)`, `bf16_to_f32(b)`, `f32_to_bf16(x)` | half and bfloat16 numbers (in a `u16`) ↔ `f32`; also on vectors |
| `error(message)` | `return error("…")` in a `-> T!` function |
| `addr(x)` | pointer to x; `addr(fn)` = C callback |
| `assert(cond)`, `assert(cond, "why")` | stop with a runtime error when cond is false (in a `test` block: that test fails) |

View file

@ -53,8 +53,9 @@ The computer reads your program from top to bottom, line by line.
## 2. Getting Finch ready
Finch runs on **Linux** computers. If a teacher, parent or friend has already set it up
for you, skip to the next section.
Finch runs on **Linux**, **macOS** and **Windows** computers. If a teacher, parent or friend has already set it up
for you, skip to the next section. (On a Mac or on Windows, ask someone to follow the
[guide for technicians](for-technicians.md#2-installing): it has a ready-made Finch for both.)
Otherwise, open the **Terminal** program (a window where you type commands) and type
these lines one by one, pressing **Enter** after each:
@ -76,7 +77,7 @@ To check it works, type:
./build/finch version
```
You should see something like `Finch 2.5.0`.
You should see something like `Finch 2.6.0`.
---

View file

@ -1,13 +1,11 @@
# Finch for Engineers
**The internals of Finch 2.5, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
**The internals of Finch 2.6, 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 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)
@ -34,7 +32,7 @@ the Finch one.
## 1. Architecture
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.
generation and linking. That is what makes it small (about 12k lines) and what lets it be written in Finch itself.
```
main.fch ─► loader ── lex ── parse ──► Program (+ every imported module, recursively)
@ -60,7 +58,7 @@ Design decisions that shape the rest:
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,
- **A small C runtime** (`runtime/finch_rt.c`, ~1150 lines) does what is tedious in IR: strings, array growth, maps, input,
files, panics. It is compiled once and cached in `~/.cache/finch`.
---
@ -70,17 +68,17 @@ Design decisions that shape the rest:
| File | Lines | Responsibility |
|---|---|---|
| `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens |
| `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/ast.fch` | 170 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 6450 | 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/gen.fch` | 7030 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, vectors (SIMD), `-g` debug metadata, the `index` mode |
| `src/cimport.fch` | 790 | libclang: functions, structs and unions (fields, offsets, padding), enums, constants, globals, macros (also struct-valued) |
| `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/platform.fch` | 120 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 480 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 530 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists |
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
| `src/lsp.fch` | 910 | The language server (§11) |
| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
| `src/lsp.fch` | 950 | The language server (§11) |
| `src/docs.fch` | 140 | One description of the built-ins, shared by hover and completion |
| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 1150 | The C runtime: text, arrays, maps, files, float printing, fs, threads, the test runner, the call chain |
| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) |
@ -116,18 +114,19 @@ dedented by the closing line's indentation in `longString()`.
## 4. Code generation
`gen.fch` is one big `Gen` struct plus functions over it. The passes are:
`gen.fch` is one big `Gen` struct plus functions over it. `generate()` runs:
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`).
1. `declare()`: every Finch and C struct gets an LLVM type (C structs with their real padding; recursive-by-value
structs are refused), then every function gets its signature, with a *borrow analysis* for parameters (§8.2).
2. `define()` for each function: the body, type checked and emitted. Scopes track what must be dropped when they end.
Instantiating a generic adds functions while this goes on, so the loop runs until no new ones appear.
3. The C entry point `main(argc, argv)` (or, for `--shared`, `finch_module_on_fatal` and the `export fn` wrappers).
4. `assemble()`: the declarations, struct types, string constants, helpers and bodies, as one `.ll` text.
With `-g`, DWARF metadata is attached as the IR is written (use `-O0`).
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
under its shown name (`max[int]`, a symbol `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.
@ -164,10 +163,13 @@ offset 0, and a field is read through memory. They can't be passed by value to C
## 6. Driver, linking and targets
`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--shared] [--overlay path=file]`.
`finch build|run|ir|index file.fch [-o out] [-O0] [-g] [-l lib] [-I dir] [--target t] [--cpu c] [--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.
`--cpu <name>` (or `cpu` in `finch.toml`) adds `-march=<name>` on x86-64 or `-mcpu=<name>` on ARM64 to the clang that
compiles the IR (not the runtime); it is part of the cache key.
`--target windows|linux|arm64|macos|android|<triple>` 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
@ -210,6 +212,7 @@ owned at that point is not freed.
A line that starts at bracket depth 0 and doesn't continue a line ending in an operator or `,` is re-indented to
4 × brace depth; other lines (continuations, `"""` bodies) are shifted by the same amount as the line that started the
statement, so their shape (and the `"""` dedent) is kept.
---
## 7. Type system
@ -222,9 +225,10 @@ statement, so their shape (and the `"""` dedent) is kept.
| `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; see src/gen.fch) |
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (entries, len, used, cap, index, icap, adds; see src/gen.fch) |
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
| `[N]T` | `[N x T]` |
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
| 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 `-> !`) |
@ -235,11 +239,16 @@ Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `i
### Values
```cpp
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```c
struct Val {
str v // the IR operand: %t12, 42, @.str.3, zeroinitializer
ast.Type ty
bool fresh
bool lit
}
```
- **`literal`**: a number written in the code or an imported C constant. It may become any integer type
- **`lit`**: a number written in the code or an imported C constant. It may become any integer type
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,
@ -281,13 +290,64 @@ Pointers/null: `icmp eq/ne ptr` only. `str`: `finch_str_eq` (length + `memcmp`)
`finch_str_cmp` for ordering. Floats: `oeq olt ole ogt oge` and `une` for `!=`. Integers/char by
signedness (char unsigned). Bool: equality only. Arrays/structs: error.
---
### Vectors
`ast.vecType(name)` recognises `<lane>x<N>` (lanes `i8`…`u64`, `f32`, `f64`; N = 2…64; at most 512 bits) and gives
`Type(kind: "vec", elem: [lane], count: N)`, which is an LLVM vector `<N x T>`. The parser takes such a name wherever a
type goes; a call `f32x8(…)` goes to `vecMake()`.
| Finch | IR |
|---|---|
| `f32x8(x)` | `insertelement` into lane 0 + `shufflevector … zeroinitializer` (a splat) |
| `f32x8(a, …, h)` | a chain of `insertelement` |
| `f32x8(list, i)` | bounds check of `i … i+7`, then `load <8 x float>, ptr …, align 4` (the lane's alignment) |
| `f32x8(i32x8 v)` | `sitofp`, `fptosi`, `sext`, `trunc`… on the whole vector |
| `v + w`, `v * 2.0` | `fadd`/`add`… on vectors; a scalar operand is coerced to the lane type, then splatted |
| `v[i]` | a place: `getelementptr <lane>, ptr %v, i64 i` after a bounds check; or `extractelement` on a temporary |
| `v < w`, `v == w` … | `fcmp`/`icmp` → `<N x i1>`, a mask (`boolxN`: `vec` with `bool` lanes) |
| `select(m, a, b)` | `select <N x i1>` |
| `m.any()`, `.all()`, `.count()` | `llvm.vector.reduce.or`/`.and`; `zext` + `llvm.vector.reduce.add` |
| `f32x8(list, i, fill)`, `v.store_part(list, i)` | `llvm.masked.load`/`llvm.masked.store` with the mask `<0, 1, …> < len - i` |
| `v.shuffle(…)`, `.lo()`, `.hi()`, `concat(a, b)` | `shufflevector` with a constant mask |
| `table.lookup(idx)` | `and` with `len - 1`, then `extractelement`/`insertelement` per lane: the pattern the x86 and ARM backends turn into `pshufb`/`tbl` |
| `acc.dot_add(a, b)` | `llvm.x86.avx512.vpdpbusd.{128,256,512}` or `llvm.aarch64.neon.{usdot,sdot,udot}` when the function's features have them; else `zext`/`sext`, `mul`, four strided `shufflevector`s and `add`s |
| `min`, `max`, `abs` | `llvm.minnum`/`maxnum`, `llvm.smin`/`umin`…, `llvm.fabs`/`llvm.abs` |
| `v.sum()`, `.min()`, `.max()` | `llvm.vector.reduce.fadd` (with `reassoc`), `.add`, `.fmin`, `.smin`… |
| `v.sqrt()`, `.floor()`, `.ceil()`, `.round()` | the `llvm.*` intrinsic of that name |
| `v.fma(b, c)`, `fma(a, b, c)` | `llvm.fma` (one rounding; a libm call per lane without FMA hardware) |
| `v.muladd(b, c)`, `muladd(a, b, c)` | `llvm.fmuladd` (fused where the target has FMA, `fmul` + `fadd` where not) |
| `v.store(list, i)` | bounds check, `store <8 x float> …, align 4` |
Integer `/` and `%` are refused: neither x86 nor ARM has a lane-wise integer divide, and LLVM would scalarise it.
Ordering comparisons are refused because their result would be a mask, which has no Finch type.
**Alignment.** LLVM gives `<8 x float>` an ABI alignment of 32 (its size), but `malloc` and Finch's `alloca … align 16`
promise only 16. `llty()` records every vector wider than 16 bytes in `g.wide`, and `resolveStructBody` records a struct
that holds one; `load()` and `store()` add `, align 16` for those types. Without it LLVM may emit `vmovaps` on a 16-byte
aligned address and crash.
**Which instructions** is clang's choice: `--cpu <name>` adds `-march=<name>` (x86-64) or `-mcpu=<name>` (ARM64) to the
IR's compile, so the same IR becomes SSE2, AVX2 + FMA, AVX-512 or NEON code. Lanes wider than the registers are split.
Some operations need to know the features at code generation time (`dot_add` picks `vpdpbusd` only where it exists).
`linker.cpuFeatures()` asks clang for its predefined macros with the same target and `--cpu`
(`clang -march=X -dM -E empty.c`: `__AVXVNNI__`, `__AVX512VNNI__`, `__ARM_FEATURE_MATMUL_INT8` …), maps them to Finch's
feature names and caches the answer per clang and flags; they reach `gen` as `Options.features`. `@cpu("…")` on a function
(`Node.cpu`) adds its own: `define … "target-features"="+avx2,+fma"` (with `+evex512` for any AVX-512 feature, which
LLVM 18+ needs for 512-bit registers), and `g.curFeatures` is the union while that function is generated. Features of the
other architecture are dropped (`cpuOf`). `checkCpuCall` refuses a vector wider than 128 bits as a parameter or result
between functions whose `@cpu` differ, because caller and callee would lower it to different registers.
`cpu_has(name)` calls `finch_cpu_has` in the runtime: CPUID leaves 1 and 7 with XGETBV for the AVX/AVX-512 state on x86,
`getauxval(AT_HWCAP/AT_HWCAP2)` on Linux ARM64, `sysctlbyname("hw.optional.arm.FEAT_…")` on macOS.
Clang is always run as `NIX_ENFORCE_NO_NATIVE=0 clang …` on Unix: Nix's cc-wrapper otherwise drops `-march=native`
inside `nix-shell`. If `--cpu native` still yields no SSE4.2 macro on x86, the driver warns.
---
## 8. Ownership and memory
### 9.1 The model
### 8.1 The model
Every value of an **owning type** (`str`, `[]T`, or a Finch struct with an owning field) has exactly one
owner: a variable, an array element, a struct field, or a heap cell from `new()`. The owner drops the
@ -311,21 +371,21 @@ A string literal is a fresh value with `cap = 0`: moving it costs nothing, dropp
(`release()` ignores constants), and writing into it (`s[i] = c`) first makes a heap copy
(`finch_str_own`). Strings from C are borrowed with `cap = -1` and always copied before being kept.
### 9.2 Parameter borrowing: `mutates()`
### 8.2 Parameter borrowing: `mutates()`
When declaring a function, every owning parameter is checked with `mutates(name, body)`: an
assignment whose root variable (`rootVar()`: through `.field` and `[i]`) is the parameter, a changing
method on it (`push pop insert remove clear resize sort reverse`), or `addr(…)` of it. Unchanged
parameters are borrowed (`Fn::borrowParam`); changed ones are copied once at entry. The analysis is
parameters are borrowed (`FnSig.borrow`); changed ones are copied once at entry. The analysis is
syntactic and conservative: anything that *might* change the parameter makes a copy, which is always correct.
The same analysis decides whether `for x in list` must copy elements.
Methods take part: `declareFns()` first finds which methods change `self` (`mutates("self", body)`),
Methods take part: `declare()` first finds which methods change `self` (`mutates("self", body)`),
repeating until nothing changes, because calling a changing method on `self` changes `self` too. Their
names go into `mutatingMethods`, which `mutatesExpr()` treats like `push`: `p.move(1)` changes `p`.
Only then is `borrowParam` computed for every function and method.
names go into `g.mm`, which `mutates()` treats like `push`: `p.move(1)` changes `p`.
Only then is `borrow` computed for every function and method.
### 9.3 Copy and drop
### 8.3 Copy and drop
`dropAt(addr, T)` / `copyAt(dst, src, T)` work on addresses:
@ -335,10 +395,10 @@ Only then is `borrowParam` computed for every function and method.
element and then `finch_arr_free`; copies call `finch_arr_clone_raw` and then deep-copy owning
elements over the raw bits. Structs drop/copy only their owning fields.
Helpers are emitted in their own functions (`HelperScope` saves the builder position and switches off
debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
Helpers are emitted in their own functions (`beginHelper()` / `endHelper()` set the function being written aside
and switch off debug locations), may call each other recursively (`struct Node { []Node kids }`), and are inlined by O2.
### 9.4 What it looks like
### 8.4 What it looks like
```c
fn greet(str name) -> str {
@ -381,22 +441,22 @@ The `-O0` IR of `main` (abridged):
and `greet` returns `msg` without copying (`ret … %5` after loading the slot: the variable was moved out).
### 9.5 defer
### 8.5 defer
`defer stmt` pushes a `Cleanup{isDefer, body, order = current varOrder}` into the current scope; it
emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
`defer stmt` records the statement in the current scope (`Scope.defers`, with `orders`: how many variables existed
then) and a `Cleanup{isDefer}`; it emits nothing there. Whenever the scope's cleanups are emitted (falling off the end of the block,
`return`, `break`/`continue` leaving it) the body is generated **at that exit**, with `deferLimit = order`
so that `lookup()` hides variables declared after the defer: those are already dropped by then
(cleanups run in reverse). `return`/`break`/`continue` inside a deferred statement are errors (`inDefer`).
Defer bodies are therefore duplicated per exit path, which is what makes them free on the normal path.
### 9.6 Manual memory
### 8.6 Manual memory
`new(v)` allocates `sizeof(T)` with `finch_alloc`, stores `own(v)` and returns `ptr[T]`. `free(p)` drops
the pointee if `T` is owning (skipping null) and calls libc `free`. Pointers never own anything:
`addr(x)` can dangle after `x`'s block ends; this is not checked.
### 9.7 Verifying it
### 8.7 Verifying it
`MEMCHECK=1 tests/run.sh` builds every test program and runs it under valgrind with
`--leak-check=full --errors-for-leak-kinds=all`: zero leaks and zero invalid accesses across all of them,
@ -405,8 +465,6 @@ iteration, nested arrays of structs, and C struct passing.
---
---
## 9. Generics, enums and match
See §4 for how they are generated. For the rules a user sees, read sections 23 and 24 of the
@ -428,8 +486,9 @@ and stops unless `finch1` and `finch` produce byte-identical IR (a fixpoint: the
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.
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.
A seed is per system because the C calling convention for the libclang calls is baked into it (§5). There are seeds
for Linux x86-64 and ARM64, macOS ARM64 and x86-64 (made with Apple's SDK headers), and Windows x86-64 (made with
llvm-mingw's headers). CI builds the compiler from each one on that system and runs the tests there.
---
@ -445,16 +504,17 @@ mode the compiler prints, besides IR-free output, tab-separated records:
| `@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 |
| `@W` | a warning |
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).
test.
---
## 12. Semantics vs. C
| Situation | C | Finch 2.0 |
| Situation | C | Finch |
|---|---|---|
| Signed integer overflow | UB | defined wraparound (no `nsw`) |
| Integer division by zero, `INT_MIN / -1` | UB | compile error if constant, otherwise runtime error |
@ -467,13 +527,22 @@ test (22 checks).
| Uninitialized variables | UB | impossible (zero or defaults) |
| Missing return | UB if used | compile error |
| Order of evaluation | mostly unspecified | left to right; in an assignment the right side first |
| Shift ≥ width, out-of-range float→int | UB | still poison (not checked yet) |
| Shift ≥ width, out-of-range float→int | UB | poison, as in LLVM (not checked) |
| `a * b + c` on floats | may be fused into an FMA (`-ffp-contract`) | never fused: two roundings |
### Floating point
Float operations are emitted as plain `fadd`/`fsub`/`fmul`/`fdiv`/`fneg` with **no fast-math flags**, so LLVM may not
reassociate, contract or assume no NaNs; `linker.fch` also compiles the IR with `-ffp-contract=on`, which fuses only the
`llvm.fmuladd` calls Finch asks for. The only flagged instruction is `v.sum()`'s `llvm.vector.reduce.fadd` (`reassoc`).
`fma` → `llvm.fma`, `muladd` → `llvm.fmuladd`, on scalars and vectors (`fmaCall`, `fusedOp`).
`f32_bits`/`f32_from_bits`/`f64_…` are `bitcast` (also between vectors). `f16_to_f32` is `bitcast i16 → half` + `fpext`,
`f32_to_f16` is `fptrunc` + `bitcast`: one instruction with F16C or on ARM64, `__extendhfsf2`/`__truncsfhf2` from the C
compiler's runtime library elsewhere. `bf16_to_f32` is `zext` + `shl 16` + `bitcast`; `f32_to_bf16` rounds to nearest even
in integer ops (`u + 0x7fff + ((u >> 16) & 1)`, then `>> 16`) and keeps NaNs quiet (`| 0x40`), so it needs no library.
| Dangling pointer from `addr()` / `new` + `free` | UB | still UB |
---
---
### The chain of calls on runtime errors
With `Options.trace` (default for `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; never with `--shared`) every Finch
@ -518,13 +587,10 @@ evaluated twice and temporaries are never pointed to.
## 13. Known limitations
- 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.
- A failed test's owned memory is not freed (the test runner `longjmp`s out of it).
- `--target android` is checked to the object file only; linking needs the NDK and has not been run on a device.
- Vectors have no integer division, and `lookup` on baseline x86-64 (no SSSE3) is done lane by lane.

View file

@ -41,6 +41,7 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md)
28. [A project: finch.toml](#28-a-project-finchtoml)
29. [Threads](#29-threads)
30. [C++, Rust and Android](#30-c-rust-and-android)
31. [Vectors (SIMD)](#31-vectors-simd)
---
@ -67,19 +68,24 @@ messages that tell you how to fix the problem.
## 2. Installing
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux**
(x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
(LLVM 21) to read C headers and to compile its output.
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every
system it needs **clang** (it compiles each program with it) and **libclang** from LLVM 21 (it reads C headers with it).
The compiler does not run on macOS or Windows yet; it needs a seed made with that system's headers
([bootstrap/README.md](../../bootstrap/README.md) says why). Until then, build your Windows and macOS programs from Linux
with `--target` (below), or use the older C++ compiler from the `before-bootstrap` branch.
### Option A: a ready build
### Option A: a ready build (Linux)
Download the archive for your system from the [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it.
Keep `runtime/` and `std/` next to the `finch` file (or install it, below).
Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
[releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. Keep `runtime/` and `std/` next to the `finch` file
(or install it, below). You also need `clang` 21 and its libclang library:
| System | Archive | Also install |
|---|---|---|
| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 and libclang (table below) |
| Linux ARM64 (Raspberry Pi 4/5, Graviton…) | `finch-linux-arm64.tar.gz` | the same |
| macOS, Apple Silicon | `finch-macos-arm64.tar.gz` | `xcode-select --install` (clang and the system headers), `brew install llvm@21` (libclang) |
| macOS, Intel | `finch-macos-x64.tar.gz` | the same |
| Windows x64 | `finch-windows-x64.zip` (`finch.exe` with `libclang.dll`) | [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/releases) (the `ucrt-x86_64` zip): unpack it and put its `bin` folder on your PATH |
On Linux:
| Distribution | Packages |
|---|---|
@ -88,12 +94,15 @@ Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
| Arch | `clang pkgconf` |
| Nix | `nix-shell` in the repository gives all of it |
On Windows, llvm-mingw gives Finch `clang`, the C headers and the linker in one folder; Windows programs built with it
need nothing else to run. Keep `libclang.dll` next to `finch.exe`.
### Option B: build it yourself
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # clang, libclang, pkg-config (or install them with your distribution)
nix-shell # Linux: clang, libclang, pkg-config (or install them with your distribution)
bootstrap/build.sh # build/finch
./build/finch version
```
@ -102,6 +111,26 @@ bootstrap/build.sh # build/finch
`src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker
looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`.
On a Mac, with Homebrew's LLVM:
```sh
brew install llvm@21
export FINCH_CLANG="$(brew --prefix llvm@21)/bin/clang"
export FINCH_LIBCLANG="-L$(brew --prefix llvm@21)/lib -lclang"
export FINCH_LLVM_INCLUDE="$(brew --prefix llvm@21)/include"
bootstrap/build.sh
```
On Windows, in Git Bash, with llvm-mingw on the PATH and the official LLVM for Windows (for `libclang.dll` and its headers)
unpacked to `C:/llvm`:
```sh
export FINCH_LIBCLANG="C:/llvm/bin/libclang.dll"
export FINCH_LLVM_INCLUDE="C:/llvm/include"
export FINCH_LDFLAGS="-LC:/llvm/bin"
bootstrap/build.sh # build/finch.exe, with libclang.dll copied next to it
```
### Building for another system
`--target` builds a program for another system. Finch needs that system's C compiler to link:
@ -151,7 +180,9 @@ options:
-l <lib> link a C library, same as link "lib" in the file
-I <dir> look for C headers there too
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
-g add debug info (for gdb / lldb / Visual Studio)
--cpu <name> use every instruction of a processor: native (this one), x86-64-v3 (AVX2),
x86-64-v4 (AVX-512), apple-m1, ... (section 31)
-g add debug info (for gdb and lldb)
-O0 skip optimizations
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
@ -314,6 +345,40 @@ str(p) // a C char pointer as text
**Fixed-size numbers wrap around**, like real hardware: a `u8` holding 255 plus 1 becomes 0.
Calculations happen in the type of the values involved, so `u8(250) + 10` is `4`, not `260`.
### Decimal numbers: what is guaranteed
`f32` and `f64` are IEEE 754 numbers, and Finch computes them **exactly as written**, the same on every processor and
with every `--cpu`:
- Each operation rounds once, in the order written. `a * b - c * d` is two multiplies and a subtract, never folded into a
fused multiply-add (C compilers often do that by default, which changes the last digits).
- Nothing is reordered or simplified: `(a + b) + c` is not turned into `a + (b + c)`, and `x * 0.0` is not assumed to be 0
(it is NaN when `x` is). The one exception is `v.sum()` on a vector, which adds the lanes in the fastest order.
When speed matters more than the last bit, ask for it by name:
| | |
|---|---|
| `fma(a, b, c)` | `a * b + c` with a single rounding, always. One instruction where the processor has FMA (with `--cpu`, on ARM64); a slower library call elsewhere. |
| `muladd(a, b, c)` | `a * b + c` the fastest way on this processor: fused where it can be, a multiply and an add where not. |
Both work on numbers and on vectors (`v.fma(b, c)`, `v.muladd(b, c)`).
**Numbers as bits.** File formats and model weights store numbers as raw bits. These look at the bits without
converting the value (all of them also work lane by lane on vectors):
| | |
|---|---|
| `f32_bits(x)` / `f32_from_bits(u)` | `f32` ↔ the `u32` with the same bits (`f32_bits(1.0)` is `0x3f800000`) |
| `f64_bits(x)` / `f64_from_bits(u)` | `f64` ↔ `u64` |
| `f16_to_f32(h)` / `f32_to_f16(x)` | half precision, kept in a `u16`, ↔ `f32` (rounded to the nearest) |
| `bf16_to_f32(b)` / `f32_to_bf16(x)` | bfloat16, kept in a `u16`, ↔ `f32` (rounded to the nearest, ties to even) |
```c
[]u16 weights = read_weights() // f16 numbers from a file
v := f16_to_f32(u16x8(weights, 0)) // 8 of them as f32, at once
```
---
## 7. Operators
@ -666,6 +731,8 @@ Using `.value`, `.field` or `[i]` on a `null` pointer **stops the program** with
| `exit(code)` | Ends the program right away. |
| `addr(x)` | A pointer to `x`; `addr(function)` gives C a function pointer (callback). |
| `new(value)`, `free(p)` | Manual heap memory (see [Memory](#13-memory-who-frees-what)). |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` with one rounding / the fastest way (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `f32_bits(x)`, `f32_from_bits(u)`, `f16_to_f32(h)`, … | A number's bits, and half-precision numbers (see [Decimal numbers](#decimal-numbers-what-is-guaranteed)). |
| `int(x)`, `str(x)`, `u8(x)`, … | Conversions (see [Conversions](#conversions)). `str(x)` works on every type and gives the same text `print` shows. `int(text) or 0` handles text that isn't a number. |
---
@ -946,8 +1013,7 @@ gdb ./game
```
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
visible; without it the optimizer may remove some. On Windows, `-g` writes CodeView, which the
Visual Studio debugger and WinDbg read.
visible; without it the optimizer may remove some. On Windows the debug info is DWARF as well: use the `lldb` that comes with llvm-mingw, or gdb.
---
@ -959,10 +1025,11 @@ Visual Studio debugger and WinDbg read.
| `… come from "x.h", but its library isn't linked` | Add `link "name"` at the top of the file. |
| `the library 'x' wasn't found` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
| `the C function 'f' can't be used from Finch yet` | It uses a union, bit fields or another unsupported type by value. Look for a variant that takes pointers. |
| `--cpu native`: the program stops with `Illegal instruction` on another computer | It was built for the processor that built it. Build without `--cpu`, or with an older one (`x86-64-v2`). |
| `can't find the module 'x'` | Put `x.fch` next to the importing file, or set `FINCH_PATH`. |
| `couldn't build the Finch runtime` | No working C compiler. Install gcc or clang, or set `CC`. |
| A library is not found when the program starts | It was linked from a folder the system doesn't search. Run inside the same `nix-shell`, or install it system-wide. |
| Windows: `finch` can't link (`LNK…` errors about `libcmt`, `kernel32`) | Install Visual Studio Build Tools with *Desktop development with C++*. |
| Windows: `no C compiler found to link with ('clang')` | Put llvm-mingw's `bin` folder on your PATH (section 2). |
| Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. |
| macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. |
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
@ -980,7 +1047,7 @@ Finch/
├── std/ the standard library, in Finch: json, path, time, proc, fs, thread
├── runtime/ finch_rt.c: the small runtime (text, arrays, maps, input, files)
├── bootstrap/ build.sh, install.sh and the seeds (the compiler's own IR, one per system)
├── examples/ hello, tour, types, structs, todo, guess, c_import, window, opengl, vulkan, raylib, llvm
├── examples/ hello, tour, types, structs, todo, guess, wordcount, c_import, simd, window, opengl, vulkan, raylib, llvm
├── tests/
│ ├── run/ new/ programs + the exact output they must print (.out), input (.in)
│ ├── fail/ new/fail/ programs that must fail, with the expected error in line 1
@ -994,35 +1061,12 @@ Finch/
```
```sh
tests/run.sh # programs, refusals, -g builds, a shared library, the language server
tests/run.sh # programs, expected errors, -g builds, a shared library, the language server
MEMCHECK=1 tests/run.sh # the same under valgrind: no leaks, no bad memory access
tests/cross.sh windows # every test built for Windows and run with Wine
tests/cross.sh arm64 # every test built for ARM64 Linux and run with QEMU
```
### Variants that hold values
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
```c
enum Shape {
Circle(float)
Rect(float, float)
Point
}
fn Shape.area() -> float {
return match self {
Circle(r) -> 3.14 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
}
```
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
---
## 23. Enums and match
@ -1069,6 +1113,29 @@ match word {
handle **every variant** or end with `else` (the compiler says which are missing); any other match needs `else`.
In an expression, every arm gives the same type.
### Variants that hold values
A variant can carry values. Make one with `Shape.Circle(2.0)`, take them out with `match`, which names them:
```c
enum Shape {
Circle(float)
Rect(float, float)
Point
}
fn Shape.area() -> float {
return match self {
Circle(r) -> 3.14 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
}
```
`_` ignores a value. `match` must handle every variant (or end with `else`). Printing shows `Rect(2, 3.5)`. Such an enum is
copied and freed like a struct; it can't be compared with `==` (match on it). Variants of these enums can't have numbers.
## 24. Generics
Write a function or a struct once for any type, with the type as a name in square brackets:
@ -1134,7 +1201,7 @@ Not there yet:
- Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly. `--target android` makes code for Android, but linking with the NDK and running on a device are untried.
- Windows on ARM64, 32-bit systems, WebAssembly.
### Warnings about big copies
@ -1193,6 +1260,7 @@ links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o /
libdirs = ["vendor/SDL3/lib"] # folders with libraries
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
cpu = "native" # the same as --cpu native (section 31)
[target.windows] # added only when building for Windows
links = ["ws2_32"]
@ -1292,6 +1360,195 @@ Write the header (`float path_len(float x, float y);`, or let `cbindgen` write i
**Android**: `finch build game.fch --target android --shared` makes `libgame.so` for 64-bit ARM Android (API 24 and
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new and only partly checked: Finch's
output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
NDK and running on a device have not been tried yet.
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS
that older Android versions need.
---
## 31. Vectors (SIMD)
A processor can add or multiply several numbers with one instruction: 4 floats at once with SSE, 8 with AVX2, 16 with
AVX-512, 4 with ARM's NEON. That is SIMD (single instruction, multiple data), and it is how image filters, audio, physics
and neural networks get their speed. In Finch it is a type: **`f32x8` is 8 `f32` side by side**.
```c
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5) // 0.5 in every lane
print(a + b) // f32x4(1.5, 2.5, 3.5, 4.5)
print(a * 2.0) // a plain number counts for every lane: f32x4(2, 4, 6, 8)
print(a.sum(), a[2], a.len) // 10 3 4
```
**The types** are a number type, `x`, and how many lanes: `f32x4` `f32x8` `f32x16`, `f64x2` `f64x4` `f64x8`,
`i8x16` `i8x32` `i8x64`, `i16x8` `i16x16`, `i32x4` `i32x8` `i32x16`, `i64x2` `i64x4`, and the same with `u` (unsigned).
2 to 64 lanes, at most 512 bits in all. A vector is a plain value like an `int`: it goes into variables, struct fields,
arrays, maps and function parameters, and `print` and `str` show it.
**Making one:**
| | |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, c, d, e, f, g, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` from a `[]f32` (or through a `ptr[f32]`); stops the program if they aren't all in the list |
| `f32x8(v)` | another 8-lane vector converted lane by lane: `f32x8(ints)`, `i32x8(floats)` (cuts the fractions), `i16x8(i32s)` |
**Working with it:**
| | |
|---|---|
| `+ - * /` | lane by lane (`/` on floats only: x86 and ARM can't divide whole numbers lane by lane) |
| `& \| ^ << >> ~` | lane by lane, on whole-number lanes |
| `-v` | every lane negated |
| `v[i]`, `v[i] = x`, `v.len` | one lane; the number of lanes |
| `min(v, w)`, `max(v, w)`, `abs(v)`, `clamp(v, lo, hi)` | lane by lane; `lo`, `hi` and `w` can be plain numbers |
| `v.sum()`, `v.min()`, `v.max()` | all the lanes made into one number |
| `v.dot(w)` | `(v * w).sum()` |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | every lane (float lanes) |
| `v.fma(b, c)` | `v * b + c` with one rounding, always (the same on every processor) |
| `v.muladd(b, c)` | `v * b + c` the fastest way here: one instruction where the processor can (the last bit may differ between processors) |
| `v.store(list, i)` | writes the lanes to `list[i]` … (or through a `ptr`) |
**A real loop.** The usual shape: as many whole vectors as fit, then the rest one by one.
```c
fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len {
total += a[i] * b[i]
i += 1
}
return total
}
```
`examples/simd.fch` times this against the plain loop.
### Masks: comparing, choosing, the end of a list
Comparing vectors compares every lane, so the answer is a **mask**: one `true`/`false` a lane, of type `boolx8` (for 8 lanes).
```c
v := f32x8(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, -4.0, 5.0)
m := v > 0.0 // boolx8(false, false, false, true, true, true, false, true)
relu := select(m, v, 0.0) // v where the mask is true, 0 elsewhere
print(m.count(), m.any(), m.all()) // 4 true false
if (v == w).all() { ... } // a condition needs one bool: all() or any()
```
| | |
|---|---|
| `v < w`, `<=`, `>`, `>=`, `==`, `!=` | a mask (a plain number on one side counts for every lane) |
| `select(m, a, b)` | lane by lane: `a` where `m` is true, `b` where false (either may be a plain number) |
| `m.any()`, `m.all()`, `m.count()` | is any lane true, are all, how many |
| `m & n`, `m \| n`, `m ^ n`, `!m` | masks combined |
| `m[i]` | one lane, as a `bool` |
| `i32x8(m)`, `boolx8(v)` | true is 1; a lane that isn't 0 is true |
**The end of a list.** A list is rarely a whole number of vectors long. Two forms touch only the lanes that are inside it:
| | |
|---|---|
| `f32x8(list, i, fill)` | `list[i]` … as far as the list goes; the lanes past its end are `fill` |
| `v.store_part(list, i)` | writes only the lanes that fit in the list |
```c
fn add_one([]f32 xs) {
i := 0
while i < xs.len { // no separate loop for the rest
(f32x8(xs, i, 0.0) + 1.0).store_part(xs, i)
i += 8
}
}
```
### Moving lanes
| | |
|---|---|
| `v.shuffle(3, 2, 1, 0)` | the lanes with these numbers (written in the code), in this order; as many lanes as numbers |
| `v.lo()`, `v.hi()` | the lower and the upper half |
| `concat(a, b)` | `a`'s lanes, then `b`'s |
| `table.lookup(idx)` | for every lane of `idx`: `table[idx % table.len]`. A byte table (`u8x16`, `i8x16`) is one `pshufb` on x86, `tbl` on ARM |
Unpacking 4-bit numbers (two to a byte, as quantized models store them) is shuffle, mask and lookup:
```c
fn unpack4(u8x8 packed, i8x16 meaning) -> i8x16 {
pairs := packed.shuffle(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7) // each byte twice
odd := u8x16(0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) == 1
nibbles := select(odd, pairs >> 4, pairs & 15) // low nibble, then high
return meaning.lookup(nibbles) // 0..15 -> what it stands for
}
```
### Int8 dot products: dot_add
`acc.dot_add(a, b)` is the step of a quantized model's matrix multiply. `acc` is an `i32x8` of sums; `a` and `b` have four
times as many bytes (`u8x32` and `i8x32`; `i8` × `i8` and `u8` × `u8` work too). Each lane of `acc` gains the sum of four
products, `a[4k]*b[4k] + … + a[4k+3]*b[4k+3]`, exactly (no saturation), and the sums stay in the vector:
```c
acc := i32x8(0)
i := 0
while i + 32 <= weights.len {
acc = acc.dot_add(u8x32(activations, i), i8x32(weights, i))
i += 32
}
total := acc.sum() // once, at the end
```
It is one instruction where the processor has one: `vpdpbusd` with AVX-VNNI (Intel since Alder Lake, AMD Zen 5) or
AVX-512 VNNI, `usdot`/`sdot`/`udot` on ARM with the dot-product features. Elsewhere it is a few instructions
(`pmaddwd` and adds), with the same result. Which one is used depends on `--cpu` or the function's `@cpu` (below).
### Choosing the instructions: --cpu, @cpu and cpu_has
By default a program runs on every processor of its system (on x86-64 that means SSE2, so an `f32x8` is done as two
halves). Three ways to use more:
**`--cpu`, for the whole program.** `--cpu native` (or `cpu = "native"` in `finch.toml`) lets LLVM use everything the
processor that builds it has: AVX2 and FMA, AVX-512, VNNI, or NEON's newer parts on ARM. The program then needs a processor
like that one. To pick a level instead: `--cpu x86-64-v3` (AVX2, most PCs since 2015), `--cpu x86-64-v4` (AVX-512),
`--cpu alderlake`, `--cpu apple-m1`, `--cpu neoverse-v1`.
**`@cpu`, for one function, and `cpu_has` to choose at run time.** One program can carry an AVX-512 version, an AVX2
version and a plain one, and pick the best the computer it runs on has:
```c
@cpu("avx512f,avx512bw,avx512vl,avx512vnni")
fn matmul_avx512([]u8 a, []i8 b, []i32 out) { ... } // may use AVX-512 inside
@cpu("x86-64-v3")
fn matmul_avx2([]u8 a, []i8 b, []i32 out) { ... } // a level works too
fn matmul([]u8 a, []i8 b, []i32 out) {
if cpu_has("avx512vnni") && cpu_has("avx512vl") {
matmul_avx512(a, b, out)
} else if cpu_has("avx2") {
matmul_avx2(a, b, out)
} else {
matmul_plain(a, b, out)
}
}
```
- The features are x86's `sse3 ssse3 sse4.1 sse4.2 popcnt avx avx2 fma f16c bmi1 bmi2 avx512f avx512bw avx512dq avx512vl
avx512vnni avx512bf16 avx512fp16 avxvnni`, the levels `x86-64-v2`/`v3`/`v4`, and ARM's `neon fp16 dotprod i8mm bf16 sve sve2`.
- Names of the other architecture are left out when building, so the x86 and ARM versions sit side by side in one file
(and `cpu_has` says `false` for them).
- Call a `@cpu` function only after `cpu_has` said yes: on a processor without the feature it stops with *Illegal instruction*.
- A vector wider than 16 bytes (`f32x8`, `i32x16` …) can't be passed between functions with different `@cpu`: the two
sides would expect it in different registers. Pass the list (or a `ptr`) instead; Finch says so if you try.
Lanes wider than the processor's registers are split by LLVM, and the same code builds for x86-64 and ARM64 alike.
`v.sum()` on float lanes adds them in whatever order is fastest, so the last digits can differ from adding them one by one.
On NixOS, Nix's clang wrapper removes `-march=native` inside `nix-shell`. Finch tells the wrapper to keep it, so
`--cpu native` works there too; if it ever has no effect, Finch warns.

View file

@ -1,12 +1,11 @@
# Finch dla inżynierów
**Wnętrze Fincha 2.5, 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.
**Jak od środka działa Finch 2.6, którego kompilator jest napisany w Finchu.** Jak kod źródłowy zamienia się w tokeny,
drzewo AST i LLVM IR *w postaci tekstu*, który `clang` kompiluje i linkuje z małym runtime'em w C; jak nagłówki C są
czytane przez libclang z poziomu samego Fincha i jak ręcznie odtworzono konwencje wywołań C; reguły typów i własności
wartości; jak kompilator buduje sam siebie z pliku startowego; i jak działa serwer języka.
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.
Zakładam, że umiesz czytać kod w Finchu (zob. [przewodnik dla techników](dla-technikow.md)) i że LLVM IR nie jest ci obcy.
> 🇬🇧 English version: [for-engineers.md](../en/for-engineers.md)
@ -15,194 +14,210 @@ Pierwsza wersja kompilatora, w C++ na API C++ LLVM, jest w gałęzi `before-boot
## Spis treści
1. [Architektura](#1-architektura)
2. [Drzewo źródeł](#2-drzewo-źródeł)
3. [Front end: lekser, parser, moduły](#3-front-end-lekser-parser-moduły)
2. [Pliki źródłowe](#2-pliki-źródłowe)
3. [Część frontowa: lekser, parser, moduły](#3-część-frontowa-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)
5. [Import z C i ABI języka C](#5-import-z-c-i-abi-języka-c)
6. [Sterownik, linkowanie i systemy docelowe](#6-sterownik-linkowanie-i-systemy-docelowe)
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)
9. [Typy generyczne, enumy i match](#9-typy-generyczne-enumy-i-match)
10. [Bootstrap](#10-bootstrap)
11. [Serwer języka](#11-serwer-języka)
12. [Semantyka a C](#12-semantyka-a-c)
12. [Semantyka w porównaniu z C](#12-semantyka-w-porównaniu-z-c)
13. [Znane ograniczenia](#13-znane-ograniczenia)
---
## 1. Architektura
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.
W kompilatorze nie ma biblioteki LLVM. Zapisuje on LLVM IR jako **tekst**, a optymalizację, generowanie kodu maszynowego
i linkowanie zostawia `clangowi`. Dzięki temu jest mały (ok. 12 tys. linii) i dało się go napisać w samym Finchu.
```
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
`import "x.h"` ─► cimport.fch (libclang wołany z Fincha) ─► funkcje, struktury i 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
gen.fch ─► generate(): deklaracje struktur i funkcji, potem ciała funkcji;
sprawdzanie typów i emisja IR w jednym przejściu; klasyfikacja ABI dla wywołań C
│ (tekst IR)
▼
linker.fch ─► clang -c prog.ll ─► clang prog.o rt-<hash>.o [twoje .c/.o/.a] -lm [pkg-config / -l]
│
▼
plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany)
plik wykonywalny (finch run: uruchamiany z pliku tymczasowego, potem usuwany)
```
Decyzje projektowe:
Decyzje, od których zależy cała reszta:
- **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`.
- **Pierwszy błąd kończy pracę.** Kompilator wypisuje `plik:linia:kolumna: error: …` z linią źródła i strzałką,
po czym się zatrzymuje. Nie próbuje kontynuować po błędzie, więc dalsze etapy nigdy nie widzą częściowo poprawnego wejścia.
- **Jedno przejście.** `gen.fch` sprawdza typy i emituje IR w jednym przejściu po AST. Każde wyrażenie daje operand IR,
swój typ Fincha (rodzaj zapisany tekstem: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `vec`, `named`…) i flagi.
- **Resztę robi clang.** Zmienne lokalne to `alloca`, które `mem2reg` zamienia na rejestry. Optymalizuje `-O2` clanga
(albo `-O0` przy `-g`).
- **Wartości, nie referencje.** Tablice, teksty i struktury zachowują się jak wartości; o własności decyduje kompilator (§8).
- **Mały runtime w C** (`runtime/finch_rt.c`, ok. 1150 linii) robi to, co w IR byłoby żmudne: teksty, powiększanie tablic,
mapy, wczytywanie, pliki, błędy w trakcie działania. Kompiluje się go raz i trzyma w `~/.cache/finch`.
---
## 2. Drzewo źródeł
## 2. Pliki źródłowe
| Plik | Linie | Zadanie |
| Plik | Linie | Za co odpowiada |
|---|---|---|
| `src/lexer.fch` | 470 | Tokeny i pozycje, tekst `"""`, `"\(x)"` zamienione na tokeny `("…" + str(x) + "…")` |
| `src/ast.fch` | 120 | Jedna struktura `Node` z napisem `kind`, dziećmi, nazwami typów, pozycjami (`line`, `endLine`) |
| `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, generyki (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 6450 | 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` | 790 | libclang: funkcje, struktury i unie (pola, offsety, wypełnienie), enumy, stałe, globalne, makra (także będące strukturą) |
| `src/platform.fch` | 90 | `Target`: triple, układ danych, kompilator C, znak `char` zależnie od systemu |
| `src/linker.fch` | 430 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
| `src/main.fch` | 515 | Sterownik: CLI, loader z wyszukiwaniem modułów i nakładkami, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy zależne od celu |
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w LSP |
| `src/lsp.fch` | 910 | Serwer języka (§11) |
| `src/docs.fch` | 110 | Jeden opis wbudowanych funkcji dla podpowiedzi i uzupełniania |
| `src/lexer.fch` | 470 | Tokeny, pozycje, tekst `"""`, zamiana `"\(x)"` na tokeny `("…" + str(x) + "…")` |
| `src/ast.fch` | 170 | Jedna struktura `Node` z rodzajem (`kind`) jako tekstem, dziećmi, nazwami typów i pozycją (`line`, `endLine`); rozpoznawanie typów wektorowych |
| `src/parser.fch` | 910 | Zejście rekurencyjne: deklaracje, instrukcje, wyrażenia, typy generyczne (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 7030 | Sprawdzanie typów i IR: struktury, funkcje, metody, zasięgi i zwalnianie, instrukcje, wyrażenia, funkcje wbudowane, mapy, błędy jako wartości, typy generyczne (monomorfizacja), enumy i `match`, wektory (SIMD), metadane debuggera (`-g`), tryb `index` |
| `src/cimport.fch` | 790 | libclang: funkcje, struktury i unie (pola, przesunięcia, wyrównanie), enumy, stałe, zmienne globalne, makra (także o wartości struktury) |
| `src/platform.fch` | 120 | `Target`: triple, układ danych, kompilator C, znak typu `char` na danym systemie |
| `src/linker.fch` | 480 | Pamięć podręczna (runtime, obiekt programu, pliki C/C++), biblioteki (`link`, pkg-config), podpowiedzi przy błędach linkera |
| `src/main.fch` | 530 | Sterownik: wiersz poleceń, wczytywanie modułów i nakładek, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: mały czytnik TOML, listy dla poszczególnych systemów |
| `src/fmt.fch` | 225 | `finch fmt` i formatowanie w serwerze języka |
| `src/lsp.fch` | 950 | Serwer języka (§11) |
| `src/docs.fch` | 140 | Jeden opis funkcji wbudowanych, wspólny dla podpowiedzi i dymków |
| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 1150 | Runtime w C: tekst, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, łańcuch wywołań |
| `bootstrap/` | | `build.sh`, `install.sh`, ziarna (§10) |
| `runtime/finch_rt.c` | 1150 | Runtime w C: teksty, tablice, mapy, pliki, wypisywanie liczb, fs, wątki, uruchamianie testów, ścieżka wywołań |
| `bootstrap/` | | `build.sh`, `install.sh`, pliki startowe (§10) |
---
## 3. Front end: lekser, parser, moduły
## 3. Część frontowa: lekser, parser, moduły
**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.
**Lekser.** Koniec linii kończy instrukcję; wcięcia nie mają znaczenia. Każdy token zna plik, linię i kolumnę, z których
korzystają wszystkie komunikaty o błędach.
**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ą.
**Parser.** Zejście rekurencyjne, jedna funkcja na każdą konstrukcję. Warto wiedzieć, że: typy są parsowane do nazwy
(`[]str`, `map[str]int`, `ptr[char]`, `Stack[int]`, `f32x8`); `fn Typ.metoda` deklaruje metodę; `fn nazwa[T]`
i `struct Nazwa[T]` deklarują szablony; `enum` i `match` mają własne węzły; `export fn` oznacza funkcję widoczną z C;
blok zapamiętuje swoją `endLine` (korzysta z niej serwer języka). Stara kolejność `fn nazwa(...) typ` daje błąd z podpowiedzią.
**Moduły.** `import nazwa` szuka kolejno `nazwa_<os>.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.
**Moduły.** `import nazwa` szuka po kolei: `nazwa_<system>.fch` obok importującego pliku (`windows`, `linux`, `macos`),
`nazwa.fch` w tym samym miejscu, katalogów z `FINCH_PATH`, a na końcu biblioteki standardowej `std/`. Każdy moduł jest
parsowany raz; jego nazwy są dostępne jako `modul.nazwa`. `import "x.h"` trafia zamiast tego do importu z C.
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka od folderu głównego pliku (albo od folderu z `FINCH_PATH`
lub `std/`) bez `.fch` i bez końcówki `_<system>`, np. `ui/przycisk`. `Program.imports` trzyma nazwę używaną w danym
pliku (`przycisk`, albo `p` przy `import ui/przycisk as p`), a `Program.modules` klucz; `modFor()` zamienia jedno na drugie.
Symbole używają klucza z `/` zamienionym na `$` (`@finch.ui$przycisk.rysuj`).
Moduł rozpoznaje się po **pliku**: kluczem jest jego ścieżka względem katalogu głównego pliku (albo katalogu z
`FINCH_PATH`, albo `std/`), bez `.fch` i bez końcówki `_<system>`, np. `ui/button`. `Program.imports` przechowuje nazwę,
której używa dany plik (`button` albo `btn` przy `import ui/button as btn`), a `Program.modules` klucz; `modFor()`
przechodzi od jednego do drugiego. Symbole używają klucza, w którym `/` zamieniono na `$` (`@finch.ui$button.make`).
**Stałe.** `const NAZWA = wyrażenie` to węzeł najwyższego poziomu; `constValue()` liczy wyrażenie przy każdym użyciu, w
module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wychodzi literał (operatory na literałach całkowitych
składa `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola i konwersje liczb; `constBusy` łapie pętle.
**Stałe.** `const NAZWA = wyrazenie` to węzeł najwyższego poziomu; `constValue()` oblicza wyrażenie przy każdym użyciu,
w module, w którym je zdefiniowano, bez zmiennych lokalnych, więc wynik zawsze składa się do literału (operatory na
literałach całkowitych są składane w `arith`). `checkConst()` dopuszcza tylko literały, nazwy, operatory, pola
i konwersje liczbowe; `constBusy` wykrywa cykle.
**Dodatki leksera.** `1e9` / `2.5e-3` to tokeny float (tekst dostaje `.` dla LLVM); teksty `"""` tracą wcięcie
linii zamykającej w `longString()`.
**Drobiazgi leksera.** `1e9` i `2.5e-3` to tokeny zmiennoprzecinkowe (tekst jest uzupełniany o `.`, którego wymaga LLVM);
z tekstu `"""` funkcja `longString()` usuwa wcięcie linii zamykającej.
---
## 4. Generowanie kodu
`gen.fch` to jedna duża struktura `Gen` i funkcje na niej. Przejścia:
`gen.fch` to jedna duża struktura `Gen` i funkcje, które na niej pracują. `generate()` wykonuje kolejno:
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`).
1. `declare()`: każda struktura Fincha i C dostaje typ LLVM (struktury C z prawdziwym wyrównaniem; struktury, które
przez wartość zawierają same siebie, są odrzucane), a potem każda funkcja dostaje sygnaturę z *analizą pożyczania*
parametrów (§8.2).
2. `define()` dla każdej funkcji: ciało, ze sprawdzeniem typów i emisją IR. Zasięgi pamiętają, co trzeba zwolnić przy
wyjściu. Tworzenie instancji typów generycznych dokłada w tym czasie nowe funkcje, więc pętla trwa, dopóki pojawiają się nowe.
3. Punkt wejścia C, `main(argc, argv)` (a przy `--shared`: `finch_module_on_fatal` i opakowania funkcji `export fn`).
4. `assemble()`: deklaracje, typy struktur, stałe tekstowe, funkcje pomocnicze i ciała funkcji jako jeden tekst `.ll`.
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.
Przy `-g` metadane DWARF są dopisywane na bieżąco, w trakcie zapisywania IR (najlepiej razem z `-O0`).
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.
Typy generyczne są **monomorfizowane**: wywołanie `max(1, 2)` wyznacza `T`, a potem AST szablonu jest generowane jeszcze
raz z podstawionym `T`, pod nazwą `max[int]` (symbol `max.int`). `match` na enumie musi być wyczerpujący (brak wariantu
to błąd, `else` obejmuje resztę); na zwykłej wartości to ciąg porównań. Wartości enumów to `i32`; `Kolor(n)` sprawdza
zakres; do wypisywania służy wygenerowana tabela nazw.
Błędy jako wartości: `-> T!` zwraca parę (wartość, tekst błędu); `try` przy błędzie wychodzi z funkcji, `or` go obsługuje,
`error("…")` go tworzy.
---
## 5. Import C i ABI C
## 5. Import z C i ABI języka C
`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.
`cimport.fch` woła **libclang** bezpośrednio z Fincha (sam jest napisany z `import "clang-c/Index.h"`), parsuje nagłówek
z flagami systemu docelowego i przechodzi po kursorach. Typy tłumaczy tak: parametr `const char *` → `str`, inne
`char *` → `ptr[char]`, wynik `char *` → `str`; liczby całkowite według rozmiaru i znaku (`char` jest bez znaku na
Linuksie ARM64); struktury i enumy według ich układu w pamięci.
LLVM IR nie realizuje za ciebie konwencji wywołań C, więc `gen.fch` ją odtwarza:
LLVM IR nie realizuje sam konwencji wywołań C, więc `gen.fch` odtwarza ją ręcznie:
- `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).
**Makra.** `#define` będący jedną liczbą albo tekstem czyta się z tokenów. Taki, którego tokeny mogą być wyrażeniem
stałym (bez klamer, średników i słów instrukcji), liczy clang: drugie parsowanie nagłówka dodaje
`static const __auto_type __finch_m_NAZWA = (NAZWA);` dla każdego, a `clang_Cursor_Evaluate` daje liczbę całkowitą,
double albo tekst. Te, które nie są stałymi, dają tylko błędy w tym drugim parsowaniu i są pomijane.
**Unie** to `CStruct` z `isUnion`: typ LLVM to `{ [n x iA] }` (rozmiar i wyrównanie od clanga), każde pole ma
przesunięcie 0, a pole czyta się przez pamięć. Nie da się ich przekazać do C przez wartość (`unsupported` mówi dlaczego).
- `classify`: System V x86-64; struktura do 16 bajtów idzie w rejestrach według klasy każdych 8 bajtów (INTEGER/SSE), większa przez pamięć;
- `classifyArm64`: AAPCS64, w tym jednorodne agregaty liczb zmiennoprzecinkowych (HFA) i wariant Apple;
- reguły Microsoft x64 (struktury 1, 2, 4 i 8-bajtowe przez wartość w rejestrze, pozostałe przez wskaźnik);
- `makePlan` zamienia klasyfikację w plan wywołania; `cThunk` sprawia, że `addr(fn)` da się wywołać z C (funkcje zwrotne).
Ten sam kod pozwala kompilatorowi wołać libclang, którego API przekazuje `CXCursor`, `CXType` i `CXString` przez wartość.
**Makra.** `#define` z jedną liczbą albo jednym literałem tekstowym jest odczytywany z tokenów. Taki, którego tokeny
mogą tworzyć wyrażenie stałe (bez klamer, średników i słów kluczowych instrukcji), oblicza clang: drugie parsowanie
nagłówka dopisuje dla każdego `static const __auto_type __finch_m_NAZWA = (NAZWA);`, a `clang_Cursor_Evaluate` zwraca
liczbę całkowitą, liczbę typu double albo tekst. Makra, które nie są stałymi, dają w tym drugim parsowaniu tylko błędy
i są pomijane.
**Unie** to `CStruct` z `isUnion`: typ LLVM to `{ [n x iA] }` (rozmiar i wyrównanie podaje clang), każde pole ma
przesunięcie 0, a pole czyta się przez pamięć. Nie da się ich przekazać do C przez wartość (pole `unsupported` mówi dlaczego).
---
## 6. Sterownik, linkowanie i cele
## 6. Sterownik, linkowanie i systemy docelowe
`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`.
`finch build|run|ir|index plik.fch [-o wynik] [-O0] [-g] [-l bib] [-I katalog] [--target t] [--cpu c] [--shared] [--overlay sciezka=plik]`.
`ir` wypisuje IR; `index` wypisuje rekordy dla serwera języka (§11); `--shared` tworzy bibliotekę współdzieloną z funkcji
`export fn`.
`--target windows|linux|arm64|macos|android|<triple>` 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.
`--cpu <nazwa>` (albo `cpu` w `finch.toml`) dodaje `-march=<nazwa>` na x86-64 albo `-mcpu=<nazwa>` na ARM64 do wywołania
clanga, które kompiluje IR (nie runtime); wartość wchodzi do klucza pamięci podręcznej.
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`).
`--target windows|linux|arm64|macos|android|<triple>` zmienia triple i układ danych, znak typu `char` w C, reguły ABI
opisane wyżej, wariant modułów dla systemu (`nazwa_windows.fch`) i kompilator C: `FINCH_CC` albo `clang --target=…`.
Przy `link "bib"` linkowanie pyta `pkg-config` o flagi; gdy linker zawiedzie, jego wyjście jest analizowane
i kompilator podpowiada brakujące `link "…"`. Obiekt runtime'u ma klucz z hasza swojego źródła i systemu docelowego.
Kompilator znajduje `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
Zmienne środowiskowe: `FINCH_PATH` (katalogi modułów), `FINCH_RT` (źródło runtime'u), `FINCH_CC` (kompilator C i linker;
IR zawsze kompiluje `clang`, nigdy `CC`), `FINCH_LIBCLANG` (dla `bootstrap/build.sh`).
**Pamięć podręczna budowania** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
Kompilator szuka `runtime/` i `std/` obok siebie (`../`, `./`) albo w `../share/finch/` (`bootstrap/install.sh`).
- `ir/<skrót pliku głównego + celu + flag>.o` z plikiem `.key`, w którym jest skrót clanga, flag i całego IR. Jedno miejsce
na program, więc cache nie rośnie z każdą zmianą; to samo IR daje ten sam obiekt bez uruchamiania clanga.
- `c/<skrót źródła + kompilatora + flag + celu>.o` dla `link "x.c"` / `.cpp`, budowane z `-MMD`; `.key` to skrót każdego
pliku z listy `.d` (źródła i jego nagłówków). Zmiana któregokolwiek przebudowuje obiekt.
- Obiekty są zapisywane do pliku `.part<pid>` i przemianowywane, więc dwa budowania naraz nie widzą pół pliku.
Skróty to dwa niezależne 32-bitowe (FNV-1a i djb2), razem 64 bity.
**Pamięć podręczna kompilacji** (`host.cacheDir()`: `$XDG_CACHE_HOME/finch`, `~/.cache/finch`, `%LOCALAPPDATA%\finch`):
**`finch.toml`** (`src/manifest.fch`) jest szukany od folderu pliku głównego (albo bieżącego) w górę. Trafia do
`values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, cele)` skleja listę z najwyższego poziomu i te z
`[target.<os>]` oraz `[target.<os>-<cpu>]`. Ścieżki liczą się od folderu manifestu. Wpisy trafiają tam, gdzie opcje
z linii poleceń: `paths` → loader, `include` → `-I` (libclang i pliki C), `links` → `link`, `libdirs` i `ldflags` →
polecenie linkowania, `cflags` → pliki C/C++.
- `ir/<hash głównego pliku + systemu + flag>.o` z plikiem `.key`, w którym jest hash clanga, flag i całego IR. Jedno
miejsce na program, więc pamięć nie rośnie przy każdej zmianie kodu; to samo IR daje ten sam obiekt bez uruchamiania clanga.
- `c/<hash źródła + kompilatora + flag + systemu>.o` dla `link "x.c"` / `.cpp`, budowany z `-MMD`; `.key` to hash
wszystkich plików wymienionych w `.d` (źródła i jego nagłówków). Zmiana któregokolwiek z nich wymusza ponowną kompilację.
- Obiekty są zapisywane do pliku `.part<pid>` i dopiero potem przemianowywane, więc dwie równoległe kompilacje nie
zobaczą połowy pliku. Hasze to dwa niezależne 32-bitowe (FNV-1a i djb2), razem 64 bity.
**Pliki C++**: `.cpp`/`.cc`/`.cxx` w `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`,
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dokłada standardową bibliotekę C++.
**`finch.toml`** (`src/manifest.fch`) jest szukany od katalogu głównego pliku (albo katalogu roboczego) w górę.
Trafia do `values["sekcja.klucz"]` i `lists["sekcja.klucz"]`; `list(klucz, systemy)` łączy listę z najwyższego poziomu
z listami z `[target.<system>]` i `[target.<system>-<procesor>]`. Ścieżki są liczone względem katalogu manifestu.
Wpisy trafiają tam, gdzie opcje z wiersza poleceń: `paths` → wczytywanie modułów, `include` → `-I` (libclang i pliki C),
`links` → `link`, `libdirs` i `ldflags` → polecenie linkowania, `cflags` → pliki C/C++.
**Pliki C++**: plik `.cpp`/`.cc`/`.cxx` z `links` kompiluje `cxxFor(cc)` (`FINCH_CXX` albo `clang` → `clang++`,
`…gcc` → `…g++`, `cc` → `c++`), a potem ten sam sterownik linkuje, co dołącza bibliotekę standardową C++.
**Android**: `--target android` to `aarch64-linux-android24`; kompilator C to clang z NDK,
`toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (z `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`), linkowanie
dodaje `-llog` i nie dodaje `-lpthread` (bionic ma go w libc). Wariant OS modułów to `linux`.
`toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android24-clang` (z `ANDROID_NDK_HOME`/`ANDROID_NDK_ROOT`);
linkowanie dodaje `-llog`, a nie dodaje `-lpthread` (bionic ma wątki w libc). Wariantem modułów jest `linux`.
**`finch test`** kompiluje jak `run`, z `Options.test`: wszystkie funkcje są dalej definiowane, ale `@main` wywołuje
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` zrobiona przez
parser), a na końcu `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w czasie
działania i nieudany `assert`) wraca przez `longjmp` z `fatal_hook`, więc jeden nieudany test nie kończy całości.
Pamięć, którą test miał w tym momencie, nie jest zwalniana.
**`finch test`** kompiluje tak jak `run`, z `Options.test`: wszystkie funkcje są definiowane jak zwykle, ale `@main` woła
`finch_test_run(nazwa, fn)` dla każdego bloku `test` z modułów użytkownika (`__test_<n>`, funkcja `void` utworzona przez
parser), a potem `finch_test_summary()`. Runtime uruchamia każdy test pod `setjmp`; `fatal()` (każdy błąd w trakcie
działania i nieudany `assert`) wraca przez `longjmp` z pomocą `fatal_hook`, więc jeden nieudany test nie przerywa reszty.
Pamięć, którą test miał w chwili błędu, nie jest zwalniana.
**`finch fmt`** działa na liniach: śledzi `{ }`, `( )`/`[ ]`, teksty (razem z `\( )`), `'…'`, `"""` i `/* */`.
Linia, która zaczyna się na głębokości nawiasów 0 i nie kontynuuje linii zakończonej operatorem albo `,`, dostaje
wcięcie 4 × głębokość klamer; pozostałe (kontynuacje, wnętrza `"""`) przesuwają się o tyle samo co linia, od której
zaczęła się instrukcja, więc zachowują kształt (i usuwanie wcięcia z `"""`).
**`finch fmt`** działa na liniach: śledzi `{ }`, `( )`/`[ ]`, teksty (z `\( )`), `'…'`, `"""` i `/* */`. Linia, która
zaczyna się na głębokości nawiasów 0 i nie ciągnie linii zakończonej operatorem albo `,`, dostaje wcięcie 4 × głębokość
klamer; pozostałe (ciągi dalsze, wnętrza `"""`) przesuwa się o tyle samo co linię, która zaczęła instrukcję, więc ich
kształt (i ucinanie wcięcia `"""`) zostaje.
---
## 7. System typów
@ -215,55 +230,61 @@ kształt (i ucinanie wcięcia `"""`) zostaje.
| `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; zob. src/gen.fch) |
| `map[K]V` | `{ ptr, i64, i64, i64, ptr, i64, i64 }` (wpisy, len, used, cap, indeks, icap, adds; 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 (§5) |
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Wektory) |
| struktura Fincha | nazwany typ struktury, naturalny układ |
| `self` w metodzie | `ptr` (pierwszy parametr) |
| wynik `-> T!` | `{ i1 ok, T wartosc, str blad }` (`i8` zamiast `T` dla `-> !`) |
| struktura C | nazwana struktura **packed** z jawnymi bajtami wyrównania (§5) |
Znakowość istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`,
`sext/zext`, `sitofp/uitofp`, `fptosi/fptoui`.
Znak liczby istnieje tylko w typie Fincha i wybiera `sdiv/udiv`, `ashr/lshr`, `icmp s*/u*`, `sext/zext`,
`sitofp/uitofp`, `fptosi/fptoui`.
### Wartości
```cpp
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```c
struct Val {
str v // operand IR: %t12, 42, @.str.3, zeroinitializer
ast.Type ty
bool fresh
bool lit
}
```
- **`literal`**: liczba wpisana w kod albo zaimportowana stała C. Może stać się dowolnym typem całkowitym,
w którym się mieści (`fits()`), albo `f32`. Przechodzi przez minus jednoargumentowy i arytmetykę samych
literałów zwijaną do stałej; jawne konwersje ją kasują (`u8(250)` to stała z typem, więc `u8(250) + 10` zawija się do 4).
- **`fresh`**: wartość posiadająca pamięć (str/tablica/struktura z takimi polami), której nikt jeszcze nie
trzyma: wynik wywołania, literał, sklejenie, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8).
- **`lit`**: liczba zapisana w kodzie albo stała zaimportowana z C. Może stać się dowolnym typem całkowitym,
w którym się mieści (`fits()`), albo `f32`. Przetrwa jednoargumentowy minus i działania na samych literałach,
które składają się do stałej; jawne konwersje ją usuwają (`u8(250)` to stała z typem, więc `u8(250) + 10` daje 4).
- **`fresh`**: wartość posiadająca pamięć (str, tablica, struktura z takimi polami), której jeszcze nikt nie trzyma:
wynik wywołania, literał, połączenie tekstów, konstruktor. Trzeba ją zapisać (przenieść) albo zwolnić (§8).
### Konwersje niejawne: `coerce(v, want)`
Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; całkowita → całkowita,
jeśli wartość to mieszczący się literał albo typ się rozszerza (`widens()`: ta sama znakowość i więcej bitów
albo bez znaku → ściśle większa ze znakiem); całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`.
Nic więcej: błąd podpowiada jawną konwersję. Typy posiadające pamięć konwertują się tylko na siebie.
Literały tablic biorą typ elementu z miejsca, do którego trafiają (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
Identyczne typy; `null` → dowolny wskaźnik; wskaźnik z typem ↔ `ptr` bez typu; liczba całkowita → całkowita, jeśli
to pasujący literał albo typ się „poszerza” (`widens()`: ten sam znak i więcej bitów, albo bez znaku → większy ze znakiem);
liczba całkowita → zmiennoprzecinkowa; `f32` → `f64`; literał `f64` → `f32`. Nic poza tym: komunikat błędu podpowiada
jawną konwersję. Typy posiadające pamięć przechodzą tylko w siebie. Literał tablicy bierze typ elementów od miejsca,
do którego trafia (`exprWant()`), więc `[]f32 xs = [1, 2.5]` działa.
### Argumenty operatorów: `unify(l, r)`
Strona-literał dopasowuje się do drugiej (także literał float obok `f32`: `x * 2.0` zostaje `f32`); w przeciwnym razie
rośnie strona, która się rozszerza; `i32` + `u32` to błąd; całkowita + zmiennoprzecinkowa → zmiennoprzecinkowa;
f32 + f64 → f64. Nie ma promocji całkowitych z C: `u8 + u8` zostaje `u8`. Dwa literały całkowite składają się w literał,
więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
Strona, która jest literałem, dopasowuje się do drugiej (także literał zmiennoprzecinkowy obok `f32`: `x * 2.0`
zostaje `f32`); w przeciwnym razie rośnie ta strona, którą da się poszerzyć; `i32` + `u32` to błąd; całkowita +
zmiennoprzecinkowa → zmiennoprzecinkowa; f32 + f64 → f64. Nie ma promocji liczb całkowitych jak w C: `u8 + u8`
zostaje `u8`. Dwa literały całkowite składają się w literał, więc `FLAG_A | FLAG_B` nadal mieści się w `u64` albo `u32`.
### Konwersje jawne: `convert()`
| Z → Na | IR |
|---|---|
| int/char ↔ int/char | `trunc`/`sext`/`zext` wg znakowości źródła |
| int/char ↔ int/char | `trunc`/`sext`/`zext` według znaku źródła |
| bool → int | `zext` |
| float ↔ int | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
| float ↔ float | `fpext`/`fptrunc` |
| zmiennoprzecinkowa ↔ całkowita | `fptosi`/`fptoui`, `sitofp`/`uitofp` |
| zmiennoprzecinkowa ↔ zmiennoprzecinkowa | `fpext`/`fptrunc` |
| int → bool | `icmp ne 0` |
| liczba/bool/char → str | `finch_str_from_int/uint/float/char/bool` (fresh) |
| str → int/float | `finch_str_to_int/float` (panic przy złym tekście) |
| str → int/float | `finch_str_to_int/float` (przy złym tekście błąd w trakcie działania) |
| `[]u8`/`[]char` → str | `finch_str_from_bytes` |
| ptr → str | `finch_str_from_c` (pożyczony, `cap = -1`) |
| ptr ↔ ptr | nic |
@ -271,102 +292,154 @@ więc `FLAG_A | FLAG_B` dalej mieści się w `u64` albo `u32`.
### Porównania
Wskaźniki/null: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
`finch_str_cmp` dla porządku. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` i `une` dla `!=`.
Całkowite i znaki wg znakowości (char bez znaku). Bool: tylko równość. Tablice/struktury: błąd.
Wskaźniki i `null`: tylko `icmp eq/ne ptr`. `str`: `finch_str_eq` (długość + `memcmp`) dla `==`/`!=`,
`finch_str_cmp` dla kolejności. Liczby zmiennoprzecinkowe: `oeq olt ole ogt oge` oraz `une` dla `!=`. Liczby całkowite
i `char` według znaku (`char` bez znaku). Bool: tylko równość. Tablice i struktury: błąd.
---
### Wektory
`ast.vecType(nazwa)` rozpoznaje `<element>x<N>` (elementy `i8`…`u64`, `f32`, `f64`; N = 2…64; najwyżej 512 bitów)
i zwraca `Type(kind: "vec", elem: [element], count: N)`, czyli wektor LLVM `<N x T>`. Parser przyjmuje taką nazwę
wszędzie tam, gdzie może stać typ; wywołanie `f32x8(…)` trafia do `vecMake()`.
| Finch | IR |
|---|---|
| `f32x8(x)` | `insertelement` do elementu 0 + `shufflevector … zeroinitializer` (rozpowielenie) |
| `f32x8(a, …, h)` | ciąg `insertelement` |
| `f32x8(lista, i)` | sprawdzenie zakresu `i … i+7`, potem `load <8 x float>, ptr …, align 4` (wyrównanie elementu) |
| `f32x8(i32x8 v)` | `sitofp`, `fptosi`, `sext`, `trunc`… na całym wektorze |
| `v + w`, `v * 2.0` | `fadd`/`add`… na wektorach; argument skalarny jest dopasowywany do typu elementu i rozpowielany |
| `v[i]` | miejsce w pamięci: `getelementptr <element>, ptr %v, i64 i` po sprawdzeniu zakresu; albo `extractelement` na wartości tymczasowej |
| `v < w`, `v == w` … | `fcmp`/`icmp` → `<N x i1>`, maska (`boolxN`: `vec` z elementami `bool`) |
| `select(m, a, b)` | `select <N x i1>` |
| `m.any()`, `.all()`, `.count()` | `llvm.vector.reduce.or`/`.and`; `zext` + `llvm.vector.reduce.add` |
| `f32x8(lista, i, wypelnienie)`, `v.store_part(lista, i)` | `llvm.masked.load`/`llvm.masked.store` z maską `<0, 1, …> < len - i` |
| `v.shuffle(…)`, `.lo()`, `.hi()`, `concat(a, b)` | `shufflevector` ze stałą maską |
| `tabela.lookup(idx)` | `and` z `len - 1`, potem `extractelement`/`insertelement` dla każdego elementu: wzorzec, który zaplecza x86 i ARM zamieniają na `pshufb`/`tbl` |
| `acc.dot_add(a, b)` | `llvm.x86.avx512.vpdpbusd.{128,256,512}` albo `llvm.aarch64.neon.{usdot,sdot,udot}`, gdy funkcja ma te rozszerzenia; w przeciwnym razie `zext`/`sext`, `mul`, cztery `shufflevector` co czwarty element i `add` |
| `min`, `max`, `abs` | `llvm.minnum`/`maxnum`, `llvm.smin`/`umin`…, `llvm.fabs`/`llvm.abs` |
| `v.sum()`, `.min()`, `.max()` | `llvm.vector.reduce.fadd` (z `reassoc`), `.add`, `.fmin`, `.smin`… |
| `v.sqrt()`, `.floor()`, `.ceil()`, `.round()` | funkcja wewnętrzna `llvm.*` o tej nazwie |
| `v.fma(b, c)`, `fma(a, b, c)` | `llvm.fma` (jedno zaokrąglenie; bez sprzętowego FMA wywołanie libm dla każdego elementu) |
| `v.muladd(b, c)`, `muladd(a, b, c)` | `llvm.fmuladd` (jedna instrukcja tam, gdzie procesor ma FMA, a gdzie nie, `fmul` + `fadd`) |
| `v.store(lista, i)` | sprawdzenie zakresu, `store <8 x float> …, align 4` |
Dzielenie całkowite `/` i `%` jest odrzucane: ani x86, ani ARM nie mają instrukcji dzielącej liczby całkowite element
po elemencie, więc LLVM i tak rozbiłby je na pojedyncze dzielenia. Porównania kolejności są odrzucane, bo ich wynikiem
byłaby maska, dla której Finch nie ma typu.
**Wyrównanie.** LLVM daje `<8 x float>` wyrównanie ABI równe 32 (jego rozmiarowi), a `malloc` i finchowe `alloca … align 16`
gwarantują tylko 16. `llty()` zapisuje każdy wektor szerszy niż 16 bajtów w `g.wide`, a `resolveStructBody` zapisuje tam
strukturę, która taki wektor zawiera; `load()` i `store()` dodają dla tych typów `, align 16`. Bez tego LLVM mógłby
użyć `vmovaps` na adresie wyrównanym do 16 i program by się wysypał.
**Jakie instrukcje** wybiera clang: `--cpu <nazwa>` dodaje `-march=<nazwa>` (x86-64) albo `-mcpu=<nazwa>` (ARM64)
do kompilacji IR, więc to samo IR staje się kodem SSE2, AVX2 + FMA, AVX-512 albo NEON. Wektory szersze niż rejestry
procesora są dzielone na części.
Niektóre działania muszą znać rozszerzenia procesora już przy generowaniu kodu (`dot_add` wybiera `vpdpbusd` tylko tam,
gdzie ono istnieje). `linker.cpuFeatures()` pyta clanga o makra, które sam definiuje dla tego samego systemu i `--cpu`
(`clang -march=X -dM -E pusty.c`: `__AVXVNNI__`, `__AVX512VNNI__`, `__ARM_FEATURE_MATMUL_INT8` …), zamienia je na nazwy
rozszerzeń Fincha i zapamiętuje odpowiedź dla danego clanga i flag; do `gen` trafiają jako `Options.features`. `@cpu("…")`
przy funkcji (`Node.cpu`) dodaje własne: `define … "target-features"="+avx2,+fma"` (z `+evex512` przy każdym rozszerzeniu
AVX-512, którego LLVM 18+ wymaga dla rejestrów 512-bitowych), a `g.curFeatures` to suma obu na czas generowania tej
funkcji. Rozszerzenia drugiej architektury są pomijane (`cpuOf`). `checkCpuCall` nie pozwala przekazać wektora szerszego
niż 128 bitów jako parametru albo wyniku między funkcjami o różnym `@cpu`, bo wywołujący i wywoływany umieściliby go
w innych rejestrach. `cpu_has(nazwa)` woła `finch_cpu_has` z runtime'u: na x86 CPUID (liście 1 i 7) z XGETBV dla stanu
AVX/AVX-512, na Linuksie ARM64 `getauxval(AT_HWCAP/AT_HWCAP2)`, na macOS `sysctlbyname("hw.optional.arm.FEAT_…")`.
Na Uniksach clang jest zawsze uruchamiany jako `NIX_ENFORCE_NO_NATIVE=0 clang …`: inaczej nakładka cc-wrapper z Niksa
usuwa `-march=native` w `nix-shell`. Jeśli `--cpu native` mimo to nie daje na x86 makra SSE4.2, sterownik ostrzega.
---
## 8. Własność i pamięć
### 9.1 Model
### 8.1 Model
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma
dokładnie jednego właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`.
Właściciel zwalnia wartość, gdy sam znika. Nie ma liczenia referencji ani garbage collectora;
każda decyzja zapada statycznie.
Każda wartość **typu posiadającego pamięć** (`str`, `[]T` albo struktura Fincha z takim polem) ma dokładnie jednego
właściciela: zmienną, element tablicy, pole struktury albo komórkę na stercie z `new()`. Właściciel zwalnia wartość,
gdy sam znika. Nie ma liczenia referencji ani garbage collectora; wszystkie decyzje zapadają przy kompilacji.
| Sytuacja | Co się dzieje |
|---|---|
| `x := expr` / `T x = expr` / `x = expr` / pole, element, `push` | **`own(v)`**: wartość fresh jest przenoszona; pożyczona jest kopiowana w głąb |
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, stara zwalniana po zapisie |
| koniec bloku, `return`, `break`, `continue` | posiadane zmienne każdego opuszczanego zasięgu są zwalniane (od najnowszej), uruchamiają się defery |
| `return lokalna` | zmienna jest **przenoszona na zewnątrz** (oznaczana jako `moved` na czas sprzątania tego returna) |
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; argumenty fresh zwalnia wywołujący po wywołaniu |
| parametr, który funkcja zmienia | kopiowany na wejściu i posiadany przez funkcję (decyduje `mutates()`, niżej) |
| wartość tymczasowa, której nikt nie trzyma (`print(a + b)`, `f(g())`, instrukcja-wyrażenie) | **`release(v)`**: zwalniana zaraz po użyciu |
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (fresh) |
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało może zmieniać `lista`, elementy są kopiowane |
| wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest jego adres): bez kopii; zmiany widzi wywołujący |
| mapy | są właścicielami kluczy i wartości tak jak tablice elementów; `m[k] = v` przenosi/kopiuje `v` i zwalnia starą wartość |
| `x := wyr` / `T x = wyr` / `x = wyr` / pole, element, `push` | **`own(v)`**: wartość świeża zostaje przeniesiona, pożyczona jest głęboko kopiowana |
| `x = …` (typ posiadający) | najpierw liczona jest nowa wartość, a stara jest zwalniana po zapisie |
| koniec bloku, `return`, `break`, `continue` | zmienne we wszystkich opuszczanych zasięgach są zwalniane (od najnowszej), wykonują się `defer` |
| `return zmienna_lokalna` | zmienna jest **przenoszona** (oznaczona jako `moved` na czas sprzątania przy tym `return`) |
| przekazanie argumentu | **pożyczka**: funkcja dostaje płytką kopię i nigdy jej nie zwalnia; świeże argumenty zwalnia wywołujący po wywołaniu |
| parametr, który funkcja zmienia | kopiowany na wejściu i należący do funkcji (decyduje `mutates()`, niżej) |
| wartość tymczasowa, której nikt nie zatrzymuje (`print(a + b)`, `f(g())`, wyrażenie jako instrukcja) | **`release(v)`**: zwalniana zaraz po użyciu |
| `a.pop()`, `a.remove(i)` | element jest przenoszony na zewnątrz (świeży) |
| `for x in lista` | `x` pożycza kolejne elementy; jeśli ciało pętli może zmienić `lista`, elementy są kopiowane |
| wywołanie metody `v.m()` | `self` **to jest** `v` (przekazywany jest adres): bez kopii, zmiany widzi wywołujący |
| mapy | są właścicielami kluczy i wartości tak jak tablice elementów; `m[k] = v` przenosi albo kopiuje `v` i zwalnia starą wartość |
Literał tekstowy to wartość fresh z `cap = 0`: przeniesienie nic nie kosztuje, zwalnianie jest pomijane
Literał tekstowy to świeża wartość z `cap = 0`: przeniesienie go nic nie kosztuje, zwalnianie jest pomijane
(`release()` ignoruje stałe), a zapis do niego (`s[i] = c`) najpierw robi kopię na stercie (`finch_str_own`).
Teksty z C są pożyczane z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
Teksty z C są pożyczone z `cap = -1` i zawsze kopiowane, zanim zostaną zatrzymane.
### 9.2 Pożyczanie parametrów: `mutates()`
### 8.2 Pożyczanie parametrów: `mutates()`
Przy deklarowaniu funkcji każdy parametr posiadający pamięć jest sprawdzany przez `mutates(nazwa, ciało)`:
przypisanie, którego zmienna-korzeń (`rootVar()`: przez `.pole` i `[i]`) jest tym parametrem, metoda
zmieniająca (`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane
parametry są pożyczane (`Fn::borrowParam`), zmieniane kopiowane raz na wejściu. Analiza jest składniowa
i zachowawcza: wszystko, co *może* zmienić parametr, powoduje kopię, co zawsze jest poprawne.
Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
Przy deklarowaniu funkcji każdy parametr typu posiadającego jest sprawdzany przez `mutates(nazwa, cialo)`: przypisanie,
którego zmienną bazową (`rootVar()`: przez `.pole` i `[i]`) jest ten parametr, zmieniająca metoda na nim
(`push pop insert remove clear resize sort reverse`) albo `addr(…)` na nim. Niezmieniane parametry są pożyczane
(`FnSig.borrow`); zmieniane są kopiowane raz, na wejściu. Analiza jest składniowa i ostrożna: wszystko, co *mogłoby*
zmienić parametr, powoduje kopię, a to zawsze jest poprawne. Ta sama analiza decyduje, czy `for x in lista` musi kopiować elementy.
Metody biorą w tym udział: `declareFns()` najpierw ustala, które metody zmieniają `self`
(`mutates("self", ciało)`), powtarzając, aż nic się nie zmieni, bo wywołanie zmieniającej metody na `self`
też zmienia `self`. Ich nazwy trafiają do `mutatingMethods`, które `mutatesExpr()` traktuje jak `push`:
`p.przesun(1)` zmienia `p`. Dopiero potem liczone jest `borrowParam` dla każdej funkcji i metody.
Metody też w tym uczestniczą: `declare()` najpierw ustala, które metody zmieniają `self` (`mutates("self", cialo)`),
powtarzając to, dopóki coś się zmienia, bo wywołanie zmieniającej metody na `self` też zmienia `self`. Ich nazwy trafiają
do `g.mm`, które `mutates()` traktuje jak `push`: `p.przesun(1)` zmienia `p`. Dopiero potem dla każdej funkcji i metody
liczone jest `borrow`.
### 9.3 Kopiowanie i zwalnianie
### 8.3 Kopiowanie i zwalnianie
`dropAt(addr, T)` / `copyAt(dst, src, T)` działają na adresach:
`dropAt(adres, T)` / `copyAt(cel, zrodlo, T)` działają na adresach:
- `str`: `finch_str_drop` (zwalnia, gdy `cap > 0`) / `finch_str_copy` (literały współdzieli, resztę duplikuje).
- `[]T` i struktury posiadające: generowane helpery `finch.drop.<klucz>` i `finch.copy.<klucz>`, tworzone
raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Gracz`). Tablice zwalniają każdy posiadający
element, a potem `finch_arr_free`; kopie wołają `finch_arr_clone_raw`, a potem kopiują w głąb posiadające
elementy na surowe bity. Struktury zwalniają/kopiują tylko pola posiadające.
- `[]T` i struktury posiadające pamięć: wygenerowane funkcje pomocnicze `finch.drop.<klucz>` i `finch.copy.<klucz>`,
tworzone raz na typ (`dropFn`, `copyFn`, klucz z `typeKey()`, np. `arr_S_main_Player`). Tablica zwalnia każdy
element posiadający pamięć, a potem woła `finch_arr_free`; kopia woła `finch_arr_clone_raw`, a potem głęboko kopiuje
takie elementy w miejsce surowych bitów. Struktury zwalniają i kopiują tylko pola posiadające pamięć.
Helpery są emitowane we własnych funkcjach (`HelperScope` zapamiętuje pozycję buildera i wyłącza lokacje
debugowe), mogą wołać się rekurencyjnie (`struct Wezel { []Wezel dzieci }`) i są wstawiane (inline) przez O2.
Funkcje pomocnicze powstają jako osobne funkcje (`beginHelper()` / `endHelper()` odkładają na bok funkcję, która jest
w trakcie pisania, i wyłączają lokalizacje debuggera), mogą wołać się nawzajem rekurencyjnie (`struct Node { []Node kids }`),
a O2 je wkleja w miejsce wywołań.
### 9.4 Jak to wygląda
### 8.4 Jak to wygląda
```c
fn greet(str name) -> str {
msg := "Hi, " + name
return msg
fn przywitaj(str imie) -> str {
wiad := "Cześć, " + imie
return wiad
}
fn main() {
a := ["x"]
b := a
b.push(greet("Ola"))
b.push(przywitaj("Ola"))
print(b[1])
}
```
IR funkcji `main` z `-O0` (skrócony):
IR funkcji `main` przy `-O0` (w skrócie):
```llvm
%3 = call ptr @finch_alloc(i64 24) ; ["x"]: jeden element na stercie
store { ptr, i64, i64 } { ptr @str.1, i64 1, i64 0 }, ptr %4 ; literał, cap 0
...
store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (fresh)
store { ptr, i64, i64 } %7, ptr %a ; przeniesione do a (świeże)
%a1 = load { ptr, i64, i64 }, ptr %a
store { ptr, i64, i64 } %a1, ptr %1
call void @finch.copy.arr_str(ptr %2, ptr %1) ; b := a kopiuje
%8 = load { ptr, i64, i64 }, ptr %2
store { ptr, i64, i64 } %8, ptr %b
%9 = call { ptr, i64, i64 } @finch.greet({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
%9 = call { ptr, i64, i64 } @finch.przywitaj({ ptr, i64, i64 } { ptr @str.2, i64 3, i64 0 })
...
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększ, jeśli trzeba
store { ptr, i64, i64 } %9, ptr %15 ; wynik greet przeniesiony, bez kopii
call void @finch_arr_reserve(ptr %b, i64 %12, i64 24) ; push: powiększenie, jeśli trzeba
store { ptr, i64, i64 } %9, ptr %15 ; wynik przywitaj przeniesiony, bez kopii
...
%20 = icmp uge i64 1, %19 ; b[1]: kontrola zakresu
%20 = icmp uge i64 1, %19 ; b[1]: sprawdzenie zakresu
br i1 %20, label %out.of.range, label %in.range
...
call void @finch.drop.arr_str(ptr %b) ; koniec main: od najnowszej
@ -374,103 +447,116 @@ IR funkcji `main` z `-O0` (skrócony):
ret void
```
a `greet` zwraca `msg` bez kopiowania (`ret … %5` po wczytaniu slotu: zmienna została przeniesiona).
a `przywitaj` zwraca `wiad` bez kopiowania (`ret … %5` po wczytaniu zmiennej: została przeniesiona).
### 9.5 defer
### 8.5 defer
`defer instrukcja` dokłada do bieżącego zasięgu `Cleanup{isDefer, body, order = bieżące varOrder}`
i niczego w tym miejscu nie emituje. Za każdym razem, gdy emitowane jest sprzątanie zasięgu (dojście do
końca bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **w tym wyjściu**, z
`deferLimit = order`, żeby `lookup()` ukrył zmienne zadeklarowane po defer: te są wtedy już zwolnione
(sprzątanie idzie w odwrotnej kolejności). `return`/`break`/`continue` w instrukcji defer to błędy (`inDefer`).
Ciała defer są więc powielane na każdą ścieżkę wyjścia, przez co na zwykłej ścieżce nic nie kosztują.
`defer instrukcja` zapisuje instrukcję w bieżącym zasięgu (`Scope.defers`, razem z `orders`: ile zmiennych wtedy
istniało) oraz `Cleanup{isDefer}`; w tym miejscu nic nie emituje. Ilekroć emitowane jest sprzątanie zasięgu (koniec
bloku, `return`, wyjście przez `break`/`continue`), ciało jest generowane **przy tym wyjściu**, z `deferLimit`
ustawionym tak, by `lookup()` ukrywało zmienne zadeklarowane po `defer`: w tym momencie są już zwolnione (sprzątanie
idzie od końca). `return`/`break`/`continue` wewnątrz odroczonej instrukcji to błąd (`inDefer`). Ciała `defer` są więc
powielane na każdą drogę wyjścia i dlatego na zwykłej ścieżce nic nie kosztują.
### 9.6 Pamięć ręczna
### 8.6 Pamięć zarządzana ręcznie
`new(v)` alokuje `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia
wskazywaną wartość, jeśli `T` posiada pamięć (pomijając null), i woła `free` z libc. Wskaźniki nigdy
niczego nie posiadają: `addr(x)` może wisieć po końcu bloku `x`; nie jest to sprawdzane.
`new(v)` przydziela `sizeof(T)` przez `finch_alloc`, zapisuje `own(v)` i zwraca `ptr[T]`. `free(p)` zwalnia to, na co
wskazuje wskaźnik, jeśli `T` posiada pamięć (pomija `null`), i woła `free` z libc. Wskaźniki nigdy niczego nie posiadają:
`addr(x)` może wskazywać w próżnię po końcu bloku `x`; tego się nie sprawdza.
### 9.7 Weryfikacja
### 8.7 Sprawdzanie
`MEMCHECK=1 tests/run.sh` buduje każdy program testowy i uruchamia go pod valgrindem z
`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero nieprawidłowych dostępów we wszystkich,
w tym przy returnach z wnętrza pętli, `continue`/`break` z posiadanymi zmiennymi, deferach, modyfikacji
podczas iteracji, zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
`--leak-check=full --errors-for-leak-kinds=all`: zero wycieków i zero błędnych dostępów we wszystkich, w tym przy
`return` z wnętrza pętli, `continue`/`break` ze zmiennymi posiadającymi pamięć, `defer`, zmianach w trakcie iteracji,
zagnieżdżonych tablicach struktur i przekazywaniu struktur C.
---
---
## 9. Typy generyczne, enumy i match
## 9. Generyki, enumy i match
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/`.
Jak są generowane, opisuje §4. Reguły widoczne dla użytkownika są w rozdziałach 23 i 24
[przewodnika dla techników](dla-technikow.md). Testy to `tests/new/generics.fch`, `tests/new/enums.fch` i oczekiwane
błędy w `tests/new/fail/`.
---
## 10. Bootstrap
Kompilator budowany jest z **ziarna**: własnego IR zapisanego jako `bootstrap/<system>.ll` (zobacz
[bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh`:
Kompilator powstaje z **pliku startowego** (ang. *seed*): jego własnego IR, zapisanego w repozytorium jako
`bootstrap/<system>.ll` (zob. [bootstrap/README.md](../../bootstrap/README.md)). `bootstrap/build.sh` robi:
```
ziarno .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch
plik .ll ──clang──► finch0 ──buduje src/──► finch1 ──buduje src/──► build/finch
```
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.
i przerywa, jeśli `finch1` i `finch` nie dają identycznego bajt w bajt IR (punkt stały: kompilator kompiluje sam siebie
tak samo, niezależnie od tego, co go zbudowało). `bootstrap/build.sh seed` zapisuje plik startowy na nowo z bieżącego
kompilatora; trzeba to zrobić, gdy kod kompilatora zaczyna używać czegoś, czego zapisany plik startowy nie zna.
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.
Plik startowy jest osobny dla każdego systemu, bo zawiera konwencję wywołań C dla funkcji libclang (§5). Są pliki
dla Linuksa x86-64 i ARM64, macOS ARM64 i x86-64 (zrobione z nagłówkami SDK Apple) oraz Windowsa x86-64 (z nagłówkami
llvm-mingw). CI na każdym z tych systemów buduje z nich kompilator i uruchamia tam testy.
---
## 11. Serwer języka
`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:
`finch lsp` rozmawia przez JSON-RPC na stdin/stdout (ramki `Content-Length`, `std/json`). Sam nie sprawdza typów:
przy każdej zmianie uruchamia `finch index plik.fch --overlay sciezka=kopia …`, gdzie nakładki to niezapisany tekst
z edytora. W trybie `index` kompilator zamiast IR wypisuje rekordy rozdzielone tabulatorami:
| Rekord | Znaczenie |
|---|---|
| `@R plik linia kol dł plikDef liniaDef kolDef podpowiedź` | odwołanie: co pokazuje hover i gdzie jest definicja |
| `@R plik linia kol dlug plikDef liniaDef kolDef dymek` | odwołanie: co pokazać w dymku 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 |
| `@F modul nazwa odbiorca plik linia kol koniec sygnatura parametry` | funkcja albo metoda |
| `@S` / `@SF`, `@E`, `@I` | struktury (i pola), enumy, importy |
| `@W` | ostrzeżenie |
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).
Pierwszy błąd jest wypisywany jako `sciezka:linia:kol: error: komunikat` i staje się diagnostyką. Gdy kod się nie
kompiluje, serwer nadal podpowiada uzupełnienia i parametry na podstawie ostatniej wersji, która się kompilowała.
Testem akceptacyjnym jest `tests/lsp_test.py`.
---
## 12. Semantyka a C
## 12. Semantyka w porównaniu z C
| Sytuacja | C | Finch 2.0 |
| Sytuacja | C | Finch |
|---|---|---|
| Przepełnienie liczby ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli stałe, inaczej błąd w czasie działania |
| Indeks tablicy poza zakresem | UB | błąd w czasie działania (także dla pól `[N]T` struktur C) |
| Dereferencja null | UB | błąd w czasie działania dla `.value`, `.pole`, `[i]` |
| Przepełnienie liczby całkowitej ze znakiem | UB | zdefiniowane zawinięcie (bez `nsw`) |
| Dzielenie całkowite przez zero, `INT_MIN / -1` | UB | błąd kompilacji, jeśli to stała; w przeciwnym razie błąd w trakcie działania |
| Indeks tablicy poza zakresem | UB | błąd w trakcie działania (także dla pól `[N]T` struktur C) |
| Dereferencja `null` | UB | błąd w trakcie działania dla `.value`, `.pole`, `[i]` |
| Użycie po zwolnieniu / podwójne zwolnienie tablic, tekstów, struktur | częste błędy | niemożliwe: tych nie zwalnia się ręcznie |
| Wyciek tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
| Wyciek pamięci tablic, tekstów, struktur | częsty błąd | niemożliwy bez `new` |
| `x & 1 == 0` | `x & (1 == 0)` | `(x & 1) == 0` |
| Niejawne zwężanie, mieszanie ze znakiem / bez znaku | po cichu | błędy kompilacji |
| Niezainicjalizowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
| Brak return | UB, jeśli użyty | błąd kompilacji |
| Kolejność obliczeń | w większości nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
| Przesunięcie ≥ szerokość, float→int poza zakresem | UB | nadal poison (jeszcze niesprawdzane) |
| Niejawne zwężanie, mieszanie liczb ze znakiem i bez | po cichu | błędy kompilacji |
| Niezainicjowane zmienne | UB | niemożliwe (zero albo wartości domyślne) |
| Brak `return` | UB, jeśli wynik jest użyty | błąd kompilacji |
| Kolejność obliczania | w dużej mierze nieokreślona | od lewej do prawej; w przypisaniu najpierw prawa strona |
| Przesunięcie o ≥ szerokość, liczba zmiennoprzecinkowa poza zakresem przy zamianie na całkowitą | UB | poison, jak w LLVM (niesprawdzane) |
| `a * b + c` na liczbach zmiennoprzecinkowych | może zostać złożone w FMA (`-ffp-contract`) | nigdy nie jest łączone: dwa zaokrąglenia |
### Liczby zmiennoprzecinkowe
Działania na liczbach zmiennoprzecinkowych to zwykłe `fadd`/`fsub`/`fmul`/`fdiv`/`fneg` **bez flag fast-math**, więc LLVM
nie może zmieniać kolejności działań, łączyć ich ani zakładać braku NaN; `linker.fch` kompiluje też IR z
`-ffp-contract=on`, które łączy tylko wywołania `llvm.fmuladd`, o które prosi Finch. Jedyna instrukcja z flagą to
`llvm.vector.reduce.fadd` w `v.sum()` (`reassoc`). `fma` → `llvm.fma`, `muladd` → `llvm.fmuladd`, na liczbach i wektorach
(`fmaCall`, `fusedOp`).
`f32_bits`/`f32_from_bits`/`f64_…` to `bitcast` (także między wektorami). `f16_to_f32` to `bitcast i16 → half` + `fpext`,
`f32_to_f16` to `fptrunc` + `bitcast`: jedna instrukcja z F16C albo na ARM64, a gdzie indziej `__extendhfsf2`/`__truncsfhf2`
z biblioteki uruchomieniowej kompilatora C. `bf16_to_f32` to `zext` + `shl 16` + `bitcast`; `f32_to_bf16` zaokrągla do
najbliższej parzystej na liczbach całkowitych (`u + 0x7fff + ((u >> 16) & 1)`, potem `>> 16`) i zostawia NaN cichym
(`| 0x40`), więc nie potrzebuje żadnej biblioteki.
| Wiszący wskaźnik z `addr()` / `new` + `free` | UB | nadal UB |
---
---
### Łańcuch wywołań przy błędach w czasie działania
### Ścieżka wywołań przy błędach w trakcie działania
Z `Options.trace` (domyślnie dla `run`, `test`, `-g`, `-O0`; `--trace`/`--no-trace`; nigdy z `--shared`) każda funkcja
Fincha ma ramkę na swoim stosie:
Fincha ma na stosie ramkę:
```llvm
%frame = alloca { ptr, ptr, ptr, i64 } ; ramka wywołującego, nazwa, plik, linia
@ -479,45 +565,44 @@ store ptr @"nazwa", ...pole 1 ; ptr @"plik", pole 2 ; i64 <linia>, pole 3
store ptr %frame, ptr @finch_trace
```
`@finch_trace` to globalna `thread_local` w module programu (model initial-exec, chyba że biblioteka albo Windows);
każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący wpisuje z powrotem swoją
ramkę do `@finch_trace`, co sprząta też ramki funkcji, która wróciła wcześniej, więc wiele ścieżek `return` nie
potrzebuje epilogu. Opakowania wołane z C (thunki `addr(fn)`, `export fn`) zapamiętują i przywracają `@finch_trace`.
`main` woła `finch_trace_init(top, set)` z dwiema małymi funkcjami do czytania i zapisu, i tak runtime (`print_trace()`
w `fatal()` oraz uruchamianie testów) dostaje się do zmiennej wątku, której nie zna z nazwy. Koszt: ok. 3 razy na
`fib(40)`, który jest samymi wywołaniami; dlatego zwykły `finch build` go pomija.
`@finch_trace` to globalna zmienna `thread_local` w module programu (model initial-exec, chyba że to biblioteka
albo Windows); każda instrukcja zapisuje swoją linię do pola 3. Po każdym wywołaniu funkcji Fincha wywołujący zapisuje
z powrotem swoją ramkę do `@finch_trace`, co przy okazji usuwa ramki pozostawione przez funkcję, która wyszła wcześniej,
więc na licznych ścieżkach powrotu nie trzeba nic sprzątać. Opakowania wywoływane z C (`addr(fn)`, `export fn`)
zapamiętują i odtwarzają `@finch_trace` wokół wywołania. `main` woła `finch_trace_init(top, set)` z dwiema małymi
funkcjami, które ją czytają i zapisują; tak runtime (`print_trace()` w `fatal()` i uruchamianie testów) dostaje się do
zmiennej `thread_local`, której nie potrafi nazwać. Koszt: ok. 3 razy wolniej na `fib(40)`, które składa się wyłącznie
z wywołań; dlatego zwykłe `finch build` tego nie dodaje.
### Wątki i operacje atomowe
`std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo
`CreateThread` i `CRITICAL_SECTION` na Windowsie). Funkcja wątku to dowolna `fn f(ptr)` podana przez `addr(f)`, co robi
thunk wołany z C. `atomic_add/sub/swap` to `atomicrmw add/sub/xchg … seq_cst`, `atomic_load/store` to
`load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst` zwracające `i1`. Biorą
`ptr[T]` do dowolnej liczby całkowitej. Tablice, teksty i mapy Fincha nie są synchronizowane; dzielenie ich między
wątkami wymaga mutexu.
`std/thread.fch` to cienka warstwa nad runtime'em (`finch_thread_spawn/join`, `finch_mutex_*`: pthreads albo, na
Windowsie, `CreateThread` i `CRITICAL_SECTION`). Funkcja wątku to dowolna `fn f(ptr)`, przekazywana przez `addr(f)`,
co tworzy opakowanie wywoływalne z C. `atomic_add/sub/swap` stają się `atomicrmw add/sub/xchg … seq_cst`,
`atomic_load/store` to `load/store atomic … seq_cst, align <rozmiar>`, a `atomic_cas` to `cmpxchg … seq_cst seq_cst`
zwracające `i1`. Przyjmują `ptr[T]` do dowolnego typu całkowitego. Tablice, teksty i mapy Fincha nie są synchronizowane;
dzielenie ich między wątkami wymaga muteksu.
### Ostrzeżenia
`warn()` wypisuje `plik:linia:kolumna: warning: …` na stderr (więc `finch ir` daje samo IR) albo, w trybie `index`,
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno na miejsce w kodzie. Są dwa: parametr-struktura oraz
element `for x in lista`, gdy ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone struktury, `[N]T` z C; `str`, tablice
i mapy liczą się jako 0, bo kopiowany jest tylko ich uchwyt) mają 256 bajtów lub więcej.
`warn()` wypisuje `plik:linia:kol: warning: …` na stderr (żeby `finch ir` pozostało czystym IR) albo, w trybie `index`,
rekord `@W`, który serwer języka publikuje z ważnością 2. Jedno ostrzeżenie na miejsce w kodzie. Są dwa rodzaje:
parametr będący strukturą oraz element w `for x in lista`, jeśli ich zwykłe dane (`byteSize()`: liczby, zagnieżdżone
struktury, `[N]T` z C; `str`, tablice i mapy liczą się jako 0, bo kopiuje się tylko ich uchwyt) mają co najmniej 256 bajtów.
### Struktura jako argument przez adres
### Struktury przekazywane przez adres
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr to `ptr[S]`, a argument to zwykła zmienna typu `S`,
argumentem jest adres zmiennej, jakby napisano `addr(x)`. Tylko zmienne, więc nic nie jest liczone dwa razy, a na
wartości tymczasowe nic nie wskazuje.
`argWant()` (tylko argumenty wywołań, nie przypisania): gdy parametr ma typ `ptr[S]`, a argument jest zwykłą zmienną
typu `S`, argumentem staje się adres tej zmiennej, tak jakby napisano `addr(x)`. Dotyczy to tylko zmiennych, więc nic
nie jest obliczane dwa razy, a wskaźnik nigdy nie wskazuje na wartość tymczasową.
---
## 13. Znane ograniczenia
- 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.
- Pamięć nieudanego testu nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
- `--target android` jest sprawdzany do pliku obiektowego; linkowanie wymaga NDK i nie było uruchamiane na urządzeniu.
- Jeden błąd na uruchomienie, bez kontynuowania po nim.
- Informacje dla debuggera (`-g`) są najlepsze z `-O0`.
- Typy generyczne są monomorfizowane (kod rośnie z każdą instancją); nie ma ograniczeń na parametry typu, więc złe
użycie daje błąd wewnątrz ciała instancji.
- Pamięć, którą miał nieudany test, nie jest zwalniana (uruchamianie testów wychodzi z niego przez `longjmp`).
- Wektory nie mają dzielenia całkowitego, a `lookup` na podstawowym x86-64 (bez SSSE3) działa element po elemencie.

View file

@ -1,8 +1,8 @@
# Finch dla początkujących
**Ten przewodnik jest dla ciebie, jeśli jeszcze nigdy nie pisałeś programu.**
Nie potrzebujesz żadnego doświadczenia ani matematyki ponad to, czego uczy się w podstawówce.
Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. Tak zostaje w głowie najlepiej.
**Programowanie to dla ciebie coś zupełnie nowego? Ten przewodnik jest dla ciebie.**
Nie musisz nic umieć: wystarczy matematyka ze szkoły podstawowej.
Czytaj spokojnie, rozdział po rozdziale, i każdy przykład przepisuj sam: tak najlepiej zostaje w głowie.
> 🇬🇧 English version: [for-beginners.md](../en/for-beginners.md)
@ -13,7 +13,7 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T
1. [Co to jest program?](#1-co-to-jest-program)
2. [Przygotowanie Fincha](#2-przygotowanie-fincha)
3. [Twój pierwszy program](#3-twój-pierwszy-program)
4. [Wypisywanie na ekran](#4-wypisywanie-na-ekran)
4. [Wypisywanie na ekranie](#4-wypisywanie-na-ekranie)
5. [Pudełka z nazwami: zmienne](#5-pudełka-z-nazwami-zmienne)
6. [Rodzaje wartości](#6-rodzaje-wartości)
7. [Liczenie](#7-liczenie)
@ -28,36 +28,35 @@ Czytaj powoli, po jednym rozdziale, i przepisuj każdy przykład samodzielnie. T
16. [Kiedy coś pójdzie nie tak](#16-kiedy-coś-pójdzie-nie-tak)
17. [Małe projekty do zrobienia](#17-małe-projekty-do-zrobienia)
18. [Ściąga](#18-ściąga)
19. [Słowniczek](#19-słowniczek)
19. [Słowa, które będziesz słyszeć](#19-słowa-które-będziesz-słyszeć)
---
## 1. Co to jest program?
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu się każe,
krok po kroku.
Komputer jest bardzo szybki, ale nie myśli. Robi dokładnie to, co mu każesz, krok po kroku.
**Program** to spisana lista takich kroków. Trochę jak przepis kuchenny:
**Program** to spisana lista takich kroków. Trochę jak przepis:
> 1. Weź dwa jajka.
> 2. Wbij je do miski.
> 3. Wymieszaj.
**Język programowania** to sposób zapisania takiego przepisu, który komputer rozumie.
**Finch** (po polsku: zięba) jest jednym z takich języków. Zrobiono go tak, żeby był
mały i prosty, więc nauczysz się go szybko.
**Język programowania** to sposób zapisania takiego przepisu tak, żeby komputer go zrozumiał.
**Finch** to jeden z takich języków. Powstał po to, żeby był mały i prosty, więc szybko się go nauczysz.
Komputer czyta program od góry do dołu, linijka po linijce.
Komputer czyta program od góry do dołu, linia po linii.
---
## 2. Przygotowanie Fincha
Finch działa na komputerach z **Linuksem**. Jeśli nauczyciel, rodzic albo znajomy już go
dla ciebie zainstalował, przejdź od razu do następnego rozdziału.
Finch działa na komputerach z **Linuksem**, **macOS** i **Windowsem**. Jeśli nauczyciel, rodzic albo znajomy
już go dla ciebie przygotował, przejdź do następnego rozdziału. (Na Macu i na Windowsie poproś kogoś o skorzystanie
z [przewodnika dla techników](dla-technikow.md#2-instalacja): są tam gotowe wersje Fincha na oba systemy.)
W przeciwnym razie otwórz program **Terminal** (okienko, w którym wpisuje się polecenia)
i wpisz te linijki po kolei, naciskając **Enter** po każdej:
W przeciwnym razie otwórz program **Terminal** (okno, w którym wpisuje się polecenia) i wpisz po kolei te linie,
naciskając po każdej **Enter**:
```sh
git clone https://github.com/Nexoniarz/Finch.git
@ -66,24 +65,24 @@ nix-shell
bootstrap/build.sh
```
Jeśli ostatnia linijka skończy się bez słowa `error`, Finch jest gotowy.
(Jeśli komputer nie zna polecenia `nix-shell`, poproś kogoś o skorzystanie z
[przewodnika dla techników](dla-technikow.md#2-instalacja), gdzie opisano inny sposób.)
Jeśli ostatnie polecenie skończy się bez słowa `error`, Finch jest gotowy.
(Jeśli na twoim komputerze nie ma `nix-shell`, poproś kogoś o skorzystanie z
[przewodnika dla techników](dla-technikow.md#2-instalacja). Jest tam opisany inny sposób.)
Żeby sprawdzić, czy działa, wpisz:
Żeby sprawdzić, czy wszystko działa, wpisz:
```sh
./build/finch version
```
Powinieneś zobaczyć coś w stylu `finch 2.5.0`.
Powinien pojawić się napis w rodzaju `Finch 2.6.0`.
---
## 3. Twój pierwszy program
1. Otwórz dowolny edytor tekstu (na przykład *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
2. Wpisz dokładnie to:
1. Otwórz dowolny edytor tekstu (np. *Edytor tekstu*, *Kate*, *gedit* albo *VS Code*).
2. Przepisz dokładnie to:
```c
fn main() {
@ -91,11 +90,11 @@ fn main() {
}
```
3. Zapisz plik w folderze `Finch` pod nazwą **`czesc.fch`**. Końcówka `.fch` mówi, że to program w Finchu.
3. Zapisz plik w katalogu `Finch` pod nazwą **`hello.fch`**. Końcówka `.fch` mówi, że to program w Finchu.
4. W Terminalu wpisz:
```sh
./build/finch run czesc.fch
./build/finch run hello.fch
```
Zobaczysz:
@ -104,7 +103,7 @@ Zobaczysz:
Cześć!
```
🎉 **Właśnie napisałeś i uruchomiłeś program.**
🎉 **Właśnie udało ci się napisać i uruchomić program.**
### Co właściwie napisaliśmy?
@ -114,26 +113,26 @@ fn main() { ← „tu zaczyna się główna część mojego programu
} ← „tu główna część się kończy”
```
- `fn main()` to miejsce, od którego **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
- `fn main()` to miejsce, w którym **zaczyna się** każdy program w Finchu. Każdy program ma dokładnie jedno `main`.
- Nawiasy klamrowe `{` i `}` obejmują wszystko, co należy do `main`, jak dwie okładki książki.
- `print(...)` pokazuje coś na ekranie.
- Tekst zawsze piszemy w cudzysłowie: `"Cześć!"`.
- Tekst zawsze stoi w cudzysłowie: `"Cześć!"`.
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku
klamer. Zawsze je dodawaj, to dobry nawyk.
Spacje przed `print` nie są obowiązkowe, ale dzięki nim od razu widać, co jest w środku nawiasów.
Zawsze je dodawaj. To dobry nawyk.
---
## 4. Wypisywanie na ekran
## 4. Wypisywanie na ekranie
`print` umie pokazać tekst i liczby. Między kolejne rzeczy wstaw przecinek, a Finch sam doda spację:
`print` potrafi pokazać tekst i liczby. Oddziel je przecinkami, a Finch sam wstawi między nimi spacje:
```c
fn main() {
print("Mam", 10, "lat")
print("2 + 2 =", 2 + 2)
print()
print("Linijka wyżej jest pusta")
print("Linia wyżej jest pusta")
}
```
@ -143,10 +142,10 @@ Pokaże:
Mam 10 lat
2 + 2 = 4
Linijka wyżej jest pusta
Linia wyżej jest pusta
```
Każdy `print` zaczyna nową linijkę. `print()` bez niczego w środku robi pustą linijkę.
Każde `print` zaczyna nową linię. `print()` bez niczego w środku robi pustą linię.
### Notatki dla siebie: komentarze
@ -163,8 +162,8 @@ fn main() {
## 5. Pudełka z nazwami: zmienne
Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a potem sprawdzić,
co jest w środku, czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
Wyobraź sobie pudełko z naklejoną etykietą. Możesz coś do niego włożyć, a później sprawdzić, co jest w środku,
czytając etykietę. W programowaniu takie pudełko nazywa się **zmienną**.
```c
fn main() {
@ -173,9 +172,9 @@ fn main() {
}
```
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”*.
`wiek := 10` znaczy: *„zrób nowe pudełko, nazwij je `wiek` i włóż do niego 10”.*
Później możesz zmienić zawartość pudełka, używając samego `=`:
Później możesz zmienić zawartość pudełka, pisząc samo `=`:
```c
fn main() {
@ -196,42 +195,39 @@ Punkty: 5
Punkty: 6
```
`punkty = punkty + 1` czytamy tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”*.
To samo można zapisać krócej: `punkty += 1`.
`punkty = punkty + 1` czyta się tak: *„weź to, co jest w `punkty`, dodaj 1 i włóż wynik z powrotem do `punkty`”.*
To samo można napisać krócej: `punkty += 1`.
**Zapamiętaj:**
- `:=` robi **nowe** pudełko. Używasz tego tylko raz dla danej nazwy.
- `:=` robi **nowe** pudełko. Dla każdej nazwy używasz tego tylko raz.
- `=` wkłada nową wartość do pudełka, **które już istnieje**.
### Nazywanie pudełek
Nazwy mogą zawierać angielskie litery `a`–`z` i `A`–`Z`, cyfry i znak `_`, ale nie mogą zaczynać się od cyfry:
`wiek`, `suma_punktow`, `gracz2` są w porządku. `2gracz` już nie.
⚠️ **Bez polskich liter w nazwach!** `ą`, `ę`, `ł`, `ż` i inne mogą być tylko w tekście w cudzysłowie.
Pisz `zolw`, a nie `żółw`. Natomiast `print("Żółw")` działa bez problemu.
W nazwach można używać liter od `a` do `z` i od `A` do `Z`, cyfr i znaku `_`, ale nazwa nie może zaczynać się od cyfry:
`wiek`, `suma_punktow`, `gracz2` są w porządku, a `2gracz` nie.
Polskich liter (`ą`, `ę`, `ż`…) nie wolno używać w nazwach, tylko w tekstach w cudzysłowie: `print("Łódź")` jest w porządku.
Wybieraj nazwy, które mówią, co jest w środku: `cena` jest lepsza niż `c`.
---
## 6. Rodzaje wartości
Finch pilnuje, *jakiego rodzaju* rzecz jest w każdym pudełku. Ten rodzaj nazywa się **typem**.
Finch pilnuje, jaki *rodzaj* rzeczy leży w każdym pudełku. Taki rodzaj to **typ**.
Najczęściej będziesz używać tych:
| Typ | Co przechowuje | Przykłady |
|---------|---------------------------|--------------------------|
| `int` | liczby całkowite | `0`, `42`, `-7` |
| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` |
| `str` | tekst | `"Cześć"`, `"Ola"` |
| `bool` | tak albo nie | `true` (prawda), `false` (fałsz) |
| `char` | jeden znak | `'A'`, `'z'`, `'?'` |
| Typ | Co przechowuje | Przykłady |
|---------|-----------------------------|--------------------------|
| `int` | liczby całkowite | `0`, `42`, `-7` |
| `float` | liczby z przecinkiem | `3.14`, `0.5`, `-2.0` |
| `str` | tekst | `"Cześć"`, `"Ola"` |
| `bool` | tak albo nie | `true`, `false` |
| `char` | jedną literę albo znak | `'A'`, `'z'`, `'?'` |
⚠️ W liczbach zamiast przecinka piszemy **kropkę**: `3.14`, a nie `3,14`. Tak jest w prawie wszystkich językach programowania.
Uwaga: w programach część ułamkową oddziela się **kropką**, nie przecinkiem: `3.14`, a nie `3,14`.
Kiedy używasz `:=`, Finch sam rozpoznaje typ:
Przy `:=` Finch sam rozpoznaje typ:
```c
imie := "Ola" // str
@ -240,7 +236,7 @@ wzrost := 1.42 // float
lubi_koty := true // bool
```
Typ możesz też napisać sam, przed nazwą:
Typ można też napisać samemu, przed nazwą:
```c
int wiek = 10
@ -249,24 +245,23 @@ str imie = "Ola"
Oba sposoby robią to samo. Wybierz ten, który ci bardziej pasuje.
**Pudełko nie zmienia typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
Finch zatrzyma się i powie o tym, co pomaga wcześnie wyłapywać pomyłki.
**Pudełko trzyma się swojego typu.** Jeśli w `wiek` są liczby całkowite, nie włożysz tam potem tekstu.
Finch się zatrzyma i ci o tym powie, a to pomaga wcześnie wyłapywać pomyłki.
---
## 7. Liczenie
| Znak | Co robi | Przykład | Wynik |
|------|-----------------------------|----------|-------|
| `+` | dodawanie | `7 + 2` | `9` |
| `-` | odejmowanie | `7 - 2` | `5` |
| `*` | mnożenie | `7 * 2` | `14` |
| `/` | dzielenie | `7 / 2` | `3` |
| `%` | reszta z dzielenia | `7 % 2` | `1` |
| Znak | Znaczenie | Przykład | Wynik |
|------|--------------------------------|----------|--------|
| `+` | dodawanie | `7 + 2` | `9` |
| `-` | odejmowanie | `7 - 2` | `5` |
| `*` | mnożenie | `7 * 2` | `14` |
| `/` | dzielenie | `7 / 2` | `3` |
| `%` | reszta z dzielenia | `7 % 2` | `1` |
**Uwaga na dzielenie liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`. Obie liczby są
całkowite, więc wynik też jest całkowity (część po kropce jest odrzucana).
Jeśli chcesz dostać `3.5`, użyj liczby z kropką: `7.0 / 2`.
**Uważaj przy dzieleniu liczb całkowitych:** `7 / 2` daje `3`, a nie `3.5`, bo obie liczby są całkowite, więc wynik
też jest całkowity (część po kropce znika). Jeśli chcesz `3.5`, użyj liczby z kropką: `7.0 / 2`.
Mnożenie i dzielenie wykonują się przed dodawaniem i odejmowaniem, tak jak w szkole.
Nawiasy zmieniają kolejność:
@ -276,28 +271,28 @@ print(2 + 3 * 4) // 14
print((2 + 3) * 4) // 20
```
`%` przydaje się do sprawdzania, czy liczba jest parzysta. Liczba parzysta dzielona przez 2
nie daje reszty, więc `n % 2` wynosi `0`.
`%` przydaje się do sprawdzania, czy liczba jest parzysta: liczba parzysta dzielona przez 2 nie daje reszty,
więc `n % 2` wynosi `0`.
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linijce to się stało.
**Nie wolno dzielić przez zero.** Jeśli program spróbuje, zatrzyma się i powie, w której linii to się stało.
---
## 8. Podejmowanie decyzji: if
Program może wybierać, co zrobić. `if` (czyt. „if”, po polsku „jeżeli”) znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
Program może wybierać, co zrobić. `if` znaczy *„zrób to tylko wtedy, gdy coś jest prawdą”*.
```c
fn main() {
temperatura := 25
if temperatura > 20 {
print("Jest ciepło, załóż koszulkę")
print("Jest ciepło, włóż koszulkę")
}
}
```
Dodaj `else` („w przeciwnym razie”):
Dopisz `else`, żeby powiedzieć *„a jeśli nie, zrób to”*:
```c
if temperatura > 20 {
@ -307,12 +302,11 @@ if temperatura > 20 {
}
```
A `else if` dodaje kolejne możliwości. Finch sprawdza je od góry i wykonuje tylko
**pierwszą** prawdziwą:
A `else if` daje więcej możliwości. Finch sprawdza je od góry do dołu i wykonuje tylko **pierwszą**, która jest prawdą:
```c
if temperatura > 25 {
print("Gorąco!")
print("Upał!")
} else if temperatura > 15 {
print("Przyjemnie")
} else {
@ -328,18 +322,18 @@ if temperatura > 25 {
| `!=` | nie jest równe |
| `<` | jest mniejsze niż |
| `>` | jest większe niż |
| `<=` | jest mniejsze lub równe |
| `>=` | jest większe lub równe |
| `<=` | jest mniejsze albo równe |
| `>=` | jest większe albo równe |
⚠️ **Jedno `=` wkłada wartość do pudełka. Dwa `==` pytają „czy to jest równe?”.**
Tekst też można porównywać: `if imie == "Ola" { ... }`.
Porównywać można też teksty: `if imie == "Ola" { ... }`.
### Łączenie pytań
- `&&` znaczy **i**: oba warunki muszą być prawdziwe. `if wiek > 6 && wiek < 12 { ... }`
- `||` znaczy **lub**: wystarczy, że jeden jest prawdziwy. `if dzien == "sob" || dzien == "nd" { ... }`
- `!` znaczy **nie**: odwraca prawdę i fałsz. `if !pada { ... }`
- `||` znaczy **lub**: przynajmniej jeden musi być prawdziwy. `if dzien == "sob" || dzien == "niedz" { ... }`
- `!` znaczy **nie**: zamienia prawdę w fałsz i odwrotnie. `if !pada { ... }`
---
@ -365,7 +359,7 @@ Liczba 4
Liczba 5
```
`for i in 1..6` znaczy: *„licz za pomocą `i` od 1 do 6, ale **bez** 6”*.
`for i in 1..6` znaczy: *„licz w `i` od 1 do 6, ale **bez** 6”*.
Czyli `0..10` daje dziesięć liczb: 0, 1, 2 … 9.
### Powtarzanie, dopóki coś jest prawdą: `while`
@ -388,8 +382,8 @@ fn main() {
Start!
```
⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się
w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać.
⚠️ Upewnij się, że pętla może się skończyć. Gdyby `licznik` nigdy nie malał, pętla kręciłaby się bez końca.
Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**, żeby ją zatrzymać.
### Wcześniejsze wyjście i pomijanie
@ -399,9 +393,9 @@ w nieskończoność. Jeśli tak się stanie, naciśnij w Terminalu **Ctrl + C**,
```c
for i in 1..100 {
if i == 5 {
break // stop, gdy dojdziemy do 5
break // zatrzymaj się, gdy dojdziemy do 5
}
print(i) // pokaże 1 2 3 4
print(i) // wypisze 1 2 3 4
}
```
@ -409,7 +403,7 @@ for i in 1..100 {
## 10. Własne polecenia: funkcje
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany kawałek kodu to **funkcja**.
Kiedy robisz to samo w wielu miejscach, nadaj temu nazwę. Taki nazwany blok to **funkcja**.
Jedną już znasz: `main`.
```c
@ -428,12 +422,12 @@ Cześć, Ola
Cześć, Tomek
```
`str imie` w nawiasie znaczy: *„ta funkcja potrzebuje jednego tekstu, a w środku nazywamy go `imie`”*.
To, co wpiszesz w nawias przy wywołaniu, zostanie przekazane do środka.
`str imie` w nawiasach znaczy: *„ta funkcja potrzebuje jednego tekstu i w środku będziemy go nazywać `imie`”.*
To, co wpiszesz w nawiasach przy jej użyciu, trafia do środka.
### Funkcje, które oddają odpowiedź
### Funkcje, które dają odpowiedź
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj słowem `return`:
Napisz `->` i typ odpowiedzi, a odpowiedź oddaj przez `return`:
```c
fn dodaj(int a, int b) -> int {
@ -446,14 +440,14 @@ fn main() {
}
```
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad czy pod `main`.
Nie ma znaczenia, gdzie w pliku umieścisz swoje funkcje, nad `main` czy pod nim.
---
## 11. Listy: wiele wartości w jednym pudełku
Czasem jedno pudełko nie wystarczy. Na liście zakupów jest wiele rzeczy.
**Lista** (programiści mówią też **tablica**) trzyma wiele wartości po kolei:
Czasem jedno pudełko nie wystarcza. Na liście zakupów jest wiele rzeczy.
**Lista** (programiści mówią też **tablica**) przechowuje wiele wartości po kolei:
```c
fn main() {
@ -470,34 +464,34 @@ Mam 3 owoce
Pierwszy to jabłko
```
- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddzielamy przecinkami.
- Nawiasy kwadratowe `[ ]` tworzą listę. Wartości oddziela się przecinkami.
- `.len` mówi, ile rzeczy jest na liście.
- `owoce[0]` to **pierwsza** rzecz. Liczymy od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia.
- `owoce[0]` to **pierwsza** rzecz. Liczy się od **0**, więc `owoce[1]` to druga, a `owoce[2]` trzecia.
Jeśli zapytasz o miejsce, którego nie ma, np. `owoce[10]`, program się zatrzyma i powie:
`index 10 is out of range (the length is 3)`, czyli „indeks 10 jest poza zakresem (długość to 3)”.
`index 10 is out of range (the length is 3)` (indeks 10 jest poza listą, która ma długość 3).
### Dodawanie i zabieranie
```c
fn main() {
[]int wyniki // pusta lista liczb całkowitych
wyniki.push(10) // dołóż 10 na koniec
wyniki.push(10) // dodaj 10 na końcu
wyniki.push(25)
wyniki.push(7)
print(wyniki) // [10, 25, 7]
ostatni := wyniki.pop() // zabierz ostatni
ostatni := wyniki.pop() // zdejmij ostatni
print(ostatni, wyniki) // 7 [10, 25]
wyniki.sort() // posortuj
wyniki.sort()
print(wyniki)
}
```
### Przechodzenie po liście
`for ... in` odwiedza każdą wartość po kolei:
`for ... in` odwiedza po kolei każdą wartość:
```c
fn main() {
@ -509,8 +503,8 @@ fn main() {
}
```
Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, daj `for` dwie nazwy: pierwsza liczy
0, 1, 2…, druga to wartość:
Jeśli chcesz też wiedzieć, *w którym miejscu* listy jesteś, podaj w `for` dwie nazwy: pierwsza liczy 0, 1, 2…,
a druga to wartość:
```c
fn main() {
@ -526,21 +520,20 @@ fn main() {
3 wiśnia
```
Listy umieją więcej: `contains` (czy zawiera), `find` (znajdź), `insert` (wstaw), `remove` (usuń),
`sort` (sortuj), `reverse` (odwróć), `join` (połącz). Wszystkie opisuje [przewodnik dla techników](dla-technikow.md#10-tablice-i-mapy).
Listy potrafią więcej: `contains`, `find`, `insert`, `remove`, `sort`, `reverse`, `join`.
Wszystkie są opisane w [przewodniku dla techników](dla-technikow.md#10-tablice-i-mapy).
### Szukanie po nazwie: mapy
Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem chcesz znaleźć coś po **nazwie**, tak jak
słowo w słowniku albo numer w książce telefonicznej. Do tego służy **mapa** (`map`): każdy
**klucz** (nazwa) ma swoją **wartość**:
Lista znajduje rzeczy po miejscu: `owoce[0]`. Czasem jednak chcesz szukać po **nazwie**, tak jak szuka się słowa
w słowniku albo numeru w książce telefonicznej. Do tego służy **mapa**: każdy **klucz** (nazwa) ma swoją **wartość**:
```c
fn main() {
telefon := ["Ola": "555-1234", "Jan": "555-9876"]
print(telefon["Ola"]) // 555-1234
telefon["Ewa"] = "555-0000" // nowa osoba
telefon["Ewa"] = "555-0000" // dodaj nowe imię
print(telefon.len) // 3
if telefon.has("Tomek") {
@ -549,26 +542,26 @@ fn main() {
print("nie ma Tomka")
}
for imie, numer in telefon { // każde imię z jego numerem
for imie, numer in telefon { // każde imię razem z numerem
print(imie, numer)
}
}
```
- `["klucz": wartość, ...]` tworzy mapę. Klucze to zwykle tekst, ale mogą być też liczby.
- `["klucz": wartość, ...]` tworzy mapę. Kluczami są często teksty, ale mogą być też liczby.
- `telefon["Ola"]` daje wartość dla klucza. Pytanie o klucz, którego nie ma, zatrzymuje program,
więc najpierw sprawdź `telefon.has("Tomek")` (czy ma) albo użyj `telefon.get("Tomek", "nie wiem")`.
- `for imie, numer in telefon` odwiedza każdy klucz z wartością, w kolejności dodawania.
więc najpierw sprawdź `telefon.has("Tomek")` albo użyj `telefon.get("Tomek", "nieznany")`.
- `for imie, numer in telefon` odwiedza każdy klucz z jego wartością, w kolejności, w jakiej je dodano.
Mapy świetnie nadają się do **liczenia**. Nowy klucz zaczyna od 0, więc `+= 1` po prostu działa:
```c
fn main() {
map[str]int glosy // pusta mapa: klucze to tekst, wartości to liczby
map[str]int glosy // pusta mapa: klucze to teksty, wartości to liczby całkowite
for g in ["kot", "pies", "kot", "kot", "rybka"] {
glosy[g] += 1
}
print(glosy) // {"kot": 3, "pies": 1, "rybka": 1}
print(glosy) // {"kot": 3, "pies": 1, "rybka": 1}
}
```
@ -576,7 +569,7 @@ fn main() {
## 12. Zabawa tekstem
Teksty sklejasz znakiem `+`:
Teksty można sklejać przez `+`:
```c
fn main() {
@ -600,16 +593,16 @@ fn main() {
slowo := "Finch"
print(slowo.len) // 5 liter
print(slowo[0]) // F (pierwsza litera)
print(slowo.upper()) // FINCH (wielkimi literami)
print(slowo.lower()) // finch (małymi literami)
print(slowo.contains("in")) // true (czy zawiera "in")
print(slowo.upper()) // FINCH
print(slowo.lower()) // finch
print(slowo.contains("in")) // true
for litera in slowo {
print(litera) // F, i, n, c, h, każda w osobnej linijce
print(litera) // F, i, n, c, h, każda w osobnej linii
}
}
```
⚠️ `.len` liczy bajty, a polskie litery (ą, ę, ż…) zajmują po 2 bajty. `"żółw".len` to 7, a nie 4.
(`.len` liczy bajty, a polskie litery zajmują po dwa, więc `"żółw".len` to 7, a nie 4.)
---
@ -634,15 +627,15 @@ fn main() {
}
```
Jeśli ktoś wpisze coś, co nie jest liczbą, program zatrzyma się z komunikatem
w stylu `can't turn "abc" into int` („nie da się zamienić "abc" na int”).
Jeśli ktoś wpisze coś, co nie jest liczbą, program się zatrzyma z komunikatem
w rodzaju `can't turn "abc" into int` (nie da się zamienić "abc" na liczbę).
---
## 14. Własne rodzaje wartości: struktury
Wyobraź sobie kartę w bibliotece: każda karta ma tytuł, autora i rok.
**Struktura** (`struct`) pozwala zrobić własny rodzaj wartości z nazwanymi częściami:
Pomyśl o karcie w bibliotece: każda ma tytuł, autora i rok wydania.
**Struktura** pozwala stworzyć własny rodzaj wartości z nazwanymi częściami:
```c
struct Ksiazka {
@ -664,11 +657,11 @@ Hobbit napisał Tolkien
Ksiazka(tytul: "Hobbit", autor: "Tolkien", rok: 1938)
```
- `struct Ksiazka { ... }` opisuje, co ma każda książka. Pisz to **poza** `main`.
- `struct Ksiazka { ... }` opisuje, co ma każda książka. Umieść to **poza** `main`.
- `Ksiazka(tytul: ..., autor: ..., rok: ...)` tworzy jedną książkę.
- `k.tytul` odczytuje jedną część, a `k.rok = 1938` ją zmienia.
- `k.tytul` odczytuje jedną część. `k.rok = 1938` ją zmienia.
Częściom możesz nadać wartość początkową. Wtedy nie trzeba ich podawać:
Częściom można nadać wartość początkową. Wtedy nie trzeba ich wypełniać:
```c
struct Gracz {
@ -682,12 +675,12 @@ fn main() {
}
```
Możesz też trzymać wiele struktur na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
Wiele takich wartości można trzymać na liście: `[]Ksiazka polka`, a potem `polka.push(k)`.
### Polecenia twojej struktury: metody
### Polecenia dla struktury: metody
Struktura może mieć własne polecenia (**metody**). Napisz nazwę struktury, kropkę i nazwę polecenia.
W środku `self` (ang. „sam”) to struktura, na której użyto polecenia:
Struktura może mieć własne polecenia. Napisz nazwę struktury, kropkę i nazwę polecenia.
W środku `self` to struktura, na której polecenie użyto:
```c
struct Gracz {
@ -697,7 +690,7 @@ struct Gracz {
fn Gracz.trafiony() {
self.zycia -= 1
print(self.imie, "ma jeszcze", self.zycia, "życia")
print(self.imie, "- pozostałe życia:", self.zycia)
}
fn Gracz.zyje() -> bool {
@ -706,8 +699,8 @@ fn Gracz.zyje() -> bool {
fn main() {
g := Gracz(imie: "Ola")
g.trafiony() // Ola ma jeszcze 2 życia
g.trafiony() // Ola ma jeszcze 1 życia
g.trafiony() // Ola - pozostałe życia: 2
g.trafiony() // Ola - pozostałe życia: 1
print(g.zyje()) // true
}
```
@ -731,14 +724,14 @@ fn main() {
- `read_file(nazwa)` oddaje tekst z pliku.
- `file_exists(nazwa)` mówi, czy plik istnieje (`true` albo `false`).
Plik pojawi się w folderze, w którym uruchomiłeś program.
Plik pojawi się w katalogu, w którym uruchomiono program.
---
## 16. Kiedy coś pójdzie nie tak
Każdy się myli przy programowaniu, nawet eksperci, codziennie.
Finch stara się tłumaczyć, co poszło nie tak, prostymi słowami (na razie po angielsku). Przykład:
Każdy, kto programuje, robi błędy, nawet eksperci, codziennie.
Finch bardzo się stara wyjaśnić prostymi słowami, co poszło nie tak. Przykład:
```c
fn main() {
@ -748,68 +741,67 @@ fn main() {
```
```
czesc.fch:3:11: error: there is no variable named 'wiekk'
hello.fch:3:11: error: there is no variable named 'wiekk'
3 | print(wiekk)
| ^
```
Jak to czytać:
- `czesc.fch` to plik,
- `3` to numer **linijki**,
- `11` to który znak w linijce (**kolumna**),
- potem wyjaśnienie, a pod nim ta linijka ze strzałką `^` wskazującą problem.
- `hello.fch` to plik,
- `3` to numer **linii**,
- `11` mówi, jak daleko w linii (to **kolumna**),
- dalej jest wyjaśnienie, a pod nim ta linia ze znakiem `^`, który wskazuje problem.
Tutaj po prostu źle napisaliśmy `wiek`. Komunikat znaczy: „nie ma zmiennej o nazwie 'wiekk'”.
Komunikaty są po angielsku; tutaj znaczy to „nie ma zmiennej o nazwie 'wiekk'”. Po prostu literówka w `wiek`.
### Częste błędy
### Częste pomyłki
| Finch mówi | Co to znaczy | Jak naprawić |
|---|---|---|
| `there is no variable named 'x'` | Nie ma zmiennej `x`: literówka albo zapomniałeś ją zrobić | Sprawdź pisownię. Utwórz ją przez `x := ...` |
| `a variable named 'x' already exists here` | Użyłeś `:=` dwa razy dla tej samej nazwy | Za drugim razem użyj `=` |
| `there is no variable named 'x'` | Literówka w nazwie albo takie pudełko nigdy nie powstało | Sprawdź pisownię. Utwórz je przez `x := ...` |
| `a variable named 'x' already exists here` | Użyto `:=` dwa razy dla tej samej nazwy | Za drugim razem napisz `=` |
| `'x' must be int, but this is str` | Zły rodzaj wartości dla tego pudełka | Włóż wartość właściwego rodzaju |
| `put each statement on its own line` | Dwa polecenia w jednej linijce | Wciśnij Enter między nimi |
| `put each statement on its own line` | Dwa polecenia w jednej linii | Rozdziel je Enterem |
| `this '{' is never closed` | Brakuje `}` | Dopisz `}` |
| `a condition must be bool` | `if` potrzebuje pytania tak/nie | Porównaj coś: `if x > 0` |
| `unexpected character 'ż'` | Polska litera w nazwie | Zmień na zwykłą literę: `zolw` |
| `runtime error: division by zero` | Program podzielił przez 0 w trakcie działania | Sprawdź liczbę przed dzieleniem |
| `index 5 is out of range (the length is 3)` | Zapytałeś listę o miejsce, którego nie ma | Pamiętaj, że liczymy od 0; sprawdź `.len` |
| `can't turn "abc" into int` | `int(...)` dostał tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik |
| `can't use '+' on str and int` | Tekst plus liczba | Zamień liczbę na tekst przez `str(...)` |
| `'f' can fail, so say what happens then` | Użyłeś polecenia, które może się nie udać, i nie napisałeś, co wtedy | Dopisz `or ...` (niżej) |
| `a condition must be bool` | `if` potrzebuje pytania typu tak/nie | Porównaj coś: `if x > 0` |
| `runtime error: division by zero` | W trakcie działania programu nastąpiło dzielenie przez 0 | Sprawdź liczbę przed dzieleniem |
| `index 5 is out of range (the length is 3)` | Pytanie listy o miejsce, którego nie ma | Pamiętaj, że liczy się od 0; sprawdź `.len` |
| `can't turn "abc" into int` | `int(...)` dostało tekst, który nie jest liczbą | Sprawdź, co wpisał użytkownik |
| `can't use '+' on str and int` | Tekst plus liczba | Użyj `str(...)` na liczbie |
| `'f' can fail, so say what happens then` | Użyto polecenia, które może się nie udać, bez powiedzenia, co wtedy zrobić | Dopisz `or ...` (zob. niżej) |
### Rzeczy, które mogą się nie udać: `or` i `try`
Niektóre rzeczy mogą pójść źle *w trakcie działania programu*: ktoś wpisze `abc` zamiast liczby
albo pliku nie ma. Zamiast zatrzymywać program, możesz powiedzieć, co wtedy zrobić, słowem **`or`** („albo”):
Niektóre rzeczy mogą się nie udać *w trakcie działania programu*: ktoś wpisze `abc` tam, gdzie miała być liczba,
albo nie ma pliku. Zamiast zatrzymywać program, możesz przez **`or`** powiedzieć, co wtedy zrobić:
```c
fn main() {
wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0
wiek := int(input("Ile masz lat? ")) or 0 // to nie liczba? weź 0
print("Za rok będziesz mieć", wiek + 1)
notatka := read_file("notatka.txt") or "(jeszcze brak notatki)"
notatka := read_file("notatka.txt") or "(jeszcze nie ma notatki)"
print(notatka)
}
```
Po `or` może też być blok. W środku `err` to komunikat, co poszło nie tak:
Po `or` może też stać blok. W jego środku `err` to komunikat, który mówi, co poszło nie tak:
```c
fn main() {
while true {
n := int(input("Podaj liczbę: ")) or {
n := int(input("Wybierz liczbę: ")) or {
print("To nie jest liczba, spróbuj jeszcze raz")
continue
}
print("Wybrałeś", n)
print("Wybrana liczba:", n)
break
}
}
```
Twoje własne funkcje też mogą się nie udać. Dopisz `!` po typie wyniku i użyj `return error("...")`:
Twoje własne funkcje też mogą się nie udać. Postaw `!` po typie wyniku i użyj `return error("...")`:
```c
fn bezpieczne_dzielenie(int a, int b) -> int! {
@ -825,15 +817,15 @@ fn main() {
}
```
Finch nie pozwoli ci zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który
przypomina, żeby to obsłużyć. (Można też przekazać problem dalej przez `try`; wyjaśnia to
Finch nie pozwoli o tym zapomnieć: wywołanie `bezpieczne_dzielenie(10, 0)` bez `or` to błąd, który każe ci to obsłużyć.
(Problem można też przekazać dalej przez `try`; wyjaśnia to
[przewodnik dla techników](dla-technikow.md#błędy-jako-wartości).)
---
## 17. Małe projekty do zrobienia
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero, gdy utkniesz.
Spróbuj najpierw napisać każdy sam. Do rozwiązania zajrzyj dopiero wtedy, gdy utkniesz.
### Projekt 1: Tabliczka mnożenia
@ -852,7 +844,7 @@ fn main() {
### Projekt 2: Parzysta czy nieparzysta
Dla każdej liczby od 1 do 10 napisz, czy jest parzysta, czy nieparzysta.
Dla każdej liczby od 1 do 10 pokaż, czy jest parzysta, czy nieparzysta.
<details><summary>Rozwiązanie</summary>
@ -871,7 +863,7 @@ fn main() {
### Projekt 3: Stopnie Celsjusza na Fahrenheita
Pokaż tabelkę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
Pokaż tabelę od 0 °C do 100 °C co 10 stopni. Wzór: `F = C * 9 / 5 + 32`.
<details><summary>Rozwiązanie</summary>
@ -893,8 +885,8 @@ fn main() {
### Projekt 4: Fizz Buzz
Policz od 1 do 20. Dla liczb podzielnych przez 3 napisz `Fizz`, przez 5 napisz `Buzz`,
przez obie naraz napisz `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
Policz od 1 do 20. Dla liczb podzielnych przez 3 powiedz `Fizz`, przez 5 `Buzz`,
przez obie `FizzBuzz`, a w pozostałych przypadkach samą liczbę.
<details><summary>Rozwiązanie</summary>
@ -917,7 +909,7 @@ fn main() {
### Projekt 5: Największa z trzech
Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która oddaje największą z trzech liczb.
Napisz funkcję `najwieksza(int a, int b, int c) -> int`, która zwraca największą liczbę.
<details><summary>Rozwiązanie</summary>
@ -940,7 +932,8 @@ fn main() {
### Projekt 6: Zgadnij liczbę
Komputer ma tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem mówi „więcej” albo „mniej”.
Komputer wybiera tajną liczbę. Gracz zgaduje, aż trafi, a komputer za każdym razem
mówi „więcej” albo „mniej”.
<details><summary>Rozwiązanie</summary>
@ -956,7 +949,7 @@ fn main() {
} else if strzal > tajna {
print("Mniej!")
} else {
print("Tak! Potrzebowałeś", proby, "prób")
print("Tak! Liczba prób:", proby)
return
}
}
@ -968,7 +961,7 @@ fn main() {
### Projekt 7: Średnia ocen
Zapytaj o 3 oceny, zapisz je na liście, a potem pokaż listę i średnią.
Zapytaj o 3 oceny, zapisz je na liście i pokaż listę oraz średnią.
<details><summary>Rozwiązanie</summary>
@ -998,9 +991,9 @@ fn main() {
fn main() { // tu zaczyna się program
print("tekst", 42) // pokaż coś
x := 5 // nowe pudełko (typ zgadnięty)
x := 5 // nowe pudełko (typ rozpoznany sam)
int y = 10 // nowe pudełko (typ podany)
x = 7 // zmień zawartość
x = 7 // zmiana zawartości pudełka
x += 1 // dodaj 1 do x
if x > 5 { // decyzja
@ -1020,72 +1013,49 @@ fn main() { // tu zaczyna się program
}
}
fn dodaj(int a, int b) -> int { // własne polecenie
fn dodaj(int a, int b) -> int { // twoje własne polecenie
return a + b
}
struct Zwierzak { // własny rodzaj wartości
struct Zwierzak { // twój własny rodzaj wartości
str imie
int wiek = 1
}
// listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { }
// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, lata in wiek { }
// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
// listy: liczby := [1, 2, 3] liczby.push(4) liczby[0] liczby.len for n in liczby { } for i, n in liczby { }
// mapy: wiek := ["Ola": 9] wiek["Jan"] = 10 wiek["Ola"] wiek.has("Tomek") for imie, w in wiek { }
// tekst: "a" + "b" str(42) int("42") s.len s.upper() s.contains("x")
// pytanie: imie := input("Imię? ")
// pliki: write_file("p.txt", tekst) read_file("p.txt")
// metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij()
// pliki: write_file("p.txt", tekst) read_file("p.txt")
// metody: fn Zwierzak.urosnij() { self.wiek += 1 } z.urosnij()
// porażki: n := int(tekst) or 0 x := read_file("p.txt") or { print(err) ... }
```
---
## 19. Słowniczek
## 19. Słowa, które będziesz słyszeć
| Słowo | Znaczenie |
|---|---|
| **program** | Lista kroków dla komputera |
| **kod** | Tekst programu |
| **uruchomić** | Kazać komputerowi wykonać kroki |
| **kompilować** | Przetłumaczyć kod na coś, co komputer umie uruchomić. `finch run` robi to za ciebie |
| **zmienna** | Pudełko z nazwą, w którym jest wartość |
| **skompilować** | Przetłumaczyć kod na coś, co komputer potrafi uruchomić. `finch run` robi to za ciebie |
| **zmienna** | Pudełko z nazwą, w którym leży wartość |
| **typ** | Rodzaj wartości: liczba, tekst, tak/nie… |
| **funkcja** | Nazwana grupa kroków, której można używać wiele razy |
| **pętla** | Kroki, które się powtarzają |
| **warunek** | Pytanie tak/nie, np. `x > 5` |
| **warunek** | Pytanie typu tak/nie, np. `x > 5` |
| **lista / tablica** | Wiele wartości po kolei, np. `[1, 2, 3]` |
| **indeks** | Pozycja wartości na liście, liczona od 0 |
| **struktura** (struct) | Własny rodzaj wartości złożony z nazwanych części |
| **indeks** | Miejsce wartości na liście, liczone od 0 |
| **struktura** | Twój własny rodzaj wartości z nazwanych części |
| **metoda** | Polecenie należące do struktury, np. `g.trafiony()` |
| **mapa** (map) | Wartości znajdowane po kluczu (nazwie), jak w słowniku |
| **wejście** (input) | To, co użytkownik wpisuje do programu |
| **błąd** (error) | Komunikat, że coś jest nie tak, i gdzie |
| **mapa** | Wartości wyszukiwane po kluczu (nazwie), jak w słowniku |
| **wejście** (ang. *input*) | To, co ktoś wpisuje do programu |
| **błąd** (ang. *error*) | Komunikat, że coś jest nie tak, i gdzie |
| **bug** | Pomyłka w programie |
### Słówka po angielsku, które spotkasz w Finchu
| Słowo | Po polsku |
|---|---|
| `print` | wypisz |
| `if` / `else` | jeżeli / w przeciwnym razie |
| `for` … `in` | dla … w |
| `while` | dopóki |
| `break` / `continue` | przerwij / kontynuuj |
| `return` | zwróć (oddaj) |
| `true` / `false` | prawda / fałsz |
| `fn` (od *function*) | funkcja |
| `struct` | struktura |
| `push` / `pop` | dołóż / zabierz |
| `input` | wejście (pytanie) |
| `len` (od *length*) | długość |
| `error` | błąd |
| `or` | albo (co zrobić, gdy się nie uda) |
| `try` | spróbuj (a jak się nie uda, przekaż błąd dalej) |
| `self` | sam (struktura, na której użyto metody) |
| `map` | mapa (słownik) |
| `has` / `get` | ma / weź |
---
**Co dalej?** Kiedy to wszystko wyda ci się łatwe, przejdź do
**Co dalej?** Kiedy to wszystko będzie już łatwe, przejdź do
[przewodnika dla techników](dla-technikow.md). Opisuje każdą część Fincha.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
# Ściąga Fincha
# Ściąga z Fincha
Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.pl.md#dokumentacja).
Cały Finch na jednej stronie. Wyjaśnienia znajdziesz w [przewodnikach](../../README.pl.md#dokumentacja).
> 🇬🇧 English version: [cheatsheet.md](../en/cheatsheet.md)
@ -8,39 +8,41 @@ Cały Finch na jednej stronie. Wyjaśnienia są w [przewodnikach](../../README.p
## Polecenie `finch`
| Polecenie | Robi |
| Polecenie | Co robi |
|---|---|
| `finch run plik.fch [argumenty…]` | skompiluj i uruchom (argumenty trafiają do programu) |
| `finch build plik.fch [-o nazwa]` | zrób program (`nazwa.exe` na Windowsie) |
| `finch build plik.fch --shared` | zrób bibliotekę (`libplik.so`, `plik.dll`) dla programu w C; jej `export fn` to jej funkcje C |
| `finch test plik.fch` | uruchom jego bloki `test "…" { }`, każdy osobno (także z importowanych modułów) |
| `finch fmt [--check] [pliki/foldery]` | wcięcia wg `{ }` po 4 spacje, bez spacji na końcach linii, najwyżej jedna pusta linia (`--check`: tylko wypisz, co by zmienił) |
| `finch run` / `build` / `test` bez pliku | w folderze z `finch.toml`: jego `entry` |
| `finch ir plik.fch` | pokaż LLVM IR |
| `finch run plik.fch [argumenty…]` | kompiluje i uruchamia (argumenty trafiają do programu) |
| `finch build plik.fch [-o nazwa]` | tworzy program (na Windowsie `nazwa.exe`) |
| `finch build plik.fch --shared [-I katalog]` | tworzy bibliotekę (`libplik.so`, `plik.dll`), którą wczyta program w C; jej funkcjami C są funkcje `export fn` (liczby, bool, char, wskaźniki) |
| `finch test plik.fch` | uruchamia każdy blok `test "…" { }` osobno (także w importowanych modułach) |
| `finch fmt [--check] [pliki/katalogi]` | wcięcia po 4 spacje na każdy poziom `{ }`, bez spacji na końcach linii, najwyżej jedna pusta linia z rzędu (`--check`: tylko wypisuje, co by zmienił) |
| `finch run` / `build` / `test` bez pliku | w katalogu z `finch.toml`: plik z pola `entry` |
| `finch ir plik.fch` | wypisuje LLVM IR |
| `finch lsp` | serwer języka (korzystają z niego edytory) |
| `finch version` | wersja |
| `-l lib` | dolinkuj bibliotekę C (jak `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | zbuduj dla innego systemu (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) |
| `-g` | informacje dla debuggera (gdb, lldb, Visual Studio) |
| `-l bib` | dolinkowuje bibliotekę C (to samo co `link "bib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | buduje na inny system (`android`: clang z NDK wskazanego przez `ANDROID_NDK_HOME`, zwykle z `--shared`) |
| `-g` | informacje dla debuggera (gdb, lldb) |
| `-O0` | bez optymalizacji |
| `--trace` / `--no-trace` | błędy w czasie działania pokazują łańcuch wywołań; włączone dla `run`, `test`, `-g`, `-O0`, wyłączone dla zwykłego `build` (kosztuje szybkość) |
| `--cpu native` / `x86-64-v3` / `apple-m1` … | używa wszystkich instrukcji danego procesora (AVX2, AVX-512, NEON); program będzie wtedy wymagał podobnego procesora |
| `--trace` / `--no-trace` | błąd w trakcie działania pokazuje ścieżkę wywołań; domyślnie włączone dla `run`, `test`, `-g` i `-O0`, wyłączone dla zwykłego `build` (spowalnia program) |
Zmienne środowiskowe: `FINCH_PATH` (foldery z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`).
Skompilowany kod programu i jego pliki `link "x.c"` zostają w cache (`~/.cache/finch`), dopóki one albo ich nagłówki się nie zmienią.
Zmienne środowiskowe: `FINCH_PATH` (katalogi z modułami), `FINCH_CC` (kompilator C, który linkuje), `FINCH_CXX` (dla plików `.cpp`).
Skompilowany program i jego pliki z `link "x.c"` zostają w pamięci podręcznej (`~/.cache/finch`), dopóki nie zmienią się one same albo ich nagłówki.
`finch.toml` (obok kodu; `finch run` w tym folderze nie potrzebuje pliku ani flag):
`finch.toml` (obok kodu; wtedy `finch run` nie potrzebuje ani nazwy pliku, ani opcji):
```toml
name = "gra" # co robi finch build
name = "gra" # co zbuduje finch build
entry = "src/main.fch"
paths = ["src"] # foldery z modułami
paths = ["src"] # katalogi z modułami
include = ["vendor/SDL3/include"] # nagłówki C
links = ["SDL3", "native/glue.cpp"] # biblioteki i własne .c / .cpp / .o / .a
links = ["SDL3", "native/glue.cpp"] # biblioteki i twoje pliki .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # dla własnych plików .c / .cpp
cflags = ["-DFAST"] # dla twoich plików .c / .cpp
ldflags = ["-Wl,-rpath,."]
cpu = "native" # to samo co --cpu native
[target.windows] # tylko przy budowaniu dla tego systemu (też linux, macos, android, linux-arm64)
[target.windows] # tylko przy budowaniu na ten system (są też linux, macos, android, linux-arm64)
links = ["ws2_32"]
```
@ -63,8 +65,8 @@ fn main() {
}
```
`main` to `fn main()`, `fn main() -> int` (kod wyjścia) albo `fn main([]str args)`.
Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawiasów wokół warunków.
`main` może wyglądać tak: `fn main()`, `fn main() -> int` (zwraca kod wyjścia) albo `fn main([]str args)`.
Jedna instrukcja w linii, bez średników, `{` w tej samej linii, warunki bez nawiasów.
---
@ -72,12 +74,12 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
| Kod | Znaczenie |
|---|---|
| `x := 5` | nowa zmienna, typ zgadnięty |
| `int x = 5` | nowa zmienna, typ podany |
| `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (też struktur w niej) |
| `x := 5` | nowa zmienna, typ wynika z wartości |
| `int x = 5` | nowa zmienna z podanym typem |
| `int x` | zero / `""` / `[]` / `null` / wartości domyślne struktury (także struktur w jej środku) |
| `x = 7` | przypisanie |
| `x += 1` `-=` `*=` `/=` `%=` | zmiana |
| `const W = 640`, `const f32 G = 9.8` | stała (na najwyższym poziomie pliku; `ksztalty.W` z modułu) |
| `const W = 640`, `const f32 G = 9.8` | stała (na najwyższym poziomie pliku; z modułu: `ksztalty.W`) |
---
@ -87,15 +89,16 @@ Jedna instrukcja na linijkę, bez średników, `{` w tej samej linijce, bez nawi
|---|---|
| `int` (= `i64`) | `42`, `-7`, `0xFF`, `1_000_000` |
| `float` (= `f64`) | `3.14`, `1e9`, `2.5e-3` |
| `bool` | `true` (prawda), `false` (fałsz) |
| `bool` | `true`, `false` |
| `char` | `'A'`, `'\n'` |
| `str` | `"tekst\t\"w cudzysłowie\""`, `"hp \(hp), prędkość \(v, 2)"` (kod w tekście; `, 2` = dwie cyfry po przecinku), `"""` kilka linii `"""` (wcięcie zamykających cudzysłowów znika) |
| `i8 i16 i32 i64` | ze znakiem, z rozmiarem |
| `u8 u16 u32 u64` | bez znaku, z rozmiarem |
| `f32 f64` | zmiennoprzecinkowe |
| `str` | `"tekst\t\"w cudzysłowie\""`, `"hp \(hp), prędkość \(v, 2)"` (kod w tekście; `, 2` = dwie cyfry po przecinku), `"""` kilka linii `"""` (wcięcie zamykającego cudzysłowu znika) |
| `i8 i16 i32 i64` | liczby całkowite ze znakiem, o podanym rozmiarze |
| `u8 u16 u32 u64` | liczby całkowite bez znaku |
| `f32 f64` | liczby zmiennoprzecinkowe |
| `[]T` | tablica: `[1, 2, 3]`, `[]str imiona` |
| `map[K]V` | mapa: `["a": 1]`, `map[str]int ile`, `[:]` |
| `ptr[T]`, `ptr` | wskaźnik, wskaźnik bez typu |
| `f32x8`, `i8x32`, `f64x4`, `u16x16` … | wektor: N liczb liczonych naraz (SIMD), zob. niżej |
| `Nazwa` / `modul.Nazwa` | struktura |
Znaki specjalne: `\n \t \r \0 \\ \" \'`.
@ -107,11 +110,11 @@ Znaki specjalne: `\n \t \r \0 \\ \" \'`.
| Priorytet | Operatory |
|---|---|
| 5 | `*` `/` `%` `<<` `>>` `&` |
| 4 | `+` `-` `\|` `^` (`+` skleja też tekst) |
| 4 | `+` `-` `\|` `^` (`+` łączy też teksty) |
| 3 | `==` `!=` `<` `<=` `>` `>=` |
| 2 | `&&` (i) |
| 1 | `\|\|` (lub) |
| jednoargumentowe | `-x` `!x` (nie) `~x` |
| 2 | `&&` |
| 1 | `\|\|` |
| jednoargumentowe | `-x` `!x` `~x` |
`x & 1 == 0` znaczy `(x & 1) == 0`.
@ -121,14 +124,14 @@ Znaki specjalne: `\n \t \r \0 \\ \" \'`.
```c
if a > b { … } else if a == b { … } else { … }
x := if a > b { a } else { b } // jako wartość: jedna wartość na gałąź, else wymagane
x := if a > b { a } else { b } // jako wartość: po jednej wartości w każdej gałęzi, else obowiązkowe
while x > 0 { … }
for i in 0..10 { … } // 0 … 9
for el in lista { … } // każdy element (albo znak str)
for el in lista { … } // każdy element (albo znak tekstu)
for i, el in lista { … } // razem z indeksem
for klucz, wart in m { … } // każdy wpis mapy, w kolejności dodawania
for klucz, wartosc in m { … } // każdy wpis mapy, w kolejności dodania
break continue return wartosc
defer sprzatanie() // uruchomi się na końcu bloku
defer sprzatnij() // wykona się, gdy blok się skończy
```
---
@ -144,48 +147,48 @@ fn przywitaj(str imie) { // bez wyniku
}
```
Parametry zachowują się jak kopie. Kolejność w pliku nie ma znaczenia. Przecinek po ostatnim argumencie jest dozwolony.
Parametry zachowują się jak kopie. Kolejność funkcji w pliku nie ma znaczenia. Po ostatnim argumencie może stać przecinek.
---
## Metody
```c
fn Punkt.przesun(int dx, int dy) { // self to Punkt, na którym wywołano (nie kopia)
fn Punkt.przesun(int dx, int dy) { // self to punkt, na którym ją wywołano (nie kopia)
self.x += dx
self.y += dy
}
fn Punkt.odleglosc() -> float { … }
p.przesun(1, 2) lista[0].przesun(1, 1) wsk_na_punkt.przesun(0, 1)
p.przesun(1, 2) lista[0].przesun(1, 1) wskaznik_na_punkt.przesun(0, 1)
```
Także na strukturach z C: `fn Vector2.length() -> f32 { … }`.
Działają też na strukturach z C: `fn Vector2.length() -> f32 { … }`.
---
## Błędy jako wartości
```c
fn parsuj(str s) -> int! { // ! = może się nie udać
fn parsuj(str s) -> int! { // ! = może się nie udać
if s.len == 0 {
return error("pusty")
return error("pusty tekst")
}
return int(s) or { return error("to nie liczba: " + s) }
}
fn zapisz(str p, str t) -> ! { // może się nie udać, bez wyniku
fn zapisz(str p, str t) -> ! { // może się nie udać, bez wyniku
try write_file(p, t)
}
```
| Kod | Przy porażce |
| Kod | Gdy się nie uda |
|---|---|
| `x := parsuj(s) or 0` | użyj 0 |
| `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err`: komunikat) |
| `x := try parsuj(s)` | zakończ porażką też bieżącą funkcję |
| `fn main() -> !` | porażka wypisuje `error: …`, kod wyjścia 1 |
| `x := parsuj(s) or 0` | weź 0 |
| `x := parsuj(s) or { print(err); return }` | wykonaj blok (`err` to komunikat) |
| `x := try parsuj(s)` | przerwij też bieżącą funkcję z tym samym błędem |
| `fn main() -> !` | błąd wypisuje `error: …`, kod wyjścia 1 |
Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`.
Wbudowane funkcje, które mogą się nie udać: `int(s)`, `float(s)`, `read_file`, `write_file`, `delete_file`.
---
@ -193,9 +196,9 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
| Kod | Znaczenie |
|---|---|
| `struct P { … }` | definicja (jedno pole na linijkę, `= domyślna` opcjonalnie) |
| `struct P { … }` | definicja (jedno pole w linii, `= wartość` opcjonalnie) |
| `P(1, 2)` | utworzenie, wszystkie pola po kolei |
| `P(x: 1)` | utworzenie po nazwie, reszta domyślna/zero |
| `P(x: 1)` | utworzenie przez nazwy pól, reszta domyślna albo zero |
| `p.x`, `p.x = 5` | odczyt, zmiana pola |
| `b := a` | pełna kopia |
@ -203,62 +206,90 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
## Tablice `[]T`
| Kod | Daje |
| Kod | Wynik |
|---|---|
| `a.len` | liczba elementów |
| `a[i]`, `a[i] = x` | element (z kontrolą zakresu) |
| `a.push(x)` | dodaj na końcu |
| `a.pop()` | zabierz ostatni i go oddaj |
| `a.insert(i, x)` | wstaw na pozycję i |
| `a.remove(i)` | usuń pozycję i i ją oddaj |
| `a.clear()` | wyczyść |
| `a.resize(n)` | długość n (nowe elementy to zera) |
| `a[i]`, `a[i] = x` | element (zakres jest sprawdzany) |
| `a.push(x)` | dodaje na końcu |
| `a.pop()` | zdejmuje ostatni i go zwraca |
| `a.insert(i, x)` | wstawia na pozycję i |
| `a.remove(i)` | usuwa pozycję i i ją zwraca |
| `a.clear()` | opróżnia |
| `a.resize(n)` | długość n (nowe elementy są zerami) |
| `a.find(x)` | indeks albo -1 |
| `a.contains(x)` | bool |
| `a.sort()` | posortuj (liczby, znaki, str) |
| `a.reverse()` | odwróć |
| `a.slice(s, e)` | nowa tablica z s … e-1 |
| `a.join(sep)` | `[]str` → jeden str |
| `a.sort()` | sortuje (liczby, znaki, teksty) |
| `a.reverse()` | odwraca kolejność |
| `a.slice(p, k)` | nowa tablica z elementów p … k-1 |
| `a.join(sep)` | `[]str` → jeden tekst |
| `a.ptr` | adres pierwszego elementu (dla C) |
---
## Mapy `map[K]V`
| Kod | Daje |
| Kod | Wynik |
|---|---|
| `m := ["a": 1, "b": 2]` | mapa (klucze: liczby, char, bool, str) |
| `m[k]` | wartość (brak klucza: program się zatrzymuje) |
| `m[k] = v` | dodaj / zastąp |
| `m[k]` | wartość (brak klucza zatrzymuje program) |
| `m[k] = v` | dodaje albo podmienia |
| `m[k] += 1`, `m[k].push(x)` | brakujący klucz zaczyna od wartości domyślnej (0, `[]`, …) |
| `m.len` | liczba kluczy |
| `m.has(k)` | czy jest |
| `m.get(k, domyslna)` | wartość albo domyślna |
| `m.remove(k)` | bool: czy był |
| `m.clear()` | wyczyść |
| `m.keys()`, `m.values()` | tablice, w kolejności dodawania |
| `m.has(k)` | bool |
| `m.get(k, domyslna)` | wartość albo `domyslna` |
| `m.remove(k)` | bool: czy klucz był |
| `m.clear()` | opróżnia |
| `m.keys()`, `m.values()` | tablice, w kolejności dodania |
---
## Wektory `f32x8` (SIMD)
Typy: `f32x4/8/16`, `f64x2/4/8`, `i8x16/32/64`, `i16x8/16/32`, `i32x4/8/16`, `i64x2/4/8` i tak samo `u…`; najwyżej 512 bitów.
| Kod | Wynik |
|---|---|
| `f32x8(x)` | `x` w każdym elemencie |
| `f32x8(a, b, …, h)` | po jednej liczbie na element |
| `f32x8(lista, i)` | `lista[i]` … `lista[i+7]` (z `[]f32` albo `ptr[f32]`) |
| `f32x8(v)` | 8-elementowy wektor przeliczony element po elemencie (`f32x8(calkowite)`, `i32x8(ulamki)`) |
| `v + w`, `v * 2.0`, `-v` | element po elemencie; zwykła liczba działa na każdy element (`/` tylko dla liczb zmiennoprzecinkowych) |
| `& \| ^ << >> ~` | element po elemencie, dla liczb całkowitych |
| `v < w`, `v == w`, … | maska `boolx8`: `m.all()`, `m.any()`, `m.count()`, `m & n`, `!m`, `m[i]` |
| `select(m, a, b)` | element po elemencie: `a` tam, gdzie `m` jest prawdą, a gdzie nie, `b` |
| `v[i]`, `v[i] = x`, `v.len` | jeden element; liczba elementów |
| `min(v, w)`, `max`, `abs(v)`, `clamp(v, lo, hi)` | element po elemencie |
| `v.sum()`, `v.min()`, `v.max()`, `v.dot(w)` | jedna liczba ze wszystkich elementów |
| `v.sqrt()`, `v.floor()`, `v.ceil()`, `v.round()` | każdy element (liczby zmiennoprzecinkowe) |
| `v.fma(b, c)`, `v.muladd(b, c)` | `v * b + c`: zawsze z jednym zaokrągleniem / najszybciej na tym procesorze |
| `v.store(lista, i)` | zapisuje elementy do `lista[i]` … |
| `f32x8(lista, i, wypelnienie)`, `v.store_part(lista, i)` | koniec listy: tylko elementy, które są w niej (pozostałe to `wypelnienie`) |
| `v.shuffle(3, 2, 1, 0)`, `v.lo()`, `v.hi()`, `concat(a, b)` | przestawione elementy (shuffle przyjmuje numery zapisane w kodzie) |
| `tabela.lookup(idx)` | `tabela[idx % tabela.len]` dla każdego elementu (`pshufb`, `tbl`) |
| `acc.dot_add(a, b)` | sumy `i32x8` dostają po 4 iloczyny bajtów (`u8x32` × `i8x32`): `vpdpbusd`, `usdot` tam, gdzie są |
| `@cpu("avx2,fma") fn …`, `cpu_has("avx2")` | funkcja, która może używać tych rozszerzeń; przed jej wywołaniem sprawdź procesor |
---
## Tekst `str`
| Kod | Daje |
| Kod | Wynik |
|---|---|
| `s.len` | liczba bajtów (ą, ż… to 2) |
| `s.len` | liczba bajtów |
| `s[i]`, `s[i] = 'x'` | znak |
| `a + b` | sklejony tekst |
| `"x = \(x)"` | wartość dowolnego kodu w tekście (`\(x, 2)`: liczba z 2 cyframi po przecinku) |
| `a + b` | połączony tekst |
| `"x = \(x)"` | wartość dowolnego kodu wstawiona w tekst (`\(x, 2)`: liczba z dwiema cyframi po przecinku) |
| `s.sub(a, b)` | fragment a … b-1 |
| `s.find(t)` | pozycja albo -1 |
| `s.contains(t)` | czy zawiera |
| `s.starts_with(t)`, `s.ends_with(t)` | czy zaczyna / kończy się |
| `s.contains(t)` | bool |
| `s.starts_with(t)`, `s.ends_with(t)` | bool |
| `s.split(sep)` | `[]str` |
| `s.trim()` | bez spacji na końcach |
| `s.upper()`, `s.lower()` | A–Z / a–z |
| `s.replace(a, b)` | każde a → b |
| `s.repeat(n)` | n razy |
| `s.replace(a, b)` | każde a zamienione na b |
| `s.repeat(n)` | tekst n razy |
| `s.bytes()` | `[]u8` |
| `s.ptr` | `ptr[char]` (dla C; `str` trafia też do parametru `const char *` bez zmian) |
| `s.ptr` | `ptr[char]` (dla C; parametr `const char *` przyjmie też samo `str`) |
| `==` `!=` `<` `>` | porównanie |
---
@ -271,41 +302,44 @@ Funkcje wbudowane, które mogą się nie udać: `int(s)`, `float(s)`, `read_file
| `int("42")` | `42` (zatrzymuje program, jeśli to nie liczba) |
| `int('A')` | `65` |
| `float(7)`, `float("2.5")` | `7`, `2.5` |
| `u8(300)` | `44` (zostawia najniższe bity) |
| `u8(300)` | `44` (zostają najniższe bity) |
| `char(66)` | `'B'` |
| `bool(0)` | `false` |
| `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | tekst (float: najkrótszy tekst, który jest tą samą liczbą) |
| `str(3.14159, 2)` | `"3.14"` (tyle cyfr po kropce) |
| `str(42)`, `str(1.5)`, `str(true)`, `str('c')` | tekst (liczba zmiennoprzecinkowa: najkrótszy zapis tej samej liczby) |
| `str(3.14159, 2)` | `"3.14"` (tyle cyfr po przecinku) |
| `str([1, 2])`, `str(m)`, `str(punkt)` | ten sam tekst, który pokazuje `print` |
| `int("x") or 0`, `float(s) or 0.0` | liczba albo wartość zapasowa |
| `str(bajty)` | `[]u8` → tekst |
| `str(p)` | `char*` z C → tekst |
| `ptr(p)`, `ptr(16)` | wskaźnik bez typu, ze wskaźnika albo liczby |
| `ptr(p)`, `ptr(16)` | wskaźnik bez typu, ze wskaźnika albo z liczby |
| `int(p)` | wskaźnik → adres |
Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowolna całkowita → float, `f32` → `f64`.
Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza liczba → większa, liczba całkowita → zmiennoprzecinkowa, `f32` → `f64`.
---
## Funkcje wbudowane
| Funkcja | Robi |
| Funkcja | Co robi |
|---|---|
| `print(a, b, …)` | wypisz, spacje między, nowa linia na końcu |
| `input()`, `input("Pytanie? ")` | wczytaj linijkę |
| `print(a, b, …)` | wypisuje wartości oddzielone spacjami i kończy linię |
| `input()`, `input("Pytanie? ")` | wczytuje linię |
| `read_file(sciezka)` | plik → str |
| `write_file(sciezka, tekst)` | str → plik, `true`, jeśli się udało |
| `file_exists(sciezka)` | czy plik istnieje |
| `delete_file(sciezka)` | usuń plik |
| `shell(polecenie)` | uruchom polecenie, jego kod wyjścia |
| `exit(kod)` | zakończ program od razu (kończy też blok `or { }`) |
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, od, do)` | dla dowolnych liczb (twoja funkcja o tej nazwie wygrywa) |
| `file_exists(sciezka)` | bool |
| `delete_file(sciezka)` | bool |
| `shell(polecenie)` | uruchamia polecenie, zwraca jego kod wyjścia |
| `exit(kod)` | od razu kończy program (kończy też blok `or { }`) |
| `min(a, b, …)`, `max(a, b, …)`, `abs(x)`, `clamp(x, lo, hi)` | dla dowolnych liczb (twoja funkcja o tej samej nazwie ma pierwszeństwo) |
| `fma(a, b, c)`, `muladd(a, b, c)` | `a * b + c` z jednym zaokrągleniem / jedną instrukcją, gdzie procesor potrafi (zapisane `a * b + c` nigdy nie jest łączone) |
| `f32_bits(x)`, `f32_from_bits(u)`, `f64_bits`, `f64_from_bits` | bity liczby zmiennoprzecinkowej jako `u32`/`u64` i z powrotem, bez zmian |
| `f16_to_f32(h)`, `f32_to_f16(x)`, `bf16_to_f32(b)`, `f32_to_bf16(x)` | liczby połówkowej precyzji i bfloat16 (w `u16`) ↔ `f32`; także na wektorach |
| `error(komunikat)` | `return error("…")` w funkcji `-> T!` |
| `addr(x)` | wskaźnik na x; `addr(fn)` = callback dla C |
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymaj się z błędem, gdy warunek jest fałszywy (w bloku `test`: ten test nie przechodzi) |
| `atomic_add(p, n)`, `atomic_sub`, `atomic_load(p)`, `atomic_store(p, n)`, `atomic_swap(p, n)`, `atomic_cas(p, stara, nowa)` | liczba całkowita zmieniana też przez inne wątki, przez jej `ptr` (`addr(licznik)`) |
| `addr(x)` | wskaźnik na x; `addr(fn)` = funkcja zwrotna dla C |
| `assert(warunek)`, `assert(warunek, "dlaczego")` | zatrzymuje program, gdy warunek jest fałszywy (w bloku `test`: ten test nie przechodzi) |
| `atomic_add(p, n)`, `atomic_sub`, `atomic_load(p)`, `atomic_store(p, n)`, `atomic_swap(p, n)`, `atomic_cas(p, stara, nowa)` | liczba całkowita, którą zmieniają też inne wątki, przez jej `ptr` (`addr(licznik)`) |
| `new(wartosc)` | wartość na stercie → `ptr[T]` |
| `free(p)` | oddaj ją |
| `free(p)` | oddaje tę pamięć |
---
@ -314,7 +348,7 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
| Finch | C |
|---|---|
| `ptr[int] p` | `int *p` |
| `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie idzie `ptr[TaStruktura]`: `pchnij(cialo)` = `pchnij(addr(cialo))`) |
| `addr(x)` | `&x` (zmienna-struktura podana tam, gdzie oczekiwany jest `ptr[TaStruktura]`: `push(cialo)` = `push(addr(cialo))`) |
| `p.value` | `*p` |
| `p.pole` | `p->pole` |
| `p[i]` | `p[i]` |
@ -325,23 +359,23 @@ Automatycznie tylko wtedy, gdy nic nie ginie: mniejsza → większa liczba, dowo
## Pamięć
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. Bez `free`.
- `b := a` kopiuje; wynik wywołania jest przenoszony; `return x` przenosi.
- Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (ta u wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia tę u wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy kopię. Struktura z 256+ bajtami liczb przekazana przez wartość albo w `for x in lista` dostaje ostrzeżenie.
- `new(...)` + `free(...)` (często z `defer`) do list wiązanych, drzew, współdzielenia.
- Pamięć z C: funkcjami z C (`malloc` / `free`, `fclose`, …).
- Tablice, teksty i struktury zwalniają się same na końcu swojego bloku. `free` nie jest potrzebne.
- `b := a` kopiuje; wynik funkcji jest przenoszony; `return x` przenosi.
- Przekazywanie struktury: `fn f(Cialo c)` pracuje na własnej kopii (struktura wywołującego się nie zmienia), `fn f(ptr[Cialo] c)` zmienia strukturę wywołującego (`f(cialo)` albo `f(addr(cialo))`), metoda dostaje `self` przez adres, nigdy jako kopię. Struktura z co najmniej 256 bajtami liczb, przekazywana przez wartość albo w `for x in lista`, dostaje ostrzeżenie.
- `new(...)` z `free(...)` (często razem z `defer`) przydaje się do list wiązanych, drzew i współdzielenia.
- Pamięć z C zwalniasz funkcjami C (`malloc` / `free`, `fclose`, …).
---
## Moduły
```c
import ksztalty // ksztalty.fch obok tego pliku (albo w FINCH_PATH)
ksztalty.pole(b) // jego funkcje
ksztalty.Pudlo(2, 3) // jego struktury
ksztalty.Pudlo inne // w deklaracjach
import ui/przycisk // ui/przycisk.fch: przycisk.rysuj()
import ui/przycisk as p // inna nazwa: p.rysuj()
import ksztalty // ksztalty.fch obok tego pliku (albo w FINCH_PATH)
ksztalty.pole(b) // jego funkcje
ksztalty.Pudelko(2, 3) // jego struktury
ksztalty.Pudelko inne // w deklaracjach
import ui/button // ui/button.fch: button.draw()
import ui/button as btn // pod inną nazwą: btn.draw()
```
---
@ -350,14 +384,14 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| Kod | Znaczenie |
|---|---|
| `import "x.h"` | funkcje, struktury, enumy, liczbowe `#define`, zmienne globalne |
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pkg-config) |
| `link "plik.c"`, `link "glue.cpp"` | skompiluj własny plik C albo C++ (C++: funkcje w `extern "C"`) |
| `addr(mojaFn)` | funkcja Fincha jako callback |
| `import "x.h"` | funkcje, struktury, enumy, liczby z `#define`, zmienne globalne |
| `link "nazwa"` | libnazwa.so / nazwa.lib (najpierw pyta pkg-config) |
| `link "plik.c"`, `link "glue.cpp"` | kompiluje twój plik C albo C++ (w C++ funkcje w `extern "C"`) |
| `addr(mojaFn)` | przekazuje funkcję Fincha jako funkcję zwrotną |
| Typ C | Finch |
| Typ w C | W Finchu |
|---|---|
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (wg rozmiaru) |
| `int` / `long long` / `unsigned` | `i32` / `i64` / `u32` (według rozmiaru) |
| `float` / `double` | `f32` / `f64` |
| `char *` (parametr, wynik) | `str` |
| `char *` (pole, za wskaźnikiem) | `ptr[char]` |
@ -365,46 +399,46 @@ import ui/przycisk as p // inna nazwa: p.rysuj()
| `T *` | `ptr[T]` |
| `void *`, uchwyty | `ptr` |
| `T x[N]` (w strukturze) | `[N]T` |
| `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, przez wskaźnik do C (nie przez wartość) |
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr-funkcji) |
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | `Color` |
| `union U` | `U`: `U u`, `u.pole`, `U(pole: v)`, do C przez wskaźnik (nie przez wartość) |
| `#define N 4`, `"tekst"`, `(1u << 3)`, `((Uint64)2)` | stałe (bez makr z parametrami) |
| `#define RAYWHITE (Color){ 245, 245, 245, 255 }` | wartość typu `Color` |
**`char *` to `str`**: funkcja C, która bierze albo zwraca `char *`, bierze albo daje `str` Fincha (bez `.ptr`, bez zamiany); tylko pole `char *` i `char **` zostają `ptr[char]`.
C++ i Rust: przez nagłówek C. C++ z `extern "C"` (i `link "x.cpp"`), Rust z `#[no_mangle] pub extern "C" fn` zbudowany jako `staticlib` (`link "target/release/libgra.a"`) i nagłówek (napisze go cbindgen).
**`char *` to `str`**: funkcja C, która przyjmuje albo zwraca `char *`, przyjmuje albo zwraca `str` Fincha (bez `.ptr` i bez zamiany); tylko pole `char *` i `char **` zostają jako `ptr[char]`.
C++ i Rust: przez nagłówek C. C++ z `extern "C"` (i `link "x.cpp"`), Rust z `#[no_mangle] pub extern "C" fn`, zbudowany jako `staticlib` (`link "target/release/libgra.a"`), z nagłówkiem (może go napisać cbindgen).
Wynik `char *` z funkcji C to od razu `str`; struktury przez wartość działają w konwencjach System V, Windows x64 i ARM64 Linux (sprawdzone w emulatorach; macOS przez CI).
Wynik `char *` funkcji C od razu jest `str`; struktury przez wartość działają w konwencjach wywołań System V, Windows x64 i ARM64 (Linux i macOS).
---
## Błędy w czasie działania (program zatrzymuje się z plikiem i linijką)
## Błędy w trakcie działania (program kończy się z plikiem i linią)
| Komunikat | Przyczyna |
|---|---|
| `division by zero` | `/` albo `%` przez 0 |
| `index 5 is out of range (the length is 3)` | `a[5]` |
| `pop() on an empty array` | `pop()` na pustej tablicy |
| `pop() on an empty array` | |
| `used .value on a null pointer` | `.value` / `.pole` / `[i]` przez `null` |
| `can't turn "x" into int` | `int("x")` |
| `can't read the file "…"` | `read_file` (bez `or`) |
| `the key "x" is not in the map` | `m["x"]` |
| `assert failed: dlaczego` | `assert(warunek, "dlaczego")` |
Pod komunikatem wywołania, które do niego doprowadziły (`finch run`, `finch test`, `-g`, `-O0`, `--trace`):
Pod komunikatem pojawiają się wywołania, które do niego doprowadziły (`finch run`, `finch test`, `-g`, `-O0`, `--trace`):
```
gra.fch:6: runtime error: index 3 is out of range (the length is 2)
in Enemy.next (gra.fch:6)
in step (gra.fch:10)
in Wrog.nastepny (gra.fch:6)
in krok (gra.fch:10)
in main (gra.fch:15)
```
## Testy
```c
test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia, finch run/build je pomijają
g := Gracz()
g.skocz()
assert(g.y > 0.0, "y to \(g.y)")
test "wysokość skoku" { // obok kodu; uruchamia je finch test plik.fch, finch run/build je pomijają
p := Gracz()
p.skocz()
assert(p.y > 0.0, "y wynosi \(p.y)")
}
```
@ -413,74 +447,55 @@ test "wysokość skoku" { // obok kodu; finch test plik.fch je uruchamia
## Edytory
Kate: `editors/kate/install.sh`, potem włącz wtyczkę **LSP Client**; uruchamianie przez **Build & Run** → `finch run %f`.
Każdy edytor z LSP: `finch lsp`.
Dowolny edytor z LSP: `finch lsp`.
### VS Code (rozszerzenie „Finch”)
| Klawisz / akcja | Robi |
| Klawisz / akcja | Co robi |
|---|---|
| ▶ / `Ctrl+F5` | uruchom plik |
| `Finch: Build This File` | zbuduj; błędy trafiają do panelu Problems |
| `F12` | idź do definicji |
| najechanie myszą | typ / sygnatura / opis |
| ▶ / `Ctrl+F5` | uruchamia plik |
| `Finch: Build This File` | buduje; błędy trafiają do panelu Problems |
| `F12` | przejście do definicji |
| najechanie kursorem | typ / sygnatura / opis |
| `Ctrl+Spacja` | podpowiedzi (po `.`: pola, metody, zawartość modułu) |
| `Ctrl+Shift+O` | funkcje i struktury w pliku |
| `main`, `fn`, `fnr`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | szablony |
| ustawienie `finch.path` | gdzie jest finch / finch.exe |
| `main`, `fn`, `fnr`, `fnf`, `method`, `struct`, `for`, `foreach`, `forkv`, `map`, `orb`, `if`, `ife`, `while`, `input` + Tab | gotowe fragmenty kodu |
| ustawienie `finch.path` | gdzie leży finch / finch.exe |
---
## Enumy i `match`
```
enum Kolor { Czerwony, Zielony } enum Status { Ok = 200, Brak = 404 }
k := Kolor.Zielony int(k) Kolor(1) k == Kolor.Czerwony // tylko == i !=; Kolor(7) to błąd
enum Kolor { Czerwony, Zielony, Niebieski } enum Status { Ok = 200, NotFound = 404 }
k := Kolor.Zielony int(k) Kolor(2) k == Kolor.Czerwony // tylko == i !=; Kolor(7) to błąd
fn Kolor.nastepny() -> Kolor { ... } // metody działają
match k { Czerwony -> ... Zielony -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else
x := match n { 1, 2 -> "mało" else -> "dużo" } // wyrażenie: gałęzie tego samego typu
match k { Czerwony -> ... Zielony, Niebieski -> { ... } else -> ... } // same nazwy wariantów; każdy wariant albo else
x := match n { 1, 2 -> "kilka" else -> "dużo" } // wyrażenie: każda gałąź tego samego typu
```
## Typy generyczne
```
fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // wynika z argumentów albo zapisane
struct Stos[T] { []T elementy } fn Stos[T].dodaj(T x) { ... } // typ zawsze podaje się przy tworzeniu:
fn max[T](T a, T b) -> T { ... } max(3, 9) max[float](1, 2.5) // typ wywnioskowany albo podany
struct Stos[T] { []T el } fn Stos[T].push(T x) { ... } // typ zawsze idzie z nazwą:
s := Stos[int]() Stos[int] t Para[str, int]("wiek", 31) // nigdy samo Stos()
```
## Biblioteka standardowa (`import nazwa`; własny plik o tej nazwie ma pierwszeństwo)
## Biblioteka standardowa (`import nazwa`; twój plik o tej samej nazwie ma pierwszeństwo)
| | |
|---|---|
| `path` | `join(a, b)` `base(p)` `dir(p)` `ext(p)` `stem(p)` `parts(p)` `clean(p)` `is_abs(p)` |
| `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, fmt)` `days_in_month(r, m)` |
| `proc` | `run(cmd)` `output(cmd)` (błąd z jego wyjściem) `env(nazwa, domyślna)` `pid()` `cwd()` |
| `time` | `now()` `millis()` `sleep(ms)` `date(t)` `local(t)` `format(t, wzor)` `days_in_month(r, m)` |
| `proc` | `run(polecenie)` `output(polecenie)` (przy błędzie zwraca jego wyjście) `env(nazwa, domyslna)` `pid()` `cwd()` |
| `thread` | `spawn(addr(praca), addr(dane))` → `t.join()`; `mutex()` → `m.lock()` `m.unlock()` `m.close()`; `cpus()` (funkcja wątku: `fn praca(ptr arg)`) |
| `fs` | `read_bytes(p)` `write_bytes(p, dane)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(folder)` `remove_dir(p)` `data_dir(app)` |
| `fs` | `read_bytes(p)` `write_bytes(p, dane)` `exists(p)` `is_dir(p)` `mkdir(p)` `list(katalog)` `remove_dir(p)` `data_dir(aplikacja)` |
| `json` | `parse(tekst)` `stringify(v)` `pretty(v)` `new_text/number/bool/null/list/object` `v.get(k)` `v.at(i)` `v.set(k, x)` `v.push(x)` `v.has(k)` `v.str_or(d)` `v.int_or(d)` `v.float_or(d)` `v.bool_or(d)` |
## Słowa kluczowe
| Słowo | Po polsku |
|---|---|
| `fn` | funkcja |
| `return` | zwróć |
| `if` / `else` | jeżeli / w przeciwnym razie |
| `while` | dopóki |
| `for` … `in` | dla … w |
| `break` / `continue` | przerwij / kontynuuj |
| `true` / `false` / `null` | prawda / fałsz / nic |
| `import` / `link` | zaimportuj / dolinkuj |
| `struct` | struktura |
| `enum` | wyliczenie |
| `match` | dopasuj |
| `defer` | odłóż (na koniec bloku) |
| `or` | albo (co zrobić przy porażce) |
| `try` | spróbuj (porażkę przekaż dalej) |
| `const` | stała |
| `export` | wystaw (funkcja dla programu w C) |
| `self` | sam (w metodzie: struktura, na której ją wywołano) |
| `test` | test (tylko jako `test "nazwa" { }`) |
| `as` | jako (tylko w `import x as y`) |
| `map` | mapa |
`fn return if else while for in break continue true false null import link struct enum match defer or try const export`
(`test` i `as` tylko tam, gdzie coś znaczą: `test "nazwa" { }`, `import x as y`)
(oraz `self` wewnątrz metod)

View file

@ -5,7 +5,7 @@
Install: copy to ~/.local/share/org.kde.syntax-highlighting/syntax/finch.xml
(or run editors/kate/install.sh), then restart Kate.
-->
<language name="Finch" version="4" kateversion="5.0" section="Sources" extensions="*.fch"
<language name="Finch" version="5" kateversion="5.0" section="Sources" extensions="*.fch"
mimetype="text/x-finch" indenter="cstyle" author="The Finch authors" license="Apache-2.0">
<highlighting>
<list name="keywords">
@ -82,6 +82,19 @@
<item>atomic_store</item>
<item>atomic_swap</item>
<item>atomic_cas</item>
<item>select</item>
<item>concat</item>
<item>fma</item>
<item>muladd</item>
<item>cpu_has</item>
<item>f32_bits</item>
<item>f32_from_bits</item>
<item>f64_bits</item>
<item>f64_from_bits</item>
<item>f16_to_f32</item>
<item>f32_to_f16</item>
<item>bf16_to_f32</item>
<item>f32_to_bf16</item>
</list>
<contexts>
@ -103,6 +116,9 @@
<keyword String="imports" attribute="Import"/>
<keyword String="constants" attribute="Constant"/>
<keyword String="types" attribute="Data Type"/>
<!-- vectors: f32x8, i8x32, u16x16, f64x4 ... -->
<RegExpr String="\b([iuf](8|16|32|64)|bool)x(2|4|8|16|32|64)\b" attribute="Data Type"/>
<RegExpr String="@cpu\b" attribute="Keyword"/>
<keyword String="builtins" attribute="Built-in"/>
<RegExpr String="\b0[xX][0-9a-fA-F_]+\b" attribute="Hex"/>

View file

@ -1,5 +1,10 @@
# Changelog
## 2.6.0
- Vector types (`f32x8`, `i8x32`, `f64x4`, …) and masks (`boolx8`) highlighted as types; completion of their methods (`sum`, `dot_add`, `shuffle`, `lookup`, `store`, …).
- `@cpu(...)` and the new built-ins (`select`, `concat`, `fma`, `muladd`, `cpu_has`, `f32_bits`, `f16_to_f32`, …) highlighted.
## 2.5.0
- Highlighting for code inside text (`"hp \(hp)"`), `test "…" { }` blocks, `const`, `"""` text, `1e9`.

View file

@ -1,12 +1,12 @@
{
"name": "finch-lang",
"version": "2.4.0",
"version": "2.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "finch-lang",
"version": "2.4.0",
"version": "2.6.0",
"license": "Apache-2.0",
"dependencies": {
"vscode-languageclient": "^9.0.1"

View file

@ -2,7 +2,7 @@
"name": "finch-lang",
"displayName": "Finch",
"description": "The Finch programming language: highlighting, errors as you type, completion, hover, go to definition, and a Run button.",
"version": "2.5.0",
"version": "2.6.0",
"publisher": "nexoniarz",
"license": "Apache-2.0",
"icon": "images/icon.png",

View file

@ -147,11 +147,15 @@
"match": "\\bself\\b"
},
{
"name": "storage.type.primitive.finch",
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64)\\b"
"name": "storage.modifier.attribute.finch",
"match": "@cpu\\b"
},
{
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error|min|max|abs|clamp|assert|atomic_add|atomic_sub|atomic_load|atomic_store|atomic_swap|atomic_cas)\\s*(?=\\()",
"name": "storage.type.primitive.finch",
"match": "\\b(int|float|bool|char|str|ptr|map|i8|i16|i32|i64|u8|u16|u32|u64|f32|f64|(?:[iuf](?:8|16|32|64)|bool)x(?:2|4|8|16|32|64))\\b"
},
{
"match": "\\b(print|input|read_file|write_file|file_exists|delete_file|shell|exit|addr|new|free|error|min|max|abs|clamp|assert|atomic_add|atomic_sub|atomic_load|atomic_store|atomic_swap|atomic_cas|select|concat|fma|muladd|cpu_has|f32_bits|f32_from_bits|f64_bits|f64_from_bits|f16_to_f32|f32_to_f16|bf16_to_f32|f32_to_bf16)\\s*(?=\\()",
"captures": {
"1": {
"name": "support.function.builtin.finch"

View file

@ -29,6 +29,8 @@ const code = [
'test "lives \\(hp + f(1)) left" {',
' assert(clamp(x, 0, 9) == 3)',
'}',
'fn dot(f32x8 a, i8x32 b) {',
'@cpu("avx2") fn k(boolx8 m) { select(m, a, b) }',
];
const expect = [ // [line, token text, scope that must be present]
[0, 'import', 'keyword.control.import'], [0, '"stdio.h"', 'string.quoted.double'],
@ -49,6 +51,8 @@ const expect = [ // [line, token text, scope that must be present]
[17, 'test', 'keyword.other.test'], [17, '\\(', 'punctuation.section.embedded.begin'], [17, 'hp', 'meta.embedded.line'],
[17, 'f', 'meta.embedded.line'], [17, 'left', 'string.quoted.double'],
[18, 'assert', 'support.function.builtin'], [18, 'clamp', 'support.function.builtin'],
[20, 'f32x8', 'storage.type.primitive'], [20, 'i8x32', 'storage.type.primitive'],
[21, '@cpu', 'storage.modifier.attribute'], [21, 'boolx8', 'storage.type.primitive'], [21, 'select', 'support.function.builtin'],
];
registry.loadGrammar('source.finch').then((g) => {
let state = vsctm.INITIAL, toks = [];

69
examples/simd.fch Normal file
View file

@ -0,0 +1,69 @@
// Vectors (SIMD): eight f32 at once instead of one.
//
// finch run examples/simd.fch runs on any x86-64 or ARM64 processor
// finch run examples/simd.fch --cpu native uses all of this one's instructions (AVX2, AVX-512, NEON)
import time
// one number at a time
fn dot_plain([]f32 a, []f32 b) -> f32 {
f32 total = 0.0
for i in 0..a.len {
total += a[i] * b[i]
}
return total
}
// eight at a time: f32x8(list, i) takes list[i] ... list[i+7]; muladd multiplies and adds, in one step where it can
fn dot_simd([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len { // the few that are left
total += a[i] * b[i]
i += 1
}
return total
}
// brighten every color: the same work on every lane, with min keeping it below 1
fn brighten([]f32 pixels, f32 amount) {
i := 0
while i + 8 <= pixels.len {
v := min(f32x8(pixels, i) * (1.0 + amount), 1.0)
v.store(pixels, i)
i += 8
}
}
fn main() {
[]f32 a
[]f32 b
for i in 0..4_000_000 {
a.push(f32(i % 100) / 100.0)
b.push(0.5)
}
start := time.millis()
x := 0.0
for round in 0..20 {
x = float(dot_plain(a, b))
}
plain := time.millis() - start
start = time.millis()
y := 0.0
for round in 0..20 {
y = float(dot_simd(a, b))
}
simd := time.millis() - start
print("one at a time: ", str(x, 1), "in", plain, "ms")
print("eight at a time:", str(y, 1), "in", simd, "ms")
brighten(a, 0.5)
print("brightened:", a[10], a[90], a.len)
}

View file

@ -2,7 +2,7 @@
// It is embedded in the compiler, compiled once into a cache folder, and linked
// statically into every Finch program. Everything here is plain C with no state.
//
// Layout shared with the compiler (src/codegen.cpp):
// Layout shared with the compiler (src/gen.fch):
// str = { char *ptr; int64 len; int64 cap } ptr is always NUL-terminated
// cap > 0 : heap memory owned by the value (cap bytes allocated)
// cap == 0 : a string literal, valid forever, never written to
@ -1147,3 +1147,96 @@ __attribute__((noreturn, cold)) void finch_map_missing(const char *file, int64_t
(int)(key->len > 100 ? 100 : key->len), key->ptr ? key->ptr : "");
finch_panic(file, line, msg);
}
// ---------- cpu_has("avx2"): what the processor running the program can do ----------
//
// x86: CPUID, and for the AVX and AVX-512 families also XGETBV (the system must save those registers, or the
// instructions fault). ARM64: the kernel's hwcaps on Linux and Android, sysctl on macOS. A name of the other
// architecture is false.
#if defined(__x86_64__) || defined(__i386__)
#include <cpuid.h>
static int x86_leaf(unsigned leaf, unsigned sub, unsigned r[4]) {
unsigned max = __get_cpuid_max(0, 0);
if (leaf > max) return 0;
__cpuid_count(leaf, sub, r[0], r[1], r[2], r[3]);
return 1;
}
static unsigned long long x86_xcr0(void) {
unsigned lo, hi;
__asm__ volatile("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
return ((unsigned long long)hi << 32) | lo;
}
static int finch_cpu_has_c(const char *n) {
unsigned l1[4] = {0}, l7[4] = {0}, l71[4] = {0};
x86_leaf(1, 0, l1);
x86_leaf(7, 0, l7);
x86_leaf(7, 1, l71);
int osx = (l1[2] >> 27) & 1; // OSXSAVE
unsigned long long xcr0 = osx ? x86_xcr0() : 0;
int ymm = osx && (xcr0 & 6) == 6; // the system saves xmm and ymm
int zmm = ymm && (xcr0 & 0xe0) == 0xe0; // and the AVX-512 registers
if (!strcmp(n, "sse3")) return l1[2] & 1;
if (!strcmp(n, "ssse3")) return (l1[2] >> 9) & 1;
if (!strcmp(n, "sse4.1")) return (l1[2] >> 19) & 1;
if (!strcmp(n, "sse4.2")) return (l1[2] >> 20) & 1;
if (!strcmp(n, "popcnt")) return (l1[2] >> 23) & 1;
if (!strcmp(n, "avx")) return ymm && ((l1[2] >> 28) & 1);
if (!strcmp(n, "fma")) return ymm && ((l1[2] >> 12) & 1);
if (!strcmp(n, "f16c")) return ymm && ((l1[2] >> 29) & 1);
if (!strcmp(n, "avx2")) return ymm && ((l7[1] >> 5) & 1);
if (!strcmp(n, "bmi1")) return (l7[1] >> 3) & 1;
if (!strcmp(n, "bmi2")) return (l7[1] >> 8) & 1;
if (!strcmp(n, "avxvnni")) return ymm && ((l71[0] >> 4) & 1);
if (!strcmp(n, "avx512f")) return zmm && ((l7[1] >> 16) & 1);
if (!strcmp(n, "avx512dq")) return zmm && ((l7[1] >> 17) & 1);
if (!strcmp(n, "avx512bw")) return zmm && ((l7[1] >> 30) & 1);
if (!strcmp(n, "avx512vl")) return zmm && ((l7[1] >> 31) & 1);
if (!strcmp(n, "avx512vnni")) return zmm && ((l7[2] >> 11) & 1);
if (!strcmp(n, "avx512fp16")) return zmm && ((l7[3] >> 23) & 1);
if (!strcmp(n, "avx512bf16")) return zmm && ((l71[0] >> 5) & 1);
return 0;
}
#elif defined(__aarch64__) && defined(__APPLE__)
#include <sys/sysctl.h>
static int sysctl_flag(const char *name) {
int v = 0;
size_t len = sizeof v;
if (sysctlbyname(name, &v, &len, NULL, 0) != 0) return 0;
return v != 0;
}
static int finch_cpu_has_c(const char *n) {
if (!strcmp(n, "neon")) return 1;
if (!strcmp(n, "fp16")) return sysctl_flag("hw.optional.arm.FEAT_FP16");
if (!strcmp(n, "dotprod")) return sysctl_flag("hw.optional.arm.FEAT_DotProd");
if (!strcmp(n, "i8mm")) return sysctl_flag("hw.optional.arm.FEAT_I8MM");
if (!strcmp(n, "bf16")) return sysctl_flag("hw.optional.arm.FEAT_BF16");
return 0; // no SVE on Apple chips
}
#elif defined(__aarch64__) && defined(__linux__)
#include <sys/auxv.h>
static int finch_cpu_has_c(const char *n) {
unsigned long h = getauxval(AT_HWCAP), h2 = getauxval(AT_HWCAP2);
if (!strcmp(n, "neon")) return 1;
if (!strcmp(n, "fp16")) return (h >> 10) & 1; // HWCAP_ASIMDHP
if (!strcmp(n, "dotprod")) return (h >> 20) & 1; // HWCAP_ASIMDDP
if (!strcmp(n, "sve")) return (h >> 22) & 1; // HWCAP_SVE
if (!strcmp(n, "sve2")) return (h2 >> 1) & 1; // HWCAP2_SVE2
if (!strcmp(n, "i8mm")) return (h2 >> 13) & 1; // HWCAP2_I8MM
if (!strcmp(n, "bf16")) return (h2 >> 14) & 1; // HWCAP2_BF16
return 0;
}
#else
static int finch_cpu_has_c(const char *n) {
(void)n;
return 0;
}
#endif
int finch_cpu_has(const FStr *name) {
char n[32];
int64_t len = name->len < 31 ? name->len : 31;
memcpy(n, name->ptr ? name->ptr : "", (size_t)len);
n[len] = 0;
return finch_cpu_has_c(n) ? 1 : 0;
}

View file

@ -15,4 +15,7 @@ pkgs.mkShell {
vulkan-headers
vulkan-loader
];
# Nix's clang wrapper drops -march=native unless this is 0 (finch --cpu native also tells it so itself)
NIX_ENFORCE_NO_NATIVE = 0;
}

View file

@ -36,6 +36,41 @@ fn ptrTo(Type t) -> Type {
return out
}
// f32x8, i8x32, u16x16, f64x4: N numbers of one type side by side, worked on all at once (SIMD).
// Its kind is "vec", elem[0] the type of one lane, count the number of lanes. kind "" if `name` is no such type.
fn vecType(str name) -> Type {
x := name.find("x")
if x < 2 || x + 1 >= name.len {
return Type()
}
lane := name.sub(0, x)
if !["i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", "bool"].contains(lane) {
return Type()
}
n := 0
for c in name.sub(x + 1, name.len) {
if c < '0' || c > '9' {
return Type()
}
n = n * 10 + (int(c) - int('0'))
}
if n != 2 && n != 4 && n != 8 && n != 16 && n != 32 && n != 64 {
return Type()
}
bits := int(lane.sub(1, lane.len)) or 1 // bool: a mask, one bit a lane
if n * bits > 512 {
return Type() // the widest registers (AVX-512) hold 512 bits
}
if lane == "i64" {
lane = "int"
} else if lane == "f64" {
lane = "float"
}
out := Type(kind: "vec", count: n)
out.elem.push(simple(lane))
return out
}
fn same(Type a, Type b) -> bool {
if a.kind != b.kind {
return false
@ -43,10 +78,10 @@ fn same(Type a, Type b) -> bool {
if a.kind == "struct" || a.kind == "named" || a.kind == "enum" {
return a.name == b.name && a.module == b.module
}
if a.kind == "fixed" && a.count != b.count {
if (a.kind == "fixed" || a.kind == "vec") && a.count != b.count {
return false
}
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" {
if a.kind == "array" || a.kind == "ptr" || a.kind == "map" || a.kind == "fixed" || a.kind == "vec" {
if a.elem.len != b.elem.len {
return false
}
@ -67,6 +102,15 @@ fn show(Type t) -> str {
if t.kind == "fixed" {
return "[" + str(t.count) + "]" + show(t.elem[0])
}
if t.kind == "vec" {
lane := t.elem[0].kind
if lane == "int" {
lane = "i64"
} else if lane == "float" {
lane = "f64"
}
return lane + "x" + str(t.count)
}
if t.kind == "map" {
return "map[" + show(t.elem[0]) + "]" + show(t.elem[1])
}
@ -103,6 +147,7 @@ struct Node {
bool flag // decl: has a type; or: the right side is a { block }
bool fallible // fn: -> T! or -> !
bool exported // export fn: a C function of a library (finch build --shared)
str cpu // @cpu("avx2,fma") fn: the processor features the function may use
[]Node kids
[]str names // named arguments: Point(x: 1)
int line

View file

@ -1,10 +1,9 @@
// C headers, read through libclang, written in Finch.
//
// What `import "stdio.h"` teaches the compiler: the C functions, the constants (enum values and
// plain #define numbers), the extern variables (stdout) and the structs. The same job as
// src/cimport.cpp, done by calling libclang's C API through Finch's own C import -- which is
// what makes this the part that closes the bootstrap: to compile this file the self-hosted
// compiler has to read libclang's headers with this very code.
// plain #define numbers), the extern variables (stdout) and the structs. It calls libclang's C API
// through Finch's own C import -- so to compile this file, the compiler reads libclang's headers
// with this very code.
//
// C structs by value appear in types as the named type `module "C"`.
import ast

View file

@ -26,6 +26,19 @@ fn builtins() -> []Doc {
doc("max", "max(a, b, ...) -> number", "The biggest of the numbers, in the type they all fit."),
doc("abs", "abs(x) -> number", "x without its minus sign (any number type)."),
doc("clamp", "clamp(x, lo, hi) -> number", "x, but at least lo and at most hi."),
doc("cpu_has", "cpu_has(str feature) -> bool", "Does the processor running the program have this feature (\"avx2\", \"avx512f\", \"avxvnni\", \"dotprod\", \"i8mm\" ...)? Check before calling a @cpu(...) function."),
doc("concat", "concat(V a, V b) -> V2", "One vector with a's lanes, then b's (twice as many)."),
doc("select", "select(mask, a, b) -> V", "Lane by lane: a where the mask is true, b where it is false. The mask comes from comparing vectors (v > 0.0)."),
doc("fma", "fma(a, b, c) -> float", "a * b + c with one rounding, always (numbers or vectors). a * b + c written out is never fused: two roundings."),
doc("muladd", "muladd(a, b, c) -> float", "a * b + c the fastest way here: one fused instruction where the processor has one, else a multiply and an add."),
doc("f32_bits", "f32_bits(f32 x) -> u32", "The bits of x, unchanged (also f32xN -> u32xN). f64_bits gives a u64."),
doc("f32_from_bits", "f32_from_bits(u32 bits) -> f32", "The f32 with these bits (also u32xN -> f32xN). f64_from_bits takes a u64."),
doc("f64_bits", "f64_bits(float x) -> u64", "The bits of x, unchanged (also f64xN -> u64xN)."),
doc("f64_from_bits", "f64_from_bits(u64 bits) -> float", "The float with these bits (also u64xN -> f64xN)."),
doc("f16_to_f32", "f16_to_f32(u16 half) -> f32", "A half-precision number, kept in a u16, as an f32 (also u16xN -> f32xN)."),
doc("f32_to_f16", "f32_to_f16(f32 x) -> u16", "x as a half-precision number in a u16, rounded to the nearest (also f32xN -> u16xN)."),
doc("bf16_to_f32", "bf16_to_f32(u16 b) -> f32", "A bfloat16, kept in a u16, as an f32 (also u16xN -> f32xN)."),
doc("f32_to_bf16", "f32_to_bf16(f32 x) -> u16", "x as a bfloat16 in a u16, rounded to the nearest, ties to even (also f32xN -> u16xN)."),
doc("atomic_add", "atomic_add(ptr[int] p, n) -> int", "Adds n to the number at p in one step other threads can't split; gives back what was there before."),
doc("atomic_sub", "atomic_sub(ptr[int] p, n) -> int", "Takes n from the number at p in one step; gives back what was there before."),
doc("atomic_load", "atomic_load(ptr[int] p) -> int", "Reads the number at p, as another thread last wrote it."),
@ -40,7 +53,8 @@ fn builtins() -> []Doc {
doc("float", "float(x) -> float", "Converts to a decimal number; parses text (int(s) or 0.0 handles bad text)."),
doc("char", "char(int n) -> char", "The character with this code."),
doc("bool", "bool(int n) -> bool", "true for any number but 0."),
doc("ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C).")
doc("ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C)."),
doc("f32x8", "f32x8(x) / f32x8(a, b, ...) / f32x8(list, i) / f32x8(v)", "A vector: 8 f32 side by side, worked on all at once (SIMD). Every lane x; one value per lane; 8 numbers from list[i]; or another 8-lane vector converted. The same goes for i8x16, i32x8, f64x4 and the rest.")
]
}
@ -94,13 +108,42 @@ fn mapMethods() -> []Doc {
]
}
fn vecMethods() -> []Doc {
return [
doc("len", ".len -> int", "The number of lanes."),
doc("sum", "sum() -> T", "All the lanes added up (floats: in whatever order is fastest)."),
doc("min", "min() -> T", "The smallest lane."),
doc("max", "max() -> T", "The biggest lane."),
doc("dot", "dot(V w) -> T", "Lane times lane, added up: (v * w).sum()."),
doc("sqrt", "sqrt() -> V", "The square root of every lane (f32/f64 lanes)."),
doc("floor", "floor() -> V", "Every lane rounded down (f32/f64 lanes)."),
doc("ceil", "ceil() -> V", "Every lane rounded up (f32/f64 lanes)."),
doc("round", "round() -> V", "Every lane rounded to the nearest whole number, halves away from 0 (f32/f64 lanes)."),
doc("abs", "abs() -> V", "Every lane without its minus sign."),
doc("fma", "fma(V b, V c) -> V", "v * b + c with one rounding, always (f32/f64 lanes). One instruction with FMA hardware, slower without."),
doc("muladd", "muladd(V b, V c) -> V", "v * b + c the fastest way here: fused into one instruction where the processor can (f32/f64 lanes)."),
doc("store", "store([]T list, int i)", "Writes the lanes into list[i], list[i+1], ... (or through a ptr[T])."),
doc("store_part", "store_part([]T list, int i)", "Writes the lanes that fit in the list (for its last, shorter piece)."),
doc("shuffle", "shuffle(int a, int b, ...) -> V", "A vector of the lanes with these numbers (written in the code): v.shuffle(3, 2, 1, 0) reverses an f32x4."),
doc("lo", "lo() -> V", "The lower half of the lanes."),
doc("hi", "hi() -> V", "The upper half of the lanes."),
doc("lookup", "lookup(I indices) -> V", "For every lane of indices: this vector's lane (index % len). A byte-table lookup (pshufb, tbl)."),
doc("dot_add", "dot_add(a, b) -> i32xN", "On an i32xN of sums: each lane gains the sum of 4 byte products of a and b (u8/i8, 4N lanes). One instruction with AVX-VNNI, AVX-512 VNNI or ARM's dot product."),
doc("any", "any() -> bool", "A mask: is any lane true?"),
doc("all", "all() -> bool", "A mask: are all lanes true?"),
doc("count", "count() -> int", "A mask: how many lanes are true.")
]
}
fn keywords() -> []str {
return ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false", "null", "import",
"link", "struct", "enum", "match", "export", "defer", "or", "try", "self", "const"]
}
fn typeNames() -> []str {
return ["int", "float", "bool", "char", "str", "ptr", "map", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64"]
return ["int", "float", "bool", "char", "str", "ptr", "map", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64",
"f32x4", "f32x8", "f32x16", "f64x2", "f64x4", "f64x8", "i8x16", "i8x32", "i8x64", "u8x16", "u8x32", "u8x64",
"i16x8", "i16x16", "i16x32", "i32x4", "i32x8", "i32x16", "u32x4", "u32x8", "u32x16", "i64x2", "i64x4", "i64x8"]
}
// the entry called `name`, or an empty Doc

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -70,7 +70,7 @@ fn operator(ptr[Lexer] lx) -> str {
return two
}
}
ones := "(){}[],.:+-*/%&|^~<>=!"
ones := "(){}[],.:+-*/%&|^~<>=!@"
c := lx.src.sub(lx.i, lx.i + 1)
if ones.contains(c) {
return c

View file

@ -371,10 +371,7 @@ struct Build {
// IR -> program. 0 if it worked.
fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
clang := getenv("FINCH_CLANG")
if clang.len == 0 {
clang = "clang"
}
clang := clangCmd()
// the C compiler of the runtime and the linker: the target's own when building for another system
cc := clang
if tg.cross {
@ -391,7 +388,8 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
target = " --target=" + tg.triple
}
log := b.out + ".log"
obj := irObject(clang, target + " " + b.opt + b.extra, b.ir, b.name + "\n" + tg.triple, log) or {
// -ffp-contract=on: only what asks to be fused is (muladd); a * b + c stays two roundings, whatever clang's default
obj := irObject(clang, target + " " + b.opt + " -ffp-contract=on" + b.extra, b.ir, b.name + "\n" + tg.triple, log) or {
print(err)
host.remove(log)
return 1
@ -477,3 +475,74 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
}
return 0
}
// clang, as the program is compiled with it. Nix's clang wrapper removes -march=native when NIX_ENFORCE_NO_NATIVE
// is 1, as it is in nix-shell; --cpu native asks for it on purpose, so the wrapper is told to keep it.
fn clangCmd() -> str {
clang := getenv("FINCH_CLANG")
if clang.len == 0 {
clang = "clang"
}
if host.pathSeparator() != ";" {
return "NIX_ENFORCE_NO_NATIVE=0 " + clang
}
return clang
}
// -march=x86-64-v3 or -mcpu=apple-m1: how clang is told the processor
fn cpuFlag(platform.Target tg, str cpu) -> str {
if cpu.len == 0 {
return ""
}
if tg.arm64 {
return " -mcpu=" + quote(cpu)
}
return " -march=" + quote(cpu)
}
// What the processor the program is for can do, as Finch's feature names (avx2, avxvnni, i8mm ...): clang says it in
// the macros it predefines for that target and --cpu. Kept in the cache, one file per clang, target and cpu.
fn cpuFeatures(platform.Target tg, str cpu) -> []str {
clang := clangCmd()
flags := cpuFlag(tg, cpu)
if tg.cross || tg.windows {
flags = " --target=" + tg.triple + flags
}
dir := host.cacheDir() + "/cpu"
host.makeDirs(dir)
slot := dir + "/" + fingerprint2(clang + flags)
text := read_file(slot + ".txt") or ""
if text.len == 0 {
empty := slot + ".part" + str(host.pid()) + ".c"
out := slot + ".part" + str(host.pid()) + ".txt"
write_file(empty, "")
host.run(clang + flags + " -dM -E " + quote(empty) + " > " + quote(out) + " 2> " + host.nullDevice())
host.remove(empty)
text = read_file(out) or ""
if text.len == 0 {
host.remove(out)
[]str none
return none
}
host.move(out, slot + ".txt")
}
macros := ["__SSE3__", "__SSSE3__", "__SSE4_1__", "__SSE4_2__", "__POPCNT__", "__AVX__", "__AVX2__", "__FMA__", "__F16C__",
"__BMI__", "__BMI2__", "__AVX512F__", "__AVX512BW__", "__AVX512DQ__", "__AVX512VL__", "__AVX512VNNI__",
"__AVX512BF16__", "__AVX512FP16__", "__AVXVNNI__", "__ARM_NEON", "__ARM_FEATURE_FP16_VECTOR_ARITHMETIC",
"__ARM_FEATURE_DOTPROD", "__ARM_FEATURE_MATMUL_INT8", "__ARM_FEATURE_BF16_VECTOR_ARITHMETIC", "__ARM_FEATURE_SVE",
"__ARM_FEATURE_SVE2"]
names := ["sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2", "fma", "f16c", "bmi1", "bmi2", "avx512f", "avx512bw",
"avx512dq", "avx512vl", "avx512vnni", "avx512bf16", "avx512fp16", "avxvnni", "neon", "fp16", "dotprod", "i8mm", "bf16",
"sve", "sve2"]
[]str have
for line in text.split("\n") {
parts := line.split(" ")
if parts.len >= 2 && parts[0] == "#define" {
k := macros.find(parts[1])
if k >= 0 && !have.contains(names[k]) {
have.push(names[k])
}
}
}
return have
}

View file

@ -7,6 +7,7 @@
import json
import host
import docs
import ast
import fmt
import "stdio.h"
import "stdlib.h"
@ -621,6 +622,8 @@ fn completionAt(Analysis a, str text, str path, int line, int col) -> json.Json
methodItems(addr(items), docs.strMethods())
} else if type.starts_with("map[") {
methodItems(addr(items), docs.mapMethods())
} else if ast.vecType(type).kind.len > 0 {
methodItems(addr(items), docs.vecMethods()) // f32x8, i8x32, ...
} else {
base := type
for st in a.structs {

View file

@ -1,7 +1,4 @@
// finch-boot: the Finch compiler written in Finch.
//
// finch-boot build <file.fch> [-o name] compile a program
// finch-boot ir <file.fch> print the LLVM IR
// finch: the Finch compiler, written in Finch (`finch` with no arguments lists the commands).
//
// It reads the program and the modules it imports, checks it, writes LLVM IR as text
// and lets clang turn that into a program, linked with Finch's C runtime.
@ -174,6 +171,9 @@ fn usage() {
print(" --trace runtime errors show the chain of calls (finch run, finch test, -g and -O0 do it anyway)")
print(" --no-trace they don't (finch build without -g or -O0 doesn't, it costs speed)")
print(" --target <name> build for another system: windows, linux, arm64 (Linux), macos, or an LLVM triple")
print(" --cpu <name> use all of this processor's instructions (AVX2, AVX-512, NEON...): native is the one")
print(" building it; or x86-64-v3 (AVX2), x86-64-v4 (AVX-512), apple-m1, ... The program then")
print(" needs such a processor. Default: any processor of the target system")
exit(1)
}
@ -243,7 +243,7 @@ fn format([]str args) -> int {
fn main([]str args) -> int {
if args.len == 2 && (args[1] == "version" || args[1] == "--version") {
print("Finch 2.5.0 (the compiler is written in Finch)")
print("Finch 2.6.0 (the compiler is written in Finch)")
return 0
}
if args.len >= 2 && args[1] == "fmt" {
@ -293,6 +293,7 @@ fn main([]str args) -> int {
trace := cmd != "build" // finch build: only with --trace, -g or -O0 (the chain costs speed)
traceGiven := false
targetName := ""
cpu := ""
opt := "-O2"
[]str extraLibs
[]str extraDirs
@ -335,6 +336,9 @@ fn main([]str args) -> int {
} else if a == "--target" && ai + 1 < args.len {
targetName = args[ai + 1]
ai += 1
} else if a == "--cpu" && ai + 1 < args.len {
cpu = args[ai + 1]
ai += 1
} else if cmd == "run" {
progArgs.push(a)
} else {
@ -418,6 +422,9 @@ fn main([]str args) -> int {
for f in man.list("cflags", targets) {
cflags = cflags + " " + linker.quote(f)
}
if cpu.len == 0 {
cpu = man.value("cpu", "")
}
}
for d in getenv("FINCH_PATH").split(host.pathSeparator()) {
if d.len > 0 {
@ -464,7 +471,15 @@ fn main([]str args) -> int {
testModules.push(prog.module) // your own files' tests, not the standard library's
}
}
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg, test: cmd == "test", testModules: testModules, trace: trace))
// what the processor can do (dot_add and the like pick their instructions by it); not needed for the editor's index
[]str features
if cmd != "index" {
features = linker.cpuFeatures(tg, cpu)
if cpu == "native" && !tg.arm64 && !features.contains("sse4.2") {
print("warning: --cpu native made no difference to clang (a clang wrapper may remove -march=native; try --cpu with a name, like x86-64-v3)")
}
}
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, index: cmd == "index", cwd: host.cwd(), target: tg, test: cmd == "test", testModules: testModules, trace: trace, features: features))
if cmd == "ir" || cmd == "index" {
print(ir)
return 0
@ -494,6 +509,7 @@ fn main([]str args) -> int {
if debug {
extra = extra + " -g" // or clang drops the debug info that is in the IR
}
extra = extra + linker.cpuFlag(tg, cpu) // x86-64 names a processor with -march, ARM with -mcpu
[]str cflagList
for d in dirs {
cflagList.push(" -I" + linker.quote(d)) // also the program's own folders (its headers, for inline C functions)

View file

@ -86,7 +86,7 @@ fn node(ptr[Parser] p, str kind) -> ast.Node {
fn isTypeName(str n) -> bool {
names := ["int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64"]
return names.contains(n)
return names.contains(n) || ast.vecType(n).kind.len > 0
}
// int, str, ptr[T], []T, Name, module.Name
@ -117,6 +117,10 @@ fn parseType(ptr[Parser] p) -> ast.Type {
if name == "f64" {
return ast.simple("float")
}
vt := ast.vecType(name)
if vt.kind.len > 0 {
return vt
}
return ast.simple(name)
}
t := ast.Type(kind: "named", name: name)
@ -285,6 +289,22 @@ fn parseProgram(ptr[Parser] p) -> ast.Program {
if e.flag {
prog.structs.push(sumStruct(e)) // an enum with values inside is stored as a struct (see sumStruct)
}
} else if at(p, "@") {
// @cpu("avx2,fma") fn ...: a function that may use these processor features (check with cpu_has first)
a := next(p)
name := expect(p, "ident", "an attribute name, like @cpu(\"avx2\")").text
if name != "cpu" {
fail(p, a, "the attribute here is @cpu(\"...\"): the processor features a function may use")
}
expect(p, "(", "'('")
feats := expect(p, "string", "the features in quotes, like @cpu(\"avx2,fma\")").text
expect(p, ")", "')'")
if !at(p, "fn") && !at(p, "export") {
fail(p, cur(p), "@cpu(...) goes right before a function")
}
f := parseFn(p)
f.cpu = feats
prog.fns.push(f)
} else {
prog.fns.push(parseFn(p))
}

View file

@ -1,6 +1,5 @@
// The system a program is built for, and what depends on it: the C compiler that links, the file
// name of the program, how it is run here, and (through the flags) the C calling convention.
// The same as src/target.cpp.
import host
import "stdlib.h"

View file

@ -0,0 +1,7 @@
// expect: @cpu: 'avx3' isn't a feature Finch knows
@cpu("avx2,avx3")
fn f() {
}
fn main() {
f()
}

View file

@ -0,0 +1,4 @@
// expect: cpu_has: 'avx-512' isn't a feature Finch knows
fn main() {
print(cpu_has("avx-512"))
}

View file

@ -0,0 +1,8 @@
// expect: can't be passed between them (its registers differ)
@cpu("avx2")
fn double(f32x8 v) -> f32x8 {
return v * 2.0
}
fn main() {
print(double(f32x8(1.0)))
}

View file

@ -0,0 +1,5 @@
// expect: no instruction that divides whole numbers lane by lane
fn main() {
a := i32x4(8)
print(a / 2)
}

View file

@ -0,0 +1,6 @@
// expect: index 4
fn main() {
v := i32x4(1)
i := 4
print(v[i])
}

4
tests/fail/vec_lanes.fch Normal file
View file

@ -0,0 +1,4 @@
// expect: f32x4(a, b, ...) takes 4 numbers
fn main() {
print(f32x4(1.0, 2.0, 3.0))
}

7
tests/fail/vec_less.fch Normal file
View file

@ -0,0 +1,7 @@
// expect: use .all() or .any()
fn main() {
a := f32x4(1.0)
if a < a {
print("no")
}
}

View file

@ -0,0 +1,5 @@
// expect: the 8 numbers of the vector don't all fit in the list there
fn main() {
[]f32 data = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
print(f32x8(data, 2))
}

6
tests/fail/vec_mix.fch Normal file
View file

@ -0,0 +1,6 @@
// expect: can't use '+' on f32x8 and i32x8 (convert one: f32x8(...))
fn main() {
a := f32x8(1.0)
b := i32x8(1)
print(a + b)
}

View file

@ -0,0 +1,5 @@
// expect: lane 4 isn't in a f32x4 (its lanes are 0 to 3)
fn main() {
v := f32x4(1.0)
print(v.shuffle(0, 1, 2, 4))
}

View file

@ -0,0 +1,6 @@
// expect: shuffle(...) takes lane numbers written in the code
fn main() {
v := f32x4(1.0)
k := 2
print(v.shuffle(k, 0, 1, 3))
}

5
tests/fail/vec_type.fch Normal file
View file

@ -0,0 +1,5 @@
// expect: f32x8(x) puts x in every lane
fn main() {
f32x8 v = 1.0
print(v)
}

69
tests/run/dot_int8.fch Normal file
View file

@ -0,0 +1,69 @@
// The int8 dot product of quantized models: plain, with dot_add, and in @cpu functions picked by cpu_has
fn plain([]u8 a, []i8 b) -> int {
total := 0
for i in 0..a.len {
total += int(a[i]) * int(b[i])
}
return total
}
// 32 bytes a step, 8 sums kept in a vector until the end
fn with_vectors([]u8 a, []i8 b) -> int {
acc := i32x8(0)
i := 0
while i + 32 <= a.len {
acc = acc.dot_add(u8x32(a, i), i8x32(b, i))
i += 32
}
return int(acc.sum())
}
@cpu("avxvnni")
fn with_vnni([]u8 a, []i8 b) -> int {
acc := i32x8(0)
i := 0
while i + 32 <= a.len {
acc = acc.dot_add(u8x32(a, i), i8x32(b, i))
i += 32
}
return int(acc.sum())
}
@cpu("avx512f,avx512bw,avx512vl,avx512vnni,i8mm")
fn with_avx512([]u8 a, []i8 b) -> int {
acc := i32x16(0)
i := 0
while i + 64 <= a.len {
acc = acc.dot_add(u8x64(a, i), i8x64(b, i))
i += 64
}
return int(acc.sum())
}
fn best([]u8 a, []i8 b) -> int {
if cpu_has("avx512vnni") && cpu_has("avx512vl") && cpu_has("avx512bw") {
return with_avx512(a, b)
}
if cpu_has("avxvnni") {
return with_vnni(a, b)
}
return with_vectors(a, b)
}
fn main() {
[]u8 a
[]i8 b
for k in 0..1024 {
a.push(u8((k * 37 + 11) % 256))
b.push(i8((k * 53 + 7) % 256 - 128))
}
want := plain(a, b)
print(want, with_vectors(a, b) == want, best(a, b) == want)
// the extremes: 255 * -128 four times in a lane, no saturation
acc := i32x4(1, 2, 3, 4).dot_add(u8x16(255), i8x16(-128))
print(acc, i32x4(0).dot_add(i8x16(-128), i8x16(-128)), i32x4(0).dot_add(u8x16(255), u8x16(255)))
print(i32x4(0).dot_add(i8x16(-1), u8x16(200)))
print(cpu_has("avx2") || cpu_has("neon") || true)
}

4
tests/run/dot_int8.out Normal file
View file

@ -0,0 +1,4 @@
-68096 true true
i32x4(-130559, -130558, -130557, -130556) i32x4(65536, 65536, 65536, 65536) i32x4(260100, 260100, 260100, 260100)
i32x4(-800, -800, -800, -800)
true

31
tests/run/floatbits.fch Normal file
View file

@ -0,0 +1,31 @@
// a * b + c is never fused; fma rounds once; numbers as their bits; f16 and bf16
fn plain(float a, float b, float c) -> float {
return a * b + c
}
fn main() {
// 0.1 * 10 is 1.0000000000000000555..., which rounds to 1 before the - 1 (two roundings), but not inside fma
print(plain(0.1, 10.0, -1.0), fma(0.1, 10.0, -1.0))
print(fma(f32(0.1), 10.0, -1.0), fma(2, 3, 4))
m := muladd(1.5, 2.0, 0.25) // fused or not, exact here
print(m, f32x4(1.5).muladd(2.0, 0.25), fma(f32x4(0.1), 10.0, f32x4(-1.0))[0])
det := f32x4(0.1) * f32x4(10.0) - f32x4(1.0)
print(det[0])
// bits
print(f32_bits(1.0), f64_bits(1.0), f32_bits(-0.0))
print(f32_from_bits(0x40490fdb), f64_from_bits(f64_bits(2.5)), f32_from_bits(i32(-1082130432)))
u32 inf = 0x7f800000
print(f32_from_bits(inf), f32_bits(f32x4(1.0, 2.0, -2.0, 0.5)))
// f16
print(f16_to_f32(0x3c00), f16_to_f32(0xc000), f16_to_f32(0x7bff), f16_to_f32(0x0001))
print(f32_to_f16(0.1), f32_to_f16(65504.0), f32_to_f16(1.0e6), f16_to_f32(f32_to_f16(0.333)))
print(f16_to_f32(u16x8(0x3c00, 0x4000, 0x4200, 0x4400, 0, 0x8000, 0x3800, 0x3400)))
// bf16
print(bf16_to_f32(0x3f80), bf16_to_f32(0x4049), f32_to_bf16(3.14159))
print(f32_to_bf16(f32_from_bits(0x3f808000)), f32_to_bf16(f32_from_bits(0x3f818000)), f32_to_bf16(f32_from_bits(0x7fc00001)))
print(f32_to_bf16(f32x4(1.0, -2.0, 0.5, 3.14159)), bf16_to_f32(u16x4(0x3f80, 0xc000, 0x3f00, 0x4049)))
}

13
tests/run/floatbits.out Normal file
View file

@ -0,0 +1,13 @@
0 5.551115123125783e-17
1.4901161e-08 10
3.25 f32x4(3.25, 3.25, 3.25, 3.25) 1.4901161e-08
0
1065353216 4607182418800017408 2147483648
3.1415927 2.5 -1
inf u32x4(1065353216, 1073741824, 3221225472, 1056964608)
1 -2 65504 5.9604645e-08
11878 31743 31744 0.3330078
f32x8(1, 2, 3, 4, 0, -0, 0.5, 0.25)
1 3.140625 16457
16256 16258 32704
u16x4(16256, 49152, 16128, 16457) f32x4(1, -2, 0.5, 3.140625)

50
tests/run/masks.fch Normal file
View file

@ -0,0 +1,50 @@
// Masks: comparisons of vectors, select, any/all/count, and the end of a list
fn relu(f32x8 v) -> f32x8 {
return select(v > 0.0, v, 0.0)
}
// the sum of a list of any length: whole vectors, then the last piece with its missing lanes as 0
fn total([]f32 xs) -> f32 {
acc := f32x8(0.0)
i := 0
while i < xs.len {
acc = acc + f32x8(xs, i, 0.0)
i += 8
}
return acc.sum()
}
// add 1 to every number of a list, in place, the last piece too
fn bump([]f32 xs) {
i := 0
while i < xs.len {
(f32x8(xs, i, 0.0) + 1.0).store_part(xs, i)
i += 8
}
}
fn main() {
v := f32x8(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, -4.0, 5.0)
m := v > 0.0
print(m, m.count(), m.any(), m.all(), m[3])
print(relu(v), select(!m, v, -1.0))
print((v >= -1.0) & (v < 3.0), (v == 0.0) | (v == 5.0), m ^ (v > 1.0))
a := i32x4(1, 5, 3, 7)
b := i32x4(4, 4, 4, 4)
print(select(a > b, a, b), (a == a).all(), (a != b).all())
u := u8x16(200)
print((u > u8x16(100)).all(), i32x4(a > b), f32x4(a < b))
k := i32x4(0, 2, 0, -1)
print(boolx4(k), boolx4(true, false, true, false))
[]f32 xs
for n in 0..13 {
xs.push(f32(n))
}
print(total(xs))
bump(xs)
print(xs)
print(f32x8(xs, 8, -1.0), f32x8(xs, 13, 9.0), f32x8(xs, 100, 7.0))
}

9
tests/run/masks.out Normal file
View file

@ -0,0 +1,9 @@
boolx8(false, false, false, true, true, true, false, true) 4 true false true
f32x8(0, 0, 0, 1, 2, 3, 0, 5) f32x8(-2, -1, 0, -1, -1, -1, -4, -1)
boolx8(false, true, true, true, true, false, false, false) boolx8(false, false, true, false, false, false, false, true) boolx8(false, false, false, true, false, false, false, false)
i32x4(4, 5, 4, 7) true true
true i32x4(0, 1, 0, 1) f32x4(1, 0, 1, 0)
boolx4(false, true, false, true) boolx4(true, false, true, false)
78
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
f32x8(9, 10, 11, 12, 13, -1, -1, -1) f32x8(9, 9, 9, 9, 9, 9, 9, 9) f32x8(7, 7, 7, 7, 7, 7, 7, 7)

27
tests/run/shuffles.fch Normal file
View file

@ -0,0 +1,27 @@
// Moving lanes: shuffle, lo/hi, concat, and a byte table lookup (how 4-bit weights are unpacked)
// sixteen 4-bit numbers, two to a byte, made into sixteen bytes through a table of what each nibble means
fn unpack4(u8x8 packed, i8x16 table) -> i8x16 {
pairs := packed.shuffle(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7) // u8x16: each byte twice
odd := u8x16(0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1) == 1
nibbles := select(odd, pairs >> 4, pairs & 15) // low nibble first, then the high one
return table.lookup(nibbles)
}
fn main() {
v := f32x4(1.0, 2.0, 3.0, 4.0)
print(v.shuffle(3, 2, 1, 0), v.shuffle(0, 0, 1, 1, 2, 2, 3, 3), v.shuffle(1, 2))
w := i32x8(0, 1, 2, 3, 4, 5, 6, 7)
print(w.lo(), w.hi(), concat(w.hi(), w.lo()))
print(concat(w, w).len, concat(v, v * 10.0).shuffle(0, 4, 1, 5, 2, 6, 3, 7))
table := u8x16(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25)
print(table.lookup(u8x16(15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)))
print(table.lookup(u8x16(16, 17, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)).lo())
print(table.lookup(u8x32(3)).hi().lo().lo())
q4 := i8x16(-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7) // what each 4-bit number means
print(unpack4(u8x8(0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe), q4))
m := f32x4(1.0, -1.0, 2.0, -2.0) > 0.0
print(m.shuffle(1, 0, 3, 2), m.lo())
}

8
tests/run/shuffles.out Normal file
View file

@ -0,0 +1,8 @@
f32x4(4, 3, 2, 1) f32x8(1, 1, 2, 2, 3, 3, 4, 4) f32x2(2, 3)
i32x4(0, 1, 2, 3) i32x4(4, 5, 6, 7) i32x8(4, 5, 6, 7, 0, 1, 2, 3)
16 f32x8(1, 10, 2, 20, 3, 30, 4, 40)
u8x16(25, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)
u8x8(10, 11, 25, 10, 10, 10, 10, 10)
u8x4(13, 13, 13, 13)
i8x16(-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7)
boolx4(false, true, false, true) boolx2(true, false)

69
tests/run/vectors.fch Normal file
View file

@ -0,0 +1,69 @@
// Vectors: N numbers side by side, worked on all at once (SIMD)
struct Particle {
f32x8 pos
int id = 7
}
fn scale(f32x8 v, f32 k) -> f32x8 {
return v * k
}
// a dot product of two lists, 8 at a time, with the rest one by one
fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()
while i < a.len {
total += a[i] * b[i]
i += 1
}
return total
}
fn main() {
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5)
print(a + b, a * 2.0, 10.0 - a, -a)
print(a.sum(), a.min(), a.max(), a.dot(a), a.len)
print((a * a).sqrt(), f32x4(1.5, -1.5, 2.5, -0.2).round(), f32x4(1.7).floor(), f32x4(1.2).ceil())
print(abs(f32x4(-1.0, 2.0, -3.0, 4.0)), min(a, 2.5), max(a, f32x4(2.0)), clamp(a, 1.5, 3.5))
// lanes
a[0] = 9.0
print(a[0], a[3], a)
print((a == f32x4(9.0, 2.0, 3.0, 4.0)).all(), (a != a).any())
// whole numbers, bits, conversions
n := i32x8(1, 2, 3, 4, 5, 6, 7, 8)
print(n * 3 - 1, n << 2, n & 1, ~n, n.sum(), n.max())
print(f32x8(n) / 2.0, i32x8(f32x8(2.9)), i16x8(n), u8x16(200).sum())
print(abs(i32x4(-3, 3, -7, 0)), min(u8x16(5), u8x16(9)).max())
// from lists and back
[]f32 data
for i in 0..20 {
data.push(f32(i))
}
v := f32x8(data, 4)
print(v)
(v * 10.0).store(data, 12)
print(data)
print(dot(data, data))
// in structs, arrays, functions, and str()
p := Particle(pos: f32x8(1.0))
p.pos = scale(p.pos, 3.0)
p.pos[7] = 0.5
print(p)
[]f64x4 list
list.push(f64x4(1.0, 2.0, 3.0, 4.0))
list.push(list[0] * list[0])
print(list, str(list[1]).len)
i64x2 big = i64x2(1 << 40, -5)
print(big + big)
}

15
tests/run/vectors.out Normal file
View file

@ -0,0 +1,15 @@
f32x4(1.5, 2.5, 3.5, 4.5) f32x4(2, 4, 6, 8) f32x4(9, 8, 7, 6) f32x4(-1, -2, -3, -4)
10 1 4 30 4
f32x4(1, 2, 3, 4) f32x4(2, -2, 3, -0) f32x4(1, 1, 1, 1) f32x4(2, 2, 2, 2)
f32x4(1, 2, 3, 4) f32x4(1, 2, 2.5, 2.5) f32x4(2, 2, 3, 4) f32x4(1.5, 2, 3, 3.5)
9 4 f32x4(9, 2, 3, 4)
true false
i32x8(2, 5, 8, 11, 14, 17, 20, 23) i32x8(4, 8, 12, 16, 20, 24, 28, 32) i32x8(1, 0, 1, 0, 1, 0, 1, 0) i32x8(-2, -3, -4, -5, -6, -7, -8, -9) 36 8
f32x8(0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4) i32x8(2, 2, 2, 2, 2, 2, 2, 2) i16x8(1, 2, 3, 4, 5, 6, 7, 8) 128
i32x4(3, 3, 7, 0) 5
f32x8(4, 5, 6, 7, 8, 9, 10, 11)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 40, 50, 60, 70, 80, 90, 100, 110]
49706
Particle(pos: f32x8(3, 3, 3, 3, 3, 3, 3, 0.5), id: 7)
[f64x4(1, 2, 3, 4), f64x4(1, 4, 9, 16)] 18
i64x2(2199023255552, -10)