jolt/jolt-core/jolt/ir.clj
Dmitri Sotnikov 124cbf370a
Stage2 task2 (#13)
* core: Stage 2 Task 2 tier 2a — compile defprotocol/extend-type/extend-protocol

Applies the proven enabler: stateful primitives become per-ctx closures
captured over ctx, interned in clojure.core (install-stateful-fns!), so
they resolve + compile as plain :var invokes and work for deferred calls.

- protocol-dispatch / register-method: extracted from the interpreter
  special handlers into ctx-taking impls (protocol-dispatch-impl /
  register-method-impl) + interned as ctx-capturing clojure.core fns.
  Removed their special-symbol? entries + handler arms, and dropped them
  from host_iface special-names + compiler uncompilable-heads.
- defprotocol/extend-type/extend-protocol macros now pass the protocol/
  method/type NAMES as strings (not symbols), so the emitted calls compile
  as ordinary invokes; removed the three macros from special-names so the
  analyzer expands+compiles them instead of punting to the interpreter.
- Both interpreter and compiled paths now call the same ctx-capturing
  closures (one dispatch implementation, no special-form duplication).

reify/make-reified deferred to tier 2b (map-eval shape); defrecord waits
on deftype (tier 5).

Gate green: conformance 267x3, fallback-zero 31/5, bootstrap-fixpoint
stage1==2==3, self-host, staged-bootstrap, clojure-test-suite >=4034/67,
features 78/78, all unit + spec (protocols 7/7, multimethods 9/9).

* core: Stage 2 Task 2 tier 2b — compile reify (make-reified as a fn)

Completes the protocol machinery: make-reified joins protocol-dispatch/
register-method as a ctx-capturing clojure.core fn (install-stateful-fns!).
- make-reified-impl takes the EVALUATED {keyword fn} method map (a phm when
  compiled, struct/table when interpreted) and builds the reified object.
- reify macro passes the protocol NAME as a string; method map is an ordinary
  map literal evaluating to {keyword fn}.
- Removed make-reified's special-symbol? entry + handler arm, and dropped
  make-reified + reify from host_iface special-names + compiler
  uncompilable-heads.

reify now compiles and dispatches in both modes (single- and multi-method).
With tier 2a, the full protocol surface (defprotocol/extend-type/
extend-protocol/reify) compiles; defrecord still waits on deftype (tier 5).

Gate green: conformance 267x3, fallback-zero 31/5, bootstrap-fixpoint
stage1==2==3, self-host, staged-bootstrap, clojure-test-suite >=4034/67,
features 78/78, all unit + spec (protocols 7/7x3).

* core: Stage 2 Task 2 tier 3 — compile (var x) + binding

binding keys its thread-binding frame on var cells via (var x), so it
needed (var x) to compile. Added a the-var IR node that emits the embedded
var cell itself (vs var-ref, which derefs):
- ir.clj: the-var node.
- analyzer: 'var' added to handled; analyze-special resolves the symbol to
  its var and emits the-var (uncompilable for a non-var, matching Clojure).
- backend: :the-var emits (quote cell) — the exact per-ctx cell var-get
  keys on, so a compiled binding overrides + restores correctly.
- removed var from loader stateful-head? + host_iface special-names, and
  binding from special-names so it expands+compiles.

Dynamic binding now compiles end-to-end (override/restore, and a compiled
fn reading the dynamic var under the binding) in both modes.

Gate green: conformance 267x3, fallback-zero 31/5, bootstrap-fixpoint
stage1==2==3, self-host, clojure-test-suite >=4034/67, features 78/78,
all unit + spec (state/metadata).

---------

Co-authored-by: Yogthos <yogthos@gmail.com>
2026-06-10 03:20:44 +08:00

61 lines
2.4 KiB
Clojure

(ns jolt.ir
"Host-neutral intermediate representation for the Jolt compiler.
The analyzer (jolt.analyzer) produces IR; a host back end consumes it. IR nodes
are plain maps tagged with :op — no host values embedded. Globals reference vars
by name (:ns/:name), never by a host var cell, so the IR is portable and
AOT-safe. This namespace is pure Clojure (portable jolt-core): it depends on
nothing host-specific.")
;; Node constructors. Kept as data so any back end can pattern-match on :op.
(defn const [v] {:op :const :val v})
(defn local [name] {:op :local :name name})
;; A global var reference, by name. The back end resolves it to a host var.
(defn var-ref [ns name] {:op :var :ns ns :name name})
;; The var object itself — (var x) / #'x. Unlike var-ref (which derefs), the back
;; end emits the embedded var cell so `binding`'s thread-binding frame can key on it.
(defn the-var [ns name] {:op :the-var :ns ns :name name})
;; A runtime primitive (cons, +, get, apply, …) the back end maps to the host RT.
(defn rt [name] {:op :rt :name name})
;; A name that resolves only via the host's own environment (e.g. + or int? on
;; Janet) — the back end emits a host-appropriate reference.
(defn host-ref [name] {:op :host :name name})
(defn if-node [test then else] {:op :if :test test :then then :else else})
(defn do-node [statements ret] {:op :do :statements statements :ret ret})
(defn invoke [f args] {:op :invoke :fn f :args args})
;; meta is the var metadata (e.g. {:dynamic true} / {:redef true}) the back end
;; applies to the cell; absent when the def name carried none.
(defn def-node
([ns name init] {:op :def :ns ns :name name :init init})
([ns name init meta]
(if meta
{:op :def :ns ns :name name :init init :meta meta}
{:op :def :ns ns :name name :init init})))
(defn let-node [bindings body] {:op :let :bindings bindings :body body})
;; A fn is one or more arities. Each arity: {:params [..] :body ir}, plus :rest
;; name when variadic. :name is absent for an anonymous fn.
(defn fn-node [name arities]
(if name
{:op :fn :name name :arities arities}
{:op :fn :arities arities}))
(defn vector-node [items] {:op :vector :items items})
(defn map-node [pairs] {:op :map :pairs pairs})
(defn set-node [items] {:op :set :items items})
(defn quote-node [form] {:op :quote :form form})
(defn throw-node [expr] {:op :throw :expr expr})
(defn op [node] (:op node))