kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mdriver/cc.c | 227++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/driver.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/env.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/ld.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdriver/main.c | 26++++++++------------------
Minclude/cfree.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/core/core.h | 8+++-----
Msrc/core/diag.h | 26++++++++++----------------
Msrc/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