Skip to content

Commit 88a128f

Browse files
committed
Status and README catch up with S1: cljwit.host works, and says how
status.md's Next section still listed 0016 and 0017 as pending; both are done, with 0018 and 0019 behind them, so Next is now the S1 close-out -- its one named capability gap (a resource nested in a container in an import's signature) -- the unblocked 0015 revisit, and S2. The README promoted cljwit.host from "planned" to what it is: working, pre-alpha. A five-minute section shows the three lifetimes, imports, WASI and the value mapping, with the git-dep coordinates and the Java 22+ / libwasmtime >= 43 floor spelled out. Claude-Session: https://claude.ai/code/session_01XF5Hfq4Ca2N2XYEzWQQuHt
1 parent c62a9e4 commit 88a128f

2 files changed

Lines changed: 80 additions & 39 deletions

File tree

README.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,61 @@ The name is from [WIT], the language-neutral interface definition language of th
1515
[Component Model]. WIT is the seam; ClojureWit is what sits on the Clojure side
1616
of it.
1717

18-
> **Status: pre-alpha, design + feasibility phase.** There is no compiler yet.
19-
> The first milestone is a set of benchmarks that decide whether the core design
20-
> is viable at all — see [`doc/roadmap.md`](doc/roadmap.md). Nothing here is
21-
> stable; do not build on it.
18+
> **Status: pre-alpha.** There is no compiler yet. The feasibility benchmarks
19+
> are measured and the design survived them — the verdict is
20+
> [`doc/design/0010`](doc/design/0010-s0-verdict.md). `cljwit.host` works
21+
> today; its API is not yet stable. See [`doc/roadmap.md`](doc/roadmap.md).
2222
2323
## Two deliverables
2424

2525
| | What | State |
2626
|---|---|---|
27-
| **`cljwit.host`** | A plain Clojure library: call Wasm components from JVM Clojure. No new dialect, no compiler. | planned |
27+
| **`cljwit.host`** | A plain Clojure library: call Wasm components from JVM Clojure. No new dialect, no compiler. | **works, pre-alpha** |
2828
| **`cljwit`** | The compiler: Clojure → Wasm component (WasmGC, tail calls, Wasm 3.0). | design |
2929

3030
`cljwit.host` ships first and stands on its own. It also settles the WIT ↔ Clojure
3131
type mapping that the compiler needs, so the hard part gets solved once.
3232

33+
## `cljwit.host` in five minutes
34+
35+
Requirements: **Java 22+** (`java.lang.foreign`; developed on 25) and
36+
**libwasmtime ≥ 43**`brew install wasmtime` provides it, or unpack a
37+
[`*-c-api` release tarball](https://github.com/bytecodealliance/wasmtime/releases)
38+
and point `CLJWIT_WASMTIME_LIB` at the shared library
39+
([`doc/design/0019`](doc/design/0019-finding-libwasmtime.md) has the full
40+
resolution order).
41+
42+
```clojure
43+
;; deps.edn
44+
{:deps {io.github.clojurewasm/ClojureWit
45+
{:git/sha "..."}} ;; pre-alpha: pin a sha, expect breakage
46+
:aliases {:dev {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
47+
```
48+
49+
```clojure
50+
(require '[cljwit.host :as host])
51+
52+
;; Three lifetimes, three orders of magnitude apart (0014):
53+
(with-open [e (host/engine) ; process-wide, share it
54+
a (host/compile e "component.wasm") ; milliseconds — cache it
55+
i (host/instantiate a)] ; ~0.06 ms — one per request is fine
56+
;; Exports are discovered from the component itself. Names are the exact
57+
;; WIT strings; keywords work where the name reads back as itself.
58+
((i "greet") "world") ; => "hello, world"
59+
((i "wasi:cli/run@0.2.3#run"))) ; interface functions too
60+
61+
;; The guest can call *you*: imports are ordinary Clojure functions,
62+
;; and WASI capabilities are named explicitly or absent (deny-by-default).
63+
(host/instantiate a {:imports {"acme:log/sink@1.0.0#write" println}
64+
:wasi {:inherit-stdout true :args ["-v"]}})
65+
```
66+
67+
WIT values arrive as the Clojure data you would guess: `record` → map,
68+
`variant``[:tag payload]`, `enum` → keyword, `flags` → set, `option`
69+
value-or-nil, `result``[:ok v]`/`[:err e]`, resources → `AutoCloseable`
70+
handles. The full mapping — and why it is not negotiable — is
71+
[`doc/design/0012`](doc/design/0012-wit-clojure-type-mapping.md).
72+
3373
## Why now
3474

3575
Three things landed that were not true a year ago:

doc/status.md

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,31 @@
33
_Short by design, and printed at every session start — so findings live in
44
`doc/design/`, plans in `doc/roadmap.md`, and only the present tense here._
55

6-
**Updated:** 2026-07-30 · **Phase:** S0 closed, entering S1 (pre-alpha, no compiler)
6+
**Updated:** 2026-07-30 · **Phase:** S1 delivered, closing (pre-alpha, no compiler)
77

88
## Next
99

10-
1. **Implement `0016``own<T>` handles.** `flags` and `tuple` now marshal,
11-
so **4 of `dev/resources/zoo.wit`'s 8 exports are callable**; the other four
12-
are resource methods. `0016` decides the shape (opaque `AutoCloseable`,
13-
`close` as `try { drop } finally { delete }`, lowering an `own` transfers,
14-
the instance closes outstanding handles before deleting its store) and
15-
`borrow` is out of scope because it **cannot appear in a return position**.
16-
**Done.** `cljwit.host` marshals every `0012` row that a component can
17-
express today. What is left of `0012` is `map`, `list<T,N>`,
18-
`stream`/`future` and `error-context`, none of which are in a shipped
19-
release.
20-
3. **Host imports (`0017`).** The mechanism works — an FFM upcall stub reaches
21-
Clojure from inside a component call (`bb spike-import`), pure Clojure, no C
22-
shim. The API is designed and argued, and an adversarial review changed four
23-
of its five decisions. Three findings that outlive the note: `add_wasip2`
24-
without `wasmtime_context_set_wasi` **aborts the process** at the first WASI
25-
call; an import that throws poisons the whole store, not just the call; and
26-
wasmtime **frees** what the callback writes, so `lower-fn`'s Arena
27-
allocation cannot be reused in that direction. Resource imports are inside
28-
this unit, not after it — every `wasi:io` interface needs one.
29-
4. **`0012`'s `ex-data` contract.** It promises a WIT type name that reflection
30-
cannot supply; `0015` declined to be the codegen layer that would, so the
31-
contract has to shrink instead.
32-
5. **`require`-a-component is deferred, not pending** (`0015`). When it is
33-
picked up it should be a generated `.clj`, not a macro, and the instance
34-
should not be ambient.
10+
1. **Close S1 against its stop condition.** What remains of "a component this
11+
project did not author, calling and being called, with every WIT type a
12+
shipped release can express": the un-authored component (`zoo.wit`) is
13+
*called*; the *calling* side is exercised only by guests this repo wrote.
14+
One named capability gap: **a resource nested in a container in an
15+
import's signature is refused at instantiate** (`:nested-resource`) rather
16+
than marshalled — `import-stub` routes only whole parameters/results
17+
through the rep table. Accepted until a real component needs the shape;
18+
the first one that does reopens it.
19+
2. **The ergonomics layer (`0015` revisited).** Its blockers are gone —
20+
marshalling and host imports both exist — so the generated-`.clj` shape
21+
("not a macro, and the instance not ambient") is buildable now. Decide
22+
whether S1 ships with it or it opens S2.
23+
3. **S2 — developer experience skeleton** (`doc/roadmap.md`): `cljwit.edn`,
24+
an nREPL entry point, the shadow-cljs shape.
25+
26+
Done since the last update: `0016` `own<T>` handles, `0017` host imports
27+
(A–F), `0018` host-defined resources, `0012`'s `ex-data` contract shrunk to
28+
what reflection can supply, `0019` libwasmtime resolution for library users.
29+
What is left of `0012` is `map`, `list<T,N>`, `stream`/`future` and
30+
`error-context`, none of which a shipped release can express.
3531

3632
## Where we are
3733

@@ -69,14 +65,19 @@ is unmeasured.** That is S0's residue and it belongs to S3.
6965

7066
### S1 — reaching a component
7167

72-
**`cljwit.host` exists** (`src/cljwit/host.clj`, `0014`). Three lifetimes —
73-
engine, compiled artifact, instance — with exports discovered from the
74-
component itself: no WIT file and no code generation at run time. Names are the
75-
exact WIT strings, including `pkg:name/iface@ver#func` for functions inside an
76-
interface, with keyword aliases only where the name reads back equal to itself.
77-
Every entry into a store takes a non-concurrency check, and results are lifted
78-
eagerly. It marshals every `0012` row except `own`/`borrow`, `map`,
79-
`list<T,N>`, `stream`/`future` and `error-context`.
68+
**`cljwit.host` is delivered** (`src/cljwit/host.clj`, `0014`). Three
69+
lifetimes — engine, compiled artifact, instance — with exports discovered from
70+
the component itself: no WIT file and no code generation at run time. Names
71+
are the exact WIT strings, including `pkg:name/iface@ver#func` for functions
72+
inside an interface, with keyword aliases only where the name reads back equal
73+
to itself. Every entry into a store takes a non-concurrency check, and results
74+
are lifted eagerly. It marshals **every `0012` row a shipped release can
75+
express, in both directions**: exports and host imports (`0017`), resources in
76+
both ownerships — guest-defined handles as `AutoCloseable` (`0016`),
77+
host-defined resources with destructors (`0018`) — and WASI, deny-by-default
78+
(`0017` E). The one named gap is a resource nested in a container in an
79+
import's signature, refused at instantiate. `0019` gives library users a
80+
libwasmtime resolution order that does not require this repo's flake.
8081

8182
**The calling convention is decided and the cost structure is measured**
8283
(`0011`, corrected by `0013`): `MethodHandleProxies/asInterfaceInstance` behind

0 commit comments

Comments
 (0)