Refactor phase 3b: keep :try IR nodes structs, not phms (jolt-26dm)

analyze-try assoc'd :catch-sym/:catch-body/:finally nil-when-absent, so a try
with no catch (or no finally) carried a nil-valued key — which makes the node a
phm in jolt's map representation and forces the back end to densify it
(norm-node) before reading :op. That's the map-nil-representation trap Phase 2
already cleaned up for def/fn/arity nodes. Add those keys only when the clause is
present, matching the arity :rest discipline; a try node stays a fast struct.

Behavior-invisible: emit-try reads each key with a nil-safe (node :k) and gates
on it, so an absent key and a present-nil key are indistinguishable to every
consumer. Adds ir-try-shape-test asserting the node shape across all four
try/catch/finally combinations plus end-to-end eval.

Note on scope: the plan's "delete the defensive norm-node calls" is NOT done — it
can't be. {:op :const :val nil} (e.g. (def x nil)) and nil map keys are
inherently phm, so the emit-dispatch norm-node guards a real case, not a
present-or-absent artifact. This PR removes a source of gratuitous phm nodes
rather than the densification itself. Full gate green.
This commit is contained in:
Yogthos 2026-06-15 05:09:02 -04:00
parent 61e4bbf2de
commit 130a6c5c4d
2 changed files with 59 additions and 6 deletions

View file

@ -167,12 +167,22 @@
(= hname "finally")
(reset! finally-body (rest (vec (form-elements c))))
:else (swap! body conj c))))
{:op :try
:body (analyze-seq ctx @body env)
:catch-sym @catch-sym
:catch-body (when @catch-body
(analyze-seq ctx @catch-body (add-locals env [@catch-sym])))
:finally (when @finally-body (analyze-seq ctx @finally-body env))}))
;; Add :catch-sym/:catch-body/:finally ONLY when present (same discipline as
;; the arity :rest key above). Assoc'ing them nil-when-absent would give the
;; node a nil-valued key, which makes it a phm in jolt's map representation
;; and forces the back end to densify it (norm-node) before reading :op — the
;; map-nil-representation trap Phase 2 cleaned up for def/fn/arity nodes. The
;; back end reads each key with a nil-safe (node :k) and gates on it, so an
;; absent key is indistinguishable from a present-nil one.
(let [n {:op :try :body (analyze-seq ctx @body env)}
n (if @catch-body
(assoc n :catch-sym @catch-sym
:catch-body (analyze-seq ctx @catch-body (add-locals env [@catch-sym])))
n)
n (if @finally-body
(assoc n :finally (analyze-seq ctx @finally-body env))
n)]
n)))
(defn- analyze-special [ctx op items env]
(case op