Const-link stable vars under whole-program; direct-link cfunction roots

direct-var? now treats a cfunction root the same as a function root, so a
call/ref to a native fn (clojure.math/sqrt et al.) embeds the value instead of
a per-call cell deref. This was the hot indirection in the ray tracer — sqrt
runs every bounce — and it applies in every direct-link build, not just
whole-program.

const-link? is new and whole-program-only: in a closed world every non-dynamic
var has a stable root, so embed it as a constant (quoted unless it's already
callable) rather than reading the cell each reference. Covers what direct-var?
can't — ^:redef vars (reloading is off under the flag), data defs, and record
type/ctor roots. Dynamic vars stay indirect; a nil (not-yet-defined) root stays
indirect and the whole-program re-emit picks it up once the root is in place.

Measured on the records ray tracer: hot-path indirect refs (sqrt + data vars)
gone; the only indirect refs left are cold defrecord self-references. whole-
program-test now also checks a ^:redef fn and a data def so the per-ns vs
whole-program comparison guards const-link soundness.
This commit is contained in:
Yogthos 2026-06-14 11:03:28 -04:00
parent 840f699f54
commit 4018eb87ed
2 changed files with 32 additions and 3 deletions

View file

@ -123,7 +123,24 @@
(and (get (ctx :env) :direct-linking?)
(not (cell :dynamic))
(not (let [m (cell :meta)] (and m (get m :redef))))
(function? (cell :root))))
(let [r (cell :root)] (or (function? r) (cfunction? r)))))
# Whole-program constant-linking (closed world): under JOLT_WHOLE_PROGRAM every
# non-dynamic var has a stable root we can embed as a CONSTANT, eliminating the
# per-reference cell deref the indirect path pays. This covers what direct-var?
# can't: ^:redef vars (no reloading under the flag, so redef is moot), data vars
# (def of a number/vector/etc.), and record-type / non-fn callable roots. The
# value is quoted at the emit site unless it's callable (a function/cfunction is
# valid in head AND value position as-is). Dynamic vars stay indirect — thread
# binding is a runtime mechanism, not redefinition. A nil root (a not-yet-run
# forward def) stays indirect so the live deref picks the value up; the
# whole-program re-emit (infer-program!, callee-first after full load) then
# const-links it once its root is in place.
(defn- const-link? [ctx cell]
(and (get (ctx :env) :whole-program?)
(get (ctx :env) :direct-linking?)
(not (cell :dynamic))
(not (nil? (cell :root)))))
# Fresh Janet symbol for back-end-introduced bindings (arity dispatch). NOT
# Janet's `gensym` — `(use ./core)` shadows it with Jolt's, which returns a jolt
@ -579,6 +596,13 @@
:var (let [cell (cell-for ctx (node :ns) (node :name))]
(if (direct-var? ctx cell)
(cell :root) # direct link: embed the fn value
(if (const-link? ctx cell)
# whole-program closed world: embed the stable root as a constant.
# Callable roots go in bare (valid in head/value position); any
# other value is quoted so Janet returns it rather than evaluating
# it as code (a bare tuple/struct in code position would be called).
(let [r (cell :root)]
(if (or (function? r) (cfunction? r)) r (tuple 'quote r)))
# Indirect: live deref, with the var-get FN CALL inlined away
# (jolt-8sq): a non-dynamic var's value is always its root, so
# the common case is two native table ops + a branch instead of
@ -597,7 +621,7 @@
(let [qcell (tuple 'quote cell)]
['if ['in qcell :dynamic]
(tuple var-get qcell)
['in qcell :root]])))
['in qcell :root]]))))
# (var x): the var object itself (not its value) — the embedded cell, by
# reference. binding keys its thread-binding frame on this exact cell.
:the-var (tuple 'quote (cell-for ctx (node :ns) (node :name)))

View file

@ -13,10 +13,15 @@
(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)) (dot a b (dec n)))))\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"