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)
|
||||
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
|
||||
"Evaluate a Clojure source string in a Jolt context.
|
||||
When :compile? is enabled, compiles to Janet and evaluates.
|
||||
|
|
@ -50,31 +77,7 @@
|
|||
Context-modifying forms (ns, defmacro, deftype, require, in-ns, defmulti, defmethod)
|
||||
always use the interpreter."
|
||||
[ctx s]
|
||||
(let [compile? (get (ctx :env) :compile?)
|
||||
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))))
|
||||
(eval-one ctx (parse-string s)))
|
||||
|
||||
(defn eval-string*
|
||||
"Evaluate a Clojure source string with explicit bindings."
|
||||
|
|
@ -93,7 +96,7 @@
|
|||
(def [form rest] (parse-next cur))
|
||||
(set cur rest)
|
||||
(when (not (nil? form))
|
||||
(set result (eval-form ctx @{} form))))
|
||||
(set result (eval-one ctx form))))
|
||||
result)
|
||||
|
||||
(defn compile-string
|
||||
|
|
|
|||
|
|
@ -8,6 +8,24 @@
|
|||
(use ./core)
|
||||
(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
|
||||
@{"+" "core-+"
|
||||
"-" "core-sub"
|
||||
|
|
@ -854,18 +872,30 @@
|
|||
(emit-expr (analyze-form form @{} ctx)))
|
||||
|
||||
(defn compile-and-eval
|
||||
"Compile a Clojure form and evaluate it as Janet.
|
||||
For def/defn forms, interns the result in the Jolt namespace
|
||||
so the interpreter can resolve it later."
|
||||
"Compile a Clojure form and evaluate it as Janet, in the context's persistent
|
||||
Janet env (so compiled def/defn bindings resolve across forms). For def/defn
|
||||
forms, also interns the result in the Jolt namespace so the interpreter can
|
||||
resolve it later."
|
||||
[form ctx]
|
||||
(def result (eval (compile-ast form ctx)))
|
||||
# Intern def/defn results in Jolt namespace for interpreter resolution
|
||||
(when (and ctx (array? form) (> (length form) 0)
|
||||
(struct? (first form)) (= :symbol ((first form) :jolt/type)))
|
||||
(def head-name ((first form) :name))
|
||||
(when (or (= head-name "def") (= head-name "defn") (= head-name "defn-"))
|
||||
(def name-sym (in form 1))
|
||||
(def name (if (struct? name-sym) (name-sym :name) name-sym))
|
||||
(def ns (ctx-find-ns ctx (ctx-current-ns ctx)))
|
||||
(ns-intern ns name result)))
|
||||
(def env (ctx-janet-env ctx))
|
||||
(def def-form? (and ctx (array? form) (> (length form) 0)
|
||||
(struct? (first form)) (= :symbol ((first form) :jolt/type))
|
||||
(let [h ((first form) :name)] (or (= h "def") (= h "defn") (= h "defn-")))))
|
||||
(def def-name (when def-form?
|
||||
(let [name-sym (in form 1)] (if (struct? name-sym) (name-sym :name) name-sym))))
|
||||
(var compiled (compile-ast form ctx))
|
||||
# Name the fn after the def so a recursive body self-references lexically
|
||||
# ((def f (fn [..] (f ..))) -> (def f (fn f [..] (f ..)))); the anonymous form
|
||||
# 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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue