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

@ -21,8 +21,9 @@ Everything in Finch on one page. For explanations, see the [guides](../../README
| `finch version` | version |
| `-l lib` | link a C library (like `link "lib"`) |
| `--target windows` / `linux` / `arm64` / `macos` / `android` / triple | build for another system (`android`: the NDK's clang from `ANDROID_NDK_HOME`, usually with `--shared`) |
| `-g` | debug info (gdb, lldb, Visual Studio) |
| `-g` | debug info (gdb, lldb) |
| `-O0` | no optimizations |
| `--cpu native` / `x86-64-v3` / `apple-m1` … | use every instruction of that processor (AVX2, AVX-512, NEON); the program then needs one like it |
| `--trace` / `--no-trace` | runtime errors show the chain of calls; on for `run`, `test`, `-g`, `-O0`, off for a plain `build` (it costs speed) |
Environment: `FINCH_PATH` (folders with modules), `FINCH_CC` (the C compiler that links), `FINCH_CXX` (for `.cpp` files).
@ -39,6 +40,7 @@ links = ["SDL3", "native/glue.cpp"] # libraries and your own .c / .cpp / .o / .a
libdirs = ["vendor/SDL3/lib"]
cflags = ["-DFAST"] # for your .c / .cpp files
ldflags = ["-Wl,-rpath,."]
cpu = "native" # the same as --cpu native
[target.windows] # only when building for that system (also linux, macos, android, linux-arm64)
links = ["ws2_32"]
@ -96,6 +98,7 @@ One statement per line, no semicolons, `{` on the same line, no brackets around
| `[]T` | array: `[1, 2, 3]`, `[]str names` |
| `map[K]V` | map: `["a": 1]`, `map[str]int counts`, `[:]` |
| `ptr[T]`, `ptr` | pointer, untyped pointer |
| `f32x8`, `i8x32`, `f64x4`, `u16x16` … | vector: N numbers worked on at once (SIMD), see below |
| `Name` / `module.Name` | a struct |
Escapes: `\n \t \r \0 \\ \" \'`.
@ -240,6 +243,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`
| Code | Gives |

View file

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

View file

@ -1,13 +1,11 @@
# Finch for Engineers
**The internals of Finch 2.5, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
**The internals of Finch 2.6, whose compiler is written in Finch.** How source becomes tokens, an AST and LLVM IR *as text*,
which `clang` compiles and links with a small C runtime; how C headers are read with libclang from Finch itself and how
the C calling conventions are reproduced by hand; the type and ownership rules; how the compiler builds itself from a
seed; and the language server.
Assumed: you can read Finch (see the [technicians' guide](for-technicians.md)) and have at least seen LLVM IR.
The compiler's first version, in C++ on LLVM's C++ API, is kept on the `before-bootstrap` branch; this guide describes
the Finch one.
> 🇵🇱 Polska wersja: [dla-inzynierow.md](../pl/dla-inzynierow.md)
@ -34,7 +32,7 @@ the Finch one.
## 1. Architecture
The compiler has no LLVM library inside it. It writes LLVM IR as **text** and lets `clang` do optimisation, code
generation and linking. That is what makes it small (about 8k lines) and what lets it be written in Finch itself.
generation and linking. That is what makes it small (about 12k lines) and what lets it be written in Finch itself.
```
main.fch ─► loader ── lex ── parse ──► Program (+ every imported module, recursively)
@ -60,7 +58,7 @@ Design decisions that shape the rest:
Finch type (a string kind: `int`, `f32`, `str`, `array`, `map`, `struct`, `enum`, `named`…) and flags.
- **Let clang do the work.** Locals are `alloca`s promoted by `mem2reg`. Optimisation is clang's `-O2` (or `-O0` with `-g`).
- **Values, not references.** Arrays, strings and structs have value semantics; ownership is decided at compile time (§8).
- **A tiny C runtime** (`runtime/finch_rt.c`, ~760 lines) does what is tedious in IR: strings, array growth, maps, input,
- **A small C runtime** (`runtime/finch_rt.c`, ~1150 lines) does what is tedious in IR: strings, array growth, maps, input,
files, panics. It is compiled once and cached in `~/.cache/finch`.
---
@ -70,17 +68,17 @@ Design decisions that shape the rest:
| File | Lines | Responsibility |
|---|---|---|
| `src/lexer.fch` | 470 | Tokens, positions, `"""` text, `"\(x)"` turned into `("…" + str(x) + "…")` tokens |
| `src/ast.fch` | 120 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/ast.fch` | 170 | One `Node` struct with a `kind` string, children, type names, positions (`line`, `endLine`) |
| `src/parser.fch` | 910 | Recursive descent: declarations, statements, expressions, generics (`[T]`), `enum`, `match`, `export`, `link`, `const`, `test "…" { }` |
| `src/gen.fch` | 6450 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, `-g` debug metadata, the `index` mode |
| `src/gen.fch` | 7030 | Type check + IR: structs, functions, methods, scopes and drops, statements, expressions, built-ins, maps, errors as values, generics (monomorphisation), enums and `match`, vectors (SIMD), `-g` debug metadata, the `index` mode |
| `src/cimport.fch` | 790 | libclang: functions, structs and unions (fields, offsets, padding), enums, constants, globals, macros (also struct-valued) |
| `src/platform.fch` | 90 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 430 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 515 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/platform.fch` | 120 | `Target`: triple, data layout, C compiler, signedness of `char` per system |
| `src/linker.fch` | 480 | The cache (runtime, program object, C/C++ files), libraries (`link`, pkg-config), link-error advice |
| `src/main.fch` | 530 | Driver: CLI, loader with module lookup and overlays, `build/run/test/fmt/ir/index/lsp` |
| `src/manifest.fch` | 240 | `finch.toml`: a small TOML reader, per-target lists |
| `src/fmt.fch` | 225 | `finch fmt` and LSP formatting |
| `src/lsp.fch` | 910 | The language server (§11) |
| `src/docs.fch` | 110 | One description of the built-ins, shared by hover and completion |
| `src/lsp.fch` | 950 | The language server (§11) |
| `src/docs.fch` | 140 | One description of the built-ins, shared by hover and completion |
| `std/*.fch` | 1040 | `json`, `path`, `time` (+`_windows`), `proc` (+`_windows`), `fs`, `thread` |
| `runtime/finch_rt.c` | 1150 | The C runtime: text, arrays, maps, files, float printing, fs, threads, the test runner, the call chain |
| `bootstrap/` | | `build.sh`, `install.sh`, the seeds (§10) |
@ -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
`export fn`s.
`--cpu <name>` (or `cpu` in `finch.toml`) adds `-march=<name>` on x86-64 or `-mcpu=<name>` on ARM64 to the clang that
compiles the IR (not the runtime); it is part of the cache key.
`--target windows|linux|arm64|macos|android|<triple>` changes the triple and data layout, the C `char` signedness, the ABI rules
above, the OS variant of modules (`name_windows.fch`), and the C compiler: `FINCH_CC` or `clang --target=…`. Linking
asks `pkg-config` for flags for `link "lib"`; when the linker fails the output is analysed and the likely missing
@ -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) |
| `ptr`, `ptr[T]`, `null` | `ptr` (opaque) |
| `[N]T` | `[N x T]` |
| `f32x8`, `i8x32`, … | `<8 x float>`, `<32 x i8>` (§7, Vectors) |
| Finch struct | named struct type, natural layout |
| a method's `self` | `ptr` (the first parameter) |
| the result of `-> T!` | `{ i1 ok, T value, str error }` (`i8` in place of `T` for `-> !`) |
@ -235,11 +237,16 @@ Signedness lives only in the Finch type and selects `sdiv/udiv`, `ashr/lshr`, `i
### Values
```cpp
struct Value_ { Value *v; FType type; bool literal; bool fresh; };
```c
struct Val {
str v // the IR operand: %t12, 42, @.str.3, zeroinitializer
ast.Type ty
bool fresh
bool lit
}
```
- **`literal`**: a number written in the code or an imported C constant. It may become any integer type
- **`lit`**: a number written in the code or an imported C constant. It may become any integer type
it fits (`fits()`), or `f32`. It survives unary minus and literal-only arithmetic that folds to a
constant; explicit conversions clear it (`u8(250)` is a typed constant, so `u8(250) + 10` wraps to 4).
- **`fresh`**: an owning value (str/array/owning struct) that nobody holds yet: the result of a call,
@ -281,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
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
own source starts to use a feature the committed seed lacks.
A seed is per system because the C calling convention for the libclang calls is baked into it (§5).
Linux x86-64 and Linux ARM64 have seeds. macOS and Windows would need seeds made with Apple's and Microsoft's headers.
A seed is per system because the C calling convention for the libclang calls is baked into it (§5). There are seeds
for Linux x86-64 and ARM64, macOS ARM64 and x86-64 (made with Apple's SDK headers), and Windows x86-64 (made with
llvm-mingw's headers). CI builds the compiler from each one on that system and runs the tests there.
---
@ -518,13 +558,10 @@ evaluated twice and temporaries are never pointed to.
## 13. Known limitations
- The compiler runs on Linux x86-64 and ARM64 only (no seed for macOS or Windows yet). Programs can be built for all three.
- macOS output is only checked as IR; Windows and ARM64 are run under Wine and qemu in CI.
- One error per run, no recovery.
- `-g` debug info is best with `-O0`.
- The driver uses `sh` utilities (`rm`, `mkdir`), so it needs a POSIX shell.
- Generics are monomorphised (code size grows with instantiations); there are no trait bounds, a wrong use is an error
inside the instantiated body.
- A failed test's owned memory is not freed (the test runner `longjmp`s out of it).
- `--target android` is checked to the object file only; linking needs the NDK and has not been run on a device.
- Vectors have no lane masks (`v < w`), shuffles or integer division.

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.