█ █░ ██▓ ██████ ██▓███
▓█░ █ ░█░▓██▒▒██ ▒ ▓██░ ██▒
▒█░ █ ░█ ▒██▒░ ▓██▄ ▓██░ ██▓▒
░█░ █ ░█ ░██░ ▒ ██▒▒██▄█▓▒ ▒
░░██▒██▓ ░██░▒██████▒▒▒██▒ ░ ░
░ ▓░▒ ▒ ░▓ ▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░
▒ ░ ░ ▒ ░░ ░▒ ░ ░░▒ ░
░ ░ ▒ ░░ ░ ░ ░░
░ ░ ░
Wisp is a Lisp system for WebAssembly and native execution.
It aims to offer an unusually pleasant developer experience both while hacking and in production, though it’s still very immature and far from ready for any serious use.
You can try the live browser-based IDE at Wisp.Town.
Feel free to join the Wisp Discord chat if you’re interested in following the development work, hacking on the system, or chatting about Lisp, WebAssembly, and Zig.
Wisp has:
- a browser-based IDE for live hacking and interactive debugging;
- saving and loading of full system state images, including debugger state, also in browser local storage;
- thoughtful integration with the browser environment, Node, Deno, and Unix in general;
- first-class delimited continuations for advanced control flow;
- interactive restartable conditions in the style of Common Lisp;
- first-class stepwise execution for custom debuggers, schedulers, etc;
- sandboxed execution with fine-grained capabilities via WebAssembly;
- an implementation written in Zig;
- and more!
Wisp follows Zig master and currently requires
0.17.0-dev.1465+8b2d0ce21. The exact lower bound is also
recorded in core/build.zig.zon.
make
make testRun the diagnostic evaluator suite with semantic counters enabled:
make benchThe benchmark runner writes one JSON object per case. A single case and iteration count can be selected explicitly:
cd core
zig build bench -Doptimize=ReleaseFast -- call-16 25000
zig build bench -Doptimize=ReleaseFast -- --listThe counters cover evaluator steps, calls, argument-list scans and
reversals, lexical lookup depth and comparisons, continuation
searches, captures, and copied frames, logical heap allocations, GC
copies, and GC time. The final bucket in the call_arity and
lexical_depth histograms means 16 or more. Core loading,
benchmark setup, and parsing are excluded from the counters and
elapsed time.
The suite has two layers. The call-* and lookup-*
diagnostics isolate evaluator mechanisms. Four small
Gabriel-derived programs exercise them in recognizable Lisp
workloads:
takuses the historical(18 12 6)input and stresses non-tail calls and argument binding.derivperforms symbolic differentiation and allocates syntax trees.diviteranddivrecdivide a prebuilt list of 1000 =NIL=s, contrasting tail-recursive and non-tail-recursive traversal.
Four repository-derived cases exercise Wisp as it is used here:
stdlib-listruns a map/append/reverse/remove pipeline using the boot core’s list functions.backquotecalls the boot core’s actualbq-completely-processimplementation on a nested unquote and unquote-splicing form.router-hitandrouter-misspreserveweb/http.wisp’s route matcher and nested continuation-prompt search, with fixed data in place of the Deno request and response bridge.
Each program checks its result after timing. When no iteration count is supplied, each case uses a workload-appropriate default; an explicit count still overrides it:
cd core
zig build bench -Doptimize=ReleaseFast -- tak 1
zig build bench -Doptimize=ReleaseFast -- deriv 100The JSON input field identifies the fixed program workload.
Sources are adapted from the
R7RS benchmark collection; the repetition count is the
harness parameter and is not part of that fixed input.
For an uncontaminated native CPU profile, compile the semantic counters out and record a sufficiently long single case:
cd core
zig build bench-build \
-Doptimize=ReleaseFast \
-Dsemantic-profile=false
xcrun xctrace record \
--template 'Time Profiler' \
--output /tmp/wisp-call-16.trace \
--launch -- \
"$PWD/zig-out/bin/wisp-bench" call-16 5000000The resulting trace opens in Instruments. On Linux, the older
etc/profile script provides a starting point for recording with
perf.
Run the Gabriel workloads in Wisp, Python, Ruby, Tcl, JavaScript, Wren, Chibi-Scheme, Racket, Common Lisp, and optimized C. The repository-derived Lisp cases run in Wisp, Racket, Chibi-Scheme, and Common Lisp; the continuation router currently runs in Wisp and Racket:
make bench-sweepThe sweep verifies results, takes five samples by default, prints per-case rankings, a Wisp-focused comparison with the slowest other runtime in each case, and a normalized geometric mean for runtimes that support every selected case. It saves raw JSONL samples outside version control. Unsupported cases are omitted rather than replaced with unlike mechanisms. Its pinned Wren and Chibi-Scheme runtimes are built locally on first use, not installed system-wide. See the cross-language benchmark notes for workload and comparability details.
Start the Wisp development server and open http://127.0.0.1:8765/ in a browser:
./wisp-web startEvaluate Wisp inside that live browser image from the shell:
./wisp-web '(+ 20 22)'
./wisp-web - < program.wispThe result comes back as a Wisp form rendered by Wisp’s own pretty printer. Conditions remain suspended in the browser and can be resumed from a later shell invocation:
./wisp-web '(oopsfail!)'
./wisp-web tickets
./wisp-web use 1 42
./wisp-web retry 1
./wisp-web abort 1Every push to master builds, tests, and publishes the browser
workbench at https://mbrock.github.io/wisp/. The workbench
uses only relative asset URLs, so the same files work at the
repository subpath and at a custom domain.
The listener’s download button compacts the active Wisp system
and downloads its complete tape image as a .core file. A
small service worker keeps the static workbench available after
it has been visited once. Local named images can later use the
same snapshot bytes in IndexedDB without changing the Wisp tape
format.
Export the native boot heap as one Apache Arrow IPC file:
./core/zig-out/bin/wisp arrow wisp.arrowThe file contains one nested heap record. Its byte and word
arenas are Arrow arrays, and every Vat table is a list of
structs backed by the table’s physical columns. Zig presents
those existing buffers through the Arrow C Data Interface;
vendored nanoarrow 0.8.0 writes the IPC envelope. The included
core/arrow.sql adds DuckDB views for the nested tables, Wisp
word decoding, strings, and symbols:
duckdb < core/arrow.sqlLocal Variables: fill-column: 62 End: