;; dispatch — a POLYMORPHIC-DISPATCH stress test. A protocol method is called in
;; a hot loop over a heterogeneous (megamorphic) collection of record types, with
;; minimal per-call work, so protocol dispatch dominates. This is the regime
;; devirtualization and the inline-cache target, and the one the ray
;; tracer can't reveal — its dispatch is monomorphic and a small fraction of the
;; float-math cost (devirt measured FLAT there).
;;
;; Portable Clojure (jolt + JVM Clojure).
;;   jolt -m dispatch 20000     (JOLT_DIRECT_LINK=1 JOLT_WHOLE_PROGRAM=1)
(ns dispatch)

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

(defrecord Circle [r]    Shape (area [_] (* (* 3.14159 r) r))      (sides [_] 0))
(defrecord Square [s]    Shape (area [_] (* s s))                  (sides [_] 4))
(defrecord Triangle [b h] Shape (area [_] (* (* 0.5 b) h))         (sides [_] 3))
(defrecord Rect [w h]    Shape (area [_] (* w h))                  (sides [_] 4))

(defn build-shapes [n]
  (mapv (fn [i]
          (let [k (mod i 4)]
            (cond
              (= k 0) (->Circle (+ 1 (mod i 7)))
              (= k 1) (->Square (+ 1 (mod i 5)))
              (= k 2) (->Triangle (+ 1 (mod i 3)) (+ 2 (mod i 6)))
              :else   (->Rect (+ 1 (mod i 4)) (+ 1 (mod i 8))))))
        (range n)))

;; megamorphic: every element may be a different type -> the call site sees all 4
(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 "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"))))
