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:
Yogthos 2026-06-25 19:49:17 -04:00
parent bd7b75fb5d
commit 6fcc9fa8e6
5 changed files with 240 additions and 180 deletions

View file

@ -9,12 +9,19 @@
;; larger scale; multiply adds scales; divide gives the exact quotient at minimal
;; scale or throws ArithmeticException on a non-terminating expansion. Clojure
;; contagion: a bigdec mixed with an integer stays a bigdec; a flonum operand wins
;; (the result is a double). jbd+/jbd-/jbd*/jbd-div and the comparison helpers are
;; the shared engine; the value-position shims (jolt-add/-sub/-mul/-div, compare)
;; route through them when a bigdec is present, leaving the inlined native hot path
;; untouched. Call-position `(+ 1.5M 2.5M)` reaches the raw Chez op and needs the
;; analyzer's :bigdec type to dispatch (not yet wired); use it through reduce/apply
;; or a let where the type is known.
;; (the result is a double). jbd-add/-sub/-mul/-div, the jbd-lt?/…/zero? helpers,
;; and jbd-quot/-rem are the shared engine. Two paths reach it, both leaving the
;; inlined native hot path untouched:
;; - value position ((reduce + bigs)/(apply * bigs)): the jolt-add/-sub/-mul/-div
;; and compare shims dispatch here when a bigdec operand is present.
;; - call position ((+ 1.5M 2.5M), (< a b), (zero? b)): jolt.passes.numeric tags
;; the invoke :num-kind :bigdec when every operand is statically a bigdec (M
;; literal or a let-bound copy, integer literals allowed), and the back end
;; lowers it to the jbd op. Non-bigdec code is unaffected.
;; Gaps (a runtime bigdec the analyzer can't see statically): a bigdec mixed with a
;; flonum in call position ((+ 1.5M 2.0)) and arithmetic over a bigdec the analyzer
;; types as :any ((+ (bigdec x) 1)) fall through to the raw op and throw; use value
;; position or a literal-typed let.
(define-record-type jbigdec (fields unscaled scale) (nongenerative chez-jbigdec-v1))

File diff suppressed because one or more lines are too long