@@ -10687,6 +10687,90 @@ fn maybe_nothing() -> *mut Value {
1068710687 alloc(Value::Constructor(intern_str("Nothing"), encode_unit()))
1068810688}
1068910689
10690+// ── `compile : Text -> Maybe a` ────────────────────────────────────
10691+//
10692+// The JIT compiler is not part of knot-runtime (that would create a Cargo
10693+// cycle — knot-compiler's build.rs builds knot-runtime). Instead the program
10694+// links a separate `knot-compile-rt` archive whose code registers a compile
10695+// implementation here at startup; `base.compile` calls through the pointer.
10696+// A program built without the JIT archive leaves the slot empty and
10697+// `base.compile` returns `Nothing` for everything.
10698+
10699+/// Signature of the registered compile implementation: takes the program
10700+/// source and the host db handle, returns the forced value plus the inferred
10701+/// type string (out-params), or null on any compile error. Implemented by
10702+/// knot-compile-rt (`knot_compile_impl`).
10703+type CompileImpl = unsafe extern "C" fn(
10704+ src_ptr: *const u8,
10705+ src_len: usize,
10706+ db: *mut c_void,
10707+ out_ty_ptr: *mut *mut u8,
10708+ out_ty_len: *mut usize,
10709+) -> *mut Value;
10710+
10711+static COMPILE_IMPL: std::sync::atomic::AtomicPtr<c_void> =
10712+ std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
10713+
10714+/// Registered by knot-compile-rt at program startup to install the compile
10715+/// implementation. Called from the generated program's `main`.
10716+#[unsafe(no_mangle)]
10717+pub extern "C-unwind" fn knot_register_compile_impl(f: *mut c_void) {
10718+ COMPILE_IMPL.store(f, std::sync::atomic::Ordering::Release);
10719+}
10720+
10721+/// Look up the registered compile implementation, if any.
10722+fn compile_impl() -> Option<CompileImpl> {
10723+ let p = COMPILE_IMPL.load(std::sync::atomic::Ordering::Acquire);
10724+ if p.is_null() {
10725+ None
10726+ } else {
10727+ Some(unsafe { std::mem::transmute::<*mut c_void, CompileImpl>(p) })
10728+ }
10729+}
10730+
10731+/// `base.compile : Text -> Maybe a`. Compile+run the knot program in `src`
10732+/// in-process against the host runtime, returning `Just {value}` on success
10733+/// and `Nothing` on any compile error or when no JIT is linked.
10734+#[unsafe(no_mangle)]
10735+pub extern "C-unwind" fn knot_builtin_compile(src: *mut Value) -> *mut Value {
10736+ let Some(f) = compile_impl() else {
10737+ return maybe_nothing();
10738+ };
10739+ let Value::Text(source) = (unsafe { as_ref(src) }) else {
10740+ return maybe_nothing();
10741+ };
10742+ // Open a db handle for the snippet. knot_db_open records the path on first
10743+ // open (DB_PATH), so reuse it; a pure program (no store) gets :memory:.
10744+ // A snippet reading the host's persisted relations thus sees real data.
10745+ let db_path = DB_PATH.lock().unwrap_or_else(|e| e.into_inner()).clone();
10746+ let (pstr, plen) = if db_path.is_empty() {
10747+ (":memory:".as_bytes().as_ptr(), ":memory:".len())
10748+ } else {
10749+ (db_path.as_ptr(), db_path.len())
10750+ };
10751+ let db = knot_db_open(pstr, plen);
10752+ let mut ty_ptr: *mut u8 = std::ptr::null_mut();
10753+ let mut ty_len: usize = 0;
10754+ let value = unsafe {
10755+ f(
10756+ source.as_ptr(),
10757+ source.len(),
10758+ db,
10759+ &mut ty_ptr,
10760+ &mut ty_len,
10761+ )
10762+ };
10763+ if value.is_null() {
10764+ return maybe_nothing();
10765+ }
10766+ // TODO: unify the compiled program's type (ty_ptr/ty_len) against the
10767+ // caller's expected `a`; Nothing on mismatch. For now accept any type.
10768+ if !ty_ptr.is_null() {
10769+ unsafe { libc::free(ty_ptr as *mut c_void) };
10770+ }
10771+ maybe_just(value)
10772+}
10773+
1069010774/// nil — the empty List (1-arg builtin that ignores its argument, so the
1069110775/// bare `base.list.nil` function value has the standard 1-param shape).
1069210776#[unsafe(no_mangle)]