commit d44c3a68cca85703d06d240afa54156da80e96c4
parent bf74553de4e30da15763daf4a86a55e8cf88d69c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 7 May 2026 22:50:17 -0700
driver wired to cfree_run; libc isolated to env.c
Diffstat:
| M | driver/cc.c | | | 227 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | driver/driver.h | | | 48 | ++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | driver/env.c | | | 323 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | driver/ld.c | | | 127 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | driver/main.c | | | 26 | ++++++++------------------ |
| M | include/cfree.h | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
| M | src/core/core.h | | | 8 | +++----- |
| M | src/core/diag.h | | | 26 | ++++++++++---------------- |
| M | src/core/heap.h | | | 18 | +++++++++--------- |
9 files changed, 803 insertions(+), 81 deletions(-)
diff --git a/driver/cc.c b/driver/cc.c
@@ -1,11 +1,226 @@
#include "driver.h"
-#include <cfree.h>
+
+#include <stdint.h>
+
+/* `cfree cc` — compile C sources. With -c produces a single object;
+ * without -c compiles all sources and links to an executable. The flag
+ * surface is intentionally a basic subset of the GCC convention. */
+
+#define CC_TOOL "cc"
+
+typedef struct CcOptions {
+ DriverEnv* env; /* heap for scratch arrays */
+ size_t argv_bound; /* upper bound on list size */
+
+ int compile_only; /* -c */
+ int opt_level; /* -O0/-O1/-O2 (default 0) */
+ int debug_info; /* -g */
+ const char* output_path; /* -o */
+ const char** include_dirs; /* -I */
+ uint32_t ninclude_dirs;
+ const char** system_include_dirs; /* -isystem */
+ uint32_t nsystem_include_dirs;
+ CfreeDefine* defines; /* -D name[=body] */
+ uint32_t ndefines;
+ const char** undefines; /* -U */
+ uint32_t nundefines;
+ const char** sources; /* positional */
+ uint32_t nsources;
+ /* Owning storage for split -D names. Indexed in parallel with
+ * defines[]; entries are NULL when the body was unspecified or argv
+ * already provides the standalone name. The (name, length+1) pair is
+ * needed at release time to free through the heap. */
+ char** owned_define_names;
+ size_t* owned_define_name_sizes;
+} CcOptions;
+
+static void cc_usage(void)
+{
+ driver_errf(CC_TOOL, "%s",
+ "usage: cfree cc [-c] [-o out] [-O0|-O1|-O2] [-g]\n"
+ " [-I dir]... [-isystem dir]...\n"
+ " [-D name[=body]]... [-U name]...\n"
+ " input.c...");
+}
+
+static int cc_alloc_arrays(CcOptions* o, int argc)
+{
+ size_t bound = (size_t)argc;
+ o->argv_bound = bound;
+ o->include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->include_dirs));
+ o->system_include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->system_include_dirs));
+ o->defines = driver_alloc_zeroed(o->env, bound * sizeof(*o->defines));
+ o->owned_define_names = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_names));
+ o->owned_define_name_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_name_sizes));
+ o->undefines = driver_alloc_zeroed(o->env, bound * sizeof(*o->undefines));
+ o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources));
+ if (!o->include_dirs || !o->system_include_dirs || !o->defines ||
+ !o->owned_define_names || !o->owned_define_name_sizes ||
+ !o->undefines || !o->sources) {
+ driver_errf(CC_TOOL, "out of memory");
+ return 1;
+ }
+ return 0;
+}
+
+static int cc_record_define(CcOptions* o, const char* arg)
+{
+ const char* eq = driver_strchr(arg, '=');
+ CfreeDefine* d = &o->defines[o->ndefines];
+ if (eq) {
+ size_t n = (size_t)(eq - arg);
+ size_t bytes = n + 1;
+ char* name = driver_alloc(o->env, bytes);
+ if (!name) { driver_errf(CC_TOOL, "out of memory"); return 1; }
+ driver_memcpy(name, arg, n);
+ name[n] = '\0';
+ o->owned_define_names[o->ndefines] = name;
+ o->owned_define_name_sizes[o->ndefines] = bytes;
+ d->name = name;
+ d->body = eq + 1;
+ } else {
+ d->name = arg;
+ d->body = NULL;
+ }
+ o->ndefines++;
+ return 0;
+}
+
+/* Returns 0 on success; non-zero on bad args (already reported). */
+static int cc_parse(int argc, char** argv, CcOptions* o)
+{
+ int i;
+ if (cc_alloc_arrays(o, argc) != 0) return 1;
+
+ for (i = 1; i < argc; ++i) {
+ const char* a = argv[i];
+ if (driver_streq(a, "-c")) { o->compile_only = 1; continue; }
+ if (driver_streq(a, "-g")) { o->debug_info = 1; continue; }
+ if (driver_streq(a, "-O0")) { o->opt_level = 0; continue; }
+ if (driver_streq(a, "-O1")) { o->opt_level = 1; continue; }
+ if (driver_streq(a, "-O2")) { o->opt_level = 2; continue; }
+
+ if (driver_streq(a, "-o")) {
+ if (++i >= argc) { driver_errf(CC_TOOL, "-o requires an argument"); return 1; }
+ o->output_path = argv[i];
+ continue;
+ }
+
+ if (driver_strneq(a, "-I", 2)) {
+ const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!dir) { driver_errf(CC_TOOL, "-I requires an argument"); return 1; }
+ o->include_dirs[o->ninclude_dirs++] = dir;
+ continue;
+ }
+
+ if (driver_streq(a, "-isystem")) {
+ if (++i >= argc) { driver_errf(CC_TOOL, "-isystem requires an argument"); return 1; }
+ o->system_include_dirs[o->nsystem_include_dirs++] = argv[i];
+ continue;
+ }
+
+ if (driver_strneq(a, "-D", 2)) {
+ const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!arg) { driver_errf(CC_TOOL, "-D requires an argument"); return 1; }
+ if (cc_record_define(o, arg) != 0) return 1;
+ continue;
+ }
+
+ if (driver_strneq(a, "-U", 2)) {
+ const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL);
+ if (!arg) { driver_errf(CC_TOOL, "-U requires an argument"); return 1; }
+ o->undefines[o->nundefines++] = arg;
+ continue;
+ }
+
+ if (a[0] == '-' && a[1] != '\0') {
+ driver_errf(CC_TOOL, "unknown flag: %s", a);
+ return 1;
+ }
+
+ o->sources[o->nsources++] = a;
+ }
+
+ if (o->nsources == 0) {
+ driver_errf(CC_TOOL, "no input files");
+ cc_usage();
+ return 1;
+ }
+ if (o->compile_only && o->nsources != 1) {
+ driver_errf(CC_TOOL, "-c requires exactly one input");
+ return 1;
+ }
+ if (!o->output_path) {
+ driver_errf(CC_TOOL, "-o is required");
+ return 1;
+ }
+ return 0;
+}
+
+static void cc_options_release(CcOptions* o)
+{
+ uint32_t i;
+ size_t bound = o->argv_bound;
+ for (i = 0; i < o->ndefines; ++i) {
+ if (o->owned_define_names[i]) {
+ driver_free(o->env, o->owned_define_names[i],
+ o->owned_define_name_sizes[i]);
+ }
+ }
+ driver_free(o->env, o->include_dirs, bound * sizeof(*o->include_dirs));
+ driver_free(o->env, o->system_include_dirs, bound * sizeof(*o->system_include_dirs));
+ driver_free(o->env, o->defines, bound * sizeof(*o->defines));
+ driver_free(o->env, o->owned_define_names, bound * sizeof(*o->owned_define_names));
+ driver_free(o->env, o->owned_define_name_sizes, bound * sizeof(*o->owned_define_name_sizes));
+ driver_free(o->env, o->undefines, bound * sizeof(*o->undefines));
+ driver_free(o->env, o->sources, bound * sizeof(*o->sources));
+}
+
+static void cc_to_cfree(const CcOptions* o, CfreeOptions* out)
+{
+ /* Zero-initialize via a local literal — driver/env.c owns memset. */
+ CfreeOptions z = {0};
+ *out = z;
+ out->target = driver_host_target();
+ out->env = driver_env_to_cfree(o->env);
+ out->output_kind = o->compile_only ? CFREE_OUTPUT_OBJ : CFREE_OUTPUT_EXE;
+ out->opt_level = o->opt_level;
+ out->debug_info = o->debug_info;
+ out->output_path = o->output_path;
+
+ out->source_files = o->sources;
+ out->nsource_files = o->nsources;
+
+ out->pp.include_dirs = o->include_dirs;
+ out->pp.ninclude_dirs = o->ninclude_dirs;
+ out->pp.system_include_dirs = o->system_include_dirs;
+ out->pp.nsystem_include_dirs = o->nsystem_include_dirs;
+ out->pp.defines = o->defines;
+ out->pp.ndefines = o->ndefines;
+ out->pp.undefines = o->undefines;
+ out->pp.nundefines = o->nundefines;
+}
int driver_cc(int argc, char** argv)
{
- (void)argc;
- (void)argv;
- /* TODO: argv parsing, env setup (heap, file_io, diag), build CfreeOptions,
- * call cfree_run for OBJ output. */
- return 1;
+ DriverEnv env;
+ CcOptions co = {0};
+ CfreeOptions copts;
+ int rc;
+
+ driver_env_init(&env);
+ co.env = &env;
+
+ if (cc_parse(argc, argv, &co) != 0) {
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return 2;
+ }
+
+ cc_to_cfree(&co, &copts);
+ rc = cfree_run(&copts);
+
+ cc_options_release(&co);
+ driver_env_fini(&env);
+ return rc;
}
diff --git a/driver/driver.h b/driver/driver.h
@@ -1,6 +1,8 @@
#ifndef CFREE_DRIVER_H
#define CFREE_DRIVER_H
+#include <cfree.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
@@ -28,4 +30,50 @@ int driver_ar (int argc, char** argv);
int driver_objdump(int argc, char** argv);
int driver_dbg (int argc, char** argv);
+/* 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 CfreeEnv. */
+typedef struct DriverEnv {
+ CfreeHeap* heap;
+ CfreeDiagSink* diag;
+ CfreeFileIO file_io;
+} DriverEnv;
+
+void driver_env_init(DriverEnv*);
+void driver_env_fini(DriverEnv*);
+CfreeEnv driver_env_to_cfree(const DriverEnv*);
+
+/* 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 /
+ * POSIX directly. Everything else (main.c, cc.c, ld.c, ...) uses only
+ * the helpers below so the libc surface stays in one place.
+ * ---------------------------------------------------------------------- */
+
+/* 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);
+
+/* Diagnostic printing to host stderr. Format is `"<tool>: <fmt>\n"`. */
+void driver_errf(const char* tool, const char* fmt, ...);
+
#endif
diff --git a/driver/env.c b/driver/env.c
@@ -0,0 +1,323 @@
+#include "driver.h"
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Host-side implementations of the three vtable interfaces libcfree needs:
+ *
+ * - DriverHeapLibc — malloc/realloc/free
+ * - DriverDiagStderr — vfprintf to stderr, with kind labels
+ * - DriverFdWriter — write/lseek/close on a POSIX fd
+ *
+ * Plus the POSIX CfreeFileIO that opens paths and hands the resulting fd
+ * to a freshly-allocated DriverFdWriter. None of this lives in libcfree:
+ * the core never depends on stdio, malloc, or POSIX I/O directly. */
+
+/* ---------------- heap (libc-backed) ---------------- */
+
+static void* heap_libc_alloc(CfreeHeap* h, size_t size, size_t align)
+{
+ (void)h; (void)align; /* malloc satisfies all max_align_t alignments */
+ return size ? malloc(size) : NULL;
+}
+
+static void* heap_libc_realloc(CfreeHeap* h, void* p,
+ size_t old_size, size_t new_size, size_t align)
+{
+ (void)h; (void)old_size; (void)align;
+ return realloc(p, new_size);
+}
+
+static void heap_libc_free(CfreeHeap* h, void* p, size_t size)
+{
+ (void)h; (void)size;
+ free(p);
+}
+
+static CfreeHeap g_heap_libc = {
+ heap_libc_alloc, heap_libc_realloc, heap_libc_free, NULL,
+};
+
+/* ---------------- diag sink (stderr) ---------------- */
+
+static const char* diag_label(CfreeDiagKind k)
+{
+ switch (k) {
+ case CFREE_DIAG_NOTE: return "note";
+ case CFREE_DIAG_WARN: return "warning";
+ case CFREE_DIAG_ERROR: return "error";
+ case CFREE_DIAG_FATAL: return "fatal";
+ }
+ return "diag";
+}
+
+static void diag_stderr_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
+ const char* fmt, va_list ap)
+{
+ (void)s;
+ if (loc.file_id || loc.line) {
+ fprintf(stderr, "<file:%u>:%u:%u: %s: ",
+ loc.file_id, loc.line, loc.col, diag_label(k));
+ } else {
+ fprintf(stderr, "%s: ", diag_label(k));
+ }
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+
+static CfreeDiagSink g_diag_stderr = {
+ diag_stderr_emit, NULL, 0, 0,
+};
+
+/* ---------------- writer (fd-backed) ---------------- */
+
+typedef struct DriverFdWriter {
+ CfreeWriter base; /* must be first; libcfree reads via this */
+ CfreeHeap* heap;
+ int fd;
+ int err;
+ uint64_t pos;
+} DriverFdWriter;
+
+static void fdw_write(CfreeWriter* w, const void* data, size_t n)
+{
+ DriverFdWriter* fw = (DriverFdWriter*)w;
+ const unsigned char* p = (const unsigned char*)data;
+ if (fw->err) return;
+ while (n > 0) {
+ ssize_t k = write(fw->fd, p, n);
+ if (k < 0) { fw->err = 1; return; }
+ p += (size_t)k;
+ n -= (size_t)k;
+ fw->pos += (uint64_t)k;
+ }
+}
+
+static void fdw_seek(CfreeWriter* w, uint64_t off)
+{
+ DriverFdWriter* fw = (DriverFdWriter*)w;
+ if (fw->err) return;
+ if (lseek(fw->fd, (off_t)off, SEEK_SET) < 0) { fw->err = 1; return; }
+ fw->pos = off;
+}
+
+static uint64_t fdw_tell (CfreeWriter* w) { return ((DriverFdWriter*)w)->pos; }
+static int fdw_error(CfreeWriter* w) { return ((DriverFdWriter*)w)->err; }
+
+static void fdw_close(CfreeWriter* w)
+{
+ DriverFdWriter* fw = (DriverFdWriter*)w;
+ if (fw->fd >= 0) close(fw->fd);
+ fw->heap->free(fw->heap, fw, sizeof(*fw));
+}
+
+static CfreeWriter* driver_writer_fd(CfreeHeap* h, int fd)
+{
+ DriverFdWriter* fw = (DriverFdWriter*)h->alloc(h, sizeof(*fw), _Alignof(DriverFdWriter));
+ if (!fw) return NULL;
+ fw->base.write = fdw_write;
+ fw->base.seek = fdw_seek;
+ fw->base.tell = fdw_tell;
+ fw->base.error = fdw_error;
+ fw->base.close = fdw_close;
+ fw->heap = h;
+ fw->fd = fd;
+ fw->err = 0;
+ fw->pos = 0;
+ return &fw->base;
+}
+
+/* ---------------- file_io (POSIX) ---------------- */
+
+static int posix_read_all(void* user, const char* path, CfreeFileData* out)
+{
+ DriverEnv* env = (DriverEnv*)user;
+ int fd;
+ struct stat sb;
+ size_t size;
+ size_t got;
+ void* buf;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) return 0;
+ if (fstat(fd, &sb) < 0) { close(fd); return 0; }
+ size = (size_t)sb.st_size;
+ buf = size ? env->heap->alloc(env->heap, size, 1) : NULL;
+ if (size && !buf) { close(fd); return 0; }
+
+ got = 0;
+ while (got < size) {
+ ssize_t n = read(fd, (unsigned char*)buf + got, size - got);
+ if (n <= 0) {
+ env->heap->free(env->heap, buf, size);
+ close(fd);
+ return 0;
+ }
+ got += (size_t)n;
+ }
+ close(fd);
+
+ out->data = (const uint8_t*)buf;
+ out->size = size;
+ out->token = buf;
+ return 1;
+}
+
+static void posix_release(void* user, CfreeFileData* d)
+{
+ DriverEnv* env = (DriverEnv*)user;
+ if (d->token) env->heap->free(env->heap, d->token, d->size);
+ d->data = NULL; d->size = 0; d->token = NULL;
+}
+
+static CfreeWriter* posix_open_writer(void* user, const char* path)
+{
+ DriverEnv* env = (DriverEnv*)user;
+ int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0) return NULL;
+ return driver_writer_fd(env->heap, fd);
+}
+
+/* ---------------- env wiring ---------------- */
+
+void driver_env_init(DriverEnv* e)
+{
+ e->heap = &g_heap_libc;
+ e->diag = &g_diag_stderr;
+ e->file_io.read_all = posix_read_all;
+ e->file_io.release = posix_release;
+ e->file_io.open_writer = posix_open_writer;
+ e->file_io.user = e;
+}
+
+void driver_env_fini(DriverEnv* e)
+{
+ /* Singletons; nothing to release. */
+ (void)e;
+}
+
+CfreeEnv driver_env_to_cfree(const DriverEnv* e)
+{
+ CfreeEnv ce;
+ ce.heap = e->heap;
+ ce.file_io = &e->file_io;
+ ce.diag = e->diag;
+ return ce;
+}
+
+/* ---------------- host-shim helpers ---------------- */
+
+int driver_streq(const char* a, const char* b)
+{
+ return strcmp(a, b) == 0;
+}
+
+int driver_strneq(const char* a, const char* b, size_t n)
+{
+ return strncmp(a, b, n) == 0;
+}
+
+size_t driver_strlen(const char* s)
+{
+ return strlen(s);
+}
+
+const char* driver_strchr(const char* s, int c)
+{
+ return strchr(s, c);
+}
+
+const char* driver_basename(const char* path)
+{
+ const char* slash = strrchr(path, '/');
+ return slash ? slash + 1 : path;
+}
+
+int driver_has_suffix(const char* s, const char* suffix)
+{
+ size_t ls = strlen(s);
+ size_t lf = strlen(suffix);
+ return ls >= lf && strcmp(s + ls - lf, suffix) == 0;
+}
+
+void* driver_alloc(DriverEnv* e, size_t n)
+{
+ return e->heap->alloc(e->heap, n, _Alignof(max_align_t));
+}
+
+void* driver_alloc_zeroed(DriverEnv* e, size_t n)
+{
+ void* p = driver_alloc(e, n);
+ if (p) memset(p, 0, n);
+ return p;
+}
+
+void driver_free(DriverEnv* e, void* p, size_t n)
+{
+ if (p) e->heap->free(e->heap, p, n);
+}
+
+void driver_memcpy(void* dst, const void* src, size_t n)
+{
+ memcpy(dst, src, n);
+}
+
+void driver_errf(const char* tool, const char* fmt, ...)
+{
+ va_list ap;
+ fprintf(stderr, "%s: ", tool);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+CfreeTarget driver_host_target(void)
+{
+ CfreeTarget t;
+#if defined(__x86_64__)
+ t.arch = CFREE_ARCH_X86_64;
+#elif defined(__aarch64__)
+ t.arch = CFREE_ARCH_ARM_64;
+#elif defined(__arm__)
+ t.arch = CFREE_ARCH_ARM_32;
+#elif defined(__i386__)
+ t.arch = CFREE_ARCH_X86_32;
+#elif defined(__riscv) && (__riscv_xlen == 64)
+ t.arch = CFREE_ARCH_RV64;
+#elif defined(__riscv) && (__riscv_xlen == 32)
+ t.arch = CFREE_ARCH_RV32;
+#elif defined(__wasm__)
+ t.arch = CFREE_ARCH_WASM;
+#else
+ t.arch = CFREE_ARCH_X86_64;
+#endif
+
+#if defined(__APPLE__)
+ t.os = CFREE_OS_MACOS;
+ t.obj = CFREE_OBJ_MACHO;
+#elif defined(__linux__)
+ t.os = CFREE_OS_LINUX;
+ t.obj = CFREE_OBJ_ELF;
+#elif defined(_WIN32)
+ t.os = CFREE_OS_WINDOWS;
+ t.obj = CFREE_OBJ_COFF;
+#elif defined(__wasi__)
+ t.os = CFREE_OS_WASI;
+ t.obj = CFREE_OBJ_WASM;
+#else
+ t.os = CFREE_OS_FREESTANDING;
+ t.obj = CFREE_OBJ_ELF;
+#endif
+
+ t.ptr_size = (uint8_t)sizeof(void*);
+ t.ptr_align = (uint8_t)sizeof(void*);
+ t.big_endian = 0;
+ return t;
+}
diff --git a/driver/ld.c b/driver/ld.c
@@ -1,10 +1,127 @@
#include "driver.h"
-#include <cfree.h>
+
+#include <stdint.h>
+
+/* `cfree ld` — link object/archive paths into an executable. The flag set
+ * is intentionally minimal: -o, -e, and positional inputs separated into
+ * .a archives vs everything-else (treated as object files). Library
+ * resolution (-l/-L) is deferred. */
+
+#define LD_TOOL "ld"
+
+typedef struct LdOptions {
+ DriverEnv* env;
+ size_t argv_bound;
+
+ const char* output_path; /* -o */
+ const char* entry; /* -e */
+ const char** object_files; /* positional, not ending .a */
+ uint32_t nobject_files;
+ const char** archives; /* positional, ending .a */
+ uint32_t narchives;
+} LdOptions;
+
+static void ld_usage(void)
+{
+ driver_errf(LD_TOOL, "%s",
+ "usage: cfree ld -o out [-e entry] input.o|input.a...");
+}
+
+static int ld_alloc_arrays(LdOptions* o, int argc)
+{
+ size_t bound = (size_t)argc;
+ o->argv_bound = bound;
+ o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files));
+ o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives));
+ if (!o->object_files || !o->archives) {
+ driver_errf(LD_TOOL, "out of memory");
+ return 1;
+ }
+ return 0;
+}
+
+static int ld_parse(int argc, char** argv, LdOptions* o)
+{
+ int i;
+ if (ld_alloc_arrays(o, argc) != 0) return 1;
+
+ for (i = 1; i < argc; ++i) {
+ const char* a = argv[i];
+ if (driver_streq(a, "-o")) {
+ if (++i >= argc) { driver_errf(LD_TOOL, "-o requires an argument"); return 1; }
+ o->output_path = argv[i];
+ continue;
+ }
+ if (driver_streq(a, "-e")) {
+ if (++i >= argc) { driver_errf(LD_TOOL, "-e requires an argument"); return 1; }
+ o->entry = argv[i];
+ continue;
+ }
+ if (a[0] == '-' && a[1] != '\0') {
+ driver_errf(LD_TOOL, "unknown flag: %s", a);
+ return 1;
+ }
+ if (driver_has_suffix(a, ".a")) {
+ o->archives[o->narchives++] = a;
+ } else {
+ o->object_files[o->nobject_files++] = a;
+ }
+ }
+
+ if (!o->output_path) {
+ driver_errf(LD_TOOL, "-o is required");
+ return 1;
+ }
+ if (o->nobject_files == 0 && o->narchives == 0) {
+ driver_errf(LD_TOOL, "no input files");
+ ld_usage();
+ return 1;
+ }
+ return 0;
+}
+
+static void ld_options_release(LdOptions* o)
+{
+ size_t bound = o->argv_bound;
+ driver_free(o->env, o->object_files, bound * sizeof(*o->object_files));
+ driver_free(o->env, o->archives, bound * sizeof(*o->archives));
+}
+
+static void ld_to_cfree(const LdOptions* o, CfreeOptions* out)
+{
+ CfreeOptions z = {0};
+ *out = z;
+ out->target = driver_host_target();
+ out->env = driver_env_to_cfree(o->env);
+ out->output_kind = CFREE_OUTPUT_EXE;
+ out->output_path = o->output_path;
+ out->entry = o->entry;
+ out->object_files = o->object_files;
+ out->nobject_files = o->nobject_files;
+ out->archives = o->archives;
+ out->narchives = o->narchives;
+}
int driver_ld(int argc, char** argv)
{
- (void)argc;
- (void)argv;
- /* TODO: argv parsing for -o, -l/-L, scripts; cfree_link_exe via cfree_run. */
- return 1;
+ DriverEnv env;
+ LdOptions lo = {0};
+ CfreeOptions copts;
+ int rc;
+
+ driver_env_init(&env);
+ lo.env = &env;
+
+ if (ld_parse(argc, argv, &lo) != 0) {
+ ld_options_release(&lo);
+ driver_env_fini(&env);
+ return 2;
+ }
+
+ ld_to_cfree(&lo, &copts);
+ rc = cfree_run(&copts);
+
+ ld_options_release(&lo);
+ driver_env_fini(&env);
+ return rc;
}
diff --git a/driver/main.c b/driver/main.c
@@ -1,28 +1,18 @@
#include "driver.h"
-#include <cfree.h>
-
-#include <string.h>
-
/* Multi-call dispatch. Looks at argv[0]'s basename for "cc", "cpp", "as",
* "ld", "ar", "objdump", or "dbg"; if argv[0] is the bare "cfree" binary,
* falls back to argv[1]. */
-static const char* basename_of(const char* path)
-{
- const char* slash = strrchr(path, '/');
- return slash ? slash + 1 : path;
-}
-
static int dispatch(const char* name, int argc, char** argv)
{
- if (!strcmp(name, "cc")) return driver_cc (argc, argv);
- if (!strcmp(name, "cpp")) return driver_cpp (argc, argv);
- if (!strcmp(name, "as")) return driver_as (argc, argv);
- if (!strcmp(name, "ld")) return driver_ld (argc, argv);
- if (!strcmp(name, "ar")) return driver_ar (argc, argv);
- if (!strcmp(name, "objdump")) return driver_objdump(argc, argv);
- if (!strcmp(name, "dbg")) return driver_dbg (argc, argv);
+ if (driver_streq(name, "cc")) return driver_cc (argc, argv);
+ if (driver_streq(name, "cpp")) return driver_cpp (argc, argv);
+ if (driver_streq(name, "as")) return driver_as (argc, argv);
+ if (driver_streq(name, "ld")) return driver_ld (argc, argv);
+ if (driver_streq(name, "ar")) return driver_ar (argc, argv);
+ if (driver_streq(name, "objdump")) return driver_objdump(argc, argv);
+ if (driver_streq(name, "dbg")) return driver_dbg (argc, argv);
return -1;
}
@@ -32,7 +22,7 @@ int driver_main(int argc, char** argv)
int rc;
if (argc < 1) return 2;
- name = basename_of(argv[0]);
+ name = driver_basename(argv[0]);
rc = dispatch(name, argc, argv);
if (rc != -1) return rc;
diff --git a/include/cfree.h b/include/cfree.h
@@ -7,6 +7,7 @@
*
* Every public identifier starts with cfree_, Cfree, or CFREE_. */
+#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
@@ -14,14 +15,11 @@
* Opaque handles
* ============================================================ */
typedef struct CfreeCompiler CfreeCompiler;
-typedef struct CfreeHeap CfreeHeap;
-typedef struct CfreeDiagSink CfreeDiagSink;
-typedef struct CfreeWriter CfreeWriter;
typedef struct CfreeObjBuilder CfreeObjBuilder;
typedef struct CfreeJit CfreeJit;
/* ============================================================
- * Source locations (carried in a small subset of public callbacks)
+ * Source locations (carried in diagnostics)
* ============================================================ */
typedef struct CfreeSrcLoc {
uint32_t file_id;
@@ -30,6 +28,51 @@ typedef struct CfreeSrcLoc {
} CfreeSrcLoc;
/* ============================================================
+ * Host-implemented interfaces (vtables)
+ * ============================================================
+ * Heap, DiagSink, and Writer are implemented *outside* libcfree, by the
+ * host. This keeps libcfree free of stdio, malloc, and POSIX I/O — the
+ * host provides them. Subclass by placing the struct as the first field
+ * of an enclosing type and casting; libcfree calls the function pointers
+ * with the base pointer. */
+
+typedef struct CfreeHeap CfreeHeap;
+struct CfreeHeap {
+ void* (*alloc) (CfreeHeap*, size_t size, size_t align);
+ void* (*realloc)(CfreeHeap*, void* p, size_t old_size, size_t new_size, size_t align);
+ void (*free) (CfreeHeap*, void* p, size_t size);
+ void* user;
+};
+
+typedef enum CfreeDiagKind {
+ CFREE_DIAG_NOTE,
+ CFREE_DIAG_WARN,
+ CFREE_DIAG_ERROR,
+ CFREE_DIAG_FATAL,
+} CfreeDiagKind;
+
+typedef struct CfreeDiagSink CfreeDiagSink;
+struct CfreeDiagSink {
+ void (*emit)(CfreeDiagSink*, CfreeDiagKind, CfreeSrcLoc,
+ const char* fmt, va_list);
+ void* user;
+ /* libcfree maintains these counters; hosts may inspect, must not write. */
+ uint32_t errors;
+ uint32_t warnings;
+};
+
+typedef struct CfreeWriter CfreeWriter;
+struct CfreeWriter {
+ void (*write)(CfreeWriter*, const void* data, size_t n);
+ void (*seek) (CfreeWriter*, uint64_t offset);
+ uint64_t (*tell) (CfreeWriter*);
+ int (*error)(CfreeWriter*);
+ /* close is responsible for any host-side teardown (closing fds, freeing
+ * the enclosing struct). After close the pointer is invalid. */
+ void (*close)(CfreeWriter*);
+};
+
+/* ============================================================
* Target description
* ============================================================ */
typedef enum CfreeArchKind {
@@ -98,30 +141,24 @@ CfreeCompiler* cfree_compiler_new (CfreeTarget, const CfreeEnv*);
void cfree_compiler_free(CfreeCompiler*);
/* ============================================================
- * Writer (streaming output sink)
+ * Writer dispatch (inline)
* ============================================================
* Callers obtain CfreeWriter*s from CfreeFileIO.open_writer or from
- * cfree_writer_mem. The Writer is consumed by file emitters and the link
- * step; libcfree never closes writers it did not allocate. */
-void cfree_writer_write(CfreeWriter*, const void* data, size_t n);
-void cfree_writer_seek (CfreeWriter*, uint64_t offset);
-uint64_t cfree_writer_tell (CfreeWriter*);
-int cfree_writer_error(CfreeWriter*);
-void cfree_writer_close(CfreeWriter*);
-
-/* In-memory writer backed by the supplied heap. The buffer is owned by the
- * Writer; cfree_writer_mem_bytes returns its current contents (valid until
- * the next write or close). */
+ * cfree_writer_mem. The dispatch helpers below are pure inline thunks
+ * over the vtable — libcfree itself uses the vtable directly. */
+static inline void cfree_writer_write(CfreeWriter* w, const void* d, size_t n) { w->write(w, d, n); }
+static inline void cfree_writer_seek (CfreeWriter* w, uint64_t off) { w->seek (w, off); }
+static inline uint64_t cfree_writer_tell (CfreeWriter* w) { return w->tell(w); }
+static inline int cfree_writer_error(CfreeWriter* w) { return w->error(w);}
+static inline void cfree_writer_close(CfreeWriter* w) { w->close(w); }
+
+/* In-memory writer backed by the supplied heap. Useful as a building
+ * block; the buffer is owned by the Writer and cfree_writer_mem_bytes
+ * returns its current contents (valid until the next write or close). */
CfreeWriter* cfree_writer_mem (CfreeHeap*);
const uint8_t* cfree_writer_mem_bytes(CfreeWriter*, size_t* len_out);
/* ============================================================
- * Built-in heap and diag sink
- * ============================================================ */
-CfreeHeap* cfree_heap_libc (void);
-CfreeDiagSink* cfree_diag_stderr(void);
-
-/* ============================================================
* JIT
* ============================================================
* cfree_link_jit produces a CfreeJit owning its mapped pages and resolved
diff --git a/src/core/core.h b/src/core/core.h
@@ -42,11 +42,9 @@ typedef struct SourceManager SourceManager;
* intentionally not intern-pool concepts; see obj/obj.h and link/link.h. */
typedef u32 Sym;
-typedef struct SrcLoc {
- u32 file_id;
- u32 line;
- u32 col;
-} SrcLoc;
+/* SrcLoc is the public CfreeSrcLoc; the alias keeps internal call sites
+ * terse. */
+typedef CfreeSrcLoc SrcLoc;
typedef struct SrcRange {
SrcLoc begin;
diff --git a/src/core/diag.h b/src/core/diag.h
@@ -3,23 +3,17 @@
#include "core/core.h"
-typedef enum DiagKind {
- DIAG_NOTE,
- DIAG_WARN,
- DIAG_ERROR,
- DIAG_FATAL,
-} DiagKind;
+/* DiagKind / DiagSink struct are public (see <cfree.h>). The internal
+ * aliases below keep terse names; the unprefixed enum constants below
+ * are the names libcfree's source uses internally. */
+typedef CfreeDiagKind DiagKind;
+#define DIAG_NOTE CFREE_DIAG_NOTE
+#define DIAG_WARN CFREE_DIAG_WARN
+#define DIAG_ERROR CFREE_DIAG_ERROR
+#define DIAG_FATAL CFREE_DIAG_FATAL
-struct DiagSink {
- void (*emit)(DiagSink*, DiagKind, SrcLoc, const char* fmt, va_list);
- void* user;
- u32 errors;
- u32 warnings;
-};
-
-void diag_emit(DiagSink*, DiagKind, SrcLoc, const char* fmt, ...);
+/* Convenience varargs wrappers around `sink->emit`. Internal use only. */
+void diag_emit (DiagSink*, DiagKind, SrcLoc, const char* fmt, ...);
void diag_emitv(DiagSink*, DiagKind, SrcLoc, const char* fmt, va_list);
-DiagSink* diag_stderr(void);
-
#endif
diff --git a/src/core/heap.h b/src/core/heap.h
@@ -3,14 +3,14 @@
#include "core/core.h"
-struct CfreeHeap {
- void* (*alloc)(Heap*, size_t size, size_t align);
- void* (*realloc)(Heap*, void* p, size_t old_size, size_t new_size, size_t align);
- void (*free)(Heap*, void* p, size_t size);
- void* user;
-};
-
-Heap* heap_libc(void);
-Heap* heap_mmap_exec(void); /* JIT: pages with PROT_EXEC available on flip */
+/* CfreeHeap struct definition is in <cfree.h> (public). The host
+ * implements `alloc`/`realloc`/`free` and passes the heap in via
+ * CfreeEnv.heap.
+ *
+ * heap_mmap_exec is a libcfree-internal helper used only by the JIT path
+ * (mapped pages with PROT_EXEC available on flip). It is the one place
+ * inside libcfree that genuinely depends on host memory mapping; for now
+ * it stays internal until the JIT is wired up. */
+Heap* heap_mmap_exec(void);
#endif