Oleksandr Kozachuk d55a27873e fix(build): exempt build scripts and proc-macros from release strip
`wasm-pack build --release` died with "can't find crate" for rustversion,
then thiserror_impl, then every other proc-macro. Cargo strips debuginfo
from release artifacts by default and on macOS that takes the metadata
proc-macro dylibs need to be loadable with it, so rustc could no longer
open them.

Debug builds are unstripped, which is why the whole test suite stayed
green while the browser REPL could not be built for production at all.

Stripping buys nothing for build scripts and proc-macros, so
[profile.release.build-override] exempts them; release binaries stay
stripped.

Also pins wafer-core to 0.2.3 in wafer-web and wafer-cli — both still
asked for 0.2.1. The caret requirement resolved, so nothing broke.
2026-08-06 20:16:41 +02:00
2026-04-09 20:11:03 +02:00
2026-08-06 12:44:34 +02:00

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 (loops + IF) + 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
  • 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:

Benchmark                   WAFER     CONSOL     gforth      WAFER/gf
Fibonacci(25)                1629       1535       3422        0.45x
Factorial(12)x10K             340        339        638        0.53x
GCD-bench(500)                 18         15         30        0.50x
NestedLoops(50)                84         73        720        0.10x
Collatz(2K)                  1212       1202       3914        0.31x

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

Testing

# All tests (~570 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 stack-to-local promotion (with loop and IF/ELSE support), DO/LOOP index locals, 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

S
Description
No description provided
Readme 4.4 MiB
Languages
Rust 92.4%
Python 3.9%
Forth 1.3%
JavaScript 1%
CSS 0.8%
Other 0.6%