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.
51 lines
2.6 KiB
Text
51 lines
2.6 KiB
Text
# Whole-program (Stalin) mode (jolt-t34, opt-in JOLT_WHOLE_PROGRAM): one closed-
|
|
# world inference fixpoint over ALL user namespaces, so param types propagate
|
|
# across ns boundaries (a non-inlined fn's record params get proven from its
|
|
# callers in another unit). This must be SOUND — same results as the per-ns
|
|
# pass — which is what this test guards, by running a cross-namespace record
|
|
# program both ways through the built binary and comparing output. Skips cleanly
|
|
# if build/jolt is absent (source-only test run).
|
|
(def jolt "build/jolt")
|
|
|
|
(defn- run [env-extra]
|
|
(def dir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-wp-test"))
|
|
(os/mkdir dir)
|
|
(spit (string dir "/wputil.clj")
|
|
(string "(ns wputil)\n"
|
|
"(defrecord V [x y z])\n"
|
|
# const-link targets (jolt-rvt): a data def and a ^:redef fn are
|
|
# indirect (cell deref) per-ns but embedded as constants under
|
|
# whole-program. Soundness => both modes must give the same answer.
|
|
"(def scale 2.0)\n"
|
|
"(defn ^:redef bump [x] (+ x 1.0))\n"
|
|
# recursive => never inlined; params proven only whole-program
|
|
"(defn dot [a b n]\n"
|
|
" (if (<= n 0) 0.0\n"
|
|
" (+ (* (:x a) (:x b)) (* (:y a) (:y b)) (* (:z a) (:z b)) (bump (* scale (dot a b (dec n)))))))\n"))
|
|
(spit (string dir "/wpmain.clj")
|
|
(string "(ns wpmain (:require [wputil :as v]))\n"
|
|
"(defn -main []\n"
|
|
" (loop [i 0 acc 0.0]\n"
|
|
" (if (< i 1000)\n"
|
|
" (let [a (v/->V (double i) 2.0 3.0) b (v/->V 1.0 (double i) 0.5)]\n"
|
|
" (recur (inc i) (+ acc (v/dot a b 2))))\n"
|
|
" (println \"sum\" acc))))\n"))
|
|
(def out (string dir "/out.txt"))
|
|
(def jbin (string (os/cwd) "/" jolt))
|
|
(def cmd (string env-extra "JOLT_DIRECT_LINK=1 JOLT_PATH=" dir " " jbin
|
|
" -m wpmain > " out " 2>&1"))
|
|
(os/execute ["sh" "-c" cmd] :p)
|
|
(string/trimr (slurp out)))
|
|
|
|
(if (not (os/stat jolt))
|
|
(print "whole-program: SKIP (no build/jolt — run from source)")
|
|
# -m now auto-enables whole-program under direct-linking, so the per-ns
|
|
# baseline must explicitly opt out to exercise the per-namespace path.
|
|
(let [per-ns (run "JOLT_NO_WHOLE_PROGRAM=1 ")
|
|
whole (run "JOLT_WHOLE_PROGRAM=1 ")]
|
|
(printf " per-ns: %s" per-ns)
|
|
(printf " whole-program: %s" whole)
|
|
(if (and (= per-ns whole) (string/has-prefix? "sum" per-ns))
|
|
(print "whole-program: results match — sound")
|
|
(do (printf "whole-program: MISMATCH per-ns=%q whole=%q" per-ns whole)
|
|
(os/exit 1)))))
|