cgen: single-binary native build via jolt cgen-build (jolt-a7ds) (#150)
Fuse an app's native-compiled numeric-leaf fns plus its source into one static executable: no sidecar .so, no toolchain on the target. The AOT path (#148) already produced a prebuilt module + manifest; this links them into the jpm-built exe so the app ships as a single file. `jolt cgen-build -m NS -o OUT` stages a build dir (src/jolt-core symlinks into the jolt tree, a generated cg.c of the hot fns, an uberscript bundle of the app, and an entry that bakes the runtime, installs the native fns as var roots, and runs -main), then runs `jpm build` there — declare-native builds cg.a and declare-executable static-links it (jpm's create-executable marshals the module cfns and calls its static entry at startup). Build needs cc + jpm; the result needs neither. Mechanics that bit, codified in cgen_build.janet: stdlib_embed slurps .clj cwd-relative so the build runs in a repo-mirroring dir; jpm hardcodes ./project.janet and sets syspath=modpath; the executable's dofile imports cg and static-links cg.a, neither ordered nor release-built by default, so deps are wired explicitly; cleanup must lstat (the tree symlinks must not be followed); the inner build runs --workers=1 so it doesn't starve siblings in the parallel gate. test/integration/cgen-build-test.janet builds the mandelbrot fixture, runs it from a clean dir with no src/ and no cg.so, and checks the total at native speed. Closes jolt-a7ds. Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
c22e6279fa
commit
6cace3db90
5 changed files with 352 additions and 3 deletions
30
test/fixtures/cgen-build/cgapp.clj
vendored
Normal file
30
test/fixtures/cgen-build/cgapp.clj
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
;; Fixture app for the cgen single-binary build test (jolt-a7ds).
|
||||
;; count-point is a numeric leaf -> compiled to native C and statically linked;
|
||||
;; run/-main stay bytecode and call into it. -main prints a deterministic total.
|
||||
(ns cgapp)
|
||||
|
||||
(defn count-point [cr ci cap]
|
||||
(loop [i 0 zr 0.0 zi 0.0]
|
||||
(if (or (>= i cap) (> (+ (* zr zr) (* zi zi)) 4.0))
|
||||
i
|
||||
(recur (inc i)
|
||||
(+ (- (* zr zr) (* zi zi)) cr)
|
||||
(+ (* 2.0 (* zr zi)) ci)))))
|
||||
|
||||
(defn run [n]
|
||||
(let [cap 200
|
||||
nd (* 1.0 n)]
|
||||
(loop [y 0 acc 0]
|
||||
(if (< y n)
|
||||
(let [ci (- (/ (* 2.0 y) nd) 1.0)
|
||||
row (loop [x 0 a 0]
|
||||
(if (< x n)
|
||||
(let [cr (- (/ (* 2.0 x) nd) 1.5)]
|
||||
(recur (inc x) (+ a (count-point cr ci cap))))
|
||||
a))]
|
||||
(recur (inc y) (+ acc row)))
|
||||
acc))))
|
||||
|
||||
(defn -main [& args]
|
||||
(let [n (if (seq args) (Integer/parseInt (first args)) 200)]
|
||||
(println (run n))))
|
||||
51
test/integration/cgen-build-test.janet
Normal file
51
test/integration/cgen-build-test.janet
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Single-binary native build (jolt-a7ds): `jolt cgen-build` fuses an app's hot
|
||||
# numeric-leaf fns (compiled to C) + the app source into ONE static executable —
|
||||
# no sidecar .so, no toolchain on the target. This test builds the fixture app to
|
||||
# a binary, runs it from a CLEAN dir (no src/, no cg.so) to prove it's
|
||||
# self-contained, and checks the result + native fn count. Skips with no cc.
|
||||
(import ../../src/jolt/cgen :as cgen)
|
||||
(import ../../src/jolt/cgen_build :as cb)
|
||||
|
||||
(print "Single-binary native build (jolt-a7ds)...")
|
||||
|
||||
(var failures 0)
|
||||
(defn check [label ok] (if ok (print " ok: " label) (do (++ failures) (eprintf " FAIL: %s" label))))
|
||||
|
||||
(def home (os/cwd)) # the repo (gate runs here)
|
||||
(def fixture-root (string home "/test/fixtures/cgen-build"))
|
||||
|
||||
(if (cgen/toolchain-available?)
|
||||
(do
|
||||
(def out (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-cgen-build-test/app"))
|
||||
(def r (cb/build-app {:main "cgapp" :out out :jolt-home home
|
||||
:source-roots [fixture-root]}))
|
||||
(check "build reported the single native leaf (count-point)" (= 1 (r :native-count)))
|
||||
(check "binary exists at :out" (os/stat out))
|
||||
|
||||
# Run from a CLEAN dir with NO src/ and NO cg.so beside it — proves the binary
|
||||
# is self-contained (runtime needs neither the jolt tree nor a sidecar .so).
|
||||
(def rundir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-cgen-build-run"))
|
||||
(when (os/stat rundir) (each e (os/dir rundir) (os/rm (string rundir "/" e))) (os/rmdir rundir))
|
||||
(os/mkdir rundir)
|
||||
(def bin (string rundir "/app"))
|
||||
(spit bin (slurp out))
|
||||
(os/chmod bin 8r755)
|
||||
|
||||
(defn run-app [&opt n]
|
||||
(def tmp (string rundir "/out.txt"))
|
||||
(def f (file/open tmp :w))
|
||||
(def code (os/execute (if n [bin (string n)] [bin]) :px {:out f} ))
|
||||
(file/close f)
|
||||
{:code code :out (string/trim (slurp tmp))})
|
||||
|
||||
(def res (run-app))
|
||||
(check "exits 0" (= 0 (res :code)))
|
||||
(check "self-contained binary computes the right mandelbrot total (n=200)"
|
||||
(= "3288753" (res :out)))
|
||||
(check "passes through command-line args (n=400)"
|
||||
(= "13162060" ((run-app 400) :out))))
|
||||
(print " (toolchain absent — skipping single-binary build)"))
|
||||
|
||||
(if (= 0 failures)
|
||||
(print "All tests passed.")
|
||||
(do (eprintf "%d cgen-build check(s) failed" failures) (os/exit 1)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue