Devirtualize monomorphic protocol calls (#227)

When the inference proves a protocol call's receiver is one record type, the
back end resolves the impl by that static tag (find-protocol-method) instead of
routing through the protocol var -> jolt-invoke -> protocol-resolve, which
re-derives the tag and walks the type table. Same table lookup, minus the
var-deref, the rest-cons, and the receiver-type computation.

Fires only on a monomorphic site: a megamorphic receiver joins to :any and
carries no :devirt-type, so it keeps ordinary dispatch (the dispatch bench is
unaffected). The annotation comes from the whole-program fixpoint typing a
reduce/HOF element or a ctor return as a specific record.

Modest on the dispatch benchmarks (~6% on mono-dispatch) — float boxing in the
reduce accumulator dominates there, a separate numeric lever — but it removes
the dispatch overhead wherever a typed receiver is known.

run-devirt.ss gate: emitted form uses find-protocol-method, and evaluating it
matches ordinary dispatch for an inline impl, an extend-type impl, and the
non-devirt path. make test / shakesmoke green, 0 new divergences.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-26 11:16:19 +00:00 committed by GitHub
parent 09712ec575
commit af11aaa7ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 167 additions and 78 deletions

View file

@ -498,6 +498,18 @@
(fn [[f & as]]
(str "(jolt-invoke " f (if (seq as) (str " " (str/join " " as)) "") ")"))))]
(cond
;; devirtualized protocol call: the inference proved the receiver (arg 0) is
;; one record type, so resolve the impl by that static tag instead of routing
;; through the protocol var -> jolt-invoke -> protocol-resolve (which recomputes
;; the tag and walks the type table). find-protocol-method does the same table
;; lookup the dispatch would, but with no var-deref, no rest-cons, and no
;; receiver-type computation. Fires only on a monomorphic site (a megamorphic
;; receiver joins to :any and carries no :devirt-type).
(:devirt-type node)
(order-args (fn [as]
(str "((find-protocol-method " (chez-str-lit (:devirt-type node)) " "
(chez-str-lit (:devirt-proto node)) " " (chez-str-lit (:devirt-method node))
") " (str/join " " as) ")")))
;; hint-directed fast arithmetic: jolt.passes.numeric proved every operand a
;; flonum (^double) or fixnum (^long), so emit the Chez fl*/fx* op.
(:num-kind node) (emit-numeric (:num-kind node) (:name fnode) args order-args)