commit bf74553de4e30da15763daf4a86a55e8cf88d69c
parent bc320ed9645db020921f11869a38e2e951c8db9d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 7 May 2026 21:37:16 -0700
include/cfree.h public API + driver/ split
Diffstat:
23 files changed, 1392 insertions(+), 795 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,17 +1,53 @@
CC = clang
SYSROOT = $(shell xcrun --show-sdk-path)
-CFLAGS = -std=c11 -Wpedantic -Wall -Wextra -Werror -ffreestanding -Isrc -isysroot $(SYSROOT)
-SRCS = $(shell find src -name '*.c')
-OBJS = $(patsubst src/%.c,build/%.o,$(SRCS))
+CFLAGS_COMMON = -std=c11 -Wpedantic -Wall -Wextra -Werror -isysroot $(SYSROOT)
-.PHONY: all clean
+# libcfree: written in C11 freestanding; sees both src/ (internal) and
+# include/ (its own public surface).
+LIB_CFLAGS = $(CFLAGS_COMMON) -ffreestanding -Iinclude -Isrc
-all: $(OBJS)
+# Driver: hosted CLI binary. Sees only the public include/ tree — that's
+# what makes the driver the first consumer of libcfree.
+DRIVER_CFLAGS = $(CFLAGS_COMMON) -Iinclude
-build/%.o: src/%.c
+LIB_SRCS = $(shell find src -name '*.c')
+LIB_OBJS = $(patsubst src/%.c,build/lib/%.o,$(LIB_SRCS))
+
+DRIVER_SRCS = $(wildcard driver/*.c)
+DRIVER_OBJS = $(patsubst driver/%.c,build/driver/%.o,$(DRIVER_SRCS))
+
+LIB_AR = build/libcfree.a
+BIN = build/cfree
+
+.PHONY: all lib driver bin clean
+
+# Default: compile libcfree.a, the driver objects, and link the cfree
+# binary. The link step currently fails because most libcfree functions
+# are header-only declarations — that's expected and tracks how much of
+# the implementation is still TODO.
+all: lib driver bin
+
+lib: $(LIB_AR)
+
+driver: $(DRIVER_OBJS)
+
+bin: $(BIN)
+
+$(LIB_AR): $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ ar rcs $@ $(LIB_OBJS)
+
+$(BIN): $(DRIVER_OBJS) $(LIB_AR)
+ $(CC) -isysroot $(SYSROOT) -o $@ $(DRIVER_OBJS) $(LIB_AR)
+
+build/lib/%.o: src/%.c
+ @mkdir -p $(dir $@)
+ $(CC) $(LIB_CFLAGS) -c $< -o $@
+
+build/driver/%.o: driver/%.c
@mkdir -p $(dir $@)
- $(CC) $(CFLAGS) -c $< -o $@
+ $(CC) $(DRIVER_CFLAGS) -c $< -o $@
clean:
rm -rf build
diff --git a/driver/ar.c b/driver/ar.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+int driver_ar(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+ /* TODO: archiver. */
+ return 1;
+}
diff --git a/driver/as.c b/driver/as.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+int driver_as(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+ /* TODO: standalone assembler. */
+ return 1;
+}
diff --git a/driver/cc.c b/driver/cc.c
@@ -0,0 +1,11 @@
+#include "driver.h"
+#include <cfree.h>
+
+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;
+}
diff --git a/driver/cpp.c b/driver/cpp.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+int driver_cpp(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+ /* TODO: preprocessor-only mode. */
+ return 1;
+}
diff --git a/driver/dbg.c b/driver/dbg.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+int driver_dbg(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+ /* TODO: debugger front-end. */
+ return 1;
+}
diff --git a/driver/driver.h b/driver/driver.h
@@ -0,0 +1,31 @@
+#ifndef CFREE_DRIVER_H
+#define CFREE_DRIVER_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
+ * (<cfree.h>); it has no access to libcfree's internal headers. */
+
+typedef enum DriverTool {
+ DRIVER_TOOL_CC,
+ DRIVER_TOOL_CPP,
+ DRIVER_TOOL_AS,
+ DRIVER_TOOL_LD,
+ DRIVER_TOOL_AR,
+ DRIVER_TOOL_OBJDUMP,
+ DRIVER_TOOL_DBG,
+} DriverTool;
+
+/* Multi-call entry: dispatches by argv[0] basename (or argv[1] fallback). */
+int driver_main(int argc, char** argv);
+
+/* Direct entry per tool. Each lives in driver/<tool>.c. */
+int driver_cc (int argc, char** argv);
+int driver_cpp (int argc, char** argv);
+int driver_as (int argc, char** argv);
+int driver_ld (int argc, char** argv);
+int driver_ar (int argc, char** argv);
+int driver_objdump(int argc, char** argv);
+int driver_dbg (int argc, char** argv);
+
+#endif
diff --git a/driver/ld.c b/driver/ld.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+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;
+}
diff --git a/driver/main.c b/driver/main.c
@@ -0,0 +1,47 @@
+#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);
+ return -1;
+}
+
+int driver_main(int argc, char** argv)
+{
+ const char* name;
+ int rc;
+
+ if (argc < 1) return 2;
+ name = basename_of(argv[0]);
+ rc = dispatch(name, argc, argv);
+ if (rc != -1) return rc;
+
+ /* `cfree <tool> ...` form. */
+ if (argc < 2) return 2;
+ return dispatch(argv[1], argc - 1, argv + 1);
+}
+
+int main(int argc, char** argv)
+{
+ return driver_main(argc, argv);
+}
diff --git a/driver/objdump.c b/driver/objdump.c
@@ -0,0 +1,10 @@
+#include "driver.h"
+#include <cfree.h>
+
+int driver_objdump(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+ /* TODO: object inspection / disassembler. */
+ return 1;
+}
diff --git a/include/cfree.h b/include/cfree.h
@@ -0,0 +1,286 @@
+#ifndef CFREE_H
+#define CFREE_H
+
+/* libcfree's complete public API. The driver and any other consumer of
+ * libcfree only includes this single header — internal headers live under
+ * src/ and are not part of the stable surface.
+ *
+ * Every public identifier starts with cfree_, Cfree, or CFREE_. */
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* ============================================================
+ * 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)
+ * ============================================================ */
+typedef struct CfreeSrcLoc {
+ uint32_t file_id;
+ uint32_t line;
+ uint32_t col;
+} CfreeSrcLoc;
+
+/* ============================================================
+ * Target description
+ * ============================================================ */
+typedef enum CfreeArchKind {
+ CFREE_ARCH_X86_32,
+ CFREE_ARCH_X86_64,
+ CFREE_ARCH_ARM_32,
+ CFREE_ARCH_ARM_64,
+ CFREE_ARCH_RV32,
+ CFREE_ARCH_RV64,
+ CFREE_ARCH_WASM,
+} CfreeArchKind;
+
+typedef enum CfreeOSKind {
+ CFREE_OS_FREESTANDING,
+ CFREE_OS_LINUX,
+ CFREE_OS_MACOS,
+ CFREE_OS_WINDOWS,
+ CFREE_OS_WASI,
+} CfreeOSKind;
+
+typedef enum CfreeObjFmt {
+ CFREE_OBJ_ELF,
+ CFREE_OBJ_COFF,
+ CFREE_OBJ_MACHO,
+ CFREE_OBJ_WASM,
+} CfreeObjFmt;
+
+typedef struct CfreeTarget {
+ CfreeArchKind arch;
+ CfreeOSKind os;
+ CfreeObjFmt obj;
+ uint8_t ptr_size; /* 4 or 8 */
+ uint8_t ptr_align;
+ uint8_t big_endian;
+} CfreeTarget;
+
+/* ============================================================
+ * Host environment
+ * ============================================================
+ * The host supplies a heap, optional file I/O, and a diag sink. The
+ * freestanding core never takes paths; path-shaped helpers in the driver
+ * feed bytes/Writers. */
+typedef struct CfreeFileData {
+ const uint8_t* data;
+ size_t size;
+ void* token; /* opaque ownership handle for release */
+} CfreeFileData;
+
+typedef struct CfreeFileIO {
+ int (*read_all) (void* user, const char* path, CfreeFileData* out);
+ void (*release) (void* user, CfreeFileData*);
+ CfreeWriter* (*open_writer)(void* user, const char* path);
+ void* user;
+} CfreeFileIO;
+
+typedef struct CfreeEnv {
+ CfreeHeap* heap;
+ const CfreeFileIO* file_io; /* may be NULL for purely in-memory pipelines */
+ CfreeDiagSink* diag;
+} CfreeEnv;
+
+/* ============================================================
+ * Compiler lifecycle
+ * ============================================================ */
+CfreeCompiler* cfree_compiler_new (CfreeTarget, const CfreeEnv*);
+void cfree_compiler_free(CfreeCompiler*);
+
+/* ============================================================
+ * Writer (streaming output sink)
+ * ============================================================
+ * 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). */
+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
+ * image. Symbol lookup is by name (object-local handles never escape
+ * libcfree). */
+typedef void (*CfreeJitFn)(void);
+
+void cfree_jit_free (CfreeJit*);
+CfreeJitFn cfree_jit_lookup(CfreeJit*, const char* name);
+
+/* Resolver invoked when the linker encounters an undefined symbol. Returning
+ * NULL is an error. */
+typedef void* (*CfreeExternResolver)(void* user, const char* name);
+
+/* ============================================================
+ * Pipeline
+ * ============================================================
+ * Layered driver-facing API. Three core operations + one convenience:
+ *
+ * cfree_compile_obj one C TU -> in-memory CfreeObjBuilder (chains into link)
+ * cfree_compile_obj_emit one C TU -> CfreeWriter (cc -c)
+ * cfree_link_exe link inputs -> CfreeWriter (ld)
+ * cfree_link_jit link inputs -> owning CfreeJit handle
+ * cfree_run convenience: compile N inputs and link/jit
+ *
+ * The freestanding core takes only byte buffers and Writers — never paths.
+ * Path-shaped helpers live in driver-level adapters (and in cfree_run, which
+ * is itself a driver convenience), and feed the byte/Writer APIs after
+ * consulting CfreeEnv.file_io.
+ *
+ * Errors are reported through libcfree's internal panic mechanism. Each
+ * top-level function in this header saves and restores the active panic
+ * handler around its own boundary, so these functions are safely nestable: a
+ * caller that has already installed one keeps it across these calls. On
+ * failure the function unwinds its own cleanups, restores the caller's
+ * handler, and returns nonzero. */
+
+typedef struct CfreeDefine {
+ const char* name;
+ const char* body; /* NULL means "1" */
+} CfreeDefine;
+
+/* Generic byte-buffer input. Used for C source TUs, encoded objects, and
+ * archives. `name` is a diagnostic label (typically a path or pseudo-path);
+ * the linker interns it on entry. `data` may be any byte-shaped content. */
+typedef struct CfreeBytesInput {
+ const char* name;
+ const uint8_t* data;
+ size_t len;
+} CfreeBytesInput;
+
+/* Preprocessor configuration shared by compile_* and the convenience run. */
+typedef struct CfreePpOptions {
+ const char* const* include_dirs;
+ uint32_t ninclude_dirs;
+ const char* const* system_include_dirs;
+ uint32_t nsystem_include_dirs;
+ const CfreeDefine* defines;
+ uint32_t ndefines;
+ const char* const* undefines;
+ uint32_t nundefines;
+} CfreePpOptions;
+
+/* Per-TU compile knobs. */
+typedef struct CfreeCompileOptions {
+ int opt_level; /* 0 direct, 1 minimal, 2 full */
+ int debug_info;
+ CfreePpOptions pp;
+} CfreeCompileOptions;
+
+/* Compile one C input (memory bytes).
+ *
+ * cfree_compile_obj returns a CfreeObjBuilder owned by the CfreeCompiler. The
+ * builder is already finalized; do not write to it further. Pass it to
+ * cfree_link_exe / cfree_link_jit. It must be alive until the linker has
+ * consumed it. The CfreeCompiler must outlive the returned builder.
+ *
+ * cfree_compile_obj_emit writes the encoded object to `out` and frees its
+ * temporary builder before returning. The Writer is not closed. On nonzero
+ * return the Writer may contain partial output and should not be consumed.
+ *
+ * Returns 0 on success, nonzero on failure. */
+int cfree_compile_obj (CfreeCompiler*, const CfreeCompileOptions*,
+ const CfreeBytesInput* input, CfreeObjBuilder** out);
+int cfree_compile_obj_emit(CfreeCompiler*, const CfreeCompileOptions*,
+ const CfreeBytesInput* input, CfreeWriter* out);
+
+typedef struct CfreeLinkOptions {
+ CfreeObjBuilder* const* objs; /* fresh-compiled, by reference */
+ uint32_t nobjs;
+ const CfreeBytesInput* obj_bytes;
+ uint32_t nobj_bytes;
+ const CfreeBytesInput* archives;
+ uint32_t narchives;
+ const char* linker_script_text; /* NULL = no script.
+ * Non-NULL: linker_script_len
+ * must match the buffer. */
+ size_t linker_script_len;
+ const char* entry; /* NULL = format/target default */
+ CfreeExternResolver extern_resolver;
+ void* extern_resolver_user;
+} CfreeLinkOptions;
+
+/* All bytes inputs (obj_bytes, archives) must remain alive until the
+ * matching cfree_link_* call returns. */
+
+/* Link to executable. Writer is not closed by the call. On nonzero return
+ * the Writer may contain partial output and should not be consumed. */
+int cfree_link_exe(CfreeCompiler*, const CfreeLinkOptions*, CfreeWriter* out);
+
+/* Link as JIT. On success, *out_jit owns its image and mapped pages and
+ * must be released with cfree_jit_free. */
+int cfree_link_jit(CfreeCompiler*, const CfreeLinkOptions*, CfreeJit** out_jit);
+
+/* ----- Convenience: compose compile + link/jit for the common case. ----- */
+
+typedef enum CfreeOutputKind {
+ CFREE_OUTPUT_OBJ, /* exactly one source input total */
+ CFREE_OUTPUT_EXE,
+ CFREE_OUTPUT_JIT,
+} CfreeOutputKind;
+
+typedef struct CfreeOptions {
+ CfreeTarget target;
+ CfreeEnv env;
+ CfreeOutputKind output_kind;
+
+ int opt_level;
+ int debug_info;
+
+ const char* output_path; /* OBJ/EXE: required, used via env.file_io */
+
+ /* C source inputs. Path-shaped sources are read via env.file_io; memory
+ * sources are passed directly. The combined sequence (paths first, then
+ * memory) is compiled in order. For OBJ output exactly one total source
+ * is required. */
+ const char* const* source_files;
+ uint32_t nsource_files;
+ const CfreeBytesInput* source_memory;
+ uint32_t nsource_memory;
+
+ CfreePpOptions pp;
+
+ /* Path-shaped link inputs: driver reads via env.file_io. Library
+ * resolution (-lfoo against -L paths) is the CLI driver's job; by the
+ * time options reach cfree_run, archives must be concrete paths. */
+ const char* const* object_files;
+ uint32_t nobject_files;
+ const char* const* archives;
+ uint32_t narchives;
+ const char* linker_script; /* path; driver reads via env.file_io */
+
+ const char* entry; /* NULL = format/target default */
+ CfreeExternResolver extern_resolver;
+ void* extern_resolver_user;
+
+ CfreeJit** out_jit; /* JIT only: caller owns on success */
+} CfreeOptions;
+
+int cfree_run(const CfreeOptions*);
+
+#endif
diff --git a/src/api/pipeline.c b/src/api/pipeline.c
@@ -0,0 +1,624 @@
+/* libcfree's top-level API surface (compile/link/jit/run + the
+ * CfreeCompiler lifecycle). The public declarations live under
+ * <cfree.h>; this file is the single composition point that owns the
+ * setjmp boundary, glues the subsystems together, and converts host inputs
+ * (bytes, paths, writers) into work for the internal pipeline. */
+
+#include <cfree.h>
+
+#include "arch/arch.h"
+#include "cg/cg.h"
+#include "core/arena.h"
+#include "core/heap.h"
+#include "debug/debug.h"
+#include "decl/decl.h"
+#include "lex/lex.h"
+#include "link/link.h"
+#include "obj/obj.h"
+#include "opt/opt.h"
+#include "parse/parse.h"
+#include "pp/pp.h"
+
+/* ============================================================
+ * CfreeCompiler lifecycle (public)
+ * ============================================================ */
+
+CfreeCompiler* cfree_compiler_new(CfreeTarget target, const CfreeEnv* env)
+{
+ Heap* h;
+ Compiler* c;
+
+ if (!env || !env->heap) return NULL;
+ h = env->heap;
+ c = h->alloc(h, sizeof(*c), _Alignof(Compiler));
+ if (!c) return NULL;
+ compiler_init(c, target, env);
+ return c;
+}
+
+void cfree_compiler_free(CfreeCompiler* c)
+{
+ Heap* h;
+ if (!c) return;
+ h = (Heap*)c->env->heap;
+ compiler_fini(c);
+ h->free(h, c, sizeof(*c));
+}
+
+/* ============================================================
+ * Helpers
+ * ============================================================ */
+
+static SrcLoc no_loc(void)
+{
+ SrcLoc loc;
+ loc.file_id = 0;
+ loc.line = 0;
+ loc.col = 0;
+ return loc;
+}
+
+static _Noreturn void panic_bad_options(Compiler* c, const char* msg)
+{
+ compiler_panic(c, no_loc(), "bad cfree options: %s", msg);
+}
+
+static const CfreeFileIO* require_file_io(Compiler* c, const char* what)
+{
+ if (!c->env || !c->env->file_io) {
+ compiler_panic(c, no_loc(), "%s requires env.file_io", what);
+ }
+ return c->env->file_io;
+}
+
+static void apply_pp_options(Pp* pp, const CfreePpOptions* opts)
+{
+ u32 i;
+
+ for (i = 0; i < opts->ninclude_dirs; ++i) {
+ pp_add_include_dir(pp, opts->include_dirs[i], 0);
+ }
+ for (i = 0; i < opts->nsystem_include_dirs; ++i) {
+ pp_add_include_dir(pp, opts->system_include_dirs[i], 1);
+ }
+ for (i = 0; i < opts->ndefines; ++i) {
+ const char* body = opts->defines[i].body ? opts->defines[i].body : "1";
+ pp_define(pp, opts->defines[i].name, body);
+ }
+ for (i = 0; i < opts->nundefines; ++i) {
+ pp_undef(pp, opts->undefines[i]);
+ }
+}
+
+/* ============================================================
+ * Compile one TU
+ * ============================================================ */
+
+/* One-TU compile against a fresh ObjBuilder. The builder is finalized on
+ * exit so it is immediately consumable by the linker or an emit_* function.
+ * The input bytes must outlive this call. */
+static void compile_into(Compiler* c, const CfreeCompileOptions* opts,
+ const CfreeBytesInput* input, ObjBuilder* ob)
+{
+ Pp* pp = pp_new(c);
+ Lexer* lex = lex_open_mem(c, input->name,
+ (const char*)input->data, input->len);
+ DeclTable* decls = decl_new(c, ob);
+ MCEmitter* mc = mc_new(c, ob);
+ CGTarget* target = cgtarget_new(c, ob, mc);
+ Debug* debug = NULL;
+ CG* cg = NULL;
+
+ apply_pp_options(pp, &opts->pp);
+ pp_push_input(pp, lex); /* PP owns the lexer from here on */
+
+ if (opts->opt_level > 0) {
+ target = opt_cgtarget_new(c, target, opts->opt_level);
+ }
+ if (opts->debug_info) {
+ debug = debug_new(c, ob);
+ }
+ cg = cg_new(c, target, debug);
+
+ parse_c(c, pp, decls, cg);
+ cgtarget_finalize(target);
+ if (debug) {
+ debug_emit(debug);
+ }
+ obj_finalize(ob);
+
+ cg_free(cg);
+ if (debug) {
+ debug_free(debug);
+ }
+ cgtarget_free(target); /* opt_cgtarget cascades to wrapped target */
+ mc_free(mc);
+ decl_free(decls);
+ pp_free(pp); /* releases the pushed lexer */
+}
+
+static void validate_bytes_input(Compiler* c, const CfreeBytesInput* in)
+{
+ if (!in->name) panic_bad_options(c, "input name is NULL");
+ if (!in->data && in->len != 0) {
+ panic_bad_options(c, "input data is NULL but len > 0");
+ }
+}
+
+int cfree_compile_obj(CfreeCompiler* c, const CfreeCompileOptions* opts,
+ const CfreeBytesInput* input, CfreeObjBuilder** out)
+{
+ PanicSave saved;
+ ObjBuilder* ob;
+
+ if (!out) {
+ return 1;
+ }
+ *out = NULL;
+ compiler_panic_save(c, &saved);
+ if (setjmp(c->panic)) {
+ compiler_run_cleanups(c);
+ compiler_panic_restore(c, &saved);
+ return 1;
+ }
+ if (!opts || !input) {
+ panic_bad_options(c, "compile options or input is NULL");
+ }
+ validate_bytes_input(c, input);
+ ob = obj_new(c);
+ compile_into(c, opts, input, ob);
+ *out = ob;
+ compiler_panic_restore(c, &saved);
+ return 0;
+}
+
+static void emit_object_bytes(Compiler* c, ObjBuilder* ob, Writer* w)
+{
+ switch (c->target.obj) {
+ case CFREE_OBJ_ELF:
+ emit_elf(c, ob, w);
+ break;
+ case CFREE_OBJ_COFF:
+ emit_coff(c, ob, w);
+ break;
+ case CFREE_OBJ_MACHO:
+ emit_macho(c, ob, w);
+ break;
+ case CFREE_OBJ_WASM:
+ emit_wasm(c, ob, w);
+ break;
+ }
+}
+
+int cfree_compile_obj_emit(CfreeCompiler* c, const CfreeCompileOptions* opts,
+ const CfreeBytesInput* input, CfreeWriter* out)
+{
+ PanicSave saved;
+ ObjBuilder* ob;
+
+ compiler_panic_save(c, &saved);
+ if (setjmp(c->panic)) {
+ compiler_run_cleanups(c);
+ compiler_panic_restore(c, &saved);
+ return 1;
+ }
+ if (!opts || !input || !out) {
+ panic_bad_options(c, "compile_emit args missing");
+ }
+ validate_bytes_input(c, input);
+ ob = obj_new(c);
+ compile_into(c, opts, input, ob);
+ emit_object_bytes(c, ob, out);
+ obj_free(ob);
+ compiler_panic_restore(c, &saved);
+ return 0;
+}
+
+/* ============================================================
+ * Link
+ * ============================================================ */
+
+static Linker* build_linker(Compiler* c, const CfreeLinkOptions* opts)
+{
+ Linker* linker = link_new(c);
+ u32 i;
+
+ for (i = 0; i < opts->nobjs; ++i) {
+ link_add_obj(linker, opts->objs[i]);
+ }
+ for (i = 0; i < opts->nobj_bytes; ++i) {
+ link_add_obj_bytes(linker, opts->obj_bytes[i].name,
+ opts->obj_bytes[i].data, opts->obj_bytes[i].len);
+ }
+ for (i = 0; i < opts->narchives; ++i) {
+ link_add_archive_bytes(linker, opts->archives[i].name,
+ opts->archives[i].data, opts->archives[i].len);
+ }
+ if (opts->linker_script_text) {
+ link_set_script_text(linker, opts->linker_script_text,
+ opts->linker_script_len);
+ }
+ if (opts->entry) {
+ link_set_entry(linker, opts->entry);
+ }
+ if (opts->extern_resolver) {
+ link_set_extern_resolver(linker, opts->extern_resolver,
+ opts->extern_resolver_user);
+ }
+ return linker;
+}
+
+int cfree_link_exe(CfreeCompiler* c, const CfreeLinkOptions* opts,
+ CfreeWriter* out)
+{
+ PanicSave saved;
+ Linker* linker;
+ LinkImage* image;
+
+ compiler_panic_save(c, &saved);
+ if (setjmp(c->panic)) {
+ compiler_run_cleanups(c);
+ compiler_panic_restore(c, &saved);
+ return 1;
+ }
+ if (!opts || !out) {
+ panic_bad_options(c, "link_exe args missing");
+ }
+ linker = build_linker(c, opts);
+ image = link_resolve(linker); /* deferred-cleanup-registered */
+ link_emit_image_writer(image, out);
+ link_image_free(image); /* undefers + frees */
+ link_free(linker);
+ compiler_panic_restore(c, &saved);
+ return 0;
+}
+
+int cfree_link_jit(CfreeCompiler* c, const CfreeLinkOptions* opts,
+ CfreeJit** out_jit)
+{
+ PanicSave saved;
+ Linker* linker;
+ LinkImage* image;
+
+ if (!out_jit) {
+ return 1;
+ }
+ *out_jit = NULL;
+ compiler_panic_save(c, &saved);
+ if (setjmp(c->panic)) {
+ compiler_run_cleanups(c);
+ compiler_panic_restore(c, &saved);
+ return 1;
+ }
+ if (!opts) {
+ panic_bad_options(c, "link_jit options missing");
+ }
+ linker = build_linker(c, opts);
+ image = link_resolve(linker); /* deferred-cleanup-registered */
+ *out_jit = cfree_jit_from_image(image); /* undefers + transfers ownership */
+ link_free(linker);
+ compiler_panic_restore(c, &saved);
+ return 0;
+}
+
+/* ============================================================
+ * Convenience: cfree_run
+ * ============================================================
+ *
+ * cfree_run owns a single Compiler for the whole composition. Scratch
+ * arrays (objs, loaded-bytes tables, staging arrays) are allocated from
+ * c->tu and reaped by compiler_fini. File-io loans for path-shaped inputs
+ * are tracked through the cleanup stack so a panic anywhere reaps them; on
+ * success they are released explicitly. */
+
+typedef struct LoadedBytes {
+ const CfreeFileIO* io;
+ CfreeBytesInput in;
+ CfreeFileData file;
+ int loaded;
+ CompilerCleanup* cleanup; /* compiler_defer handle */
+} LoadedBytes;
+
+static void loaded_bytes_release_cb(void* arg)
+{
+ LoadedBytes* lb = (LoadedBytes*)arg;
+ if (lb->loaded && lb->io && lb->io->release) {
+ lb->io->release(lb->io->user, &lb->file);
+ }
+ lb->loaded = 0;
+}
+
+static void load_path_bytes(Compiler* c, const char* path, LoadedBytes* out)
+{
+ out->io = require_file_io(c, "file input");
+ out->loaded = 0;
+ out->cleanup = NULL;
+ out->file.data = NULL;
+ out->file.size = 0;
+ out->file.token = NULL;
+ out->in.name = path;
+ out->in.data = NULL;
+ out->in.len = 0;
+
+ /* Defer release before reading so a read failure still cleans up. The
+ * callback is a no-op while loaded == 0. */
+ out->cleanup = compiler_defer(c, loaded_bytes_release_cb, out);
+
+ if (!out->io->read_all(out->io->user, path, &out->file)) {
+ compiler_panic(c, no_loc(), "failed to read: %s", path);
+ }
+ out->loaded = 1;
+ out->in.data = out->file.data;
+ out->in.len = out->file.size;
+}
+
+static void release_loaded_bytes(Compiler* c, LoadedBytes* lb)
+{
+ if (!lb->cleanup) return;
+ compiler_undefer(c, lb->cleanup);
+ lb->cleanup = NULL;
+ loaded_bytes_release_cb(lb);
+}
+
+static void release_loaded_array(Compiler* c, LoadedBytes* arr, u32 n)
+{
+ u32 i;
+ if (!arr) return;
+ for (i = 0; i < n; ++i) release_loaded_bytes(c, &arr[i]);
+}
+
+static u32 total_sources(const CfreeOptions* opts)
+{
+ return opts->nsource_files + opts->nsource_memory;
+}
+
+static void validate_run_options(Compiler* c, const CfreeOptions* opts)
+{
+ u32 i;
+ u32 nsrc;
+
+ if (!opts) panic_bad_options(c, "options pointer is NULL");
+ nsrc = total_sources(opts);
+ if (nsrc == 0) {
+ panic_bad_options(c, "at least one C source input is required");
+ }
+ if (opts->opt_level < 0 || opts->opt_level > 2) {
+ panic_bad_options(c, "opt_level must be 0, 1, or 2");
+ }
+ if (!opts->env.heap) {
+ panic_bad_options(c, "env.heap is required");
+ }
+ if (opts->output_kind != CFREE_OUTPUT_OBJ &&
+ opts->output_kind != CFREE_OUTPUT_EXE &&
+ opts->output_kind != CFREE_OUTPUT_JIT) {
+ panic_bad_options(c, "output_kind is invalid");
+ }
+ if ((opts->output_kind == CFREE_OUTPUT_OBJ ||
+ opts->output_kind == CFREE_OUTPUT_EXE) && !opts->output_path) {
+ panic_bad_options(c, "output_path is required for file output");
+ }
+ if ((opts->output_kind == CFREE_OUTPUT_OBJ ||
+ opts->output_kind == CFREE_OUTPUT_EXE) &&
+ (!opts->env.file_io || !opts->env.file_io->open_writer)) {
+ panic_bad_options(c, "env.file_io.open_writer is required for file output");
+ }
+ if (opts->output_kind == CFREE_OUTPUT_OBJ && nsrc != 1) {
+ panic_bad_options(c, "object output accepts exactly one C source input");
+ }
+ if (opts->output_kind == CFREE_OUTPUT_OBJ &&
+ (opts->nobject_files || opts->narchives ||
+ opts->linker_script || opts->extern_resolver)) {
+ panic_bad_options(c, "link options are not valid for object output");
+ }
+ if (opts->output_kind == CFREE_OUTPUT_EXE && opts->extern_resolver) {
+ panic_bad_options(c, "extern_resolver is JIT-only; not valid for exe output");
+ }
+ if (opts->output_kind == CFREE_OUTPUT_JIT && !opts->out_jit) {
+ panic_bad_options(c, "out_jit is required for JIT output");
+ }
+
+ if (opts->nsource_files &&
+ (!opts->env.file_io || !opts->env.file_io->read_all)) {
+ panic_bad_options(c, "env.file_io.read_all is required for source_files");
+ }
+ for (i = 0; i < opts->nsource_files; ++i) {
+ if (!opts->source_files[i]) {
+ panic_bad_options(c, "source_files entry is NULL");
+ }
+ }
+ for (i = 0; i < opts->nsource_memory; ++i) {
+ const CfreeBytesInput* in = &opts->source_memory[i];
+ if (!in->name || (!in->data && in->len != 0)) {
+ panic_bad_options(c, "source_memory entry is incomplete");
+ }
+ }
+
+ if ((opts->nobject_files || opts->narchives || opts->linker_script) &&
+ (!opts->env.file_io || !opts->env.file_io->read_all)) {
+ panic_bad_options(c, "env.file_io.read_all is required for linker file input");
+ }
+}
+
+/* Resolves the i-th source (paths first, then memory) into a CfreeBytesInput,
+ * lazily loading paths into `loaded` slots. */
+static const CfreeBytesInput* nth_source(const CfreeOptions* opts,
+ LoadedBytes* loaded, u32 i)
+{
+ if (i < opts->nsource_files) {
+ return &loaded[i].in;
+ }
+ return &opts->source_memory[i - opts->nsource_files];
+}
+
+int cfree_run(const CfreeOptions* opts)
+{
+ Compiler c_store;
+ Compiler* c = &c_store;
+ ObjBuilder** objs = NULL;
+ LoadedBytes* src_loaded = NULL;
+ LoadedBytes* obj_bytes = NULL;
+ LoadedBytes* arch_bytes = NULL;
+ LoadedBytes script;
+ CfreeBytesInput* obj_in = NULL;
+ CfreeBytesInput* arch_in = NULL;
+ CfreeLinkOptions link_opts;
+ CfreeCompileOptions co;
+ Writer* out_writer = NULL;
+ u32 nsrc;
+ u32 i;
+
+ if (!opts || !opts->env.heap) return 1;
+
+ script.loaded = 0;
+ script.cleanup = NULL;
+ script.io = NULL;
+ script.file.data = NULL;
+ script.file.size = 0;
+ script.file.token = NULL;
+
+ compiler_init(c, opts->target, &opts->env);
+
+ if (setjmp(c->panic)) {
+ if (out_writer) cfree_writer_close(out_writer);
+ compiler_run_cleanups(c);
+ compiler_fini(c);
+ return 1;
+ }
+
+ validate_run_options(c, opts);
+ nsrc = total_sources(opts);
+
+ co.opt_level = opts->opt_level;
+ co.debug_info = opts->debug_info;
+ co.pp = opts->pp;
+
+ /* Load source paths (if any) up front so OBJ and EXE/JIT share one path. */
+ if (opts->nsource_files) {
+ src_loaded = arena_array(c->tu, LoadedBytes, opts->nsource_files);
+ for (i = 0; i < opts->nsource_files; ++i) {
+ src_loaded[i].loaded = 0;
+ src_loaded[i].cleanup = NULL;
+ }
+ for (i = 0; i < opts->nsource_files; ++i) {
+ load_path_bytes(c, opts->source_files[i], &src_loaded[i]);
+ }
+ }
+
+ /* OBJ output: compile single TU and emit to writer. */
+ if (opts->output_kind == CFREE_OUTPUT_OBJ) {
+ const CfreeBytesInput* the_input = nth_source(opts, src_loaded, 0);
+ out_writer = opts->env.file_io->open_writer(opts->env.file_io->user,
+ opts->output_path);
+ if (!out_writer) {
+ compiler_panic(c, no_loc(), "failed to open output file: %s",
+ opts->output_path);
+ }
+ if (cfree_compile_obj_emit(c, &co, the_input, out_writer)) {
+ cfree_writer_close(out_writer);
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ compiler_fini(c);
+ return 1;
+ }
+ cfree_writer_close(out_writer);
+ out_writer = NULL;
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ compiler_fini(c);
+ return 0;
+ }
+
+ /* EXE/JIT: compile all sources, then link. */
+ objs = arena_array(c->tu, ObjBuilder*, nsrc);
+ for (i = 0; i < nsrc; ++i) objs[i] = NULL;
+ for (i = 0; i < nsrc; ++i) {
+ const CfreeBytesInput* in = nth_source(opts, src_loaded, i);
+ if (cfree_compile_obj(c, &co, in, &objs[i])) {
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ compiler_fini(c);
+ return 1;
+ }
+ }
+
+ if (opts->nobject_files) {
+ obj_bytes = arena_array(c->tu, LoadedBytes, opts->nobject_files);
+ for (i = 0; i < opts->nobject_files; ++i) {
+ obj_bytes[i].loaded = 0;
+ obj_bytes[i].cleanup = NULL;
+ }
+ for (i = 0; i < opts->nobject_files; ++i) {
+ load_path_bytes(c, opts->object_files[i], &obj_bytes[i]);
+ }
+ }
+ if (opts->narchives) {
+ arch_bytes = arena_array(c->tu, LoadedBytes, opts->narchives);
+ for (i = 0; i < opts->narchives; ++i) {
+ arch_bytes[i].loaded = 0;
+ arch_bytes[i].cleanup = NULL;
+ }
+ for (i = 0; i < opts->narchives; ++i) {
+ load_path_bytes(c, opts->archives[i], &arch_bytes[i]);
+ }
+ }
+ if (opts->linker_script) {
+ load_path_bytes(c, opts->linker_script, &script);
+ }
+
+ /* Stage parallel CfreeBytesInput arrays for the linker. */
+ if (opts->nobject_files) {
+ obj_in = arena_array(c->tu, CfreeBytesInput, opts->nobject_files);
+ for (i = 0; i < opts->nobject_files; ++i) obj_in[i] = obj_bytes[i].in;
+ }
+ if (opts->narchives) {
+ arch_in = arena_array(c->tu, CfreeBytesInput, opts->narchives);
+ for (i = 0; i < opts->narchives; ++i) arch_in[i] = arch_bytes[i].in;
+ }
+
+ link_opts.objs = (ObjBuilder* const*)objs;
+ link_opts.nobjs = nsrc;
+ link_opts.obj_bytes = obj_in;
+ link_opts.nobj_bytes = opts->nobject_files;
+ link_opts.archives = arch_in;
+ link_opts.narchives = opts->narchives;
+ link_opts.linker_script_text = script.loaded ? (const char*)script.file.data : NULL;
+ link_opts.linker_script_len = script.loaded ? script.file.size : 0;
+ link_opts.entry = opts->entry;
+ link_opts.extern_resolver = opts->extern_resolver;
+ link_opts.extern_resolver_user = opts->extern_resolver_user;
+
+ if (opts->output_kind == CFREE_OUTPUT_EXE) {
+ out_writer = opts->env.file_io->open_writer(opts->env.file_io->user,
+ opts->output_path);
+ if (!out_writer) {
+ compiler_panic(c, no_loc(), "failed to open output file: %s",
+ opts->output_path);
+ }
+ if (cfree_link_exe(c, &link_opts, out_writer)) {
+ cfree_writer_close(out_writer);
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ release_loaded_array(c, obj_bytes, opts->nobject_files);
+ release_loaded_array(c, arch_bytes, opts->narchives);
+ release_loaded_bytes(c, &script);
+ compiler_fini(c);
+ return 1;
+ }
+ cfree_writer_close(out_writer);
+ out_writer = NULL;
+ } else {
+ if (cfree_link_jit(c, &link_opts, opts->out_jit)) {
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ release_loaded_array(c, obj_bytes, opts->nobject_files);
+ release_loaded_array(c, arch_bytes, opts->narchives);
+ release_loaded_bytes(c, &script);
+ compiler_fini(c);
+ return 1;
+ }
+ }
+
+ release_loaded_array(c, src_loaded, opts->nsource_files);
+ release_loaded_array(c, obj_bytes, opts->nobject_files);
+ release_loaded_array(c, arch_bytes, opts->narchives);
+ release_loaded_bytes(c, &script);
+ /* objs (ObjBuilders) are owned by the Compiler; arena scratch is freed
+ * by compiler_fini. */
+ compiler_fini(c);
+ return 0;
+}
diff --git a/src/core/arena.h b/src/core/arena.h
@@ -0,0 +1,26 @@
+#ifndef CFREE_ARENA_H
+#define CFREE_ARENA_H
+
+#include "core/core.h"
+#include "core/heap.h"
+
+typedef struct ArenaBlock ArenaBlock;
+
+struct Arena {
+ Heap* heap;
+ ArenaBlock* head;
+ u8* cur; /* points into head's buffer */
+ u8* end; /* end of head's buffer */
+ size_t block_size;
+};
+
+void arena_init(Arena*, Heap*, size_t block_size);
+void arena_fini(Arena*);
+void arena_reset(Arena*);
+void* arena_alloc(Arena*, size_t size, size_t align);
+char* arena_strdup(Arena*, const char* s, size_t len);
+
+#define arena_new(a, T) ((T*)arena_alloc((a), sizeof(T), _Alignof(T)))
+#define arena_array(a, T, n) ((T*)arena_alloc((a), sizeof(T) * (size_t)(n), _Alignof(T)))
+
+#endif
diff --git a/src/core/buf.h b/src/core/buf.h
@@ -0,0 +1,34 @@
+#ifndef CFREE_BUF_H
+#define CFREE_BUF_H
+
+#include "core/core.h"
+#include "core/heap.h"
+
+#define BUF_CHUNK 65536
+
+typedef struct BufChunk BufChunk;
+struct BufChunk {
+ BufChunk* next;
+ u32 used;
+ u32 cap; /* usable size of data[] */
+ u8 data[];
+};
+
+typedef struct Buf {
+ Heap* heap;
+ BufChunk* head;
+ BufChunk* tail;
+ u32 total; /* sum of used across all chunks */
+} Buf;
+
+void buf_init(Buf*, Heap*);
+void buf_fini(Buf*);
+
+void buf_write(Buf*, const void* data, size_t n);
+u8* buf_reserve(Buf*, size_t n); /* contiguous; spills to a fresh chunk if needed */
+u32 buf_pos(const Buf*);
+void buf_patch(Buf*, u32 ofs, const void* data, size_t n); /* must lie within written range */
+
+void buf_flatten(const Buf*, u8* dst); /* copy entire contents out, dst >= total bytes */
+
+#endif
diff --git a/src/core/core.h b/src/core/core.h
@@ -0,0 +1,152 @@
+#ifndef CFREE_INTERNAL_CORE_H
+#define CFREE_INTERNAL_CORE_H
+
+#include <cfree.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <stdarg.h>
+
+/* Short integer aliases used throughout libcfree's internal headers. */
+typedef int8_t i8;
+typedef int16_t i16;
+typedef int32_t i32;
+typedef int64_t i64;
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+
+/* Internal aliases for types that also have a public Cfree-prefixed name.
+ * <cfree.h> is the single source of truth for those types' identities;
+ * src/ keeps its terser, plain names so the internal call sites don't
+ * change. Both names refer to the same struct. */
+typedef CfreeCompiler Compiler;
+typedef CfreeHeap Heap;
+typedef CfreeDiagSink DiagSink;
+typedef CfreeWriter Writer;
+typedef CfreeTarget Target;
+typedef CfreeObjBuilder ObjBuilder;
+typedef enum CfreeArchKind ArchKind;
+typedef enum CfreeOSKind OSKind;
+typedef enum CfreeObjFmt ObjFmt;
+
+/* Internal-only forward declarations. */
+typedef struct Arena Arena;
+typedef struct Pool Pool;
+typedef struct TargetABI TargetABI;
+typedef struct SourceManager SourceManager;
+
+/* Interned string ids. 0 reserved as "none". Object and linker symbols are
+ * 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;
+
+typedef struct SrcRange {
+ SrcLoc begin;
+ SrcLoc end;
+} SrcRange;
+
+typedef enum SourceFileKind {
+ SRC_FILE_REAL,
+ SRC_FILE_MEMORY,
+ SRC_FILE_BUILTIN,
+ SRC_FILE_MACRO,
+} SourceFileKind;
+
+typedef struct SourceFile {
+ u32 id;
+ Sym name; /* spelling used in diagnostics */
+ Sym path; /* normalized path, 0 for memory/builtin-only input */
+ u8 kind; /* SourceFileKind */
+ u8 system_header;
+ u16 pad;
+} SourceFile;
+
+typedef struct SourceInclude {
+ u32 includer_file_id;
+ u32 included_file_id;
+ SrcLoc include_loc;
+ u8 system;
+ u8 pad[3];
+} SourceInclude;
+
+typedef struct SourceExpansion {
+ SrcLoc spelling_loc; /* where the token text came from */
+ SrcLoc expansion_loc; /* where the macro expansion was requested */
+ Sym macro_name;
+} SourceExpansion;
+
+typedef struct SourceDepIter SourceDepIter;
+
+SourceManager* source_new(Compiler*);
+void source_free(SourceManager*);
+
+u32 source_add_file(SourceManager*, const char* path, int system_header);
+u32 source_add_memory(SourceManager*, const char* name);
+u32 source_add_builtin(SourceManager*, const char* name);
+void source_add_include(SourceManager*, u32 includer_file_id, u32 included_file_id,
+ SrcLoc include_loc, int system);
+u32 source_add_macro_expansion(SourceManager*, Sym macro_name,
+ SrcLoc spelling_loc, SrcLoc expansion_loc);
+
+const SourceFile* source_file(SourceManager*, u32 file_id);
+const SourceExpansion* source_expansion(SourceManager*, u32 expansion_file_id);
+SrcLoc source_spelling_loc(SourceManager*, SrcLoc);
+SrcLoc source_expansion_loc(SourceManager*, SrcLoc);
+
+SourceDepIter* source_depiter_new(SourceManager*);
+const SourceInclude* source_depiter_next(SourceDepIter*);
+void source_depiter_free(SourceDepIter*);
+
+/* compiler_defer registers a cleanup that runs LIFO from cfree_run's panic
+ * boundary (or any caller that establishes its own setjmp). Each subsystem
+ * _new registers its matching _free; the matched _free calls compiler_undefer
+ * with the returned handle. The cleanup stack lives in scratch arena and is
+ * bounded by pipeline depth (~10 entries). */
+typedef struct CompilerCleanup CompilerCleanup;
+
+struct CfreeCompiler {
+ jmp_buf panic;
+ const CfreeEnv* env;
+ Pool* global;
+ Arena* tu;
+ Arena* scratch;
+ SourceManager* sources;
+ TargetABI* abi;
+ Target target;
+ CompilerCleanup* cleanup; /* top of LIFO cleanup stack */
+ void* reserved;
+};
+
+void compiler_init(Compiler*, Target, const CfreeEnv*);
+void compiler_fini(Compiler*);
+
+/* Cleanup stack. compiler_defer returns an opaque handle; compiler_undefer
+ * removes the entry without running it (use after a successful _free).
+ * compiler_run_cleanups runs everything LIFO, used by the panic handler. */
+CompilerCleanup* compiler_defer(Compiler*, void (*fn)(void*), void* arg);
+void compiler_undefer(Compiler*, CompilerCleanup*);
+void compiler_run_cleanups(Compiler*);
+
+/* Emits a diagnostic and longjmps c->panic. Used by parse/cg/arch fatal paths. */
+_Noreturn void compiler_panic(Compiler*, SrcLoc, const char* fmt, ...);
+_Noreturn void compiler_panicv(Compiler*, SrcLoc, const char* fmt, va_list);
+
+/* Save/restore the panic jmp_buf so layered APIs can nest. Each top-level
+ * driver function (cfree_compile_obj, cfree_link_*, ...) saves c->panic,
+ * installs its own setjmp, and restores on every exit path — both the
+ * panic-return path (after compiler_run_cleanups) and the success path.
+ * Without this, an inner setjmp clobbers an outer setjmp's jmp_buf, and a
+ * subsequent compiler_panic by the outer caller longjmps to a dead frame. */
+typedef struct PanicSave { jmp_buf buf; } PanicSave;
+void compiler_panic_save(Compiler*, PanicSave* out);
+void compiler_panic_restore(Compiler*, const PanicSave* saved);
+
+#endif
diff --git a/src/core/diag.h b/src/core/diag.h
@@ -0,0 +1,25 @@
+#ifndef CFREE_DIAG_H
+#define CFREE_DIAG_H
+
+#include "core/core.h"
+
+typedef enum DiagKind {
+ DIAG_NOTE,
+ DIAG_WARN,
+ DIAG_ERROR,
+ DIAG_FATAL,
+} DiagKind;
+
+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, ...);
+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
@@ -0,0 +1,16 @@
+#ifndef CFREE_HEAP_H
+#define CFREE_HEAP_H
+
+#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 */
+
+#endif
diff --git a/src/core/pool.h b/src/core/pool.h
@@ -0,0 +1,21 @@
+#ifndef CFREE_POOL_H
+#define CFREE_POOL_H
+
+#include "core/core.h"
+#include "core/heap.h"
+
+typedef struct Type Type; /* declared in src/type/type.h */
+
+void pool_init(Pool*, Heap*);
+void pool_fini(Pool*);
+
+/* Strings. Returns canonical id; equal strings → equal ids. */
+Sym pool_intern(Pool*, const char* s, size_t len);
+Sym pool_intern_cstr(Pool*, const char* s);
+const char* pool_str(Pool*, Sym, size_t* len_out);
+
+/* Types. Caller fills a stack-allocated Type template; pool returns the canonical
+ * pointer (allocating into the pool the first time). Equal types → equal pointers. */
+const Type* pool_type(Pool*, const Type* tmpl);
+
+#endif
diff --git a/src/driver/driver.h b/src/driver/driver.h
@@ -1,23 +0,0 @@
-#ifndef CFREE_DRIVER_H
-#define CFREE_DRIVER_H
-
-#include "core/core.h"
-
-typedef enum Tool {
- TOOL_CC,
- TOOL_CPP,
- TOOL_AS,
- TOOL_LD,
- TOOL_AR,
- TOOL_OBJDUMP,
- TOOL_DBG,
-} Tool;
-
-/* Multi-call entry: dispatches by argv[0] basename, falling back to argv[1]
- * (e.g. `cfree cc ...`). */
-int driver_main(int argc, char** argv);
-
-/* Direct entry per tool. */
-int driver_run(Tool, int argc, char** argv);
-
-#endif
diff --git a/src/driver/pipeline.c b/src/driver/pipeline.c
@@ -1,589 +0,0 @@
-#include "driver/pipeline.h"
-
-#include "arch/arch.h"
-#include "cg/cg.h"
-#include "core/arena.h"
-#include "debug/debug.h"
-#include "decl/decl.h"
-#include "lex/lex.h"
-#include "obj/obj.h"
-#include "opt/opt.h"
-#include "parse/parse.h"
-#include "pp/pp.h"
-
-/* ============================================================
- * Helpers
- * ============================================================ */
-
-static SrcLoc no_loc(void)
-{
- SrcLoc loc;
- loc.file_id = 0;
- loc.line = 0;
- loc.col = 0;
- return loc;
-}
-
-static _Noreturn void panic_bad_options(Compiler* c, const char* msg)
-{
- compiler_panic(c, no_loc(), "bad cfree options: %s", msg);
-}
-
-static const CfreeFileIO* require_file_io(Compiler* c, const char* what)
-{
- if (!c->env || !c->env->file_io) {
- compiler_panic(c, no_loc(), "%s requires env.file_io", what);
- }
- return c->env->file_io;
-}
-
-static void apply_pp_options(Pp* pp, const CfreePpOptions* opts)
-{
- u32 i;
-
- for (i = 0; i < opts->ninclude_dirs; ++i) {
- pp_add_include_dir(pp, opts->include_dirs[i], 0);
- }
- for (i = 0; i < opts->nsystem_include_dirs; ++i) {
- pp_add_include_dir(pp, opts->system_include_dirs[i], 1);
- }
- for (i = 0; i < opts->ndefines; ++i) {
- const char* body = opts->defines[i].body ? opts->defines[i].body : "1";
- pp_define(pp, opts->defines[i].name, body);
- }
- for (i = 0; i < opts->nundefines; ++i) {
- pp_undef(pp, opts->undefines[i]);
- }
-}
-
-/* ============================================================
- * Compile one TU
- * ============================================================ */
-
-/* One-TU compile against a fresh ObjBuilder. The builder is finalized on
- * exit so it is immediately consumable by the linker or an emit_* function.
- * The input bytes must outlive this call. */
-static void compile_into(Compiler* c, const CfreeCompileOptions* opts,
- const CfreeBytesInput* input, ObjBuilder* ob)
-{
- Pp* pp = pp_new(c);
- Lexer* lex = lex_open_mem(c, input->name,
- (const char*)input->data, input->len);
- DeclTable* decls = decl_new(c, ob);
- MCEmitter* mc = mc_new(c, ob);
- CGTarget* target = cgtarget_new(c, ob, mc);
- Debug* debug = NULL;
- CG* cg = NULL;
-
- apply_pp_options(pp, &opts->pp);
- pp_push_input(pp, lex); /* PP owns the lexer from here on */
-
- if (opts->opt_level > 0) {
- target = opt_cgtarget_new(c, target, opts->opt_level);
- }
- if (opts->debug_info) {
- debug = debug_new(c, ob);
- }
- cg = cg_new(c, target, debug);
-
- parse_c(c, pp, decls, cg);
- cgtarget_finalize(target);
- if (debug) {
- debug_emit(debug);
- }
- obj_finalize(ob);
-
- cg_free(cg);
- if (debug) {
- debug_free(debug);
- }
- cgtarget_free(target); /* opt_cgtarget cascades to wrapped target */
- mc_free(mc);
- decl_free(decls);
- pp_free(pp); /* releases the pushed lexer */
-}
-
-static void validate_bytes_input(Compiler* c, const CfreeBytesInput* in)
-{
- if (!in->name) panic_bad_options(c, "input name is NULL");
- if (!in->data && in->len != 0) {
- panic_bad_options(c, "input data is NULL but len > 0");
- }
-}
-
-int cfree_compile_obj(Compiler* c, const CfreeCompileOptions* opts,
- const CfreeBytesInput* input, ObjBuilder** out)
-{
- PanicSave saved;
- ObjBuilder* ob;
-
- if (!out) {
- return 1;
- }
- *out = NULL;
- compiler_panic_save(c, &saved);
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- compiler_panic_restore(c, &saved);
- return 1;
- }
- if (!opts || !input) {
- panic_bad_options(c, "compile options or input is NULL");
- }
- validate_bytes_input(c, input);
- ob = obj_new(c);
- compile_into(c, opts, input, ob);
- *out = ob;
- compiler_panic_restore(c, &saved);
- return 0;
-}
-
-static void emit_object_bytes(Compiler* c, ObjBuilder* ob, Writer* w)
-{
- switch (c->target.obj) {
- case OBJ_ELF:
- emit_elf(c, ob, w);
- break;
- case OBJ_COFF:
- emit_coff(c, ob, w);
- break;
- case OBJ_MACHO:
- emit_macho(c, ob, w);
- break;
- case OBJ_WASM:
- emit_wasm(c, ob, w);
- break;
- }
-}
-
-int cfree_compile_obj_emit(Compiler* c, const CfreeCompileOptions* opts,
- const CfreeBytesInput* input, Writer* out)
-{
- PanicSave saved;
- ObjBuilder* ob;
-
- compiler_panic_save(c, &saved);
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- compiler_panic_restore(c, &saved);
- return 1;
- }
- if (!opts || !input || !out) {
- panic_bad_options(c, "compile_emit args missing");
- }
- validate_bytes_input(c, input);
- ob = obj_new(c);
- compile_into(c, opts, input, ob);
- emit_object_bytes(c, ob, out);
- obj_free(ob);
- compiler_panic_restore(c, &saved);
- return 0;
-}
-
-/* ============================================================
- * Link
- * ============================================================ */
-
-static Linker* build_linker(Compiler* c, const CfreeLinkOptions* opts)
-{
- Linker* linker = link_new(c);
- u32 i;
-
- for (i = 0; i < opts->nobjs; ++i) {
- link_add_obj(linker, opts->objs[i]);
- }
- for (i = 0; i < opts->nobj_bytes; ++i) {
- link_add_obj_bytes(linker, opts->obj_bytes[i].name,
- opts->obj_bytes[i].data, opts->obj_bytes[i].len);
- }
- for (i = 0; i < opts->narchives; ++i) {
- link_add_archive_bytes(linker, opts->archives[i].name,
- opts->archives[i].data, opts->archives[i].len);
- }
- if (opts->linker_script_text) {
- link_set_script_text(linker, opts->linker_script_text,
- opts->linker_script_len);
- }
- if (opts->entry) {
- link_set_entry(linker, opts->entry);
- }
- if (opts->extern_resolver) {
- link_set_extern_resolver(linker, opts->extern_resolver,
- opts->extern_resolver_user);
- }
- return linker;
-}
-
-int cfree_link_exe(Compiler* c, const CfreeLinkOptions* opts, Writer* out)
-{
- PanicSave saved;
- Linker* linker;
- LinkImage* image;
-
- compiler_panic_save(c, &saved);
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- compiler_panic_restore(c, &saved);
- return 1;
- }
- if (!opts || !out) {
- panic_bad_options(c, "link_exe args missing");
- }
- linker = build_linker(c, opts);
- image = link_resolve(linker); /* deferred-cleanup-registered */
- link_emit_image_writer(image, out);
- link_image_free(image); /* undefers + frees */
- link_free(linker);
- compiler_panic_restore(c, &saved);
- return 0;
-}
-
-int cfree_link_jit(Compiler* c, const CfreeLinkOptions* opts,
- CfreeJit** out_jit)
-{
- PanicSave saved;
- Linker* linker;
- LinkImage* image;
-
- if (!out_jit) {
- return 1;
- }
- *out_jit = NULL;
- compiler_panic_save(c, &saved);
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- compiler_panic_restore(c, &saved);
- return 1;
- }
- if (!opts) {
- panic_bad_options(c, "link_jit options missing");
- }
- linker = build_linker(c, opts);
- image = link_resolve(linker); /* deferred-cleanup-registered */
- *out_jit = cfree_jit_from_image(image); /* undefers + transfers ownership */
- link_free(linker);
- compiler_panic_restore(c, &saved);
- return 0;
-}
-
-/* ============================================================
- * Convenience: cfree_run
- * ============================================================
- *
- * cfree_run owns a single Compiler for the whole composition. Scratch
- * arrays (objs, loaded-bytes tables, staging arrays) are allocated from
- * c->tu and reaped by compiler_fini. File-io loans for path-shaped inputs
- * are tracked through the cleanup stack so a panic anywhere reaps them; on
- * success they are released explicitly. */
-
-typedef struct LoadedBytes {
- const CfreeFileIO* io;
- CfreeBytesInput in;
- CfreeFileData file;
- int loaded;
- CompilerCleanup* cleanup; /* compiler_defer handle */
-} LoadedBytes;
-
-static void loaded_bytes_release_cb(void* arg)
-{
- LoadedBytes* lb = (LoadedBytes*)arg;
- if (lb->loaded && lb->io && lb->io->release) {
- lb->io->release(lb->io->user, &lb->file);
- }
- lb->loaded = 0;
-}
-
-static void load_path_bytes(Compiler* c, const char* path, LoadedBytes* out)
-{
- out->io = require_file_io(c, "file input");
- out->loaded = 0;
- out->cleanup = NULL;
- out->file.data = NULL;
- out->file.size = 0;
- out->file.token = NULL;
- out->in.name = path;
- out->in.data = NULL;
- out->in.len = 0;
-
- /* Defer release before reading so a read failure still cleans up. The
- * callback is a no-op while loaded == 0. */
- out->cleanup = compiler_defer(c, loaded_bytes_release_cb, out);
-
- if (!out->io->read_all(out->io->user, path, &out->file)) {
- compiler_panic(c, no_loc(), "failed to read: %s", path);
- }
- out->loaded = 1;
- out->in.data = out->file.data;
- out->in.len = out->file.size;
-}
-
-static void release_loaded_bytes(Compiler* c, LoadedBytes* lb)
-{
- if (!lb->cleanup) return;
- compiler_undefer(c, lb->cleanup);
- lb->cleanup = NULL;
- loaded_bytes_release_cb(lb);
-}
-
-static void release_loaded_array(Compiler* c, LoadedBytes* arr, u32 n)
-{
- u32 i;
- if (!arr) return;
- for (i = 0; i < n; ++i) release_loaded_bytes(c, &arr[i]);
-}
-
-static u32 total_sources(const CfreeOptions* opts)
-{
- return opts->nsource_files + opts->nsource_memory;
-}
-
-static void validate_run_options(Compiler* c, const CfreeOptions* opts)
-{
- u32 i;
- u32 nsrc;
-
- if (!opts) panic_bad_options(c, "options pointer is NULL");
- nsrc = total_sources(opts);
- if (nsrc == 0) {
- panic_bad_options(c, "at least one C source input is required");
- }
- if (opts->opt_level < 0 || opts->opt_level > 2) {
- panic_bad_options(c, "opt_level must be 0, 1, or 2");
- }
- if (!opts->env.heap) {
- panic_bad_options(c, "env.heap is required");
- }
- if (opts->output_kind != CFREE_OUTPUT_OBJ &&
- opts->output_kind != CFREE_OUTPUT_EXE &&
- opts->output_kind != CFREE_OUTPUT_JIT) {
- panic_bad_options(c, "output_kind is invalid");
- }
- if ((opts->output_kind == CFREE_OUTPUT_OBJ ||
- opts->output_kind == CFREE_OUTPUT_EXE) && !opts->output_path) {
- panic_bad_options(c, "output_path is required for file output");
- }
- if ((opts->output_kind == CFREE_OUTPUT_OBJ ||
- opts->output_kind == CFREE_OUTPUT_EXE) &&
- (!opts->env.file_io || !opts->env.file_io->open_writer)) {
- panic_bad_options(c, "env.file_io.open_writer is required for file output");
- }
- if (opts->output_kind == CFREE_OUTPUT_OBJ && nsrc != 1) {
- panic_bad_options(c, "object output accepts exactly one C source input");
- }
- if (opts->output_kind == CFREE_OUTPUT_OBJ &&
- (opts->nobject_files || opts->narchives ||
- opts->linker_script || opts->extern_resolver)) {
- panic_bad_options(c, "link options are not valid for object output");
- }
- if (opts->output_kind == CFREE_OUTPUT_EXE && opts->extern_resolver) {
- panic_bad_options(c, "extern_resolver is JIT-only; not valid for exe output");
- }
- if (opts->output_kind == CFREE_OUTPUT_JIT && !opts->out_jit) {
- panic_bad_options(c, "out_jit is required for JIT output");
- }
-
- if (opts->nsource_files &&
- (!opts->env.file_io || !opts->env.file_io->read_all)) {
- panic_bad_options(c, "env.file_io.read_all is required for source_files");
- }
- for (i = 0; i < opts->nsource_files; ++i) {
- if (!opts->source_files[i]) {
- panic_bad_options(c, "source_files entry is NULL");
- }
- }
- for (i = 0; i < opts->nsource_memory; ++i) {
- const CfreeBytesInput* in = &opts->source_memory[i];
- if (!in->name || (!in->data && in->len != 0)) {
- panic_bad_options(c, "source_memory entry is incomplete");
- }
- }
-
- if ((opts->nobject_files || opts->narchives || opts->linker_script) &&
- (!opts->env.file_io || !opts->env.file_io->read_all)) {
- panic_bad_options(c, "env.file_io.read_all is required for linker file input");
- }
-}
-
-/* Resolves the i-th source (paths first, then memory) into a CfreeBytesInput,
- * lazily loading paths into `loaded` slots. */
-static const CfreeBytesInput* nth_source(const CfreeOptions* opts,
- LoadedBytes* loaded, u32 i)
-{
- if (i < opts->nsource_files) {
- return &loaded[i].in;
- }
- return &opts->source_memory[i - opts->nsource_files];
-}
-
-int cfree_run(const CfreeOptions* opts)
-{
- Compiler c_store;
- Compiler* c = &c_store;
- ObjBuilder** objs = NULL;
- LoadedBytes* src_loaded = NULL;
- LoadedBytes* obj_bytes = NULL;
- LoadedBytes* arch_bytes = NULL;
- LoadedBytes script;
- CfreeBytesInput* obj_in = NULL;
- CfreeBytesInput* arch_in = NULL;
- CfreeLinkOptions link_opts;
- CfreeCompileOptions co;
- Writer* out_writer = NULL;
- u32 nsrc;
- u32 i;
-
- if (!opts || !opts->env.heap) return 1;
-
- script.loaded = 0;
- script.cleanup = NULL;
- script.io = NULL;
- script.file.data = NULL;
- script.file.size = 0;
- script.file.token = NULL;
-
- compiler_init(c, opts->target, &opts->env);
-
- if (setjmp(c->panic)) {
- if (out_writer) writer_close(out_writer);
- compiler_run_cleanups(c);
- compiler_fini(c);
- return 1;
- }
-
- validate_run_options(c, opts);
- nsrc = total_sources(opts);
-
- co.opt_level = opts->opt_level;
- co.debug_info = opts->debug_info;
- co.pp = opts->pp;
-
- /* Load source paths (if any) up front so OBJ and EXE/JIT share one path. */
- if (opts->nsource_files) {
- src_loaded = arena_array(c->tu, LoadedBytes, opts->nsource_files);
- for (i = 0; i < opts->nsource_files; ++i) {
- src_loaded[i].loaded = 0;
- src_loaded[i].cleanup = NULL;
- }
- for (i = 0; i < opts->nsource_files; ++i) {
- load_path_bytes(c, opts->source_files[i], &src_loaded[i]);
- }
- }
-
- /* OBJ output: compile single TU and emit to writer. */
- if (opts->output_kind == CFREE_OUTPUT_OBJ) {
- const CfreeBytesInput* the_input = nth_source(opts, src_loaded, 0);
- out_writer = opts->env.file_io->open_writer(opts->env.file_io->user,
- opts->output_path);
- if (!out_writer) {
- compiler_panic(c, no_loc(), "failed to open output file: %s",
- opts->output_path);
- }
- if (cfree_compile_obj_emit(c, &co, the_input, out_writer)) {
- writer_close(out_writer);
- release_loaded_array(c, src_loaded, opts->nsource_files);
- compiler_fini(c);
- return 1;
- }
- writer_close(out_writer);
- out_writer = NULL;
- release_loaded_array(c, src_loaded, opts->nsource_files);
- compiler_fini(c);
- return 0;
- }
-
- /* EXE/JIT: compile all sources, then link. */
- objs = arena_array(c->tu, ObjBuilder*, nsrc);
- for (i = 0; i < nsrc; ++i) objs[i] = NULL;
- for (i = 0; i < nsrc; ++i) {
- const CfreeBytesInput* in = nth_source(opts, src_loaded, i);
- if (cfree_compile_obj(c, &co, in, &objs[i])) {
- release_loaded_array(c, src_loaded, opts->nsource_files);
- compiler_fini(c);
- return 1;
- }
- }
-
- if (opts->nobject_files) {
- obj_bytes = arena_array(c->tu, LoadedBytes, opts->nobject_files);
- for (i = 0; i < opts->nobject_files; ++i) {
- obj_bytes[i].loaded = 0;
- obj_bytes[i].cleanup = NULL;
- }
- for (i = 0; i < opts->nobject_files; ++i) {
- load_path_bytes(c, opts->object_files[i], &obj_bytes[i]);
- }
- }
- if (opts->narchives) {
- arch_bytes = arena_array(c->tu, LoadedBytes, opts->narchives);
- for (i = 0; i < opts->narchives; ++i) {
- arch_bytes[i].loaded = 0;
- arch_bytes[i].cleanup = NULL;
- }
- for (i = 0; i < opts->narchives; ++i) {
- load_path_bytes(c, opts->archives[i], &arch_bytes[i]);
- }
- }
- if (opts->linker_script) {
- load_path_bytes(c, opts->linker_script, &script);
- }
-
- /* Stage parallel CfreeBytesInput arrays for the linker. */
- if (opts->nobject_files) {
- obj_in = arena_array(c->tu, CfreeBytesInput, opts->nobject_files);
- for (i = 0; i < opts->nobject_files; ++i) obj_in[i] = obj_bytes[i].in;
- }
- if (opts->narchives) {
- arch_in = arena_array(c->tu, CfreeBytesInput, opts->narchives);
- for (i = 0; i < opts->narchives; ++i) arch_in[i] = arch_bytes[i].in;
- }
-
- link_opts.objs = (ObjBuilder* const*)objs;
- link_opts.nobjs = nsrc;
- link_opts.obj_bytes = obj_in;
- link_opts.nobj_bytes = opts->nobject_files;
- link_opts.archives = arch_in;
- link_opts.narchives = opts->narchives;
- link_opts.linker_script_text = script.loaded ? (const char*)script.file.data : NULL;
- link_opts.linker_script_len = script.loaded ? script.file.size : 0;
- link_opts.entry = opts->entry;
- link_opts.extern_resolver = opts->extern_resolver;
- link_opts.extern_resolver_user = opts->extern_resolver_user;
-
- if (opts->output_kind == CFREE_OUTPUT_EXE) {
- out_writer = opts->env.file_io->open_writer(opts->env.file_io->user,
- opts->output_path);
- if (!out_writer) {
- compiler_panic(c, no_loc(), "failed to open output file: %s",
- opts->output_path);
- }
- if (cfree_link_exe(c, &link_opts, out_writer)) {
- writer_close(out_writer);
- release_loaded_array(c, src_loaded, opts->nsource_files);
- release_loaded_array(c, obj_bytes, opts->nobject_files);
- release_loaded_array(c, arch_bytes, opts->narchives);
- release_loaded_bytes(c, &script);
- compiler_fini(c);
- return 1;
- }
- writer_close(out_writer);
- out_writer = NULL;
- } else {
- if (cfree_link_jit(c, &link_opts, opts->out_jit)) {
- release_loaded_array(c, src_loaded, opts->nsource_files);
- release_loaded_array(c, obj_bytes, opts->nobject_files);
- release_loaded_array(c, arch_bytes, opts->narchives);
- release_loaded_bytes(c, &script);
- compiler_fini(c);
- return 1;
- }
- }
-
- release_loaded_array(c, src_loaded, opts->nsource_files);
- release_loaded_array(c, obj_bytes, opts->nobject_files);
- release_loaded_array(c, arch_bytes, opts->narchives);
- release_loaded_bytes(c, &script);
- /* objs (ObjBuilders) are owned by the Compiler; arena scratch is freed
- * by compiler_fini. */
- compiler_fini(c);
- return 0;
-}
diff --git a/src/driver/pipeline.h b/src/driver/pipeline.h
@@ -1,152 +0,0 @@
-#ifndef CFREE_PIPELINE_H
-#define CFREE_PIPELINE_H
-
-#include "core/core.h"
-#include "link/link.h"
-#include "obj/obj.h"
-
-/* Layered driver-facing API. Three core operations + one convenience:
- *
- * cfree_compile_obj one C TU -> in-memory ObjBuilder (chains into link)
- * cfree_compile_obj_emit one C TU -> Writer (cc -c)
- * cfree_link_exe link inputs -> Writer (ld)
- * cfree_link_jit link inputs -> owning CfreeJit handle
- * cfree_run convenience: compile N inputs and link/jit
- *
- * The freestanding core takes only byte buffers and Writers — never paths.
- * Path-shaped helpers live in driver-level adapters (and in cfree_run, which
- * is itself a driver convenience), and feed the byte/Writer APIs after
- * consulting Compiler.env->file_io.
- *
- * Errors are reported through Compiler.panic. Each top-level function in
- * this header saves and restores Compiler.panic around its own setjmp, so
- * these functions are safely nestable: a caller that has already installed
- * a panic handler keeps it across calls into this API. On panic the inner
- * function runs compiler_run_cleanups, restores the caller's jmp_buf, and
- * returns nonzero. */
-
-typedef struct CfreeDefine {
- const char* name;
- const char* body; /* NULL means "1" */
-} CfreeDefine;
-
-/* Generic byte-buffer input. Used for C source TUs, encoded objects, and
- * archives. `name` is a diagnostic label (typically a path or pseudo-path);
- * the linker interns it on entry. `data` may be any byte-shaped content. */
-typedef struct CfreeBytesInput {
- const char* name;
- const u8* data;
- size_t len;
-} CfreeBytesInput;
-
-/* Preprocessor configuration shared by compile_* and the convenience run. */
-typedef struct CfreePpOptions {
- const char* const* include_dirs;
- u32 ninclude_dirs;
- const char* const* system_include_dirs;
- u32 nsystem_include_dirs;
- const CfreeDefine* defines;
- u32 ndefines;
- const char* const* undefines;
- u32 nundefines;
-} CfreePpOptions;
-
-/* Per-TU compile knobs. */
-typedef struct CfreeCompileOptions {
- int opt_level; /* 0 direct, 1 minimal, 2 full */
- int debug_info;
- CfreePpOptions pp;
-} CfreeCompileOptions;
-
-/* Compile one C input (memory bytes).
- *
- * cfree_compile_obj returns an ObjBuilder owned by Compiler. The builder is
- * already finalized; do not write to it further. Pass it to link_add_obj or
- * an emit_* function. It must be alive until the linker has consumed it
- * (link_resolve). The Compiler must outlive the returned builder.
- *
- * cfree_compile_obj_emit writes the encoded object to `out` and frees its
- * temporary builder before returning. The Writer is not closed. On nonzero
- * return the Writer may contain partial output and should not be consumed.
- *
- * Returns 0 on success, nonzero if the compiler panicked. */
-int cfree_compile_obj(Compiler*, const CfreeCompileOptions*,
- const CfreeBytesInput* input, ObjBuilder** out);
-int cfree_compile_obj_emit(Compiler*, const CfreeCompileOptions*,
- const CfreeBytesInput* input, Writer* out);
-
-typedef struct CfreeLinkOptions {
- ObjBuilder* const* objs; /* fresh-compiled, by reference */
- u32 nobjs;
- const CfreeBytesInput* obj_bytes;
- u32 nobj_bytes;
- const CfreeBytesInput* archives;
- u32 narchives;
- const char* linker_script_text; /* NULL = no script.
- * Non-NULL: linker_script_len
- * must match the buffer. */
- size_t linker_script_len;
- Sym entry; /* 0 = format/target default */
- LinkExternResolver extern_resolver;
- void* extern_resolver_user;
-} CfreeLinkOptions;
-
-/* All bytes inputs (obj_bytes, archives) must remain alive until the
- * matching cfree_link_* call returns. */
-
-/* Link to executable. Writer is not closed by the call. On nonzero return
- * the Writer may contain partial output and should not be consumed. */
-int cfree_link_exe(Compiler*, const CfreeLinkOptions*, Writer* out);
-
-/* Link as JIT. On success, *out_jit owns its LinkImage and mapped pages and
- * must be released with cfree_jit_free. */
-int cfree_link_jit(Compiler*, const CfreeLinkOptions*, CfreeJit** out_jit);
-
-/* ----- Convenience: compose compile + link/jit for the common case. ----- */
-
-typedef enum CfreeOutputKind {
- CFREE_OUTPUT_OBJ, /* exactly one source input total */
- CFREE_OUTPUT_EXE,
- CFREE_OUTPUT_JIT,
-} CfreeOutputKind;
-
-typedef struct CfreeOptions {
- Target target;
- CfreeEnv env;
- CfreeOutputKind output_kind;
-
- int opt_level;
- int debug_info;
-
- const char* output_path; /* OBJ/EXE: required, used via env.file_io */
-
- /* C source inputs. Path-shaped sources are read via env.file_io; memory
- * sources are passed directly. The combined sequence (paths first, then
- * memory) is compiled in order. For OBJ output exactly one total source
- * is required. */
- const char* const* source_files;
- u32 nsource_files;
- const CfreeBytesInput* source_memory;
- u32 nsource_memory;
-
- CfreePpOptions pp;
-
- /* Path-shaped link inputs: driver reads via env.file_io. Library
- * resolution (-lfoo against -L paths) is the CLI driver's job; by the
- * time options reach cfree_run, archives must be concrete paths. */
- const char* const* object_files;
- u32 nobject_files;
- const char* const* archives;
- u32 narchives;
- const char* linker_script; /* path; driver reads via env.file_io */
-
- Sym entry;
- LinkExternResolver extern_resolver;
- void* extern_resolver_user;
-
- CfreeJit** out_jit; /* JIT only: caller owns on success */
-} CfreeOptions;
-
-int cfree_run(const CfreeOptions*);
-
-#endif
diff --git a/src/link/link.h b/src/link/link.h
@@ -1,11 +1,11 @@
#ifndef CFREE_LINK_H
#define CFREE_LINK_H
+#include <cfree.h>
#include "obj/obj.h"
typedef struct Linker Linker;
typedef struct LinkImage LinkImage;
-typedef struct CfreeJit CfreeJit;
typedef enum LinkInputKind {
LINK_INPUT_OBJ,
@@ -85,7 +85,9 @@ typedef struct LinkRelocApply {
i64 addend;
} LinkRelocApply;
-typedef void* (*LinkExternResolver)(void* user, Sym name);
+/* Internal resolver type matches the public CfreeExternResolver: name is a
+ * C string at the linker boundary. The Linker interns it on entry. */
+typedef CfreeExternResolver LinkExternResolver;
Linker* link_new(Compiler*);
void link_free(Linker*);
@@ -103,7 +105,7 @@ LinkInputId link_add_obj_bytes(Linker*, const char* name,
LinkInputId link_add_archive_bytes(Linker*, const char* name,
const u8* data, size_t len);
-void link_set_entry(Linker*, Sym name);
+void link_set_entry(Linker*, const char* name);
void link_set_script_text(Linker*, const char* text, size_t len);
void link_set_extern_resolver(Linker*, LinkExternResolver, void* user);
@@ -134,15 +136,9 @@ void link_emit_image_writer(LinkImage*, Writer*);
/* JIT: maps the image into executable memory and returns an owning handle.
* The returned CfreeJit takes ownership of the LinkImage (undefers it from
* the cleanup stack registered by link_resolve); on cfree_jit_free both the
- * JIT mapping and the LinkImage are released. Symbol lookup is by resolved
- * Sym name (object-local ObjSymIds never escape as JIT handles). Sym is
- * for callers that already interned the name; the const char* form interns
- * lazily. */
-typedef void (*CfreeJitFn)(void);
-
-CfreeJit* cfree_jit_from_image(LinkImage*);
-void cfree_jit_free(CfreeJit*);
-CfreeJitFn cfree_jit_lookup(CfreeJit*, const char* name);
-CfreeJitFn cfree_jit_lookup_sym(CfreeJit*, Sym name);
+ * JIT mapping and the LinkImage are released. Lookup is by name; the public
+ * `cfree_jit_lookup` and `cfree_jit_free` declarations live in
+ * <cfree.h>. */
+CfreeJit* cfree_jit_from_image(LinkImage*);
#endif
diff --git a/src/obj/obj.h b/src/obj/obj.h
@@ -158,8 +158,10 @@ typedef struct ObjGroup {
* 4. obj_finalize closes the builder: computes flat section offsets, applies
* pending fixups within sections, and freezes the read-side view.
* No further writes are permitted afterward.
- * 5. File emitters and the linker consume via the read API. */
-typedef struct ObjBuilder ObjBuilder;
+ * 5. File emitters and the linker consume via the read API.
+ *
+ * The handle type itself is the public CfreeObjBuilder, aliased to ObjBuilder
+ * inside libcfree (see src/core/core.h). */
ObjBuilder* obj_new(Compiler*);
void obj_free(ObjBuilder*);
@@ -214,15 +216,9 @@ ObjSymIter* obj_symiter_new (const ObjBuilder*);
int obj_symiter_next(ObjSymIter*, ObjSymEntry* out); /* returns 0 at end */
void obj_symiter_free(ObjSymIter*);
-/* ---- streaming output sink (for file emitters) ---- */
-typedef struct Writer Writer;
-Writer* writer_file(const char* path);
-Writer* writer_mem (Heap*);
-void writer_write(Writer*, const void* data, size_t n);
-void writer_seek (Writer*, u64 offset);
-u64 writer_tell (Writer*);
-int writer_error(Writer*);
-void writer_close(Writer*);
+/* Writer is the public CfreeWriter type aliased to Writer inside libcfree
+ * (see src/core/core.h). The streaming API lives in <cfree.h> as
+ * cfree_writer_*. */
/* ---- file format emitters ---- */
void emit_elf (Compiler*, ObjBuilder*, Writer*);