feat(compile): Phase 1 — wire shared persistent env (compile mode now works across forms)
Compile mode was broken: compiled defns interned only into the jolt namespace, which Janet's eval couldn't see, so calling a compiled function threw 'unknown symbol'. And load-string ignored :compile? entirely. - Each context gets a persistent Janet env (ctx-janet-env), a child of the compiler module env (so core-* resolve) holding the context's user defs. compile-and-eval evals into it, so def/defn persist and resolve across forms; contexts stay isolated. nil ctx (one-off eval) gets a fresh child. - Emit a named fn for defn ((def f (fn f [..] (f ..)))) so recursion resolves lexically (the anonymous form couldn't forward-reference f at compile time). - Extract eval-one (per-form routing) and use it in both eval-string and load-string, so load-string honors :compile?. Stateful forms still interpret. compiled fib(30): ~50s (interpreted) -> 3.4s (~15x). spec: compile-mode-test gains cross-form/recursion/def + context-isolation cases. jpm test green. Fast operator emission (the rest of the win) is Phase 2.
This commit is contained in:
parent
b6e66e53d2
commit
f74af5dbc5
3 changed files with 90 additions and 40 deletions
|
|
@ -43,6 +43,33 @@
|
||||||
(install-async! ctx)
|
(install-async! ctx)
|
||||||
ctx))
|
ctx))
|
||||||
|
|
||||||
|
# Stateful / context-modifying forms always use the interpreter (they mutate
|
||||||
|
# the context: namespaces, macros, types, multimethods, dynamic vars, …).
|
||||||
|
(defn- stateful-head? [head-name]
|
||||||
|
(or (= head-name "defmacro") (= head-name "ns")
|
||||||
|
(= head-name "deftype") (= head-name "defmulti") (= head-name "defmethod")
|
||||||
|
(= head-name "require") (= head-name "in-ns")
|
||||||
|
(= head-name "syntax-quote") (= head-name "set!")
|
||||||
|
(= head-name "var") (= head-name ".") (= head-name "new")
|
||||||
|
(= head-name "eval")))
|
||||||
|
|
||||||
|
(defn eval-one
|
||||||
|
"Evaluate a single already-parsed form, routing to the compiler when the
|
||||||
|
context has :compile? enabled (stateful forms always interpret)."
|
||||||
|
[ctx form]
|
||||||
|
(if (get (ctx :env) :compile?)
|
||||||
|
(if (array? form)
|
||||||
|
(let [first-form (first form)
|
||||||
|
head-name (if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
|
||||||
|
(first-form :name) nil)]
|
||||||
|
(if (stateful-head? head-name)
|
||||||
|
(eval-form ctx @{} form)
|
||||||
|
(compile-and-eval form ctx)))
|
||||||
|
(if (or (and (struct? form) (= :symbol (form :jolt/type))) (tuple? form))
|
||||||
|
(compile-and-eval form ctx)
|
||||||
|
(eval-form ctx @{} form)))
|
||||||
|
(eval-form ctx @{} form)))
|
||||||
|
|
||||||
(defn eval-string
|
(defn eval-string
|
||||||
"Evaluate a Clojure source string in a Jolt context.
|
"Evaluate a Clojure source string in a Jolt context.
|
||||||
When :compile? is enabled, compiles to Janet and evaluates.
|
When :compile? is enabled, compiles to Janet and evaluates.
|
||||||
|
|
@ -50,31 +77,7 @@
|
||||||
Context-modifying forms (ns, defmacro, deftype, require, in-ns, defmulti, defmethod)
|
Context-modifying forms (ns, defmacro, deftype, require, in-ns, defmulti, defmethod)
|
||||||
always use the interpreter."
|
always use the interpreter."
|
||||||
[ctx s]
|
[ctx s]
|
||||||
(let [compile? (get (ctx :env) :compile?)
|
(eval-one ctx (parse-string s)))
|
||||||
form (parse-string s)]
|
|
||||||
(if compile?
|
|
||||||
(if (array? form)
|
|
||||||
# Lists: check for stateful forms
|
|
||||||
(let [first-form (first form)
|
|
||||||
head-name (if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
|
|
||||||
(first-form :name)
|
|
||||||
nil)
|
|
||||||
stateful? (or (= head-name "defmacro") (= head-name "ns")
|
|
||||||
(= head-name "deftype") (= head-name "defmulti") (= head-name "defmethod")
|
|
||||||
(= head-name "require") (= head-name "in-ns")
|
|
||||||
(= head-name "syntax-quote") (= head-name "set!")
|
|
||||||
(= head-name "var") (= head-name ".") (= head-name "new")
|
|
||||||
(= head-name "eval"))]
|
|
||||||
(if stateful?
|
|
||||||
(eval-form ctx @{} form)
|
|
||||||
(compile-and-eval form ctx)))
|
|
||||||
# Bare symbols and other non-literal forms: also compile
|
|
||||||
(if (or (and (struct? form) (= :symbol (form :jolt/type)))
|
|
||||||
(tuple? form))
|
|
||||||
(compile-and-eval form ctx)
|
|
||||||
(eval-form ctx @{} form)))
|
|
||||||
# No compile flag: always interpret
|
|
||||||
(eval-form ctx @{} form))))
|
|
||||||
|
|
||||||
(defn eval-string*
|
(defn eval-string*
|
||||||
"Evaluate a Clojure source string with explicit bindings."
|
"Evaluate a Clojure source string with explicit bindings."
|
||||||
|
|
@ -93,7 +96,7 @@
|
||||||
(def [form rest] (parse-next cur))
|
(def [form rest] (parse-next cur))
|
||||||
(set cur rest)
|
(set cur rest)
|
||||||
(when (not (nil? form))
|
(when (not (nil? form))
|
||||||
(set result (eval-form ctx @{} form))))
|
(set result (eval-one ctx form))))
|
||||||
result)
|
result)
|
||||||
|
|
||||||
(defn compile-string
|
(defn compile-string
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,24 @@
|
||||||
(use ./core)
|
(use ./core)
|
||||||
(use ./phm)
|
(use ./phm)
|
||||||
|
|
||||||
|
# The compiler emits Janet that references the core fns (core-+, core-<, …),
|
||||||
|
# which are bare-bound in this module's environment via (use ./core). Capture it
|
||||||
|
# so each Jolt context can get a child env where those resolve and where compiled
|
||||||
|
# `def`/`defn` bindings persist across forms (isolated per context).
|
||||||
|
(def jolt-runtime-env (curenv))
|
||||||
|
|
||||||
|
(defn ctx-janet-env
|
||||||
|
"Lazily create/cache a per-context Janet environment for compiled code: a child
|
||||||
|
of the runtime env (so core fns resolve) that holds this context's user defs.
|
||||||
|
For a nil context (one-off compile/eval) returns a fresh child env."
|
||||||
|
[ctx]
|
||||||
|
(if (and ctx (table? (get ctx :env)))
|
||||||
|
(or (get (ctx :env) :janet-rt)
|
||||||
|
(let [e (make-env jolt-runtime-env)]
|
||||||
|
(put (ctx :env) :janet-rt e)
|
||||||
|
e))
|
||||||
|
(make-env jolt-runtime-env)))
|
||||||
|
|
||||||
(def- core-renames
|
(def- core-renames
|
||||||
@{"+" "core-+"
|
@{"+" "core-+"
|
||||||
"-" "core-sub"
|
"-" "core-sub"
|
||||||
|
|
@ -854,18 +872,30 @@
|
||||||
(emit-expr (analyze-form form @{} ctx)))
|
(emit-expr (analyze-form form @{} ctx)))
|
||||||
|
|
||||||
(defn compile-and-eval
|
(defn compile-and-eval
|
||||||
"Compile a Clojure form and evaluate it as Janet.
|
"Compile a Clojure form and evaluate it as Janet, in the context's persistent
|
||||||
For def/defn forms, interns the result in the Jolt namespace
|
Janet env (so compiled def/defn bindings resolve across forms). For def/defn
|
||||||
so the interpreter can resolve it later."
|
forms, also interns the result in the Jolt namespace so the interpreter can
|
||||||
|
resolve it later."
|
||||||
[form ctx]
|
[form ctx]
|
||||||
(def result (eval (compile-ast form ctx)))
|
(def env (ctx-janet-env ctx))
|
||||||
# Intern def/defn results in Jolt namespace for interpreter resolution
|
(def def-form? (and ctx (array? form) (> (length form) 0)
|
||||||
(when (and ctx (array? form) (> (length form) 0)
|
(struct? (first form)) (= :symbol ((first form) :jolt/type))
|
||||||
(struct? (first form)) (= :symbol ((first form) :jolt/type)))
|
(let [h ((first form) :name)] (or (= h "def") (= h "defn") (= h "defn-")))))
|
||||||
(def head-name ((first form) :name))
|
(def def-name (when def-form?
|
||||||
(when (or (= head-name "def") (= head-name "defn") (= head-name "defn-"))
|
(let [name-sym (in form 1)] (if (struct? name-sym) (name-sym :name) name-sym))))
|
||||||
(def name-sym (in form 1))
|
(var compiled (compile-ast form ctx))
|
||||||
(def name (if (struct? name-sym) (name-sym :name) name-sym))
|
# Name the fn after the def so a recursive body self-references lexically
|
||||||
(def ns (ctx-find-ns ctx (ctx-current-ns ctx)))
|
# ((def f (fn [..] (f ..))) -> (def f (fn f [..] (f ..)))); the anonymous form
|
||||||
(ns-intern ns name result)))
|
# can't resolve f at compile time.
|
||||||
|
(when (and def-name (indexed? compiled) (= 3 (length compiled))
|
||||||
|
(= 'def (in compiled 0))
|
||||||
|
(indexed? (in compiled 2)) (= 'fn (in (in compiled 2) 0))
|
||||||
|
(indexed? (in (in compiled 2) 1))) # 3rd elem is (fn [params] ...)
|
||||||
|
(let [f (in compiled 2)]
|
||||||
|
(set compiled [(in compiled 0) (in compiled 1)
|
||||||
|
[(in f 0) (symbol def-name) ;(tuple/slice f 1)]])))
|
||||||
|
(def result (eval compiled env))
|
||||||
|
# Also intern def/defn results in the Jolt namespace for interpreter resolution.
|
||||||
|
(when def-name
|
||||||
|
(ns-intern (ctx-find-ns ctx (ctx-current-ns ctx)) def-name result))
|
||||||
result)
|
result)
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,23 @@
|
||||||
|
|
||||||
(print " complex...")
|
(print " complex...")
|
||||||
(assert (= 6 (ct-eval ctx "(let [f (fn [n] (loop [i 0 acc 0] (if (< i n) (recur (inc i) (+ acc i)) acc)))] (f 4))")) "nested")
|
(assert (= 6 (ct-eval ctx "(let [f (fn [n] (loop [i 0 acc 0] (if (< i n) (recur (inc i) (+ acc i)) acc)))] (f 4))")) "nested")
|
||||||
(assert (= 15 (ct-eval ctx "(reduce + (map inc [0 1 2 3 4]))")) "reduce+map"))
|
(assert (= 15 (ct-eval ctx "(reduce + (map inc [0 1 2 3 4]))")) "reduce+map")
|
||||||
|
|
||||||
|
# Phase 1 wiring: compiled defns persist across forms (the per-context Janet
|
||||||
|
# env) and recurse correctly (named-fn self-reference).
|
||||||
|
(print " cross-form defns + recursion...")
|
||||||
|
(eval-string ctx "(defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))")
|
||||||
|
(assert (= 832040 (ct-eval ctx "(fib 30)")) "recursive fib across forms")
|
||||||
|
(eval-string ctx "(defn sq [x] (* x x))")
|
||||||
|
(eval-string ctx "(defn sum-sq [a b] (+ (sq a) (sq b)))")
|
||||||
|
(assert (= 25 (ct-eval ctx "(sum-sq 3 4)")) "defn calling earlier defn")
|
||||||
|
(eval-string ctx "(def base 100)")
|
||||||
|
(assert (= 142 (ct-eval ctx "(+ base 42)")) "compiled def referenced later"))
|
||||||
|
|
||||||
|
# Context isolation: a def in one compiled context is invisible in another.
|
||||||
|
(let [a (init {:compile? true}) b (init {:compile? true})]
|
||||||
|
(eval-string a "(def secret 7)")
|
||||||
|
(assert (= 7 (ct-eval a "secret")) "def visible in its own ctx")
|
||||||
|
(assert (not ((protect (ct-eval b "secret")) 0)) "def isolated to its ctx"))
|
||||||
|
|
||||||
(print "\nAll Phase 6 tests passed!")
|
(print "\nAll Phase 6 tests passed!")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue