jolt/test/integration/cgen-test.janet
Dmitri Sotnikov 19e8ee906a
cgen: jolt-IR -> C for numeric-leaf fns (jolt-ihdp) (#145)
First slice of the native-codegen tier. A new standalone module, src/jolt/
cgen.janet, that translates a numeric-leaf fn (numeric in/out, body uses only
native-op arithmetic + loop/recur/if/let/do) to a Janet native C module: params
unboxed to C doubles at entry, loop/recur lowered to a while loop, reboxed at
return. compile-fn runs cc and loads the .so via the native builtin, returning a
cfunction; it returns nil for non-candidates or when the toolchain is absent.

count-point compiles and matches the bytecode fn across the mandelbrot grid
(test/integration/cgen-test.janet, which skips the behavioral leg where cc/janet.h
are missing). Nothing wires this into the default compile path yet — detecting
hot fns and installing the C version onto the var cell is the next step.

See docs/foundational-runtime-lever1-native-codegen.md for the ceiling
(native-C ~18-22x faster than bytecode, edges out JVM) and the leaf-first rule.

Co-authored-by: Yogthos <yogthos@gmail.com>
2026-06-16 16:56:33 +00:00

71 lines
3.3 KiB
Text

# Native codegen (jolt-ihdp): IR -> C for numeric-leaf fns. Pins that the C
# translator (1) classifies candidates correctly and (2) produces a native fn
# whose results match the bytecode/interpreted fn over the mandelbrot grid.
# Skips cleanly where the C toolchain (cc + janet.h) is absent — the rest of the
# gate still runs.
(import ../../src/jolt/api :as api)
(import ../../src/jolt/backend :as backend)
(import ../../src/jolt/reader :as reader)
(import ../../src/jolt/cgen :as cgen)
(print "Native codegen IR->C (jolt-ihdp)...")
(def ctx (api/init-cached {:compile? true}))
(put (ctx :env) :direct-linking? true)
(put (ctx :env) :inline? true)
(api/eval-string ctx "(ns cgentest)")
(defn ir-of [src] (backend/analyze-form ctx (reader/parse-string src)))
(def count-point-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)))))")
(var failures 0)
(defn check [label ok] (unless ok (++ failures) (eprintf " FAIL: %s" label)))
# --- classification ---
(check "count-point is a numeric leaf" (cgen/numeric-leaf? (ir-of count-point-src)))
(check "fn calling a non-native fn is NOT a leaf"
(not (cgen/numeric-leaf? (ir-of "(defn f [x] (str x))"))))
(check "fn building a collection is NOT a leaf"
(not (cgen/numeric-leaf? (ir-of "(defn f [x] [x x])"))))
(check "plain numeric expr fn IS a leaf"
(cgen/numeric-leaf? (ir-of "(defn sq [x] (* x x))")))
# --- C generation is well-formed (smoke: contains the wrapper + a loop) ---
(def c-src (cgen/gen-c-fn (ir-of count-point-src) "count_point"))
(check "emits a cfunction wrapper" (string/find "cfun_count_point" c-src))
(check "lowers the loop to a C while" (string/find "while (" c-src))
(check "unboxes params with janet_getnumber" (string/find "janet_getnumber" c-src))
# --- behavioral equivalence (only where the toolchain is present) ---
(if (cgen/toolchain-available?)
(do
(api/eval-string ctx count-point-src)
(def bc-cp (api/eval-string ctx "count-point")) # the compiled/interpreted fn
(def c-cp (cgen/compile-fn (ir-of count-point-src)
{:dir "build/cgen-test" :name "count_point_test"}))
(defn callable? [x] (or (function? x) (cfunction? x)))
(check "compile-fn returns a callable" (callable? c-cp))
(when (callable? c-cp)
# spot points spanning escape-fast .. in-set
(var ptmatch true)
(each [cr ci] [[2.0 2.0] [0.5 0.5] [-0.5 0.6] [0.28 0.0] [-0.74 0.1] [-0.5 0.0] [0.0 0.0]]
(unless (= (c-cp cr ci 200) (bc-cp cr ci 200)) (set ptmatch false)))
(check "C count-point matches bytecode on sample points" ptmatch)
# full mandelbrot grid total
(defn grid-total [cp n]
(def cap 200) (def nd (* 1.0 n)) (var acc 0) (var y 0)
(while (< y n)
(def ci (- (/ (* 2.0 y) nd) 1.0)) (var x 0) (var a 0)
(while (< x n)
(def cr (- (/ (* 2.0 x) nd) 1.5)) (set a (+ a (cp cr ci cap))) (++ x))
(set acc (+ acc a)) (++ y))
acc)
(check "C and bytecode agree on the full n=80 grid total"
(= (grid-total c-cp 80) (grid-total bc-cp 80)))))
(print " (toolchain absent — skipping behavioral equivalence)"))
(if (= 0 failures)
(print "All tests passed.")
(do (eprintf "%d cgen check(s) failed" failures) (os/exit 1)))