;; mono-dispatch — protocol dispatch where every call site sees ONE record type
;; (monomorphic). This is the regime where devirtualization and a
;; call-site inline cache CAN fire — the megamorphic `dispatch` bench deliberately
;; defeats them, so this is its complement: it measures how close a proven/cached
;; monomorphic dispatch gets to a direct call. Same per-call work as `dispatch`.
;;
;; Portable Clojure (jolt + JVM Clojure).
;;   jolt -m mono-dispatch 20000     (JOLT_DIRECT_LINK=1 JOLT_WHOLE_PROGRAM=1)
(ns mono-dispatch)

(defprotocol Shape
  (area [s])
  (sides [s]))

(defrecord Circle [r] Shape (area [_] (* (* 3.14159 r) r)) (sides [_] 0))

;; homogeneous: every element is a Circle -> the call site is monomorphic
(defn build-shapes [n]
  (mapv (fn [i] (->Circle (+ 1 (mod i 7)))) (range n)))

(defn sum-area [shapes]
  (reduce (fn [acc s] (+ (+ acc (area s)) (sides s))) 0.0 shapes))

(defn run [iters]
  (let [shapes (build-shapes 1000)]
    (loop [i 0 acc 0.0]
      (if (< i iters)
        (recur (inc i) (+ acc (sum-area shapes)))
        acc))))

(defn -main [& args]
  (let [iters (if (seq args) (Integer/parseInt (first args)) 20000)]
    (dotimes [_ 2] (run (quot iters 4)))                 ; warmup
    (let [runs 3
          times (mapv (fn [_]
                        (let [t0 (System/nanoTime)
                              r (run iters)
                              ms (/ (- (System/nanoTime) t0) 1000000.0)]
                          [ms r]))
                      (range runs))
          mss (mapv first times)
          mean (/ (reduce + mss) runs)]
      (println "mono-dispatch iters" iters "result" (second (first times)))
      (println "runs:" (mapv (fn [t] (/ (Math/round (* t 10.0)) 10.0)) mss))
      (println "mean:" (/ (Math/round (* mean 10.0)) 10.0) "ms"))))
