Type literal-init loop counters as fixnums (lever 2/4)
A loop var with an integer-literal init now types :long (fx ops) when every recur arg in its slot is an increment-style step — the var unchanged, inc/dec, or (+/- var <int-literal>). So (loop [i 0] (recur (inc i))) gets fx1+/fx<? without a hint, matching how Clojure treats a primitive-long loop counter. Soundness: only increment steps qualify. A multiplicative or large-growth accumulator like (recur (* acc 2)) is never seeded, so it stays generic and keeps arbitrary precision — a bignum-producing loop (e.g. a factorial) is unaffected. counter-step? gates this; the existing fixpoint demotes anything inconsistent. test/chez/numeric-test.ss 44/44 (incl. a factorial loop staying bignum-exact while its counter is fx); full make test green, 0 new corpus divergences.
This commit is contained in:
parent
79fa22eeab
commit
d4ba87446a
3 changed files with 231 additions and 156 deletions
|
|
@ -66,6 +66,41 @@
|
|||
(= op :do) (recur-kinds (get node :ret) tenv)
|
||||
:else [])))
|
||||
|
||||
;; The recur-arg NODE lists for the recurs at THIS loop level (structural, no env),
|
||||
;; parallel to recur-kinds. Used to recognise a counter.
|
||||
(defn- recur-arg-lists [node]
|
||||
(let [op (get node :op)]
|
||||
(cond
|
||||
(= op :recur) [(get node :args)]
|
||||
(= op :let) (recur-arg-lists (get node :body))
|
||||
(= op :if) (concat (recur-arg-lists (get node :then)) (recur-arg-lists (get node :else)))
|
||||
(= op :do) (recur-arg-lists (get node :ret))
|
||||
:else [])))
|
||||
|
||||
;; Is `arg` an increment-style step of loop var `vname`: the var unchanged, or
|
||||
;; inc/dec/unchecked-inc/dec, or (+/- var <int-literal>)? Bounded growth that a
|
||||
;; fixnum-range counter can sustain for any realistic loop — unlike (* acc x), which
|
||||
;; overflows fast, so a multiplicative accumulator never qualifies and stays
|
||||
;; arbitrary-precision.
|
||||
(defn- counter-step? [arg vname]
|
||||
(cond
|
||||
(and (= :local (get arg :op)) (= vname (get arg :name))) true
|
||||
(= :invoke (get arg :op))
|
||||
(let [f (get arg :fn) as (get arg :args)]
|
||||
(and (= :var (get f :op)) (= "clojure.core" (get f :ns))
|
||||
(let [nm (get f :name)
|
||||
v? (fn [n] (and (= :local (get n :op)) (= vname (get n :name))))]
|
||||
(cond
|
||||
(and (contains? #{"inc" "dec" "unchecked-inc" "unchecked-dec"} nm) (= 1 (count as)))
|
||||
(v? (nth as 0))
|
||||
(and (contains? #{"+" "unchecked-add"} nm) (= 2 (count as)))
|
||||
(or (and (v? (nth as 0)) (int-lit? (nth as 1)))
|
||||
(and (v? (nth as 1)) (int-lit? (nth as 0))))
|
||||
(and (contains? #{"-" "unchecked-subtract"} nm) (= 2 (count as)))
|
||||
(and (v? (nth as 0)) (int-lit? (nth as 1)))
|
||||
:else false))))
|
||||
:else false))
|
||||
|
||||
;; Loop-var kinds by bounded fixpoint. A var keeps its init kind (:double or :long)
|
||||
;; only if every recur arg in that slot is the same kind (under the current
|
||||
;; assumption) — a monotone demotion that stops at a fixpoint, bounded by the var
|
||||
|
|
@ -73,8 +108,8 @@
|
|||
;; keeps arbitrary precision (no :long from a bare literal). A typed loop var's init
|
||||
;; and recur args are all flonums/fixnums (a :long init flows from a coerced ^long
|
||||
;; value or an fx op), so no entry coercion is needed here, unlike a fn param.
|
||||
(defn- loop-kinds [names ik body tenv]
|
||||
(loop [cur ik iter 0]
|
||||
(defn- loop-kinds [names seed body tenv]
|
||||
(loop [cur seed iter 0]
|
||||
(if (> iter (count names))
|
||||
cur
|
||||
(let [te (reduce (fn [t i] (assoc t (nth names i) (nth cur i))) tenv (range (count names)))
|
||||
|
|
@ -152,7 +187,24 @@
|
|||
(let [binds (get node :bindings)
|
||||
names (mapv (fn [b] (nth b 0)) binds)
|
||||
ik (mapv (fn [b] (nth (an (nth b 1) tenv) 0)) binds)
|
||||
lk (loop-kinds names ik (get node :body) tenv)
|
||||
rlists (recur-arg-lists (get node :body))
|
||||
;; seed each var: an already-typed init keeps its kind; an integer-literal
|
||||
;; init whose recur args are all counter steps is a fixnum counter (:long).
|
||||
seed (mapv (fn [j]
|
||||
(let [k (nth ik j) b (nth binds j)]
|
||||
(cond
|
||||
k k
|
||||
;; an int-literal var is a fixnum counter only in a real
|
||||
;; iterating loop (>= 1 recur) whose every step is bounded.
|
||||
;; A recur-less loop is a let — its int literal stays
|
||||
;; generic (arbitrary precision), like a let binding.
|
||||
(and (seq rlists)
|
||||
(int-lit? (nth b 1))
|
||||
(every? (fn [args] (counter-step? (nth args j) (nth b 0))) rlists))
|
||||
:long
|
||||
:else nil)))
|
||||
(range (count names)))
|
||||
lk (loop-kinds names seed (get node :body) tenv)
|
||||
te (reduce (fn [t i] (assoc t (nth names i) (nth lk i))) tenv (range (count names)))]
|
||||
[nil (assoc node
|
||||
:bindings (mapv (fn [b] [(nth b 0) (nth (an (nth b 1) tenv) 1)]) binds)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue