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

@ -74,6 +74,28 @@ the existing ctx image cache.
- C API used: `janet_getnumber/getinteger`, `janet_wrap_number`, `janet_fixarity`,
`janet_getfunction`, `janet_call`, `janet_cfuns`, `JANET_MODULE_ENTRY`.
## Status: wired into the compile path (JOLT_CGEN, opt-in)
`src/jolt/cgen.janet` (IR→C translator) is wired into the backend's `:def` emit
via `cgen-root`, gated behind **`JOLT_CGEN=1`** (off by default; needs
direct-linking). When on, 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` defns stay
bytecode.
The leaf-first rule emerges for free: `run` calls `count-point` (a user var, not
a native-op), so `run` isn't a numeric leaf and stays bytecode — calling the
native `count-point` over the cheap forward crossing.
**Measured end-to-end (`jolt -m mandelbrot 200`): 224 ms → 12.4 ms, ~18×**, with
the correct result — matching the spike's native-C ceiling. The default gate
(cgen off) is unchanged. Tests: `test/integration/cgen-pipeline-test.janet`.
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.
## Open questions for the implementation (next beads)
1. **IR→C for the numeric subset.** Translate jolt IR → C for proven-double