mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
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
This commit is contained in:
parent
68a9493572
commit
0bf66740df
15 changed files with 463 additions and 171 deletions
43
.github/workflows/ci.yml
vendored
43
.github/workflows/ci.yml
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
# The compiler is written in Finch. It is built from bootstrap/<system>.ll, its own LLVM IR (see bootstrap/README.md),
|
# 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),
|
# then from itself, and tested, on every system it runs on: Linux x86-64 and ARM64, macOS ARM64 and x86-64, Windows x86-64.
|
||||||
# macOS ARM64 and x86-64 (made with Apple's headers; only checked as IR until the macos job below passes).
|
# A tag v* also makes the release: every package below is attached to it.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -75,7 +75,14 @@ jobs:
|
||||||
fi
|
fi
|
||||||
|
|
||||||
macos:
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install LLVM 21 (libclang)
|
- name: Install LLVM 21 (libclang)
|
||||||
|
|
@ -92,11 +99,11 @@ jobs:
|
||||||
- name: Package
|
- name: Package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r runtime std examples dist/finch/
|
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r runtime std examples dist/finch/
|
||||||
tar -czf finch-macos-arm64.tar.gz -C dist finch
|
tar -czf finch-macos-${{ matrix.arch }}.tar.gz -C dist finch
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: finch-macos-arm64
|
name: finch-macos-${{ matrix.arch }}
|
||||||
path: finch-macos-arm64.tar.gz
|
path: finch-macos-${{ matrix.arch }}.tar.gz
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
@ -154,3 +161,27 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: finch-vscode
|
name: finch-vscode
|
||||||
path: editors/vscode/finch-lang.vsix
|
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
|
||||||
|
|
|
||||||
35
README.md
35
README.md
|
|
@ -40,6 +40,8 @@ fn main() {
|
||||||
Operator precedence that does not bite (`x & 1 == 0` means what it says).
|
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
|
- **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.
|
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:**
|
- **Errors that talk like a person:**
|
||||||
```
|
```
|
||||||
game.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
|
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
|
- **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.
|
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
|
- **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. Its first version, in C++,
|
seed (its own LLVM IR, `bootstrap/`) and then from itself, and the two come out identical.
|
||||||
is kept on the `before-bootstrap` branch.
|
|
||||||
|
|
||||||
## Quick start
|
## 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
|
```sh
|
||||||
git clone https://github.com/Nexoniarz/Finch.git
|
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 run examples/hello.fch
|
||||||
./build/finch build examples/tour.fch -o tour && ./tour
|
./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:
|
The same `bootstrap/build.sh` works on a Mac (with Homebrew's `llvm@21`) and on Windows (with llvm-mingw): see
|
||||||
unpack, keep `runtime/` and `std/` next to `finch`). **Programs for Windows, ARM64 and macOS** are built from Linux with
|
[bootstrap/README.md](bootstrap/README.md). Programs for another system are built with `--target windows|linux|arm64|macos`
|
||||||
`--target windows|arm64|macos` (see the [guide](docs/en/for-technicians.md#3-the-finch-command)); the compiler itself does not run on
|
(see the [guide](docs/en/for-technicians.md#building-for-another-system)).
|
||||||
macOS or Windows yet (it needs a seed made with that system's headers: see [bootstrap/README.md](bootstrap/README.md)).
|
|
||||||
|
|
||||||
Editors: **Kate** (`editors/kate/install.sh`), **VS Code** (the `finch-lang` `.vsix` from the release), or any
|
Editors: **Kate** (`editors/kate/install.sh`), **VS Code** (the `finch-lang` `.vsix` from the release), or any
|
||||||
editor with LSP support (`finch lsp`).
|
editor with LSP support (`finch lsp`).
|
||||||
|
|
@ -91,13 +96,14 @@ Pick the guide that fits you:
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Variables | `x := 5` (inferred) or `int x = 5`; `const W = 640` |
|
| 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 }` |
|
| 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)` |
|
| 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)` |
|
| 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 { }` |
|
| 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 { }` |
|
| 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)` |
|
| 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()`, `v.dot(w)`, `min(v, 1.0)`, `v.store(list, i)`, `v[3]` |
|
||||||
| Text | `"hp \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
|
| 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` |
|
| 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 |
|
| Memory | automatic for arrays, text and structs; `new(...)` / `free(...)` for your own heap structures |
|
||||||
|
|
@ -108,7 +114,7 @@ Pick the guide that fits you:
|
||||||
| Tests | `test "name" { assert(x == 3) }` next to the code, run with `finch test` |
|
| 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 |
|
| 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` |
|
| 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
|
## Examples
|
||||||
|
|
||||||
|
|
@ -122,6 +128,7 @@ Pick the guide that fits you:
|
||||||
| [`examples/structs.fch`](examples/structs.fch) | structs, arrays of structs, a linked list with `new`/`free` |
|
| [`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/types.fch`](examples/types.fch) | sized numbers and pointers |
|
||||||
| [`examples/c_import.fch`](examples/c_import.fch) | `printf`, `math.h`, `malloc`/`free`, `stderr` |
|
| [`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/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/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 |
|
| [`examples/vulkan.fch`](examples/vulkan.fch) | Vulkan: list the GPUs, their memory and queues, create a device |
|
||||||
|
|
@ -151,14 +158,16 @@ Pick the guide that fits you:
|
||||||
- [x] A standard library in Finch: `json`, `path`, `time`, `proc`, `fs`
|
- [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] `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] `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`
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
src/ the compiler, in Finch: lexer, parser, AST, code generation (to LLVM IR as text), C import (libclang) and
|
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
|
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
|
runtime/ the small C runtime linked into every program
|
||||||
bootstrap/ build.sh and the seeds: the compiler's own LLVM IR, one per system
|
bootstrap/ build.sh and the seeds: the compiler's own LLVM IR, one per system
|
||||||
examples/ example programs
|
examples/ example programs
|
||||||
|
|
|
||||||
175
README.pl.md
175
README.pl.md
|
|
@ -5,8 +5,8 @@
|
||||||
</picture>
|
</picture>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**Mały, zwinny, skuteczny.** Język programowania na LLVM ze składnią podobną do C,
|
**Mały, szybki, celny.** Język programowania oparty na LLVM, ze składnią w stylu C. Nauczysz się go w godzinę,
|
||||||
na tyle prosty, że nauczysz się go w godzinę, i tak szybki jak C.
|
a programy działają tak szybko jak w C.
|
||||||
|
|
||||||
> 🇬🇧 [English version](README.md)
|
> 🇬🇧 [English version](README.md)
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ fn przeciwprostokatna(float a, float b) -> float {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
imie := "Finch"
|
imie := "Finch"
|
||||||
print("Cześć od", imie, "!")
|
print("Cześć, tu", imie, "!")
|
||||||
|
|
||||||
for i in 1..4 {
|
for i in 1..4 {
|
||||||
print(i, "→", przeciwprostokatna(float(i), 1.0))
|
print(i, "→", przeciwprostokatna(float(i), 1.0))
|
||||||
|
|
@ -29,51 +29,58 @@ fn main() {
|
||||||
|
|
||||||
## Dlaczego Finch
|
## Dlaczego Finch
|
||||||
|
|
||||||
- **Prosty.** Bez średników, bez plików nagłówkowych, bez preprocesora. Każdą rzecz robi się na jeden sposób.
|
- **Prosty.** Nie ma średników, plików nagłówkowych ani preprocesora. Na każdą rzecz jest jeden sposób.
|
||||||
- **Szybki.** Kompiluje się przez potok `-O2` z LLVM do kodu natywnego, bez garbage collectora.
|
- **Szybki.** Kod przechodzi przez optymalizacje LLVM (`-O2`) i staje się zwykłym programem, 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
|
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
|
||||||
z kontrolą zakresu dorównuje C.
|
ze sprawdzaniem zakresu są tak samo szybkie jak w C.
|
||||||
- **Pamięć bez bólu.** Listy, teksty i struktury są wartościami: przypisanie kopiuje, a koniec bloku je zwalnia.
|
- **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 można zapomnieć, nie ma użycia po zwolnieniu, a każdy test jest czysty pod valgrindem.
|
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 to tanie.** Nie ma cichego zwężania typów ani mieszania liczb ze znakiem
|
- **Bezpieczniejszy niż C tam, gdzie nic to nie kosztuje.** Liczby nie zmieniają po cichu typu, a liczb ze znakiem
|
||||||
i bez znaku. Indeks poza zakresem, dzielenie przez zero i null zatrzymują program czytelnym błędem, zamiast
|
i bez znaku nie da się przypadkiem pomieszać. Indeks poza tablicą, dzielenie przez zero czy `null` kończą program
|
||||||
powodować niezdefiniowane zachowanie. Priorytety operatorów nie gryzą (`x & 1 == 0` znaczy to, co widać).
|
jasnym komunikatem, a nie niezdefiniowanym zachowaniem. Kolejność działań jest taka, jakiej się spodziewasz
|
||||||
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, każda
|
(`x & 1 == 0` znaczy dokładnie to, co widać).
|
||||||
biblioteka C i serwer języka dla twojego edytora. Działa na Linuksie, macOS i Windowsie, na x86-64 i ARM64.
|
- **Praktyczny.** Mapy, metody, błędy jako wartości (`int(s) or 0`, `try read_file(p)`), moduły, dowolna biblioteka C
|
||||||
- **Błędy mówią po ludzku:**
|
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)
|
gra.fch:3:13: error: 'y' must be i32, but this is int (use i32(...) to convert)
|
||||||
3 | i32 y = x
|
3 | i32 y = x
|
||||||
| ^
|
| ^
|
||||||
```
|
```
|
||||||
- **Każda biblioteka C, bezpośrednio.** Piszesz `import "raylib.h"` i `link "raylib"`, i możesz z niej korzystać,
|
- **Biblioteki C bez pośredników.** Wystarczy `import "raylib.h"` i `link "raylib"`, żeby z niej korzystać, łącznie
|
||||||
także ze struktur przekazywanych przez wartość i z callbacków. Nagłówki czyta libclang, więc nie trzeba niczego deklarować ręcznie.
|
ze strukturami przekazywanymi przez wartość i funkcjami zwrotnymi. Nagłówki czyta libclang, więc niczego nie trzeba
|
||||||
- **Napisany w sobie.** Kompilator to kod w Finchu i nic więcej: `src/*.fch`, ok. 8,6 tys. linii. Buduje się go z ziarna
|
deklarować ręcznie.
|
||||||
(własny kod LLVM IR, `bootstrap/`), potem z samego siebie, a oba wyniki są identyczne. Pierwsza wersja, w C++,
|
- **Napisany w samym sobie.** Kompilator to wyłącznie kod w Finchu: `src/*.fch`, ok. 12 tys. linii. Buduje się go
|
||||||
jest w gałęzi `before-bootstrap`.
|
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
|
```sh
|
||||||
git clone https://github.com/Nexoniarz/Finch.git
|
git clone https://github.com/Nexoniarz/Finch.git
|
||||||
cd Finch
|
cd Finch
|
||||||
nix-shell # albo zainstaluj samodzielnie clang + libclang (LLVM 21) + pkg-config
|
nix-shell # albo zainstaluj clang, libclang (LLVM 21) i pkg-config po swojemu
|
||||||
bootstrap/build.sh # kompilator to build/finch: z ziarna, potem z samego siebie
|
bootstrap/build.sh # gotowy kompilator to build/finch
|
||||||
|
|
||||||
./build/finch run examples/hello.fch
|
./build/finch run examples/hello.fch
|
||||||
./build/finch build examples/tour.fch -o tour && ./tour
|
./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
|
Ten sam `bootstrap/build.sh` działa na Macu (z `llvm@21` z Homebrew) i na Windowsie (z llvm-mingw); szczegóły są w
|
||||||
`runtime/` i `std/` obok `finch`). **Programy dla Windowsa, ARM64 i macOS** buduje się z Linuksa przez
|
[bootstrap/README.md](bootstrap/README.md). Program na inny system zbudujesz opcją `--target windows|linux|arm64|macos`
|
||||||
`--target windows|arm64|macos`; sam kompilator nie działa jeszcze na macOS ani Windowsie (potrzebuje ziarna
|
(zob. [przewodnik](docs/pl/dla-technikow.md#budowanie-na-inny-system)).
|
||||||
zrobionego z nagłówkami tego systemu: patrz [bootstrap/README.md](bootstrap/README.md)).
|
|
||||||
|
|
||||||
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo dowolny
|
Edytory: **Kate** (`editors/kate/install.sh`), **VS Code** (plik `.vsix` `finch-lang` z wydania) albo każdy edytor,
|
||||||
edytor z obsługą LSP (`finch lsp`).
|
który obsługuje LSP (`finch lsp`).
|
||||||
|
|
||||||
## Dokumentacja
|
## Dokumentacja
|
||||||
|
|
||||||
|
|
@ -81,90 +88,94 @@ Wybierz przewodnik dla siebie:
|
||||||
|
|
||||||
| Przewodnik | Dla kogo | Polski | English |
|
| 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) |
|
| **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) |
|
||||||
| **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) |
|
| **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) |
|
||||||
| **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) |
|
| **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** | Każda funkcja, metoda, operator i polecenie na jednej stronie | [sciaga.md](docs/pl/sciaga.md) | [cheatsheet.md](docs/en/cheatsheet.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
|
## Język w pigułce
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Zmienne | `x := 5` (typ zgadnięty) albo `int x = 5`; `const W = 640` |
|
| Zmienne | `x := 5` (typ wynika z wartości) 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]` |
|
| 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 add(int a, int b) -> int { return a + b }` |
|
| Funkcje | `fn dodaj(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)` |
|
| 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)` |
|
| 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 { }` |
|
| 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 { }` |
|
| 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ły") }` → `parsuj(s) or 0`, `parsuj(s) or { … }`, `try parsuj(s)` |
|
| Błędy | `fn parsuj(str s) -> int! { … return error("zła liczba") }` → `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]` |
|
| Wektory (SIMD) | `v := f32x8(lista, i)`, `v * 2.0 + w`, `v.sum()`, `v.dot(w)`, `min(v, 1.0)`, `v.store(lista, i)`, `v[3]` |
|
||||||
| Sterowanie | `if` / `else if` / `else` (też jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
|
| Tekst | `"hp: \(hp)"`, `"a" + "b"`, `str(42)`, `int("42")`, `s.split(",")`, `s.upper()`, `s[0]` |
|
||||||
| Pamięć | automatyczna dla tablic, tekstów i struktur; `new(...)` / `free(...)` dla własnych struktur na stercie |
|
| Sterowanie | `if` / `else if` / `else` (również jako wartość), `while`, `for i in 0..10`, `break`, `continue`, `defer` |
|
||||||
| Enumy, match | `enum Kolor { Czerwony, Zielony }`, `match k { Czerwony -> ... else -> ... }` (także jako wartość) |
|
| Pamięć | tablice, teksty i struktury zwalniają się same; `new(...)` / `free(...)` dla własnych struktur na stercie |
|
||||||
| Generyki | `fn max[T](T a, T b) -> T`, `struct Stos[T]`, `Stos[int]()` |
|
| 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` |
|
| 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)` |
|
| 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` |
|
| 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` (też `(Color){…}`), `addr(fn)` dla callbacków |
|
| 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/wyjście | `print(...)`, `input("? ")`, `read_file`, `write_file`, `shell` |
|
| 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, cache budowania, `--target windows/arm64/macos/android`, `-g` dla gdb/lldb, `-O0`, łańcuch wywołań przy błędach w czasie działania |
|
| 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
|
## 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/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/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/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/structs.fch`](examples/structs.fch) | struktury, tablice struktur, lista jednokierunkowa z `new`/`free` |
|
||||||
| [`examples/types.fch`](examples/types.fch) | liczby z rozmiarem i wskaźniki |
|
| [`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/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/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/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 ze strukturami C przez wartość |
|
| [`examples/raylib.fch`](examples/raylib.fch) | raylib i struktury C przekazywane przez wartość |
|
||||||
| [`examples/llvm.fch`](examples/llvm.fch) | Finch budujący LLVM IR przez API LLVM-C |
|
| [`examples/llvm.fch`](examples/llvm.fch) | Finch, który buduje LLVM IR przez API LLVM-C |
|
||||||
| [`src/`](src/) | kompilator napisany w Finchu (ziarno do jego zbudowania jest w [`bootstrap/`](bootstrap/)) |
|
| [`src/`](src/) | kompilator napisany w Finchu (plik startowy do jego zbudowania leży w [`bootstrap/`](bootstrap/)) |
|
||||||
|
|
||||||
## Plan rozwoju
|
## Plan rozwoju
|
||||||
|
|
||||||
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
|
- [x] Lekser, parser, generowanie kodu LLVM, optymalizacja O2
|
||||||
- [x] Typy, w tym liczby z rozmiarem, wskaźniki, konwersje; funkcje; `if`/`while`/`for`
|
- [x] Typy, w tym liczby o podanym rozmiarze, wskaźniki i konwersje; funkcje; `if`/`while`/`for`
|
||||||
- [x] Czytelne błędy kompilacji; kontrole w czasie działania (dzielenie, null, zakres)
|
- [x] Czytelne błędy kompilacji; sprawdzanie w trakcie działania (dzielenie, `null`, zakres tablicy)
|
||||||
- [x] Operatory bitowe z rozsądnymi priorytetami
|
- [x] Operatory bitowe z rozsądną kolejnością działań
|
||||||
- [x] Nagłówki C przez libclang; `link` z pkg-config; porady przy błędach linkowania
|
- [x] Nagłówki C przez libclang; `link` z pkg-config; podpowiedzi przy błędach linkera
|
||||||
- [x] `struct`, także struktury C przez wartość (ABI System V) i callbacki
|
- [x] `struct`, w tym struktury C przekazywane przez wartość (ABI System V) i funkcje zwrotne
|
||||||
- [x] Tablice z `.len`, kontrolą zakresu i metodami; `for x in lista`
|
- [x] Tablice z `.len`, sprawdzaniem zakresu i metodami; `for x in lista`
|
||||||
- [x] Pamięć: zwalnianie na końcu bloku, `defer`, `new`/`free`
|
- [x] Pamięć: wartości zwalniane na końcu bloku, `defer`, `new`/`free`
|
||||||
- [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki
|
- [x] Teksty: `+`, `.len`, indeksowanie, metody, `str(x)`, `int(s)`; `input()`; pliki
|
||||||
- [x] Moduły Fincha (`import nazwa`)
|
- [x] Moduły Fincha (`import nazwa`)
|
||||||
- [x] Informacje dla debuggera (`-g`)
|
- [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] 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] 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] Typy generyczne (`fn max[T]`, `struct Stos[T]`), `match`, enumy
|
||||||
- [x] Biblioteka standardowa w Finchu: `json`, `path`, `time`, `proc`, `fs`
|
- [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] `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`, cache budowania, łańcuch wywołań przy błędach
|
- [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` (kompiluje się; linkowanie z NDK i urządzenia niesprawdzone)
|
- [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`
|
||||||
|
|
||||||
## Struktura
|
## Co gdzie leży
|
||||||
|
|
||||||
```
|
```
|
||||||
src/ kompilator, w Finchu: lekser, parser, AST, generowanie kodu (do LLVM IR jako tekst), import C (libclang)
|
src/ kompilator w Finchu: lekser, parser, AST, generowanie kodu (LLVM IR jako tekst), import C (libclang)
|
||||||
i konwencje wywołań C, serwer języka, sterownik
|
i konwencje wywołań C, serwer języka, polecenie finch
|
||||||
std/ biblioteka standardowa, w Finchu: json, path, time, proc, fs
|
std/ biblioteka standardowa w Finchu: json, path, time, proc, fs, thread
|
||||||
runtime/ mały runtime w C dołączany do każdego programu
|
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
|
bootstrap/ build.sh i pliki startowe: własny LLVM IR kompilatora, osobno dla każdego systemu
|
||||||
examples/ przykładowe programy
|
examples/ przykładowe programy
|
||||||
editors/ rozszerzenie VS Code, podświetlanie i konfiguracja LSP dla Kate
|
editors/ rozszerzenie do VS Code, podświetlanie składni 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
|
tests/ run/ new/ (wzorcowe wyniki), 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
|
docs/ przewodniki po angielsku (en/) i po polsku (pl/) dla trzech grup czytelników
|
||||||
```
|
```
|
||||||
|
|
||||||
## Licencja
|
## Licencja
|
||||||
|
|
|
||||||
|
|
@ -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
|
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:
|
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-x86_64.ll` | Linux x86-64 | ubuntu-24.04 |
|
||||||
| `linux-aarch64.ll` | Linux ARM64 | made with the ARM64 cross compiler; CI builds and tests it on an ARM64 runner |
|
| `linux-aarch64.ll` | Linux ARM64 | ubuntu-24.04-arm |
|
||||||
| `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, Apple Silicon | macos-15 |
|
||||||
| `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) |
|
| `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
|
`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.
|
`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
|
`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>`.
|
`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
|
macOS: the seeds were made with the macOS 11.3 SDK. `brew install llvm@21`
|
||||||
`FINCH_CLANG`, `FINCH_LIBCLANG` and `FINCH_LLVM_INCLUDE`. If the macOS build fails, that job is where it shows first.
|
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).
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ Everything in Finch on one page. For explanations, see the [guides](../../README
|
||||||
| `finch version` | version |
|
| `finch version` | version |
|
||||||
| `-l lib` | link a C library (like `link "lib"`) |
|
| `-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`) |
|
| `--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 |
|
| `-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) |
|
| `--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).
|
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"]
|
libdirs = ["vendor/SDL3/lib"]
|
||||||
cflags = ["-DFAST"] # for your .c / .cpp files
|
cflags = ["-DFAST"] # for your .c / .cpp files
|
||||||
ldflags = ["-Wl,-rpath,."]
|
ldflags = ["-Wl,-rpath,."]
|
||||||
|
cpu = "native" # the same as --cpu native
|
||||||
|
|
||||||
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
|
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
|
||||||
links = ["ws2_32"]
|
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` |
|
| `[]T` | array: `[1, 2, 3]`, `[]str names` |
|
||||||
| `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` |
|
| `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` |
|
||||||
| `ptr[T]`, `ptr` | pointer, untyped pointer |
|
| `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 |
|
| `Name` / `module.Name` | a struct |
|
||||||
|
|
||||||
Escapes: `\n \t \r \0 \\ \" \'`.
|
Escapes: `\n \t \r \0 \\ \" \'`.
|
||||||
|
|
@ -240,6 +243,27 @@ 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` | bool: every lane equal / not |
|
||||||
|
| `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()`, `v.fma(b, c)` | every lane (float lanes; `fma` = `v * b + c`) |
|
||||||
|
| `v.store(list, i)` | lanes into `list[i]` … |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Text `str`
|
## Text `str`
|
||||||
|
|
||||||
| Code | Gives |
|
| Code | Gives |
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,9 @@ The computer reads your program from top to bottom, line by line.
|
||||||
|
|
||||||
## 2. Getting Finch ready
|
## 2. Getting Finch ready
|
||||||
|
|
||||||
Finch runs on **Linux** computers. If a teacher, parent or friend has already set it up
|
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.
|
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
|
Otherwise, open the **Terminal** program (a window where you type commands) and type
|
||||||
these lines one by one, pressing **Enter** after each:
|
these lines one by one, pressing **Enter** after each:
|
||||||
|
|
@ -76,7 +77,7 @@ To check it works, type:
|
||||||
./build/finch version
|
./build/finch version
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something like `Finch 2.5.0`.
|
You should see something like `Finch 2.6.0`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
# Finch for Engineers
|
# 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
|
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
|
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.
|
seed; and the language server.
|
||||||
|
|
||||||
Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) and have at least seen LLVM IR.
|
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)
|
> 🇵🇱 Polska wersja: [dla-inzynierow.md](../pl/dla-inzynierow.md)
|
||||||
|
|
||||||
|
|
@ -34,7 +32,7 @@ the Finch one.
|
||||||
## 1. Architecture
|
## 1. Architecture
|
||||||
|
|
||||||
The compiler has no LLVM library inside it. It writes LLVM IR as **text** and lets `clang` do optimisation, code
|
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)
|
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.
|
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`).
|
- **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).
|
- **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`.
|
files, panics. It is compiled once and cached in `~/.cache/finch`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -70,17 +68,17 @@ Design decisions that shape the rest:
|
||||||
| File | Lines | Responsibility |
|
| File | Lines | Responsibility |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens |
|
| `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/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/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/platform.fch` | 120 | `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/linker.fch` | 480 | 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/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/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists |
|
||||||
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
|
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
|
||||||
| `src/lsp.fch` | 910 | The language server (§11) |
|
| `src/lsp.fch` | 950 | The language server (§11) |
|
||||||
| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
|
| `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` |
|
| `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 |
|
| `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) |
|
| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) |
|
||||||
|
|
@ -168,6 +166,9 @@ offset 0, and a field is read through memory. They can't be passed by value to C
|
||||||
`ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the
|
`ir` prints the IR; `index` prints the records the language server uses (§11); `--shared` makes a shared library of the
|
||||||
`export fn`s.
|
`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
|
`--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
|
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
|
asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing
|
||||||
|
|
@ -225,6 +226,7 @@ statement, so their shape (and the `"""` dedent) is kept.
|
||||||
| `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 }` (entries, len, used, cap, index, icap; see src/gen.fch) |
|
||||||
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
|
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
|
||||||
| `[N]T` | `[N x T]` |
|
| `[N]T` | `[N x T]` |
|
||||||
|
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
|
||||||
| Finch struct | named struct type, natural layout |
|
| Finch struct | named struct type, natural layout |
|
||||||
| a method's `self` | `ptr` (the first parameter) |
|
| a method's `self` | `ptr` (the first parameter) |
|
||||||
| the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) |
|
| the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) |
|
||||||
|
|
@ -235,11 +237,16 @@ Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `i
|
||||||
|
|
||||||
### Values
|
### Values
|
||||||
|
|
||||||
```cpp
|
```c
|
||||||
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
|
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
|
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).
|
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,
|
- **`fresh`**: an owning value (str/array/owning struct) that nobody holds yet: the result of a call,
|
||||||
|
|
@ -281,6 +288,38 @@ 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
|
`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.
|
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` | `fcmp oeq`/`icmp eq` → `<N x i1>` → `llvm.vector.reduce.and` |
|
||||||
|
| `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)` | `llvm.fmuladd` (fused where the target has FMA; `llvm.fma` would be a libm call per lane without it) |
|
||||||
|
| `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -428,8 +467,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
|
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.
|
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).
|
A seed is per system because the C calling convention for the libclang calls is baked into it (§5). There are seeds
|
||||||
Linux x86-64 and Linux ARM64 have seeds. macOS and Windows would need seeds made with Apple's and Microsoft's headers.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -518,13 +558,10 @@ evaluated twice and temporaries are never pointed to.
|
||||||
|
|
||||||
## 13. Known limitations
|
## 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.
|
- One error per run, no recovery.
|
||||||
- `-g` debug info is best with `-O0`.
|
- `-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
|
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
|
||||||
inside the instantiated body.
|
inside the instantiated body.
|
||||||
- A failed test's owned memory is not freed (the test runner `longjmp`s out of it).
|
- 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 lane masks (`v < w`), shuffles or integer division.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
28. [A project: finch.toml](#28-a-project-finchtoml)
|
||||||
29. [Threads](#29-threads)
|
29. [Threads](#29-threads)
|
||||||
30. [C++, Rust and Android](#30-c-rust-and-android)
|
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
|
## 2. Installing
|
||||||
|
|
||||||
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux**
|
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and
|
||||||
(x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
|
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every
|
||||||
(LLVM 21) to read C headers and to compile its output.
|
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
|
### Option A: a ready build
|
||||||
([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 (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
|
| System | Archive | Also install |
|
||||||
[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:
|
| 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 |
|
| Distribution | Packages |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -88,12 +94,15 @@ Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
|
||||||
| Arch | `clang pkgconf` |
|
| Arch | `clang pkgconf` |
|
||||||
| Nix | `nix-shell` in the repository gives all of it |
|
| 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
|
### Option B: build it yourself
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Nexoniarz/Finch.git
|
git clone https://github.com/Nexoniarz/Finch.git
|
||||||
cd Finch
|
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
|
bootstrap/build.sh # build/finch
|
||||||
./build/finch version
|
./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
|
`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`.
|
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
|
### Building for another system
|
||||||
|
|
||||||
`--target` builds a program for another system. Finch needs that system's C compiler to link:
|
`--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
|
-l <lib> link a C library, same as link "lib" in the file
|
||||||
-I <dir> look for C headers there too
|
-I <dir> look for C headers there too
|
||||||
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
|
--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
|
-O0 skip optimizations
|
||||||
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
|
--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)
|
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
|
||||||
|
|
@ -946,8 +977,7 @@ gdb ./game
|
||||||
```
|
```
|
||||||
|
|
||||||
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
|
`-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
|
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.
|
||||||
Visual Studio debugger and WinDbg read.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -959,10 +989,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. |
|
| `… 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 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. |
|
| `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`. |
|
| `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`. |
|
| `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. |
|
| 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`. |
|
| 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: `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`. |
|
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
|
||||||
|
|
@ -1134,7 +1165,7 @@ Not there yet:
|
||||||
- Interfaces.
|
- Interfaces.
|
||||||
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
|
- 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).
|
- 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
|
### Warnings about big copies
|
||||||
|
|
||||||
|
|
@ -1193,6 +1224,7 @@ links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o /
|
||||||
libdirs = ["vendor/SDL3/lib"] # folders with libraries
|
libdirs = ["vendor/SDL3/lib"] # folders with libraries
|
||||||
cflags = ["-DFAST"] # for your .c / .cpp files
|
cflags = ["-DFAST"] # for your .c / .cpp files
|
||||||
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
|
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
|
||||||
|
cpu = "native" # the same as --cpu native (section 31)
|
||||||
|
|
||||||
[target.windows] # added only when building for Windows
|
[target.windows] # added only when building for Windows
|
||||||
links = ["ws2_32"]
|
links = ["ws2_32"]
|
||||||
|
|
@ -1292,6 +1324,82 @@ 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
|
**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
|
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
|
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS
|
||||||
output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
|
that older Android versions need.
|
||||||
NDK and running on a device have not been tried yet.
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 == w`, `v != w` | `true` if every lane is equal / if any differs |
|
||||||
|
| `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`, as one instruction where the processor has one |
|
||||||
|
| `v.store(list, i)` | writes the lanes to `list[i]` … (or through a `ptr`) |
|
||||||
|
|
||||||
|
`<` and the other comparisons would give an answer per lane, so they aren't there; use `min`, `max` or `v[i] < w[i]`.
|
||||||
|
|
||||||
|
**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).fma(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.
|
||||||
|
|
||||||
|
**Which instructions.** 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). `--cpu native` (or `cpu = "native"` in `finch.toml`) lets LLVM use everything the processor
|
||||||
|
that builds it has: AVX2 and FMA, AVX-512, or NEON's newer parts on ARM. Such a program needs a processor like that one to
|
||||||
|
run. To pick a level instead: `--cpu x86-64-v3` (AVX2, most PCs since 2015), `--cpu x86-64-v4` (AVX-512), `--cpu apple-m1`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
||||||
69
examples/simd.fch
Normal file
69
examples/simd.fch
Normal 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], fma multiplies and adds in one step
|
||||||
|
fn dot_simd([]f32 a, []f32 b) -> f32 {
|
||||||
|
acc := f32x8(0.0)
|
||||||
|
i := 0
|
||||||
|
while i + 8 <= a.len {
|
||||||
|
acc = f32x8(a, i).fma(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)
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// It is embedded in the compiler, compiled once into a cache folder, and linked
|
// 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.
|
// 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
|
// 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 : heap memory owned by the value (cap bytes allocated)
|
||||||
// cap == 0 : a string literal, valid forever, never written to
|
// cap == 0 : a string literal, valid forever, never written to
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
// C headers, read through libclang, written in Finch.
|
// C headers, read through libclang, written in Finch.
|
||||||
//
|
//
|
||||||
// What `import "stdio.h"` teaches the compiler: the C functions, the constants (enum values and
|
// 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
|
// plain #define numbers), the extern variables (stdout) and the structs. It calls libclang's C API
|
||||||
// src/cimport.cpp, done by calling libclang's C API through Finch's own C import -- which is
|
// through Finch's own C import -- so to compile this file, the compiler reads libclang's headers
|
||||||
// what makes this the part that closes the bootstrap: to compile this file the self-hosted
|
// with this very code.
|
||||||
// compiler has to read libclang's headers with this very code.
|
|
||||||
//
|
//
|
||||||
// C structs by value appear in types as the named type `module "C"`.
|
// C structs by value appear in types as the named type `module "C"`.
|
||||||
import ast
|
import ast
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ fn vecMethods() -> []Doc {
|
||||||
doc("ceil", "ceil() -> V", "Every lane rounded up (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("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("abs", "abs() -> V", "Every lane without its minus sign."),
|
||||||
doc("fma", "fma(V b, V c) -> V", "v * b + c in one step, with one rounding (f32/f64 lanes)."),
|
doc("fma", "fma(V b, V c) -> V", "v * b + c, as one instruction where the processor has one (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", "store([]T list, int i)", "Writes the lanes into list[i], list[i+1], ... (or through a ptr[T]).")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/gen.fch
18
src/gen.fch
|
|
@ -1,8 +1,7 @@
|
||||||
// Type checking and LLVM IR generation (as text), written in Finch.
|
// Type checking and LLVM IR generation (as text), written in Finch.
|
||||||
//
|
//
|
||||||
// The output is a .ll file that clang turns into a program, linked with the same
|
// The output is a .ll file that clang turns into a program, linked with Finch's C runtime
|
||||||
// C runtime the main compiler uses. This compiler copies values like the main one
|
// (runtime/finch_rt.c). Values are copied when assigned and freed when their block ends.
|
||||||
// (so programs behave the same) but never frees them: it is the bootstrap compiler.
|
|
||||||
import ast
|
import ast
|
||||||
import docs
|
import docs
|
||||||
import cimport
|
import cimport
|
||||||
|
|
@ -477,7 +476,7 @@ fn T(str kind) -> ast.Type {
|
||||||
return ast.simple(kind)
|
return ast.simple(kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `int` is i64 and `float` is f64, as in the C++ compiler: the everyday names
|
// `int` is i64 and `float` is f64: the everyday names
|
||||||
// for the two default sizes, and the only way they are ever written back.
|
// for the two default sizes, and the only way they are ever written back.
|
||||||
fn isSigned(ast.Type t) -> bool {
|
fn isSigned(ast.Type t) -> bool {
|
||||||
k := t.kind
|
k := t.kind
|
||||||
|
|
@ -4642,11 +4641,12 @@ fn convert(ptr[Gen] g, ast.Node c, ast.Type to) -> Val {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- the C calling convention for structs passed by value (System V x86-64) ----------
|
// ---------- the C calling convention for structs passed by value ----------
|
||||||
//
|
//
|
||||||
// A struct of up to 16 bytes travels in registers, one INTEGER or SSE class per eightbyte;
|
// System V x86-64: a struct of up to 16 bytes travels in registers, one INTEGER or SSE class per eightbyte;
|
||||||
// bigger ones are copied to the stack (byval) and returned through a hidden pointer (sret).
|
// bigger ones are copied to the stack (byval) and returned through a hidden pointer (sret).
|
||||||
// Other targets (Windows, ARM64) have their own rules and are not done here yet.
|
// ARM64 (AAPCS64, and Apple's variant) and Windows x64 have their own rules: classifyArm64, and the
|
||||||
|
// windows branch of classify.
|
||||||
|
|
||||||
struct Abi {
|
struct Abi {
|
||||||
str kind // "direct", "expand" (in registers: `parts`) or "memory"
|
str kind // "direct", "expand" (in registers: `parts`) or "memory"
|
||||||
|
|
@ -6992,7 +6992,9 @@ fn vecMethod(ptr[Gen] g, ast.Node m, Val v) -> Val {
|
||||||
need(g, m, 2)
|
need(g, m, 2)
|
||||||
b := asVec(g, expr(g, m.kids[1]), vt, "fma", m.kids[1])
|
b := asVec(g, expr(g, m.kids[1]), vt, "fma", m.kids[1])
|
||||||
c := asVec(g, expr(g, m.kids[2]), vt, "fma", m.kids[2])
|
c := asVec(g, expr(g, m.kids[2]), vt, "fma", m.kids[2])
|
||||||
return Val(v: vecIntrinsic(g, lt, "fma", vt, [lt, lt, lt], [v.v, b.v, c.v], ""), ty: vt)
|
// fmuladd: one fused instruction where the processor has one (FMA, NEON), a multiply and an add where
|
||||||
|
// it doesn't (llvm.fma would call a slow library function there, to round only once)
|
||||||
|
return Val(v: vecIntrinsic(g, lt, "fmuladd", vt, [lt, lt, lt], [v.v, b.v, c.v], ""), ty: vt)
|
||||||
}
|
}
|
||||||
need(g, m, 0)
|
need(g, m, 0)
|
||||||
return Val(v: vecIntrinsic(g, lt, n, vt, [lt], [v.v], ""), ty: vt)
|
return Val(v: vecIntrinsic(g, lt, n, vt, [lt], [v.v], ""), ty: vt)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
// finch-boot: the Finch compiler written in Finch.
|
// finch: the Finch compiler, written in Finch (`finch` with no arguments lists the commands).
|
||||||
//
|
|
||||||
// finch-boot build <file.fch> [-o name] compile a program
|
|
||||||
// finch-boot ir <file.fch> print the LLVM IR
|
|
||||||
//
|
//
|
||||||
// It reads the program and the modules it imports, checks it, writes LLVM IR as text
|
// 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.
|
// and lets clang turn that into a program, linked with Finch's C runtime.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// The system a program is built for, and what depends on it: the C compiler that links, the file
|
// 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.
|
// 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 host
|
||||||
import "stdlib.h"
|
import "stdlib.h"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue