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:
Claude 2026-11-01 13:57:28 +00:00
commit 0bf66740df
No known key found for this signature in database
15 changed files with 463 additions and 171 deletions

View file

@ -41,6 +41,7 @@ compiler works inside. For that, see the [guide for engineers](for-engineers.md)
28. [A project: finch.toml](#28-a-project-finchtoml)
29. [Threads](#29-threads)
30. [C++, Rust and Android](#30-c-rust-and-android)
31. [Vectors (SIMD)](#31-vectors-simd)
---
@ -67,19 +68,24 @@ messages that tell you how to fix the problem.
## 2. Installing
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux**
(x86-64 and ARM64) and builds programs for **Linux, Windows and macOS**. It needs **clang 21** and **libclang**
(LLVM 21) to read C headers and to compile its output.
The Finch compiler is written in Finch (see the [engineers' guide](for-engineers.md)). It runs on **Linux** (x86-64 and
ARM64), **macOS** (Apple Silicon and Intel) and **Windows** (x86-64), and builds programs for all of them. On every
system it needs **clang** (it compiles each program with it) and **libclang** from LLVM 21 (it reads C headers with it).
The compiler does not run on macOS or Windows yet; it needs a seed made with that system's headers
([bootstrap/README.md](../../bootstrap/README.md) says why). Until then, build your Windows and macOS programs from Linux
with `--target` (below), or use the older C++ compiler from the `before-bootstrap` branch.
### Option A: a ready build
### Option A: a ready build (Linux)
Download the archive for your system from the [releases](https://github.com/Nexoniarz/Finch/releases) and unpack it.
Keep `runtime/` and `std/` next to the `finch` file (or install it, below).
Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
[releases](https://github.com/Nexoniarz/Finch/releases) and unpack it. Keep `runtime/` and `std/` next to the `finch` file
(or install it, below). You also need `clang` 21 and its libclang library:
| System | Archive | Also install |
|---|---|---|
| Linux x86-64 | `finch-linux-x64.tar.gz` | clang 21 and libclang (table below) |
| Linux ARM64 (Raspberry Pi 4/5, Graviton…) | `finch-linux-arm64.tar.gz` | the same |
| macOS, Apple Silicon | `finch-macos-arm64.tar.gz` | `xcode-select --install` (clang and the system headers), `brew install llvm@21` (libclang) |
| macOS, Intel | `finch-macos-x64.tar.gz` | the same |
| Windows x64 | `finch-windows-x64.zip` (`finch.exe` with `libclang.dll`) | [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/releases) (the `ucrt-x86_64` zip): unpack it and put its `bin` folder on your PATH |
On Linux:
| Distribution | Packages |
|---|---|
@ -88,12 +94,15 @@ Download `finch-linux-x64.tar.gz` or `finch-linux-arm64.tar.gz` from the
| Arch | `clang pkgconf` |
| Nix | `nix-shell` in the repository gives all of it |
On Windows, llvm-mingw gives Finch `clang`, the C headers and the linker in one folder; Windows programs built with it
need nothing else to run. Keep `libclang.dll` next to `finch.exe`.
### Option B: build it yourself
```sh
git clone https://github.com/Nexoniarz/Finch.git
cd Finch
nix-shell # clang, libclang, pkg-config (or install them with your distribution)
nix-shell # Linux: clang, libclang, pkg-config (or install them with your distribution)
bootstrap/build.sh # build/finch
./build/finch version
```
@ -102,6 +111,26 @@ bootstrap/build.sh # build/finch
`src/` with it, builds `src/` again with the result and checks the two are identical. If libclang isn't where the linker
looks, tell it: `FINCH_LIBCLANG="-L/usr/lib/llvm-21/lib -lclang-21" bootstrap/build.sh`.
On a Mac, with Homebrew's LLVM:
```sh
brew install llvm@21
export FINCH_CLANG="$(brew --prefix llvm@21)/bin/clang"
export FINCH_LIBCLANG="-L$(brew --prefix llvm@21)/lib -lclang"
export FINCH_LLVM_INCLUDE="$(brew --prefix llvm@21)/include"
bootstrap/build.sh
```
On Windows, in Git Bash, with llvm-mingw on the PATH and the official LLVM for Windows (for `libclang.dll` and its headers)
unpacked to `C:/llvm`:
```sh
export FINCH_LIBCLANG="C:/llvm/bin/libclang.dll"
export FINCH_LLVM_INCLUDE="C:/llvm/include"
export FINCH_LDFLAGS="-LC:/llvm/bin"
bootstrap/build.sh # build/finch.exe, with libclang.dll copied next to it
```
### Building for another system
`--target` builds a program for another system. Finch needs that system's C compiler to link:
@ -151,7 +180,9 @@ options:
-l <lib> link a C library, same as link "lib" in the file
-I <dir> look for C headers there too
--target <name> build for another system: windows, linux, arm64, macos, android, or an LLVM triple
-g add debug info (for gdb / lldb / Visual Studio)
--cpu <name> use every instruction of a processor: native (this one), x86-64-v3 (AVX2),
x86-64-v4 (AVX-512), apple-m1, ... (section 31)
-g add debug info (for gdb and lldb)
-O0 skip optimizations
--trace runtime errors show the chain of calls (on anyway for run, test, -g and -O0)
--no-trace they don't (a plain finch build doesn't: keeping the chain makes calls slower)
@ -946,8 +977,7 @@ gdb ./game
```
`-g` adds debug info (lines, functions, variables, struct fields). `-O0` keeps every variable
visible; without it the optimizer may remove some. On Windows, `-g` writes CodeView, which the
Visual Studio debugger and WinDbg read.
visible; without it the optimizer may remove some. On Windows the debug info is DWARF as well: use the `lldb` that comes with llvm-mingw, or gdb.
---
@ -959,10 +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. |
| `the library 'x' wasn't found` | Not installed, or a wrong name. On Nix, add it to `shell.nix` and run inside `nix-shell`. |
| `the C function 'f' can't be used from Finch yet` | It uses a union, bit fields or another unsupported type by value. Look for a variant that takes pointers. |
| `--cpu native`: the program stops with `Illegal instruction` on another computer | It was built for the processor that built it. Build without `--cpu`, or with an older one (`x86-64-v2`). |
| `can't find the module 'x'` | Put `x.fch` next to the importing file, or set `FINCH_PATH`. |
| `couldn't build the Finch runtime` | No working C compiler. Install gcc or clang, or set `CC`. |
| A library is not found when the program starts | It was linked from a folder the system doesn't search. Run inside the same `nix-shell`, or install it system-wide. |
| Windows: `finch` can't link (`LNK…` errors about `libcmt`, `kernel32`) | Install Visual Studio Build Tools with *Desktop development with C++*. |
| Windows: `no C compiler found to link with ('clang')` | Put llvm-mingw's `bin` folder on your PATH (section 2). |
| Windows: `finch.exe` doesn't start (missing `libclang.dll`) | Keep `libclang.dll` next to `finch.exe`. |
| macOS: `finch` doesn't start (`Library not loaded: …libLLVM…`) | `brew install llvm@21`. |
| macOS: `can't find the C header 'stdio.h'` | Install the command line tools: `xcode-select --install`. |
@ -1134,7 +1165,7 @@ Not there yet:
- Interfaces.
- Unicode-aware text: `.len`, `s[i]` and `upper()` work on bytes / ASCII.
- Checks that stop two threads from changing the same array at once (use a mutex).
- Windows on ARM64, 32-bit systems, WebAssembly. `--target android` makes code for Android, but linking with the NDK and running on a device are untried.
- Windows on ARM64, 32-bit systems, WebAssembly.
### Warnings about big copies
@ -1193,6 +1224,7 @@ links = ["SDL3", "native/glue.cpp"] # libraries, and your own .c / .cpp / .o /
libdirs = ["vendor/SDL3/lib"] # folders with libraries
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,$ORIGIN"] # for the linker
cpu = "native" # the same as --cpu native (section 31)
[target.windows] # added only when building for Windows
links = ["ws2_32"]
@ -1292,6 +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
up) with the NDK's clang, found through `ANDROID_NDK_HOME` (or `FINCH_CC`). Load it from an app, for example through
SDL3's Android project (the library's `SDL_main` is an `export fn`). This is new and only partly checked: Finch's
output compiles for Android (thread-local data goes through the emulated TLS older Android needs), but linking with the
NDK and running on a device have not been tried yet.
SDL3's Android project (the library's `SDL_main` is an `export fn`). Thread-local data goes through the emulated TLS
that older Android versions need.
---
## 31. Vectors (SIMD)
A processor can add or multiply several numbers with one instruction: 4 floats at once with SSE, 8 with AVX2, 16 with
AVX-512, 4 with ARM's NEON. That is SIMD (single instruction, multiple data), and it is how image filters, audio, physics
and neural networks get their speed. In Finch it is a type: **`f32x8` is 8 `f32` side by side**.
```c
a := f32x4(1.0, 2.0, 3.0, 4.0)
b := f32x4(0.5) // 0.5 in every lane
print(a + b) // f32x4(1.5, 2.5, 3.5, 4.5)
print(a * 2.0) // a plain number counts for every lane: f32x4(2, 4, 6, 8)
print(a.sum(), a[2], a.len) // 10 3 4
```
**The types** are a number type, `x`, and how many lanes: `f32x4` `f32x8` `f32x16`, `f64x2` `f64x4` `f64x8`,
`i8x16` `i8x32` `i8x64`, `i16x8` `i16x16`, `i32x4` `i32x8` `i32x16`, `i64x2` `i64x4`, and the same with `u` (unsigned).
2 to 64 lanes, at most 512 bits in all. A vector is a plain value like an `int`: it goes into variables, struct fields,
arrays, maps and function parameters, and `print` and `str` show it.
**Making one:**
| | |
|---|---|
| `f32x8(x)` | `x` in every lane |
| `f32x8(a, b, c, d, e, f, g, h)` | one number per lane |
| `f32x8(list, i)` | `list[i]` … `list[i+7]` from a `[]f32` (or through a `ptr[f32]`); stops the program if they aren't all in the list |
| `f32x8(v)` | another 8-lane vector converted lane by lane: `f32x8(ints)`, `i32x8(floats)` (cuts the fractions), `i16x8(i32s)` |
**Working with it:**
| | |
|---|---|
| `+ - * /` | lane by lane (`/` on floats only: x86 and ARM can't divide whole numbers lane by lane) |
| `& \| ^ << >> ~` | lane by lane, on whole-number lanes |
| `-v` | every lane negated |
| `v == 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.