Make direct-linking opt-in, not a release default
Release builds can legitimately want runtime dynamism (redefinition, eval,
load-string), so closed-world direct-linking shouldn't be forced on them. Gate it
behind an explicit --direct-link flag (or deps.edn :jolt/build {:direct-link
true}); off by default in every mode, including release and --opt.
build-binary takes an explicit direct-link? arg instead of deriving it from the
mode. build-smoke now covers the --direct-link path and asserts the cross-ns call
actually lowers to a jv$ binding; default release stays dynamically linked.
This commit is contained in:
parent
c908e996c3
commit
2c18fcdc61
4 changed files with 50 additions and 23 deletions
|
|
@ -96,11 +96,13 @@ a C compiler; `JOLT_CHEZ_CSV` overrides the auto-detected `csv<ver>/<machine>`
|
||||||
dir. `--opt` turns on the inference/flatten/scalar-replace passes; the default
|
dir. `--opt` turns on the inference/flatten/scalar-replace passes; the default
|
||||||
`release` mode is const-fold only.
|
`release` mode is const-fold only.
|
||||||
|
|
||||||
`release` and `--opt` are closed-world: a call between the app's own functions
|
`--direct-link` (or `:jolt/build {:direct-link true}`) opts into a closed world: a
|
||||||
binds to its target directly, skipping the var lookup and generic dispatch a
|
call between the app's own functions binds to its target directly, skipping the var
|
||||||
runtime call pays. That assumes app vars are final — mark one `^:redef` (or
|
lookup and generic dispatch a runtime call pays — at the cost of runtime
|
||||||
`^:dynamic`) to keep it redefinable and indirect. `--dev` keeps everything
|
redefinition of those vars and `eval`/`load-string`. It's off by default, so
|
||||||
indirect/open. Calls into `clojure.core` stay indirect in every mode.
|
ordinary builds (including `release` and `--opt`) stay dynamically linked. A var
|
||||||
|
marked `^:redef` or `^:dynamic` stays indirect even under `--direct-link`, and calls
|
||||||
|
into `clojure.core` stay indirect in every mode.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,19 @@ if [ "$got_opt" != "$want" ]; then
|
||||||
echo "--- got ----"; echo "$got_opt"
|
echo "--- got ----"; echo "$got_opt"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "build smoke: passed (release + optimized)"
|
|
||||||
|
# Closed-world direct-linking (opt-in): same result, and the cross-namespace call
|
||||||
|
# (app.core -> app.util/shout) must lower to a direct jv$ binding, not var-deref.
|
||||||
|
if ! JOLT_PWD="$app" bin/joltc build -m app.core -o "$out" --direct-link >/dev/null 2>&1; then
|
||||||
|
echo " FAIL: jolt build --direct-link exited non-zero"; exit 1
|
||||||
|
fi
|
||||||
|
got_dl="$(cd / && "$out" alpha bb ccc 2>&1)"
|
||||||
|
if [ "$got_dl" != "$want" ]; then
|
||||||
|
echo " FAIL: --direct-link binary output mismatch"
|
||||||
|
echo "--- got ----"; echo "$got_dl"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q '(jv\$app.util\$shout' "$out.build/flat.ss"; then
|
||||||
|
echo " FAIL: --direct-link did not emit a direct app->app call"; exit 1
|
||||||
|
fi
|
||||||
|
echo "build smoke: passed (release + optimized + direct-link)"
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,10 @@
|
||||||
;; natives: encoded :jolt/native libs to load at startup. embed-dirs: dirs whose
|
;; natives: encoded :jolt/native libs to load at startup. embed-dirs: dirs whose
|
||||||
;; files bake into the binary (single-file). ext-roots: project-relative io/resource
|
;; files bake into the binary (single-file). ext-roots: project-relative io/resource
|
||||||
;; roots resolved at runtime against JOLT_PWD (ship-alongside resources).
|
;; roots resolved at runtime against JOLT_PWD (ship-alongside resources).
|
||||||
(define (build-binary entry-ns out-path mode natives embed-dirs ext-roots)
|
;; direct-link?: opt-in closed-world direct-linking (app->app calls bind directly,
|
||||||
|
;; no runtime redefinition). Off by default in every mode — release stays
|
||||||
|
;; dynamically linked.
|
||||||
|
(define (build-binary entry-ns out-path mode natives embed-dirs ext-roots direct-link?)
|
||||||
(bld-check-toolchain)
|
(bld-check-toolchain)
|
||||||
;; 1. record app namespaces in dependency order as they finish loading.
|
;; 1. record app namespaces in dependency order as they finish loading.
|
||||||
(let ((app-order '()))
|
(let ((app-order '()))
|
||||||
|
|
@ -224,14 +227,15 @@
|
||||||
(error 'jolt-build (string-append "no source namespace loaded for " entry-ns
|
(error 'jolt-build (string-append "no source namespace loaded for " entry-ns
|
||||||
" — is it on the source roots?")))
|
" — is it on the source roots?")))
|
||||||
;; 2. emit each app namespace. `optimized` turns on the inference + flatten
|
;; 2. emit each app namespace. `optimized` turns on the inference + flatten
|
||||||
;; + scalar-replace passes (closed world); release/dev get const-fold only.
|
;; + scalar-replace passes; release/dev get const-fold only.
|
||||||
;; release + optimized are closed-world: turn on direct-linking so app->app
|
;; direct-link? (opt-in) commits to a closed world: app->app calls bind
|
||||||
;; calls bind directly (dev stays open/indirect). The defined-set accumulates
|
;; directly, giving up runtime redefinition of those vars. Off by default in
|
||||||
;; across the dependency-ordered namespaces, so a dep's defs are direct-linkable
|
;; every mode. The defined-set accumulates across the dependency-ordered
|
||||||
;; by the time the entry that calls them is emitted.
|
;; namespaces, so a dep's defs are direct-linkable by the time the entry that
|
||||||
|
;; calls them is emitted.
|
||||||
(set-optimize! (string=? mode "optimized"))
|
(set-optimize! (string=? mode "optimized"))
|
||||||
(let ((dl (not (string=? mode "dev"))))
|
(when direct-link?
|
||||||
((var-deref "jolt.backend-scheme" "set-direct-link!") dl)
|
((var-deref "jolt.backend-scheme" "set-direct-link!") #t)
|
||||||
((var-deref "jolt.backend-scheme" "direct-link-reset!")))
|
((var-deref "jolt.backend-scheme" "direct-link-reset!")))
|
||||||
(let* ((app-strs (apply append
|
(let* ((app-strs (apply append
|
||||||
(map (lambda (nf) (bld-emit-ns (car nf) (read-file-string (cdr nf))))
|
(map (lambda (nf) (bld-emit-ns (car nf) (read-file-string (cdr nf))))
|
||||||
|
|
@ -325,9 +329,9 @@
|
||||||
(display (string-append "jolt build: wrote " out-path "\n"))))))
|
(display (string-append "jolt build: wrote " out-path "\n"))))))
|
||||||
|
|
||||||
(def-var! "jolt.host" "build-binary"
|
(def-var! "jolt.host" "build-binary"
|
||||||
(lambda (entry out mode natives embed-dirs ext-roots)
|
(lambda (entry out mode natives embed-dirs ext-roots direct-link?)
|
||||||
(build-binary (jolt-str-render-one entry)
|
(build-binary (jolt-str-render-one entry)
|
||||||
(jolt-str-render-one out)
|
(jolt-str-render-one out)
|
||||||
(jolt-str-render-one mode)
|
(jolt-str-render-one mode)
|
||||||
natives embed-dirs ext-roots)
|
natives embed-dirs ext-roots (jolt-truthy? direct-link?))
|
||||||
jolt-nil))
|
jolt-nil))
|
||||||
|
|
|
||||||
|
|
@ -115,10 +115,13 @@
|
||||||
(map? task) (do (apply-project! resolved) (apply-main-opts (:main-opts task) more))
|
(map? task) (do (apply-project! resolved) (apply-main-opts (:main-opts task) more))
|
||||||
:else (throw (ex-info (str "bad task " name) {})))))
|
:else (throw (ex-info (str "bad task " name) {})))))
|
||||||
|
|
||||||
;; build [-m NS | FILE] [-o OUT] [--opt | --dev] — AOT-compile the app into a
|
;; build [-m NS | FILE] [-o OUT] [--opt | --dev] [--direct-link] — AOT-compile the
|
||||||
;; standalone executable. Resolves deps + roots like `run`, then hands the entry
|
;; app into a standalone executable. Resolves deps + roots like `run`, then hands the
|
||||||
;; namespace to the host build driver (jolt.host/build-binary, defined by
|
;; entry namespace to the host build driver (jolt.host/build-binary, defined by
|
||||||
;; build.ss). Default mode is release; --opt selects optimized, --dev unoptimized.
|
;; build.ss). Default mode is release; --opt selects optimized, --dev unoptimized.
|
||||||
|
;; --direct-link (or deps.edn :jolt/build {:direct-link true}) opts into closed-world
|
||||||
|
;; direct-linking: app->app calls bind directly, giving up runtime redefinition of
|
||||||
|
;; those vars and eval/load-string. Off by default — release stays dynamically linked.
|
||||||
;; Encode a deps.edn :jolt/native spec for the build launcher, resolving the
|
;; Encode a deps.edn :jolt/native spec for the build launcher, resolving the
|
||||||
;; current platform's candidate list now (the binary runs on this OS). Each entry
|
;; current platform's candidate list now (the binary runs on this OS). Each entry
|
||||||
;; becomes a vector the launcher (build.ss) reads: ["process"] for the running
|
;; becomes a vector the launcher (build.ss) reads: ["process"] for the running
|
||||||
|
|
@ -133,7 +136,7 @@
|
||||||
(into [(if (:optional spec) "opt" "req")] cands)))))))
|
(into [(if (:optional spec) "opt" "req")] cands)))))))
|
||||||
|
|
||||||
(defn- cmd-build [more]
|
(defn- cmd-build [more]
|
||||||
(let [{:keys [project-paths embed-dirs] :as resolved}
|
(let [{:keys [project-paths embed-dirs build] :as resolved}
|
||||||
(deps/resolve-project (project-dir))]
|
(deps/resolve-project (project-dir))]
|
||||||
(apply-project! resolved)
|
(apply-project! resolved)
|
||||||
(let [opts (loop [a more, entry nil, out nil]
|
(let [opts (loop [a more, entry nil, out nil]
|
||||||
|
|
@ -164,10 +167,13 @@
|
||||||
(nil? o) (str pdir "/target/" (if (= mode "dev") "debug" "release") "/" proj)
|
(nil? o) (str pdir "/target/" (if (= mode "dev") "debug" "release") "/" proj)
|
||||||
(str/starts-with? o "/") o
|
(str/starts-with? o "/") o
|
||||||
:else (str pdir "/" o)))
|
:else (str pdir "/" o)))
|
||||||
natives (encode-natives (:natives resolved))]
|
natives (encode-natives (:natives resolved))
|
||||||
|
;; closed-world direct-linking is opt-in: the --direct-link flag or a
|
||||||
|
;; deps.edn :jolt/build {:direct-link true}. Off otherwise.
|
||||||
|
direct-link? (boolean (or (some #{"--direct-link"} more) (:direct-link build)))]
|
||||||
;; embed-dirs (absolute) are walked + baked into the binary by the driver;
|
;; embed-dirs (absolute) are walked + baked into the binary by the driver;
|
||||||
;; project-paths (relative) become runtime io/resource roots (ship-alongside).
|
;; project-paths (relative) become runtime io/resource roots (ship-alongside).
|
||||||
(jolt.host/build-binary entry out mode natives embed-dirs project-paths)))))
|
(jolt.host/build-binary entry out mode natives embed-dirs project-paths direct-link?)))))
|
||||||
|
|
||||||
(defn- nrepl [more]
|
(defn- nrepl [more]
|
||||||
;; resolve the project (deps on the roots, native libs loaded), then start the
|
;; resolve the project (deps on the roots, native libs loaded), then start the
|
||||||
|
|
@ -185,7 +191,7 @@
|
||||||
(println "usage: jolt <command> [args]")
|
(println "usage: jolt <command> [args]")
|
||||||
(println " run -m NS [args] resolve deps.edn, load NS, call its -main")
|
(println " run -m NS [args] resolve deps.edn, load NS, call its -main")
|
||||||
(println " run FILE load a Clojure file")
|
(println " run FILE load a Clojure file")
|
||||||
(println " build -m NS [-o OUT] [--opt|--dev] compile a standalone binary")
|
(println " build -m NS [-o OUT] [--opt|--dev] [--direct-link] compile a standalone binary")
|
||||||
(println " -M:alias [args] run the alias's :main-opts")
|
(println " -M:alias [args] run the alias's :main-opts")
|
||||||
(println " -A:alias [args] add the alias's paths/deps")
|
(println " -A:alias [args] add the alias's paths/deps")
|
||||||
(println " repl start a line REPL")
|
(println " repl start a line REPL")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue