backend: emit bitwise ops as native ops (test.check PRNG ~2.4x)

Profiling the test.check distribution/large-sample slowness (jolt-i5if): the
hot path is the SplitMix PRNG, dominated by 64-bit mix arithmetic, and the
bitwise ops (bit-and/or/xor/not, shifts) were NOT in the backend native-ops
table — so (bit-xor a b) compiled to a var-deref through the variadic overlay
(__bit-xor) instead of a direct call, the way +/-/* already emit.

Map bit-and/or/xor/not to the Chez bitwise-and/ior/xor/not primitives (inlined
to native code; a non-integer operand now errors like the JVM instead of being
silently truncated) and the shifts to a direct helper call. bit-and-not stays on
its overlay — its only Scheme impl is 2-arg, so a value-position arity-3 use
would mis-emit.

mix-64 arithmetic 2.7x faster, raw split+rand-long 2.4x, gen/vector ~1.4x. The
remaining gap is the bignum-vs-native-long floor (~20x, substrate) plus the
generator machinery (deftype/fn dispatch, separate). Corpus rows added for value
position, bit-not, apply, and a full-64-bit unsigned shift.
This commit is contained in:
Yogthos 2026-06-28 11:25:52 -04:00
parent b5ea06c5c2
commit f17b68ccfe
4 changed files with 153 additions and 135 deletions

View file

@ -37,6 +37,16 @@
"even?" "jolt-even?" "odd?" "jolt-odd?" "pos?" "jolt-pos?" "neg?" "jolt-neg?"
"zero?" "jolt-zero?" "identity" "jolt-identity" "nil?" "jolt-nil?" "some?" "jolt-some?"
"ex-info" "jolt-ex-info"
;; bit ops: and/or/xor/not are Chez bitwise primitives (inlined to native code,
;; no helper call); operands must be integers (a non-integer errors, like the
;; JVM). The shifts keep their helpers (Java >>> masking / arithmetic shift) but
;; emit a direct call instead of var-deref + the variadic overlay.
;; and/or/xor/not map to variadic Chez bitwise prims (safe as a value at any
;; arity). bit-and-not is left to its overlay: its only Scheme impl is 2-arg, so
;; a value-position arity-3 use (via the variadic overlay) would mis-emit.
"bit-and" "bitwise-and" "bit-or" "bitwise-ior" "bit-xor" "bitwise-xor" "bit-not" "bitwise-not"
"bit-shift-left" "jolt-bit-shift-left" "bit-shift-right" "jolt-bit-shift-right"
"unsigned-bit-shift-right" "jolt-unsigned-bit-shift-right"
;; positional protocol-method dispatch (defprotocol-emitted shims) — bind
;; directly to the records.ss entry points so a protocol call doesn't var-deref.
"protocol-dispatch1" "protocol-dispatch1" "protocol-dispatch2" "protocol-dispatch2"
@ -66,7 +76,10 @@
"cons" #(= % 2) "filter" #(= % 2) "remove" #(= % 2) "into" #(= % 2)
"take" #(= % 2) "drop" #(= % 2) "map" #(>= % 2) "apply" #(>= % 2)
"reduce" #(or (= % 2) (= % 3)) "range" #(and (>= % 0) (<= % 3))
"ex-info" #(or (= % 2) (= % 3))})
"ex-info" #(or (= % 2) (= % 3))
"bit-and" #(= % 2) "bit-or" #(= % 2) "bit-xor" #(= % 2) "bit-not" #(= % 1)
"bit-shift-left" #(= % 2) "bit-shift-right" #(= % 2)
"unsigned-bit-shift-right" #(= % 2)})
;; jolt's comparison ops are vacuously true at arity 1 and DON'T inspect the arg,
;; but Scheme's < demands a number even there — special-case.