^long is a 64-bit long: fast-path-with-fallback ops + logical unsigned shift

Completes the JVM long-compatibility gap so clojure.test.check (and the
property-based suites built on it, e.g. data.codec) run on jolt.

A ^long is 64-bit but a Chez fixnum is only 61-bit, so the backend's fast fx
comparison / quot / min / max / inc / dec ops raised on a full-width long (one
from the PRNG or wrapping arithmetic). They now go through the jolt-l* macros
(host/chez/seq.ss): the fx fast path when the operands ARE fixnums, the generic
op otherwise — so e.g. ((fn [^long a ^long b] (< a b)) Long/MAX 1) is false, not
an error. Arithmetic +/-/* keep the raw fx ops (under *unchecked-math* they're
already the wrapping unchecked-*).

Also fixes unsigned-bit-shift-right: it was an arithmetic (sign-propagating)
shift, now a logical shift over the 64-bit two's-complement window, so
(unsigned-bit-shift-right -1 1) is 2^63-1 like the JVM.

Result: test.check 1.1.3 loads and runs (generators, quick-check, shrinking);
data.codec's base64 property suite passes (12/12 defspecs; the 2 deftests check
clojure.lang.IFn$OLLOL, a JVM primitive-fn interface, N/A). Both added to
docs/libraries.md + the site.

re-mint (backend/seed). make test green (+3 corpus rows, 0 new divergences,
numeric gate updated to the jolt-l* ops), shakesmoke byte-identical.
This commit is contained in:
Yogthos 2026-06-27 16:04:19 -04:00
parent a028cab04f
commit 3340635714
7 changed files with 62 additions and 20 deletions

View file

@ -94,13 +94,18 @@
(def ^:private dbl-ops
{"+" "fl+" "-" "fl-" "*" "fl*" "/" "fl/" "min" "flmin" "max" "flmax"
"<" "fl<?" ">" "fl>?" "<=" "fl<=?" ">=" "fl>=?" "=" "fl=?" "==" "fl=?"})
;; A ^long is 64-bit; a Chez fixnum is only 61-bit. Arithmetic +/-/* keep the raw
;; fx ops (the fast-arith path; under *unchecked-math* they're already rewritten to
;; the wrapping unchecked-*). The comparisons / min/max / quot/rem/mod use the
;; jolt-l* fast-path-with-fallback macros (host/chez/seq.ss) so a full 64-bit
;; operand falls back to the generic op instead of raising.
(def ^:private lng-ops
{"+" "fx+" "-" "fx-" "*" "fx*" "min" "fxmin" "max" "fxmax"
{"+" "fx+" "-" "fx-" "*" "fx*" "min" "jolt-l-min" "max" "jolt-l-max"
;; unchecked-* WRAP to signed 64 bits (Java long), so they can't use the raising
;; fx ops — the backend emits the wrapping jolt-unc* helpers (host/chez/seq.ss).
"unchecked-add" "jolt-uncadd2" "unchecked-subtract" "jolt-uncsub2" "unchecked-multiply" "jolt-uncmul2"
"quot" "fxquotient" "rem" "fxremainder" "mod" "fxmodulo"
"<" "fx<?" ">" "fx>?" "<=" "fx<=?" ">=" "fx>=?" "=" "fx=?" "==" "fx=?"})
"quot" "jolt-l-quot" "rem" "jolt-l-rem" "mod" "jolt-l-mod"
"<" "jolt-l<" ">" "jolt-l>" "<=" "jolt-l<=" ">=" "jolt-l>=" "=" "jolt-l=" "==" "jolt-l="})
;; BigDecimal ops. jolt.passes.numeric tags an arithmetic/comparison invoke
;; :num-kind :bigdec when every operand is a bigdec (or an integer literal); these
@ -487,9 +492,10 @@
(cond
(and (= kind :double) (= nm "inc")) (str "(fl+ " (first args) " 1.0)")
(and (= kind :double) (= nm "dec")) (str "(fl- " (first args) " 1.0)")
(and (= kind :long) (= nm "inc")) (str "(fx1+ " (first args) ")")
(and (= kind :long) (= nm "dec")) (str "(fx1- " (first args) ")")
;; unchecked-inc/dec wrap (Java long), so the raising fx1+/fx1- can't be used.
;; inc/dec tolerate a 64-bit operand (jolt-l-inc/dec fall back past fixnum range);
;; unchecked-inc/dec wrap (Java long). Neither can use the raising fx1+/fx1-.
(and (= kind :long) (= nm "inc")) (str "(jolt-l-inc " (first args) ")")
(and (= kind :long) (= nm "dec")) (str "(jolt-l-dec " (first args) ")")
(and (= kind :long) (= nm "unchecked-inc")) (str "(jolt-uncinc " (first args) ")")
(and (= kind :long) (= nm "unchecked-dec")) (str "(jolt-uncdec " (first args) ")")
:else