cgen: build-time AOT — native fns without a toolchain on the target (jolt-a7ds) (#148)
Splits native codegen into a build phase (needs cc) and a deploy phase (none):
- gen-c-module/compile-module compile MANY numeric-leaf fns into ONE native
module (the AOT shape), generalizing the one-fn-per-.so JIT path.
- Backend :cgen-collect? records each numeric-leaf defn's IR while the app loads
as bytecode; cgen/aot-build compiles them into one module and write-manifest
persists {sopath, [{ns name sym}]}.
- Backend :cgen-prebuilt + cgen/load-aot: the deploy run loads the prebuilt .so
(via the native builtin, no cc) and installs each cfunction as the var root
with the same timing as the JIT path, so callers direct-link to native code.
- toolchain-available? no longer crashes when cc is off PATH (os/execute raises
on a missing exe) — a toolchain-less target now gets false.
Proven end-to-end in two processes (spike/native/aot-demo.janet): build with cc,
then deploy with cc removed from PATH -> count-point still native, mandelbrot
3288753 at 12.4ms (full 18x). Test: test/integration/cgen-aot-test.janet. Default
path unchanged; the modes are opt-in. Gate green (118 files).
Remaining for a literal single binary: fuse the .so + manifest into the jpm exe.
Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
ce3c7df24b
commit
bffb492c1c
5 changed files with 282 additions and 43 deletions
|
|
@ -96,6 +96,28 @@ Known limitation: building *core* with `JOLT_CGEN=1` would try to cgen core
|
|||
numeric-leaf fns into the cached ctx image, where embedded cfunctions may not
|
||||
serialize — keep cgen for app/user code until image-cache interaction is handled.
|
||||
|
||||
## Build-time AOT: native speed without a toolchain on the target (jolt-a7ds)
|
||||
|
||||
The JIT path above runs `cc` at runtime. The AOT path moves compilation to build
|
||||
time so the deploy target needs no `cc`/`janet.h`:
|
||||
|
||||
- **Build phase** (`:cgen-collect?`, needs cc): loading the app records every
|
||||
numeric-leaf defn's IR; `cgen/aot-build` compiles them all into ONE native
|
||||
module (`gen-c-module`) and `write-manifest` persists `{sopath, [{ns name sym}]}`.
|
||||
- **Deploy phase** (`:cgen-prebuilt`, NO cc): `cgen/load-aot` loads the prebuilt
|
||||
`.so` (via the `native` builtin — no compiler) into a qname→cfunction map; the
|
||||
backend's `:def` hook installs each as the var root with the same timing as the
|
||||
JIT path, so callers direct-link to native code.
|
||||
|
||||
**Proven** (`spike/native/aot-demo.janet`, two processes): build with cc, then
|
||||
deploy with `cc` removed from PATH → `count-point` is still native, mandelbrot =
|
||||
3288753 at **12.4 ms** (full 18×). Test: `test/integration/cgen-aot-test.janet`.
|
||||
|
||||
This removes the runtime-toolchain dependency — the core of the deployment story.
|
||||
What remains for a literal single binary: fuse the prebuilt `.so` + manifest into
|
||||
the `jpm`-built executable (declare-native/static-lib link + an uberscript-style
|
||||
source bundle), so it ships as one file instead of an exe + sidecar `.so`.
|
||||
|
||||
## Open questions for the implementation (next beads)
|
||||
|
||||
1. **IR→C for the numeric subset.** Translate jolt IR → C for proven-double
|
||||
|
|
|
|||
47
spike/native/aot-demo.janet
Normal file
47
spike/native/aot-demo.janet
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# AOT build/deploy demo (jolt-a7ds). Two phases, run as separate processes:
|
||||
# janet spike/native/aot-demo.janet build # needs cc; compiles + writes manifest
|
||||
# janet spike/native/aot-demo.janet deploy # run with cc REMOVED from PATH
|
||||
# Proves the deploy target needs no C toolchain: it only loads the prebuilt .so.
|
||||
(import ../../src/jolt/api :as api)
|
||||
(import ../../src/jolt/cgen :as cgen)
|
||||
|
||||
(def cp "(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)))))")
|
||||
(def run "(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))))")
|
||||
(def manifest (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-aot-demo.jdn"))
|
||||
(def dir "spike/native/build/aot")
|
||||
|
||||
(defn setup [ctx]
|
||||
(put (ctx :env) :direct-linking? true)
|
||||
(api/eval-string ctx "(ns demo)"))
|
||||
|
||||
(def phase (get (dyn :args) 1))
|
||||
(cond
|
||||
(= phase "build")
|
||||
(do
|
||||
(def ctx (api/init-cached {:compile? true}))
|
||||
(put (ctx :env) :cgen-collect? true)
|
||||
(setup ctx)
|
||||
(api/eval-string ctx cp)
|
||||
(api/eval-string ctx run)
|
||||
(def build (cgen/aot-build (get (ctx :env) :cgen-collected) {:dir dir}))
|
||||
(cgen/write-manifest manifest build)
|
||||
(printf "BUILD: cc-available? %p -> %s (%d fn)" (cgen/toolchain-available?)
|
||||
(build :sopath) (length (build :entries))))
|
||||
|
||||
(= phase "deploy")
|
||||
(do
|
||||
(printf "DEPLOY: cc-available? %p (should be false with cc off PATH)"
|
||||
(cgen/toolchain-available?))
|
||||
(def ctx (api/init-cached {:compile? true}))
|
||||
(setup ctx)
|
||||
(put (ctx :env) :cgen-prebuilt (cgen/load-aot manifest))
|
||||
(api/eval-string ctx cp)
|
||||
(api/eval-string ctx run)
|
||||
(def native? (cfunction? (api/eval-string ctx "count-point")))
|
||||
(def t0 (os/clock))
|
||||
(def total (api/eval-string ctx "(run 200)"))
|
||||
(def ms (* 1000 (- (os/clock) t0)))
|
||||
(printf "DEPLOY: count-point native? %p total %d (%s) %.1f ms"
|
||||
native? total (if (= total 3288753) "OK" "MISMATCH") ms))
|
||||
|
||||
(eprint "usage: aot-demo.janet build|deploy"))
|
||||
|
|
@ -614,13 +614,34 @@
|
|||
# candidate / redefable / toolchain absent / cc failed -> normal bytecode path).
|
||||
# Skip ^:redef / ^:dynamic: those must stay redefinable bytecode.
|
||||
(defn- cgen-root [ctx node]
|
||||
(when (get (ctx :env) :cgen?)
|
||||
(def env (ctx :env))
|
||||
(def meta (node :meta))
|
||||
(def redefable (and meta (or (get meta :redef) (get meta :dynamic))))
|
||||
(when (not redefable)
|
||||
(def prebuilt (get env :cgen-prebuilt))
|
||||
(def qn (string (node :ns) "/" (node :name)))
|
||||
(cond
|
||||
# AOT deploy: install the build-time-compiled cfunction as the root (no cc).
|
||||
# Same timing as the JIT path, so callers compiled later direct-link to it.
|
||||
(and prebuilt (get prebuilt qn)) (get prebuilt qn)
|
||||
# JIT: compile to C now (runs cc).
|
||||
(and (get env :cgen?) (cgen/numeric-leaf? (node :init)))
|
||||
(let [r (protect (cgen/compile-fn (node :init) {:name (string (node :ns) "_" (node :name))}))]
|
||||
(and (r 0) (r 1))))))
|
||||
|
||||
# Build-time collection (jolt-a7ds): under :cgen-collect?, record each numeric-
|
||||
# leaf defn's full fn IR (with its ns/name) so an AOT driver can compile them all
|
||||
# into one native module. Does NOT swap the root — the app still loads as bytecode
|
||||
# during collection; the native version is wired at the later deploy run.
|
||||
(defn- cgen-collect! [ctx node]
|
||||
(def env (ctx :env))
|
||||
(when (get env :cgen-collect?)
|
||||
(def meta (node :meta))
|
||||
(def redefable (and meta (or (get meta :redef) (get meta :dynamic))))
|
||||
(when (and (not redefable) (cgen/numeric-leaf? (node :init)))
|
||||
(def r (protect (cgen/compile-fn (node :init)
|
||||
{:name (string (node :ns) "_" (node :name))})))
|
||||
(and (r 0) (r 1)))))
|
||||
(def coll (or (get env :cgen-collected)
|
||||
(let [a @[]] (put env :cgen-collected a) a)))
|
||||
(array/push coll {:ns (node :ns) :name (node :name) :ir (node :init)}))))
|
||||
|
||||
(set emit
|
||||
(fn emit [ctx raw]
|
||||
|
|
@ -670,6 +691,7 @@
|
|||
:def (let [cell (cell-for ctx (node :ns) (node :name))
|
||||
meta (node :meta)
|
||||
setter (if (and meta (not (empty? meta))) (var-setter-meta cell meta) (var-setter cell))
|
||||
_ (cgen-collect! ctx node)
|
||||
cfn (cgen-root ctx node)]
|
||||
# cgen path: install the native cfunction as the root and DON'T
|
||||
# inline-stash — callers must call the C fn, not inline the bytecode
|
||||
|
|
|
|||
|
|
@ -169,32 +169,46 @@
|
|||
(and (>= c 48) (<= c 57)) (= c 95)) c 95)))
|
||||
(string b))
|
||||
|
||||
(defn gen-c-fn
|
||||
"ir (a numeric-leaf :def-of-fn or :fn node), c-name -> a C source string for a
|
||||
Janet native module exporting `c-name` as a cfunction. Assumes (numeric-leaf? ir)."
|
||||
[ir c-name]
|
||||
# Emit just the `static Janet cfun_<c-name>(...) { ... }` definition for one fn
|
||||
# into buf. Shared by the single-fn and multi-fn (AOT module) generators.
|
||||
(defn- emit-cfun [buf ir c-name]
|
||||
(def fnode (fn-ir-of ir))
|
||||
(def ar (first (ct/vview (fnode :arities))))
|
||||
(def params (map string (ct/vview (ar :params))))
|
||||
(def namer (new-namer))
|
||||
(def scope @{})
|
||||
(def pre @"")
|
||||
(buffer/push pre "#include <janet.h>\n")
|
||||
(buffer/push pre "static Janet cfun_" c-name "(int32_t argc, Janet *argv) {\n")
|
||||
(buffer/push pre "janet_fixarity(argc, " (string (length params)) ");\n")
|
||||
(buffer/push buf "static Janet cfun_" c-name "(int32_t argc, Janet *argv) {\n")
|
||||
(buffer/push buf "janet_fixarity(argc, " (string (length params)) ");\n")
|
||||
(eachp [i pn] params
|
||||
(def cv (namer))
|
||||
(buffer/push pre "double " cv " = janet_getnumber(argv, " (string i) ");\n")
|
||||
(buffer/push buf "double " cv " = janet_getnumber(argv, " (string i) ");\n")
|
||||
(put scope pn cv))
|
||||
(def out @"")
|
||||
(def ret (namer))
|
||||
(buffer/push out "double " ret ";\n")
|
||||
(emit-to (ar :body) ret scope nil out namer)
|
||||
(string pre out
|
||||
"return janet_wrap_number(" ret ");\n}\n"
|
||||
"static const JanetReg cfuns[] = {{\"" c-name "\", cfun_" c-name
|
||||
", NULL}, {NULL, NULL, NULL}};\n"
|
||||
"JANET_MODULE_ENTRY(JanetTable *env) { janet_cfuns(env, \"cg\", cfuns); }\n"))
|
||||
(buffer/push buf "double " ret ";\n")
|
||||
(emit-to (ar :body) ret scope nil buf namer)
|
||||
(buffer/push buf "return janet_wrap_number(" ret ");\n}\n"))
|
||||
|
||||
(defn gen-c-module
|
||||
"entries: [{:sym <C identifier> :ir <numeric-leaf fn IR>} ...] -> C source for
|
||||
ONE Janet native module exporting every entry's :sym as a cfunction. This is
|
||||
the AOT/build-time shape (jolt-a7ds): all of an app's hot fns in a single .so,
|
||||
compiled once at build time. Assumes each :ir is a numeric leaf."
|
||||
[entries]
|
||||
(def buf @"")
|
||||
(buffer/push buf "#include <janet.h>\n")
|
||||
(each e entries (emit-cfun buf (e :ir) (e :sym)))
|
||||
(buffer/push buf "static const JanetReg cfuns[] = {\n")
|
||||
(each e entries
|
||||
(buffer/push buf " {\"" (e :sym) "\", cfun_" (e :sym) ", NULL},\n"))
|
||||
(buffer/push buf " {NULL, NULL, NULL}};\n")
|
||||
(buffer/push buf "JANET_MODULE_ENTRY(JanetTable *env) { janet_cfuns(env, \"cg\", cfuns); }\n")
|
||||
(string buf))
|
||||
|
||||
(defn gen-c-fn
|
||||
"ir (a numeric-leaf :def-of-fn or :fn node), c-name -> C source for a Janet
|
||||
native module exporting `c-name` as a cfunction. Assumes (numeric-leaf? ir)."
|
||||
[ir c-name]
|
||||
(gen-c-module [{:sym c-name :ir ir}]))
|
||||
|
||||
# --- toolchain ---
|
||||
(defn header-dir
|
||||
|
|
@ -208,11 +222,15 @@
|
|||
found)
|
||||
|
||||
(defn toolchain-available?
|
||||
"True when a C compiler and janet.h are present (so compile-fn can work)."
|
||||
"True when a C compiler and janet.h are present (so compile-fn can work). Never
|
||||
throws: a missing cc (not on PATH) makes os/execute raise, so guard it — a
|
||||
toolchain-less deploy target must get false, not a crash."
|
||||
[]
|
||||
(and (header-dir)
|
||||
(= 0 (os/execute ["cc" "--version"] :px
|
||||
{:out (file/open "/dev/null" :w) :err (file/open "/dev/null" :w)}))))
|
||||
(let [r (protect (os/execute ["cc" "--version"] :p
|
||||
{:out (file/open "/dev/null" :w)
|
||||
:err (file/open "/dev/null" :w)}))]
|
||||
(and (r 0) (= 0 (r 1))))))
|
||||
|
||||
(defn- mkdirs [path]
|
||||
(def parts (filter |(not (empty? $)) (string/split "/" path)))
|
||||
|
|
@ -233,32 +251,103 @@
|
|||
(def r (os/execute cmd :p))
|
||||
(unless (= 0 r) (error (string "cgen: cc failed (" r ") for " cpath))))
|
||||
|
||||
(defn- abspath [p] (if (string/has-prefix? "/" p) p (string (os/cwd) "/" p)))
|
||||
|
||||
# Build a .so from C source, content-addressed and cached. The C + Janet ABI +
|
||||
# platform fully determine the .so, so a matching cache entry is always valid.
|
||||
# Returns the absolute .so path. JOLT_CGEN_NO_CACHE=1 forces a rebuild.
|
||||
(defn- build-so [src dir]
|
||||
(def stamp (string src "|" janet/version "-" janet/build "|" (os/which)))
|
||||
(def key (string (band (hash stamp) 0x7FFFFFFF) "-" (length src)))
|
||||
(mkdirs dir)
|
||||
(def base (string dir "/cg-" key))
|
||||
(def sopath (string base ".so"))
|
||||
(def abs (abspath sopath))
|
||||
(unless (and (not (os/getenv "JOLT_CGEN_NO_CACHE")) (os/stat abs))
|
||||
(def cpath (string base ".c"))
|
||||
(spit cpath src)
|
||||
(cc-compile cpath sopath))
|
||||
abs)
|
||||
|
||||
(defn load-module
|
||||
"Load a prebuilt cgen .so (NO cc — works on a target with no toolchain) and
|
||||
return a table of symbol-string -> cfunction for every cfunction it exports.
|
||||
This is the runtime side of the build-time/AOT path (jolt-a7ds)."
|
||||
[sopath]
|
||||
(def env @{})
|
||||
(native (abspath sopath) env)
|
||||
(def out @{})
|
||||
(eachp [k v] env
|
||||
(when (and (symbol? k) (table? v) (or (cfunction? (v :value)) (function? (v :value))))
|
||||
(put out (string k) (v :value))))
|
||||
out)
|
||||
|
||||
(defn compile-module
|
||||
"AOT path: compile MANY numeric-leaf fns into ONE native module, once, at build
|
||||
time. entries: [{:sym <C id> :ir <numeric-leaf IR>} ...]. Returns
|
||||
{:sopath <abs .so> :fns {sym -> cfunction}}, or nil if the toolchain is absent
|
||||
or any entry isn't a numeric leaf. opts: :dir (cache dir)."
|
||||
[entries &opt opts]
|
||||
(default opts {})
|
||||
(when (and (header-dir) (all |(numeric-leaf? ($ :ir)) entries))
|
||||
(def src (gen-c-module entries))
|
||||
(def sopath (build-so src (get opts :dir (cache-dir))))
|
||||
{:sopath sopath :fns (load-module sopath)}))
|
||||
|
||||
(defn compile-fn
|
||||
"Compile a numeric-leaf fn IR to a native Janet cfunction. Returns the
|
||||
"Compile a single numeric-leaf fn IR to a native Janet cfunction. Returns the
|
||||
cfunction, or nil if ir isn't a candidate or the toolchain is unavailable.
|
||||
opts: :dir (cache dir, default a persistent jolt-cgen under TMPDIR), :name (C
|
||||
identifier base). The .so is content-addressed by a hash of the generated C +
|
||||
the Janet ABI + platform, so cc only runs on the first build of a given fn;
|
||||
later runs (same source) reuse the cached .so. JOLT_CGEN_NO_CACHE=1 forces a
|
||||
rebuild. cc errors propagate (a compiler crash is a cgen bug, not a punt)."
|
||||
identifier base). Content-addressed/cached like compile-module."
|
||||
[ir &opt opts]
|
||||
(default opts {})
|
||||
(when (and (numeric-leaf? ir) (header-dir))
|
||||
(def c-name (sanitize (get opts :name "jolt_cfn")))
|
||||
(def src (gen-c-fn ir c-name))
|
||||
# content address: the C (semantics + symbol name) + ABI + platform fully
|
||||
# determine the .so, so a matching cache entry is always valid.
|
||||
(def stamp (string src "|" janet/version "-" janet/build "|" (os/which)))
|
||||
(def key (string (band (hash stamp) 0x7FFFFFFF) "-" (length src)))
|
||||
(def dir (get opts :dir (cache-dir)))
|
||||
(mkdirs dir)
|
||||
(def base (string dir "/cg-" key))
|
||||
(def sopath (string base ".so"))
|
||||
(def abs (if (string/has-prefix? "/" sopath) sopath (string (os/cwd) "/" sopath)))
|
||||
(unless (and (not (os/getenv "JOLT_CGEN_NO_CACHE")) (os/stat abs))
|
||||
(def cpath (string base ".c"))
|
||||
(spit cpath src)
|
||||
(cc-compile cpath sopath))
|
||||
(def sopath (build-so src (get opts :dir (cache-dir))))
|
||||
(def env @{})
|
||||
(native abs env)
|
||||
(native sopath env)
|
||||
((get env (symbol c-name)) :value)))
|
||||
|
||||
# --- AOT build/deploy (jolt-a7ds) ---------------------------------------------
|
||||
# Build phase (needs cc): collect an app's numeric-leaf fns (the backend's
|
||||
# :cgen-collect? mode fills (ctx :env) :cgen-collected with [{:ns :name :ir}]),
|
||||
# compile them all into ONE native module, and write a manifest. Deploy phase
|
||||
# (NO cc, target needs no toolchain): load the prebuilt module + manifest into a
|
||||
# qname->cfunction map that the backend installs as var roots (:cgen-prebuilt).
|
||||
|
||||
(defn aot-build
|
||||
"collected: [{:ns :name :ir} ...] (numeric-leaf fns). Compile them into one
|
||||
native module and return {:sopath <abs .so> :entries [{:ns :name :sym}]}, or
|
||||
nil (toolchain absent / a non-leaf / empty). opts: :dir (cache dir).
|
||||
Syms are positional (f0, f1, ...) so they're collision-free."
|
||||
[collected &opt opts]
|
||||
(default opts {})
|
||||
(when (and (header-dir) (not (empty? collected)))
|
||||
(def entries @[])
|
||||
(def manifest @[])
|
||||
(eachp [i e] collected
|
||||
(def sym (string "f" i))
|
||||
(array/push entries {:sym sym :ir (e :ir)})
|
||||
(array/push manifest {:ns (e :ns) :name (e :name) :sym sym}))
|
||||
(def m (compile-module entries opts))
|
||||
(and m {:sopath (m :sopath) :entries manifest})))
|
||||
|
||||
(defn write-manifest
|
||||
"Persist an aot-build result to path as jdn (the deploy side reads it)."
|
||||
[path build]
|
||||
(spit path (string/format "%j" build)))
|
||||
|
||||
(defn load-aot
|
||||
"Deploy side (NO cc): read a manifest written by write-manifest, load its
|
||||
prebuilt .so, and return a qname (\"ns/name\") -> cfunction map suitable for
|
||||
(ctx :env) :cgen-prebuilt. nil if the manifest/.so is missing."
|
||||
[manifest-path]
|
||||
(when (os/stat manifest-path)
|
||||
(def build (parse (slurp manifest-path)))
|
||||
(def fns (load-module (build :sopath)))
|
||||
(def out @{})
|
||||
(each e (build :entries)
|
||||
(when-let [f (get fns (e :sym))]
|
||||
(put out (string (e :ns) "/" (e :name)) f)))
|
||||
out))
|
||||
|
|
|
|||
59
test/integration/cgen-aot-test.janet
Normal file
59
test/integration/cgen-aot-test.janet
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Native codegen AOT build/deploy (jolt-a7ds): compile an app's numeric-leaf fns
|
||||
# into ONE native module at build time, then deploy with NO cc — load the
|
||||
# prebuilt module and install the cfunctions as var roots. Proves the build-time
|
||||
# path that removes the runtime-toolchain dependency. Skips where cc/janet.h are
|
||||
# absent.
|
||||
(import ../../src/jolt/api :as api)
|
||||
(import ../../src/jolt/cgen :as cgen)
|
||||
|
||||
(print "Native codegen AOT build/deploy (jolt-a7ds)...")
|
||||
|
||||
(var failures 0)
|
||||
(defn check [label ok] (unless ok (++ failures) (eprintf " FAIL: %s" label)))
|
||||
|
||||
(def cp-src "(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)))))")
|
||||
(def run-src "(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))))")
|
||||
|
||||
(if (cgen/toolchain-available?)
|
||||
(do
|
||||
# --- build phase: collect numeric-leaf fns, compile one module, write manifest
|
||||
(def bctx (api/init-cached {:compile? true}))
|
||||
(put (bctx :env) :direct-linking? true)
|
||||
(put (bctx :env) :cgen-collect? true)
|
||||
(api/eval-string bctx "(ns aot)")
|
||||
(api/eval-string bctx cp-src)
|
||||
(api/eval-string bctx run-src)
|
||||
(def collected (get (bctx :env) :cgen-collected))
|
||||
(check "collected exactly the numeric-leaf fn (count-point, not run)"
|
||||
(and collected (= 1 (length collected))
|
||||
(= "count-point" ((first collected) :name))))
|
||||
(def manifest-path (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-aot-test.jdn"))
|
||||
(def build (cgen/aot-build collected {:dir "build/cgen-aot-test"}))
|
||||
(check "aot-build produced a module" (and build (build :sopath)))
|
||||
(cgen/write-manifest manifest-path build)
|
||||
|
||||
# --- deploy phase: fresh ctx, load prebuilt (NO cc), install roots, run
|
||||
(def dctx (api/init-cached {:compile? true}))
|
||||
(put (dctx :env) :direct-linking? true)
|
||||
(def prebuilt (cgen/load-aot manifest-path))
|
||||
(check "load-aot maps the qname to a cfunction"
|
||||
(cfunction? (get prebuilt "aot/count-point")))
|
||||
(put (dctx :env) :cgen-prebuilt prebuilt)
|
||||
(api/eval-string dctx "(ns aot)")
|
||||
(api/eval-string dctx cp-src)
|
||||
(api/eval-string dctx run-src)
|
||||
(check "deployed count-point root is the prebuilt cfunction"
|
||||
(cfunction? (api/eval-string dctx "count-point")))
|
||||
(check "deployed run stays bytecode" (not (cfunction? (api/eval-string dctx "run"))))
|
||||
(check "AOT-deployed mandelbrot computes the right total"
|
||||
(= 3288753 (api/eval-string dctx "(run 200)")))
|
||||
|
||||
# a fn NOT in the manifest must stay bytecode in deploy
|
||||
(api/eval-string dctx "(defn sq [x] (* x x))")
|
||||
(check "unlisted numeric-leaf stays bytecode in deploy (no cc)"
|
||||
(not (cfunction? (api/eval-string dctx "sq")))))
|
||||
(print " (toolchain absent — skipping AOT legs)"))
|
||||
|
||||
(if (= 0 failures)
|
||||
(print "All tests passed.")
|
||||
(do (eprintf "%d cgen-aot check(s) failed" failures) (os/exit 1)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue