docs: consolidate doc/ into docs/
One documentation root: the prose docs (building-and-deps, self-hosting architecture/compiler, tools-deps, grammar.ebnf) join the spec and RFCs under docs/. References in README and deps-conformance-test updated.
This commit is contained in:
parent
fdfd086df6
commit
808ce6a725
5 changed files with 0 additions and 0 deletions
138
docs/self-hosting-architecture.md
Normal file
138
docs/self-hosting-architecture.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# Self-hosting architecture: portable jolt-core over a host runtime
|
||||
|
||||
Design for splitting Jolt into a **portable Clojure-in-Clojure core** and a
|
||||
**host runtime** (Janet today, another runtime tomorrow), so the language is
|
||||
truly self-hosted and `jolt-core` can be lifted out and re-hosted.
|
||||
|
||||
This is the design that must be right *before* writing the compiler in Clojure —
|
||||
see [[self-hosting-compiler]] for the staged plan it plugs into.
|
||||
|
||||
## What "truly self-hosted + portable" requires
|
||||
|
||||
Two independent properties:
|
||||
|
||||
1. **Self-hosted** — the compiler and most of `clojure.core` are written in
|
||||
Clojure and compiled by Jolt itself.
|
||||
2. **Portable** — that Clojure code (`jolt-core`) depends only on a small,
|
||||
explicit **host contract**, never on Janet directly. Re-hosting means
|
||||
implementing the contract for a new runtime; `jolt-core` is reused verbatim.
|
||||
|
||||
The enemy is `jolt-core` calling `janet/tuple`, `make-vec`, `ns-find`, etc.
|
||||
directly — that welds it to Janet. Every host dependency must go through the
|
||||
contract.
|
||||
|
||||
## Prior art (the seam everyone uses)
|
||||
|
||||
- **Clojure (JVM).** `clojure.lang.*` (Java) is the host: `RT`/`Numbers` runtime
|
||||
helpers, the `Compiler` (form → JVM bytecode), persistent data structures,
|
||||
`Var`/`Namespace`, the reader. `clojure/core.clj` is the language, in Clojure.
|
||||
Seam: ~20 primitive special forms + `RT` static methods. Everything else is
|
||||
Clojure.
|
||||
- **ClojureScript (self-hosted).** Two portable passes — `cljs.analyzer`
|
||||
(form → AST **as data**, reading a **compiler-state map** of
|
||||
namespaces/defs/macros, *not* host objects) and `cljs.compiler` (AST → JS, the
|
||||
host-specific back end). `cljs.core` is Clojure compiled to JS. Platform splits
|
||||
live in `.cljc` reader conditionals. This is the closest model to what we want:
|
||||
**the analyzer is host-agnostic; only the back end and the runtime are
|
||||
host-specific.**
|
||||
- **Nanopass / Guile Tree-IL.** A high-level IR is the portability seam; multiple
|
||||
back ends consume it.
|
||||
- **ClojureCLR / ClojureDart / jank.** Same shape every time: portable analyzer +
|
||||
host back end + host runtime.
|
||||
|
||||
The invariant across all of them: **the IR (analyzer output) and a small runtime
|
||||
protocol are the contract; the front end is portable, the back end and runtime
|
||||
are per-host.**
|
||||
|
||||
## Decisions (locked)
|
||||
|
||||
- **Seam = a minimal host protocol.** `jolt-core` calls a small documented set of
|
||||
host fns (in ns `jolt.host`): `resolve-sym`, `macro?`, `macroexpand-1`,
|
||||
`current-ns`, `intern!`, plus the `RT` primitives. Each host provides `jolt.host`
|
||||
(+ RT). Re-hosting = reimplement that handful of fns. The protocol *is* the
|
||||
boundary; `jolt-core` never touches Janet directly.
|
||||
- **Physical split now.** Portable Clojure lives under `jolt-core/` (a new source
|
||||
root, embedded into the binary like the rest of the stdlib); host Janet code for
|
||||
the new pipeline under `host/janet/`. Legacy host modules under `src/jolt/*.janet`
|
||||
are the existing Janet host and get relocated under `host/janet/` in a later
|
||||
mechanical pass (tracked) — not moved big-bang now, to keep the suite green.
|
||||
|
||||
## The Jolt split
|
||||
|
||||
```
|
||||
jolt-core/ PORTABLE Clojure — no Janet. Depends only on the contract.
|
||||
ir the IR spec (data shapes the analyzer emits)
|
||||
analyzer form -> IR (macroexpands; resolves via host protocol)
|
||||
macros when/cond/->/defn/... (the macro library, in Clojure)
|
||||
core clojure.core fns expressible in Clojure, over RT primitives
|
||||
|
||||
host/janet/ THE HOST — Janet. Implements the contract.
|
||||
reader text -> jolt forms
|
||||
rt data structures + RT primitive fns (cons/first/+/get/apply…)
|
||||
backend IR -> Janet forms -> Janet compile -> bytecode (the emitter)
|
||||
cenv the compile-time host protocol impl (resolve/macro?/intern)
|
||||
bootstrap load jolt-core, wire analyzer+backend into the loader
|
||||
interop janet.* bridge
|
||||
```
|
||||
|
||||
Two contracts cross the seam:
|
||||
|
||||
### 1. The IR (analyzer → back end)
|
||||
The existing `:op`-tagged AST, made **host-neutral**:
|
||||
- `{:op :const :val v}`, `:if`, `:do`, `:let`, `:fn` (arities), `:invoke`,
|
||||
`:vector`/`:map`/`:set`, `:quote`, `:throw`/`:try`, `:loop`/`:recur`.
|
||||
- **Globals reference vars by NAME, not by host cell:**
|
||||
`{:op :var :ns "clojure.core" :name "map"}`. (compiler.janet today embeds the
|
||||
Janet var cell as a constant — that's a host leak and breaks AOT. Name-based
|
||||
refs are both portable and AOT-friendly; the back end resolves the cell.)
|
||||
- No embedded host function values. Calls to runtime primitives are
|
||||
`{:op :rt :name "cons"}` resolved by the back end to the host's RT fn.
|
||||
|
||||
### 2. The host contract (two protocols)
|
||||
- **Compile-time (`cenv`)** — what the analyzer needs from the host while
|
||||
analyzing: `(current-ns)`, `(resolve-sym sym) -> {:kind :var|:macro|:local|:special|:host, :ns, :name}`,
|
||||
`(macroexpand-1 form)`, `(intern! ns sym meta)`. The analyzer calls only these;
|
||||
it never touches Janet ns/var tables. (CLJS keeps this as pure data; we use a
|
||||
small protocol — a minimal, documented boundary — because Jolt already has live
|
||||
ns/var objects. The protocol *is* the seam.)
|
||||
- **Runtime (`RT`)** — the primitive fns emitted code and `jolt-core` call by
|
||||
stable name: arithmetic/compare, `cons/first/rest/seq/conj/get/assoc/count`,
|
||||
`apply`, `=`, vector/map/set constructors, var deref/bind, keyword/symbol
|
||||
construction. The back end maps each to the host (on Janet, mostly the existing
|
||||
`core-*`). To re-host, implement this set.
|
||||
|
||||
## Why name-based vars (not embedded cells)
|
||||
|
||||
`compiler.janet` compiles a global ref to a closure over the Janet var cell. That
|
||||
(a) is a Janet value baked into the IR — not portable, and (b) can't be marshaled
|
||||
for AOT without the runtime-dict trick. Compiling instead to *resolve var by
|
||||
(ns,name) at call time* through an RT primitive keeps redefinition live, makes the
|
||||
IR host-neutral, and makes images trivially portable. The per-call lookup is the
|
||||
cost; it can be cached/direct-linked later as an opt-in optimization.
|
||||
|
||||
## Bootstrap & staging (keeps the suite green throughout)
|
||||
|
||||
`compiler.janet` stays as the **bootstrap back end** until the Clojure pipeline is
|
||||
proven. Order:
|
||||
|
||||
1. **Freeze the IR** spec and refactor `compiler.janet`'s emit to consume
|
||||
name-based `:var` (no behavior change; bootstrap still works).
|
||||
2. **Define the host contract** (`cenv` + `RT`) and implement it on Janet,
|
||||
exposed under a stable namespace the Clojure core can call.
|
||||
3. **Write `jolt.analyzer` in Clojure** producing IR, against `cenv`. Diff its IR
|
||||
against the Janet analyzer on the conformance corpus until identical.
|
||||
4. **Janet back end consumes IR** from the Clojure analyzer; wire into the loader
|
||||
behind a flag. Validate at parity (dual-mode conformance + clojure-test-suite).
|
||||
5. **Flip** the loader to the Clojure analyzer + Janet back end; `compiler.janet`
|
||||
shrinks to the back end only.
|
||||
6. **Move `clojure.core`** macros then fns into `jolt-core` incrementally, each
|
||||
compiled by the prior stage, isolating host bits behind `RT`.
|
||||
|
||||
Guards at every step: the dual-mode conformance harness (interpret vs compile)
|
||||
and the clojure-test-suite baseline.
|
||||
|
||||
## The portability test
|
||||
|
||||
When done, re-hosting Jolt to runtime X means writing only: `host/X/{reader, rt,
|
||||
backend, cenv, bootstrap}`. `jolt-core/{ir, analyzer, macros, core}` is reused
|
||||
unchanged. That is the concrete bar for "truly self-hosted and portable."
|
||||
Loading…
Add table
Add a link
Reference in a new issue