Refactor phase 3a: one map-ir-children combinator for the IR rewrite walks (jolt-26dm)
Six bottom-up IR rewrites (const-fold, inline-node, subst, flatten-lets, subst-lookup, scalar-replace) each hand-listed every op's child positions — ~250 lines of identical "recurse children, rebuild" arms that had to be kept in sync whenever an op was added. Extract one map-ir-children into ir.clj that knows each op's child layout; each walk keeps only its genuine specials (const-fold's invoke/if, inline-node's invoke, subst's local/let alpha-rename, scalar-replace's invoke/let folds) and delegates the rest. The combinator is total over the op set, so the walks are now total too: a couple soundly gain coverage they previously skipped (const-fold now folds inside :try; subst-lookup now recurses :def inits, which fixes a latent dangling ref where a dropped const-key-map binding was referenced inside a def). These are sound — all six are result-preserving optimizations — and 3-mode conformance + fixpoint confirm identical program behavior. map-ir-children is shape-preserving for :try (recurses :catch-body/:finally only when present, never assoc's nil) so it can't turn a struct node into a phm. Written with cond/get only, matching the passes' tier, so no new load-order dep. Predicates (body-closed?/pure?/local-escapes?), the type-threading infer, and the Janet backend emit stay as-is: their conservative :else defaults / [type node] threading / host language don't fit a node-rebuilding combinator. Adds ir-passes-test coverage for folding reaching fn/loop/try bodies. Full gate green (conformance x3, suite >=4695/88, fixpoint stage1==2==3, inline-sra + devirt).
This commit is contained in:
parent
b54bd6c856
commit
8789777323
4 changed files with 101 additions and 196 deletions
|
|
@ -59,3 +59,56 @@
|
|||
(defn throw-node [expr] {:op :throw :expr expr})
|
||||
|
||||
(defn op [node] (:op node))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Structural recursion over IR child nodes (jolt-26dm / phase 3a).
|
||||
;;
|
||||
;; A tree-rewriting pass recurses into each op's child NODE positions and
|
||||
;; rebuilds the node; this combinator does that one place, so the per-op child
|
||||
;; layout is single-sourced and adding an op is a one-site change here (was: an
|
||||
;; edit to every walk). `(map-ir-children f node)` returns node with f applied to
|
||||
;; each child IR node — re-applied per element for seq positions (:args/:items/
|
||||
;; :statements), per value for :map pairs, per init for :let/:loop bindings, and
|
||||
;; per arity :body for :fn. Non-node positions (binding NAMES, fn :params/:rest,
|
||||
;; the :op tag, :ns/:name/:val) are left intact. Leaf ops and any op with no
|
||||
;; child nodes pass through unchanged, so walks built on this are TOTAL over the
|
||||
;; op set (an unknown op recurses nowhere rather than being silently dropped).
|
||||
;;
|
||||
;; Uses cond/=/get only — same constructs as the passes that consume it, so it
|
||||
;; loads at the same compiler tier with no new macro dependency.
|
||||
(defn map-ir-children [f node]
|
||||
(let [op (get node :op)]
|
||||
(cond
|
||||
(= op :if) (assoc node :test (f (get node :test))
|
||||
:then (f (get node :then))
|
||||
:else (f (get node :else)))
|
||||
(= op :do) (assoc node :statements (mapv f (get node :statements))
|
||||
:ret (f (get node :ret)))
|
||||
(= op :throw) (assoc node :expr (f (get node :expr)))
|
||||
(= op :invoke) (assoc node :fn (f (get node :fn))
|
||||
:args (mapv f (get node :args)))
|
||||
(= op :vector) (assoc node :items (mapv f (get node :items)))
|
||||
(= op :set) (assoc node :items (mapv f (get node :items)))
|
||||
(= op :map) (assoc node :pairs (mapv (fn [pr] [(f (nth pr 0)) (f (nth pr 1))])
|
||||
(get node :pairs)))
|
||||
(= op :let) (assoc node :bindings (mapv (fn [b] [(nth b 0) (f (nth b 1))])
|
||||
(get node :bindings))
|
||||
:body (f (get node :body)))
|
||||
(= op :loop) (assoc node :bindings (mapv (fn [b] [(nth b 0) (f (nth b 1))])
|
||||
(get node :bindings))
|
||||
:body (f (get node :body)))
|
||||
(= op :recur) (assoc node :args (mapv f (get node :args)))
|
||||
(= op :fn) (assoc node :arities (mapv (fn [a] (assoc a :body (f (get a :body))))
|
||||
(get node :arities)))
|
||||
(= op :def) (assoc node :init (f (get node :init)))
|
||||
;; :catch-body / :finally are optional; recurse them only when PRESENT.
|
||||
;; Assoc'ing them nil-when-absent would turn the node into a phm (jolt's
|
||||
;; nil-valued-key representation) and force backend densification — so we
|
||||
;; preserve the node's shape and never introduce a nil key.
|
||||
(= op :try)
|
||||
(let [n (assoc node :body (f (get node :body)))
|
||||
n (if (get node :catch-body) (assoc n :catch-body (f (get node :catch-body))) n)
|
||||
n (if (get node :finally) (assoc n :finally (f (get node :finally))) n)]
|
||||
n)
|
||||
;; :const :local :var :host :the-var :rt :quote — no child nodes
|
||||
:else node)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue