Enable IR inlining: splice small defns at call sites (lever 1/4)
jolt.passes.inline was fully written but dormant — it fetched bodies via the inline-ir host hook, which was a stub returning nil. Wire it up: run-passes stashes each inline-eligible defn (single fixed arity) as its form is optimized, and inline-ir hands the body back at call sites under --opt. The catch was the ^double/^long coercion: an inlined fn drops its param-entry and return coercion, so (work 3 4) on a ^double fn would return 25 instead of 25.0. New :coerce IR node carries the coercion inside the spliced body — the inline pass wraps a hinted param's arg and the return in :coerce, the back end lowers it (exact->inexact / jolt->fx), and jolt.passes.numeric reads its :kind. So an inlined call matches the called one and the body's fl*/fx* fast path still fires. Only under --opt (closed world); the seed mint and -e don't inline, so selfhost and the corpus are unaffected. test/chez/inline-test.ss 12/12 (make inline); full make test green, 0 new corpus divergences. Bench (hot loop, body is a ^double helper call): direct-link 500ms -> --opt (inlined) 184ms = 2.7x, by eliminating the call + coercion wrappers and letting Chez fuse the fl-ops unboxed. ~26x over the default dispatched build.
This commit is contained in:
parent
9927fd7f74
commit
79fa22eeab
9 changed files with 365 additions and 240 deletions
|
|
@ -13,7 +13,7 @@
|
|||
: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? record-shapes]]
|
||||
(:require [jolt.host :refer [inline-enabled? record-shapes stash-inline!]]
|
||||
[jolt.passes.fold :refer [const-fold]]
|
||||
[jolt.passes.numeric :as numeric]
|
||||
[jolt.passes.inline :refer [inline-node flatten-lets scalar-replace dirty set-rec-shapes!]]
|
||||
|
|
@ -28,6 +28,17 @@
|
|||
;; sets `dirty` when it rewrote something; the loop stops at a clean pass or here.
|
||||
(def ^:private inline-fixpoint-cap 8)
|
||||
|
||||
;; A top-level defn the inline pass may splice: a single fixed arity (no rest). The
|
||||
;; pass itself checks body size + closedness, so any such fn is stashable.
|
||||
(defn- inline-eligible? [node]
|
||||
(and (= :def (:op node)) (:init node) (= :fn (:op (:init node)))
|
||||
(= 1 (count (:arities (:init node))))
|
||||
(not (:rest (first (:arities (:init node)))))))
|
||||
|
||||
(defn- stash-of [node]
|
||||
(let [a (first (:arities (:init node)))]
|
||||
{:params (:params a) :body (:body a) :nhints (:nhints a) :ret (:ret-nhint a)}))
|
||||
|
||||
(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),
|
||||
|
|
@ -40,6 +51,10 @@
|
|||
numeric/annotate runs last in both branches (hint-directed fl*/fx* arithmetic);
|
||||
it benefits open builds too, so it is not gated on inlining."
|
||||
[node ctx]
|
||||
;; stash an inline-eligible defn so later call sites can splice it (closed-world
|
||||
;; optimization only). Done before optimizing, from the analyzed node.
|
||||
(when (and (inline-enabled? ctx) (inline-eligible? node))
|
||||
(stash-inline! ctx (:ns node) (:name node) (stash-of node)))
|
||||
(numeric/annotate
|
||||
(if (inline-enabled? ctx)
|
||||
(let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue