kit

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

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:
MMakefile | 50+++++++++++++++++++++++++++++++++++++++++++-------
Adriver/ar.c | 10++++++++++
Adriver/as.c | 10++++++++++
Adriver/cc.c | 11+++++++++++
Adriver/cpp.c | 10++++++++++
Adriver/dbg.c | 10++++++++++
Adriver/driver.h | 31+++++++++++++++++++++++++++++++
Adriver/ld.c | 10++++++++++
Adriver/main.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Adriver/objdump.c | 10++++++++++
Ainclude/cfree.h | 286+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/api/pipeline.c | 624+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/arena.h | 26++++++++++++++++++++++++++
Asrc/core/buf.h | 34++++++++++++++++++++++++++++++++++
Asrc/core/core.h | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/diag.h | 25+++++++++++++++++++++++++
Asrc/core/heap.h | 16++++++++++++++++
Asrc/core/pool.h | 21+++++++++++++++++++++
Dsrc/driver/driver.h | 23-----------------------
Dsrc/driver/pipeline.c | 589-------------------------------------------------------------------------------
Dsrc/driver/pipeline.h | 152-------------------------------------------------------------------------------
Msrc/link/link.h | 22+++++++++-------------
Msrc/obj/obj.h | 18+++++++-----------
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*);