BigDecimal call-position arithmetic via :bigdec type (Phase 2)
A direct (+ 1.5M 2.5M) emits a raw Chez + that rejects the bigdec record. Rather than guard every arithmetic call site (measured 2-4x on unhinted fixnum loops), let the analyzer dispatch where it can prove the type. jolt.passes.numeric seeds a :bigdec kind from the M-literal and flows it through let/loop/if like the existing :double/:long kinds; an arithmetic/comparison invoke whose operands are all bigdec (integer literals allowed) gets :num-kind :bigdec. The back end (bd-ops + emit-numeric) lowers those to the bigdec.ss engine (jbd-add/-sub/-mul/-div, jbd-lt?/…, jbd-zero?/-pos?/-neg?, jbd-quot/-rem). Zero cost on non-bigdec code: with no bigdec literals present the kind never arises, so emission is byte-identical — the re-mint leaves prelude.ss unchanged, only image.ss (the compiler) moves. Gaps (filed): a bigdec mixed with a flonum in call position, and a bigdec the analyzer types :any, still hit the raw op and throw; use value position or a literal-typed let. Re-mint (numeric/backend are seed sources). 16 JVM-certified corpus rows.
This commit is contained in:
parent
bd7b75fb5d
commit
6fcc9fa8e6
5 changed files with 240 additions and 180 deletions
|
|
@ -94,6 +94,17 @@
|
|||
"quot" "fxquotient" "rem" "fxremainder" "mod" "fxmodulo"
|
||||
"<" "fx<?" ">" "fx>?" "<=" "fx<=?" ">=" "fx>=?" "=" "fx=?" "==" "fx=?"})
|
||||
|
||||
;; BigDecimal ops. jolt.passes.numeric tags an arithmetic/comparison invoke
|
||||
;; :num-kind :bigdec when every operand is a bigdec (or an integer literal); these
|
||||
;; are the bigdec.ss engine procedures it lowers to. Variadic where the source op
|
||||
;; is; an integer-literal operand is coerced to a bigdec at runtime, so unlike the
|
||||
;; flonum path no literal rewrite is needed.
|
||||
(def ^:private bd-ops
|
||||
{"+" "jbd-add" "-" "jbd-sub" "*" "jbd-mul" "/" "jbd-div"
|
||||
"quot" "jbd-quot" "rem" "jbd-rem"
|
||||
"<" "jbd-lt?" ">" "jbd-gt?" "<=" "jbd-le?" ">=" "jbd-ge?"
|
||||
"zero?" "jbd-zero?" "pos?" "jbd-pos?" "neg?" "jbd-neg?"})
|
||||
|
||||
;; PRELUDE MODE. The default (subset) mode rejects any clojure.core ref
|
||||
;; that isn't a native-op — a clean "out of subset" signal for user-facing `-e`.
|
||||
;; When emitting clojure.core ITSELF as a prelude, core fns reference each other
|
||||
|
|
@ -461,7 +472,7 @@
|
|||
(and (= kind :long) (or (= nm "inc") (= nm "unchecked-inc"))) (str "(fx1+ " (first args) ")")
|
||||
(and (= kind :long) (or (= nm "dec") (= nm "unchecked-dec"))) (str "(fx1- " (first args) ")")
|
||||
:else
|
||||
(let [op (if (= kind :double) (dbl-ops nm) (lng-ops nm))]
|
||||
(let [op (case kind :double (dbl-ops nm) :long (lng-ops nm) :bigdec (bd-ops nm))]
|
||||
(order-args (fn [as] (str "(" op " " (str/join " " as) ")"))))))
|
||||
|
||||
(defn- emit-invoke [node]
|
||||
|
|
|
|||
|
|
@ -49,6 +49,18 @@
|
|||
(and (>= n 2) (contains? #{"<" ">" "<=" ">=" "=" "=="} nm)) :bool
|
||||
:else nil))
|
||||
|
||||
;; result kind of a bigdec-specialized op, or nil. Arithmetic / quot / rem yield a
|
||||
;; bigdec; the comparisons and zero?/pos?/neg? yield a bool. `=` is left to the
|
||||
;; generic jolt= (already bigdec-aware), and `/` can throw (non-terminating) but is
|
||||
;; still a bigdec op. Each non-nil name must have an entry in backend bd-ops.
|
||||
(defn- bd-spec [nm n]
|
||||
(cond
|
||||
(and (>= n 1) (contains? #{"+" "-" "*" "/"} nm)) :bigdec
|
||||
(and (= n 2) (contains? #{"quot" "rem"} nm)) :bigdec
|
||||
(and (= n 1) (contains? #{"zero?" "pos?" "neg?"} nm)) :bool
|
||||
(and (>= n 2) (contains? #{"<" ">" "<=" ">="} nm)) :bool
|
||||
:else nil))
|
||||
|
||||
;; A non-numeric result (a comparison) doesn't propagate a numeric kind.
|
||||
(defn- propagate [spec] (if (= spec :bool) nil spec))
|
||||
|
||||
|
|
@ -150,11 +162,12 @@
|
|||
(get fnode :num-ret) [(get fnode :num-ret) node1]
|
||||
(nil? nm) [nil node1]
|
||||
:else
|
||||
(let [;; per-operand class: :double / :long (typed), :wild (integer literal,
|
||||
;; usable in either), or :no (anything else — blocks specialization).
|
||||
(let [;; per-operand class: :double / :long / :bigdec (typed), :wild (integer
|
||||
;; literal, usable in any), or :no (anything else — blocks specialization).
|
||||
cls (mapv (fn [r] (let [k (nth r 0) nd (nth r 1)]
|
||||
(cond (= k :double) :double
|
||||
(= k :long) :long
|
||||
(= k :bigdec) :bigdec
|
||||
(int-lit? nd) :wild
|
||||
:else :no)))
|
||||
ars)
|
||||
|
|
@ -163,7 +176,8 @@
|
|||
(every? (fn [c] (or (= c :wild) (= c allowed))) cls)
|
||||
(some (fn [c] (= c need)) cls)))
|
||||
ds (dbl-spec nm n)
|
||||
ls (lng-spec nm n)]
|
||||
ls (lng-spec nm n)
|
||||
bs (bd-spec nm n)]
|
||||
(cond
|
||||
(and ds (ok? :double :double))
|
||||
;; coerce integer-literal operands to flonum so fl-ops never see an exact int.
|
||||
|
|
@ -172,6 +186,11 @@
|
|||
[(propagate ds) (assoc node1 :args args' :num-kind :double)])
|
||||
(and ls (ok? :long :long))
|
||||
[(propagate ls) (assoc node1 :num-kind :long)]
|
||||
;; bigdec: every operand a bigdec (integer literals allowed, coerced at
|
||||
;; runtime). A flonum operand blocks this (double contagion) and falls
|
||||
;; through to the generic op.
|
||||
(and bs (ok? :bigdec :bigdec))
|
||||
[(propagate bs) (assoc node1 :num-kind :bigdec)]
|
||||
:else [nil node1])))))
|
||||
|
||||
;; Returns [kind node'] — kind is :double, :long, or nil.
|
||||
|
|
@ -179,6 +198,9 @@
|
|||
(let [op (get node :op)]
|
||||
(cond
|
||||
(= op :const) [(if (float-lit? node) :double nil) node]
|
||||
;; a bigdec (M) literal seeds the :bigdec kind so call-position arithmetic
|
||||
;; over it (and let-bound copies of it) dispatches to the bigdec engine.
|
||||
(= op :bigdec) [:bigdec node]
|
||||
(= op :local) [(get tenv (get node :name)) node]
|
||||
(= op :coerce) [(get node :kind) (assoc node :expr (nth (an (get node :expr) tenv) 1))]
|
||||
(= op :invoke) (an-invoke node tenv)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue