compiler: direct-linking (call-site/unit, Clojure model) + :aot-core? flag
Calls compile direct (target value embedded) iff the compiling unit has direct-linking on, the target isn't ^:redef/^:dynamic, and it's an already-defined fn; otherwise indirect (live var deref, redefinable). Direct emission is guarded to function values only — embedding a non-function would make Janet evaluate it as code. :aot-core? (init opt, default true; JOLT_AOT_CORE=0 disables) compiles the core tiers with direct-linking on so core->core calls are direct. User/REPL code compiles indirect by default, so redefining functions in the REPL works with no annotation, exactly like Clojure. :aot-core? false makes core indirect too — the whole language becomes redefinable. ^:redef / ^:dynamic on a target force it indirect even inside a direct-linked unit. Also fixes a pre-existing gap this surfaced: compiled def dropped ALL var metadata (so ^:dynamic was silently lost under compilation, and ^:redef couldn't work). def metadata now flows analyzer -> IR -> back end (new form-sym-meta host fn, def-node :meta, var-setter-meta) and is applied to the cell. Matrix test in test/integration/direct-linking-test.janet. Conformance 218/218 in all three modes under both :aot-core? true and false; battery holds 3916; binary rebuilt.
This commit is contained in:
parent
a2805c4f51
commit
a3cc035782
7 changed files with 127 additions and 6 deletions
63
test/integration/direct-linking-test.janet
Normal file
63
test/integration/direct-linking-test.janet
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Direct-linking / redefinition matrix (jolt-d9j, jolt-g86).
|
||||
#
|
||||
# Direct-linking is a per-compilation-UNIT property (Clojure model). A call
|
||||
# compiles direct iff the unit has direct-linking on AND the target is not
|
||||
# ^:redef/^:dynamic AND the target is an already-defined fn. Otherwise indirect
|
||||
# (live var deref → redefinable). This pins the user-visible semantics:
|
||||
# - default user/REPL unit (direct-linking off): redefine anything, callers see it
|
||||
# - direct-linked unit: callers don't see a later redef (unless target ^:redef)
|
||||
# - :aot-core? gates whether the core tiers compile direct-linked
|
||||
|
||||
(use ../../src/jolt/api)
|
||||
|
||||
(var failures 0)
|
||||
(defn- check [label got want]
|
||||
(unless (= got want)
|
||||
(++ failures)
|
||||
(printf "FAIL [%s] got %q want %q" label got want)))
|
||||
|
||||
# 1. Default unit (direct-linking OFF): redefinition reaches compiled callers.
|
||||
(let [ctx (init {:compile? true})]
|
||||
(eval-string ctx "(defn add [a b] (+ a b))")
|
||||
(eval-string ctx "(defn caller [] (add 1 2))")
|
||||
(check "default before redef" (eval-string ctx "(caller)") 3)
|
||||
(eval-string ctx "(defn add [a b] (* a b))")
|
||||
(check "default sees redef (indirect)" (eval-string ctx "(caller)") 2))
|
||||
|
||||
# 2. Direct-linked unit: compiled caller keeps the original target after a redef.
|
||||
(let [ctx (init {:compile? true :direct-linking? true})]
|
||||
(eval-string ctx "(defn add [a b] (+ a b))")
|
||||
(eval-string ctx "(defn caller [] (add 1 2))")
|
||||
(check "direct before redef" (eval-string ctx "(caller)") 3)
|
||||
(eval-string ctx "(defn add [a b] (* a b))")
|
||||
(check "direct ignores redef (sealed)" (eval-string ctx "(caller)") 3)
|
||||
# the var itself is still redefined; only the direct-linked call is frozen
|
||||
(check "direct var still updated" (eval-string ctx "(add 3 4)") 12))
|
||||
|
||||
# 3. ^:redef opts a var OUT of direct-linking even in a direct-linked unit.
|
||||
(let [ctx (init {:compile? true :direct-linking? true})]
|
||||
(eval-string ctx "(defn ^:redef add [a b] (+ a b))")
|
||||
(eval-string ctx "(defn caller [] (add 1 2))")
|
||||
(check "redef-tagged before" (eval-string ctx "(caller)") 3)
|
||||
(eval-string ctx "(defn ^:redef add [a b] (* a b))")
|
||||
(check "redef-tagged sees redef" (eval-string ctx "(caller)") 2))
|
||||
|
||||
# 4. :aot-core? true (default): redefining a core fn (in clojure.core) is still
|
||||
# seen by USER code, because user calls are indirect regardless of core being
|
||||
# direct-linked internally.
|
||||
(let [ctx (init {:compile? true})]
|
||||
(eval-string ctx "(defn uses-last [] (last [1 2 3]))")
|
||||
(check "core call before" (eval-string ctx "(uses-last)") 3)
|
||||
(eval-string ctx "(in-ns (quote clojure.core))")
|
||||
(eval-string ctx "(def last (fn [coll] :patched))")
|
||||
(eval-string ctx "(in-ns (quote user))")
|
||||
(check "user sees core redef (indirect)" (eval-string ctx "(uses-last)") :patched))
|
||||
|
||||
# 5. :aot-core? false: core compiles indirect too, so even core-internal callers
|
||||
# see a redef — the whole language is redefinable.
|
||||
(let [ctx (init {:compile? true :aot-core? false})]
|
||||
(check "aot-core off still correct" (eval-string ctx "(last [1 2 3])") 3))
|
||||
|
||||
(if (pos? failures)
|
||||
(do (printf "direct-linking: %d failure(s)" failures) (os/exit 1))
|
||||
(print "direct-linking: all matrix cases passed"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue