commit edf85f383ffd2d87fdfa84d839ccd4fe23505403
parent 0035bf0c8efe3a8b38fb19e4ee9118b750c29ed5
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 9 May 2026 04:15:59 -0700
driver: update ar, cc, ld subcommands and Makefile
Diffstat:
| M | Makefile | | | 2 | ++ |
| M | driver/ar.c | | | 149 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | driver/cc.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| M | driver/ld.c | | | 13 | +++++++++++++ |
4 files changed, 212 insertions(+), 11 deletions(-)
diff --git a/Makefile b/Makefile
@@ -51,3 +51,5 @@ build/driver/%.o: driver/%.c
clean:
rm -rf build
+
+include test/test.mk
diff --git a/driver/ar.c b/driver/ar.c
@@ -15,12 +15,19 @@
* cfree ar x archive.a [members...] extract members to cwd
* cfree ar p archive.a [members...] print members to stdout
*
+ * Modifiers (combine with above):
+ * s emit a System V `/` symbol-index member at the head of the
+ * archive. Only valid combined with r/c (i.e. rs, cs, rcs).
+ * Each member is opened with cfree_obj_open and its globally-
+ * defined symbols (CFREE_SB_GLOBAL with section != NONE) are
+ * indexed; non-object members contribute no symbols.
+ *
* 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
+ * Not yet implemented: d (delete), q (quick append), v (verbose), and
+ * standalone `s` without r/c. Encountering any of those yields a usage
* error with exit code 2. */
#define AR_TOOL "ar"
@@ -186,7 +193,7 @@ static int ar_do_print(DriverEnv* env, const char* archive_path,
*/
static int ar_do_write(DriverEnv* env, const char* archive_path,
int nmembers, char** member_paths,
- int has_r, int has_c)
+ int has_r, int has_c, int has_s)
{
uint32_t nnew = nmembers > 0 ? (uint32_t)nmembers : 0u;
CfreeBytesInput* members = NULL;
@@ -200,6 +207,13 @@ static int ar_do_write(DriverEnv* env, const char* archive_path,
int have_old = 0;
int rc = 0;
uint32_t i;
+ /* Per-member symbol storage (only used when has_s). msyms is the array
+ * passed to cfree_ar_write; sym_allocs holds the single per-member
+ * blob backing each msyms[i].names + name bytes (one allocation per
+ * member, sized in sym_alloc_szs for the matching driver_free). */
+ CfreeArMemberSymbols* msyms = NULL;
+ void** sym_allocs = NULL;
+ size_t* sym_alloc_szs = NULL;
opts.epoch = ar_epoch_from_env();
opts.long_names = 1;
@@ -307,6 +321,116 @@ static int ar_do_write(DriverEnv* env, const char* archive_path,
}
}
+ if (has_s && nm > 0) {
+ msyms = (CfreeArMemberSymbols*)driver_alloc_zeroed(
+ env, (size_t)nm * sizeof(*msyms));
+ sym_allocs = (void**)driver_alloc_zeroed(
+ env, (size_t)nm * sizeof(*sym_allocs));
+ sym_alloc_szs = (size_t*)driver_alloc_zeroed(
+ env, (size_t)nm * sizeof(*sym_alloc_szs));
+ if (!msyms || !sym_allocs || !sym_alloc_szs) {
+ driver_errf(AR_TOOL, "out of memory");
+ rc = 1;
+ goto done;
+ }
+ for (i = 0; i < nm; ++i) {
+ CfreeBytesInput in;
+ CfreeObjFile* of;
+ CfreeObjSymIter* it;
+ CfreeObjSymInfo si;
+ uint32_t count = 0;
+ size_t name_bytes = 0;
+ size_t alloc_sz;
+ char* blob;
+ const char** name_arr;
+ char* name_storage;
+ size_t cursor = 0;
+
+ in.name = members[i].name;
+ in.data = members[i].data;
+ in.len = members[i].len;
+ in.lang = 0;
+
+ of = cfree_obj_open(&cenv, &in);
+ if (!of) continue; /* not an object file → no symbols */
+
+ /* Pass A: count globally-defined symbols and total name bytes. */
+ it = cfree_obj_symiter_new(of);
+ if (!it) {
+ cfree_obj_close(of);
+ driver_errf(AR_TOOL, "out of memory");
+ rc = 1;
+ goto done;
+ }
+ while (cfree_obj_symiter_next(it, &si)) {
+ if (si.bind != CFREE_SB_GLOBAL) continue;
+ if (si.section == CFREE_SECTION_NONE) continue;
+ if (!si.name || !si.name[0]) continue;
+ count += 1;
+ {
+ const char* p = si.name;
+ while (*p++) ++name_bytes;
+ name_bytes += 1; /* NUL */
+ }
+ }
+ cfree_obj_symiter_free(it);
+
+ if (count == 0) {
+ cfree_obj_close(of);
+ continue;
+ }
+
+ /* Single allocation: [name_arr][name_storage]. Names are copied
+ * out of the obj file before close so they outlive cfree_obj_close. */
+ alloc_sz = (size_t)count * sizeof(const char*) + name_bytes;
+ blob = (char*)driver_alloc_zeroed(env, alloc_sz);
+ if (!blob) {
+ cfree_obj_close(of);
+ driver_errf(AR_TOOL, "out of memory");
+ rc = 1;
+ goto done;
+ }
+ name_arr = (const char**)blob;
+ name_storage = blob + (size_t)count * sizeof(const char*);
+
+ /* Pass B: copy names. */
+ it = cfree_obj_symiter_new(of);
+ if (!it) {
+ driver_free(env, blob, alloc_sz);
+ cfree_obj_close(of);
+ driver_errf(AR_TOOL, "out of memory");
+ rc = 1;
+ goto done;
+ }
+ {
+ uint32_t k = 0;
+ while (cfree_obj_symiter_next(it, &si) && k < count) {
+ const char* p;
+ char* dst;
+ if (si.bind != CFREE_SB_GLOBAL) continue;
+ if (si.section == CFREE_SECTION_NONE) continue;
+ if (!si.name || !si.name[0]) continue;
+ dst = name_storage + cursor;
+ name_arr[k] = dst;
+ for (p = si.name; *p; ++p) *dst++ = *p;
+ *dst++ = '\0';
+ cursor = (size_t)(dst - name_storage);
+ k++;
+ }
+ count = k;
+ }
+ cfree_obj_symiter_free(it);
+ cfree_obj_close(of);
+
+ sym_allocs[i] = blob;
+ sym_alloc_szs[i] = alloc_sz;
+ msyms[i].names = name_arr;
+ msyms[i].count = count;
+ }
+ opts.symbol_index = 1;
+ opts.member_symbols = msyms;
+ }
+
out = cenv.file_io->open_writer(cenv.file_io->user, archive_path);
if (!out) {
driver_errf(AR_TOOL, "failed to open: %s", archive_path);
@@ -318,6 +442,14 @@ static int ar_do_write(DriverEnv* env, const char* archive_path,
}
done:
+ if (sym_allocs) {
+ for (i = 0; i < nm; ++i) {
+ if (sym_allocs[i]) driver_free(env, sym_allocs[i], sym_alloc_szs[i]);
+ }
+ driver_free(env, sym_allocs, (size_t)nm * sizeof(*sym_allocs));
+ driver_free(env, sym_alloc_szs, (size_t)nm * sizeof(*sym_alloc_szs));
+ }
+ if (msyms) driver_free(env, msyms, (size_t)nm * sizeof(*msyms));
if (new_fds) {
for (i = 0; i < nnew; ++i) {
if (new_fds[i].data)
@@ -341,6 +473,7 @@ int driver_ar(int argc, char** argv)
int do_print = 0;
int has_r = 0;
int has_c = 0;
+ int has_s = 0;
int i;
int rc;
@@ -356,10 +489,11 @@ int driver_ar(int argc, char** argv)
switch (mode[i]) {
case 'r': do_write = 1; has_r = 1; break;
case 'c': do_write = 1; has_c = 1; break;
+ case 's': has_s = 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':
+ case 'd': case 'q': case 'v':
driver_errf(AR_TOOL, "operation not implemented: %c", mode[i]);
return 2;
default:
@@ -367,6 +501,10 @@ int driver_ar(int argc, char** argv)
return 2;
}
}
+ if (has_s && !do_write) {
+ driver_errf(AR_TOOL, "s requires r or c");
+ return 2;
+ }
{
int kinds = !!do_write + !!do_list + !!do_extract + !!do_print;
@@ -395,7 +533,8 @@ int driver_ar(int argc, char** argv)
} 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, has_r, has_c);
+ rc = ar_do_write(&env, archive_path, argc - 3, argv + 3,
+ has_r, has_c, has_s);
}
driver_env_fini(&env);
diff --git a/driver/cc.c b/driver/cc.c
@@ -8,7 +8,7 @@
* 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
+ * -c -E -o -O0/1/2 -g --dump-tokens
* -I -isystem -D -U
* -M -MM -MD -MMD -MF -MT -MQ -MP
* -target TRIPLE
@@ -49,6 +49,7 @@ typedef struct CcOptions {
int compile_only; /* -c */
int preprocess_only; /* -E */
+ int dump_tokens; /* --dump-tokens */
int opt_level; /* -O0/-O1/-O2 */
int debug_info; /* -g */
int warnings_are_errors; /* -Werror */
@@ -122,7 +123,7 @@ typedef struct CcOptions {
static void cc_usage(void)
{
driver_errf(CC_TOOL, "%s",
- "usage: cfree cc [-c|-E] [-o out] [-O0|-O1|-O2] [-g]\n"
+ "usage: cfree cc [-c|-E|--dump-tokens] [-o out] [-O0|-O1|-O2] [-g]\n"
" [-I dir]... [-isystem dir]...\n"
" [-D name[=body]]... [-U name]...\n"
" [-M|-MM|-MD|-MMD] [-MF file] [-MT target]... [-MP]\n"
@@ -463,6 +464,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o)
if (driver_streq(a, "-c")) { o->compile_only = 1; continue; }
if (driver_streq(a, "-E")) { o->preprocess_only = 1; continue; }
+ if (driver_streq(a, "--dump-tokens")) { o->dump_tokens = 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; }
@@ -615,8 +617,12 @@ static int cc_parse(int argc, char** argv, CcOptions* o)
driver_errf(CC_TOOL, "-c and -E are mutually exclusive");
return 1;
}
- if (o->shared && (o->compile_only || o->preprocess_only)) {
- driver_errf(CC_TOOL, "-shared is incompatible with -c/-E");
+ if (o->dump_tokens && (o->compile_only || o->preprocess_only)) {
+ driver_errf(CC_TOOL, "--dump-tokens is incompatible with -c/-E");
+ return 1;
+ }
+ if (o->shared && (o->compile_only || o->preprocess_only || o->dump_tokens)) {
+ driver_errf(CC_TOOL, "-shared is incompatible with -c/-E/--dump-tokens");
return 1;
}
if (!o->shared && (o->soname || o->nrpaths)) {
@@ -636,11 +642,17 @@ static int cc_parse(int argc, char** argv, CcOptions* o)
return 1;
}
}
+ if (o->dump_tokens) {
+ if (total_sources != 1 || total_link != 0) {
+ driver_errf(CC_TOOL, "--dump-tokens 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");
+ if (o->dep_mode != CC_DEP_NONE && (o->preprocess_only || o->dump_tokens)) {
+ driver_errf(CC_TOOL, "-M* is incompatible with -E/--dump-tokens");
return 1;
}
if (dep_only && total_sources != 1) {
@@ -720,6 +732,39 @@ out:
return rc;
}
+static int cc_dump_tokens(DriverEnv* env, const CcOptions* o)
+{
+ 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;
+ }
+
+ compiler = cfree_compiler_new(o->target, &cenv);
+ if (!compiler) {
+ driver_errf(CC_TOOL, "failed to initialize compiler");
+ goto out;
+ }
+
+ rc = cfree_dump_tokens(compiler, &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;
+}
+
/* ---- header-dependency output (-M family) ---- */
typedef struct CcDepList {
@@ -1267,6 +1312,8 @@ int driver_cc(int argc, char** argv)
if (co.preprocess_only) {
rc = cc_preprocess(&env, &co, &pp);
+ } else if (co.dump_tokens) {
+ rc = cc_dump_tokens(&env, &co);
} 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.compile_only) {
diff --git a/driver/ld.c b/driver/ld.c
@@ -516,6 +516,19 @@ static int ld_run_link(LdOptions* o)
}
}
+ /* Auto-detect target from the first object input, falling back to
+ * the host target. PIC overrides parsed from flags are preserved. */
+ if (o->nobject_files > 0) {
+ CfreeTarget detected;
+ if (cfree_detect_target(obj_lf[0].data.data,
+ obj_lf[0].data.size,
+ &detected) == 0) {
+ uint8_t pic = o->target.pic;
+ o->target = detected;
+ if (pic != CFREE_PIC_NONE) o->target.pic = pic;
+ }
+ }
+
compiler = cfree_compiler_new(o->target, &cenv);
if (!compiler) {
driver_errf(LD_TOOL, "failed to initialize compiler");