Skip to content

mbrock/wisp

Repository files navigation

 █     █░ ██▓  ██████  ██▓███
▓█░ █ ░█░▓██▒▒██    ▒ ▓██░  ██▒
▒█░ █ ░█ ▒██▒░ ▓██▄   ▓██░ ██▓▒
░█░ █ ░█ ░██░  ▒   ██▒▒██▄█▓▒ ▒
░░██▒██▓ ░██░▒██████▒▒▒██▒ ░  ░
░ ▓░▒ ▒  ░▓  ▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░
  ▒ ░ ░   ▒ ░░ ░▒  ░ ░░▒ ░
  ░   ░   ▒ ░░  ░  ░  ░░
    ░     ░        ░

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!

Building

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 test

Evaluator profiling

Run the diagnostic evaluator suite with semantic counters enabled:

make bench

The 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 -- --list

The 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:

  • tak uses the historical (18 12 6) input and stresses non-tail calls and argument binding.
  • deriv performs symbolic differentiation and allocates syntax trees.
  • diviter and divrec divide 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-list runs a map/append/reverse/remove pipeline using the boot core’s list functions.
  • backquote calls the boot core’s actual bq-completely-process implementation on a nested unquote and unquote-splicing form.
  • router-hit and router-miss preserve web/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 100

The 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 5000000

The resulting trace opens in Instruments. On Linux, the older etc/profile script provides a starting point for recording with perf.

Cross-language sweep

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-sweep

The 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.

Browser development

Start the Wisp development server and open http://127.0.0.1:8765/ in a browser:

./wisp-web start

Evaluate Wisp inside that live browser image from the shell:

./wisp-web '(+ 20 22)'
./wisp-web - < program.wisp

The 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 1

GitHub Pages

Every 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.

Arrow heap export

Export the native boot heap as one Apache Arrow IPC file:

./core/zig-out/bin/wisp arrow wisp.arrow

The 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.sql

Local Variables

Local Variables: fill-column: 62 End:

Releases

Packages

Used by

Contributors

Languages