jolt/test/integration/ir-passes-test.janet
Yogthos 35e8821a92 compiler: IR pass pipeline + constant folding (jolt-2om, nanopass-lite)
jolt.passes is the new portable pipeline stage between the analyzer and the
back end: pure IR -> IR rewrites, total over node :ops (unknown ops pass
through with folded children), loaded with the compiler namespaces and
resolved lazily by analyze-form (JOLT_NO_IR_PASSES=1 disables — the same
escape-hatch pattern as the macro oracle). The shape is flatiron's opt.clj
applied to the jolt IR, which is what jolt-2om asked for.

The first pass is constant folding: a call of a foldable numeric SEED fn
(the later tiers don't exist when the compiler loads) whose args are all
constant numbers becomes a constant, and an if with a constant test becomes
the taken branch (dead-branch elimination — the untaken side never even
resolves). Folding computes with the ACTUAL jolt fns, so results match
runtime semantics by construction; a fold that would throw (mod 5 0) is
left for runtime.

Two walk lessons paid for in debugging: let/loop bindings are
[name init-ir] PAIRS, not maps (assoc'ing :init into a pair corrupts it);
and a throw inside the interpreted pass unwinds past the interpreter's ns
restores, so analyze-form restores the compile ns after the (protected)
pass call — without that, one pass error left current-ns in jolt.passes and
the rest of the tier compile resolved against the wrong namespace (sort-by
landed on the 2-arg JANET builtin).

ir-passes-test pins folds, conservatism (free vars, throwing folds), and
end-to-end eval. Gate exit 0.
2026-06-10 19:29:36 -04:00

36 lines
1.6 KiB
Text

# IR pass pipeline (jolt-2om, nanopass-lite): jolt.passes/run-passes applies
# pure IR->IR rewrites between the analyzer and the back end. The first pass
# is constant folding — it computes with the ACTUAL jolt fns, so folded
# results match runtime semantics by construction.
(import ../../src/jolt/api :as api)
(import ../../src/jolt/backend :as backend)
(import ../../src/jolt/reader :as reader)
(print "IR passes (constant folding)...")
(def ctx (api/init-cached {:compile? true}))
(defn ir [src] (backend/analyze-form ctx (reader/parse-string src)))
(defn check-const [src want]
(def n (ir src))
(assert (= :const (n :op)) (string src " folds to a constant"))
(assert (= want (n :val)) (string src " folds to " (string/format "%q" want))))
(check-const "(+ 1 2 3)" 6)
(check-const "(* 2 (+ 3 4))" 14)
(check-const "(quot 7 2)" 3)
(check-const "(mod -7 3)" 2)
(check-const "(if (< 1 2) :yes :no)" :yes)
# dead-branch elimination: the untaken branch never evaluates
(check-const "(if false (this-would-not-resolve) 2)" 2)
# non-constants stay calls; folding must be conservative
(assert (= :invoke ((ir "(+ x 2)") :op)) "free var stays a call")
(assert (= :invoke ((ir "(mod x 0)") :op)) "non-const args stay calls")
# a fold that would THROW is left for runtime
(assert (= :invoke ((ir "(mod 5 0)") :op)) "throwing fold left to runtime")
# and the folded code evaluates identically (3-mode conformance covers the
# broader matrix; this pins a couple end-to-end)
(assert (= 6 (api/eval-string ctx "(+ 1 2 3)")) "folded eval")
(assert (= :yes (api/eval-string ctx "(if (< 1 2) :yes :no)")) "folded if eval")
(print "IR passes passed!")