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>
71 lines
3.3 KiB
Text
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)))
|