cgen: install native fns into the compile path under JOLT_CGEN (jolt-ihdp) (#146)

Wires src/jolt/cgen.janet into the backend's :def emit. With JOLT_CGEN=1 (off by
default, needs direct-linking), a plain defn of a numeric-leaf fn is compiled to
C at def time and the cfunction installed as the var root, so direct-linked
callers embed native code. The fn is not inline-stashed when cgen fires —
callers must call the C fn, not inline the bytecode body. ^:redef/^:dynamic stay
bytecode.

The leaf-first rule falls out: run calls count-point (a user var), so run isn't a
numeric leaf and stays bytecode, calling the native count-point over the cheap
forward crossing. mandelbrot 200: 224ms -> 12.4ms (~18x), result unchanged.

Adds JOLT_CGEN to ctx-shaping-env-vars (rides the disk-cache key) and :cgen? to
resolve-run-mode. Default path (cgen off) is a no-op: cgen-root returns nil and
the normal bytecode emit runs. Gate green (117 files). Test:
test/integration/cgen-pipeline-test.janet.

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-16 17:12:48 +00:00 committed by GitHub
parent 19e8ee906a
commit 393656d8d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 107 additions and 10 deletions

View file

@ -12,6 +12,7 @@
(use ./core)
(use ./evaluator)
(import ./reader :as r)
(import ./cgen :as cgen)
(import ./phm :as phm)
(import ./phs :as phs)
(import ./pv :as pv)
@ -607,6 +608,20 @@
(def items (map |(emit ctx $) (vview (node :items))))
(tuple/slice (array/concat @[phs/make-phs] items)))
# Native codegen hook (jolt-ihdp). Under :cgen?, a plain defn of a numeric-leaf
# fn is compiled to C and the resulting cfunction installed as the var root, so
# direct-linked callers embed native code. Returns the cfunction or nil (not a
# 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 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)))))
(set emit
(fn emit [ctx raw]
(def node (norm-node raw))
@ -654,14 +669,23 @@
:throw ['error (emit ctx (node :expr))]
: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))]
(inline-stash! ctx cell node)
# Emit the init BEFORE marking the cell unit-defined, so a recursive
# self-reference in the init still direct-links; a LATER sibling
# reference in this unit then sees it as redefined (jolt-wf4).
(let [init-form (emit ctx (node :init))]
(when-let [ud (get (ctx :env) :unit-defs)] (put ud cell true))
(tuple setter init-form)))
setter (if (and meta (not (empty? meta))) (var-setter-meta cell meta) (var-setter cell))
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
# body (which would bypass the native version). Otherwise the normal
# bytecode path: stash for inlining, emit the init.
(if cfn
(do (when-let [ud (get (ctx :env) :unit-defs)] (put ud cell true))
(tuple setter cfn))
(do (inline-stash! ctx cell node)
# Emit the init BEFORE marking the cell unit-defined, so a
# recursive self-reference in the init still direct-links; a
# LATER sibling reference in this unit then sees it as
# redefined (jolt-wf4).
(let [init-form (emit ctx (node :init))]
(when-let [ud (get (ctx :env) :unit-defs)] (put ud cell true))
(tuple setter init-form)))))
:let (emit-let ctx node)
:fn (emit-fn ctx node)
:invoke (emit-invoke ctx node)

View file

@ -28,7 +28,7 @@
"JOLT_INTERPRET" "JOLT_INTERPRET_MACROS" "JOLT_DIRECT_LINK"
"JOLT_NO_DIRECT_LINK" "JOLT_OPTIMIZE" "JOLT_WHOLE_PROGRAM"
"JOLT_NO_WHOLE_PROGRAM" "JOLT_SHAPE" "JOLT_NO_SHAPE"
"JOLT_NO_IR_PASSES" "JOLT_CHECK_HINTS"])
"JOLT_NO_IR_PASSES" "JOLT_CHECK_HINTS" "JOLT_CGEN"])
(defn ctx-cache-key
"Build a disk-cache key from labeled prefix pairs plus the value of every
@ -69,4 +69,8 @@
:map-shapes? (and (os/getenv "JOLT_SHAPE") (not (os/getenv "JOLT_NO_SHAPE")))
:whole-program? (and optimize?
(not (os/getenv "JOLT_NO_WHOLE_PROGRAM"))
(or main-entry? (os/getenv "JOLT_WHOLE_PROGRAM")))})
(or main-entry? (os/getenv "JOLT_WHOLE_PROGRAM")))
# Native codegen (jolt-ihdp): compile numeric-leaf fns to C at def time and
# install the cfunction as the var root, so callers direct-link to native
# code. Opt-in (cc latency per fn); needs direct-linking so callers embed it.
:cgen? (and dl (= "1" (os/getenv "JOLT_CGEN")))})