devirtualize protocol dispatch on known record receivers (jolt-41m)

A protocol method call compiles to (protocol-dispatch proto method this rest) — a
runtime registry walk (type-tag -> proto -> method) on every call, ~19x a direct
call. When the inference proves the receiver (arg 0) is a known record type, the
call now resolves to a DIRECT method call at compile time, skipping the registry.

- defprotocol registers each method's var-key 'ns/method' -> [proto method] (a
  ctx-capturing register-protocol-methods! emitted into the do-block); infer-unit!
  feeds it to the inference via a box (like record-shapes).
- the record-ctor return type carries :type (the record tag) so the inference
  knows the receiver type; the :else invoke case annotates a protocol call whose
  arg0 has a known :type with :devirt-{type,proto,method}.
- emit-invoke resolves the impl via find-protocol-method at emit time and emits a
  direct call to the embedded impl fn value. Unknown/polymorphic receivers (no
  proven :type) fall back to the dispatch path unchanged.

Measured: removes the dispatch overhead (14.7s -> 9.3s on a 10M-call loop); the
remaining cost is the method body itself (non-inlined, unproven reads) — inlining
the resolved method is the follow-up (jolt-t6r) toward direct-call speed.

Sound under the closed-world assumption direct-linking already makes (the impl is
resolved + embedded at compile time). Adds devirt-test (subprocess: dispatched ==
devirtualized across polymorphic dispatch, unknown-receiver fallback, and
heterogeneous collections). Stalin's compile-call/callee-environment is the model.
This commit is contained in:
Yogthos 2026-06-14 03:33:48 -04:00
parent a83792cc51
commit fa02c8f93d
5 changed files with 94 additions and 11 deletions

View file

@ -318,6 +318,9 @@
{} sigs)]
`(do
(def ~pname (make-protocol ~(name pname) ~methods))
;; register method var-keys for devirtualization (jolt-41m); the inference
;; reads this (via infer-unit!) to resolve a protocol call on a known record
(register-protocol-methods! ~(name pname) [~@(map (fn [s] (name (first s))) sigs)])
~@(map (fn [sig]
`(def ~(first sig)
;; protocol-dispatch is a fn (clojure.core); pass the protocol /