Numeric return-type hints: ^double/^long on a defn (round 3)

A ^double/^long return hint on a fn's name now (a) coerces the fn's value on the
way out — exact->inexact / jolt->fx, like a JVM primitive return — and (b) types a
call to it, so an accumulator over the result specializes:

  (defn ^double work [^double x ^double y] (+ (* x x) (* y y)))
  (loop [acc 0.0] (recur (+ acc (work a b))))   ; (+ acc (work ..)) -> fl+

The analyzer pushes the name's numeric tag onto each arity (:ret-nhint) for the
back-end coercion, and resolve-global surfaces the callee's declared return
(:num-ret, read from var meta) onto the :var node so jolt.passes.numeric types the
call. defn carries the name hint through.

This unblocks the accumulator-over-fn-result pattern that round 2 had to demote.
The win is bounded by call overhead in an open/dispatched build (~1.15x on a hot
loop whose body is a helper call); it compounds with direct-linking and, later,
inlining. A numeric return hint is a contract, like ^long — redefining the var to
return another type in an open build breaks it.

Not yet: per-arity arglist return hints, (defn f (^double [x] ..)). Gate:
test/chez/numeric-test.ss 39/39; full make test green, 0 new corpus divergences.
This commit is contained in:
Yogthos 2026-06-23 17:21:53 -04:00
parent 0db417a491
commit 5a9acd3cf4
6 changed files with 227 additions and 164 deletions

View file

@ -32,6 +32,9 @@
(define hc-kw-name (keyword #f "name"))
(define hc-kw-var (keyword #f "var"))
(define hc-kw-unresolved (keyword #f "unresolved"))
(define hc-kw-num-ret (keyword #f "num-ret"))
(define hc-kw-double (keyword #f "double"))
(define hc-kw-long (keyword #f "long"))
(define hc-kw-regex (keyword #f "regex"))
(define hc-kw-inst (keyword #f "#inst"))
(define hc-kw-uuid (keyword #f "#uuid"))
@ -176,13 +179,24 @@
;; No :host branch: there is no separate native-op env — the hot
;; clojure.core primitives (+,-,map,...) are declared in clojure.core below so
;; they classify as :var and the emitter's native-op path lowers them.
;; A var's declared numeric return (^double/^long on its name) -> :double/:long,
;; read from its meta. Lets jolt.passes.numeric type a call to it.
(define (hc-cell-num-ret cell)
(let ((m (and cell (hashtable-ref var-meta-table cell #f))))
(and m (let ((t (jolt-get m hc-kw-tag)))
(cond ((equal? t "double") hc-kw-double)
((equal? t "long") hc-kw-long)
(else #f))))))
(define (hc-resolve-global ctx sym)
(let* ((nm (symbol-t-name sym))
(cell (hc-resolve-cell ctx sym)))
(if (and cell (var-cell-defined? cell))
(jolt-hash-map hc-kw-kind hc-kw-var
hc-kw-ns (var-cell-ns cell)
hc-kw-name (var-cell-name cell))
(let ((base (jolt-hash-map hc-kw-kind hc-kw-var
hc-kw-ns (var-cell-ns cell)
hc-kw-name (var-cell-name cell)))
(nr (hc-cell-num-ret cell)))
(if nr (jolt-assoc base hc-kw-num-ret nr) base))
(jolt-hash-map hc-kw-kind hc-kw-unresolved hc-kw-name nm))))
(define (hc-intern! ctx ns-name nm) (declare-var! ns-name nm) jolt-nil)

File diff suppressed because one or more lines are too long