ffi: foreign-callable — receive callbacks from C

jolt could call C (foreign-fn -> foreign-procedure) but C could not call back
into jolt, which GTK signals (and any callback-taking C API) require. Add the
inverse: jolt.ffi/foreign-callable wraps a jolt fn as a C-callable function
pointer, mirroring the foreign-fn pipeline.

A new jolt.ffi/__ccallable special form carries the fn as a child expression
(analyzed + walked by the passes; ir.clj gains an :ffi-callable arm in both
child walks) plus literal arg/ret type keywords. The back end lowers it to a
locked Chez foreign-callable and returns its entry-point address as a jolt
pointer; host/chez/ffi.ss registers the code object so the collector keeps it,
and free-callable unlocks it. :collect-safe emits the convention that
reactivates the thread on entry, for callbacks fired while it is parked in a
:blocking call (a GTK main loop).

Test: ffi-binding-test.ss sorts an int array through libc qsort with a jolt
comparator (C -> jolt -> C). Re-minted seed.
This commit is contained in:
Yogthos 2026-06-23 09:54:32 -04:00
parent 91eed2b622
commit c91b6092bc
7 changed files with 178 additions and 70 deletions

View file

@ -16,7 +16,9 @@
The memory/library primitives (alloc/free/read/write/sizeof/load-library/
ptr->string/string->ptr/null/null?) are provided by the host. foreign-fn lowers
a compile-time-typed signature to a real Chez foreign-procedure.")
a compile-time-typed signature to a real Chez foreign-procedure. foreign-callable
is the inverse it wraps a jolt fn as a C-callable function pointer so C can
call back into jolt (e.g. GTK signal handlers); free-callable releases it.")
;; foreign-fn binds C symbol `csym` to a typed callable. Expands to the __cfn
;; special form (always fully-qualified, so an :as alias on jolt.ffi resolves):
@ -33,3 +35,19 @@
(list 'def name (if (= opt :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype :blocking)
(list 'jolt.ffi/__cfn csym argtypes rettype))))
;; foreign-callable wraps a jolt fn `f` as a C-callable function pointer — the
;; inverse of foreign-fn, so C can call back INTO jolt (GTK signal handlers, a
;; qsort comparator, any C API that takes a callback). Returns the pointer; pass
;; it where C expects a function pointer. argtypes/rettype use the same keywords
;; as foreign-fn; the args C passes arrive as jolt values and the jolt return is
;; marshaled back. The callback stays live until free-callable is called on the
;; pointer. Pass a trailing :collect-safe when C invokes the callback from a
;; thread parked in a :blocking foreign call (e.g. a GTK main loop):
;; (g-signal-connect button "clicked"
;; (ffi/foreign-callable on-click [:pointer :pointer] :void :collect-safe)
;; (ffi/null))
(defmacro foreign-callable [f argtypes rettype & [opt]]
(if (= opt :collect-safe)
(list 'jolt.ffi/__ccallable f argtypes rettype :collect-safe)
(list 'jolt.ffi/__ccallable f argtypes rettype)))