Running a program is a closed world — every namespace is required, then it runs to completion — so make it direct-link by default (inlining, record shapes, the inference's specialization), and for a -m/-M entry auto-enable the whole-program cross-namespace inference pass. A decomposed multi-namespace program was ~3.7x slower than the same code in one namespace purely because per-namespace inference can't see a caller in a not-yet-loaded namespace; this closes that for the common case with no flags and no hints. Interactive modes (repl, -e, nrepl-server) stay indirect/open — they have to let you redefine vars, which direct-linking seals against. Opt-outs: JOLT_NO_DIRECT_LINK forces the open path even for a program run (hot-reload, runtime redefinition); JOLT_NO_WHOLE_PROGRAM keeps direct-linking but per-ns; JOLT_DIRECT_LINK / JOLT_WHOLE_PROGRAM still force-on. Namespaces required inside -main (after the batch pass) fall back to per-ns inference. The success checker (RFC 0006) rides on the inference for free, but a casual program run shouldn't spam type warnings just because it now direct-links, so its default-on is suppressed when direct-linking was auto-enabled (:direct-link-auto?); an explicit JOLT_DIRECT_LINK or JOLT_TYPE_CHECK still turns it on. whole-program- test and devirt-test opt their per-ns baseline out of the new auto-default. Docs: RFC 0005 gains 'Compilation modes and defaults' + 'Cross-namespace inference'; RFC 0004 documents cross-ns/param hints; self-hosting-compiler and --help updated. Full gate green.
43 lines
2.4 KiB
Text
43 lines
2.4 KiB
Text
# Protocol-dispatch devirtualization (jolt-41m): when the inference proves a
|
|
# protocol call's receiver is a known record type, the call is compiled to a
|
|
# DIRECT method call, skipping the runtime dispatch registry. This must stay
|
|
# SOUND — same results as the dispatched path — including polymorphic dispatch
|
|
# (the right method per type), fallback when the receiver type is unknown, and
|
|
# heterogeneous collections. Runs a protocol+record program through the built
|
|
# binary (devirt needs infer-unit!, which runs on ns load, not eval-string) and
|
|
# checks the output. Skips cleanly if build/jolt is absent.
|
|
(def jolt "build/jolt")
|
|
|
|
(defn- run [whole?]
|
|
(def dir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-dv-test"))
|
|
(os/mkdir dir)
|
|
(spit (string dir "/dv.clj")
|
|
(string
|
|
"(ns dv)\n"
|
|
"(defprotocol Shape (area [s]) (kind [s]))\n"
|
|
"(defrecord Rect [w h] Shape (area [r] (* (:w r) (:h r))) (kind [_] :rect))\n"
|
|
"(defrecord Circ [r] Shape (area [c] (* 3 (:r c) (:r c))) (kind [_] :circ))\n"
|
|
"(defn poly [s] (area s))\n" # receiver unknown -> must fall back
|
|
"(defn -main []\n"
|
|
" (println (area (->Rect 3 4)) (area (->Circ 5))\n" # devirt: 12 75
|
|
" (kind (->Rect 1 1)) (kind (->Circ 1))\n" # devirt: :rect :circ
|
|
" (poly (->Rect 3 4)) (poly (->Circ 5))\n" # fallback: 12 75
|
|
" (mapv area [(->Rect 2 3) (->Circ 2)])))\n")) # heterogeneous: [6 12]
|
|
(def out (string dir "/out.txt"))
|
|
(def jbin (string (os/cwd) "/" jolt))
|
|
# -m auto-enables whole-program under direct-linking now, so the per-ns case
|
|
# (whole? false) must explicitly opt out to test the dispatched/per-ns path.
|
|
(def cmd (string (if whole? "JOLT_WHOLE_PROGRAM=1 " "JOLT_NO_WHOLE_PROGRAM=1 ")
|
|
"JOLT_DIRECT_LINK=1 JOLT_PATH=" dir " " jbin " -m dv > " out " 2>&1"))
|
|
(os/execute ["sh" "-c" cmd] :p)
|
|
(string/trimr (slurp out)))
|
|
|
|
(def expected "12 75 :rect :circ 12 75 [6 12]")
|
|
(if (not (os/stat jolt))
|
|
(print "devirt: SKIP (no build/jolt — run from source)")
|
|
(let [per-ns (run false) whole (run true)]
|
|
(printf " per-ns: %s" per-ns)
|
|
(printf " whole-program: %s" whole)
|
|
(if (and (= per-ns expected) (= whole expected))
|
|
(print "devirt: correct (dispatched == devirtualized)")
|
|
(do (printf "devirt: WRONG — expected %q" expected) (os/exit 1)))))
|