Status: Pre-Alpha / Experimental
ClojureWasm is under active development. APIs may change, and there are behavioral differences from reference Clojure. Bugs and rough edges are expected. See DIFFERENCES.md for details.
Verified on: macOS (Apple Silicon / aarch64) and Linux (x86_64). Cross-compiles to aarch64-linux. All test suites pass on both platforms.
A Clojure runtime written from scratch in Zig. No JVM, no transpilation — a native implementation targeting behavioral compatibility with Clojure.
- Fast startup — ~4ms to evaluate an expression (ReleaseSafe)
- Small binary — ~4MB single executable (ReleaseSafe)
- Single binary distribution —
cljw build app.clj -o app, runs without cljw installed (temporarily disabled during the Zig 0.16 migration; see note below) - Wasm FFI — call WebAssembly modules from Clojure (523 opcodes including SIMD + GC)
- Dual backend — bytecode VM (default) + TreeWalk interpreter (reference)
- deps.edn compatible — Clojure CLI subset (-A/-M/-X/-P, git deps, local deps)
- 1130+ vars across 30+ namespaces (651/706 clojure.core)
- Zig 0.16.0 (or
nix developfor a pinned environment)
zig build # Debug build
zig build -Doptimize=ReleaseSafe # Optimized build./zig-out/bin/cljw -e '(println "Hello, world!")' # Evaluate expression
./zig-out/bin/cljw script.clj # Run a file
./zig-out/bin/cljw # Interactive REPL# Download dependencies (git deps require explicit -P)
./zig-out/bin/cljw -P
# Run with aliases
./zig-out/bin/cljw -M:run # Main opts
./zig-out/bin/cljw -X:build # Exec function
./zig-out/bin/cljw -A:dev src/app.clj # Extra paths
./zig-out/bin/cljw -Spath # Show classpath
# Run tests
./zig-out/bin/cljw test # Auto-discover test/
./zig-out/bin/cljw test -A:test # With aliasSupports :local/root, :git/url+:git/sha, :deps/root, transitive deps.
No Maven/Clojars support (git deps and local deps only).
./zig-out/bin/cljw build app.clj -o myapp
./myapp # Runs without cljwNote (Zig 0.16 migration):
cljw build, the nREPL server, and the built-in HTTP server/client are temporarily disabled while their backing stdlib APIs (std.fs.selfExePath,std.net.Server,std.http.Client) migrate to the newstd.Iomodel. Each prints a clear runtime error when invoked. Tracked in.dev/checklist.mdF140-F144 (target: next minor release after this one). Usezig build && cljw <file.clj>to run apps in the meantime.
./zig-out/bin/cljw --nrepl-server --port=7888 app.cljConnect from Emacs CIDER or any nREPL client. 14 ops supported (eval, complete, info, stacktrace, eldoc, etc.). (See the build note above — temporarily unavailable while the std.net migration lands.)
Each namespace targets behavioral equivalence with its Clojure JVM counterpart. Known divergences are documented in DIFFERENCES.md.
Core Language
| Namespace | Vars | Description |
|---|---|---|
| clojure.core | 651/706 | Core language functions |
| clojure.core.protocols | 10/11 | CollReduce, IKVReduce, Datafiable |
| clojure.core.reducers | 22/22 | Parallel fold, monoid, reducers |
Standard Library
| Namespace | Vars | Description |
|---|---|---|
| clojure.string | 21/21 | String manipulation |
| clojure.math | 45/45 | Math functions |
| clojure.set | 12/12 | Set operations |
| clojure.walk | 10/10 | Tree walking |
| clojure.zip | 28/28 | Zipper data structure |
| clojure.data | 5/5 | Data diff |
| clojure.edn | 2/2 | EDN reader |
| clojure.template | 2/2 | Code templates |
| clojure.xml | 7/9 | XML parsing (pure Clojure) |
| clojure.datafy | 2/2 | datafy/nav protocols |
| clojure.instant | 3/5 | #inst reader, RFC3339 parser |
| clojure.uuid | — | #uuid data reader (reader only) |
Spec
| Namespace | Vars | Description |
|---|---|---|
| clojure.spec.alpha | 87/87 | Spec validation, s/def, s/valid? |
| clojure.spec.gen.alpha | 54/54 | Spec generators |
| clojure.core.specs.alpha | 1/1 | Spec for core macros |
Dev & Test
| Namespace | Vars | Description |
|---|---|---|
| clojure.test | 38/39 | Test framework |
| clojure.test.tap | 7/7 | TAP output formatter |
| clojure.repl | 11/13 | doc, dir, apropos, source, pst |
| clojure.pprint | 26/26 | Pretty printing, print-table |
| clojure.stacktrace | 6/6 | Stack trace utilities |
| clojure.main | 16/20 | REPL, script loading, ex-triage |
IO & System
| Namespace | Vars | Description |
|---|---|---|
| clojure.java.io | 19/19 | File I/O (Zig-native) |
| clojure.java.shell | 5/5 | Shell commands (sh) |
| clojure.java.browse | 2/2 | Open URL in browser |
| clojure.java.process | 5/9 | Process API (Clojure 1.12) |
Infrastructure (stubs — requireable, API surface for compatibility)
| Namespace | Vars | Description |
|---|---|---|
| clojure.core.server | 7/11 | Socket REPL, prepl (partial) |
| clojure.repl.deps | 3/3 | Dynamic lib addition (stub) |
ClojureWasm Extensions
| Namespace | Vars | Description |
|---|---|---|
| cljw.wasm | 17/17 | WebAssembly FFI |
| cljw.http | 6/6 | HTTP server/client |
Not implemented (JVM-only): clojure.reflect, clojure.inspector, clojure.java.javadoc, clojure.test.junit
Call WebAssembly modules directly from Clojure:
(require '[cljw.wasm :as wasm])
(def mod (wasm/load "add.wasm"))
(def add (wasm/fn mod "add" [:i32 :i32] :i32))
(add 1 2) ;=> 3- 523 opcodes (236 core + 256 SIMD + 31 GC)
- All Wasm 3.0 proposals (9/9 including GC, function references, exception handling)
- WASI support (file I/O, clock, random, args, environ)
- Multi-module linking with cross-module imports
- Predecoded IR with superinstructions for optimized dispatch
Performance note: The Wasm runtime (zwasm) uses Register IR with ARM64/x86_64 JIT. Full Wasm 3.0 support (all 9 proposals including GC, function references, SIMD, exception handling). zwasm wins 14/23 benchmarks vs wasmtime, with ~43x smaller binary.
(require '[cljw.http :as http])
(defn handler [req]
(case (:uri req)
"/hello" {:status 200 :body "Hello!"}
{:status 404 :body "Not Found"}))
(http/run-server handler {:port 8080})- Ring-compatible handler model
- HTTP client:
http/get,http/post,http/put,http/delete - nREPL in built binaries (
./myapp --nrepl 7888) - SIGINT/SIGTERM graceful shutdown with hooks
Temporarily disabled during the Zig 0.16 migration — see the build note earlier in this README. Tracked as
F140/F141/F142in.dev/checklist.md.
- NaN-boxed Value — 8-byte tagged representation (float pass-through, i48 integer, 40-bit heap pointer)
- MarkSweep GC — allocation tracking, free-pool recycling, safe points
- Bytecode VM — 75 opcodes, superinstructions, fused branch ops
- ARM64 JIT — hot integer loop detection with native code generation
- Bootstrap cache — core.clj pre-compiled at build time (~5ms restore)
- deps.edn projects — Clojure CLI compatible config (git deps, local deps, aliases)
src/
├── main.zig CLI entry point
├── root.zig Library root
├── clj/clojure/ Clojure source files
│ ├── core.clj Core library (~2400 lines)
│ └── string.clj, set.clj... Standard library namespaces
│
├── runtime/ Layer 0: Value, collections, GC, environment
├── engine/ Layer 1: Reader, Analyzer, Compiler, VM, TreeWalk
│ ├── reader/ Source → Form
│ ├── analyzer/ Form → Node
│ ├── compiler/ Node → Bytecode
│ ├── vm/ Bytecode execution (+ ARM64 JIT)
│ └── evaluator/ TreeWalk interpreter
├── lang/ Layer 2: Built-in functions, interop, lib namespaces
├── app/ Layer 3: CLI, REPL, deps.edn, Wasm bridge
└── regex/ Regex engine
bench/ 31 benchmarks, multi-language
test/ 83 Clojure test namespaces (54 upstream ports)
Strict 4-zone layered architecture: lower layers never import from higher layers. Zone dependencies enforced by CI gate (0 violations).
The .dev/ directory contains design decisions, optimization logs,
and development notes.
The benchmark suite is in bench/ with 31 programs
covering computation, collections, higher-order functions, GC pressure, and Wasm.
# Requires hyperfine
bash bench/run_bench.sh # All benchmarks (ReleaseSafe)
bash bench/run_bench.sh --quick # Quick check (1 run)
bash bench/run_bench.sh --bench=fib_recursive # Single benchmarkzig build test # 1,300+ Zig test blocks
bash test/e2e/run_e2e.sh # End-to-end tests (6 wasm)
bash test/e2e/deps/run_deps_e2e.sh # deps.edn E2E tests (14)83 Clojure test namespaces including 54 upstream test ports with 600+ deftests. All tests verified on both VM and TreeWalk backends.
- JIT expansion — float operations, function calls, broader loop patterns
- Generational GC — nursery/tenured generations for throughput
- Persistent data structures — RRB-Tree for vectors (HAMT for maps: done)
- wasm_rt — compile Clojure to run inside WebAssembly
Once production-ready, ClojureWasm could enable workloads where the JVM is too heavy:
- Serverless functions — ~4MB image + ~4ms cold start for AWS Lambda or Fly.io, eliminating JVM warm-up penalties
- Wasm plugin host — embed user-supplied .wasm modules as extensibility points (e.g., Cloudflare Workers-style logic, game scripting)
- Edge / IoT — run Clojure on Raspberry Pi or resource-constrained devices where a JVM runtime is impractical
Built on Clojure by Rich Hickey and Zig by Andrew Kelley. Includes adapted Clojure standard library code and ported test cases (EPL-1.0). See NOTICE for attribution details.
Eclipse Public License 1.0 (EPL-1.0)
Copyright (c) 2026 chaploud
Developed in spare time alongside a day job. If you'd like to support continued development, sponsorship is welcome via GitHub Sponsors.