release: 0.2.8
CI / check (push) Has been cancelled

Ships the self-guard expansion, and corrects what the benchmark tables claim.
Measured with wafer, gforth and SwiftForth all native on x86-64 -- the macOS
sf64 build runs under Rosetta 2 and flatters us -- Fibonacci is 1.16x rather
than 0.83x, so sf64 still wins it and wafer takes the other four. README and
OPTIMIZATIONS now carry both tables.
This commit is contained in:
Oleksandr Kozachuk
2026-08-10 12:48:33 +02:00
parent e963e636d3
commit 4f96f8860a
8 changed files with 68 additions and 24 deletions
+32 -8
View File
@@ -8,7 +8,7 @@ An optimizing Forth 2012 compiler targeting WebAssembly. WAFER JIT-compiles each
- **200+ words** across 12 Forth 2012 word sets, all at **100% compliance**
- **Optimizing compiler** with 6 IR passes + stack-to-local promotion (per region, so a hot loop keeps its registers even inside a word that does I/O; `DO` and `BEGIN` loops alike) + consolidation
- **Faster than gforth** on all benchmarks in release mode (3-20x), and past SwiftForth `sf64` on all five
- **Faster than gforth** on every benchmark, and past SwiftForth `sf64` -- a native-code compiler -- on four of five
- **JIT compilation** — each `:` definition compiles to its own WASM module
- **Self-recursive direct calls** — RECURSE compiles to native `call` instead of `call_indirect`
- **Typed calling convention** — a word with a statically known stack effect passes its stack items as WASM values, so a call keeps them in registers instead of round-tripping through memory
@@ -80,8 +80,29 @@ git submodule update --init
## Performance
WAFER beats gforth (the GNU Forth reference implementation) on all benchmarks in release mode, and
SwiftForth `sf64`, which compiles to native code, on all five:
WAFER beats gforth (the GNU Forth reference implementation) on every benchmark, and SwiftForth
`sf64` -- which compiles to native code -- on four of the five.
Measured with all three engines running **native x86-64**, on an idle 16-vCPU Xeon Platinum 8124M
@ 3.0 GHz (median of three runs):
```
Benchmark WAFER gforth sf64 WAFER/gf WAFER/sf
Fibonacci(25) 411 3221 355 0.13x 1.16x
Factorial(12)x100K 994 7141 3058 0.14x 0.33x
GCD-bench(20K) 1591 3211 2423 0.50x 0.66x
NestedLoops(50)x1K 889 6824 2342 0.13x 0.38x
Collatz(2K) 391 3981 1659 0.10x 0.24x
```
Times in microseconds; WAFER is the better of the JIT and `CONSOLIDATE` runs. Below 1.0 means WAFER
is faster. Fibonacci is the one WAFER loses: it is one call per node with no loop to promote, and
`sf64` keeps its stack in registers across a call the way only a native code generator can.
Fibonacci, GCD and Collatz held to within 2% across the three runs; Factorial and NestedLoops are
softer, since `sf64` varied by half there, but they are wide wins either way.
`just bench-compare` on the development machine (M1 Ultra, arm64) reports different numbers, and
they flatter WAFER:
```
Benchmark WAFER CONSOL gforth sf64 WAFER/gf WAFER/sf
@@ -92,11 +113,14 @@ NestedLoops(50)x1K 501 509 7092 1898 0.07x
Collatz(2K) 196 190 3955 633 0.05x 0.30x
```
Times in microseconds. WAFER/gf < 1.0 means WAFER is faster. CONSOL = after `CONSOLIDATE`.
The only SwiftForth build for macOS is x86-64 under Rosetta 2, while WAFER and gforth are native
arm64 -- so that `sf64` column is native against emulated. The gap is not small, and it lands
exactly where it matters: Fibonacci reads 0.83x there and 1.16x when neither engine is emulated.
Treat the arm64 table as what the regression limits in `comparison.rs` are calibrated against, and
the x86-64 table as what to believe about the engines.
Two caveats on the `sf64` column. The SwiftForth build here is x86-64 running under Rosetta 2
while WAFER and gforth are native arm64, so it is a native-vs-emulated comparison; and sf64
uses 64-bit cells to WAFER's 32-bit.
A caveat applies to both: `sf64` uses 64-bit cells to WAFER's 32-bit, so WAFER does less work per
operation.
A word whose stack effect is statically known gets a **typed entry point**: its stack items travel in and out
as WASM values instead of through the memory data stack, so cranelift keeps them in registers across a call
@@ -108,7 +132,7 @@ Call-heavy code is what this pays for -- Fibonacci went from 4.3x slower than `s
Recursive words then get one more thing: their base-case guard is tested at the **call site**, so a
leaf of the recursion costs a comparison instead of a call. `: FIB DUP 2 < IF EXIT THEN ... RECURSE`
compiles its `RECURSE` as `DUP 2 < IF ELSE RECURSE THEN`, which is what the callee would have done
on entry anyway. Half of fib's nodes are leaves, and that is the last 1.4x.
on entry anyway. Half of fib's nodes are leaves, and that is worth 1.4x.
## Testing