kit

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

commit 4b346e744cca9faad6fd986ceb5f1d7e78e3d067
parent 30e75d19b7e18b8ef01b44fe1ea16eee9233e8ee
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  8 May 2026 16:59:16 -0700

driver: cflags/lib_resolve/target split, dbg subcommand, expanded ld/cc/objdump

Diffstat:
Mdriver/ar.c | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdriver/as.c | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/cc.c | 1108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Adriver/cflags.c | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/cflags.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/dbg.c | 1155++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdriver/driver.h | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/env.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/ld.c | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Adriver/lib_resolve.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/lib_resolve.h | 26++++++++++++++++++++++++++
Mdriver/objdump.c | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdriver/run.c | 286++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Adriver/target.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 3966 insertions(+), 446 deletions(-)

diff --git a/driver/ar.c b/driver/ar.c @@ -1,134 +1,298 @@ #include "driver.h" -/* `cfree ar` — create/list POSIX ar archives. +/* `cfree ar` — POSIX ar archive front-end. * - * Supported operations: - * cfree ar {r|c|rc|cr} out.a in.o... create / replace archive - * cfree ar t in.a list member names */ + * Supported operations (mutually exclusive): + * cfree ar {r|c|rc|cr} archive.a in.o... create / replace archive + * cfree ar t archive.a list member names + * cfree ar x archive.a [members...] extract members to cwd + * cfree ar p archive.a [members...] print members to stdout + * + * Reproducibility: SOURCE_DATE_EPOCH (when set to a positive integer) is + * written to ar_date for every member on write. Long member names are + * routed through a `//` extended-name table. + * + * Not yet implemented: d (delete), q (quick append), s (symbol index), + * v (verbose). Encountering any of those mode letters yields a usage + * error with exit code 2. */ #define AR_TOOL "ar" static void ar_usage(void) { driver_errf(AR_TOOL, "%s", - "usage: cfree ar {rc|r|c|t} archive.a [file.o...]"); + "usage: cfree ar {rc|r|c|t|x|p} archive.a [members...]"); } -int driver_ar(int argc, char** argv) +/* Parse SOURCE_DATE_EPOCH into a u64; 0 (or unset/invalid) means "no epoch". */ +static uint64_t ar_epoch_from_env(void) { - DriverEnv env; - const char* mode; - const char* archive_path; - int do_write = 0; - int do_list = 0; - int i; - int rc = 0; - - if (argc < 3) { - ar_usage(); - return 2; + const char* s = driver_getenv("SOURCE_DATE_EPOCH"); + uint64_t v = 0; + if (!s || !*s) return 0; + for (; *s; ++s) { + if (*s < '0' || *s > '9') return 0; + v = v * 10 + (uint64_t)(*s - '0'); } + return v; +} - mode = argv[1]; - archive_path = argv[2]; - - for (i = 0; mode[i]; ++i) { - if (mode[i] == 'r' || mode[i] == 'c') do_write = 1; - else if (mode[i] == 't') do_list = 1; +/* Return 1 iff `name` matches any of the names in argv[start..argc) — or if + * there are no filters, in which case every member matches. */ +static int ar_name_selected(const char* name, + int argc, char** argv, int start) +{ + int i; + if (start >= argc) return 1; + for (i = start; i < argc; ++i) { + const char* base = driver_basename(argv[i]); + if (driver_streq(name, base)) return 1; } + return 0; +} - if (!do_write && !do_list) { - driver_errf(AR_TOOL, "unrecognized operation: %s", mode); - return 2; +/* Open the archive, init the iterator. Caller must release fd via cenv. */ +static int ar_open_for_read(DriverEnv* env, const char* path, + CfreeEnv* cenv_out, CfreeFileData* fd_out, + CfreeBytesInput* input_out) +{ + *cenv_out = driver_env_to_cfree(env); + if (!cenv_out->file_io->read_all(cenv_out->file_io->user, path, fd_out)) { + driver_errf(AR_TOOL, "failed to read: %s", path); + return 0; } - if (do_write && do_list) { - driver_errf(AR_TOOL, "conflicting operations: %s", mode); - return 2; + input_out->name = path; + input_out->data = fd_out->data; + input_out->len = fd_out->size; + return 1; +} + +static int ar_do_list(DriverEnv* env, const char* archive_path) +{ + CfreeEnv cenv; + CfreeFileData fd = {0}; + CfreeBytesInput input; + CfreeWriter* out; + int rc; + + if (!ar_open_for_read(env, archive_path, &cenv, &fd, &input)) return 1; + + out = driver_stdout_writer(env); + if (!out) { + driver_errf(AR_TOOL, "out of memory"); + cenv.file_io->release(cenv.file_io->user, &fd); + return 1; } - driver_env_init(&env); + rc = cfree_ar_list(&input, out); + if (rc) driver_errf(AR_TOOL, "failed to read archive: %s", archive_path); + cfree_writer_close(out); + cenv.file_io->release(cenv.file_io->user, &fd); + return rc; +} - if (do_list) { - CfreeEnv cenv = driver_env_to_cfree(&env); - CfreeFileData fd = {0}; - CfreeBytesInput input; - CfreeWriter* out; +static int ar_do_extract(DriverEnv* env, const char* archive_path, + int argc, char** argv, int start) +{ + CfreeEnv cenv; + CfreeFileData fd = {0}; + CfreeBytesInput input; + CfreeArIter it; + CfreeArMember m; + int rc = 0; - if (!cenv.file_io->read_all(cenv.file_io->user, archive_path, &fd)) { - driver_errf(AR_TOOL, "failed to read: %s", archive_path); - driver_env_fini(&env); - return 1; - } + if (!ar_open_for_read(env, archive_path, &cenv, &fd, &input)) return 1; + if (!cfree_ar_iter_init(&it, &input)) { + driver_errf(AR_TOOL, "not an archive: %s", archive_path); + cenv.file_io->release(cenv.file_io->user, &fd); + return 1; + } - input.name = archive_path; - input.data = fd.data; - input.len = fd.size; + while (cfree_ar_iter_next(&it, &m)) { + CfreeWriter* out; + if (!ar_name_selected(m.name, argc, argv, start)) continue; - out = driver_stdout_writer(&env); + out = cenv.file_io->open_writer(cenv.file_io->user, m.name); if (!out) { - driver_errf(AR_TOOL, "out of memory"); - cenv.file_io->release(cenv.file_io->user, &fd); - driver_env_fini(&env); - return 1; + driver_errf(AR_TOOL, "failed to open: %s", m.name); + rc = 1; + continue; + } + if (m.size) cfree_writer_write(out, m.data, m.size); + if (cfree_writer_error(out)) { + driver_errf(AR_TOOL, "write failed: %s", m.name); + rc = 1; } - - rc = cfree_ar_list(&input, out); - if (rc) driver_errf(AR_TOOL, "failed to read archive: %s", archive_path); cfree_writer_close(out); + } + + cenv.file_io->release(cenv.file_io->user, &fd); + return rc; +} + +static int ar_do_print(DriverEnv* env, const char* archive_path, + int argc, char** argv, int start) +{ + CfreeEnv cenv; + CfreeFileData fd = {0}; + CfreeBytesInput input; + CfreeArIter it; + CfreeArMember m; + CfreeWriter* out; + int multi = (argc - start) != 1; /* prefix when 0 or >=2 filters */ + int rc = 0; + + if (!ar_open_for_read(env, archive_path, &cenv, &fd, &input)) return 1; + if (!cfree_ar_iter_init(&it, &input)) { + driver_errf(AR_TOOL, "not an archive: %s", archive_path); cenv.file_io->release(cenv.file_io->user, &fd); - } else { - /* Write archive. */ - int nmembers = argc - 3; - uint32_t nm = nmembers > 0 ? (uint32_t)nmembers : 0u; - CfreeBytesInput* members = NULL; - CfreeFileData* fds = NULL; - CfreeEnv cenv = driver_env_to_cfree(&env); - CfreeWriter* out = NULL; - - if (nm > 0) { - members = (CfreeBytesInput*)driver_alloc_zeroed( - &env, (size_t)nm * sizeof(*members)); - fds = (CfreeFileData*)driver_alloc_zeroed( - &env, (size_t)nm * sizeof(*fds)); - if (!members || !fds) { - driver_errf(AR_TOOL, "out of memory"); + return 1; + } + + out = driver_stdout_writer(env); + if (!out) { + driver_errf(AR_TOOL, "out of memory"); + cenv.file_io->release(cenv.file_io->user, &fd); + return 1; + } + + while (cfree_ar_iter_next(&it, &m)) { + if (!ar_name_selected(m.name, argc, argv, start)) continue; + if (multi) driver_printf("%s(%s):\n", archive_path, m.name); + if (m.size) cfree_writer_write(out, m.data, m.size); + } + if (cfree_writer_error(out)) rc = 1; + + cfree_writer_close(out); + cenv.file_io->release(cenv.file_io->user, &fd); + return rc; +} + +static int ar_do_write(DriverEnv* env, const char* archive_path, + int nmembers, char** member_paths) +{ + uint32_t nm = nmembers > 0 ? (uint32_t)nmembers : 0u; + CfreeBytesInput* members = NULL; + CfreeFileData* fds = NULL; + CfreeEnv cenv = driver_env_to_cfree(env); + CfreeWriter* out = NULL; + CfreeArWriteOptions opts = {0}; + int rc = 0; + int i; + + opts.epoch = ar_epoch_from_env(); + opts.long_names = 1; + + if (nm > 0) { + members = (CfreeBytesInput*)driver_alloc_zeroed( + env, (size_t)nm * sizeof(*members)); + fds = (CfreeFileData*)driver_alloc_zeroed( + env, (size_t)nm * sizeof(*fds)); + if (!members || !fds) { + driver_errf(AR_TOOL, "out of memory"); + rc = 1; + goto done; + } + for (i = 0; i < (int)nm && rc == 0; ++i) { + const char* path = member_paths[i]; + if (!cenv.file_io->read_all(cenv.file_io->user, path, &fds[i])) { + driver_errf(AR_TOOL, "failed to read: %s", path); rc = 1; - goto ar_done; - } - for (i = 0; i < (int)nm && rc == 0; ++i) { - const char* path = argv[3 + i]; - if (!cenv.file_io->read_all(cenv.file_io->user, path, &fds[i])) { - driver_errf(AR_TOOL, "failed to read: %s", path); - rc = 1; - break; - } - members[i].name = driver_basename(path); - members[i].data = fds[i].data; - members[i].len = fds[i].size; + break; } + members[i].name = driver_basename(path); + members[i].data = fds[i].data; + members[i].len = fds[i].size; } + } - if (rc == 0) { - out = cenv.file_io->open_writer(cenv.file_io->user, archive_path); - if (!out) { - driver_errf(AR_TOOL, "failed to open: %s", archive_path); - rc = 1; - } else { - rc = cfree_ar_write(out, members, nm); - if (rc == 0 && cfree_writer_error(out)) rc = 1; - cfree_writer_close(out); - } + if (rc == 0) { + out = cenv.file_io->open_writer(cenv.file_io->user, archive_path); + if (!out) { + driver_errf(AR_TOOL, "failed to open: %s", archive_path); + rc = 1; + } else { + rc = cfree_ar_write(out, members, nm, &opts); + if (rc == 0 && cfree_writer_error(out)) rc = 1; + cfree_writer_close(out); } + } - ar_done: - if (fds) { - for (i = 0; i < (int)nm; ++i) { - if (fds[i].data) - cenv.file_io->release(cenv.file_io->user, &fds[i]); - } - driver_free(&env, fds, (size_t)nm * sizeof(*fds)); +done: + if (fds) { + for (i = 0; i < (int)nm; ++i) { + if (fds[i].data) + cenv.file_io->release(cenv.file_io->user, &fds[i]); } - if (members) driver_free(&env, members, (size_t)nm * sizeof(*members)); + driver_free(env, fds, (size_t)nm * sizeof(*fds)); + } + if (members) driver_free(env, members, (size_t)nm * sizeof(*members)); + return rc; +} + +int driver_ar(int argc, char** argv) +{ + DriverEnv env; + const char* mode; + const char* archive_path; + int do_write = 0; + int do_list = 0; + int do_extract = 0; + int do_print = 0; + int i; + int rc; + + if (argc < 3) { + ar_usage(); + return 2; + } + + mode = argv[1]; + archive_path = argv[2]; + + for (i = 0; mode[i]; ++i) { + switch (mode[i]) { + case 'r': case 'c': do_write = 1; break; + case 't': do_list = 1; break; + case 'x': do_extract = 1; break; + case 'p': do_print = 1; break; + case 'd': case 'q': case 's': case 'v': + driver_errf(AR_TOOL, "operation not implemented: %c", mode[i]); + return 2; + default: + driver_errf(AR_TOOL, "unrecognized mode letter: %c", mode[i]); + return 2; + } + } + + { + int kinds = !!do_write + !!do_list + !!do_extract + !!do_print; + if (kinds == 0) { + driver_errf(AR_TOOL, "no operation in mode: %s", mode); + ar_usage(); + return 2; + } + if (kinds > 1) { + driver_errf(AR_TOOL, "conflicting operations: %s", mode); + return 2; + } + } + + driver_env_init(&env); + + if (do_list) { + if (argc != 3) { + driver_errf(AR_TOOL, "t takes no member arguments"); + driver_env_fini(&env); + return 2; + } + rc = ar_do_list(&env, archive_path); + } else if (do_extract) { + rc = ar_do_extract(&env, archive_path, argc, argv, 3); + } else if (do_print) { + rc = ar_do_print(&env, archive_path, argc, argv, 3); + } else { + rc = ar_do_write(&env, archive_path, argc - 3, argv + 3); } driver_env_fini(&env); diff --git a/driver/as.c b/driver/as.c @@ -1,9 +1,132 @@ #include "driver.h" +#include <stdint.h> + +/* `cfree as` — standalone assembler. Reads a single text source, writes a + * relocatable object via cfree_assemble_obj_emit. The accepted input is a + * GAS subset (AT&T syntax on x86); see <cfree.h> for the contract on + * CfreeAsmOptions. */ + +#define AS_TOOL "as" + +typedef struct AsOptions { + int debug_info; /* -g */ + const char* output_path; /* -o */ + const char* source; /* single positional input */ + CfreeTarget target; /* -target TRIPLE; host default */ +} AsOptions; + +static void as_usage(void) +{ + driver_errf(AS_TOOL, "%s", + "usage: cfree as [-g] [-target TRIPLE] -o out.o input.s"); +} + +/* Returns 0 on success; non-zero on bad args (already reported). */ +static int as_parse(int argc, char** argv, AsOptions* o) +{ + int i; + + o->target = driver_host_target(); + + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + + if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } + + if (driver_streq(a, "-o")) { + if (++i >= argc) { driver_errf(AS_TOOL, "-o requires an argument"); return 1; } + o->output_path = argv[i]; + continue; + } + + if (driver_streq(a, "-target")) { + if (++i >= argc) { driver_errf(AS_TOOL, "-target requires an argument"); return 1; } + if (driver_target_from_triple(argv[i], &o->target) != 0) { + driver_errf(AS_TOOL, "unrecognized target: %s", argv[i]); + return 1; + } + continue; + } + + if (a[0] == '-' && a[1] != '\0') { + driver_errf(AS_TOOL, "unknown flag: %s", a); + return 1; + } + + if (o->source) { + driver_errf(AS_TOOL, "multiple inputs not supported"); + return 1; + } + o->source = a; + } + + if (!o->source) { + driver_errf(AS_TOOL, "no input file"); + as_usage(); + return 1; + } + if (!o->output_path) { + driver_errf(AS_TOOL, "-o is required"); + return 1; + } + return 0; +} + int driver_as(int argc, char** argv) { - (void)argc; - (void)argv; - /* TODO: standalone assembler. */ - return 1; + DriverEnv env; + AsOptions o = {0}; + CfreeEnv cenv; + CfreeCompiler* compiler = NULL; + CfreeWriter* writer = NULL; + CfreeFileData src = {0}; + CfreeBytesInput input; + CfreeAsmOptions aopts; + CfreeAsmOptions zero = {0}; + int rc = 1; + int loaded = 0; + + driver_env_init(&env); + + if (as_parse(argc, argv, &o) != 0) { + driver_env_fini(&env); + return 2; + } + + cenv = driver_env_to_cfree(&env); + + if (!cenv.file_io->read_all(cenv.file_io->user, o.source, &src)) { + driver_errf(AS_TOOL, "failed to read: %s", o.source); + goto out; + } + loaded = 1; + + writer = cenv.file_io->open_writer(cenv.file_io->user, o.output_path); + if (!writer) { + driver_errf(AS_TOOL, "failed to open output: %s", o.output_path); + goto out; + } + + compiler = cfree_compiler_new(o.target, &cenv); + if (!compiler) { + driver_errf(AS_TOOL, "failed to initialize compiler"); + goto out; + } + + aopts = zero; + aopts.debug_info = o.debug_info; + + input.name = o.source; + input.data = src.data; + input.len = src.size; + + rc = cfree_assemble_obj_emit(compiler, &aopts, &input, writer); + +out: + if (compiler) cfree_compiler_free(compiler); + if (writer) cfree_writer_close(writer); + if (loaded) cenv.file_io->release(cenv.file_io->user, &src); + driver_env_fini(&env); + return rc; } diff --git a/driver/cc.c b/driver/cc.c @@ -1,38 +1,113 @@ +#include "cflags.h" #include "driver.h" +#include "lib_resolve.h" #include <stdint.h> -/* `cfree cc` — compile C sources. With -c produces a single object; - * without -c compiles all sources and links to an executable. The flag - * surface is intentionally a basic subset of the GCC convention. */ +/* `cfree cc` — C compiler driver. With -c produces a single object; + * without -c compiles all C sources, links any .o/.a inputs alongside, and + * emits an executable. The flag surface is a GCC subset: + * + * -c -E -o -O0/1/2 -g + * -I -isystem -D -U + * -M -MM -MD -MMD -MF -MT -MQ -MP + * -target TRIPLE + * -fPIC -fPIE -fpic -fpie -mcmodel=small|medium|large + * -Werror -fmax-errors=N + * --build-id=none|sha256|uuid|0xHEX + * -ffile-prefix-map=old=new + * SOURCE_DATE_EPOCH (env) + * -e symbol -T script.ld + * -static -pie -no-pie + * -l name -L dir + * -x c (no-op; rejected for any other language) + * - (stdin source) + * .c/.cc/.cpp -> source; .o/.obj -> object inputs; .a -> archive inputs. + * + * Library resolution (-lfoo against -L paths) happens here and produces + * concrete archive paths for libcfree. */ #define CC_TOOL "cc" +/* Header-dependency emission mode (subset of GCC's -M family). + * M — print all deps; do not compile. + * MM — like M but skip <bracketed> includes. + * MD — compile normally AND write all deps to a file. + * MMD — like MD but skip <bracketed> includes. + * MD/MMD currently require -c (single-source compile_obj_emit path). */ +typedef enum CcDepMode { + CC_DEP_NONE = 0, + CC_DEP_M, + CC_DEP_MM, + CC_DEP_MD, + CC_DEP_MMD, +} CcDepMode; + typedef struct CcOptions { - DriverEnv* env; /* heap for scratch arrays */ - size_t argv_bound; /* upper bound on list size */ - - int compile_only; /* -c */ - int preprocess_only; /* -E */ - int opt_level; /* -O0/-O1/-O2 (default 0) */ - int debug_info; /* -g */ - const char* output_path; /* -o */ - const char** include_dirs; /* -I */ - uint32_t ninclude_dirs; - const char** system_include_dirs; /* -isystem */ - uint32_t nsystem_include_dirs; - CfreeDefine* defines; /* -D name[=body] */ - uint32_t ndefines; - const char** undefines; /* -U */ - uint32_t nundefines; - const char** sources; /* positional */ - uint32_t nsources; - /* Owning storage for split -D names. Indexed in parallel with - * defines[]; entries are NULL when the body was unspecified or argv - * already provides the standalone name. The (name, length+1) pair is - * needed at release time to free through the heap. */ - char** owned_define_names; - size_t* owned_define_name_sizes; + DriverEnv* env; + size_t argv_bound; /* upper bound on per-array list size */ + + int compile_only; /* -c */ + int preprocess_only; /* -E */ + int opt_level; /* -O0/-O1/-O2 */ + int debug_info; /* -g */ + int warnings_are_errors; /* -Werror */ + uint32_t max_errors; /* -fmax-errors=N */ + CfreeTarget target; /* -target / host */ + int target_set; /* did -target appear */ + const char* output_path; /* -o */ + int output_path_set; + const char* entry; /* -e */ + const char* linker_script; /* -T path */ + + /* -ffile-prefix-map=old=new entries; old is heap-owned (split out), new + * aliases argv. */ + CfreePathPrefixMap* path_map; + uint32_t npath_map; + char** owned_path_map_olds; + size_t* owned_path_map_old_sizes; + + /* Build-id. mode is a CfreeBuildIdMode value; bytes/len are owned and + * non-NULL only when mode == CFREE_BUILDID_USER. */ + uint8_t build_id_mode; + uint8_t* build_id_bytes; + uint32_t build_id_len; + + /* Reproducibility: SOURCE_DATE_EPOCH parsed at end-of-parse. */ + uint64_t epoch; + + /* Cflags via shared helper. */ + DriverCflags cf; + + /* Positional inputs split by suffix. */ + const char** source_files; /* .c/.cc/.cpp paths */ + uint32_t nsource_files; + CfreeBytesInput* source_memory; /* "-" stdin slurp */ + uint32_t nsource_memory; + uint8_t* stdin_buf; /* owning storage for the one stdin */ + size_t stdin_size; + const char** object_files; /* .o/.obj paths */ + uint32_t nobject_files; + const char** archives; /* .a paths + resolved -l names */ + uint32_t narchives; + /* Sub-list of `archives[]` whose buffers are heap-owned (resolved -l). + * Their (path, size) sit in parallel arrays for free(). */ + char** owned_archives; + size_t* owned_archive_sizes; + uint32_t nowned_archives; + /* -L search paths (argv-borrowed). */ + const char** lib_search_paths; + uint32_t nlib_search_paths; + /* Pending -l names (resolved at end-of-parse). */ + const char** pending_libs; + uint32_t npending_libs; + + /* -M family */ + int dep_mode; /* CcDepMode */ + int dep_phony; /* -MP */ + const char* dep_file; /* -MF */ + const char** dep_targets; /* -MT/-MQ */ + uint32_t ndep_targets; } CcOptions; static void cc_usage(void) @@ -41,241 +116,928 @@ static void cc_usage(void) "usage: cfree cc [-c|-E] [-o out] [-O0|-O1|-O2] [-g]\n" " [-I dir]... [-isystem dir]...\n" " [-D name[=body]]... [-U name]...\n" - " input.c..."); + " [-M|-MM|-MD|-MMD] [-MF file] [-MT target]... [-MP]\n" + " [-target TRIPLE] [-fPIC|-fPIE|-fpic|-fpie]\n" + " [-mcmodel=small|medium|large]\n" + " [-Werror] [-fmax-errors=N]\n" + " [--build-id=none|sha256|uuid|0xHEX]\n" + " [-ffile-prefix-map=old=new]\n" + " [-e symbol] [-T script]\n" + " [-static|-pie|-no-pie]\n" + " [-l name]... [-L dir]...\n" + " [-x c] input.c... [-] [input.o]... [input.a]..."); } static int cc_alloc_arrays(CcOptions* o, int argc) { size_t bound = (size_t)argc; - o->argv_bound = bound; - o->include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->include_dirs)); - o->system_include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->system_include_dirs)); - o->defines = driver_alloc_zeroed(o->env, bound * sizeof(*o->defines)); - o->owned_define_names = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_names)); - o->owned_define_name_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_name_sizes)); - o->undefines = driver_alloc_zeroed(o->env, bound * sizeof(*o->undefines)); - o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources)); - if (!o->include_dirs || !o->system_include_dirs || !o->defines || - !o->owned_define_names || !o->owned_define_name_sizes || - !o->undefines || !o->sources) { + o->argv_bound = bound; + o->source_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->source_files)); + o->source_memory = driver_alloc_zeroed(o->env, bound * sizeof(*o->source_memory)); + o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files)); + o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives)); + o->owned_archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_archives)); + o->owned_archive_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_archive_sizes)); + o->lib_search_paths = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_search_paths)); + o->pending_libs = driver_alloc_zeroed(o->env, bound * sizeof(*o->pending_libs)); + o->dep_targets = driver_alloc_zeroed(o->env, bound * sizeof(*o->dep_targets)); + o->path_map = driver_alloc_zeroed(o->env, bound * sizeof(*o->path_map)); + o->owned_path_map_olds = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_olds)); + o->owned_path_map_old_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_old_sizes)); + if (!o->source_files || !o->source_memory || !o->object_files || + !o->archives || !o->owned_archives || !o->owned_archive_sizes || + !o->lib_search_paths || !o->pending_libs || !o->dep_targets || + !o->path_map || !o->owned_path_map_olds || !o->owned_path_map_old_sizes) { driver_errf(CC_TOOL, "out of memory"); return 1; } + if (driver_cflags_init(&o->cf, o->env, argc) != 0) { + driver_errf(CC_TOOL, "out of memory"); + return 1; + } + return 0; +} + +static void cc_options_release(CcOptions* o) +{ + uint32_t i; + size_t bound = o->argv_bound; + for (i = 0; i < o->nowned_archives; ++i) { + driver_free(o->env, o->owned_archives[i], o->owned_archive_sizes[i]); + } + for (i = 0; i < o->npath_map; ++i) { + if (o->owned_path_map_olds[i]) { + driver_free(o->env, o->owned_path_map_olds[i], + o->owned_path_map_old_sizes[i]); + } + } + if (o->stdin_buf) driver_free(o->env, o->stdin_buf, o->stdin_size); + if (o->build_id_bytes) driver_free(o->env, o->build_id_bytes, o->build_id_len); + driver_cflags_fini(&o->cf, o->env); + driver_free(o->env, o->source_files, bound * sizeof(*o->source_files)); + driver_free(o->env, o->source_memory, bound * sizeof(*o->source_memory)); + driver_free(o->env, o->object_files, bound * sizeof(*o->object_files)); + driver_free(o->env, o->archives, bound * sizeof(*o->archives)); + driver_free(o->env, o->owned_archives, bound * sizeof(*o->owned_archives)); + driver_free(o->env, o->owned_archive_sizes, bound * sizeof(*o->owned_archive_sizes)); + driver_free(o->env, o->lib_search_paths, bound * sizeof(*o->lib_search_paths)); + driver_free(o->env, o->pending_libs, bound * sizeof(*o->pending_libs)); + driver_free(o->env, o->dep_targets, bound * sizeof(*o->dep_targets)); + driver_free(o->env, o->path_map, bound * sizeof(*o->path_map)); + driver_free(o->env, o->owned_path_map_olds, bound * sizeof(*o->owned_path_map_olds)); + driver_free(o->env, o->owned_path_map_old_sizes, bound * sizeof(*o->owned_path_map_old_sizes)); +} + +/* Suffix predicate: is `s` a recognized C source suffix? */ +static int cc_is_c_source(const char* s) +{ + return driver_has_suffix(s, ".c") || + driver_has_suffix(s, ".cc") || + driver_has_suffix(s, ".cpp"); +} + +/* Decimal uint64 parse for SOURCE_DATE_EPOCH. Stops at the first non-digit; + * returns 0 on success and writes the parsed value, 1 if the string was + * empty or contained no digits, or on overflow. */ +static int cc_parse_u64(const char* s, uint64_t* out) +{ + uint64_t v = 0; + int any = 0; + if (!s) return 1; + while (*s) { + unsigned d; + if (*s < '0' || *s > '9') return 1; + d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - d) / 10u) return 1; + v = v * 10u + d; + any = 1; + s++; + } + if (!any) return 1; + *out = v; + return 0; +} + +/* Parse an even-length lowercase/uppercase hex string into a freshly- + * allocated byte buffer. `s` must point at the first hex digit (no `0x` + * prefix). Returns 0 on success. */ +static int cc_parse_hex_bytes(DriverEnv* env, const char* s, + uint8_t** out_bytes, uint32_t* out_len) +{ + size_t n = driver_strlen(s); + uint8_t* bs; + size_t i; + if (n == 0 || (n & 1u)) return 1; + bs = driver_alloc(env, n / 2u); + if (!bs) return 1; + for (i = 0; i < n; i += 2) { + unsigned hi, lo; + char c1 = s[i], c2 = s[i + 1]; + if (c1 >= '0' && c1 <= '9') hi = (unsigned)(c1 - '0'); + else if (c1 >= 'a' && c1 <= 'f') hi = (unsigned)(c1 - 'a' + 10); + else if (c1 >= 'A' && c1 <= 'F') hi = (unsigned)(c1 - 'A' + 10); + else { driver_free(env, bs, n / 2u); return 1; } + if (c2 >= '0' && c2 <= '9') lo = (unsigned)(c2 - '0'); + else if (c2 >= 'a' && c2 <= 'f') lo = (unsigned)(c2 - 'a' + 10); + else if (c2 >= 'A' && c2 <= 'F') lo = (unsigned)(c2 - 'A' + 10); + else { driver_free(env, bs, n / 2u); return 1; } + bs[i / 2u] = (uint8_t)((hi << 4) | lo); + } + *out_bytes = bs; + *out_len = (uint32_t)(n / 2u); return 0; } -static int cc_record_define(CcOptions* o, const char* arg) +/* Record one -ffile-prefix-map=OLD=NEW entry. `arg` points past the leading + * "-ffile-prefix-map=". Splits on the first '='; both halves may be empty. */ +static int cc_record_path_map(CcOptions* o, const char* arg) { - const char* eq = driver_strchr(arg, '='); - CfreeDefine* d = &o->defines[o->ndefines]; - if (eq) { + const char* eq = driver_strchr(arg, '='); + CfreePathPrefixMap* m = &o->path_map[o->npath_map]; + if (!eq) { + driver_errf(CC_TOOL, "-ffile-prefix-map requires old=new"); + return 1; + } + { size_t n = (size_t)(eq - arg); size_t bytes = n + 1; - char* name = driver_alloc(o->env, bytes); - if (!name) { driver_errf(CC_TOOL, "out of memory"); return 1; } - driver_memcpy(name, arg, n); - name[n] = '\0'; - o->owned_define_names[o->ndefines] = name; - o->owned_define_name_sizes[o->ndefines] = bytes; - d->name = name; - d->body = eq + 1; - } else { - d->name = arg; - d->body = NULL; + char* old_ = driver_alloc(o->env, bytes); + if (!old_) { driver_errf(CC_TOOL, "out of memory"); return 1; } + driver_memcpy(old_, arg, n); + old_[n] = '\0'; + o->owned_path_map_olds[o->npath_map] = old_; + o->owned_path_map_old_sizes[o->npath_map] = bytes; + m->old_prefix = old_; + m->new_prefix = eq + 1; + } + o->npath_map++; + return 0; +} + +/* Parse `--build-id=VALUE` (the leading `--build-id=` already stripped). */ +static int cc_record_build_id(CcOptions* o, const char* val) +{ + if (driver_streq(val, "none")) { o->build_id_mode = CFREE_BUILDID_NONE; return 0; } + if (driver_streq(val, "sha256")) { o->build_id_mode = CFREE_BUILDID_SHA256; return 0; } + if (driver_streq(val, "uuid")) { o->build_id_mode = CFREE_BUILDID_UUID; return 0; } + if (driver_strneq(val, "0x", 2)) { + if (cc_parse_hex_bytes(o->env, val + 2, + &o->build_id_bytes, &o->build_id_len) != 0) { + driver_errf(CC_TOOL, "--build-id=0x... requires an even-length hex string"); + return 1; + } + o->build_id_mode = CFREE_BUILDID_USER; + return 0; + } + driver_errf(CC_TOOL, "unknown --build-id value: %s", val); + return 1; +} + +static int cc_record_mcmodel(CcOptions* o, const char* val) +{ + if (driver_streq(val, "small")) { o->target.code_model = CFREE_CM_SMALL; return 0; } + if (driver_streq(val, "medium")) { o->target.code_model = CFREE_CM_MEDIUM; return 0; } + if (driver_streq(val, "large")) { o->target.code_model = CFREE_CM_LARGE; return 0; } + driver_errf(CC_TOOL, "unknown -mcmodel value: %s", val); + return 1; +} + +/* Slurp stdin into o->stdin_buf (single-source guarded by caller) and + * register it in source_memory[]. The diagnostic name is "<stdin>". */ +static int cc_record_stdin(CcOptions* o) +{ + CfreeBytesInput* in; + if (o->stdin_buf) { + driver_errf(CC_TOOL, "'-' (stdin) may appear at most once"); + return 1; + } + if (!driver_read_stdin(o->env, &o->stdin_buf, &o->stdin_size)) { + driver_errf(CC_TOOL, "failed to read stdin"); + return 1; + } + in = &o->source_memory[o->nsource_memory++]; + in->name = "<stdin>"; + in->data = o->stdin_buf; + in->len = o->stdin_size; + return 0; +} + +static int cc_classify_positional(CcOptions* o, const char* a) +{ + if (driver_streq(a, "-")) return cc_record_stdin(o); + if (cc_is_c_source(a)) { + o->source_files[o->nsource_files++] = a; + return 0; + } + if (driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj")) { + o->object_files[o->nobject_files++] = a; + return 0; + } + if (driver_has_suffix(a, ".a")) { + o->archives[o->narchives++] = a; + return 0; + } + driver_errf(CC_TOOL, "input does not have a recognized suffix: %s", a); + return 1; +} + +/* Resolve every accumulated -lname against -L paths; pushes resolved paths + * into archives[]. Reports first lookup failure. */ +static int cc_resolve_pending_libs(CcOptions* o) +{ + uint32_t i; + for (i = 0; i < o->npending_libs; ++i) { + char* p; + size_t sz; + if (driver_lib_resolve(o->env, o->pending_libs[i], + o->lib_search_paths, o->nlib_search_paths, + &p, &sz) != 0) { + driver_errf(CC_TOOL, "library not found: -l%s", o->pending_libs[i]); + return 1; + } + o->owned_archives[o->nowned_archives] = p; + o->owned_archive_sizes[o->nowned_archives] = sz; + o->nowned_archives++; + o->archives[o->narchives++] = p; + } + return 0; +} + +static int cc_apply_env(CcOptions* o) +{ + const char* sde = driver_getenv("SOURCE_DATE_EPOCH"); + if (sde && cc_parse_u64(sde, &o->epoch) != 0) { + driver_errf(CC_TOOL, "invalid SOURCE_DATE_EPOCH: %s", sde); + return 1; } - o->ndefines++; return 0; } /* Returns 0 on success; non-zero on bad args (already reported). */ static int cc_parse(int argc, char** argv, CcOptions* o) { + int x_lang_pinned = 0; /* set after a -x c is seen */ int i; + if (cc_alloc_arrays(o, argc) != 0) return 1; + o->target = driver_host_target(); for (i = 1; i < argc; ++i) { const char* a = argv[i]; - if (driver_streq(a, "-c")) { o->compile_only = 1; continue; } - if (driver_streq(a, "-E")) { o->preprocess_only = 1; continue; } - if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } - if (driver_streq(a, "-O0")) { o->opt_level = 0; continue; } - if (driver_streq(a, "-O1")) { o->opt_level = 1; continue; } - if (driver_streq(a, "-O2")) { o->opt_level = 2; continue; } + + /* Cflags first (-I/-isystem/-D/-U). */ + { + int r = driver_cflags_try_consume(&o->cf, o->env, CC_TOOL, argc, argv, &i); + if (r < 0) return 1; + if (r > 0) continue; + } + + if (driver_streq(a, "-c")) { o->compile_only = 1; continue; } + if (driver_streq(a, "-E")) { o->preprocess_only = 1; continue; } + if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } + if (driver_streq(a, "-O0")){ o->opt_level = 0; continue; } + if (driver_streq(a, "-O1")){ o->opt_level = 1; continue; } + if (driver_streq(a, "-O2")){ o->opt_level = 2; continue; } + + if (driver_streq(a, "-Werror")) { o->warnings_are_errors = 1; continue; } + + if (driver_strneq(a, "-fmax-errors=", 13)) { + uint64_t v; + if (cc_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { + driver_errf(CC_TOOL, "-fmax-errors= requires a non-negative integer"); + return 1; + } + o->max_errors = (uint32_t)v; + continue; + } + + if (driver_streq(a, "-fPIC")) { o->target.pic = CFREE_PIC_PIC; continue; } + if (driver_streq(a, "-fpic")) { o->target.pic = CFREE_PIC_PIC; continue; } + if (driver_streq(a, "-fPIE")) { o->target.pic = CFREE_PIC_PIE; continue; } + if (driver_streq(a, "-fpie")) { o->target.pic = CFREE_PIC_PIE; continue; } + if (driver_streq(a, "-static")){ o->target.pic = CFREE_PIC_NONE; continue; } + if (driver_streq(a, "-pie")) { o->target.pic = CFREE_PIC_PIE; continue; } + if (driver_streq(a, "-no-pie")){ o->target.pic = CFREE_PIC_NONE; continue; } + + if (driver_strneq(a, "-mcmodel=", 9)) { + if (cc_record_mcmodel(o, a + 9) != 0) return 1; + continue; + } + + if (driver_strneq(a, "--build-id=", 11)) { + if (cc_record_build_id(o, a + 11) != 0) return 1; + continue; + } + + if (driver_strneq(a, "-ffile-prefix-map=", 18)) { + if (cc_record_path_map(o, a + 18) != 0) return 1; + continue; + } if (driver_streq(a, "-o")) { if (++i >= argc) { driver_errf(CC_TOOL, "-o requires an argument"); return 1; } + if (o->output_path_set) { + driver_errf(CC_TOOL, "duplicate -o"); + return 1; + } o->output_path = argv[i]; + o->output_path_set = 1; continue; } - if (driver_strneq(a, "-I", 2)) { - const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!dir) { driver_errf(CC_TOOL, "-I requires an argument"); return 1; } - o->include_dirs[o->ninclude_dirs++] = dir; + if (driver_streq(a, "-e")) { + if (++i >= argc) { driver_errf(CC_TOOL, "-e requires an argument"); return 1; } + o->entry = argv[i]; + continue; + } + + if (driver_streq(a, "-T")) { + if (++i >= argc) { driver_errf(CC_TOOL, "-T requires an argument"); return 1; } + o->linker_script = argv[i]; + continue; + } + + if (driver_streq(a, "-target")) { + if (++i >= argc) { driver_errf(CC_TOOL, "-target requires an argument"); return 1; } + if (driver_target_from_triple(argv[i], &o->target) != 0) { + driver_errf(CC_TOOL, "unrecognized target triple: %s", argv[i]); + return 1; + } + o->target_set = 1; + continue; + } + + if (driver_streq(a, "-x")) { + if (++i >= argc) { driver_errf(CC_TOOL, "-x requires an argument"); return 1; } + if (!driver_streq(argv[i], "c")) { + driver_errf(CC_TOOL, "unsupported -x language: %s", argv[i]); + return 1; + } + x_lang_pinned = 1; continue; } - if (driver_streq(a, "-isystem")) { - if (++i >= argc) { driver_errf(CC_TOOL, "-isystem requires an argument"); return 1; } - o->system_include_dirs[o->nsystem_include_dirs++] = argv[i]; + if (driver_strneq(a, "-L", 2)) { + const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); + if (!dir) { driver_errf(CC_TOOL, "-L requires an argument"); return 1; } + o->lib_search_paths[o->nlib_search_paths++] = dir; continue; } - if (driver_strneq(a, "-D", 2)) { - const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!arg) { driver_errf(CC_TOOL, "-D requires an argument"); return 1; } - if (cc_record_define(o, arg) != 0) return 1; + if (driver_strneq(a, "-l", 2)) { + const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); + if (!name) { driver_errf(CC_TOOL, "-l requires an argument"); return 1; } + o->pending_libs[o->npending_libs++] = name; continue; } - if (driver_strneq(a, "-U", 2)) { - const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!arg) { driver_errf(CC_TOOL, "-U requires an argument"); return 1; } - o->undefines[o->nundefines++] = arg; + if (driver_streq(a, "-M")) { o->dep_mode = CC_DEP_M; continue; } + if (driver_streq(a, "-MM")) { o->dep_mode = CC_DEP_MM; continue; } + if (driver_streq(a, "-MD")) { o->dep_mode = CC_DEP_MD; continue; } + if (driver_streq(a, "-MMD")) { o->dep_mode = CC_DEP_MMD; continue; } + if (driver_streq(a, "-MP")) { o->dep_phony = 1; continue; } + if (driver_streq(a, "-MF")) { + if (++i >= argc) { driver_errf(CC_TOOL, "-MF requires an argument"); return 1; } + o->dep_file = argv[i]; + continue; + } + if (driver_streq(a, "-MT") || driver_streq(a, "-MQ")) { + if (++i >= argc) { driver_errf(CC_TOOL, "%s requires an argument", a); return 1; } + o->dep_targets[o->ndep_targets++] = argv[i]; continue; } + if (driver_streq(a, "-")) { + if (cc_classify_positional(o, a) != 0) return 1; + continue; + } if (a[0] == '-' && a[1] != '\0') { driver_errf(CC_TOOL, "unknown flag: %s", a); return 1; } - o->sources[o->nsources++] = a; + if (cc_classify_positional(o, a) != 0) return 1; } + (void)x_lang_pinned; /* recorded for future warnings; cc currently only does C */ - if (o->nsources == 0) { - driver_errf(CC_TOOL, "no input files"); - cc_usage(); - return 1; + if (cc_apply_env(o) != 0) return 1; + if (cc_resolve_pending_libs(o) != 0) return 1; + + { + uint32_t total_sources = o->nsource_files + o->nsource_memory; + uint32_t total_link = o->nobject_files + o->narchives; + + if (total_sources == 0 && total_link == 0) { + driver_errf(CC_TOOL, "no input files"); + cc_usage(); + return 1; + } + if (o->compile_only && o->preprocess_only) { + driver_errf(CC_TOOL, "-c and -E are mutually exclusive"); + return 1; + } + if (o->compile_only) { + if (total_sources != 1 || total_link != 0) { + driver_errf(CC_TOOL, "-c requires exactly one C source and no .o/.a inputs"); + return 1; + } + } + if (o->preprocess_only) { + if (total_sources != 1 || total_link != 0) { + driver_errf(CC_TOOL, "-E requires exactly one C source and no .o/.a inputs"); + return 1; + } + } + { + int dep_only = (o->dep_mode == CC_DEP_M || o->dep_mode == CC_DEP_MM); + int dep_with_compile = (o->dep_mode == CC_DEP_MD || o->dep_mode == CC_DEP_MMD); + if (o->dep_mode != CC_DEP_NONE && o->preprocess_only) { + driver_errf(CC_TOOL, "-M* is incompatible with -E"); + return 1; + } + if (dep_only && total_sources != 1) { + driver_errf(CC_TOOL, "-M/-MM requires exactly one input"); + return 1; + } + if (dep_with_compile && !o->compile_only) { + driver_errf(CC_TOOL, "-MD/-MMD currently requires -c"); + return 1; + } + if (!o->output_path && !dep_only) { + driver_errf(CC_TOOL, "-o is required"); + return 1; + } + } } - if (o->compile_only && o->preprocess_only) { - driver_errf(CC_TOOL, "-c and -E are mutually exclusive"); - return 1; + return 0; +} + +/* Borrow the single source as a CfreeBytesInput. Caller owns `*loaded` (set + * to nonzero only when the file was opened via env.file_io and must be + * released). */ +static int cc_load_single_source(DriverEnv* env, const CfreeEnv* cenv, + const CcOptions* o, + CfreeBytesInput* in, CfreeFileData* fd, + int* loaded) +{ + *loaded = 0; + if (o->nsource_memory == 1) { + *in = o->source_memory[0]; + return 0; } - if (o->compile_only && o->nsources != 1) { - driver_errf(CC_TOOL, "-c requires exactly one input"); + /* nsource_files == 1 here. */ + if (!cenv->file_io->read_all(cenv->file_io->user, o->source_files[0], fd)) { + driver_errf(CC_TOOL, "failed to read: %s", o->source_files[0]); return 1; } - if (o->preprocess_only && o->nsources != 1) { - driver_errf(CC_TOOL, "-E requires exactly one input"); - return 1; + *loaded = 1; + in->name = o->source_files[0]; + in->data = fd->data; + in->len = fd->size; + (void)env; + return 0; +} + +static int cc_preprocess(DriverEnv* env, const CcOptions* o, + const CfreePpOptions* pp_opts) +{ + CfreeEnv cenv = driver_env_to_cfree(env); + CfreeCompiler* compiler = NULL; + CfreeWriter* writer = NULL; + CfreeFileData fd = {0}; + CfreeBytesInput input = {0}; + int rc = 1; + int loaded = 0; + + if (cc_load_single_source(env, &cenv, o, &input, &fd, &loaded) != 0) goto out; + + writer = cenv.file_io->open_writer(cenv.file_io->user, o->output_path); + if (!writer) { + driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); + goto out; } - if (!o->output_path) { - driver_errf(CC_TOOL, "-o is required"); - return 1; + + compiler = cfree_compiler_new(o->target, &cenv); + if (!compiler) { + driver_errf(CC_TOOL, "failed to initialize compiler"); + goto out; } - return 0; + + rc = cfree_preprocess(compiler, pp_opts, &input, writer); + +out: + if (compiler) cfree_compiler_free(compiler); + if (writer) cfree_writer_close(writer); + if (loaded) cenv.file_io->release(cenv.file_io->user, &fd); + return rc; } -static void cc_options_release(CcOptions* o) +/* ---- header-dependency output (-M family) ---- */ + +typedef struct CcDepList { + const char** items; + uint32_t n; + uint32_t cap; +} CcDepList; + +typedef struct CcDiscardWriter { + CfreeWriter base; + DriverEnv* env; + uint64_t pos; +} CcDiscardWriter; + +static void cc_disc_write(CfreeWriter* w, const void* d, size_t n) +{ + (void)d; + ((CcDiscardWriter*)w)->pos += (uint64_t)n; +} +static void cc_disc_seek(CfreeWriter* w, uint64_t off) +{ + ((CcDiscardWriter*)w)->pos = off; +} +static uint64_t cc_disc_tell(CfreeWriter* w) { return ((CcDiscardWriter*)w)->pos; } +static int cc_disc_error(CfreeWriter* w) { (void)w; return 0; } +static void cc_disc_close(CfreeWriter* w) +{ + CcDiscardWriter* dw = (CcDiscardWriter*)w; + driver_free(dw->env, dw, sizeof(*dw)); +} + +static CfreeWriter* cc_discard_writer_new(DriverEnv* env) +{ + CcDiscardWriter* dw = (CcDiscardWriter*)driver_alloc_zeroed(env, sizeof(*dw)); + if (!dw) return NULL; + dw->base.write = cc_disc_write; + dw->base.seek = cc_disc_seek; + dw->base.tell = cc_disc_tell; + dw->base.error = cc_disc_error; + dw->base.close = cc_disc_close; + dw->env = env; + return &dw->base; +} + +static void cc_write_str(CfreeWriter* w, const char* s) +{ + cfree_writer_write(w, s, driver_strlen(s)); +} + +static int cc_dep_filters_system(int mode) +{ + return mode == CC_DEP_MM || mode == CC_DEP_MMD; +} + +static int cc_dep_list_push(DriverEnv* env, CcDepList* l, const char* s) { uint32_t i; - size_t bound = o->argv_bound; - for (i = 0; i < o->ndefines; ++i) { - if (o->owned_define_names[i]) { - driver_free(o->env, o->owned_define_names[i], - o->owned_define_name_sizes[i]); + for (i = 0; i < l->n; ++i) { + if (driver_streq(l->items[i], s)) return 0; /* dedupe */ + } + if (l->n == l->cap) { + uint32_t newcap = l->cap ? l->cap * 2 : 16; + const char** ni = driver_alloc_zeroed(env, newcap * sizeof(*ni)); + if (!ni) return 1; + if (l->items) { + driver_memcpy(ni, l->items, l->n * sizeof(*l->items)); + driver_free(env, l->items, l->cap * sizeof(*l->items)); } + l->items = ni; + l->cap = newcap; } - driver_free(o->env, o->include_dirs, bound * sizeof(*o->include_dirs)); - driver_free(o->env, o->system_include_dirs, bound * sizeof(*o->system_include_dirs)); - driver_free(o->env, o->defines, bound * sizeof(*o->defines)); - driver_free(o->env, o->owned_define_names, bound * sizeof(*o->owned_define_names)); - driver_free(o->env, o->owned_define_name_sizes, bound * sizeof(*o->owned_define_name_sizes)); - driver_free(o->env, o->undefines, bound * sizeof(*o->undefines)); - driver_free(o->env, o->sources, bound * sizeof(*o->sources)); + l->items[l->n++] = s; + return 0; } -static void cc_to_cfree(const CcOptions* o, CfreeOptions* out) +static void cc_dep_list_free(DriverEnv* env, CcDepList* l) { - /* Zero-initialize via a local literal — driver/env.c owns memset. */ - CfreeOptions z = {0}; - *out = z; - out->target = driver_host_target(); - out->env = driver_env_to_cfree(o->env); - out->output_kind = o->compile_only ? CFREE_OUTPUT_OBJ : CFREE_OUTPUT_EXE; - out->opt_level = o->opt_level; - out->debug_info = o->debug_info; - out->output_path = o->output_path; + if (l->items) driver_free(env, l->items, l->cap * sizeof(*l->items)); + l->items = NULL; l->n = 0; l->cap = 0; +} - out->source_files = o->sources; - out->nsource_files = o->nsources; +/* Default Make-target name. With -o set, use that. Otherwise (only -M/-MM) + * derive ".o" from the single C-source basename, or use "<stdin>.o" for the + * stdin source. */ +static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o, + size_t* out_size) +{ + const char* base = o->output_path; + size_t len; + char* buf; + if (base) { + len = driver_strlen(base); + buf = driver_alloc(env, len + 1); + if (!buf) return NULL; + driver_memcpy(buf, base, len); + buf[len] = '\0'; + *out_size = len + 1; + return buf; + } + if (o->nsource_memory == 1) { + const char* fallback = "<stdin>.o"; + size_t flen = driver_strlen(fallback); + buf = driver_alloc(env, flen + 1); + if (!buf) return NULL; + driver_memcpy(buf, fallback, flen); + buf[flen] = '\0'; + *out_size = flen + 1; + return buf; + } + { + const char* src = o->source_files[0]; + size_t srclen = driver_strlen(src); + size_t dot = srclen; + size_t slash = 0; + size_t k; + for (k = srclen; k > 0; --k) { + if (src[k - 1] == '.') { dot = k - 1; break; } + if (src[k - 1] == '/') break; + } + for (k = dot; k > 0; --k) { + if (src[k - 1] == '/') { slash = k; break; } + } + { + size_t name_len = dot - slash; + size_t bufsz = name_len + 3; /* ".o" + NUL */ + buf = driver_alloc(env, bufsz); + if (!buf) return NULL; + driver_memcpy(buf, src + slash, name_len); + buf[name_len] = '.'; + buf[name_len + 1] = 'o'; + buf[name_len + 2] = '\0'; + *out_size = bufsz; + return buf; + } + } +} - out->pp.include_dirs = o->include_dirs; - out->pp.ninclude_dirs = o->ninclude_dirs; - out->pp.system_include_dirs = o->system_include_dirs; - out->pp.nsystem_include_dirs = o->nsystem_include_dirs; - out->pp.defines = o->defines; - out->pp.ndefines = o->ndefines; - out->pp.undefines = o->undefines; - out->pp.nundefines = o->nundefines; +static char* cc_dep_default_path(DriverEnv* env, const char* out_path, + size_t* out_size) +{ + size_t len = driver_strlen(out_path); + size_t dot = len; + size_t k; + for (k = len; k > 0; --k) { + if (out_path[k - 1] == '.') { dot = k - 1; break; } + if (out_path[k - 1] == '/') break; + } + { + size_t bufsz = dot + 3; /* ".d" + NUL */ + char* buf = driver_alloc(env, bufsz); + if (!buf) return NULL; + driver_memcpy(buf, out_path, dot); + buf[dot] = '.'; + buf[dot + 1] = 'd'; + buf[dot + 2] = '\0'; + *out_size = bufsz; + return buf; + } } -/* Preprocessor-only path for `cc -E`. Loads the single source via - * env.file_io, opens the output via env.file_io, and runs cfree_preprocess. - * Returns 0 on success, nonzero on any I/O or compile failure. */ -static int cc_preprocess(DriverEnv* env, const CfreePpOptions* pp_opts, - const char* source, const char* output_path) +static int cc_dep_collect(DriverEnv* env, CfreeCompiler* compiler, + int system_filter, CcDepList* list) { - CfreeEnv cenv = driver_env_to_cfree(env); - CfreeCompiler* compiler = NULL; - CfreeWriter* writer = NULL; - CfreeFileData src = {0}; - CfreeBytesInput input; - int rc = 1; - int loaded = 0; + CfreeDepIter* it = cfree_dep_iter_new(compiler); + CfreeDepEdge e; + if (!it) return 1; + while (cfree_dep_iter_next(it, &e)) { + if (system_filter && e.from_system_path) continue; + if (cc_dep_list_push(env, list, e.included_name) != 0) { + cfree_dep_iter_free(it); + return 1; + } + } + cfree_dep_iter_free(it); + return 0; +} - if (!cenv.file_io->read_all(cenv.file_io->user, source, &src)) { - driver_errf(CC_TOOL, "failed to read: %s", source); +static void cc_dep_emit_rule(CfreeWriter* w, + const char* const* targets, uint32_t ntargets, + const char* primary_src, + const CcDepList* deps, + int phony) +{ + uint32_t i; + for (i = 0; i < ntargets; ++i) { + if (i) cc_write_str(w, " "); + cc_write_str(w, targets[i]); + } + cc_write_str(w, ":"); + if (primary_src) { + cc_write_str(w, " "); + cc_write_str(w, primary_src); + } + for (i = 0; i < deps->n; ++i) { + cc_write_str(w, " \\\n "); + cc_write_str(w, deps->items[i]); + } + cc_write_str(w, "\n"); + if (phony) { + for (i = 0; i < deps->n; ++i) { + cc_write_str(w, "\n"); + cc_write_str(w, deps->items[i]); + cc_write_str(w, ":\n"); + } + } +} + +static CfreeWriter* cc_dep_open_writer(DriverEnv* env, const CfreeEnv* cenv, + const CcOptions* o, + char** owned_path, size_t* owned_path_size) +{ + *owned_path = NULL; + *owned_path_size = 0; + if (o->dep_file) { + return cenv->file_io->open_writer(cenv->file_io->user, o->dep_file); + } + if (o->dep_mode == CC_DEP_M || o->dep_mode == CC_DEP_MM) { + return driver_stdout_writer(env); + } + /* MD/MMD: derive .d path from -o */ + { + char* p = cc_dep_default_path(env, o->output_path, owned_path_size); + if (!p) return NULL; + *owned_path = p; + return cenv->file_io->open_writer(cenv->file_io->user, p); + } +} + +static const char* cc_primary_source_name(const CcOptions* o) +{ + if (o->nsource_memory == 1) return o->source_memory[0].name; + return o->source_files[0]; +} + +static int cc_dep_finish(DriverEnv* env, const CfreeEnv* cenv, + CfreeCompiler* compiler, const CcOptions* o) +{ + CcDepList deps = {0}; + CfreeWriter* dep_w = NULL; + char* owned_path = NULL; + size_t owned_size = 0; + char* owned_target = NULL; + size_t owned_target_size = 0; + const char* one_target[1]; + const char* const* targets; + uint32_t ntargets; + int rc = 1; + + if (cc_dep_collect(env, compiler, cc_dep_filters_system(o->dep_mode), &deps) != 0) { + driver_errf(CC_TOOL, "out of memory"); goto out; } - loaded = 1; - writer = cenv.file_io->open_writer(cenv.file_io->user, output_path); - if (!writer) { - driver_errf(CC_TOOL, "failed to open output: %s", output_path); + targets = o->dep_targets; + ntargets = o->ndep_targets; + if (ntargets == 0) { + owned_target = cc_dep_default_target(env, o, &owned_target_size); + if (!owned_target) { driver_errf(CC_TOOL, "out of memory"); goto out; } + one_target[0] = owned_target; + targets = one_target; + ntargets = 1; + } + + dep_w = cc_dep_open_writer(env, cenv, o, &owned_path, &owned_size); + if (!dep_w) { + driver_errf(CC_TOOL, "failed to open dep output: %s", + o->dep_file ? o->dep_file : (owned_path ? owned_path : "<stdout>")); goto out; } - compiler = cfree_compiler_new(driver_host_target(), &cenv); - if (!compiler) { - driver_errf(CC_TOOL, "failed to initialize compiler"); + cc_dep_emit_rule(dep_w, targets, ntargets, + cc_primary_source_name(o), &deps, o->dep_phony); + rc = cfree_writer_error(dep_w); + +out: + if (dep_w) cfree_writer_close(dep_w); + if (owned_path) driver_free(env, owned_path, owned_size); + if (owned_target) driver_free(env, owned_target, owned_target_size); + cc_dep_list_free(env, &deps); + return rc; +} + +static void cc_fill_compile_opts(const CcOptions* o, const CfreePpOptions* pp, + CfreeCompileOptions* copts) +{ + CfreeCompileOptions zero = {0}; + *copts = zero; + copts->opt_level = o->opt_level; + copts->debug_info = o->debug_info; + copts->pp = *pp; + copts->epoch = o->epoch; + copts->path_map = o->npath_map ? o->path_map : NULL; + copts->npath_map = o->npath_map; + copts->warnings_are_errors = o->warnings_are_errors; + copts->max_errors = o->max_errors; +} + +static int cc_run_deps_only(DriverEnv* env, const CcOptions* o, + const CfreePpOptions* pp) +{ + CfreeEnv cenv = driver_env_to_cfree(env); + CfreeCompiler* compiler = NULL; + CfreeWriter* discard = NULL; + CfreeFileData fd = {0}; + CfreeBytesInput input = {0}; + int loaded = 0; + int rc = 1; + + if (cc_load_single_source(env, &cenv, o, &input, &fd, &loaded) != 0) goto out; + + discard = cc_discard_writer_new(env); + if (!discard) { driver_errf(CC_TOOL, "out of memory"); goto out; } + + compiler = cfree_compiler_new(o->target, &cenv); + if (!compiler) { driver_errf(CC_TOOL, "failed to initialize compiler"); goto out; } + + if (cfree_preprocess(compiler, pp, &input, discard) != 0) goto out; + + rc = cc_dep_finish(env, &cenv, compiler, o); + +out: + if (compiler) cfree_compiler_free(compiler); + if (discard) cfree_writer_close(discard); + if (loaded) cenv.file_io->release(cenv.file_io->user, &fd); + return rc; +} + +static int cc_run_compile_with_deps(DriverEnv* env, const CcOptions* o, + const CfreePpOptions* pp) +{ + CfreeEnv cenv = driver_env_to_cfree(env); + CfreeCompiler* compiler = NULL; + CfreeWriter* obj_w = NULL; + CfreeFileData fd = {0}; + CfreeBytesInput input = {0}; + CfreeCompileOptions copts; + int loaded = 0; + int rc = 1; + + if (cc_load_single_source(env, &cenv, o, &input, &fd, &loaded) != 0) goto out; + + obj_w = cenv.file_io->open_writer(cenv.file_io->user, o->output_path); + if (!obj_w) { + driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); goto out; } - input.name = source; - input.data = src.data; - input.len = src.size; + compiler = cfree_compiler_new(o->target, &cenv); + if (!compiler) { driver_errf(CC_TOOL, "failed to initialize compiler"); goto out; } - rc = cfree_preprocess(compiler, pp_opts, &input, writer); + cc_fill_compile_opts(o, pp, &copts); + if (cfree_compile_obj_emit(compiler, &copts, &input, obj_w) != 0) goto out; + + rc = cc_dep_finish(env, &cenv, compiler, o); out: if (compiler) cfree_compiler_free(compiler); - if (writer) cfree_writer_close(writer); - if (loaded) cenv.file_io->release(cenv.file_io->user, &src); + if (obj_w) cfree_writer_close(obj_w); + if (loaded) cenv.file_io->release(cenv.file_io->user, &fd); return rc; } -static void cc_fill_pp(const CcOptions* o, CfreePpOptions* pp) +static void cc_to_cfree(const CcOptions* o, CfreeOptions* out) { - CfreePpOptions z = {0}; - *pp = z; - pp->include_dirs = o->include_dirs; - pp->ninclude_dirs = o->ninclude_dirs; - pp->system_include_dirs = o->system_include_dirs; - pp->nsystem_include_dirs = o->nsystem_include_dirs; - pp->defines = o->defines; - pp->ndefines = o->ndefines; - pp->undefines = o->undefines; - pp->nundefines = o->nundefines; + CfreeOptions z = {0}; + *out = z; + out->target = o->target; + out->env = driver_env_to_cfree(o->env); + out->output_kind = o->compile_only ? CFREE_OUTPUT_OBJ : CFREE_OUTPUT_EXE; + out->opt_level = o->opt_level; + out->debug_info = o->debug_info; + out->output_path = o->output_path; + + out->source_files = o->source_files; + out->nsource_files = o->nsource_files; + out->source_memory = o->source_memory; + out->nsource_memory = o->nsource_memory; + + driver_cflags_fill_pp(&o->cf, &out->pp); + + out->object_files = o->object_files; + out->nobject_files = o->nobject_files; + out->archives = o->archives; + out->narchives = o->narchives; + + out->linker_script = o->linker_script; + out->entry = o->entry; + + out->epoch = o->epoch; + out->path_map = o->npath_map ? o->path_map : NULL; + out->npath_map = o->npath_map; + out->build_id_mode = o->build_id_mode; + out->build_id_bytes = o->build_id_bytes; + out->build_id_len = o->build_id_len; + + out->warnings_are_errors = o->warnings_are_errors; + out->max_errors = o->max_errors; } int driver_cc(int argc, char** argv) { - DriverEnv env; - CcOptions co = {0}; - CfreeOptions copts; - int rc; + DriverEnv env; + CcOptions co = {0}; + CfreePpOptions pp; + CfreeOptions copts; + int rc; driver_env_init(&env); co.env = &env; @@ -286,10 +1048,14 @@ int driver_cc(int argc, char** argv) return 2; } + driver_cflags_fill_pp(&co.cf, &pp); + if (co.preprocess_only) { - CfreePpOptions pp_opts; - cc_fill_pp(&co, &pp_opts); - rc = cc_preprocess(&env, &pp_opts, co.sources[0], co.output_path); + rc = cc_preprocess(&env, &co, &pp); + } else if (co.dep_mode == CC_DEP_M || co.dep_mode == CC_DEP_MM) { + rc = cc_run_deps_only(&env, &co, &pp); + } else if (co.dep_mode == CC_DEP_MD || co.dep_mode == CC_DEP_MMD) { + rc = cc_run_compile_with_deps(&env, &co, &pp); } else { cc_to_cfree(&co, &copts); rc = cfree_run(&copts); diff --git a/driver/cflags.c b/driver/cflags.c @@ -0,0 +1,142 @@ +#include "cflags.h" + +#include <stddef.h> +#include <stdint.h> + +int driver_cflags_init(DriverCflags* cf, DriverEnv* env, int argc_bound) +{ + size_t bound = argc_bound > 0 ? (size_t)argc_bound : 1; + cf->_cap = bound; + cf->include_dirs = driver_alloc_zeroed(env, bound * sizeof(*cf->include_dirs)); + cf->system_include_dirs = driver_alloc_zeroed(env, bound * sizeof(*cf->system_include_dirs)); + cf->defines = driver_alloc_zeroed(env, bound * sizeof(*cf->defines)); + cf->_owned_define_names = driver_alloc_zeroed(env, bound * sizeof(*cf->_owned_define_names)); + cf->_owned_define_name_sizes = driver_alloc_zeroed(env, bound * sizeof(*cf->_owned_define_name_sizes)); + cf->undefines = driver_alloc_zeroed(env, bound * sizeof(*cf->undefines)); + if (!cf->include_dirs || !cf->system_include_dirs || + !cf->defines || !cf->_owned_define_names || + !cf->_owned_define_name_sizes || !cf->undefines) { + return 1; + } + return 0; +} + +void driver_cflags_fini(DriverCflags* cf, DriverEnv* env) +{ + size_t cap = cf->_cap; + uint32_t i; + if (cf->_owned_define_names) { + for (i = 0; i < cf->ndefines; ++i) { + if (cf->_owned_define_names[i]) { + driver_free(env, cf->_owned_define_names[i], + cf->_owned_define_name_sizes[i]); + } + } + } + driver_free(env, cf->include_dirs, cap * sizeof(*cf->include_dirs)); + driver_free(env, cf->system_include_dirs, cap * sizeof(*cf->system_include_dirs)); + driver_free(env, cf->defines, cap * sizeof(*cf->defines)); + driver_free(env, cf->_owned_define_names, cap * sizeof(*cf->_owned_define_names)); + driver_free(env, cf->_owned_define_name_sizes, cap * sizeof(*cf->_owned_define_name_sizes)); + driver_free(env, cf->undefines, cap * sizeof(*cf->undefines)); + cf->_cap = 0; +} + +/* Split `name[=body]` into a heap-owned NUL-terminated name and a body that + * aliases the original argv tail. Returns 0 on success, 1 on alloc failure. */ +static int cflags_record_define(DriverCflags* cf, DriverEnv* env, const char* arg) +{ + const char* eq = driver_strchr(arg, '='); + CfreeDefine* d = &cf->defines[cf->ndefines]; + if (eq) { + size_t n = (size_t)(eq - arg); + size_t bytes = n + 1; + char* name = driver_alloc(env, bytes); + if (!name) return 1; + driver_memcpy(name, arg, n); + name[n] = '\0'; + cf->_owned_define_names[cf->ndefines] = name; + cf->_owned_define_name_sizes[cf->ndefines] = bytes; + d->name = name; + d->body = eq + 1; + } else { + d->name = arg; + d->body = NULL; + } + cf->ndefines++; + return 0; +} + +/* Pull the value for a flag that accepts either `-Xvalue` or `-X value`. + * `prefix_len` is the byte length of `-X` itself (e.g. 2 for `-I`/`-D`/`-U`, + * larger for `-isystem`). On the attached form (`-Xvalue`) returns argv[*i] + * + prefix_len without advancing. On the separate form, advances *i to the + * value slot and returns argv[*i]; if no value follows, reports a diagnostic + * and returns NULL. */ +static const char* cflags_pull_value(const char* tool, + int argc, char** argv, int* i, + size_t prefix_len, const char* flag_label) +{ + const char* a = argv[*i]; + if (a[prefix_len]) return a + prefix_len; + if (++(*i) >= argc) { + driver_errf(tool, "%s requires an argument", flag_label); + return NULL; + } + return argv[*i]; +} + +int driver_cflags_try_consume(DriverCflags* cf, DriverEnv* env, const char* tool, + int argc, char** argv, int* i) +{ + const char* a = argv[*i]; + + if (driver_strneq(a, "-I", 2)) { + const char* dir = cflags_pull_value(tool, argc, argv, i, 2, "-I"); + if (!dir) return -1; + cf->include_dirs[cf->ninclude_dirs++] = dir; + return 1; + } + + if (driver_streq(a, "-isystem")) { + if (++(*i) >= argc) { + driver_errf(tool, "-isystem requires an argument"); + return -1; + } + cf->system_include_dirs[cf->nsystem_include_dirs++] = argv[*i]; + return 1; + } + + if (driver_strneq(a, "-D", 2)) { + const char* arg = cflags_pull_value(tool, argc, argv, i, 2, "-D"); + if (!arg) return -1; + if (cflags_record_define(cf, env, arg) != 0) { + driver_errf(tool, "out of memory"); + return -1; + } + return 1; + } + + if (driver_strneq(a, "-U", 2)) { + const char* arg = cflags_pull_value(tool, argc, argv, i, 2, "-U"); + if (!arg) return -1; + cf->undefines[cf->nundefines++] = arg; + return 1; + } + + return 0; +} + +void driver_cflags_fill_pp(const DriverCflags* cf, CfreePpOptions* pp) +{ + CfreePpOptions z = {0}; + *pp = z; + pp->include_dirs = cf->include_dirs; + pp->ninclude_dirs = cf->ninclude_dirs; + pp->system_include_dirs = cf->system_include_dirs; + pp->nsystem_include_dirs = cf->nsystem_include_dirs; + pp->defines = cf->defines; + pp->ndefines = cf->ndefines; + pp->undefines = cf->undefines; + pp->nundefines = cf->nundefines; +} diff --git a/driver/cflags.h b/driver/cflags.h @@ -0,0 +1,73 @@ +#ifndef CFREE_DRIVER_CFLAGS_H +#define CFREE_DRIVER_CFLAGS_H + +#include "driver.h" + +/* Shared parsing of the C-preprocessor flag family used by both `cc` and + * `run`: -I, -isystem, -D, -U. Owns a back-store sized to the worst-case + * argv slot count so callers can hold borrowed pointers into argv without + * a second growth step. + * + * Usage: + * DriverCflags cf = {0}; + * if (driver_cflags_init(&cf, env, argc) != 0) return 1; + * for (i = 1; i < argc; ++i) { + * int r = driver_cflags_try_consume(&cf, env, TOOL, argc, argv, &i); + * if (r < 0) { driver_cflags_fini(&cf, env); return 1; } + * if (r) continue; + * ... handle other flags / positionals ... + * } + * driver_cflags_fill_pp(&cf, &pp_out); + * ... use pp_out ... + * driver_cflags_fini(&cf, env); + * + * The (name, body) pairs from `-Dname=body` are stored in heap-allocated + * NUL-terminated names; the body slices argv directly. Standalone `-Dname` + * entries reuse argv for both name and body=NULL. */ +typedef struct DriverCflags { + const char** include_dirs; + uint32_t ninclude_dirs; + const char** system_include_dirs; + uint32_t nsystem_include_dirs; + CfreeDefine* defines; + uint32_t ndefines; + const char** undefines; + uint32_t nundefines; + + /* Internal storage for the names of `-Dname=body` defines we had to + * split (entries are NULL when the body was unspecified — argv already + * provides a NUL-terminated name). Indexed in parallel with defines[]; + * sizes are needed to free through the heap. */ + char** _owned_define_names; + size_t* _owned_define_name_sizes; + size_t _cap; /* element count of every parallel array */ +} DriverCflags; + +/* Allocate the back-store. `argc_bound` is the upper limit on the number of + * each kind of flag the caller might collect (typically the program's + * argc). Returns 0 on success, 1 on allocation failure (already reported). */ +int driver_cflags_init(DriverCflags*, DriverEnv*, int argc_bound); + +/* Release every allocation held by *cf. Safe to call even after a failed + * init: unused slots are NULL/0 and free becomes a no-op. */ +void driver_cflags_fini(DriverCflags*, DriverEnv*); + +/* Try to consume one cflag at argv[*i]. Recognized: -I dir, -Idir, -isystem + * dir, -D name[=body], -Dname[=body], -U name, -Uname. + * + * Returns: + * 1 flag matched and was consumed (advances *i past the flag and any + * separate value argument). + * 0 the arg at argv[*i] is not a cflag we handle. + * -1 the arg matched a cflag prefix but its value/argument is missing or + * allocation failed; the error has already been reported via + * driver_errf(tool, ...). */ +int driver_cflags_try_consume(DriverCflags*, DriverEnv*, const char* tool, + int argc, char** argv, int* i); + +/* Populate a CfreePpOptions from the accumulated flags. The CfreePpOptions + * borrows the cf-owned arrays; the caller must keep cf alive across any + * libcfree call that consumes pp_out. */ +void driver_cflags_fill_pp(const DriverCflags*, CfreePpOptions* pp_out); + +#endif diff --git a/driver/dbg.c b/driver/dbg.c @@ -1,9 +1,1156 @@ +#include "cflags.h" #include "driver.h" -int driver_dbg(int argc, char** argv) +#include <stddef.h> +#include <stdint.h> + +/* `cfree dbg` — interactive JIT debugger. + * + * Mirrors `cfree run` for compile flags and argv shape, but instead of + * calling the entry directly drops into a REPL that drives a + * CfreeJitSession. The session (in libcfree) owns the worker thread, + * signal handlers, breakpoint patcher, and per-arch single-step / + * displaced-step trampoline. This driver TU only: + * + * - parses argv and turns the source list into a JIT image, + * - opens DWARF and a JIT-image view, + * - manages a driver-local breakpoint table (id, spec text, enabled + * flag) keyed off session-side handles, + * - parses LOC strings (file:line, sym[+off], 0xADDR) into addresses + * via cfree_dwarf_line_to_addr / cfree_jit_lookup, + * - reads commands from stdin and dispatches, + * - renders CfreeStopInfo into source-level stop messages and the + * backtrace via cfree_dwarf_unwind_step, + * - decodes `p name` via cfree_dwarf_var_at + cfree_dwarf_loc_read. + * + * Forwarding Ctrl-C: while a session call is in flight the driver + * installs a SIGINT handler that calls cfree_jit_session_interrupt; on + * return it restores SIG_DFL so Ctrl-C at the REPL prompt terminates + * the program. */ + +#define DBG_TOOL "dbg" +#define LINE_CAP 1024 +#define WORD_CAP 256 +#define HEX_CAP 8 /* upper bound on per-arch trap-byte save */ + +/* ============================================================ + * argv parsing (mirrors run.c) + * ============================================================ */ + +typedef struct DbgOpts { + DriverEnv* env; + size_t argv_bound; + + int opt_level; + int debug_info; + const char* entry; + + DriverCflags cf; + + const char** sources; + uint32_t nsources; + + char** prog_argv; + uint32_t prog_argc; +} DbgOpts; + +static void dbg_usage(void) +{ + driver_errf(DBG_TOOL, "%s", + "usage: cfree dbg [-O0|-O1|-O2] [-g] [-e entry]\n" + " [-I dir]... [-isystem dir]...\n" + " [-D name[=body]]... [-U name]...\n" + " input.c... [-- arg...]"); +} + +static int dbg_alloc_arrays(DbgOpts* o, int argc) +{ + size_t bound = (size_t)argc; + o->argv_bound = bound; + o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources)); + o->prog_argv = driver_alloc_zeroed(o->env, bound * sizeof(*o->prog_argv)); + if (!o->sources || !o->prog_argv) { + driver_errf(DBG_TOOL, "out of memory"); + return 1; + } + if (driver_cflags_init(&o->cf, o->env, argc) != 0) { + driver_errf(DBG_TOOL, "out of memory"); + return 1; + } + return 0; +} + +static int dbg_parse(int argc, char** argv, DbgOpts* o) +{ + int i; + int after_dash_dash = 0; + if (dbg_alloc_arrays(o, argc) != 0) return 1; + + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + + if (after_dash_dash) { + o->prog_argv[o->prog_argc++] = argv[i]; + continue; + } + if (driver_streq(a, "--")) { after_dash_dash = 1; continue; } + + { + int r = driver_cflags_try_consume(&o->cf, o->env, DBG_TOOL, argc, argv, &i); + if (r < 0) return 1; + if (r > 0) continue; + } + + if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } + if (driver_streq(a, "-O0")) { o->opt_level = 0; continue; } + if (driver_streq(a, "-O1")) { o->opt_level = 1; continue; } + if (driver_streq(a, "-O2")) { o->opt_level = 2; continue; } + + if (driver_streq(a, "-e")) { + if (++i >= argc) { driver_errf(DBG_TOOL, "-e requires an argument"); return 1; } + o->entry = argv[i]; + continue; + } + + if (a[0] == '-' && a[1] != '\0') { + driver_errf(DBG_TOOL, "unknown flag: %s", a); + return 1; + } + + o->sources[o->nsources++] = a; + } + + if (o->nsources == 0) { + driver_errf(DBG_TOOL, "no input files"); + dbg_usage(); + return 1; + } + if (!o->entry) o->entry = "main"; + if (!o->debug_info) { + /* Without -g there are no source lines or variable locations to + * read at runtime; force it on so `b file:line` and `p name` + * have something to look at. The user can always re-run without + * if they want to inspect optimized code at the asm level. */ + o->debug_info = 1; + } + return 0; +} + +static void dbg_options_release(DbgOpts* o) +{ + size_t bound = o->argv_bound; + driver_cflags_fini(&o->cf, o->env); + driver_free(o->env, o->sources, bound * sizeof(*o->sources)); + driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); +} + +static void dbg_to_cfree(const DbgOpts* o, CfreeOptions* out, CfreeJit** out_jit) +{ + CfreeOptions z = {0}; + *out = z; + out->target = driver_host_target(); + out->env = driver_env_to_cfree(o->env); + out->output_kind = CFREE_OUTPUT_JIT; + out->opt_level = o->opt_level; + out->debug_info = o->debug_info; + + out->source_files = o->sources; + out->nsource_files = o->nsources; + + driver_cflags_fill_pp(&o->cf, &out->pp); + + out->entry = o->entry; + out->extern_resolver = driver_dlsym_resolver; + out->out_jit = out_jit; +} + +/* ============================================================ + * Breakpoint table (driver-side) + * ============================================================ + * Each entry pairs a session-side handle (assigned by libcfree) with + * driver-side bookkeeping: a stable integer id we expose to the user, the + * spec text the user gave us, and a flag that lets us cheaply re-arm + * temp-disabled breakpoints. */ + +typedef enum BpKind { + BP_ADDR, + BP_SYM, + BP_LINE, +} BpKind; + +typedef struct Bp { + int id; /* user-facing handle, 1.. */ + int enabled; + BpKind kind; + char* spec; /* heap-owned NUL-terminated copy */ + size_t spec_size; + uint64_t addr; + uint32_t session_id; /* libcfree handle; 0 when disarmed */ +} Bp; + +/* ============================================================ + * Session-scoped state + * ============================================================ */ + +typedef struct DbgState { + DriverEnv* env; + CfreeJit* jit; + CfreeJitSession* session; + const CfreeObjFile* view; + CfreeDebugInfo* dwarf; + void* entry_addr; + int prog_argc; + char** prog_argv; + + Bp* bps; + uint32_t nbps; + uint32_t bps_cap; + int next_bp_id; + + int has_stop; + CfreeStopInfo last_stop; +} DbgState; + +/* SIGINT trampoline. The handler in env.c calls our cb with this state; + * we forward into the session. cfree_jit_session_interrupt is documented + * async-signal-safe. */ +static void dbg_on_sigint(void* user) { - (void)argc; - (void)argv; - /* TODO: debugger front-end. */ + DbgState* s = (DbgState*)user; + if (s && s->session) cfree_jit_session_interrupt(s->session); +} + +/* ============================================================ + * Tiny driver-local string utilities + * ============================================================ + * The driver TU may use plain libc <string.h>/<ctype.h> per the project + * rules (no syscalls). We avoid pulling in libc here only because the + * existing driver shims cover everything we need. */ + +static int dbg_isspace(int c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } +static int dbg_isdigit(int c) { return c >= '0' && c <= '9'; } +static int dbg_isxdigit(int c) +{ + return dbg_isdigit(c) + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); +} + +static int dbg_xval(int c) +{ + if (dbg_isdigit(c)) return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + return c - 'A' + 10; +} + +static char* dbg_dup(DriverEnv* env, const char* s, size_t n, size_t* size_out) +{ + char* p = driver_alloc(env, n + 1); + if (!p) return NULL; + driver_memcpy(p, s, n); + p[n] = '\0'; + if (size_out) *size_out = n + 1; + return p; +} + +/* Parse a 0x-prefixed hex literal or a decimal literal into *out. Returns + * the number of characters consumed, or 0 on failure. */ +static size_t dbg_parse_uint(const char* s, uint64_t* out) +{ + size_t i = 0; + uint64_t v = 0; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + i = 2; + if (!dbg_isxdigit((unsigned char)s[i])) return 0; + for (; dbg_isxdigit((unsigned char)s[i]); ++i) { + v = (v << 4) | (uint64_t)dbg_xval((unsigned char)s[i]); + } + } else { + if (!dbg_isdigit((unsigned char)s[0])) return 0; + for (; dbg_isdigit((unsigned char)s[i]); ++i) { + v = v * 10 + (uint64_t)(s[i] - '0'); + } + } + *out = v; + return i; +} + +/* ============================================================ + * Breakpoint table operations + * ============================================================ */ + +static Bp* dbg_bp_grow(DbgState* s) +{ + uint32_t nc; + size_t old_size, new_size; + Bp* nb; + if (s->nbps < s->bps_cap) return &s->bps[s->nbps]; + + nc = s->bps_cap ? s->bps_cap * 2 : 8; + old_size = (size_t)s->bps_cap * sizeof(Bp); + new_size = (size_t)nc * sizeof(Bp); + nb = s->env->heap->realloc(s->env->heap, s->bps, + old_size, new_size, _Alignof(Bp)); + if (!nb) { + driver_errf(DBG_TOOL, "out of memory growing breakpoint table"); + return NULL; + } + /* Zero the freshly grown tail so future driver_free walks it cleanly. */ + { + char* z = (char*)nb + old_size; + size_t n = new_size - old_size; + size_t j; + for (j = 0; j < n; ++j) z[j] = 0; + } + s->bps = nb; + s->bps_cap = nc; + return &s->bps[s->nbps]; +} + +static Bp* dbg_bp_find(DbgState* s, int id) +{ + uint32_t i; + for (i = 0; i < s->nbps; ++i) { + if (s->bps[i].id == id) return &s->bps[i]; + } + return NULL; +} + +static void dbg_bp_release(DbgState* s, Bp* b) +{ + if (b->session_id) { + cfree_jit_session_breakpoint_clear(s->session, b->session_id); + b->session_id = 0; + } + if (b->spec) { + driver_free(s->env, b->spec, b->spec_size); + b->spec = NULL; + b->spec_size = 0; + } +} + +static int dbg_bp_remove(DbgState* s, int id) +{ + uint32_t i; + for (i = 0; i < s->nbps; ++i) { + if (s->bps[i].id == id) { + dbg_bp_release(s, &s->bps[i]); + /* Shift tail down to keep the array dense; ids stay stable. */ + { + uint32_t j; + for (j = i + 1; j < s->nbps; ++j) s->bps[j - 1] = s->bps[j]; + } + s->nbps--; + { + Bp z = {0}; + s->bps[s->nbps] = z; + } + return 0; + } + } return 1; } + +static void dbg_bps_release_all(DbgState* s) +{ + uint32_t i; + for (i = 0; i < s->nbps; ++i) dbg_bp_release(s, &s->bps[i]); + if (s->bps) { + driver_free(s->env, s->bps, (size_t)s->bps_cap * sizeof(Bp)); + s->bps = NULL; + s->bps_cap = 0; + s->nbps = 0; + } +} + +/* ============================================================ + * LOC parser + * ============================================================ + * Resolves a user-supplied location specification into a single address + * within the JIT image: + * + * 0xADDR raw address + * sym[+0xN | +N] symbol via cfree_jit_lookup, optional offset + * file.c:LINE DWARF lookup + * + * Returns 0 on success, 1 on parse / resolution failure (already + * reported via driver_errf). */ + +static int dbg_resolve_loc(DbgState* s, const char* spec, + BpKind* kind_out, uint64_t* addr_out) +{ + /* file:line — uniquely identifiable by a colon NOT preceded by + and + * followed by digits. We require the suffix after ':' to be all + * digits so we don't confuse, say, hypothetical `func:42` (which + * isn't a thing in C). */ + const char* colon = driver_strchr(spec, ':'); + if (colon && dbg_isdigit((unsigned char)colon[1])) { + size_t file_n = (size_t)(colon - spec); + char* file; + uint64_t line64; + size_t used; + size_t file_size; + uint64_t pc; + + if (file_n == 0) { + driver_errf(DBG_TOOL, "empty file in '%s'", spec); + return 1; + } + file = dbg_dup(s->env, spec, file_n, &file_size); + if (!file) { + driver_errf(DBG_TOOL, "out of memory"); + return 1; + } + used = dbg_parse_uint(colon + 1, &line64); + if (!used || colon[1 + used] != '\0') { + driver_errf(DBG_TOOL, "expected file.c:LINE, got '%s'", spec); + driver_free(s->env, file, file_size); + return 1; + } + if (!s->dwarf) { + driver_errf(DBG_TOOL, "no DWARF: cannot resolve %s", spec); + driver_free(s->env, file, file_size); + return 1; + } + if (cfree_dwarf_line_to_addr(s->dwarf, file, (uint32_t)line64, &pc) != 0) { + driver_errf(DBG_TOOL, "no line entry for %s", spec); + driver_free(s->env, file, file_size); + return 1; + } + driver_free(s->env, file, file_size); + *kind_out = BP_LINE; + *addr_out = pc; + return 0; + } + + /* 0xADDR or decimal address. */ + if ((spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X')) + || dbg_isdigit((unsigned char)spec[0])) + { + uint64_t v; + size_t used = dbg_parse_uint(spec, &v); + if (!used || spec[used] != '\0') { + driver_errf(DBG_TOOL, "trailing junk in address '%s'", spec); + return 1; + } + *kind_out = BP_ADDR; + *addr_out = v; + return 0; + } + + /* sym[+off] */ + { + const char* plus = driver_strchr(spec, '+'); + size_t name_n = plus ? (size_t)(plus - spec) : driver_strlen(spec); + char* name; + size_t name_size; + void* resolved; + uint64_t off = 0; + + if (name_n == 0) { + driver_errf(DBG_TOOL, "empty symbol in '%s'", spec); + return 1; + } + name = dbg_dup(s->env, spec, name_n, &name_size); + if (!name) { + driver_errf(DBG_TOOL, "out of memory"); + return 1; + } + resolved = cfree_jit_lookup(s->jit, name); + if (!resolved) { + driver_errf(DBG_TOOL, "symbol not found: %s", name); + driver_free(s->env, name, name_size); + return 1; + } + driver_free(s->env, name, name_size); + + if (plus) { + size_t used = dbg_parse_uint(plus + 1, &off); + if (!used || plus[1 + used] != '\0') { + driver_errf(DBG_TOOL, "bad offset in '%s'", spec); + return 1; + } + } + + *kind_out = BP_SYM; + /* Object-pointer to integer cast: implementation-defined in + * standard C, defined as the address on every host where a JIT + * runs. */ + { + union { void* p; uint64_t u; } cv; + cv.p = resolved; + *addr_out = cv.u + off; + } + return 0; + } +} + +/* ============================================================ + * Stop rendering + * ============================================================ */ + +static void dbg_print_pc(DbgState* s, uint64_t pc) +{ + const char* sym = NULL; + uint64_t off = 0; + const char* file = NULL; + uint32_t line = 0; + uint32_t col = 0; + + driver_printf("0x%llx", (unsigned long long)pc); + if (cfree_jit_addr_to_sym(s->jit, pc, &sym, &off) == 0 && sym) { + if (off) driver_printf(" <%s+0x%llx>", sym, (unsigned long long)off); + else driver_printf(" <%s>", sym); + } + if (s->dwarf + && cfree_dwarf_addr_to_line(s->dwarf, pc, &file, &line, &col) == 0 + && file) + { + driver_printf(" at %s:%u", file, line); + if (col) driver_printf(":%u", col); + } +} + +static void dbg_render_stop(DbgState* s, const CfreeStopInfo* st) +{ + switch (st->kind) { + case CFREE_STOP_BREAKPOINT: { + Bp* b = NULL; + uint32_t i; + for (i = 0; i < s->nbps; ++i) { + if (s->bps[i].session_id == st->bp_id) { b = &s->bps[i]; break; } + } + if (b) driver_printf("Breakpoint %d (%s) hit at ", b->id, b->spec); + else driver_printf("Breakpoint hit at "); + dbg_print_pc(s, st->regs.pc); + driver_printf("\n"); + break; + } + case CFREE_STOP_SIGNAL: + driver_printf("Stopped on signal %d at ", st->signal); + dbg_print_pc(s, st->regs.pc); + driver_printf("\n"); + break; + case CFREE_STOP_INTERRUPT: + driver_printf("Interrupted at "); + dbg_print_pc(s, st->regs.pc); + driver_printf("\n"); + break; + case CFREE_STOP_EXIT: + driver_printf("Program exited with code %d\n", st->exit_code); + break; + } +} + +/* ============================================================ + * Run / continue / step + * ============================================================ + * Both `r` and `c` flow through the same wrapper: install SIGINT, drive + * the session (call or resume), restore SIGINT, render the stop. The + * dbg owns no signal-handling complexity itself — that's the session's + * job. */ + +typedef enum DbgRunMode { + RUN_FRESH, /* r — _call from entry */ + RUN_CONTINUE, /* c — _resume(continue) */ + RUN_STEP, /* s — _resume(step) */ +} DbgRunMode; + +static int dbg_drive(DbgState* s, DbgRunMode mode) +{ + int rc; + + if (mode == RUN_FRESH && s->has_stop) { + /* The previous session is dead (entry returned or signal landed). + * Start a new one. */ + s->has_stop = 0; + } else if (mode != RUN_FRESH && !s->has_stop) { + driver_errf(DBG_TOOL, "no program is running; use 'r' to start"); + return 1; + } + + if (driver_install_sigint(dbg_on_sigint, s) != 0) { + driver_errf(DBG_TOOL, "failed to install SIGINT handler"); + return 1; + } + + if (mode == RUN_FRESH) { + rc = cfree_jit_session_call(s->session, s->entry_addr, + CFREE_ENTRY_INT_ARGV, + s->prog_argc, s->prog_argv, + &s->last_stop); + } else { + CfreeResumeMode rm = (mode == RUN_STEP) + ? CFREE_RESUME_STEP_INSN : CFREE_RESUME_CONTINUE; + rc = cfree_jit_session_resume(s->session, rm, &s->last_stop); + } + + driver_restore_sigint(); + + if (rc != 0) { + driver_errf(DBG_TOOL, "session %s failed (rc=%d) — " + "JIT session implementation pending", + mode == RUN_FRESH ? "call" : "resume", rc); + return 1; + } + + s->has_stop = 1; + dbg_render_stop(s, &s->last_stop); + if (s->last_stop.kind == CFREE_STOP_EXIT) s->has_stop = 0; + return 0; +} + +/* ============================================================ + * Backtrace + * ============================================================ */ + +static void dbg_cmd_bt(DbgState* s) +{ + CfreeUnwindFrame frame; + int level = 0; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + if (!s->dwarf) { + driver_errf(DBG_TOOL, "no DWARF: backtrace unavailable"); + return; + } + + frame = s->last_stop.regs; + for (;;) { + const char* name = NULL; + uint64_t lo = 0; + uint64_t hi = 0; + int step; + + driver_printf("#%-2d ", level); + dbg_print_pc(s, frame.pc); + if (cfree_dwarf_func_at(s->dwarf, frame.pc, &name, &lo, &hi) == 0 + && name) + { + driver_printf(" in %s", name); + } + driver_printf("\n"); + + step = cfree_dwarf_unwind_step(s->dwarf, &frame); + if (step == 1) break; /* bottom of stack */ + if (step != 0) { + driver_errf(DBG_TOOL, "unwind step failed"); + break; + } + if (++level > 256) { + driver_errf(DBG_TOOL, "backtrace truncated at 256 frames"); + break; + } + } +} + +/* ============================================================ + * `p name` + * ============================================================ + * Source-language print. Uses cfree_dwarf_var_at to find the variable + * (locals win over globals at the current PC), cfree_dwarf_loc_read to + * pull the bytes from the worker's address space, and prints up to 8 + * bytes as a hex value (the bulk of v0 use is scalar inspection). + * Globals not in DWARF fall back to cfree_jit_lookup, which gives an + * address but no type info — for that case we print just the address. */ + +static void dbg_cmd_print(DbgState* s, const char* name) +{ + CfreeDwarfVarLoc loc; + uint8_t buf[16]; + size_t got = 0; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + + if (s->dwarf + && cfree_dwarf_var_at(s->dwarf, s->last_stop.regs.pc, name, &loc) == 0) + { + if (cfree_dwarf_loc_read(s->dwarf, &loc, &s->last_stop.regs, + s->session, buf, sizeof(buf), &got) != 0) + { + driver_errf(DBG_TOOL, "could not read %s", name); + return; + } + if (got == 0) { + driver_printf("%s = <empty>\n", name); + return; + } + /* Print up to 8 bytes as a single little-endian unsigned (matches + * DWARF base-encoding for integer scalars); larger reads dump as + * a hex byte sequence. */ + if (got <= 8) { + uint64_t v = 0; + size_t i; + for (i = 0; i < got; ++i) v |= ((uint64_t)buf[i]) << (8 * i); + driver_printf("%s = 0x%llx (%llu)\n", name, + (unsigned long long)v, (unsigned long long)v); + } else { + size_t i; + driver_printf("%s =", name); + for (i = 0; i < got; ++i) driver_printf(" %02x", buf[i]); + driver_printf("\n"); + } + return; + } + + /* DWARF didn't know about it — try a global symbol. */ + { + void* p = cfree_jit_lookup(s->jit, name); + if (p) { + union { void* p; uint64_t u; } cv; + cv.p = p; + driver_printf("%s = 0x%llx (no DWARF type info)\n", + name, (unsigned long long)cv.u); + return; + } + } + driver_errf(DBG_TOOL, "no variable or symbol named '%s'", name); +} + +/* ============================================================ + * `x ADDR [count]` + * ============================================================ + * Examine memory: reads `count` bytes (default 16) at `addr` from the + * worker's address space and prints them as 16-byte rows. */ + +static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) +{ + uint8_t buf[256]; + size_t remaining = count; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + + while (remaining) { + size_t chunk = remaining > sizeof(buf) ? sizeof(buf) : remaining; + size_t i; + if (cfree_jit_session_read_mem(s->session, addr, buf, chunk) != 0) { + driver_errf(DBG_TOOL, "read failed at 0x%llx", + (unsigned long long)addr); + return; + } + for (i = 0; i < chunk; i += 16) { + size_t j; + size_t row = chunk - i < 16 ? chunk - i : 16; + driver_printf("0x%llx:", (unsigned long long)(addr + i)); + for (j = 0; j < row; ++j) driver_printf(" %02x", buf[i + j]); + driver_printf("\n"); + } + addr += chunk; + remaining -= chunk; + } +} + +/* ============================================================ + * `info b`, `b LOC`, `d N`, `enable N` / `disable N` + * ============================================================ */ + +static void dbg_cmd_break(DbgState* s, const char* spec) +{ + BpKind kind; + uint64_t addr; + Bp* b; + + if (!*spec) { + driver_errf(DBG_TOOL, "usage: b <0xADDR | sym[+off] | file.c:line>"); + return; + } + if (dbg_resolve_loc(s, spec, &kind, &addr) != 0) return; + + b = dbg_bp_grow(s); + if (!b) return; + + { + Bp z = {0}; + *b = z; + } + b->id = ++s->next_bp_id; + b->enabled = 1; + b->kind = kind; + b->addr = addr; + b->spec = dbg_dup(s->env, spec, driver_strlen(spec), &b->spec_size); + if (!b->spec) { + driver_errf(DBG_TOOL, "out of memory"); + return; + } + + if (cfree_jit_session_breakpoint_set(s->session, addr, &b->session_id) != 0) { + driver_errf(DBG_TOOL, + "failed to arm breakpoint at 0x%llx — " + "JIT session implementation pending", + (unsigned long long)addr); + b->session_id = 0; /* surface as disarmed in `info b` */ + b->enabled = 0; + } + + s->nbps++; + driver_printf("Breakpoint %d at 0x%llx (%s)%s\n", + b->id, (unsigned long long)addr, spec, + b->session_id ? "" : " [disarmed]"); +} + +static void dbg_cmd_info_b(DbgState* s) +{ + uint32_t i; + if (s->nbps == 0) { + driver_printf("No breakpoints.\n"); + return; + } + driver_printf("Num Enb Address Spec\n"); + for (i = 0; i < s->nbps; ++i) { + Bp* b = &s->bps[i]; + driver_printf("%-4d %-4s 0x%-18llx %s%s\n", + b->id, + b->enabled ? "y" : "n", + (unsigned long long)b->addr, + b->spec ? b->spec : "", + b->session_id ? "" : " [disarmed]"); + } +} + +static void dbg_cmd_delete(DbgState* s, int id) +{ + if (dbg_bp_remove(s, id) != 0) { + driver_errf(DBG_TOOL, "no breakpoint %d", id); + } +} + +static void dbg_cmd_set_enabled(DbgState* s, int id, int enable) +{ + Bp* b = dbg_bp_find(s, id); + if (!b) { + driver_errf(DBG_TOOL, "no breakpoint %d", id); + return; + } + if (enable && !b->enabled) { + if (cfree_jit_session_breakpoint_set(s->session, b->addr, + &b->session_id) != 0) + { + driver_errf(DBG_TOOL, "failed to re-arm breakpoint %d", id); + return; + } + b->enabled = 1; + } else if (!enable && b->enabled) { + if (b->session_id) { + cfree_jit_session_breakpoint_clear(s->session, b->session_id); + b->session_id = 0; + } + b->enabled = 0; + } +} + +/* ============================================================ + * Help + * ============================================================ */ + +static void dbg_cmd_help(void) +{ + driver_printf("%s", + "Commands (abbrev. shown):\n" + " h, help show this help\n" + " q, quit exit (Ctrl-D also works)\n" + " r, run start fresh execution at entry\n" + " c, cont continue after a stop\n" + " s, step single-step one instruction\n" + " n, next step to next source line\n" + " bt, backtrace print stack trace\n" + " b LOC set breakpoint at LOC:\n" + " 0xADDR | sym[+off] | file.c:line\n" + " info b list breakpoints\n" + " d N, delete N delete breakpoint N\n" + " enable N | disable N toggle breakpoint N\n" + " p NAME print variable / global\n" + " x ADDR [count] examine memory (count bytes, default 16)\n"); +} + +/* ============================================================ + * Tokenizer / dispatch + * ============================================================ */ + +/* Trim leading whitespace and split off one whitespace-delimited word. + * Returns the start of the next region (i.e. the rest of the line, with + * its own leading whitespace skipped). The input buffer is mutated: + * the byte after the first word is replaced with a NUL. */ +static char* dbg_take_word(char* line, char** word_out) +{ + char* p = line; + char* w; + while (*p && dbg_isspace((unsigned char)*p)) ++p; + if (!*p) { *word_out = p; return p; } + w = p; + while (*p && !dbg_isspace((unsigned char)*p)) ++p; + if (*p) { *p = '\0'; ++p; } + while (*p && dbg_isspace((unsigned char)*p)) ++p; + *word_out = w; + return p; +} + +static int dbg_dispatch(DbgState* s, char* line) +{ + char* cmd; + char* rest = dbg_take_word(line, &cmd); + + if (!*cmd) return 0; /* blank line */ + + if (driver_streq(cmd, "h") || driver_streq(cmd, "help")) { + dbg_cmd_help(); + return 0; + } + if (driver_streq(cmd, "q") || driver_streq(cmd, "quit") || driver_streq(cmd, "exit")) { + return 1; /* signal "exit REPL" */ + } + if (driver_streq(cmd, "r") || driver_streq(cmd, "run")) { + dbg_drive(s, RUN_FRESH); + return 0; + } + if (driver_streq(cmd, "c") || driver_streq(cmd, "cont") || driver_streq(cmd, "continue")) { + dbg_drive(s, RUN_CONTINUE); + return 0; + } + if (driver_streq(cmd, "s") || driver_streq(cmd, "step") + || driver_streq(cmd, "n") || driver_streq(cmd, "next")) + { + /* Both step (instruction) and next (source line). The session + * exposes step-instruction directly; "next" loops single-step + * until the source line changes, falling back gracefully when + * no DWARF is present. */ + if ((driver_streq(cmd, "n") || driver_streq(cmd, "next")) && s->dwarf) { + const char* start_file = NULL; + uint32_t start_line = 0; + uint32_t start_col = 0; + int budget = 4096; /* avoid infinite loops */ + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return 0; + } + cfree_dwarf_addr_to_line(s->dwarf, s->last_stop.regs.pc, + &start_file, &start_line, &start_col); + for (;;) { + const char* f = NULL; + uint32_t l = 0; + uint32_t c = 0; + if (dbg_drive(s, RUN_STEP) != 0) return 0; + if (s->last_stop.kind != CFREE_STOP_BREAKPOINT + && s->last_stop.kind != CFREE_STOP_SIGNAL) return 0; + cfree_dwarf_addr_to_line(s->dwarf, s->last_stop.regs.pc, + &f, &l, &c); + if (!start_file || !f) break; /* nothing to compare */ + if (l != start_line || !driver_streq(f, start_file)) break; + if (--budget == 0) { + driver_errf(DBG_TOOL, "step-line budget exhausted"); + return 0; + } + } + } else { + dbg_drive(s, RUN_STEP); + } + return 0; + } + if (driver_streq(cmd, "bt") || driver_streq(cmd, "backtrace") + || driver_streq(cmd, "where")) + { + dbg_cmd_bt(s); + return 0; + } + if (driver_streq(cmd, "b") || driver_streq(cmd, "break")) { + char* loc; + dbg_take_word(rest, &loc); + dbg_cmd_break(s, loc); + return 0; + } + if (driver_streq(cmd, "info")) { + char* what; + dbg_take_word(rest, &what); + if (driver_streq(what, "b") || driver_streq(what, "break") + || driver_streq(what, "breakpoints")) + { + dbg_cmd_info_b(s); + } else { + driver_errf(DBG_TOOL, "unknown 'info' subcommand: %s", what); + } + return 0; + } + if (driver_streq(cmd, "d") || driver_streq(cmd, "delete")) { + char* arg; + uint64_t id; + size_t used; + dbg_take_word(rest, &arg); + if (!*arg) { + driver_errf(DBG_TOOL, "usage: d <id>"); + return 0; + } + used = dbg_parse_uint(arg, &id); + if (!used || arg[used] != '\0') { + driver_errf(DBG_TOOL, "expected breakpoint id"); + return 0; + } + dbg_cmd_delete(s, (int)id); + return 0; + } + if (driver_streq(cmd, "enable") || driver_streq(cmd, "disable")) { + char* arg; + uint64_t id; + size_t used; + dbg_take_word(rest, &arg); + used = dbg_parse_uint(arg, &id); + if (!used || arg[used] != '\0') { + driver_errf(DBG_TOOL, "expected breakpoint id"); + return 0; + } + dbg_cmd_set_enabled(s, (int)id, driver_streq(cmd, "enable")); + return 0; + } + if (driver_streq(cmd, "p") || driver_streq(cmd, "print")) { + char* name; + dbg_take_word(rest, &name); + if (!*name) { + driver_errf(DBG_TOOL, "usage: p <name>"); + return 0; + } + dbg_cmd_print(s, name); + return 0; + } + if (driver_streq(cmd, "x") || driver_streq(cmd, "examine")) { + char* addr_s; + char* count_s; + uint64_t addr; + uint64_t count = 16; + size_t used; + rest = dbg_take_word(rest, &addr_s); + if (!*addr_s) { + driver_errf(DBG_TOOL, "usage: x <addr> [count]"); + return 0; + } + used = dbg_parse_uint(addr_s, &addr); + if (!used || addr_s[used] != '\0') { + driver_errf(DBG_TOOL, "bad address '%s'", addr_s); + return 0; + } + dbg_take_word(rest, &count_s); + if (*count_s) { + used = dbg_parse_uint(count_s, &count); + if (!used || count_s[used] != '\0') { + driver_errf(DBG_TOOL, "bad count '%s'", count_s); + return 0; + } + } + dbg_cmd_examine(s, addr, (size_t)count); + return 0; + } + + driver_errf(DBG_TOOL, "unknown command: %s (try 'h')", cmd); + return 0; +} + +/* ============================================================ + * REPL + * ============================================================ */ + +static void dbg_repl(DbgState* s) +{ + char line[LINE_CAP]; + driver_printf("cfree dbg — 'h' for help, 'q' to quit\n"); + for (;;) { + int n; + driver_printf("(cfree) "); + driver_flush_stdout(); + n = driver_read_line(line, sizeof(line)); + if (n == 0) { /* EOF — Ctrl-D */ + driver_printf("\n"); + return; + } + if (n == -1) { + driver_errf(DBG_TOOL, "stdin read error"); + return; + } + if (n == -2) { /* SIGINT during prompt */ + driver_printf("\n"); + continue; + } + if (dbg_dispatch(s, line)) return; + } +} + +/* ============================================================ + * Top-level entry + * ============================================================ */ + +int driver_dbg(int argc, char** argv) +{ + DriverEnv env; + DbgOpts o = {0}; + CfreeOptions copts; + CfreeJit* jit = NULL; + DbgState st = {0}; + int rc; + + driver_env_init(&env); + o.env = &env; + + if (dbg_parse(argc, argv, &o) != 0) { + dbg_options_release(&o); + driver_env_fini(&env); + return 2; + } + + dbg_to_cfree(&o, &copts, &jit); + rc = cfree_run(&copts); + if (rc != 0) { + dbg_options_release(&o); + driver_env_fini(&env); + return rc; + } + + st.env = &env; + st.jit = jit; + st.prog_argc = (int)o.prog_argc; + st.prog_argv = o.prog_argv; + + st.entry_addr = cfree_jit_lookup(jit, o.entry); + if (!st.entry_addr) { + driver_errf(DBG_TOOL, "entry symbol not found: %s", o.entry); + cfree_jit_free(jit); + dbg_options_release(&o); + driver_env_fini(&env); + return 1; + } + + st.session = cfree_jit_session_new(jit); + if (!st.session) { + driver_errf(DBG_TOOL, + "JIT session not yet implemented in libcfree — " + "REPL will start in degraded mode (commands will surface " + "'session implementation pending' until the lib lands)"); + /* We deliberately keep going so the surrounding driver code path + * is exercised end-to-end during development. */ + } + + st.view = cfree_jit_view(jit); + if (st.view) { + /* DWARF needs a CfreeCompiler to allocate from. We don't have + * the compiler handle from cfree_run, so DWARF stays NULL until + * cfree_run grows an out-compiler param (or the dbg moves to + * driving the compile pipeline directly). When that lands, the + * call here is `cfree_dwarf_open(compiler, st.view)`. */ + st.dwarf = NULL; + } + + dbg_repl(&st); + + dbg_bps_release_all(&st); + if (st.dwarf) cfree_dwarf_close(st.dwarf); + if (st.session) cfree_jit_session_free(st.session); + cfree_jit_free(jit); + dbg_options_release(&o); + driver_env_fini(&env); + return 0; +} diff --git a/driver/driver.h b/driver/driver.h @@ -50,12 +50,25 @@ CfreeEnv driver_env_to_cfree(const DriverEnv*); * yet. v1: native-looking host target (chosen at compile time). */ CfreeTarget driver_host_target(void); +/* Parse a target triple string (`<arch>[-<vendor>]-<os>[-<env>]`) into a + * CfreeTarget. Recognized arches: x86_64/amd64, i386/i486/i586/i686, aarch64/ + * arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. Recognized OSes (scanned + * across the remaining components, so vendor tokens like `pc`/`apple`/ + * `unknown` are skipped): linux, darwin/macos, windows/win32, wasi, none/ + * freestanding. Sets arch/os/obj/ptr_size/ptr_align/big_endian; pic and + * code_model are left at their defaults. Returns 0 on success, nonzero on + * unrecognized arch or NULL inputs. */ +int driver_target_from_triple(const char* triple, CfreeTarget* out); + /* ---------------------------------------------------------------------- * Host-shim helpers * - * driver/env.c is the only TU in the driver allowed to depend on libc / - * POSIX directly. Everything else (main.c, cc.c, ld.c, ...) uses only - * the helpers below so the libc surface stays in one place. + * driver/env.c is the only TU in the driver allowed to depend on libc + * facilities that issue syscalls or touch host state — stdio, malloc, + * POSIX I/O, environment, time. Pure platform-independent libc (e.g. + * <string.h>, <ctype.h>, <stdint.h>, <stddef.h>) is fine in any TU. + * The shims below exist primarily for the syscall-shaped surface; other + * TUs may also call them for consistency. * ---------------------------------------------------------------------- */ /* String predicates and lookups. driver_streq / driver_strneq return @@ -79,10 +92,55 @@ void driver_memcpy (void* dst, const void* src, size_t n); * does not close the fd. */ CfreeWriter* driver_stdout_writer(DriverEnv*); +/* Test whether `path` names an existing filesystem entry (any type). + * Returns nonzero on existence, zero otherwise. Used by library-path + * resolution; intentionally distinct from read_all so candidate-search + * loops don't slurp file contents on a hit. */ +int driver_path_exists(const char* path); + /* Diagnostic printing to host stderr. Format is `"<tool>: <fmt>\n"`. */ void driver_errf(const char* tool, const char* fmt, ...); /* Formatted output to stdout. */ void driver_printf(const char* fmt, ...); +/* Lookup a process environment variable; returns NULL if unset. The returned + * pointer aliases libc-owned storage and is valid until the next setenv/ + * putenv from any caller. */ +const char* driver_getenv(const char* name); + +/* Read all of stdin into a freshly-allocated buffer. On success returns 1 + * and stores the buffer/size in out_data/out_size; the caller frees via + * driver_free(env, *out_data, *out_size). Returns 0 on read failure or + * allocation failure. */ +int driver_read_stdin(DriverEnv*, uint8_t** out_data, size_t* out_size); + +/* Read one line from stdin into `buf` (cap >= 2). Strips the trailing + * newline and NUL-terminates. Returns the line length on success, 0 at + * EOF (with buf[0]='\0'), -1 on read error, or -2 when the read was + * interrupted by a signal (caller should print a fresh prompt and + * retry). Over-long lines are truncated to cap-1 bytes; the remainder + * up to the next newline is consumed silently. */ +int driver_read_line(char* buf, size_t cap); + +/* Flush the host stdout. The dbg REPL prompt has no trailing newline, so + * without an explicit flush the prompt stays buffered until the next + * line of output. */ +void driver_flush_stdout(void); + +/* Install / restore a SIGINT handler. While installed, SIGINT runs `cb(user)` + * synchronously (so `cb` must be async-signal safe). Used by `dbg` to + * forward Ctrl-C into cfree_jit_session_interrupt while the worker is + * running, and to restore SIG_DFL while sitting at the REPL prompt so + * Ctrl-C terminates the program normally. Returns 0 on success. */ +int driver_install_sigint(void (*cb)(void*), void* user); +void driver_restore_sigint(void); + +/* Host-symbol resolver for JIT extern_resolver. Looks up `name` via + * dlsym(RTLD_DEFAULT, ...) on POSIX hosts, returning NULL on miss. Stateless; + * `user` is ignored and may be NULL. Wired into `cfree run` so JITed code + * can call libc symbols (printf, malloc, ...) without an explicit linker + * step. */ +void* driver_dlsym_resolver(void* user, const char* name); + #endif diff --git a/driver/env.c b/driver/env.c @@ -1,6 +1,9 @@ #include "driver.h" +#include <dlfcn.h> +#include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -269,6 +272,13 @@ int driver_has_suffix(const char* s, const char* suffix) return ls >= lf && strcmp(s + ls - lf, suffix) == 0; } +int driver_path_exists(const char* path) +{ + struct stat sb; + if (!path) return 0; + return stat(path, &sb) == 0; +} + void* driver_alloc(DriverEnv* e, size_t n) { return e->heap->alloc(e->heap, n, _Alignof(max_align_t)); @@ -309,6 +319,133 @@ void driver_printf(const char* fmt, ...) va_end(ap); } +const char* driver_getenv(const char* name) +{ + return getenv(name); +} + +int driver_read_stdin(DriverEnv* e, uint8_t** out_data, size_t* out_size) +{ + /* stdin is unseekable in the general case (pipes, ttys), so grow a buffer + * geometrically as we read, then shrink to the exact byte count so + * out_size is a valid argument to driver_free. */ + size_t cap = 4096; + size_t len = 0; + uint8_t* buf = e->heap->alloc(e->heap, cap, 1); + if (!buf) return 0; + for (;;) { + ssize_t n; + if (len == cap) { + size_t newcap = cap * 2; + uint8_t* nb = e->heap->realloc(e->heap, buf, cap, newcap, 1); + if (!nb) { e->heap->free(e->heap, buf, cap); return 0; } + buf = nb; + cap = newcap; + } + n = read(STDIN_FILENO, buf + len, cap - len); + if (n == 0) break; + if (n < 0) { e->heap->free(e->heap, buf, cap); return 0; } + len += (size_t)n; + } + if (len < cap) { + uint8_t* shrunk = len + ? e->heap->realloc(e->heap, buf, cap, len, 1) + : NULL; + if (len && !shrunk) { + /* Shrink failed: keep the larger buffer. The release size below + * tracks `cap`, so this remains free-correct. */ + *out_data = buf; + *out_size = cap; + return 1; + } + if (!len) { e->heap->free(e->heap, buf, cap); buf = NULL; } + else { buf = shrunk; } + } + *out_data = buf; + *out_size = len; + return 1; +} + +void* driver_dlsym_resolver(void* user, const char* name) +{ + (void)user; + return dlsym(RTLD_DEFAULT, name); +} + +int driver_read_line(char* buf, size_t cap) +{ + /* Reads one line from stdin into `buf` (cap >= 2 required). The trailing + * newline is stripped; the result is NUL-terminated. Returns the number + * of characters in the line on success, 0 at EOF (with buf[0]='\0'), + * -1 on read error, or -2 when the read was interrupted by a signal + * (errno == EINTR). Lines longer than cap-1 bytes are truncated to + * cap-1 and the remainder up to the next newline is consumed. */ + size_t len = 0; + if (!buf || cap < 2) return -1; + for (;;) { + int c; + errno = 0; + c = fgetc(stdin); + if (c == EOF) { + buf[len] = '\0'; + if (errno == EINTR) { + clearerr(stdin); + return -2; + } + if (ferror(stdin)) return -1; + if (len == 0) return 0; + return (int)len; + } + if (c == '\n') { + buf[len] = '\0'; + return (int)len; + } + if (len + 1 < cap) buf[len++] = (char)c; + /* else: drop overflow; keep consuming until the newline. */ + } +} + +void driver_flush_stdout(void) +{ + fflush(stdout); +} + +/* SIGINT handling for the dbg REPL. The driver installs a handler that + * just calls `cb(user)` so the dbg TU can decide what to do (asynchronously + * call cfree_jit_session_interrupt). The handler is short and async-signal + * safe by construction; the cb is the caller's responsibility. */ + +static void (*s_sigint_cb)(void*); +static void* s_sigint_cb_user; + +static void sigint_trampoline(int sig) +{ + (void)sig; + if (s_sigint_cb) s_sigint_cb(s_sigint_cb_user); +} + +int driver_install_sigint(void (*cb)(void*), void* user) +{ + struct sigaction sa; + s_sigint_cb = cb; + s_sigint_cb_user = user; + sa.sa_handler = sigint_trampoline; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* no SA_RESTART: fgetc returns EINTR */ + return sigaction(SIGINT, &sa, NULL) == 0 ? 0 : 1; +} + +void driver_restore_sigint(void) +{ + struct sigaction sa; + s_sigint_cb = NULL; + s_sigint_cb_user = NULL; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); +} + CfreeTarget driver_host_target(void) { CfreeTarget t; @@ -350,5 +487,7 @@ CfreeTarget driver_host_target(void) t.ptr_size = (uint8_t)sizeof(void*); t.ptr_align = (uint8_t)sizeof(void*); t.big_endian = 0; + t.pic = CFREE_PIC_NONE; + t.code_model = CFREE_CM_DEFAULT; return t; } diff --git a/driver/ld.c b/driver/ld.c @@ -1,55 +1,195 @@ #include "driver.h" +#include "lib_resolve.h" #include <stdint.h> -/* `cfree ld` — link object/archive paths into an executable. The flag set - * is intentionally minimal: -o, -e, and positional inputs separated into - * .a archives vs everything-else (treated as object files). Library - * resolution (-l/-L) is deferred. */ +/* `cfree ld` — link object/archive inputs into an executable. The driver + * loads each input via env.file_io, optionally parses a `-T` linker script + * into the structured form, and calls cfree_link_exe directly so the full + * link surface (per-archive flags, group cycling, build-id) is reachable + * without thickening the convenience CfreeOptions struct. + * + * Supported flags: + * -o out output path (required, exactly one) + * -e symbol entry symbol + * -T script.ld linker script (parsed, not raw) + * -L dir library search path (-l targets) + * -l name resolves to lib<name>.a via -L + * -static / -pie / -no-pie target.pic + * --whole-archive / --no-whole-archive + * positional state for following .a + * --start-group / --end-group cyclic-resolution group of archives + * --build-id={none|sha256|uuid|0xHEX} + * + * Out of scope (until shared-library output exists): -shared, -soname, + * -rpath, -lname.so resolution. */ #define LD_TOOL "ld" +/* Per-archive metadata mirroring the relevant subset of + * CfreeBytesInputArchive plus driver-side ownership info. */ +typedef struct LdArchive { + const char* path; /* path used for both open and CfreeBytesInput.name */ + int owned; /* 1 if `path` was alloc'd by lib_resolve */ + size_t owned_size; /* allocation size (for driver_free) */ + uint8_t flags; /* bitmask of CfreeLinkArchFlag */ + uint8_t group_id; /* cyclic resolution group id; 0 = single-pass */ +} LdArchive; + typedef struct LdOptions { DriverEnv* env; size_t argv_bound; - const char* output_path; /* -o */ - const char* entry; /* -e */ - const char** object_files; /* positional, not ending .a */ + CfreeTarget target; + + const char* output_path; /* -o */ + int output_seen; + const char* entry; /* -e */ + const char* script_path; /* -T */ + + const char** object_files; uint32_t nobject_files; - const char** archives; /* positional, ending .a */ + + LdArchive* archives; uint32_t narchives; + + const char** lib_dirs; /* -L */ + uint32_t nlib_dirs; + + /* --build-id state */ + uint8_t build_id_mode; /* CfreeBuildIdMode */ + uint8_t* build_id_bytes; /* USER mode: parsed hex, owned */ + uint32_t build_id_len; + size_t build_id_alloc; /* allocation size for free */ + + /* Mutable parse state for positional archive flags / groups. */ + uint8_t cur_arch_flags; /* pending CfreeLinkArchFlag bits */ + uint8_t cur_group_id; /* 0 outside any --start-group */ + uint8_t next_group_id; /* increments on --start-group */ } LdOptions; static void ld_usage(void) { driver_errf(LD_TOOL, "%s", - "usage: cfree ld -o out [-e entry] input.o|input.a..."); + "usage: cfree ld -o out [-e entry] [-T script.ld]\n" + " [-L dir]... [-l name]...\n" + " [-static|-pie|-no-pie]\n" + " [--whole-archive|--no-whole-archive]\n" + " [--start-group ... --end-group]\n" + " [--build-id={none|sha256|uuid|0xHEX}]\n" + " input.o|input.a..."); } +/* ---------- argv-sized scratch arrays ---------- */ + static int ld_alloc_arrays(LdOptions* o, int argc) { size_t bound = (size_t)argc; o->argv_bound = bound; o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files)); o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives)); - if (!o->object_files || !o->archives) { + o->lib_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_dirs)); + if (!o->object_files || !o->archives || !o->lib_dirs) { driver_errf(LD_TOOL, "out of memory"); return 1; } return 0; } +/* ---------- positional archive bookkeeping ---------- */ + +static void ld_push_archive(LdOptions* o, const char* path, + int owned, size_t owned_size) +{ + LdArchive* a = &o->archives[o->narchives++]; + a->path = path; + a->owned = owned; + a->owned_size = owned_size; + a->flags = o->cur_arch_flags; + a->group_id = o->cur_group_id; +} + +/* ---------- --build-id parsing ---------- */ + +static int hex_nibble(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + return -1; +} + +/* Parse `--build-id=...` argument into options. Accepts "none", "sha256", + * "uuid", or "0x<even-hex>". Returns 0 on success, 1 on bad value. */ +static int ld_parse_build_id(LdOptions* o, const char* val) +{ + if (driver_streq(val, "none")) { o->build_id_mode = CFREE_BUILDID_NONE; return 0; } + if (driver_streq(val, "sha256")) { o->build_id_mode = CFREE_BUILDID_SHA256; return 0; } + if (driver_streq(val, "uuid")) { o->build_id_mode = CFREE_BUILDID_UUID; return 0; } + + /* "0x<hex>" — must be even number of hex digits, at least one byte. */ + if (val[0] == '0' && (val[1] == 'x' || val[1] == 'X')) { + const char* hex = val + 2; + size_t hex_len = driver_strlen(hex); + size_t nbytes; + size_t i; + uint8_t* buf; + if (hex_len == 0 || (hex_len & 1u)) { + driver_errf(LD_TOOL, "--build-id=0xHEX requires an even number of hex digits"); + return 1; + } + nbytes = hex_len / 2; + buf = driver_alloc(o->env, nbytes); + if (!buf) { driver_errf(LD_TOOL, "out of memory"); return 1; } + for (i = 0; i < nbytes; ++i) { + int hi = hex_nibble(hex[2 * i]); + int lo = hex_nibble(hex[2 * i + 1]); + if (hi < 0 || lo < 0) { + driver_errf(LD_TOOL, "--build-id: invalid hex digit"); + driver_free(o->env, buf, nbytes); + return 1; + } + buf[i] = (uint8_t)((hi << 4) | lo); + } + o->build_id_mode = CFREE_BUILDID_USER; + o->build_id_bytes = buf; + o->build_id_len = (uint32_t)nbytes; + o->build_id_alloc = nbytes; + return 0; + } + + driver_errf(LD_TOOL, "--build-id: unknown value: %s", val); + return 1; +} + +/* ---------- argv parser ---------- */ + +/* If `arg` starts with `prefix` followed by '=', returns the tail past the + * '='; otherwise returns NULL. */ +static const char* arg_eq_value(const char* arg, const char* prefix) +{ + size_t n = driver_strlen(prefix); + if (!driver_strneq(arg, prefix, n)) return NULL; + if (arg[n] != '=') return NULL; + return arg + n + 1; +} + static int ld_parse(int argc, char** argv, LdOptions* o) { int i; if (ld_alloc_arrays(o, argc) != 0) return 1; + o->target = driver_host_target(); + for (i = 1; i < argc; ++i) { const char* a = argv[i]; + const char* val; + if (driver_streq(a, "-o")) { if (++i >= argc) { driver_errf(LD_TOOL, "-o requires an argument"); return 1; } + if (o->output_seen) { driver_errf(LD_TOOL, "-o specified more than once"); return 1; } o->output_path = argv[i]; + o->output_seen = 1; continue; } if (driver_streq(a, "-e")) { @@ -57,12 +197,84 @@ static int ld_parse(int argc, char** argv, LdOptions* o) o->entry = argv[i]; continue; } + if (driver_streq(a, "-T")) { + if (++i >= argc) { driver_errf(LD_TOOL, "-T requires an argument"); return 1; } + o->script_path = argv[i]; + continue; + } + + if (driver_strneq(a, "-L", 2)) { + const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); + if (!dir) { driver_errf(LD_TOOL, "-L requires an argument"); return 1; } + o->lib_dirs[o->nlib_dirs++] = dir; + continue; + } + + if (driver_strneq(a, "-l", 2)) { + const char* name = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); + char* resolved; + size_t resolved_size; + if (!name) { driver_errf(LD_TOOL, "-l requires an argument"); return 1; } + if (driver_lib_resolve(o->env, name, o->lib_dirs, o->nlib_dirs, + &resolved, &resolved_size) != 0) { + driver_errf(LD_TOOL, "cannot find -l%s", name); + return 1; + } + ld_push_archive(o, resolved, 1, resolved_size); + continue; + } + + if (driver_streq(a, "-static")) { o->target.pic = CFREE_PIC_NONE; continue; } + if (driver_streq(a, "-pie")) { o->target.pic = CFREE_PIC_PIE; continue; } + if (driver_streq(a, "-no-pie")) { o->target.pic = CFREE_PIC_NONE; continue; } + + if (driver_streq(a, "--whole-archive")) { + o->cur_arch_flags |= CFREE_LAF_WHOLE_ARCHIVE; + continue; + } + if (driver_streq(a, "--no-whole-archive")) { + o->cur_arch_flags &= (uint8_t)~CFREE_LAF_WHOLE_ARCHIVE; + continue; + } + if (driver_streq(a, "--start-group")) { + if (o->cur_group_id != 0) { + driver_errf(LD_TOOL, "nested --start-group is not supported"); + return 1; + } + if (o->next_group_id == UINT8_MAX) { + driver_errf(LD_TOOL, "too many --start-group occurrences"); + return 1; + } + o->next_group_id++; + o->cur_group_id = o->next_group_id; + continue; + } + if (driver_streq(a, "--end-group")) { + if (o->cur_group_id == 0) { + driver_errf(LD_TOOL, "--end-group without --start-group"); + return 1; + } + o->cur_group_id = 0; + continue; + } + + if ((val = arg_eq_value(a, "--build-id")) != NULL) { + if (ld_parse_build_id(o, val) != 0) return 1; + continue; + } + if (driver_streq(a, "--build-id")) { + /* Bareword form defaults to sha256, matching GNU ld. */ + o->build_id_mode = CFREE_BUILDID_SHA256; + continue; + } + if (a[0] == '-' && a[1] != '\0') { driver_errf(LD_TOOL, "unknown flag: %s", a); return 1; } + if (driver_has_suffix(a, ".a")) { - o->archives[o->narchives++] = a; + ld_push_archive(o, a, 0, 0); } else { o->object_files[o->nobject_files++] = a; } @@ -72,6 +284,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) driver_errf(LD_TOOL, "-o is required"); return 1; } + if (o->cur_group_id != 0) { + driver_errf(LD_TOOL, "missing --end-group"); + return 1; + } if (o->nobject_files == 0 && o->narchives == 0) { driver_errf(LD_TOOL, "no input files"); ld_usage(); @@ -80,34 +296,195 @@ static int ld_parse(int argc, char** argv, LdOptions* o) return 0; } +/* ---------- options teardown ---------- */ + static void ld_options_release(LdOptions* o) { - size_t bound = o->argv_bound; + size_t bound = o->argv_bound; + uint32_t i; + for (i = 0; i < o->narchives; ++i) { + LdArchive* a = &o->archives[i]; + if (a->owned && a->path) { + driver_free(o->env, (void*)a->path, a->owned_size); + } + } + if (o->build_id_bytes) { + driver_free(o->env, o->build_id_bytes, o->build_id_alloc); + } driver_free(o->env, o->object_files, bound * sizeof(*o->object_files)); driver_free(o->env, o->archives, bound * sizeof(*o->archives)); + driver_free(o->env, o->lib_dirs, bound * sizeof(*o->lib_dirs)); +} + +/* ---------- input loading ---------- */ + +typedef struct LoadedFile { + const CfreeFileIO* io; + CfreeFileData data; + int loaded; +} LoadedFile; + +static int load_file(const CfreeFileIO* io, const char* path, LoadedFile* out) +{ + out->io = io; + out->loaded = 0; + out->data.data = NULL; + out->data.size = 0; + out->data.token = NULL; + if (!io->read_all(io->user, path, &out->data)) return 1; + out->loaded = 1; + return 0; } -static void ld_to_cfree(const LdOptions* o, CfreeOptions* out) +static void release_file(LoadedFile* lf) { - CfreeOptions z = {0}; - *out = z; - out->target = driver_host_target(); - out->env = driver_env_to_cfree(o->env); - out->output_kind = CFREE_OUTPUT_EXE; - out->output_path = o->output_path; - out->entry = o->entry; - out->object_files = o->object_files; - out->nobject_files = o->nobject_files; - out->archives = o->archives; - out->narchives = o->narchives; + if (lf->loaded && lf->io && lf->io->release) { + lf->io->release(lf->io->user, &lf->data); + } + lf->loaded = 0; +} + +static void release_all(LoadedFile* arr, uint32_t n) +{ + uint32_t i; + if (!arr) return; + for (i = 0; i < n; ++i) release_file(&arr[i]); +} + +/* ---------- link execution ---------- */ + +/* Run the link. Returns 0 on success, nonzero on error. The caller owns + * `o` and any allocations within; this function only owns transient + * loaded-file buffers and the writer/compiler it constructs. */ +static int ld_run_link(LdOptions* o) +{ + CfreeEnv cenv = driver_env_to_cfree(o->env); + const CfreeFileIO* io = cenv.file_io; + CfreeCompiler* compiler = NULL; + CfreeWriter* writer = NULL; + LoadedFile* obj_lf = NULL; + LoadedFile* arch_lf = NULL; + LoadedFile script_lf = {0}; + CfreeBytesInput* obj_in = NULL; + CfreeBytesInputArchive* arch_in = NULL; + const CfreeLinkScript* script = NULL; + CfreeLinkOptions link_opts; + uint32_t i; + int rc = 1; + + if (!io || !io->read_all || !io->open_writer) { + driver_errf(LD_TOOL, "host file I/O unavailable"); + return 1; + } + + /* Allocate scratch parallel arrays sized to the actual input counts. */ + if (o->nobject_files) { + obj_lf = driver_alloc_zeroed(o->env, + o->nobject_files * sizeof(*obj_lf)); + obj_in = driver_alloc_zeroed(o->env, + o->nobject_files * sizeof(*obj_in)); + if (!obj_lf || !obj_in) { driver_errf(LD_TOOL, "out of memory"); goto out; } + } + if (o->narchives) { + arch_lf = driver_alloc_zeroed(o->env, + o->narchives * sizeof(*arch_lf)); + arch_in = driver_alloc_zeroed(o->env, + o->narchives * sizeof(*arch_in)); + if (!arch_lf || !arch_in) { driver_errf(LD_TOOL, "out of memory"); goto out; } + } + + /* Load object files. */ + for (i = 0; i < o->nobject_files; ++i) { + const char* path = o->object_files[i]; + if (load_file(io, path, &obj_lf[i]) != 0) { + driver_errf(LD_TOOL, "failed to read: %s", path); + goto out; + } + obj_in[i].name = path; + obj_in[i].data = obj_lf[i].data.data; + obj_in[i].len = obj_lf[i].data.size; + } + /* Load archives. */ + for (i = 0; i < o->narchives; ++i) { + const LdArchive* a = &o->archives[i]; + if (load_file(io, a->path, &arch_lf[i]) != 0) { + driver_errf(LD_TOOL, "failed to read: %s", a->path); + goto out; + } + arch_in[i].input.name = a->path; + arch_in[i].input.data = arch_lf[i].data.data; + arch_in[i].input.len = arch_lf[i].data.size; + arch_in[i].flags = a->flags; + arch_in[i].group_id = a->group_id; + } + + /* Load and parse the linker script (if any). The structured script is + * arena-owned by the compiler; we free it explicitly before the + * compiler is destroyed. */ + if (o->script_path) { + if (load_file(io, o->script_path, &script_lf) != 0) { + driver_errf(LD_TOOL, "failed to read: %s", o->script_path); + goto out; + } + } + + compiler = cfree_compiler_new(o->target, &cenv); + if (!compiler) { + driver_errf(LD_TOOL, "failed to initialize compiler"); + goto out; + } + + if (script_lf.loaded) { + if (cfree_link_script_parse(compiler, + (const char*)script_lf.data.data, + script_lf.data.size, + &script) != 0) { + /* The parser reports a diagnostic via env.diag. */ + goto out; + } + } + + writer = io->open_writer(io->user, o->output_path); + if (!writer) { + driver_errf(LD_TOOL, "failed to open output: %s", o->output_path); + goto out; + } + + { + CfreeLinkOptions zero = {0}; + link_opts = zero; + } + link_opts.obj_bytes = obj_in; + link_opts.nobj_bytes = o->nobject_files; + link_opts.archives = arch_in; + link_opts.narchives = o->narchives; + link_opts.linker_script = script; + link_opts.entry = o->entry; + link_opts.build_id_mode = o->build_id_mode; + link_opts.build_id_bytes = o->build_id_bytes; + link_opts.build_id_len = o->build_id_len; + + rc = cfree_link_exe(compiler, &link_opts, writer); + +out: + if (writer) cfree_writer_close(writer); + if (script && compiler) cfree_link_script_free(compiler, script); + if (compiler) cfree_compiler_free(compiler); + release_file(&script_lf); + release_all(arch_lf, o->narchives); + release_all(obj_lf, o->nobject_files); + if (arch_in) driver_free(o->env, arch_in, o->narchives * sizeof(*arch_in)); + if (arch_lf) driver_free(o->env, arch_lf, o->narchives * sizeof(*arch_lf)); + if (obj_in) driver_free(o->env, obj_in, o->nobject_files * sizeof(*obj_in)); + if (obj_lf) driver_free(o->env, obj_lf, o->nobject_files * sizeof(*obj_lf)); + return rc; } int driver_ld(int argc, char** argv) { - DriverEnv env; - LdOptions lo = {0}; - CfreeOptions copts; - int rc; + DriverEnv env; + LdOptions lo = {0}; + int rc; driver_env_init(&env); lo.env = &env; @@ -118,8 +495,7 @@ int driver_ld(int argc, char** argv) return 2; } - ld_to_cfree(&lo, &copts); - rc = cfree_run(&copts); + rc = ld_run_link(&lo); ld_options_release(&lo); driver_env_fini(&env); diff --git a/driver/lib_resolve.c b/driver/lib_resolve.c @@ -0,0 +1,49 @@ +#include "lib_resolve.h" + +#include <stddef.h> +#include <stdint.h> + +/* Compose `<dir>/lib<name>.a` into a fresh heap buffer. Inserts a separating + * '/' iff `dir` does not already end in one. Empty `dir` is treated as the + * current directory: the path is `lib<name>.a`. */ +static char* compose_path(DriverEnv* env, const char* dir, const char* name, + size_t* out_size) +{ + size_t dlen = driver_strlen(dir); + size_t nlen = driver_strlen(name); + size_t need_slash = (dlen > 0 && dir[dlen - 1] != '/') ? 1 : 0; + /* "<dir>" + "/"? + "lib" + "<name>" + ".a" + NUL */ + size_t bytes = dlen + need_slash + 3 + nlen + 2 + 1; + char* buf = driver_alloc(env, bytes); + size_t off = 0; + if (!buf) return NULL; + if (dlen) { driver_memcpy(buf + off, dir, dlen); off += dlen; } + if (need_slash) { buf[off++] = '/'; } + driver_memcpy(buf + off, "lib", 3); off += 3; + if (nlen) { driver_memcpy(buf + off, name, nlen); off += nlen; } + driver_memcpy(buf + off, ".a", 2); off += 2; + buf[off] = '\0'; + *out_size = bytes; + return buf; +} + +int driver_lib_resolve(DriverEnv* env, const char* name, + const char* const* search_dirs, uint32_t nsearch_dirs, + char** out_path, size_t* out_size) +{ + uint32_t i; + if (!env || !name) return 1; + + for (i = 0; i < nsearch_dirs; ++i) { + size_t bytes; + char* cand = compose_path(env, search_dirs[i], name, &bytes); + if (!cand) return 1; + if (driver_path_exists(cand)) { + *out_path = cand; + *out_size = bytes; + return 0; + } + driver_free(env, cand, bytes); + } + return 1; +} diff --git a/driver/lib_resolve.h b/driver/lib_resolve.h @@ -0,0 +1,26 @@ +#ifndef CFREE_DRIVER_LIB_RESOLVE_H +#define CFREE_DRIVER_LIB_RESOLVE_H + +#include "driver.h" + +/* Resolve `-l<name>` against a list of `-L`-style search directories. + * + * On success, returns 0 and writes a heap-allocated, NUL-terminated path + * into `*out_path`, with its allocation size in `*out_size`. The caller + * frees the path via driver_free(env, *out_path, *out_size). + * + * On failure, returns nonzero with `*out_path` unchanged. Failure cases: + * - no `lib<name>.a` exists in any of the search directories + * - allocation failure while constructing a candidate path + * + * v1 only resolves static archives (`lib<name>.a`); shared-library + * resolution (`lib<name>.so` / `.dylib` / `.dll`) waits on shared-output + * support in libcfree. */ +int driver_lib_resolve(DriverEnv* env, + const char* name, + const char* const* search_dirs, + uint32_t nsearch_dirs, + char** out_path, + size_t* out_size); + +#endif diff --git a/driver/objdump.c b/driver/objdump.c @@ -1,14 +1,29 @@ #include "driver.h" -/* `cfree objdump` — print section/symbol info for object files and archives. - * Archives are auto-detected by magic; each member is dumped in turn. - * All display logic lives here; the library supplies data-access APIs. */ +/* `cfree objdump` — print section/symbol info, disassembly, hex contents, + * and relocations for object files and archives. Archives are auto-detected + * by magic; each member is dumped in turn. All display logic lives here; + * the library supplies data-access APIs. */ #define OBJDUMP_TOOL "objdump" +#define MAX_J_FILTERS 16 + +typedef struct ObjdumpOpts { + int h; /* -h: section headers */ + int t; /* -t: symbol table */ + int d; /* -d: disasm exec sections */ + int D; /* -D: disasm all sections */ + int r; /* -r: relocations */ + int s; /* -s: hex section contents */ + const char* j[MAX_J_FILTERS]; + int nj; +} ObjdumpOpts; + static void objdump_usage(void) { - driver_errf(OBJDUMP_TOOL, "%s", "usage: cfree objdump input..."); + driver_errf(OBJDUMP_TOOL, "%s", + "usage: cfree objdump [-h] [-t] [-d] [-D] [-r] [-s] [-j NAME ...] input..."); } static const char* fmt_str(CfreeObjFmt fmt, uint8_t ptr_size) @@ -61,26 +76,71 @@ static char sym_kind_char(CfreeSymKind k) return ' '; } -static void dump_obj(const char* label, CfreeObjFile* f) +static int j_match(const ObjdumpOpts* o, const char* name) { - CfreeTarget target = cfree_obj_target(f); - CfreeObjFmt fmt = cfree_obj_fmt(f); - uint32_t nsec = cfree_obj_nsections(f); - uint32_t i; - CfreeObjSymIter* it; - CfreeObjSymInfo sym; + int i; + if (o->nj == 0) return 1; + for (i = 0; i < o->nj; ++i) { + if (driver_streq(o->j[i], name)) return 1; + } + return 0; +} - driver_printf("%s:\tfile format %s-%s\n\n", - label, fmt_str(fmt, target.ptr_size), arch_str(target.arch)); +/* Compose the comma-separated flag tag list GNU objdump prints in -h. */ +static void render_sec_flags(const CfreeObjSecInfo* sec, char* buf, size_t cap) +{ + size_t n = 0; + const char* tags[8]; + int nt = 0; + int i; + int is_bss = (sec->kind == CFREE_SEC_BSS); + + if (!is_bss && sec->size > 0) tags[nt++] = "CONTENTS"; + if (sec->flags & CFREE_SF_ALLOC) tags[nt++] = "ALLOC"; + if ((sec->flags & CFREE_SF_ALLOC) && !is_bss) tags[nt++] = "LOAD"; + if ((sec->flags & CFREE_SF_ALLOC) && + !(sec->flags & CFREE_SF_WRITE)) tags[nt++] = "READONLY"; + if (sec->flags & CFREE_SF_EXEC) tags[nt++] = "CODE"; + if ((sec->flags & CFREE_SF_WRITE) && + !(sec->flags & CFREE_SF_EXEC) && + (sec->flags & CFREE_SF_ALLOC)) tags[nt++] = "DATA"; + if (sec->flags & CFREE_SF_TLS) tags[nt++] = "TLS"; + if (sec->kind == CFREE_SEC_DEBUG) tags[nt++] = "DEBUGGING"; + + for (i = 0; i < nt && n + 1 < cap; ++i) { + const char* t = tags[i]; + if (i > 0 && n + 1 < cap) buf[n++] = ','; + while (*t && n + 1 < cap) buf[n++] = *t++; + } + buf[n] = '\0'; +} + +static void dump_sections(CfreeObjFile* f, const ObjdumpOpts* opts) +{ + uint32_t nsec = cfree_obj_nsections(f); + uint32_t i; + char flagbuf[128]; driver_printf("Sections:\n"); - driver_printf("Idx %-20s Size Align\n", "Name"); + driver_printf("Idx Name Size Align Flags\n"); for (i = 0; i < nsec; ++i) { CfreeObjSecInfo sec = cfree_obj_section(f, i); - driver_printf("%03x %-20s %08x %04x\n", - i, sec.name, sec.size, sec.align); + if (!j_match(opts, sec.name)) continue; + render_sec_flags(&sec, flagbuf, sizeof(flagbuf)); + driver_printf("%3u %-20s %08x 2**%-2u %s\n", + i, + sec.name[0] ? sec.name : "(anon)", + sec.size, + sec.align ? (unsigned)__builtin_ctz(sec.align ? sec.align : 1) : 0, + flagbuf); } driver_printf("\n"); +} + +static void dump_symbols(CfreeObjFile* f, const ObjdumpOpts* opts) +{ + CfreeObjSymIter* it; + CfreeObjSymInfo sym; driver_printf("SYMBOL TABLE:\n"); it = cfree_obj_symiter_new(f); @@ -91,6 +151,7 @@ static void dump_obj(const char* label, CfreeObjFile* f) } else { CfreeObjSecInfo sec = cfree_obj_section(f, sym.section); secname = sec.name[0] ? sec.name : "(none)"; + if (opts->nj && !j_match(opts, secname)) continue; } driver_printf("%016llx %c %c %-18s %016llx %s\n", (unsigned long long)sym.value, @@ -101,10 +162,152 @@ static void dump_obj(const char* label, CfreeObjFile* f) sym.name[0] ? sym.name : "(none)"); } cfree_obj_symiter_free(it); + driver_printf("\n"); +} + +static void dump_hex(CfreeObjFile* f, const ObjdumpOpts* opts) +{ + uint32_t nsec = cfree_obj_nsections(f); + uint32_t i; + + for (i = 0; i < nsec; ++i) { + CfreeObjSecInfo sec = cfree_obj_section(f, i); + size_t len = 0; + const uint8_t* data; + size_t ofs; + + if (!j_match(opts, sec.name)) continue; + data = cfree_obj_section_data(f, i, &len); + if (!data || len == 0) continue; + + driver_printf("Contents of section %s:\n", + sec.name[0] ? sec.name : "(anon)"); + for (ofs = 0; ofs < len; ofs += 16) { + size_t j; + char ascii[17]; + driver_printf(" %04llx ", (unsigned long long)ofs); + for (j = 0; j < 16; ++j) { + if (ofs + j < len) { + driver_printf("%02x", data[ofs + j]); + ascii[j] = (data[ofs + j] >= 32 && data[ofs + j] < 127) + ? (char)data[ofs + j] : '.'; + } else { + driver_printf(" "); + ascii[j] = ' '; + } + if ((j & 1) == 1) driver_printf(" "); + } + ascii[16] = '\0'; + driver_printf(" %s\n", ascii); + } + } + driver_printf("\n"); +} + +static void dump_relocs(CfreeObjFile* f, const ObjdumpOpts* opts) +{ + CfreeObjRelocIter* it = cfree_obj_reliter_new(f); + CfreeObjReloc r; + uint32_t cur_sec = (uint32_t)-1; + int printed_header = 0; + int emitted_any = 0; + + while (cfree_obj_reliter_next(it, &r)) { + CfreeObjSecInfo sec = cfree_obj_section(f, r.section); + if (!j_match(opts, sec.name)) continue; + + if (r.section != cur_sec) { + if (printed_header) driver_printf("\n"); + driver_printf("RELOCATION RECORDS FOR [%s]:\n", + sec.name[0] ? sec.name : "(anon)"); + driver_printf("OFFSET TYPE VALUE\n"); + cur_sec = r.section; + printed_header = 1; + } + + if (r.addend) { + driver_printf("%016llx %-17s %s%c0x%llx\n", + (unsigned long long)r.offset, + r.kind_name, + r.sym_name[0] ? r.sym_name : "*ABS*", + r.addend < 0 ? '-' : '+', + (unsigned long long)(r.addend < 0 ? -r.addend : r.addend)); + } else { + driver_printf("%016llx %-17s %s\n", + (unsigned long long)r.offset, + r.kind_name, + r.sym_name[0] ? r.sym_name : "*ABS*"); + } + emitted_any = 1; + } + cfree_obj_reliter_free(it); + if (emitted_any) driver_printf("\n"); +} + +static void dump_disasm(CfreeCompiler* dc, CfreeObjFile* f, const ObjdumpOpts* opts) +{ + CfreeObjBuilder* ob = cfree_obj_builder(f); + uint32_t nsec = cfree_obj_nsections(f); + uint32_t i; + + for (i = 0; i < nsec; ++i) { + CfreeObjSecInfo sec = cfree_obj_section(f, i); + size_t len = 0; + const uint8_t* data; + CfreeDisasmIter* dis; + CfreeInsn insn; + int want; + + want = opts->D ? 1 : ((sec.flags & CFREE_SF_EXEC) != 0); + if (!want) continue; + if (!j_match(opts, sec.name)) continue; + + data = cfree_obj_section_data(f, i, &len); + if (!data || len == 0) continue; + + driver_printf("Disassembly of section %s:\n\n", + sec.name[0] ? sec.name : "(anon)"); + + dis = cfree_disasm_iter_new(dc, data, len, 0, ob); + if (!dis) continue; + while (cfree_disasm_iter_next(dis, &insn)) { + uint32_t b; + driver_printf("%8llx:\t", (unsigned long long)insn.vaddr); + for (b = 0; b < insn.nbytes; ++b) driver_printf("%02x ", insn.bytes[b]); + for (b = insn.nbytes; b < 8; ++b) driver_printf(" "); + driver_printf("\t%s", insn.mnemonic ? insn.mnemonic : ""); + if (insn.operands && insn.operands[0]) { + driver_printf(" %s", insn.operands); + } + if (insn.annotation && insn.annotation[0]) { + driver_printf(" # %s", insn.annotation); + } + driver_printf("\n"); + } + cfree_disasm_iter_free(dis); + driver_printf("\n"); + } +} + +static void dump_obj(CfreeCompiler* dc, const char* label, CfreeObjFile* f, + const ObjdumpOpts* opts) +{ + CfreeTarget target = cfree_obj_target(f); + CfreeObjFmt fmt = cfree_obj_fmt(f); + + driver_printf("%s:\tfile format %s-%s\n\n", + label, fmt_str(fmt, target.ptr_size), arch_str(target.arch)); + + if (opts->h) dump_sections(f, opts); + if (opts->t) dump_symbols(f, opts); + if (opts->s) dump_hex(f, opts); + if (opts->d || opts->D) dump_disasm(dc, f, opts); + if (opts->r) dump_relocs(f, opts); } static int dump_archive(const char* path, const CfreeBytesInput* input, - const CfreeEnv* cenv, CfreeTarget target) + const CfreeEnv* cenv, CfreeTarget target, + CfreeCompiler* dc, const ObjdumpOpts* opts) { CfreeArIter it; CfreeArMember member; @@ -120,7 +323,7 @@ static int dump_archive(const char* path, const CfreeBytesInput* input, /* Build "archive.a(member.o)" label. */ j = 0; - { const char* p = path; while (*p && j < 230) label[j++] = *p++; } + { const char* p = path; while (*p && j < 230) label[j++] = *p++; } label[j++] = '('; { const char* p = member.name; while (*p && j < 252) label[j++] = *p++; } label[j++] = ')'; @@ -135,18 +338,42 @@ static int dump_archive(const char* path, const CfreeBytesInput* input, driver_errf(OBJDUMP_TOOL, "failed to parse member: %s", label); continue; } - dump_obj(label, f); + dump_obj(dc, label, f, opts); cfree_obj_close(f); } return 0; } +static int parse_short_flags(const char* arg, ObjdumpOpts* o) +{ + const char* p; + for (p = arg + 1; *p; ++p) { + switch (*p) { + case 'h': o->h = 1; break; + case 't': o->t = 1; break; + case 'd': o->d = 1; break; + case 'D': o->D = 1; break; + case 'r': o->r = 1; break; + case 's': o->s = 1; break; + default: + driver_errf(OBJDUMP_TOOL, "unknown flag: -%c", *p); + return -1; + } + } + return 0; +} + int driver_objdump(int argc, char** argv) { - DriverEnv env; - int i; - int rc = 0; + DriverEnv env; + ObjdumpOpts opts = {0}; + int i; + int rc = 0; + int saw_input = 0; + int saw_op = 0; + CfreeCompiler* dc = NULL; + CfreeEnv cenv; if (argc < 2) { objdump_usage(); @@ -154,27 +381,74 @@ int driver_objdump(int argc, char** argv) } driver_env_init(&env); + cenv = driver_env_to_cfree(&env); + + /* First pass: parse flags. */ + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + if (a[0] != '-' || a[1] == '\0') continue; + if (driver_streq(a, "-j")) { + if (i + 1 >= argc) { + driver_errf(OBJDUMP_TOOL, "%s", "-j requires a section name"); + rc = 2; goto done; + } + if (opts.nj >= MAX_J_FILTERS) { + driver_errf(OBJDUMP_TOOL, "%s", "too many -j filters"); + rc = 2; goto done; + } + opts.j[opts.nj++] = argv[++i]; + continue; + } + if (parse_short_flags(a, &opts) != 0) { + objdump_usage(); + rc = 2; goto done; + } + } + saw_op = opts.h || opts.t || opts.d || opts.D || opts.r || opts.s; + if (!saw_op) { /* Default = -h -t (matches the prior behavior). */ + opts.h = 1; + opts.t = 1; + } + + /* Compiler used by the disassembler iterator. Created once for the + * session at the host target — disasm consults the per-file builder + * for annotation but does not require a target match. */ + if (opts.d || opts.D) { + dc = cfree_compiler_new(driver_host_target(), &cenv); + if (!dc) { + driver_errf(OBJDUMP_TOOL, "%s", "failed to init disassembler"); + rc = 1; goto done; + } + } + + /* Second pass: process inputs. */ for (i = 1; i < argc && rc == 0; ++i) { - CfreeEnv cenv = driver_env_to_cfree(&env); - CfreeFileData fd = {0}; + const char* a = argv[i]; + CfreeFileData fd = {0}; CfreeBytesInput input; CfreeBinFmt bin; - if (!cenv.file_io->read_all(cenv.file_io->user, argv[i], &fd)) { - driver_errf(OBJDUMP_TOOL, "failed to read: %s", argv[i]); + if (a[0] == '-' && a[1] != '\0') { + if (driver_streq(a, "-j")) ++i; + continue; + } + saw_input = 1; + + if (!cenv.file_io->read_all(cenv.file_io->user, a, &fd)) { + driver_errf(OBJDUMP_TOOL, "failed to read: %s", a); rc = 1; break; } - input.name = argv[i]; + input.name = a; input.data = fd.data; input.len = fd.size; bin = cfree_detect_fmt(input.data, input.len); switch (bin) { case CFREE_BIN_AR: - rc = dump_archive(argv[i], &input, &cenv, driver_host_target()); + rc = dump_archive(a, &input, &cenv, driver_host_target(), dc, &opts); break; case CFREE_BIN_ELF: case CFREE_BIN_COFF: @@ -182,16 +456,16 @@ int driver_objdump(int argc, char** argv) case CFREE_BIN_WASM: { CfreeObjFile* f = cfree_obj_open(&cenv, driver_host_target(), &input); if (!f) { - driver_errf(OBJDUMP_TOOL, "failed to parse: %s", argv[i]); + driver_errf(OBJDUMP_TOOL, "failed to parse: %s", a); rc = 1; } else { - dump_obj(argv[i], f); + dump_obj(dc, a, f, &opts); cfree_obj_close(f); } break; } default: - driver_errf(OBJDUMP_TOOL, "unsupported file format: %s", argv[i]); + driver_errf(OBJDUMP_TOOL, "unsupported file format: %s", a); rc = 1; break; } @@ -199,6 +473,13 @@ int driver_objdump(int argc, char** argv) cenv.file_io->release(cenv.file_io->user, &fd); } + if (rc == 0 && !saw_input) { + objdump_usage(); + rc = 2; + } + +done: + if (dc) cfree_compiler_free(dc); driver_env_fini(&env); return rc; } diff --git a/driver/run.c b/driver/run.c @@ -1,37 +1,48 @@ +#include "cflags.h" #include "driver.h" #include <stdint.h> -/* `cfree run` — JIT-compile one or more C sources and invoke the entry - * symbol (default `main`) in-process. Any args after `--` are passed to - * the JITed program as argv. The driver returns whatever the entry - * returns, or 1 on a compile/link/lookup error. */ +/* `cfree run` — JIT-compile one or more inputs and invoke the entry symbol + * (default `main`) in-process. Args after `--` are passed to the JITed + * program as argv. Mirrors the cc front-end for input shape (.c / - sources, + * .o objects, .a archives) and target/diagnostic flag surface; libcfree's + * JIT path forces PIC regardless of `-fPIC`/`-fPIE`/`-mcmodel`. + * + * Host-symbol fallback (so JITed code can call libc) goes through + * driver_dlsym_resolver in driver/env.c. The driver returns whatever the + * entry returns, or 1 on a compile/link/lookup error. The entry is invoked + * as `int(*)(int, char**)`. */ #define RUN_TOOL "run" typedef struct RunOptions { - DriverEnv* env; - size_t argv_bound; - - int opt_level; - int debug_info; - const char* entry; /* -e, default "main" */ - const char** include_dirs; /* -I */ - uint32_t ninclude_dirs; - const char** system_include_dirs; /* -isystem */ - uint32_t nsystem_include_dirs; - CfreeDefine* defines; /* -D */ - uint32_t ndefines; - const char** undefines; /* -U */ - uint32_t nundefines; - const char** sources; /* positional .c files */ - uint32_t nsources; - - char** prog_argv; /* args after `--` */ - uint32_t prog_argc; - - char** owned_define_names; - size_t* owned_define_name_sizes; + DriverEnv* env; + size_t argv_bound; + + int opt_level; + int debug_info; + int warnings_are_errors; /* -Werror */ + uint32_t max_errors; /* -fmax-errors=N */ + const char* entry; /* -e, default "main" */ + CfreeTarget target; /* -target / host */ + + DriverCflags cf; + + /* Inputs split by suffix, like cc/ld. */ + const char** sources; /* .c paths */ + uint32_t nsources; + CfreeBytesInput* source_memory; /* "-" stdin slurp */ + uint32_t nsource_memory; + uint8_t* stdin_buf; /* owning storage for the one stdin */ + size_t stdin_size; + const char** object_files; /* .o/.obj paths */ + uint32_t nobject_files; + const char** archives; /* .a paths */ + uint32_t narchives; + + char** prog_argv; /* args after `--` */ + uint32_t prog_argc; } RunOptions; static void run_usage(void) @@ -40,58 +51,110 @@ static void run_usage(void) "usage: cfree run [-O0|-O1|-O2] [-g] [-e entry]\n" " [-I dir]... [-isystem dir]...\n" " [-D name[=body]]... [-U name]...\n" - " input.c... [-- arg...]"); + " [-target TRIPLE] [-fPIC|-fPIE|-fpic|-fpie]\n" + " [-mcmodel=small|medium|large]\n" + " [-Werror] [-fmax-errors=N]\n" + " input.c... [-] [input.o]... [input.a]... [-- arg...]"); } static int run_alloc_arrays(RunOptions* o, int argc) { size_t bound = (size_t)argc; - o->argv_bound = bound; - o->include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->include_dirs)); - o->system_include_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->system_include_dirs)); - o->defines = driver_alloc_zeroed(o->env, bound * sizeof(*o->defines)); - o->owned_define_names = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_names)); - o->owned_define_name_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_define_name_sizes)); - o->undefines = driver_alloc_zeroed(o->env, bound * sizeof(*o->undefines)); - o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources)); - o->prog_argv = driver_alloc_zeroed(o->env, bound * sizeof(*o->prog_argv)); - if (!o->include_dirs || !o->system_include_dirs || !o->defines || - !o->owned_define_names || !o->owned_define_name_sizes || - !o->undefines || !o->sources || !o->prog_argv) { + o->argv_bound = bound; + o->sources = driver_alloc_zeroed(o->env, bound * sizeof(*o->sources)); + o->source_memory = driver_alloc_zeroed(o->env, bound * sizeof(*o->source_memory)); + o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files)); + o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives)); + o->prog_argv = driver_alloc_zeroed(o->env, bound * sizeof(*o->prog_argv)); + if (!o->sources || !o->source_memory || !o->object_files || + !o->archives || !o->prog_argv) { + driver_errf(RUN_TOOL, "out of memory"); + return 1; + } + if (driver_cflags_init(&o->cf, o->env, argc) != 0) { driver_errf(RUN_TOOL, "out of memory"); return 1; } return 0; } -static int run_record_define(RunOptions* o, const char* arg) +/* Decimal uint64 parse for -fmax-errors=N. Mirrors cc's helper. */ +static int run_parse_u64(const char* s, uint64_t* out) { - const char* eq = driver_strchr(arg, '='); - CfreeDefine* d = &o->defines[o->ndefines]; - if (eq) { - size_t n = (size_t)(eq - arg); - size_t bytes = n + 1; - char* name = driver_alloc(o->env, bytes); - if (!name) { driver_errf(RUN_TOOL, "out of memory"); return 1; } - driver_memcpy(name, arg, n); - name[n] = '\0'; - o->owned_define_names[o->ndefines] = name; - o->owned_define_name_sizes[o->ndefines] = bytes; - d->name = name; - d->body = eq + 1; - } else { - d->name = arg; - d->body = NULL; + uint64_t v = 0; + int any = 0; + if (!s) return 1; + while (*s) { + unsigned d; + if (*s < '0' || *s > '9') return 1; + d = (unsigned)(*s - '0'); + if (v > (UINT64_MAX - d) / 10u) return 1; + v = v * 10u + d; + any = 1; + s++; } - o->ndefines++; + if (!any) return 1; + *out = v; return 0; } +static int run_record_mcmodel(RunOptions* o, const char* val) +{ + if (driver_streq(val, "small")) { o->target.code_model = CFREE_CM_SMALL; return 0; } + if (driver_streq(val, "medium")) { o->target.code_model = CFREE_CM_MEDIUM; return 0; } + if (driver_streq(val, "large")) { o->target.code_model = CFREE_CM_LARGE; return 0; } + driver_errf(RUN_TOOL, "unknown -mcmodel value: %s", val); + return 1; +} + +/* Slurp stdin into a single source_memory entry. Guarded against duplicates + * so a user typo (`cfree run - -`) is surfaced rather than silently leaking + * the first slurp. */ +static int run_record_stdin(RunOptions* o) +{ + CfreeBytesInput* in; + if (o->stdin_buf) { + driver_errf(RUN_TOOL, "'-' (stdin) may appear at most once"); + return 1; + } + if (!driver_read_stdin(o->env, &o->stdin_buf, &o->stdin_size)) { + driver_errf(RUN_TOOL, "failed to read stdin"); + return 1; + } + in = &o->source_memory[o->nsource_memory++]; + in->name = "<stdin>"; + in->data = o->stdin_buf; + in->len = o->stdin_size; + return 0; +} + +static int run_classify_positional(RunOptions* o, const char* a) +{ + if (driver_streq(a, "-")) return run_record_stdin(o); + if (driver_has_suffix(a, ".c") || + driver_has_suffix(a, ".cc") || + driver_has_suffix(a, ".cpp")) { + o->sources[o->nsources++] = a; + return 0; + } + if (driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj")) { + o->object_files[o->nobject_files++] = a; + return 0; + } + if (driver_has_suffix(a, ".a")) { + o->archives[o->narchives++] = a; + return 0; + } + driver_errf(RUN_TOOL, "input does not have a recognized suffix: %s", a); + return 1; +} + static int run_parse(int argc, char** argv, RunOptions* o) { int i; int after_dash_dash = 0; if (run_alloc_arrays(o, argc) != 0) return 1; + o->target = driver_host_target(); for (i = 1; i < argc; ++i) { const char* a = argv[i]; @@ -102,53 +165,66 @@ static int run_parse(int argc, char** argv, RunOptions* o) } if (driver_streq(a, "--")) { after_dash_dash = 1; continue; } + { + int r = driver_cflags_try_consume(&o->cf, o->env, RUN_TOOL, argc, argv, &i); + if (r < 0) return 1; + if (r > 0) continue; + } + if (driver_streq(a, "-g")) { o->debug_info = 1; continue; } if (driver_streq(a, "-O0")) { o->opt_level = 0; continue; } if (driver_streq(a, "-O1")) { o->opt_level = 1; continue; } if (driver_streq(a, "-O2")) { o->opt_level = 2; continue; } - if (driver_streq(a, "-e")) { - if (++i >= argc) { driver_errf(RUN_TOOL, "-e requires an argument"); return 1; } - o->entry = argv[i]; + if (driver_streq(a, "-Werror")) { o->warnings_are_errors = 1; continue; } + if (driver_strneq(a, "-fmax-errors=", 13)) { + uint64_t v; + if (run_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { + driver_errf(RUN_TOOL, "-fmax-errors= requires a non-negative integer"); + return 1; + } + o->max_errors = (uint32_t)v; continue; } - if (driver_strneq(a, "-I", 2)) { - const char* dir = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!dir) { driver_errf(RUN_TOOL, "-I requires an argument"); return 1; } - o->include_dirs[o->ninclude_dirs++] = dir; + if (driver_streq(a, "-fPIC")) { o->target.pic = CFREE_PIC_PIC; continue; } + if (driver_streq(a, "-fpic")) { o->target.pic = CFREE_PIC_PIC; continue; } + if (driver_streq(a, "-fPIE")) { o->target.pic = CFREE_PIC_PIE; continue; } + if (driver_streq(a, "-fpie")) { o->target.pic = CFREE_PIC_PIE; continue; } + + if (driver_strneq(a, "-mcmodel=", 9)) { + if (run_record_mcmodel(o, a + 9) != 0) return 1; continue; } - if (driver_streq(a, "-isystem")) { - if (++i >= argc) { driver_errf(RUN_TOOL, "-isystem requires an argument"); return 1; } - o->system_include_dirs[o->nsystem_include_dirs++] = argv[i]; + if (driver_streq(a, "-target")) { + if (++i >= argc) { driver_errf(RUN_TOOL, "-target requires an argument"); return 1; } + if (driver_target_from_triple(argv[i], &o->target) != 0) { + driver_errf(RUN_TOOL, "unrecognized target triple: %s", argv[i]); + return 1; + } continue; } - if (driver_strneq(a, "-D", 2)) { - const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!arg) { driver_errf(RUN_TOOL, "-D requires an argument"); return 1; } - if (run_record_define(o, arg) != 0) return 1; + if (driver_streq(a, "-e")) { + if (++i >= argc) { driver_errf(RUN_TOOL, "-e requires an argument"); return 1; } + o->entry = argv[i]; continue; } - if (driver_strneq(a, "-U", 2)) { - const char* arg = a[2] ? a + 2 : (++i < argc ? argv[i] : NULL); - if (!arg) { driver_errf(RUN_TOOL, "-U requires an argument"); return 1; } - o->undefines[o->nundefines++] = arg; + if (driver_streq(a, "-")) { + if (run_classify_positional(o, a) != 0) return 1; continue; } - if (a[0] == '-' && a[1] != '\0') { driver_errf(RUN_TOOL, "unknown flag: %s", a); return 1; } - o->sources[o->nsources++] = a; + if (run_classify_positional(o, a) != 0) return 1; } - if (o->nsources == 0) { + if (o->nsources + o->nsource_memory + o->nobject_files + o->narchives == 0) { driver_errf(RUN_TOOL, "no input files"); run_usage(); return 1; @@ -159,47 +235,45 @@ static int run_parse(int argc, char** argv, RunOptions* o) static void run_options_release(RunOptions* o) { - uint32_t i; - size_t bound = o->argv_bound; - for (i = 0; i < o->ndefines; ++i) { - if (o->owned_define_names[i]) { - driver_free(o->env, o->owned_define_names[i], - o->owned_define_name_sizes[i]); - } - } - driver_free(o->env, o->include_dirs, bound * sizeof(*o->include_dirs)); - driver_free(o->env, o->system_include_dirs, bound * sizeof(*o->system_include_dirs)); - driver_free(o->env, o->defines, bound * sizeof(*o->defines)); - driver_free(o->env, o->owned_define_names, bound * sizeof(*o->owned_define_names)); - driver_free(o->env, o->owned_define_name_sizes, bound * sizeof(*o->owned_define_name_sizes)); - driver_free(o->env, o->undefines, bound * sizeof(*o->undefines)); - driver_free(o->env, o->sources, bound * sizeof(*o->sources)); - driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); + size_t bound = o->argv_bound; + if (o->stdin_buf) driver_free(o->env, o->stdin_buf, o->stdin_size); + driver_cflags_fini(&o->cf, o->env); + driver_free(o->env, o->sources, bound * sizeof(*o->sources)); + driver_free(o->env, o->source_memory, bound * sizeof(*o->source_memory)); + driver_free(o->env, o->object_files, bound * sizeof(*o->object_files)); + driver_free(o->env, o->archives, bound * sizeof(*o->archives)); + driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); } static void run_to_cfree(const RunOptions* o, CfreeOptions* out, CfreeJit** out_jit) { CfreeOptions z = {0}; *out = z; - out->target = driver_host_target(); + out->target = o->target; out->env = driver_env_to_cfree(o->env); out->output_kind = CFREE_OUTPUT_JIT; out->opt_level = o->opt_level; out->debug_info = o->debug_info; - out->source_files = o->sources; - out->nsource_files = o->nsources; + out->source_files = o->sources; + out->nsource_files = o->nsources; + out->source_memory = o->source_memory; + out->nsource_memory = o->nsource_memory; + + driver_cflags_fill_pp(&o->cf, &out->pp); + + out->object_files = o->object_files; + out->nobject_files = o->nobject_files; + out->archives = o->archives; + out->narchives = o->narchives; + + out->entry = o->entry; + out->extern_resolver = driver_dlsym_resolver; + out->extern_resolver_user = NULL; - out->pp.include_dirs = o->include_dirs; - out->pp.ninclude_dirs = o->ninclude_dirs; - out->pp.system_include_dirs = o->system_include_dirs; - out->pp.nsystem_include_dirs = o->nsystem_include_dirs; - out->pp.defines = o->defines; - out->pp.ndefines = o->ndefines; - out->pp.undefines = o->undefines; - out->pp.nundefines = o->nundefines; + out->warnings_are_errors = o->warnings_are_errors; + out->max_errors = o->max_errors; - out->entry = o->entry; out->out_jit = out_jit; } diff --git a/driver/target.c b/driver/target.c @@ -0,0 +1,102 @@ +#include "driver.h" + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +/* Pure target-triple parsing. No host I/O — just string walking — so this + * lives outside driver/env.c (which is the syscall/host-state abstraction + * layer). */ + +static int triple_tok_eq(const char* s, size_t n, const char* lit) +{ + size_t l = strlen(lit); + return n == l && memcmp(s, lit, n) == 0; +} + +int driver_target_from_triple(const char* triple, CfreeTarget* out) +{ + const char* parts[4]; + size_t plen[4]; + int np = 0; + const char* p; + CfreeTarget t; + int os_set; + int i; + + if (!triple || !out) return 1; + memset(&t, 0, sizeof(t)); + + p = triple; + while (np < 4) { + const char* dash = strchr(p, '-'); + parts[np] = p; + plen[np] = dash ? (size_t)(dash - p) : strlen(p); + if (plen[np] == 0) return 1; + np++; + if (!dash) break; + p = dash + 1; + } + + if (triple_tok_eq(parts[0], plen[0], "x86_64") || + triple_tok_eq(parts[0], plen[0], "amd64")) { + t.arch = CFREE_ARCH_X86_64; t.ptr_size = 8; + } else if (triple_tok_eq(parts[0], plen[0], "i386") || + triple_tok_eq(parts[0], plen[0], "i486") || + triple_tok_eq(parts[0], plen[0], "i586") || + triple_tok_eq(parts[0], plen[0], "i686")) { + t.arch = CFREE_ARCH_X86_32; t.ptr_size = 4; + } else if (triple_tok_eq(parts[0], plen[0], "aarch64") || + triple_tok_eq(parts[0], plen[0], "arm64")) { + t.arch = CFREE_ARCH_ARM_64; t.ptr_size = 8; + } else if (triple_tok_eq(parts[0], plen[0], "arm") || + triple_tok_eq(parts[0], plen[0], "armv7")) { + t.arch = CFREE_ARCH_ARM_32; t.ptr_size = 4; + } else if (triple_tok_eq(parts[0], plen[0], "riscv64")) { + t.arch = CFREE_ARCH_RV64; t.ptr_size = 8; + } else if (triple_tok_eq(parts[0], plen[0], "riscv32")) { + t.arch = CFREE_ARCH_RV32; t.ptr_size = 4; + } else if (triple_tok_eq(parts[0], plen[0], "wasm32")) { + t.arch = CFREE_ARCH_WASM; t.ptr_size = 4; + } else if (triple_tok_eq(parts[0], plen[0], "wasm64")) { + t.arch = CFREE_ARCH_WASM; t.ptr_size = 8; + } else { + return 1; + } + + os_set = 0; + for (i = 1; i < np; ++i) { + if (triple_tok_eq(parts[i], plen[i], "linux")) { + t.os = CFREE_OS_LINUX; t.obj = CFREE_OBJ_ELF; os_set = 1; break; + } + if (triple_tok_eq(parts[i], plen[i], "darwin") || + triple_tok_eq(parts[i], plen[i], "macos")) { + t.os = CFREE_OS_MACOS; t.obj = CFREE_OBJ_MACHO; os_set = 1; break; + } + if (triple_tok_eq(parts[i], plen[i], "windows") || + triple_tok_eq(parts[i], plen[i], "win32")) { + t.os = CFREE_OS_WINDOWS; t.obj = CFREE_OBJ_COFF; os_set = 1; break; + } + if (triple_tok_eq(parts[i], plen[i], "wasi")) { + t.os = CFREE_OS_WASI; t.obj = CFREE_OBJ_WASM; os_set = 1; break; + } + if (triple_tok_eq(parts[i], plen[i], "none") || + triple_tok_eq(parts[i], plen[i], "freestanding")) { + t.os = CFREE_OS_FREESTANDING; + t.obj = (t.arch == CFREE_ARCH_WASM) ? CFREE_OBJ_WASM : CFREE_OBJ_ELF; + os_set = 1; break; + } + } + if (!os_set) { + t.os = CFREE_OS_FREESTANDING; + t.obj = (t.arch == CFREE_ARCH_WASM) ? CFREE_OBJ_WASM : CFREE_OBJ_ELF; + } + + t.ptr_align = t.ptr_size; + t.big_endian = 0; + t.pic = CFREE_PIC_NONE; + t.code_model = CFREE_CM_DEFAULT; + + *out = t; + return 0; +}