jolt/jolt-core/jolt/passes.clj
Dmitri Sotnikov a029afc127
Fold type predicates on proven types (jolt-wcw) (#129)
When the collection-type inference proves an argument's type, number?/
string?/keyword?/record?/nil?/some? fold to a compile-time boolean. A
const-fold now runs after inference so a folded predicate propagates and
collapses any if it gates to the taken branch.

Sound by construction: only a provable answer folds, and only when the
argument is side-effect-free (a const or local) so dropping its evaluation
is a no-op. Unknown types (:any/:truthy) and impure args keep the call.
vector?/set?/map? are left out — the :vec tag conflates a real vector with
a range/seq, so vector? could be wrong.

50M-iter loop, same shape isolated with a carry-only control: number? call+
branch 5080ms, predicate folded 1365ms — matching the 1417ms control floor,
so the 3.7x is entirely the eliminated call+branch.

Co-authored-by: Yogthos <yogthos@gmail.com>
2026-06-15 21:43:15 +00:00

45 lines
2.6 KiB
Clojure

(ns jolt.passes
"IR optimization passes (nanopass-lite, jolt-2om) + the inference/checking
driver. Façade over three weakly-coupled namespaces, loaded with the compiler:
jolt.passes.fold — const-fold (always-on) + the shared const-shape predicate.
jolt.passes.inline — inline + flatten-lets + scalar-replace (direct-link only).
jolt.passes.types — collection-type inference + success-type checking
(RFC 0006) + the inter-procedural driver API (jolt-767).
run-passes (below) is the single entry the back end applies to every analyzed
form. The driver/checker fns the back end looks up by name (check-form,
infer-body, reinfer-def, set-rtenv!, take-diags!, …) are re-exported here via
:refer, so jolt.passes stays the only namespace the back end imports.
Portable Clojure: kernel-tier fns + seed primitives only."
(:require [jolt.host :refer [inline-enabled?]]
[jolt.passes.fold :refer [const-fold]]
[jolt.passes.inline :refer [inline-node flatten-lets scalar-replace dirty]]
[jolt.passes.types :refer [run-inference
check-form infer-body reinfer-def
set-rtenv! set-vtypes! join-types
set-record-shapes! set-map-shapes! set-protocol-methods!
reset-escapes! collected-escapes
set-check-mode! take-diags!]]))
(defn run-passes
"All passes, in order. The back end applies this to every analyzed form. When
inlining is enabled for the unit (user code under direct-linking, jolt-87f),
run inline + flatten + scalar-replace + const-fold to a capped fixpoint —
inlining exposes map literals to lookups, scalar-replace collapses them, which
may expose more — then a collection-type inference pass (jolt-99x, optionally
also emitting success diagnostics) that auto-drops the lookup guard where the
type is proven. Otherwise (core + bootstrap) just const-fold, as before."
[node ctx]
(if (inline-enabled? ctx)
(let [opt (loop [i 0 n (const-fold node)]
(reset! dirty false)
(let [n2 (const-fold (scalar-replace (flatten-lets (inline-node n ctx))))]
(if (and @dirty (< i 8))
(recur (inc i) n2)
n2)))]
;; a final const-fold after inference propagates any predicate folded to a
;; constant (jolt-wcw), collapsing the `if` it gates to the taken branch.
(const-fold (run-inference opt)))
(const-fold node)))