Files
WAFER/README.md
T
Oleksandr Kozachuk 3bb613ece0 release: 0.2.7
Version bump plus a doc sweep: the benchmark tables in README and
docs/OPTIMIZATIONS.md were still from before the typed calling convention,
OPTIMIZATIONS listed BEGIN loop promotion as not started, and the
subroutine-threading section of docs/WAFER.md described the memory ABI as
the only one.
2026-08-09 17:36:52 +02:00

11 KiB

WAFER

WebAssembly Forth Engine in Rust

An optimizing Forth 2012 compiler targeting WebAssembly. WAFER JIT-compiles each word definition to a separate WASM module and executes it via wasmtime (CLI) or the browser's WebAssembly API (web REPL).

Highlights

  • 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 (2-10x faster)
  • 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
  • Consolidation mode — recompile all words into a single optimized WASM module
  • Interactive REPL with line editing (rustyline)
  • Browser REPL — runs entirely in the browser via wasm-pack + js-sys
  • Runtime abstractionForthVM<R: Runtime> is generic over execution backend (wasmtime or browser)

Installation

Requires Rust 1.85+ (edition 2024).

cargo install --git https://github.com/ok2/wafer.git wafer

This installs the wafer binary to ~/.cargo/bin/.

To install from a local checkout:

cargo install --path crates/cli

Usage

# Interactive REPL (type BYE to exit)
wafer

# Run a Forth file
wafer program.fth

# Pipe input
echo ': SQUARE DUP * ; 7 SQUARE .' | wafer

# Consolidation: recompile all words into a single optimized WASM module
wafer --consolidate program.fth

# Consolidation with WASM output
wafer --consolidate -o output.wasm program.fth

Example REPL session:

: FIB DUP 2 < IF DROP 1 ELSE DUP 1 - RECURSE SWAP 2 - RECURSE + THEN ;
: FIBS 0 DO I FIB . LOOP ;
12 FIBS CR    \ prints: 1 1 2 3 5 8 13 21 34 55 89 144

VARIABLE COUNTER  0 COUNTER !
: BUMP COUNTER @ 1 + COUNTER ! ;
BUMP BUMP BUMP COUNTER @ .  \ prints: 3

Building from source

git clone --recurse-submodules https://github.com/ok2/wafer.git
cd wafer
cargo build --workspace --release

If you already cloned without --recurse-submodules, fetch the Forth 2012 test suite with:

git submodule update --init

Performance

WAFER beats gforth (the GNU Forth reference implementation) on all benchmarks in release mode, and is within reach of SwiftForth sf64, which compiles to native code:

Benchmark                   WAFER     CONSOL     gforth       sf64    WAFER/gf   WAFER/sf
Fibonacci(25)                 356        361       3389        287       0.11x      1.24x
Factorial(12)x100K            479        495       6249       1650       0.08x      0.29x
GCD-bench(20K)                540        559       1801        801       0.30x      0.67x
NestedLoops(50)x1K            509        501       7023       1887       0.07x      0.27x
Collatz(2K)                   185        213       3873        610       0.05x      0.30x

Times in microseconds. WAFER/gf < 1.0 means WAFER is faster. CONSOL = after CONSOLIDATE.

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. WAFER is ahead on the four loop-heavy benchmarks and behind on Fibonacci, which is one call per node with no loop to promote.

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 the way a native Forth keeps TOS in one. The word also keeps a ( -- ) wrapper, which is what the function table, EXECUTE and the outer interpreter reach, so nothing about the memory ABI changes from the outside. Call-heavy code is what this pays for -- Fibonacci went from 4.3x slower than sf64 to 1.2x. Set WAFER_TYPED_CALLS=0 to fall back to the memory-stack convention.

Testing

# All tests (~628 currently passing)
cargo test --workspace

# Forth 2012 compliance suite
cargo test -p wafer-core --test compliance

# Cross-engine comparison (WAFER vs gforth, requires gforth)
cargo test -p wafer-core --test comparison -- --nocapture --ignored

# Optimization benchmark report (WAFER-internal)
cargo test -p wafer-core --test benchmark_report -- --nocapture --ignored

# Lints
cargo clippy --workspace

Architecture

Forth Source -> Outer Interpreter -> IR -> [Optimize] -> WASM Codegen (wasm-encoder)
                                                              |
                                                    Runtime trait instantiation
                                                    (shared memory + table)
                                                         /           \
                                              NativeRuntime      WebRuntime
                                              (wasmtime)         (js-sys)
  • Runtime abstraction: ForthVM<R: Runtime> separates the compiler from the execution engine
    • NativeRuntime — wasmtime-based, for CLI, tests, and AOT compilation
    • WebRuntime — browser WebAssembly API via js-sys, for the browser REPL
  • Subroutine threading via WASM function tables (call_indirect for cross-word, direct call for self-recursion)
  • JIT mode: each new word compiles to a separate WASM module linked to shared memory/globals/table
  • IR-based pipeline with 6 optimization passes (peephole, constant folding, strength reduction, DCE, tail call detection, inlining) plus per-region stack-to-local promotion (DO and BEGIN loops, IF/ELSE), DO/LOOP index locals, typed entry points for words with a known stack effect, and consolidation
  • Dictionary: linked-list word headers in simulated linear memory

Project Structure

crates/
  core/       wafer-core: dictionary, IR, codegen, optimizer, outer interpreter, Runtime trait
  cli/        wafer: CLI REPL, file execution, consolidation
  web/        wafer-web: browser REPL (wasm-bindgen + WebRuntime + HTML/CSS/JS frontend)
tests/        Forth 2012 compliance suite (git submodule)

Forth 2012 Compliance

Tested against Gerry Jackson's Forth 2012 test suite. 12 of 14 word sets pass at 100%.

Word Set Status
Core 100% (0 errors)
Core Plus 100% (0 errors)
Core Extensions 100% (0 errors)
Double-Number 100% (0 errors)
Exception 100% (0 errors)
Facility 100% (0 errors)
Floating-Point 100% (0 errors)
Locals 100% (0 errors)
Memory-Allocation 100% (0 errors)
Programming-Tools 100% (0 errors)
Search-Order 100% (0 errors)
String 100% (0 errors)
File-Access Not started (requires WASI integration)
Extended-Character Not started

Implemented Words

Over 200 words are implemented across the following categories:

Category Words
Stack DUP DROP SWAP OVER ROT NIP TUCK 2DUP 2DROP 2SWAP 2OVER ?DUP PICK DEPTH
Arithmetic + - * / MOD /MOD NEGATE ABS MIN MAX 1+ 1- 2* 2/ */ */MOD M* UM* UM/MOD FM/MOD SM/REM S>D <# # #S #> HOLD SIGN
Comparison = <> < > U< 0= 0< 0<> 0> WITHIN
Logic AND OR XOR INVERT LSHIFT RSHIFT
Memory @ ! C@ C! +! 2@ 2! HERE ALLOT , C, CELLS CELL+ CHARS CHAR+ ALIGNED ALIGN MOVE FILL CMOVE CMOVE>
Control IF ELSE THEN DO LOOP +LOOP I J UNLOOP LEAVE BEGIN UNTIL WHILE REPEAT RECURSE EXIT
Defining : ; VARIABLE CONSTANT VALUE CREATE DOES> IMMEDIATE DEFER
I/O . U. .S CR EMIT SPACE SPACES TYPE ." S" ACCEPT
Return stack >R R> R@
System EXECUTE ' CHAR [CHAR] ['] DECIMAL HEX BASE STATE >IN >BODY ENVIRONMENT? SOURCE ABORT TRUE FALSE BL
Compiler LITERAL POSTPONE [ ] EVALUATE ABORT"
Parsing WORD FIND COUNT >NUMBER
Exceptions CATCH THROW
Double-cell D+ D- D. D.R DNEGATE DABS D= D< D0= D0< D>S 2CONSTANT 2VARIABLE 2LITERAL M+ M*/
Strings COMPARE SEARCH SLITERAL REPLACES SUBSTITUTE UNESCAPE
Floating-Pt F+ F- F* F/ FABS FNEGATE FSQRT FSIN FCOS FTAN FEXP FLOG FMIN FMAX and 55+ more
Case CASE OF ENDOF ENDCASE
Tools WORDS SEE SEE-IR HELP INCLUDE INCLUDED .S F.S ? DUMP MARKER REMEMBER EMPTY GILD BYE

Web REPL

Build and run the browser-based REPL:

cd crates/web
wasm-pack build --target web --out-dir www/pkg
python3 -m http.server -d www 8080
# Open http://localhost:8080/

Roadmap

  • File-Access word set — requires WASI integration for file I/O
  • Extended-Character word set — Unicode support
  • Self-hosting — minimal Rust kernel (~35 primitives), everything else in Forth

License

MIT OR Apache-2.0