core: AOT context image — init-cached recovers the bootstrap cost across processes
init in compile mode is ~2.4 s (tier loading, analyzer self-compile, macro recompilation), paid by every process that builds a ctx from source — each jpm-test file, embedders, workers. init-cached marshals the built ctx to a disk image (same root-env dicts as snapshot/fork) and later processes unmarshal it in ~5 ms, any process: nothing from the baking process is needed at load. The cache key fingerprints the embedded .clj stdlib (which covers jolt-core: analyzer, IR, core tiers), the .janet seed sources next to the module, the janet version, the init opts, and the env knobs that shape a ctx (JOLT_PATH/ MUTABLE/AOT_CORE/FEATURES) — any change rebuilds. Corrupt or non-ctx images fall back to a rebuild (unmarshal of garbage can 'succeed' with a scalar, so the shape is checked, not just the throw). Writes are atomic (tmp + rename) so racing cold starts never publish a torn image. JOLT_NO_IMAGE_CACHE=1 opts out; JOLT_IMAGE_CACHE_DIR overrides the location (default TMPDIR). Test consumers switch to init-cached (harness, suite-worker, conformance, the behavioral unit/integration tests); tests that validate the bootstrap itself (bootstrap-fixpoint, staged-bootstrap, aot round-trip, direct-linking) and the deps tests (tmp-dir :paths would fragment the key) keep real init. Full jpm test: 2:46 -> 1:58 (~29%). New ctx-image-test covers cold/warm, cross-process load (subprocess runs defn/redef/macros/protocols/multimethods off the baked image), per-opts keying, and corrupt-image fallback.
This commit is contained in:
parent
bf885078f9
commit
0e3584884f
24 changed files with 222 additions and 64 deletions
|
|
@ -194,6 +194,86 @@
|
|||
[snap]
|
||||
(unmarshal snap image-load-dict))
|
||||
|
||||
# --- Disk-cached init (AOT context image) ------------------------------------
|
||||
#
|
||||
# init in compile mode is ~2.4 s (tier loading, analyzer self-compile, macro
|
||||
# recompilation) — paid by every PROCESS that builds a ctx from source, e.g.
|
||||
# each `jpm test` file. init-cached pays it once: the built ctx is snapshotted
|
||||
# to an image file and later processes unmarshal it (~tens of ms). Marshaling
|
||||
# is against root-env (same dicts as snapshot/fork), so core cfunctions ride by
|
||||
# name and everything jolt-level rides by value — a loaded image needs nothing
|
||||
# from the baking process. The cache key fingerprints everything a fresh build
|
||||
# would read: the embedded .clj stdlib (which includes jolt-core — the
|
||||
# analyzer, IR, and core tiers), the .janet seed sources next to this module,
|
||||
# the janet version, and the init opts. Any change rebuilds; a corrupt or
|
||||
# unreadable image silently rebuilds. JOLT_NO_IMAGE_CACHE=1 disables.
|
||||
|
||||
# Captured at module load: in source mode this is .../src/jolt/api.janet, so
|
||||
# the seed sources can be fingerprinted; nil or stale in a built binary, where
|
||||
# the baked-at-build-time ctx makes init-cached pointless anyway.
|
||||
(def- api-module-file (dyn :current-file))
|
||||
|
||||
(defn- src-dir []
|
||||
(when api-module-file
|
||||
(let [idxs (string/find-all "/" api-module-file)]
|
||||
(when (not (empty? idxs))
|
||||
(string/slice api-module-file 0 (last idxs))))))
|
||||
|
||||
(defn- source-fingerprint
|
||||
"Hash + total length of every source a fresh init depends on. Two numbers, so
|
||||
a 32-bit hash collision alone can't alias two different source trees."
|
||||
[]
|
||||
(def buf @"")
|
||||
(each k (sorted (keys stdlib-embed/sources))
|
||||
(buffer/push buf k "\x00" (get stdlib-embed/sources k) "\x00"))
|
||||
(def dir (src-dir))
|
||||
(when dir
|
||||
(each f (sorted (os/dir dir))
|
||||
(when (string/has-suffix? ".janet" f)
|
||||
(buffer/push buf f "\x00" (slurp (string dir "/" f)) "\x00"))))
|
||||
[(hash (string buf)) (length buf)])
|
||||
|
||||
(defn- image-cache-path [opts]
|
||||
(def dir (or (os/getenv "JOLT_IMAGE_CACHE_DIR")
|
||||
(os/getenv "TMPDIR")
|
||||
"/tmp"))
|
||||
(def [h len] (source-fingerprint))
|
||||
# Opts land in the key via their printed form; an opt that prints unstably
|
||||
# (e.g. a closure in :namespaces) just degrades to a cache miss, never to a
|
||||
# wrong hit. Runtime knobs that shape the ctx outside opts ride along too.
|
||||
(def key (string/format "%q|%q|%q|%q|%q|%q"
|
||||
(string janet/version "-" janet/build)
|
||||
opts
|
||||
(os/getenv "JOLT_PATH")
|
||||
(os/getenv "JOLT_MUTABLE")
|
||||
(os/getenv "JOLT_AOT_CORE")
|
||||
(os/getenv "JOLT_FEATURES")))
|
||||
(string dir "/jolt-ctx-" (band h 0x7FFFFFFF) "-" len "-" (band (hash key) 0x7FFFFFFF) ".jimg"))
|
||||
|
||||
(defn init-cached
|
||||
"init, but disk-cached: the first call builds the context and writes a
|
||||
bytecode image; later calls (any process, same sources) load the image
|
||||
instead of rebuilding. Same opts as init. JOLT_NO_IMAGE_CACHE=1 disables;
|
||||
JOLT_IMAGE_CACHE_DIR overrides the cache directory (default TMPDIR)."
|
||||
[&opt opts]
|
||||
(default opts {})
|
||||
(if (or (= "1" (os/getenv "JOLT_NO_IMAGE_CACHE")) (nil? (src-dir)))
|
||||
(init opts)
|
||||
(let [path (image-cache-path opts)
|
||||
loaded (when (os/stat path)
|
||||
(let [r (protect (fork (slurp path)))]
|
||||
# unmarshal of a corrupt image can also "succeed" with a
|
||||
# non-ctx value, so check the shape, not just the throw.
|
||||
(when (and (r 0) (ctx? (r 1)))
|
||||
(r 1))))]
|
||||
(or loaded
|
||||
(let [ctx (init opts)
|
||||
tmp (string path "." (os/getpid) ".tmp")]
|
||||
# Atomic publish so concurrent cold starts never see a torn image.
|
||||
(when (protect (spit tmp (snapshot ctx)))
|
||||
(protect (os/rename tmp path)))
|
||||
ctx)))))
|
||||
|
||||
(defn eval-one
|
||||
"Evaluate a single already-parsed form. Routing (compile when :compile? is set,
|
||||
stateful forms interpret, interpreter fallback for forms the compiler can't
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
(use ../../src/jolt/types)
|
||||
|
||||
(print "1: init creates context...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (ctx? ctx) "init returns context")
|
||||
(let [ns (ctx-find-ns ctx "clojure.core")]
|
||||
(assert (ns? ns) "clojure.core namespace exists")
|
||||
|
|
@ -11,27 +11,27 @@
|
|||
(print " passed")
|
||||
|
||||
(print "2: eval-string basics...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 42 (eval-string ctx "42")) "eval integer")
|
||||
(assert (= true (eval-string ctx "true")) "eval bool")
|
||||
(assert (= 3 (eval-string ctx "(+ 1 2)")) "eval list"))
|
||||
(print " passed")
|
||||
|
||||
(print "3: eval-string with core fns...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (eval-string ctx "(nil? nil)")) "nil?")
|
||||
(assert (deep= [2 3 4] (normalize-pvecs (eval-string ctx "(map inc [1 2 3])"))) "map+inc")
|
||||
(assert (= 6 (eval-string ctx "(reduce + [1 2 3])")) "reduce"))
|
||||
(print " passed")
|
||||
|
||||
(print "4: eval-string with def...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(eval-string ctx "(def x 42)")
|
||||
(assert (= 42 (eval-string ctx "x")) "def then resolve"))
|
||||
(print " passed")
|
||||
|
||||
(print "5: eval-string* with bindings...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 99 (eval-string* ctx "y" @{"y" 99})) "bound variable"))
|
||||
(print " passed")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
|
||||
|
||||
(print "Phase 6: comprehensive compile-mode tests...")
|
||||
(let [ctx (init {:compile? true})]
|
||||
(let [ctx (init-cached {:compile? true})]
|
||||
|
||||
(print " collections...")
|
||||
(assert (= :a (ct-eval ctx "(nth [:a :b :c :d] 0)")) "nth")
|
||||
|
|
@ -126,13 +126,13 @@
|
|||
# Context isolation: a def in one compiled context is invisible in another. With
|
||||
# var-indirection each context has its own var cells, so b's `secret` is a
|
||||
# distinct, unbound var (nil) rather than a's 7.
|
||||
(let [a (init {:compile? true}) b (init {:compile? true})]
|
||||
(let [a (init-cached {:compile? true}) b (init-cached {:compile? true})]
|
||||
(eval-string a "(def secret 7)")
|
||||
(assert (= 7 (ct-eval a "secret")) "def visible in its own ctx")
|
||||
(assert (nil? (ct-eval b "secret")) "def isolated to its ctx"))
|
||||
|
||||
# Redefinition is visible to already-compiled callers (var-indirection).
|
||||
(let [c (init {:compile? true})]
|
||||
(let [c (init-cached {:compile? true})]
|
||||
(eval-string c "(defn g [] 1)")
|
||||
(eval-string c "(defn calls-g [] (g))")
|
||||
(eval-string c "(defn g [] 2)")
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@
|
|||
# instead of its own init (~50 ms interpreted / ~900 ms compiled). Isolation is
|
||||
# preserved — a fork shares nothing mutable with its siblings. For self-host
|
||||
# mode, compile one form first so the lazily-built analyzer is in the snapshot.
|
||||
(def base (init init-opts))
|
||||
(def base (init-cached init-opts))
|
||||
(when selfhost? (selfhost/compile-and-eval base (parse-string "1")))
|
||||
(def snap (snapshot base))
|
||||
(def fails @[])
|
||||
|
|
|
|||
78
test/integration/ctx-image-test.janet
Normal file
78
test/integration/ctx-image-test.janet
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# init-cached: disk-cached AOT image of the fully-built context.
|
||||
#
|
||||
# init in compile mode costs ~2.4 s (tier loading, analyzer self-compile, macro
|
||||
# recompilation). init-cached pays that once, marshals the built ctx to an image
|
||||
# file (the same machinery as api/snapshot), and every later process unmarshals
|
||||
# it instead of rebuilding. The cache key fingerprints the embedded .clj stdlib,
|
||||
# the .janet seed sources, and the init opts, so any source change invalidates.
|
||||
#
|
||||
# The cross-process case is the one that matters (each `jpm test` file is its
|
||||
# own janet process), so the warm-load checks run in a SUBPROCESS against the
|
||||
# image this process bakes.
|
||||
(use ../../src/jolt/api)
|
||||
|
||||
(print "ctx image cache...")
|
||||
|
||||
(def dir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-img-test-" (os/getpid)))
|
||||
(os/mkdir dir)
|
||||
(os/setenv "JOLT_IMAGE_CACHE_DIR" dir)
|
||||
# The cache is the test subject — undo an ambient opt-out (and for the subprocess).
|
||||
(os/setenv "JOLT_NO_IMAGE_CACHE" nil)
|
||||
(defer (do (each f (os/dir dir) (os/rm (string dir "/" f))) (os/rmdir dir))
|
||||
|
||||
# 1. Cold init: builds the ctx, writes an image, and is fully functional.
|
||||
(def t0 (os/clock))
|
||||
(def ctx1 (init-cached {:compile? true}))
|
||||
(def cold-s (- (os/clock) t0))
|
||||
(assert (= 3 (eval-string ctx1 "(+ 1 2)")) "cold ctx evaluates")
|
||||
(def files (filter |(string/has-suffix? ".jimg" $) (os/dir dir)))
|
||||
(assert (= 1 (length files)) (string "one image written, got " (length files)))
|
||||
|
||||
# 2. Warm init in THIS process: loads the image, functional, and much faster.
|
||||
(def t1 (os/clock))
|
||||
(def ctx2 (init-cached {:compile? true}))
|
||||
(def warm-s (- (os/clock) t1))
|
||||
(assert (= 10 (eval-string ctx2 "(do (defn f [x] (* 2 x)) (f 5))")) "warm ctx compiles defns")
|
||||
(assert (< warm-s (/ cold-s 4))
|
||||
(string/format "warm load (%.0f ms) at least 4x faster than cold (%.0f ms)"
|
||||
(* 1000 warm-s) (* 1000 cold-s)))
|
||||
|
||||
# 3. Different opts get a different image (no false sharing between modes).
|
||||
(init-cached {})
|
||||
(def files2 (filter |(string/has-suffix? ".jimg" $) (os/dir dir)))
|
||||
(assert (= 2 (length files2)) "interpret-mode image is keyed separately")
|
||||
|
||||
# 4. Cross-process warm load: a fresh janet process loads the image this
|
||||
# process baked and runs real work — compiled defns, redefinition,
|
||||
# macros, lazy seqs, protocols, multimethods, stdlib require.
|
||||
(def checks
|
||||
``(use ./src/jolt/api)
|
||||
(def t0 (os/clock))
|
||||
(def ctx (init-cached {:compile? true}))
|
||||
(def warm-ms (* 1000 (- (os/clock) t0)))
|
||||
(def img-files (filter |(string/has-suffix? ".jimg" $)
|
||||
(os/dir (os/getenv "JOLT_IMAGE_CACHE_DIR"))))
|
||||
(assert (= 2 (length img-files)) "subprocess hit the cache (no new image)")
|
||||
(assert (= 3 (eval-string ctx "(+ 1 2)")) "arith")
|
||||
(assert (= 120 (eval-string ctx "(do (defn fact [n] (if (zero? n) 1 (* n (fact (dec n))))) (fact 5))")) "compiled defn")
|
||||
(assert (= 7 (eval-string ctx "(do (def a 3) (def a 7) a)")) "redefinition")
|
||||
(assert (= 6 (eval-string ctx "(-> 1 inc (* 3))")) "macros expand")
|
||||
(assert (= 9 (eval-string ctx "(do (defmacro tw [x] `(* 3 ~x)) (tw 3))")) "defmacro works post-load")
|
||||
(assert (= [2 4 6] (normalize-pvecs (eval-string ctx "(vec (map #(* 2 %) [1 2 3]))"))) "lazy/HOF")
|
||||
(assert (= "a-b" (eval-string ctx "(do (require '[clojure.string :as str]) (str/join \"-\" [\"a\" \"b\"]))")) "stdlib require")
|
||||
(assert (= 42 (eval-string ctx "(do (defprotocol P (pf [x])) (defrecord R [] P (pf [x] 42)) (pf (->R)))")) "protocols")
|
||||
(assert (= :big (eval-string ctx "(do (defmulti m (fn [x] (if (> x 5) :big :small))) (defmethod m :big [_] :big) (m 10))")) "multimethods")
|
||||
(print "subprocess warm load ok in " (math/round warm-ms) " ms")``)
|
||||
(def code (os/execute ["janet" "-e" checks] :p))
|
||||
(assert (= 0 code) "cross-process warm load passes")
|
||||
|
||||
# 5. A source change invalidates: poisoning the fingerprint env knob is not
|
||||
# possible from here, but a corrupted image must fall back to a rebuild
|
||||
# rather than crash.
|
||||
(each f (os/dir dir)
|
||||
(when (string/has-suffix? ".jimg" f) (spit (string dir "/" f) "garbage")))
|
||||
(def ctx3 (init-cached {:compile? true}))
|
||||
(assert (= 3 (eval-string ctx3 "(+ 1 2)")) "corrupted image falls back to rebuild")
|
||||
|
||||
(printf "ctx image cache passed! (cold %.0f ms, warm %.0f ms)"
|
||||
(* 1000 cold-s) (* 1000 warm-s)))
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
(printf "FAIL [%s] got %q want %q" label got want)))
|
||||
|
||||
(each mode [{:compile? true} {} {:aot-core? false}]
|
||||
(def ctx (init mode))
|
||||
(def ctx (init-cached mode))
|
||||
(eval-string ctx "(defprotocol P (m [x]))")
|
||||
(eval-string ctx "(extend-protocol P Number (m [x] (* x 2)))")
|
||||
(check (string mode " host dispatch") (eval-string ctx "(m 5)") 10)
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
# Multimethod hierarchy-fallback cache (jolt-g8w): the isa? walk result for a
|
||||
# dispatch value is cached; defmethod/remove-method must invalidate it.
|
||||
(each mode [{:compile? true} {} {:aot-core? false}]
|
||||
(def ctx (init mode))
|
||||
(def ctx (init-cached mode))
|
||||
(eval-string ctx "(derive ::circle ::shape)")
|
||||
(eval-string ctx "(derive ::square ::shape)")
|
||||
(eval-string ctx "(defmulti area identity)")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
(use ../../src/jolt/api)
|
||||
(use ../../src/jolt/types)
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
(ctx-set-current-ns ctx "user")
|
||||
(put (ctx :env) :source-paths @[]) # no FS roots — embedded fallback only
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
(use ../../src/jolt/api)
|
||||
(use ../../src/jolt/reader)
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
|
||||
(defn- analyzes? [s]
|
||||
# true if the analyzer produced IR (compiled), false if it punted/uncompilable.
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@
|
|||
|
||||
(defn check [label expected actual]
|
||||
# evaluate (= expected actual) in a fresh ctx; expects boolean true
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
(def res (protect (eval-string ctx (string "(= " expected " " actual ")"))))
|
||||
(cond
|
||||
(not= (res 0) true)
|
||||
(array/push fails [label "ERROR" (string (res 1))])
|
||||
(= (res 1) true)
|
||||
(++ pass)
|
||||
(let [got (protect (eval-string (init) actual))]
|
||||
(let [got (protect (eval-string (init-cached) actual))]
|
||||
(array/push fails [label "NEQ"
|
||||
(string "want=" expected " got="
|
||||
(if (= (got 0) true) (string/format "%q" (got 1)) (string "ERR:" (got 1))))]))))
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
(def fails @[])
|
||||
(each f files
|
||||
# A pass-test passes when no assertion throws (assert now errors on failure).
|
||||
(def res (protect (load-string (init) (slurp f))))
|
||||
(def res (protect (load-string (init-cached) (slurp f))))
|
||||
(if (= (res 0) true)
|
||||
(++ pass)
|
||||
(array/push fails (string/slice f (+ 1 (length jank-dir))))))
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
(do (ev/sleep 0.1) (++ tries)))))
|
||||
(assert ready "nREPL server did not start")
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
(ctx-set-current-ns ctx "user")
|
||||
(load-string ctx "(require '[jolt.nrepl])")
|
||||
(load-string ctx (string "(def c (jolt.nrepl/connect {:port " port "}))"))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
# context; the portable default is #{:jolt :default}).
|
||||
(reader-features-set! ["jolt" "clj" "default"])
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
|
||||
(printf "Loading SCI stubs...\n")
|
||||
(defn load-stubs [ctx filepath]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
# Run from project root so paths resolve
|
||||
(def root (if (has-value? (dyn :syspath) 0) (first (dyn :syspath)) "."))
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
|
||||
(load-stubs ctx (string root "/src/jolt/clojure/sci/lang_stubs.clj"))
|
||||
(load-stubs ctx (string root "/src/jolt/clojure/sci/io_stubs.clj"))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
(defn ce [ctx s] (normalize-pvecs (backend/compile-and-eval ctx (parse-string s))))
|
||||
|
||||
(print "self-host pipeline (Clojure analyzer -> IR -> Janet)...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
# primitives + control flow
|
||||
(assert (= 3 (ce ctx "(+ 1 2)")) "+")
|
||||
(assert (= 6 (ce ctx "(* 2 3)")) "*")
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
# compile path. Forms the analyzer can't handle (stateful / destructuring) fall
|
||||
# back to the interpreter, with the same observable results.
|
||||
(print "self-host via eval-toplevel routing...")
|
||||
(let [ctx (init {:compile? true})]
|
||||
(let [ctx (init-cached {:compile? true})]
|
||||
(defn ev [s] (normalize-pvecs (eval-one ctx (parse-string s))))
|
||||
(assert (= 3 (ev "(+ 1 2)")) "tl +")
|
||||
(ev "(defn sq [x] (* x x))") # def via self-host
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
# Proof the self-hosted pipeline actually ran: only backend/ensure-analyzer
|
||||
# populates jolt.analyzer. An interpret-only ctx never loads it.
|
||||
(assert (pos? (length ((ctx-find-ns ctx "jolt.analyzer") :mappings))) "analyzer loaded under :compile?"))
|
||||
(let [ctx (init {})]
|
||||
(let [ctx (init-cached {})]
|
||||
(eval-one ctx (parse-string "(+ 1 2)"))
|
||||
(assert (zero? (length ((ctx-find-ns ctx "jolt.analyzer") :mappings))) "analyzer NOT loaded when interpreting"))
|
||||
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
# load into clojure.core at init and work the same compiled or interpreted.
|
||||
(print "clojure.core overlay (Clojure-defined core fns)...")
|
||||
(each opts [{:compile? true} {}]
|
||||
(let [ctx (init opts)]
|
||||
(let [ctx (init-cached opts)]
|
||||
(defn ev [s] (normalize-pvecs (eval-one ctx (parse-string s))))
|
||||
(assert (= 1 (ev "(ffirst [[1 2] [3 4]])")) "ffirst")
|
||||
(assert (= [2] (ev "(nfirst [[1 2] [3 4]])")) "nfirst")
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
# portable Clojure analyzer + Janet back end, hybrid with interpreter fallback)
|
||||
# so the whole battery validates the self-hosted compiler against the baseline.
|
||||
(def selfhost? (= "1" (os/getenv "JOLT_SELFHOST")))
|
||||
(def ctx (init (if compile? {:compile? true} {})))
|
||||
(def ctx (init-cached (if compile? {:compile? true} {})))
|
||||
(defn run-form [f]
|
||||
(cond
|
||||
selfhost? (selfhost/compile-and-eval ctx f)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
# --- Type Predicates ---
|
||||
(print "1: type predicates...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(integer? 0)")) "integer? 0")
|
||||
(assert (= true (ct-eval ctx "(integer? 1)")) "integer? 1")
|
||||
(assert (= true (ct-eval ctx "(integer? -5)")) "integer? negative")
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
# --- Number Predicates ---
|
||||
(print "2: number predicates...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(zero? 0)")) "zero? 0")
|
||||
(assert (= false (ct-eval ctx "(zero? 1)")) "zero? 1")
|
||||
# zero?/pos?/neg? now reject non-numbers (Clojure-strict), like the JVM.
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
# --- Math ---
|
||||
(print "3: math operations...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 0 (ct-eval ctx "(+)")) "+ 0 args")
|
||||
(assert (= 5 (ct-eval ctx "(+ 2 3)")) "+ 2 args")
|
||||
(assert (= 10 (ct-eval ctx "(+ 1 2 3 4)")) "+ varargs")
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
|
||||
# --- Comparison ---
|
||||
(print "4: comparison...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= 1 1)")) "= same")
|
||||
(assert (= true (ct-eval ctx "(= 1 1 1)")) "= multi same")
|
||||
(assert (= false (ct-eval ctx "(= 1 2)")) "= different")
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
|
||||
# --- Boolean logic ---
|
||||
(print "5: boolean logic...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(and)")) "and empty")
|
||||
(assert (= true (ct-eval ctx "(and true)")) "and true")
|
||||
(assert (= nil (ct-eval ctx "(and nil)")) "and nil")
|
||||
|
|
@ -139,7 +139,7 @@
|
|||
|
||||
# --- Collections ---
|
||||
(print "6: collections...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(empty? nil)")) "empty? nil")
|
||||
(assert (= true (ct-eval ctx "(empty? [])")) "empty? []")
|
||||
(assert (= true (ct-eval ctx "(empty? ())")) "empty? ()")
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
|
||||
# --- Sequence Operations ---
|
||||
(print "7: sequence operations...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 1 (ct-eval ctx "(first [1 2 3])")) "first")
|
||||
(assert (= nil (ct-eval ctx "(first [])")) "first empty")
|
||||
(assert (= nil (ct-eval ctx "(first nil)")) "first nil")
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
|
||||
# --- Collections: conj, assoc, dissoc, get ---
|
||||
(print "8: collection mutation...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [1 2 3] (conj [1 2] 3))")) "conj vector")
|
||||
(assert (= true (ct-eval ctx "(= (quote (0 1 2)) (conj (quote (1 2)) 0))")) "conj list prepend")
|
||||
(assert (= true (ct-eval ctx "(= {:a 1 :b 2} (assoc {:a 1} :b 2))")) "assoc")
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
|
||||
# --- Higher-order functions ---
|
||||
(print "9: higher-order functions...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 3 (ct-eval ctx "((comp inc inc) 1)")) "comp")
|
||||
(assert (= 42 (ct-eval ctx "(identity 42)")) "identity")
|
||||
(assert (= 99 (ct-eval ctx "((constantly 99) :anything)")) "constantly")
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
|
||||
# --- Destructuring ---
|
||||
(print "10: destructuring...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 1 (ct-eval ctx "(let [[x] [1 2 3]] x)")) "seq destructure first")
|
||||
(assert (= 2 (ct-eval ctx "(let [[_ y] [1 2 3]] y)")) "seq destructure second"))
|
||||
(print " ok")
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
(defn fresh-ctx
|
||||
"A fresh, fully-isolated interpret-mode context (cheap fork of one shared init)."
|
||||
[]
|
||||
(when (nil? base-snap) (set base-snap (snapshot (init))))
|
||||
(when (nil? base-snap) (set base-snap (snapshot (init-cached))))
|
||||
(fork base-snap))
|
||||
|
||||
(defn jeval
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
(def actual (get (dyn :args) 2))
|
||||
|
||||
(when (and expected actual)
|
||||
(def ctx (init {}))
|
||||
(def ctx (init-cached {}))
|
||||
(def prog (string "(= " expected " " actual ")"))
|
||||
(def [ok val] (protect (eval-string ctx prog)))
|
||||
(if ok
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
(use ../../src/jolt/reader)
|
||||
(import ../../src/jolt/backend :as backend)
|
||||
|
||||
(def ctx (init))
|
||||
(def ctx (init-cached))
|
||||
|
||||
# 1. A deliberate punt (letfn needs letrec IR) falls back and evaluates correctly.
|
||||
(assert (= 3 (backend/compile-and-eval ctx (parse-string "(letfn [(f [n] (+ n 1))] (f 2))")))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
(print "Eval Tests")
|
||||
|
||||
(print "1: eval literal...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 42 (ct-eval ctx "(eval 42)")) "eval literal")
|
||||
(assert (= 3 (ct-eval ctx "(eval '(+ 1 2))")) "eval quoted form")
|
||||
(assert (= 3 (ct-eval ctx "(eval (eval '(+ 1 2)))")) "eval nested")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
# Helper: parse and eval
|
||||
# init, not bare make-ctx: with the implicit Janet root-env fallback removed
|
||||
# (Stage 3), a context without clojure.core interned can't resolve inc/+/etc.
|
||||
(def- shared-ctx (init))
|
||||
(def- shared-ctx (init-cached))
|
||||
(defn eval-str [s]
|
||||
(eval-form shared-ctx @{} (parse-string s)))
|
||||
|
||||
|
|
@ -103,13 +103,13 @@
|
|||
(print "13: locking...")
|
||||
# locking/instance? are overlay macros now (Stage 2 tier 6c) — they need the
|
||||
# full env (init loads the overlay), not a bare make-ctx.
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 42 (eval-string ctx "(locking :lock 42)")) "locking returns body result"))
|
||||
(print " passed")
|
||||
|
||||
(print "14: instance?...")
|
||||
# instance? checks type
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (eval-string ctx "(instance? Number 42)")) "instance? Number matches number")
|
||||
(assert (= false (eval-string ctx "(instance? Number \"hello\")")) "instance? Number doesn't match string"))
|
||||
(print " passed")
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
(print "15: defmulti/defmethod...")
|
||||
# defmulti/defmethod are overlay macros now (Stage 2 jolt-eaa), so this needs the
|
||||
# full env (init loads the overlay + installs the *-setup fns), not a bare make-ctx.
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(eval-form ctx @{} (parse-string "(defmulti my-dispatch (fn* [x] (x :type)))"))
|
||||
(eval-form ctx @{} (parse-string "(defmethod my-dispatch :foo [_] :got-foo)"))
|
||||
(eval-form ctx @{} (parse-string "(defmethod my-dispatch :bar [_] :got-bar)"))
|
||||
|
|
@ -126,8 +126,8 @@
|
|||
(print " passed")
|
||||
|
||||
(print "16: deftype...")
|
||||
# deftype is an overlay macro now (Stage 2 jolt-eaa) — needs the full env (init).
|
||||
(let [ctx (init)
|
||||
# deftype is an overlay macro now (Stage 2 jolt-eaa) — needs the full env (init-cached).
|
||||
(let [ctx (init-cached)
|
||||
_ (eval-form ctx @{} (parse-string "(deftype Point [x y])"))
|
||||
_ (eval-form ctx @{} (parse-string "(def p (Point. 10 20))"))
|
||||
p-val (eval-form ctx @{} (parse-string "p"))
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
(print "17: defmacro...")
|
||||
# define a macro using defmacro special form
|
||||
# init loads clojure.core so `list` is available
|
||||
(let [ctx (init)
|
||||
(let [ctx (init-cached)
|
||||
_ (eval-form ctx @{} (parse-string "(defmacro my-when [test body] (list 'if test body nil))"))
|
||||
result (eval-form ctx @{} (parse-string "(my-when true 2)"))]
|
||||
(assert (= 2 result) "defmacro defines callable macro"))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
(print "Janet Interop Tests")
|
||||
|
||||
(print "1: field access on tables...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(def t {:a 1 :b 2})")
|
||||
(assert (= 1 (ct-eval ctx "(. t :a)")) ". field access table")
|
||||
(assert (= 2 (ct-eval ctx "(. t :b)")) ". field access table 2")
|
||||
|
|
@ -11,33 +11,33 @@
|
|||
(print " ok")
|
||||
|
||||
(print "2: field access on structs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(def s {:x 10 :y 20})")
|
||||
(assert (= 10 (ct-eval ctx "(. s :x)")) ". field access struct")
|
||||
(assert (= 20 (ct-eval ctx "(. s :y)")) ". field access struct 2"))
|
||||
(print " ok")
|
||||
|
||||
(print "3: method calls on tables...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(def obj {:greet (fn [self name] (str \"Hello \" name))})")
|
||||
(assert (= "Hello Alice" (ct-eval ctx "(. obj greet \"Alice\")")) ". method call on table"))
|
||||
(print " ok")
|
||||
|
||||
(print "4: field access via .- reader sugar...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(def t {:x 42 :len 5})")
|
||||
(assert (= 42 (ct-eval ctx "(.-x t)")) ".-x reader sugar")
|
||||
(assert (= 5 (ct-eval ctx "(.-len t)")) ".-len reader sugar"))
|
||||
(print " ok")
|
||||
|
||||
(print "5: . field access still works on deftypes...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(deftype Point [x y])")
|
||||
(assert (= 3 (ct-eval ctx "(. (Point. 3 4) x)")) ". field access deftype"))
|
||||
(print " ok")
|
||||
|
||||
(print "6: method call with multiple args...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(ct-eval ctx "(def calc {:add (fn [_ a b] (+ a b)) :mul (fn [_ a b] (* a b))})")
|
||||
(assert (= 7 (ct-eval ctx "(. calc add 3 4)")) ". method add")
|
||||
(assert (= 12 (ct-eval ctx "(. calc mul 3 4)")) ". method mul"))
|
||||
|
|
|
|||
|
|
@ -3,47 +3,47 @@
|
|||
(print "LazySeq Tests")
|
||||
|
||||
(print "1: lazy-seq from list...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [1 2 3] (take 10 (lazy-seq [1 2 3])))")) "lazy-seq list")
|
||||
(assert (= true (ct-eval ctx "(= 3 (count (lazy-seq [1 2 3])))")) "count lazy-seq"))
|
||||
(print " ok")
|
||||
|
||||
(print "2: lazy-cat concatenation...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [1 2 3 4] (take 10 (lazy-cat [1 2] [3 4])))")) "lazy-cat concat")
|
||||
(assert (= true (ct-eval ctx "(= 4 (count (lazy-cat [1 2] [3 4])))")) "lazy-cat count"))
|
||||
(print " ok")
|
||||
|
||||
(print "3: first/rest on lazy-seqs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= 1 (first (lazy-seq [1 2 3])))")) "first lazy")
|
||||
(assert (= true (ct-eval ctx "(= 2 (first (rest (lazy-seq [1 2 3]))))")) "first rest lazy"))
|
||||
(print " ok")
|
||||
|
||||
(print "4: drop/nth on lazy-seqs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [3 4 5] (take 10 (drop 2 (lazy-seq [1 2 3 4 5]))))")) "drop 2 take 10")
|
||||
(assert (= true (ct-eval ctx "(= 3 (nth (lazy-seq [1 2 3 4 5]) 2))")) "nth lazy"))
|
||||
(print " ok")
|
||||
|
||||
(print "5: concat on lazy-seqs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= 5 (count (concat (lazy-seq [1 2]) (lazy-seq [3 4 5]))))")) "concat lazy"))
|
||||
(print " ok")
|
||||
|
||||
(print "6: reverse/sort on lazy-seqs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [3 2 1] (reverse (lazy-seq [1 2 3])))")) "reverse lazy")
|
||||
(assert (= true (ct-eval ctx "(= [1 2 3] (sort (lazy-seq [3 1 2])))")) "sort lazy"))
|
||||
(print " ok")
|
||||
|
||||
(print "7: distinct on lazy-seqs...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= [1 2 3] (distinct (lazy-seq [1 2 1 3 2])))")) "distinct lazy"))
|
||||
(print " ok")
|
||||
|
||||
(print "8: fib-seq (deferred — eager core-map needs lazy upgrade)...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(print " The cell-by-cell lazy-seq architecture is correct. concat, take,")
|
||||
(print " drop, nth, first, rest all work with self-referencing patterns.")
|
||||
(print " core-map still eagerly realizes all lazy-seq arguments, which")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
# 1. Basic hash-map construction and access
|
||||
# ============================================================
|
||||
(print "1: hash-map construction...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(def m1 (ct-eval ctx "(hash-map :a 1)"))
|
||||
(assert (not (nil? m1)) "hash-map returns non-nil")
|
||||
(assert (= true (ct-eval ctx "(map? (hash-map :a 1))")) "map? returns true for PHM")
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
# 2. assoc and dissoc
|
||||
# ============================================================
|
||||
(print "2: assoc/dissoc...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(= (assoc (hash-map :a 1) :b 2) (hash-map :a 1 :b 2))")) "assoc add")
|
||||
(assert (= true (ct-eval ctx "(= (assoc (hash-map :a 1) :a 99) (hash-map :a 99))")) "assoc replace")
|
||||
(assert (= true (ct-eval ctx "(= (dissoc (hash-map :a 1 :b 2) :a) (hash-map :b 2))")) "dissoc")
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
# 3. keys, vals, merge
|
||||
# ============================================================
|
||||
(print "3: keys/vals/merge...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= 2 (ct-eval ctx "(count (keys (hash-map :a 1 :b 2)))")) "keys count")
|
||||
(assert (= 2 (ct-eval ctx "(count (vals (hash-map :a 1 :b 2)))")) "vals count")
|
||||
(assert (= true (ct-eval ctx "(= (merge (hash-map :a 1) (hash-map :b 2)) (hash-map :a 1 :b 2))")) "merge"))
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
# 4. Empty and seq
|
||||
# ============================================================
|
||||
(print "4: empty? and seq...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(assert (= true (ct-eval ctx "(empty? (hash-map))")) "empty? true")
|
||||
(assert (= false (ct-eval ctx "(empty? (hash-map :a 1))")) "empty? false")
|
||||
(assert (= 1 (ct-eval ctx "(count (seq (hash-map :a 1)))")) "seq count"))
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
# 5. Larger maps
|
||||
# ============================================================
|
||||
(print "5: larger maps...")
|
||||
(let [ctx (init)]
|
||||
(let [ctx (init-cached)]
|
||||
(eval-string ctx "
|
||||
(def big-map
|
||||
(reduce (fn [m i] (assoc m (keyword (str \"k\" i)) i))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue