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:
| M | driver/ar.c | | | 358 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
| M | driver/as.c | | | 131 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | driver/cc.c | | | 1108 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
| A | driver/cflags.c | | | 142 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | driver/cflags.h | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | driver/dbg.c | | | 1155 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | driver/driver.h | | | 64 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | driver/env.c | | | 139 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | driver/ld.c | | | 436 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| A | driver/lib_resolve.c | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | driver/lib_resolve.h | | | 26 | ++++++++++++++++++++++++++ |
| M | driver/objdump.c | | | 343 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
| M | driver/run.c | | | 286 | ++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------- |
| A | driver/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;
+}