commit 805d7438efb536c528cd923294e4aa8fe7b7ef1f
parent 84391328a6db0a49e399438b7899ab18724949f5
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 13:07:13 -0700
build: isolate hosted driver environment
Diffstat:
6 files changed, 238 insertions(+), 216 deletions(-)
diff --git a/Makefile b/Makefile
@@ -10,20 +10,31 @@ HOST_OPTFLAGS ?= -O1
HOST_SYSROOT_CFLAGS = -isysroot $(SYSROOT)
HOST_SYSROOT_LDFLAGS = -isysroot $(SYSROOT)
-CFLAGS_COMMON = $(HOST_OPTFLAGS) -std=c11 -Wpedantic -Wall -Wextra -Werror $(HOST_SYSROOT_CFLAGS)
+CFLAGS_COMMON = $(HOST_OPTFLAGS) -std=c11 -Wpedantic -Wall -Wextra -Werror
+HOST_CFLAGS = $(CFLAGS_COMMON) $(HOST_SYSROOT_CFLAGS)
DEPFLAGS = -MMD -MP
+# Freestanding objects must not see host SDK/libc headers. Homebrew clang can
+# also inject a configured sysroot before command-line flags; use
+# --no-default-config when the selected compiler supports it, but omit it for
+# bootstrap stages where $(CC) is cfree cc.
+FREESTANDING_CONFIG_CFLAGS = $(shell $(CC) --no-default-config -x c -E /dev/null >/dev/null 2>&1 && printf '%s' --no-default-config)
+FREESTANDING_CFLAGS = $(CFLAGS_COMMON) $(FREESTANDING_CONFIG_CFLAGS) -ffreestanding -nostdinc -Irt/include
+
# libcfree: written in C11 freestanding; sees both src/ (internal) and
# include/ (its own public surface).
-LIB_CFLAGS = $(CFLAGS_COMMON) -ffreestanding -Iinclude -Isrc
-
-# Driver: hosted CLI binary. Sees only the public include/ tree — that's
-# what makes the driver the first consumer of libcfree. -Ilang lets `cc` reach
-# the C frontend's public header ("c/c.h") for the JIT REPL; it deliberately
-# does NOT get -Isrc, so internal headers ("core/...", "link/...") are
-# unreachable from the driver.
-DRIVER_CFLAGS = $(CFLAGS_COMMON) -Iinclude -Ilang
+LIB_CFLAGS = $(FREESTANDING_CFLAGS) -Iinclude -Isrc
+
+# Driver: mostly freestanding CLI binary. Sees only the public include/ tree —
+# that's what makes the driver the first consumer of libcfree. -Ilang lets `cc`
+# reach the C frontend's public header ("c/c.h") for the JIT REPL; it
+# deliberately does NOT get -Isrc, so internal headers ("core/...", "link/...")
+# are unreachable from the driver. driver/env.c is the sole hosted OS/libc
+# adapter and is compiled with DRIVER_ENV_CFLAGS below.
+DRIVER_CFLAGS = $(FREESTANDING_CFLAGS) -Iinclude -Ilang
+DRIVER_ENV_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang
+TEST_HOST_CFLAGS = $(HOST_CFLAGS) -Iinclude -Ilang
include mk/config.mk
@@ -187,18 +198,18 @@ $(BUILD_DIR)/lib/api/lang_registry.o: src/api/lang_registry.c Makefile
$(BUILD_DIR)/lang/cpp/%.o: lang/cpp/%.c Makefile
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS_COMMON) -ffreestanding -Iinclude -Ilang/cpp $(DEPFLAGS) -c $< -o $@
+ $(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/cpp $(DEPFLAGS) -c $< -o $@
# The C frontend includes the lexer and preprocessor headers (pp/pp.h,
# lex/lex.h) which now live under lang/cpp/, and cpp_support.h is the
# shared substrate. So lang/c objects build with -Ilang/cpp -Ilang/c.
$(BUILD_DIR)/lang/c/%.o: lang/c/%.c Makefile
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS_COMMON) -ffreestanding -Iinclude -Ilang/cpp -Ilang/c $(DEPFLAGS) -c $< -o $@
+ $(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/cpp -Ilang/c $(DEPFLAGS) -c $< -o $@
$(BUILD_DIR)/lang/wasm/%.o: lang/wasm/%.c Makefile
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS_COMMON) -ffreestanding -Iinclude -Ilang/wasm $(DEPFLAGS) -c $< -o $@
+ $(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/wasm $(DEPFLAGS) -c $< -o $@
$(BUILD_DIR)/lib/%.o: src/%.S Makefile
@mkdir -p $(dir $@)
@@ -208,9 +219,13 @@ $(BUILD_DIR)/driver/%.o: driver/%.c Makefile
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) $(DEPFLAGS) -c $< -o $@
+$(BUILD_DIR)/driver/env.o: driver/env.c Makefile
+ @mkdir -p $(dir $@)
+ $(CC) $(DRIVER_ENV_CFLAGS) $(DEPFLAGS) -c $< -o $@
+
$(BUILD_DIR)/lang/toy/%.o: lang/toy/%.c Makefile
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS_COMMON) -ffreestanding -Iinclude -Ilang/toy $(DEPFLAGS) -c $< -o $@
+ $(CC) $(FREESTANDING_CFLAGS) -Iinclude -Ilang/toy $(DEPFLAGS) -c $< -o $@
include rt/Makefile
diff --git a/driver/driver.h b/driver/driver.h
@@ -6,6 +6,8 @@
#include <cfree/jit.h>
#include <cfree/dbg.h>
+#include "env.h"
+
/* The cfree CLI driver. Multi-call binary: dispatches to one of seven tool
* front-ends by argv[0]'s basename, falling back to argv[1] (e.g.
* `cfree cc ...`). The driver only depends on libcfree's public API
@@ -77,61 +79,6 @@ int driver_is_help_flag(const char *arg);
* help request — the convention is "no args means show help". */
int driver_argv_wants_help(int argc, char **argv, int accept_short_h);
-/* Shared host environment used by every tool that calls into libcfree.
- * driver_env_init wires up the libc-backed heap, the stderr diag sink, and
- * a POSIX file_io implementation (open/read/write on real paths). It is
- * the single piece of glue that turns "the host" into a CfreeContext.
- *
- * The execmem / dbg_os / jit_tls vtables that used to live on CfreeEnv now
- * live on CfreeJitHost / CfreeDbgHost. They are still constructed here so
- * that one DriverEnv covers every libcfree-using tool, but they're handed
- * to libcfree per-call via the appropriate host struct. */
-typedef struct DriverEnv {
- CfreeHeap *heap;
- CfreeDiagSink *diag;
- CfreeFileIO file_io;
- const CfreeExecMem *execmem;
- const CfreeDbgOs *dbg_os; /* NULL unless `cfree dbg` paths run */
- const CfreeJitTls *jit_tls; /* NULL unless `cfree run` w/ TLV paths run */
- const CfreeMetrics *metrics; /* optional scoped metrics sink */
- int64_t now; /* unix seconds; -1 = unknown */
-} DriverEnv;
-
-void driver_env_init(DriverEnv *);
-void driver_env_fini(DriverEnv *);
-
-/* Build a CfreeContext value pointing at the DriverEnv's heap/diag/file_io
- * vtables. The returned value can be passed by const-pointer to any
- * libcfree entry that takes `const CfreeContext *`. */
-CfreeContext driver_env_to_context(const DriverEnv *);
-
-/* Build a CfreeJitHost from the DriverEnv (execmem + tls). The returned
- * struct holds borrowed pointers to vtables owned by g_execmem_posix /
- * g_jit_tls_posix; callers must not outlive driver_env_fini. */
-CfreeJitHost driver_env_to_jit_host(const DriverEnv *);
-
-/* Build a CfreeDbgHost from the DriverEnv (dbg_os). */
-CfreeDbgHost driver_env_to_dbg_host(const DriverEnv *);
-
-/* Tells the stderr diag sink which compiler to use when resolving
- * SrcLoc.file_id to a path. The driver_compiler_{new,free} helpers
- * below already manage this; call this directly only when you hand a
- * CfreeCompiler from outside those helpers (none today). */
-void driver_diag_set_compiler(CfreeCompiler *);
-
-/* Lifecycle helpers around cfree_compiler_{new,free}. Identical to the
- * raw entries except they register the C / TOY / WASM frontends and
- * register the active compiler with the stderr diag sink so diagnostics
- * resolve loc.file_id to its registered path. Returns CFREE_OK on
- * success; on failure *out is NULL. */
-CfreeStatus driver_compiler_new(CfreeTarget, const CfreeContext *,
- CfreeCompiler **out);
-void driver_compiler_free(CfreeCompiler *);
-
-/* Default target used by tools that don't expose a target-selection flag
- * yet. v1: native-looking host target (chosen at compile time). */
-CfreeTarget driver_host_target(void);
-
/* Parse a target triple string (`<arch>[-<vendor>]-<os>[-<env>]`) into a
* CfreeTarget. Recognized arches: x86_64/amd64, i386/i486/i586/i686, aarch64/
* arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. Recognized OSes (scanned
@@ -146,128 +93,4 @@ int driver_target_from_triple(const char *triple, CfreeTarget *out);
* nonzero when `buf` is too small. */
int driver_target_to_triple(CfreeTarget target, char *buf, size_t cap);
-/* ----------------------------------------------------------------------
- * Host-shim helpers
- *
- * driver/env.c is the only TU in the driver allowed to depend on libc
- * facilities that issue syscalls or touch host state — stdio, malloc,
- * POSIX I/O, environment, time. Pure platform-independent libc (e.g.
- * <string.h>, <ctype.h>, <stdint.h>, <stddef.h>) is fine in any TU.
- * The shims below exist primarily for the syscall-shaped surface; other
- * TUs may also call them for consistency.
- * ---------------------------------------------------------------------- */
-
-/* String predicates and lookups. driver_streq / driver_strneq return
- * non-zero when the strings (or first n bytes) match — sense-flipped
- * from libc's strcmp so call sites read naturally. */
-int driver_streq(const char *a, const char *b);
-int driver_strneq(const char *a, const char *b, size_t n);
-size_t driver_strlen(const char *s);
-const char *driver_strchr(const char *s, int c);
-const char *driver_basename(const char *path);
-int driver_has_suffix(const char *s, const char *suffix);
-
-/* Memory. Allocations route through DriverEnv.heap; release returns the
- * size used at allocation (so the heap implementation can track usage). */
-void *driver_alloc(DriverEnv *, size_t);
-void *driver_alloc_zeroed(DriverEnv *, size_t);
-void driver_free(DriverEnv *, void *p, size_t);
-void driver_memcpy(void *dst, const void *src, size_t n);
-
-/* Opens a Writer that writes to stdout (fd 1). close frees the struct but
- * does not close the fd. */
-CfreeWriter *driver_stdout_writer(DriverEnv *);
-
-/* Test whether `path` names an existing filesystem entry (any type).
- * Returns nonzero on existence, zero otherwise. Used by library-path
- * resolution; intentionally distinct from read_all so candidate-search
- * loops don't slurp file contents on a hit. */
-int driver_path_exists(const char *path);
-
-/* Read a path's last modification time in nanoseconds since the Unix epoch.
- * Returns 0 on success, nonzero on stat failure. */
-int driver_path_mtime_ns(const char *path, int64_t *out);
-
-/* Create a directory and any missing parents. Returns 0 on success. */
-int driver_mkdir_p(DriverEnv *, const char *path);
-
-/* Set a linked binary output's final mode according to the active umask.
- * Returns 0 on success, nonzero on chmod failure. */
-int driver_mark_executable_output(const char *path);
-
-/* Diagnostic printing to host stderr. Format is `"<tool>: <fmt>\n"`. */
-void driver_errf(const char *tool, const char *fmt, ...);
-
-/* Raw hosted stderr log. Used by optional metrics so libcfree stays callback-
- * only and freestanding. */
-void driver_logf(const char *fmt, ...);
-
-/* Formatted output to stdout. */
-void driver_printf(const char *fmt, ...);
-
-/* Monotonic host time in nanoseconds, or 0 if unavailable. */
-uint64_t driver_now_ns(void);
-
-/* Lookup a process environment variable; returns NULL if unset. The returned
- * pointer aliases libc-owned storage and is valid until the next setenv/
- * putenv from any caller. */
-const char *driver_getenv(const char *name);
-
-/* Read all of stdin into a freshly-allocated buffer. On success returns 1
- * and stores the buffer/size in out_data/out_size; the caller frees via
- * driver_free(env, *out_data, *out_size). Returns 0 on read failure or
- * allocation failure. */
-int driver_read_stdin(DriverEnv *, uint8_t **out_data, size_t *out_size);
-
-/* Open a temporary file in $VISUAL, then $EDITOR, then vi. `suffix` should
- * include the leading dot when a language-specific extension is useful.
- * On success, returns the edited bytes in a freshly allocated buffer that the
- * caller frees with driver_free(env, *out_data, *out_size). */
-int driver_edit_temp(DriverEnv *, const char *suffix, const uint8_t *initial,
- size_t initial_size, uint8_t **out_data, size_t *out_size);
-
-/* Path-shaped input loader. Wraps env.file_io.read_all so each tool can
- * convert a list of paths to a list of CfreeSlice without re-implementing
- * load/release/error bookkeeping. `loaded` is set to 1 on success; release is
- * idempotent and does nothing when loaded is already 0. driver_load_bytes
- * fills `in.name = path` plus the loaded data/len; driver_release_bytes hands
- * the buffer back through file_io.release. On failure an error is emitted via
- * driver_errf using the supplied tool tag. */
-typedef struct DriverLoad {
- CfreeFileData fd;
- int loaded;
-} DriverLoad;
-
-int driver_load_bytes(const CfreeFileIO *, const char *tool, const char *path,
- DriverLoad *out, CfreeSlice *in);
-void driver_release_bytes(const CfreeFileIO *, DriverLoad *);
-
-/* Read one line from stdin into `buf` (cap >= 2). Strips the trailing
- * newline and NUL-terminates. Returns the line length on success, 0 at
- * EOF (with buf[0]='\0'), -1 on read error, or -2 when the read was
- * interrupted by a signal (caller should print a fresh prompt and
- * retry). Over-long lines are truncated to cap-1 bytes; the remainder
- * up to the next newline is consumed silently. */
-int driver_read_line(char *buf, size_t cap);
-
-/* Flush the host stdout. The dbg REPL prompt has no trailing newline, so
- * without an explicit flush the prompt stays buffered until the next
- * line of output. */
-void driver_flush_stdout(void);
-
-/* Install / restore a SIGINT handler. While installed, SIGINT runs `cb(user)`
- * synchronously (so `cb` must be async-signal safe). Used by `dbg` to
- * forward Ctrl-C into cfree_jit_session_interrupt while the worker is
- * running, and to restore SIG_DFL while sitting at the REPL prompt so
- * Ctrl-C terminates the program normally. Returns 0 on success. */
-int driver_install_sigint(void (*cb)(void *), void *user);
-void driver_restore_sigint(void);
-
-/* Host-symbol resolver for JIT extern_resolver. Looks up `name` via
- * dlsym(RTLD_DEFAULT, ...) on POSIX hosts, returning NULL on miss. Stateless;
- * `user` is ignored and may be NULL. Wired into `cfree run` so JITed code
- * can call libc symbols (printf, malloc, ...) without an explicit linker
- * step. */
-void *driver_dlsym_resolver(void *user, CfreeSlice name);
-
#endif
diff --git a/driver/env.c b/driver/env.c
@@ -36,6 +36,8 @@
#include <libkern/OSCacheControl.h>
#endif
+#include "env.h"
+
#include "driver.h"
/* Dual-mapping back-ends for strict W^X. Picks per-platform:
diff --git a/driver/env.h b/driver/env.h
@@ -0,0 +1,190 @@
+#ifndef CFREE_DRIVER_ENV_H
+#define CFREE_DRIVER_ENV_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cfree/compile.h>
+#include <cfree/core.h>
+#include <cfree/dbg.h>
+#include <cfree/jit.h>
+
+/* Shared host environment used by every tool that calls into libcfree.
+ * driver_env_init wires up the libc-backed heap, the stderr diag sink, and
+ * a POSIX file_io implementation (open/read/write on real paths). It is
+ * the single piece of glue that turns "the host" into a CfreeContext.
+ *
+ * The execmem / dbg_os / jit_tls vtables that used to live on CfreeEnv now
+ * live on CfreeJitHost / CfreeDbgHost. They are still constructed here so
+ * that one DriverEnv covers every libcfree-using tool, but they're handed
+ * to libcfree per-call via the appropriate host struct. */
+typedef struct DriverEnv {
+ CfreeHeap *heap;
+ CfreeDiagSink *diag;
+ CfreeFileIO file_io;
+ const CfreeExecMem *execmem;
+ const CfreeDbgOs *dbg_os; /* NULL unless `cfree dbg` paths run */
+ const CfreeJitTls *jit_tls; /* NULL unless `cfree run` w/ TLV paths run */
+ const CfreeMetrics *metrics; /* optional scoped metrics sink */
+ int64_t now; /* unix seconds; -1 = unknown */
+} DriverEnv;
+
+void driver_env_init(DriverEnv *);
+void driver_env_fini(DriverEnv *);
+
+/* Build a CfreeContext value pointing at the DriverEnv's heap/diag/file_io
+ * vtables. The returned value can be passed by const-pointer to any
+ * libcfree entry that takes `const CfreeContext *`. */
+CfreeContext driver_env_to_context(const DriverEnv *);
+
+/* Build a CfreeJitHost from the DriverEnv (execmem + tls). The returned
+ * struct holds borrowed pointers to vtables owned by g_execmem_posix /
+ * g_jit_tls_posix; callers must not outlive driver_env_fini. */
+CfreeJitHost driver_env_to_jit_host(const DriverEnv *);
+
+/* Build a CfreeDbgHost from the DriverEnv (dbg_os). */
+CfreeDbgHost driver_env_to_dbg_host(const DriverEnv *);
+
+/* Tells the stderr diag sink which compiler to use when resolving
+ * SrcLoc.file_id to a path. The driver_compiler_{new,free} helpers
+ * below already manage this; call this directly only when you hand a
+ * CfreeCompiler from outside those helpers (none today). */
+void driver_diag_set_compiler(CfreeCompiler *);
+
+/* Lifecycle helpers around cfree_compiler_{new,free}. Identical to the
+ * raw entries except they register the active compiler with the stderr
+ * diag sink so diagnostics resolve loc.file_id to its registered path.
+ * Returns CFREE_OK on success; on failure *out is NULL. */
+CfreeStatus driver_compiler_new(CfreeTarget, const CfreeContext *,
+ CfreeCompiler **out);
+void driver_compiler_free(CfreeCompiler *);
+
+/* Default target used by tools that don't expose a target-selection flag
+ * yet. v1: native-looking host target (chosen at compile time). */
+CfreeTarget driver_host_target(void);
+
+/* ----------------------------------------------------------------------
+ * Host-shim helpers
+ *
+ * driver/env.c is the only TU in the driver allowed to depend on libc
+ * facilities that issue syscalls or touch host state -- host stdio, malloc,
+ * POSIX I/O, environment, time. Other driver TUs are compiled freestanding
+ * against rt/include plus libcfree's public headers. The shims below exist
+ * primarily for the syscall-shaped surface; other TUs may also call them for
+ * consistency.
+ * ---------------------------------------------------------------------- */
+
+/* String predicates and lookups. driver_streq / driver_strneq return
+ * non-zero when the strings (or first n bytes) match -- sense-flipped
+ * from libc's strcmp so call sites read naturally. */
+int driver_streq(const char *a, const char *b);
+int driver_strneq(const char *a, const char *b, size_t n);
+size_t driver_strlen(const char *s);
+const char *driver_strchr(const char *s, int c);
+const char *driver_basename(const char *path);
+int driver_has_suffix(const char *s, const char *suffix);
+
+/* Memory. Allocations route through DriverEnv.heap; release returns the
+ * size used at allocation (so the heap implementation can track usage). */
+void *driver_alloc(DriverEnv *, size_t);
+void *driver_alloc_zeroed(DriverEnv *, size_t);
+void driver_free(DriverEnv *, void *p, size_t);
+void driver_memcpy(void *dst, const void *src, size_t n);
+
+/* Opens a Writer that writes to stdout. close frees the struct but does
+ * not close stdout. */
+CfreeWriter *driver_stdout_writer(DriverEnv *);
+
+/* Test whether `path` names an existing filesystem entry (any type).
+ * Returns nonzero on existence, zero otherwise. Used by library-path
+ * resolution; intentionally distinct from read_all so candidate-search
+ * loops don't slurp file contents on a hit. */
+int driver_path_exists(const char *path);
+
+/* Read a path's last modification time in nanoseconds since the Unix epoch.
+ * Returns 0 on success, nonzero on stat failure. */
+int driver_path_mtime_ns(const char *path, int64_t *out);
+
+/* Create a directory and any missing parents. Returns 0 on success. */
+int driver_mkdir_p(DriverEnv *, const char *path);
+
+/* Set a linked binary output's final mode according to the active umask.
+ * Returns 0 on success, nonzero on chmod failure. */
+int driver_mark_executable_output(const char *path);
+
+/* Diagnostic printing to host stderr. Format is `"<tool>: <fmt>\n"`. */
+void driver_errf(const char *tool, const char *fmt, ...);
+
+/* Raw hosted stderr log. Used by optional metrics so libcfree stays callback-
+ * only and freestanding. */
+void driver_logf(const char *fmt, ...);
+
+/* Formatted output to stdout. */
+void driver_printf(const char *fmt, ...);
+
+/* Monotonic host time in nanoseconds, or 0 if unavailable. */
+uint64_t driver_now_ns(void);
+
+/* Lookup a process environment variable; returns NULL if unset. The returned
+ * pointer aliases libc-owned storage and is valid until the next setenv/
+ * putenv from any caller. */
+const char *driver_getenv(const char *name);
+
+/* Read all of stdin into a freshly-allocated buffer. On success returns 1
+ * and stores the buffer/size in out_data/out_size; the caller frees via
+ * driver_free(env, *out_data, *out_size). Returns 0 on read failure or
+ * allocation failure. */
+int driver_read_stdin(DriverEnv *, uint8_t **out_data, size_t *out_size);
+
+/* Open a temporary file in $VISUAL, then $EDITOR, then vi. `suffix` should
+ * include the leading dot when a language-specific extension is useful.
+ * On success, returns the edited bytes in a freshly allocated buffer that the
+ * caller frees with driver_free(env, *out_data, *out_size). */
+int driver_edit_temp(DriverEnv *, const char *suffix, const uint8_t *initial,
+ size_t initial_size, uint8_t **out_data, size_t *out_size);
+
+/* Path-shaped input loader. Wraps env.file_io.read_all so each tool can
+ * convert a list of paths to a list of CfreeSlice without re-implementing
+ * load/release/error bookkeeping. `loaded` is set to 1 on success; release is
+ * idempotent and does nothing when loaded is already 0. driver_load_bytes
+ * fills `in.name = path` plus the loaded data/len; driver_release_bytes hands
+ * the buffer back through file_io.release. On failure an error is emitted via
+ * driver_errf using the supplied tool tag. */
+typedef struct DriverLoad {
+ CfreeFileData fd;
+ int loaded;
+} DriverLoad;
+
+int driver_load_bytes(const CfreeFileIO *, const char *tool, const char *path,
+ DriverLoad *out, CfreeSlice *in);
+void driver_release_bytes(const CfreeFileIO *, DriverLoad *);
+
+/* Read one line from stdin into `buf` (cap >= 2). Strips the trailing
+ * newline and NUL-terminates. Returns the line length on success, 0 at
+ * EOF (with buf[0]='\0'), -1 on read error, or -2 when the read was
+ * interrupted by a signal (caller should print a fresh prompt and
+ * retry). Over-long lines are truncated to cap-1 bytes; the remainder
+ * up to the next newline is consumed silently. */
+int driver_read_line(char *buf, size_t cap);
+
+/* Flush the host stdout. The dbg REPL prompt has no trailing newline, so
+ * without an explicit flush the prompt stays buffered until the next
+ * line of output. */
+void driver_flush_stdout(void);
+
+/* Install / restore a SIGINT handler. While installed, SIGINT runs `cb(user)`
+ * synchronously (so `cb` must be async-signal safe). Used by `dbg` to
+ * forward Ctrl-C into cfree_jit_session_interrupt while the worker is
+ * running, and to restore SIG_DFL while sitting at the REPL prompt so
+ * Ctrl-C terminates the program normally. Returns 0 on success. */
+int driver_install_sigint(void (*cb)(void *), void *user);
+void driver_restore_sigint(void);
+
+/* Host-symbol resolver for JIT extern_resolver. Looks up `name` via
+ * dlsym(RTLD_DEFAULT, ...) on POSIX hosts, returning NULL on miss. Stateless;
+ * `user` is ignored and may be NULL. Wired into `cfree run` so JITed code
+ * can call libc symbols (printf, malloc, ...) without an explicit linker
+ * step. */
+void *driver_dlsym_resolver(void *user, CfreeSlice name);
+
+#endif
diff --git a/test/lib_deps.allowlist b/test/lib_deps.allowlist
@@ -1,11 +1,5 @@
-___memcpy_chk
-___memset_chk
-___snprintf_chk
___stack_chk_fail
___stack_chk_guard
-_bzero
-_fma
-_fmaf
_longjmp
_memcmp
_memcpy
@@ -13,9 +7,7 @@ _memmove
_memset
_qsort
_setjmp
-_sqrt
+_snprintf
_strcmp
_strlen
-_strncmp
-_strstr
_strtod
diff --git a/test/test.mk b/test/test.mk
@@ -68,7 +68,7 @@ test-ar: $(AR_TEST_BIN)
$(AR_TEST_BIN): test/ar_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) test/ar_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) test/ar_test.c $(LIB_AR) -o $@
test-ar-driver: bin
@CFREE=$(abspath $(BIN)) test/ar/run.sh
@@ -93,7 +93,7 @@ test-dwarf: $(DWARF_TEST_BIN)
$(DWARF_TEST_BIN): test/dwarf/dwarf_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/dwarf/dwarf_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/dwarf/dwarf_test.c $(LIB_AR) -o $@
# DWARF producer self-roundtrip unit test. Drives Debug directly, calls
# debug_emit, asserts the produced sections have valid DWARF 5 structure
@@ -107,7 +107,7 @@ test-debug: $(DEBUG_TEST_BIN)
$(DEBUG_TEST_BIN): test/debug/roundtrip_unit.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/debug/roundtrip_unit.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/debug/roundtrip_unit.c $(LIB_AR) -o $@
# aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
# every AA64Format the table maps and the alias-precedence invariant
@@ -122,11 +122,11 @@ test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN)
$(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_AR) -o $@
$(RV64_DECODE_TEST_BIN): test/arch/rv64_decode_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/rv64_decode_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_decode_test.c $(LIB_AR) -o $@
# test-emu: emulator unit tests. The rv64 lane builds a tiny in-memory rv64
# ELF and asserts the lifted/JIT path exits through the syscall handler with
@@ -138,7 +138,7 @@ test-emu: $(EMU_RV64_TEST_BIN)
$(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
CG_API_TEST_BIN = build/test/cg_api_test
CG_SWITCH_TEST_BIN = build/test/cg_switch_test
@@ -151,15 +151,15 @@ test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(ABI_CLASSIFY_TEST_BIN)
$(CG_API_TEST_BIN): test/api/cg_type_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/api/cg_type_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/api/cg_type_test.c $(LIB_AR) -o $@
$(CG_SWITCH_TEST_BIN): test/api/cg_switch_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/api/cg_switch_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/api/cg_switch_test.c $(LIB_AR) -o $@
$(ABI_CLASSIFY_TEST_BIN): test/api/abi_classify_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/api/abi_classify_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/api/abi_classify_test.c $(LIB_AR) -o $@
test-toy: bin
@CFREE=$(abspath $(BIN)) test/toy/run.sh
@@ -176,7 +176,7 @@ test-aa64-inline: $(AA64_INLINE_TEST_BIN)
$(AA64_INLINE_TEST_BIN): test/arch/aa64_inline_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/aa64_inline_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_inline_test.c $(LIB_AR) -o $@
# rv64 inline-asm backend unit test — parallel to test-aa64-inline.
# Drives rv_asm_block directly with hand-rolled Operand arrays and
@@ -188,7 +188,7 @@ test-rv64-inline: $(RV64_INLINE_TEST_BIN)
$(RV64_INLINE_TEST_BIN): test/arch/rv64_inline_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/rv64_inline_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_inline_test.c $(LIB_AR) -o $@
# rv64 JIT smoke test. Builds a tiny rv64 ELF .o in memory, runs it
# through cfree_link_session in JIT-output mode, and skips native execution
@@ -206,7 +206,7 @@ test-rv64-jit: $(RV64_JIT_TEST_BIN)
$(RV64_JIT_TEST_BIN): test/link/rv64_jit_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) test/link/rv64_jit_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) test/link/rv64_jit_test.c $(LIB_AR) -o $@
# x86_64 peer of test-aa64-inline (doc/INLINEASM.md). Drives x_asm_block
# (CGTarget vtable) directly with hand-rolled Operand arrays and asserts
@@ -219,7 +219,7 @@ test-x64-inline: $(X64_INLINE_TEST_BIN)
$(X64_INLINE_TEST_BIN): test/arch/x64_inline_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/x64_inline_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/x64_inline_test.c $(LIB_AR) -o $@
X64_DBG_TEST_BIN = build/test/x64_dbg_test
@@ -228,7 +228,7 @@ test-x64-dbg: $(X64_DBG_TEST_BIN)
$(X64_DBG_TEST_BIN): test/arch/x64_dbg_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/arch/x64_dbg_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/x64_dbg_test.c $(LIB_AR) -o $@
RT_HEADER_TEST_TARGETS = \
aarch64-linux-gnu \
@@ -367,7 +367,7 @@ test-opt: bin $(OPT_TEST_BIN)
$(OPT_TEST_BIN): test/opt/opt_test.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(DRIVER_CFLAGS) -Isrc test/opt/opt_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/opt/opt_test.c $(LIB_AR) -o $@
test-parse: lib rt $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
bash test/parse/run.sh