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
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue