commit e7429a445bea41bca5c1199bb98c3ca6ddced92e
parent 704b5dbd0eebb2d0abc2455c313c494e1a901f34
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 13:11:55 -0700
driver: add check, asm output, and object tools
Diffstat:
15 files changed, 2278 insertions(+), 18 deletions(-)
diff --git a/driver/addr2line.c b/driver/addr2line.c
@@ -0,0 +1,221 @@
+#include "driver.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cfree/core.h>
+#include <cfree/dwarf.h>
+#include <cfree/object.h>
+
+#define A2L_TOOL "addr2line"
+
+typedef struct A2lOpts {
+ const char *exe_path;
+ int functions; /* -f */
+ int pretty; /* -p */
+ int basenames; /* --basenames */
+ int show_addr; /* -a */
+} A2lOpts;
+
+static void a2l_strip_basename(const char **path_ptr) {
+ const char *s = *path_ptr;
+ const char *slash = NULL;
+ const char *p;
+ if (!s) return;
+ for (p = s; *p; ++p)
+ if (*p == '/') slash = p;
+ if (slash) *path_ptr = slash + 1;
+}
+
+static void a2l_translate(CfreeDebugInfo *dwarf, uint64_t addr,
+ const A2lOpts *opts) {
+ CfreeSlice file;
+ uint32_t line = 0, col = 0;
+ CfreeSlice func;
+ uint64_t func_lo = 0, func_hi = 0;
+ CfreeStatus st = cfree_dwarf_addr_to_line(dwarf, addr, &file, &line, &col);
+ int have_func = 0;
+
+ if (opts->functions) {
+ if (cfree_dwarf_func_at(dwarf, addr, &func, &func_lo, &func_hi) ==
+ CFREE_OK)
+ have_func = 1;
+ }
+
+ if (opts->show_addr)
+ driver_printf("0x%llx", (unsigned long long)addr);
+
+ if (opts->pretty) {
+ if (opts->show_addr) driver_printf(": ");
+ if (have_func)
+ driver_printf("%.*s at ", (int)func.len, func.s);
+ else if (opts->functions)
+ driver_printf("?? at ");
+ if (st == CFREE_OK) {
+ const char *f = file.s;
+ if (opts->basenames) a2l_strip_basename(&f);
+ driver_printf("%s:%u", f, line);
+ if (col) driver_printf(":%u", col);
+ } else {
+ driver_printf("??:0");
+ }
+ driver_printf("\n");
+ return;
+ }
+
+ /* default or -f, -a modes */
+ if (opts->show_addr) driver_printf(": ");
+
+ if (opts->functions) {
+ if (have_func)
+ driver_printf("%.*s\n", (int)func.len, func.s);
+ else
+ driver_printf("??\n");
+ }
+
+ if (st == CFREE_OK) {
+ const char *f = file.s;
+ if (opts->basenames) a2l_strip_basename(&f);
+ driver_printf("%s:%u", f, line);
+ if (col) driver_printf(":%u", col);
+ } else {
+ driver_printf("??:0");
+ }
+ driver_printf("\n");
+}
+
+void driver_help_addr2line(void) {
+ driver_printf(
+ "%.*s",
+ CFREE_SLICE_ARG(CFREE_SLICE_LIT(
+ "cfree addr2line — translate addresses to file:line using debug info\n"
+ "\n"
+ "USAGE\n"
+ " cfree addr2line [OPTIONS] -e FILE [ADDR...]\n"
+ "\n"
+ "OPTIONS\n"
+ " -e FILE object file with debug info (required)\n"
+ " -a, --addresses print the address before each line\n"
+ " -f, --functions print function names\n"
+ " -p, --pretty-print compact single-line output\n"
+ " --basenames strip directory from file paths\n"
+ " -h, --help show this help\n"
+ "\n"
+ "If no addresses are given on the command line, addresses are read\n"
+ "from standard input, one per line (hex).\n")));
+}
+
+int driver_addr2line(int argc, char **argv) {
+ DriverEnv env;
+ CfreeContext ctx;
+ A2lOpts opts;
+ CfreeObjFile *of = NULL;
+ CfreeDebugInfo *dwarf = NULL;
+ DriverLoad ld = {0};
+ CfreeSlice input;
+ int i, rc = 1;
+ int stdin_addr_count = 0;
+
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_addr2line();
+ return 0;
+ }
+
+ memset(&opts, 0, sizeof opts);
+ driver_env_init(&env);
+ ctx = driver_env_to_context(&env);
+
+ for (i = 1; i < argc; ++i) {
+ const char *a = argv[i];
+ if (driver_streq(a, "-e")) {
+ if (i + 1 >= argc) {
+ driver_errf(A2L_TOOL, "-e requires a path");
+ rc = 2;
+ goto done;
+ }
+ opts.exe_path = argv[++i];
+ continue;
+ }
+ if (driver_streq(a, "-a") || driver_streq(a, "--addresses")) {
+ opts.show_addr = 1; continue;
+ }
+ if (driver_streq(a, "-f") || driver_streq(a, "--functions")) {
+ opts.functions = 1; continue;
+ }
+ if (driver_streq(a, "-p") || driver_streq(a, "--pretty-print")) {
+ opts.pretty = 1; continue;
+ }
+ if (driver_streq(a, "--basenames")) {
+ opts.basenames = 1; continue;
+ }
+ if (a[0] == '-' && a[1] != '\0') {
+ driver_errf(A2L_TOOL, "unknown option: %s", a);
+ rc = 2;
+ goto done;
+ }
+ }
+
+ if (!opts.exe_path) {
+ driver_errf(A2L_TOOL, "no object file specified (-e FILE)");
+ rc = 2;
+ goto done;
+ }
+
+ if (driver_load_bytes(&env.file_io, A2L_TOOL, opts.exe_path, &ld,
+ &input) != 0) {
+ rc = 1;
+ goto done;
+ }
+
+ {
+ CfreeSlice name_slice = cfree_slice_cstr(opts.exe_path);
+ if (cfree_obj_open(&ctx, name_slice, &input, &of) != CFREE_OK) {
+ driver_errf(A2L_TOOL, "%s: not a recognized object file",
+ opts.exe_path);
+ rc = 1;
+ goto done;
+ }
+ }
+
+ if (cfree_dwarf_open(&ctx, of, &dwarf) != CFREE_OK) {
+ driver_errf(A2L_TOOL, "%s: no debug info available", opts.exe_path);
+ rc = 1;
+ goto done;
+ }
+
+ /* scan for positional addresses */
+ for (i = 1; i < argc; ++i) {
+ const char *a = argv[i];
+ if (a[0] == '-' && a[1] != '\0') {
+ if (driver_streq(a, "-e")) { ++i; continue; }
+ continue;
+ }
+ {
+ uint64_t addr = (uint64_t)strtoull(a, NULL, 16);
+ a2l_translate(dwarf, addr, &opts);
+ stdin_addr_count++;
+ }
+ }
+
+ if (stdin_addr_count == 0) {
+ char line[256];
+ for (;;) {
+ int n = driver_read_line(line, sizeof line);
+ if (n <= 0) break;
+ {
+ uint64_t addr = (uint64_t)strtoull(line, NULL, 16);
+ a2l_translate(dwarf, addr, &opts);
+ }
+ }
+ }
+
+ rc = 0;
+
+done:
+ if (dwarf) cfree_dwarf_free(dwarf);
+ if (of) cfree_obj_free(of);
+ if (ld.loaded) driver_release_bytes(&env.file_io, &ld);
+ driver_env_fini(&env);
+ return rc;
+}
diff --git a/driver/cc.c b/driver/cc.c
@@ -1,5 +1,6 @@
#include "c/c.h"
+#include <cfree/asm_emit.h>
#include <cfree/compile.h>
#include <cfree/core.h>
#include <cfree/link.h>
@@ -18,6 +19,7 @@
* emits an executable. The flag surface is a GCC subset:
*
* -c -E -o -O0/1/2 -g
+ * -fsyntax-only
* -I -isystem -D -U
* -M -MM -MD -MMD -MF -MT -MQ -MP
* -target TRIPLE
@@ -112,7 +114,9 @@ typedef struct CcOptions {
int compile_only; /* -c */
int preprocess_only; /* -E */
+ int syntax_only; /* -fsyntax-only / check */
int emit_c_source; /* --emit=c */
+ int emit_asm_source; /* -S */
int opt_level; /* -O0/-O1/-O2 */
int debug_info; /* -g */
int warnings_are_errors; /* -Werror */
@@ -230,6 +234,7 @@ void driver_help_cc(void) {
" cfree cc -c [options] input.c compile one source to "
".o\n"
" cfree cc -E [options] input.c preprocess to -o\n"
+ " cfree cc -fsyntax-only [options] inputs... check only\n"
" cfree cc -shared [options] inputs... link a shared library\n"
" cfree cc -M|-MM [options] input.c print header deps; no "
"compile\n"
@@ -239,6 +244,19 @@ void driver_help_cc(void) {
"(see source for the full GCC-subset flag reference)\n")));
}
+void driver_help_check(void) {
+ driver_printf(
+ "%.*s",
+ CFREE_SLICE_ARG(CFREE_SLICE_LIT(
+ "cfree check — run frontend checks without emitting code\n"
+ "\n"
+ "USAGE\n"
+ " cfree check [cc-options] inputs...\n"
+ "\n"
+ "Runs the same C frontend path as `cfree cc -fsyntax-only`, including "
+ "preprocessing and diagnostics, but does not write objects or link.\n")));
+}
+
static int cc_alloc_arrays(CcOptions* o, int argc) {
size_t bound = (size_t)argc + 16u;
o->argv_bound = bound;
@@ -1062,6 +1080,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
o->preprocess_only = 1;
continue;
}
+ if (driver_streq(a, "-S")) {
+ o->emit_asm_source = 1;
+ o->compile_only = 1;
+ continue;
+ }
+ if (driver_streq(a, "-fsyntax-only")) {
+ o->syntax_only = 1;
+ continue;
+ }
if (driver_streq(a, "--emit=c")) {
/* C-source output instead of object bytes. Forces -c-style single-input
* compile (no link). See doc/CBACKEND.md. */
@@ -1528,7 +1555,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (cc_apply_env(o) != 0) return 1;
if (cc_append_windows_lib_dirs(o) != 0) return 1;
- if (cc_resolve_pending_libs(o) != 0) return 1;
+ if (!o->syntax_only && cc_resolve_pending_libs(o) != 0) return 1;
{
uint32_t total_sources = o->nsource_files + o->nsource_memory;
@@ -1540,14 +1567,33 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
cc_usage();
return 1;
}
- if (o->compile_only && o->preprocess_only) {
- driver_errf(CC_TOOL, "-c and -E are mutually exclusive");
+ if ((o->compile_only && o->preprocess_only) ||
+ (o->emit_asm_source && o->preprocess_only) ||
+ (o->syntax_only && (o->compile_only || o->preprocess_only ||
+ o->emit_asm_source))) {
+ driver_errf(CC_TOOL, "-c, -S, -E, and -fsyntax-only 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->shared && (o->compile_only || o->emit_asm_source ||
+ o->preprocess_only || o->syntax_only)) {
+ driver_errf(CC_TOOL, "-shared is incompatible with -c/-S/-E/-fsyntax-only");
return 1;
}
+ if (o->syntax_only) {
+ if (total_sources == 0 || total_link != 0) {
+ driver_errf(CC_TOOL,
+ "-fsyntax-only requires source inputs and no link inputs");
+ return 1;
+ }
+ if (o->output_path) {
+ driver_errf(CC_TOOL, "-o is incompatible with -fsyntax-only");
+ return 1;
+ }
+ if (o->dep_mode != CC_DEP_NONE) {
+ driver_errf(CC_TOOL, "-M* is incompatible with -fsyntax-only");
+ return 1;
+ }
+ }
if (!o->shared && o->soname) {
driver_errf(CC_TOOL, "-Wl,-soname requires -shared");
return 1;
@@ -1594,7 +1640,9 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
return 1;
}
if (!o->output_path && !dep_only) {
- if (o->compile_only) {
+ if (o->syntax_only) {
+ /* no output */
+ } else if (o->compile_only) {
if (total_sources == 1) {
o->owned_output_path =
cc_dep_default_target(o->env, o, &o->owned_output_path_size);
@@ -1838,8 +1886,18 @@ static char* cc_default_obj_path_for_name(DriverEnv* env,
* tooling that scrapes default outputs expects the canonical
* platform extension. */
int win = (o && o->target.os == CFREE_OS_WINDOWS);
- size_t ext_len = win ? 4u : 2u; /* ".obj" or ".o" */
- const char* ext = win ? ".obj" : ".o";
+ const char* ext;
+ size_t ext_len;
+ if (o && o->emit_asm_source) {
+ ext = ".s";
+ ext_len = 2u;
+ } else if (win) {
+ ext = ".obj";
+ ext_len = 4u;
+ } else {
+ ext = ".o";
+ ext_len = 2u;
+ }
size_t srclen = driver_strlen(src);
size_t dot = srclen;
size_t slash = 0;
@@ -2028,9 +2086,11 @@ static void cc_fill_c_opts(const CcOptions* o, const CfreePreprocessOptions* pp,
CfreeCCompileOptions* copts) {
CfreeCCompileOptions zero = {0};
*copts = zero;
- copts->code.opt_level = o->opt_level;
+ copts->code.opt_level = o->syntax_only ? 0 : o->opt_level;
copts->code.debug_info = o->debug_info;
+ copts->code.check_only = o->syntax_only ? true : false;
copts->code.emit_c_source = o->emit_c_source ? true : false;
+ copts->code.emit_asm_source = o->emit_asm_source ? true : false;
copts->code.epoch = o->epoch;
copts->code.path_map = o->npath_map ? o->path_map : NULL;
copts->code.npath_map = o->npath_map;
@@ -2119,8 +2179,15 @@ static CfreeStatus cc_compile_source_emit(CfreeCompiler* compiler,
CfreeObjBuilder* ob = NULL;
CfreeStatus st =
cc_compile_source_obj(compiler, lang, copts, name, input, &ob);
- if (st == CFREE_OK && !copts->code.emit_c_source)
- st = cfree_obj_builder_emit(ob, out);
+ if (st == CFREE_OK) {
+ if (copts->code.emit_c_source) {
+ /* c_source_writer is wired during CG; nothing to do here. */
+ } else if (copts->code.emit_asm_source) {
+ st = cfree_obj_builder_emit_asm(ob, out);
+ } else {
+ st = cfree_obj_builder_emit(ob, out);
+ }
+ }
cfree_obj_builder_free(ob);
return st;
}
@@ -2218,12 +2285,84 @@ static int cc_run_compile_objs(DriverEnv* env, const CcOptions* o,
if (rc != 0) return rc;
}
for (i = 0; i < o->nsource_memory; ++i) {
- const char* out = "<stdin>.o";
+ const char* out =
+ o->emit_asm_source ? "<stdin>.s" : "<stdin>.o";
if (cc_run_compile_one(env, o, pp, 1, i, out) != 0) return 1;
}
return 0;
}
+static int cc_run_check(DriverEnv* env, const CcOptions* o,
+ const CfreePreprocessOptions* pp) {
+ CfreeContext ctx = driver_env_to_context(env);
+ const CfreeFileIO* io = ctx.file_io;
+ CfreeCompiler* compiler = NULL;
+ DriverLoad* src_lf = NULL;
+ CfreeSlice* src_bytes = NULL;
+ CfreeCCompileOptions copts;
+ uint32_t i;
+ int rc = 1;
+
+ if (!io || !io->read_all) {
+ driver_errf(CC_TOOL, "host file I/O unavailable");
+ return 1;
+ }
+
+ if (o->nsource_files) {
+ src_lf = driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_lf));
+ src_bytes = driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_bytes));
+ if (!src_lf || !src_bytes) {
+ driver_errf(CC_TOOL, "out of memory");
+ goto out;
+ }
+ }
+
+ for (i = 0; i < o->nsource_files; ++i) {
+ if (driver_load_bytes(io, CC_TOOL, o->source_files[i], &src_lf[i],
+ &src_bytes[i]) != 0)
+ goto out;
+ }
+
+ if (driver_compiler_new(o->target, &ctx, &compiler) != CFREE_OK) {
+ driver_errf(CC_TOOL, "failed to initialize compiler");
+ goto out;
+ }
+
+ cc_fill_c_opts(o, pp, &copts);
+ for (i = 0; i < o->nsource_files; ++i) {
+ CfreeObjBuilder* ob = NULL;
+ CfreeLanguage lang =
+ cc_resolve_lang(compiler, o->source_files[i], o->source_langs[i]);
+ CfreeStatus st =
+ cc_compile_source_obj(compiler, lang, &copts,
+ cfree_slice_cstr(o->source_files[i]),
+ &src_bytes[i], &ob);
+ cfree_obj_builder_free(ob);
+ if (st != CFREE_OK) goto out;
+ }
+ for (i = 0; i < o->nsource_memory; ++i) {
+ CfreeObjBuilder* ob = NULL;
+ CfreeStatus st =
+ cc_compile_source_obj(compiler, o->source_memory[i].lang, &copts,
+ o->source_memory[i].name,
+ &o->source_memory[i].bytes, &ob);
+ cfree_obj_builder_free(ob);
+ if (st != CFREE_OK) goto out;
+ }
+
+ rc = 0;
+
+out:
+ if (compiler) driver_compiler_free(compiler);
+ if (src_lf) {
+ for (i = 0; i < o->nsource_files; ++i) driver_release_bytes(io, &src_lf[i]);
+ }
+ if (src_bytes)
+ driver_free(env, src_bytes, o->nsource_files * sizeof(*src_bytes));
+ if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf));
+ return rc;
+}
+
/* exe path: compile every C source via a single CfreeCompiler, load
* .o/.a/script inputs, and call cfree_link_exe or cfree_link_shared. The
* compiler owns the per-source CfreeObjBuilders for the lifetime of the
@@ -2504,7 +2643,7 @@ out:
return rc;
}
-int driver_cc(int argc, char** argv) {
+static int driver_cc_main(int argc, char** argv, int force_check) {
DriverEnv env;
CcOptions co = {0};
DriverRuntimeSupport runtime = {0};
@@ -2514,6 +2653,10 @@ int driver_cc(int argc, char** argv) {
int link_action;
if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ if (force_check) {
+ driver_help_check();
+ return 0;
+ }
driver_help_cc();
return 0;
}
@@ -2521,6 +2664,7 @@ int driver_cc(int argc, char** argv) {
driver_env_init(&env);
co.env = &env;
co.driver_path = argv[0];
+ co.syntax_only = force_check ? 1 : 0;
if (cc_parse(argc, argv, &co) != 0) {
cc_options_release(&co);
@@ -2534,7 +2678,7 @@ int driver_cc(int argc, char** argv) {
return rc;
}
- link_action = !co.compile_only && !co.preprocess_only &&
+ link_action = !co.compile_only && !co.preprocess_only && !co.syntax_only &&
co.dep_mode != CC_DEP_M && co.dep_mode != CC_DEP_MM;
if (driver_runtime_resolve(&env, co.support_dir, co.driver_path, &runtime) ==
0) {
@@ -2591,6 +2735,8 @@ int driver_cc(int argc, char** argv) {
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.syntax_only) {
+ rc = cc_run_check(&env, &co, &pp);
} else if (co.compile_only) {
rc = cc_run_compile_objs(&env, &co, &pp);
} else {
@@ -2602,3 +2748,9 @@ int driver_cc(int argc, char** argv) {
driver_env_fini(&env);
return rc;
}
+
+int driver_cc(int argc, char** argv) { return driver_cc_main(argc, argv, 0); }
+
+int driver_check(int argc, char** argv) {
+ return driver_cc_main(argc, argv, 1);
+}
diff --git a/driver/driver.h b/driver/driver.h
@@ -8,7 +8,7 @@
#include "env.h"
-/* The cfree CLI driver. Multi-call binary: dispatches to one of seven tool
+/* The cfree CLI driver. Multi-call binary: dispatches to a named tool
* front-ends by argv[0]'s basename, falling back to argv[1] (e.g.
* `cfree cc ...`). The driver only depends on libcfree's public API
* (<cfree/...>); it has no access to libcfree's internal headers.
@@ -17,6 +17,7 @@
typedef enum DriverTool {
DRIVER_TOOL_CC,
+ DRIVER_TOOL_CHECK,
DRIVER_TOOL_CPP,
DRIVER_TOOL_AS,
DRIVER_TOOL_LD,
@@ -26,6 +27,9 @@ typedef enum DriverTool {
DRIVER_TOOL_DBG,
DRIVER_TOOL_RUN,
DRIVER_TOOL_EMU,
+ DRIVER_TOOL_NM,
+ DRIVER_TOOL_SIZE,
+ DRIVER_TOOL_ADDR2LINE,
} DriverTool;
/* Multi-call entry: dispatches by argv[0] basename (or argv[1] fallback). */
@@ -33,6 +37,7 @@ int driver_main(int argc, char **argv);
/* Direct entry per tool. Each lives in driver/<tool>.c. */
int driver_cc(int argc, char **argv);
+int driver_check(int argc, char **argv);
int driver_cpp(int argc, char **argv);
int driver_as(int argc, char **argv);
int driver_ld(int argc, char **argv);
@@ -44,12 +49,16 @@ int driver_objdump(int argc, char **argv);
int driver_dbg(int argc, char **argv);
int driver_run(int argc, char **argv);
int driver_emu(int argc, char **argv);
+int driver_nm(int argc, char **argv);
+int driver_size(int argc, char **argv);
+int driver_addr2line(int argc, char **argv);
/* Per-tool help printers. Write a multi-section help text to stdout and
* return. The tool entry-points call these when invoked with no args, -h,
* or --help (objdump excepts -h, since GNU objdump uses it for section
* headers — only --help triggers help there). */
void driver_help_cc(void);
+void driver_help_check(void);
void driver_help_cpp(void);
void driver_help_as(void);
void driver_help_ld(void);
@@ -61,6 +70,9 @@ void driver_help_objdump(void);
void driver_help_dbg(void);
void driver_help_run(void);
void driver_help_emu(void);
+void driver_help_nm(void);
+void driver_help_size(void);
+void driver_help_addr2line(void);
/* Multi-call top-level help (`cfree`, `cfree -h`, `cfree --help`,
* `cfree help`). Lists each tool with a one-line summary and explains
diff --git a/driver/main.c b/driver/main.c
@@ -2,9 +2,9 @@
#include "driver.h"
-/* Multi-call dispatch. Looks at argv[0]'s basename for "cc", "as", "ld",
- * "ar", "objdump", "dbg", "run", or "emu"; if argv[0] is the bare "cfree"
- * binary, falls back to argv[1]. Preprocessor-only mode is `cc -E`.
+/* Multi-call dispatch. Looks at argv[0]'s basename for a tool name; if argv[0]
+ * is the bare "cfree" binary, falls back to argv[1].
+ * Preprocessor-only mode is `cc -E`.
*
* Help routing is layered on top of dispatch:
*
@@ -23,6 +23,7 @@
static int dispatch(const char* name, int argc, char** argv) {
if (driver_streq(name, "cc")) return driver_cc(argc, argv);
+ if (driver_streq(name, "check")) return driver_check(argc, argv);
#if CFREE_LANG_CPP_ENABLED
if (driver_streq(name, "cpp")) return driver_cpp(argc, argv);
#endif
@@ -36,6 +37,9 @@ static int dispatch(const char* name, int argc, char** argv) {
if (driver_streq(name, "dbg")) return driver_dbg(argc, argv);
if (driver_streq(name, "run")) return driver_run(argc, argv);
if (driver_streq(name, "emu")) return driver_emu(argc, argv);
+ if (driver_streq(name, "nm")) return driver_nm(argc, argv);
+ if (driver_streq(name, "size")) return driver_size(argc, argv);
+ if (driver_streq(name, "addr2line")) return driver_addr2line(argc, argv);
return -1;
}
@@ -46,6 +50,10 @@ static int print_tool_help(const char* name) {
driver_help_cc();
return 0;
}
+ if (driver_streq(name, "check")) {
+ driver_help_check();
+ return 0;
+ }
#if CFREE_LANG_CPP_ENABLED
if (driver_streq(name, "cpp")) {
driver_help_cpp();
@@ -92,6 +100,18 @@ static int print_tool_help(const char* name) {
driver_help_emu();
return 0;
}
+ if (driver_streq(name, "nm")) {
+ driver_help_nm();
+ return 0;
+ }
+ if (driver_streq(name, "size")) {
+ driver_help_size();
+ return 0;
+ }
+ if (driver_streq(name, "addr2line")) {
+ driver_help_addr2line();
+ return 0;
+ }
return -1;
}
@@ -128,6 +148,7 @@ void driver_help_top(void) {
"TOOLS\n"
" cc Compile (and link) C sources, with cpp / dep-emit / -shared "
"modes\n"
+ " check Run C frontend checks without emitting code\n"
" cpp Standalone C preprocessor (alias for `cc -E` minus link "
"scaffold)\n"
" as Assemble a GAS-subset text source into a relocatable "
@@ -141,6 +162,9 @@ void driver_help_top(void) {
" run JIT-compile inputs and invoke the entry symbol in-process\n"
" dbg Interactive JIT debugger (REPL on top of the JIT image)\n"
" emu Run a guest user-mode ELF (aarch64/riscv64) on the host\n"
+ " nm List symbols from object files\n"
+ " size Display section sizes of object files\n"
+ " addr2line Translate addresses to file:line using debug info\n"
"\n"
"GETTING HELP\n"
" cfree <tool> --help full per-tool help (also -h, except "
diff --git a/driver/nm.c b/driver/nm.c
@@ -0,0 +1,319 @@
+#include "driver.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cfree/archive.h>
+#include <cfree/core.h>
+#include <cfree/object.h>
+
+#define NM_TOOL "nm"
+
+typedef struct NmSym {
+ CfreeSlice name;
+ uint64_t value;
+ CfreeSymBind bind;
+ CfreeSymKind kind;
+ int defined; /* 1 = has a section, 0 = undef/abs/common */
+ const char *file_prefix;
+} NmSym;
+
+typedef struct NmOpts {
+ int debug_syms; /* -a */
+ int extern_only; /* -g */
+ int undef_only; /* -u */
+ int defined_only; /* --defined-only */
+ int numeric_sort; /* -n */
+ int reverse_sort; /* -r */
+ int no_sort; /* -p / --no-sort */
+ int print_file; /* -A */
+} NmOpts;
+
+static int nm_symbol_visible(const CfreeObjSymInfo *si, const NmOpts *opts) {
+ if (!si->name.len) return 0;
+ if (opts->undef_only &&
+ si->section != CFREE_SECTION_NONE && si->kind != CFREE_SK_ABS)
+ return 0;
+ if (opts->defined_only && si->section == CFREE_SECTION_NONE &&
+ si->kind != CFREE_SK_ABS)
+ return 0;
+ if (opts->extern_only &&
+ si->bind != CFREE_SB_GLOBAL && si->bind != CFREE_SB_WEAK)
+ return 0;
+ if (!opts->debug_syms) {
+ if (si->kind == CFREE_SK_FILE) return 0;
+ if (si->kind == CFREE_SK_SECTION && si->bind == CFREE_SB_LOCAL) return 0;
+ }
+ return 1;
+}
+
+static char nm_type_char(const NmSym *s) {
+ if (s->kind == CFREE_SK_ABS)
+ return (s->bind == CFREE_SB_LOCAL) ? 'a' : 'A';
+ if (!s->defined) {
+ if (s->kind == CFREE_SK_COMMON) {
+ if (s->bind == CFREE_SB_WEAK) return 'v';
+ return (s->bind == CFREE_SB_LOCAL) ? 'c' : 'C';
+ }
+ if (s->bind == CFREE_SB_WEAK) return 'w';
+ return (s->bind == CFREE_SB_LOCAL) ? 'u' : 'U';
+ }
+ switch (s->kind) {
+ case CFREE_SK_FUNC:
+ if (s->bind == CFREE_SB_WEAK) return 'W';
+ return (s->bind == CFREE_SB_LOCAL) ? 't' : 'T';
+ case CFREE_SK_IFUNC:
+ return (s->bind == CFREE_SB_LOCAL) ? 'i' : 'I';
+ case CFREE_SK_OBJ:
+ case CFREE_SK_NOTYPE:
+ if (s->bind == CFREE_SB_WEAK) return 'V';
+ return (s->bind == CFREE_SB_LOCAL) ? 'd' : 'D';
+ case CFREE_SK_TLS:
+ if (s->bind == CFREE_SB_WEAK) return 'W';
+ return (s->bind == CFREE_SB_LOCAL) ? 'r' : 'R';
+ default:
+ return (s->bind == CFREE_SB_LOCAL) ? 'n' : 'N';
+ }
+}
+
+static int nm_name_cmp(const void *a, const void *b) {
+ const NmSym *sa = (const NmSym *)a;
+ const NmSym *sb = (const NmSym *)b;
+ size_t la = sa->name.len, lb = sb->name.len;
+ size_t n = la < lb ? la : lb;
+ int c = memcmp(sa->name.s, sb->name.s, n);
+ if (c) return c;
+ return la < lb ? -1 : (la > lb ? 1 : 0);
+}
+
+static int nm_value_cmp(const void *a, const void *b) {
+ const NmSym *sa = (const NmSym *)a;
+ const NmSym *sb = (const NmSym *)b;
+ if (sa->value < sb->value) return -1;
+ if (sa->value > sb->value) return 1;
+ return nm_name_cmp(a, b);
+}
+
+static void nm_append_sym(DriverEnv *env, NmSym **syms, uint32_t *n,
+ uint32_t *cap, const CfreeObjSymInfo *si,
+ const char *file_prefix, int *ptr_digits,
+ CfreeTarget t) {
+ int d = (t.ptr_size == 4) ? 8 : 16;
+ if (d > *ptr_digits) *ptr_digits = d;
+ if (*n >= *cap) {
+ uint32_t nc = *cap ? *cap * 2u : 64u;
+ NmSym *ns = (NmSym *)driver_alloc_zeroed(env, (size_t)nc * sizeof(NmSym));
+ if (!ns) return;
+ if (*syms) {
+ memcpy(ns, *syms, (size_t)(*n) * sizeof(NmSym));
+ driver_free(env, *syms, (size_t)(*cap) * sizeof(NmSym));
+ }
+ *syms = ns;
+ *cap = nc;
+ }
+ NmSym *ds = &(*syms)[*n];
+ ds->name = si->name;
+ ds->value = si->value;
+ ds->bind = si->bind;
+ ds->kind = si->kind;
+ ds->defined = (si->section != CFREE_SECTION_NONE);
+ ds->file_prefix = file_prefix;
+ (*n)++;
+}
+
+static void nm_collect_obj(CfreeObjFile *of, const NmOpts *opts,
+ const char *file_prefix, NmSym **syms,
+ uint32_t *n, uint32_t *cap, int *ptr_digits,
+ DriverEnv *env) {
+ CfreeTarget t = cfree_obj_target(of);
+ CfreeObjSymIter *it = NULL;
+ if (cfree_obj_symiter_new(of, &it) != CFREE_OK) return;
+ for (;;) {
+ CfreeObjSymInfo si;
+ if (cfree_obj_symiter_next(it, &si) != CFREE_ITER_ITEM) break;
+ if (!nm_symbol_visible(&si, opts)) continue;
+ nm_append_sym(env, syms, n, cap, &si, file_prefix, ptr_digits, t);
+ }
+ cfree_obj_symiter_free(it);
+}
+
+static int nm_process_file(const CfreeContext *ctx, const CfreeSlice *input,
+ const char *path, const NmOpts *opts, NmSym **syms,
+ uint32_t *n, uint32_t *cap, int *ptr_digits,
+ DriverEnv *env) {
+ CfreeBinFmt fmt = cfree_detect_fmt(input->data, input->len);
+ if (fmt == CFREE_BIN_AR) {
+ CfreeArIter *it = NULL;
+ if (cfree_ar_iter_new(ctx, input, &it) != CFREE_OK) {
+ driver_errf(NM_TOOL, "%s: not a recognized archive", path);
+ return 1;
+ }
+ for (;;) {
+ CfreeArMember m;
+ if (cfree_ar_iter_next(it, &m) != CFREE_ITER_ITEM) break;
+ CfreeObjFile *of = NULL;
+ CfreeSlice mb;
+ mb.data = m.data;
+ mb.len = m.size;
+ if (cfree_obj_open(ctx, m.name, &mb, &of) == CFREE_OK) {
+ nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env);
+ cfree_obj_free(of);
+ }
+ }
+ cfree_ar_iter_free(it);
+ } else {
+ CfreeObjFile *of = NULL;
+ if (cfree_obj_open(ctx, cfree_slice_cstr(path), input, &of) != CFREE_OK &&
+ cfree_obj_open(ctx, CFREE_SLICE_NULL, input, &of) != CFREE_OK) {
+ driver_errf(NM_TOOL, "%s: not a recognized object file", path);
+ return 1;
+ }
+ nm_collect_obj(of, opts, NULL, syms, n, cap, ptr_digits, env);
+ cfree_obj_free(of);
+ }
+ return 0;
+}
+
+static void nm_sort_syms(NmSym *syms, uint32_t nsyms, const NmOpts *opts) {
+ uint32_t i, j;
+ if (opts->no_sort) return;
+ qsort(syms, (size_t)nsyms, sizeof(NmSym),
+ opts->numeric_sort ? nm_value_cmp : nm_name_cmp);
+ if (!opts->reverse_sort) return;
+ for (i = 0, j = nsyms - 1; i < j; ++i, --j) {
+ NmSym t = syms[i];
+ syms[i] = syms[j];
+ syms[j] = t;
+ }
+}
+
+static void nm_print_syms(const NmSym *syms, uint32_t nsyms,
+ const NmOpts *opts, int ptr_digits) {
+ uint32_t i;
+ for (i = 0; i < nsyms; ++i) {
+ const NmSym *s = &syms[i];
+ char tc = nm_type_char(s);
+ const char *pfx = "";
+ char pbuf[256];
+ if (opts->print_file && s->file_prefix) {
+ snprintf(pbuf, sizeof pbuf, "%s:", s->file_prefix);
+ pfx = pbuf;
+ }
+ if (ptr_digits == 8) {
+ driver_printf("%s%08llx %c %.*s\n", pfx,
+ (unsigned long long)s->value, tc,
+ (int)s->name.len, s->name.s);
+ } else {
+ driver_printf("%s%016llx %c %.*s\n", pfx,
+ (unsigned long long)s->value, tc,
+ (int)s->name.len, s->name.s);
+ }
+ }
+}
+
+void driver_help_nm(void) {
+ driver_printf(
+ "%.*s",
+ CFREE_SLICE_ARG(CFREE_SLICE_LIT(
+ "cfree nm — list symbols from object files\n"
+ "\n"
+ "USAGE\n"
+ " cfree nm [OPTIONS] FILE...\n"
+ "\n"
+ "OPTIONS\n"
+ " -a, --debug-syms include debug symbols\n"
+ " -g, --extern-only show only external (global) symbols\n"
+ " -u, --undefined-only show only undefined symbols\n"
+ " --defined-only show only defined symbols\n"
+ " -n, --numeric-sort sort by address\n"
+ " -r, --reverse-sort reverse the sort order\n"
+ " --no-sort, -p do not sort; print in file order\n"
+ " -A, --print-file-name prefix each line with the input file name\n"
+ " -h, --help show this help\n")));
+}
+
+int driver_nm(int argc, char **argv) {
+ DriverEnv env;
+ CfreeContext ctx;
+ NmOpts opts;
+ NmSym *syms = NULL;
+ uint32_t nsyms = 0, cap = 0;
+ int ptr_digits = 8;
+ int i, rc = 1, any_input = 0;
+
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_nm();
+ return 0;
+ }
+
+ memset(&opts, 0, sizeof opts);
+ driver_env_init(&env);
+ ctx = driver_env_to_context(&env);
+
+ for (i = 1; i < argc; ++i) {
+ const char *a = argv[i];
+ if (driver_streq(a, "-a") || driver_streq(a, "--debug-syms")) {
+ opts.debug_syms = 1; continue;
+ }
+ if (driver_streq(a, "-g") || driver_streq(a, "--extern-only")) {
+ opts.extern_only = 1; continue;
+ }
+ if (driver_streq(a, "-u") || driver_streq(a, "--undefined-only")) {
+ opts.undef_only = 1; continue;
+ }
+ if (driver_streq(a, "--defined-only")) {
+ opts.defined_only = 1; continue;
+ }
+ if (driver_streq(a, "-n") || driver_streq(a, "--numeric-sort")) {
+ opts.numeric_sort = 1; continue;
+ }
+ if (driver_streq(a, "-r") || driver_streq(a, "--reverse-sort")) {
+ opts.reverse_sort = 1; continue;
+ }
+ if (driver_streq(a, "--no-sort") || driver_streq(a, "-p")) {
+ opts.no_sort = 1; continue;
+ }
+ if (driver_streq(a, "-A") || driver_streq(a, "--print-file-name")) {
+ opts.print_file = 1; continue;
+ }
+ if (a[0] == '-') {
+ driver_errf(NM_TOOL, "unknown option: %s", a);
+ rc = 2;
+ goto done;
+ }
+ {
+ const char *path = a;
+ DriverLoad ld = {0};
+ CfreeSlice input;
+ if (driver_load_bytes(&env.file_io, NM_TOOL, path, &ld, &input) != 0) {
+ rc = 1;
+ goto done;
+ }
+ if (nm_process_file(&ctx, &input, path, &opts, &syms, &nsyms, &cap,
+ &ptr_digits, &env) != 0) {
+ driver_release_bytes(&env.file_io, &ld);
+ rc = 1;
+ goto done;
+ }
+ driver_release_bytes(&env.file_io, &ld);
+ any_input = 1;
+ }
+ }
+
+ if (!any_input) {
+ driver_errf(NM_TOOL, "no input files");
+ rc = 2;
+ goto done;
+ }
+
+ nm_sort_syms(syms, nsyms, &opts);
+ nm_print_syms(syms, nsyms, &opts, ptr_digits);
+ rc = 0;
+
+done:
+ if (syms) driver_free(&env, syms, (size_t)cap * sizeof(NmSym));
+ driver_env_fini(&env);
+ return rc;
+}
diff --git a/driver/size.c b/driver/size.c
@@ -0,0 +1,355 @@
+#include "driver.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cfree/archive.h>
+#include <cfree/core.h>
+#include <cfree/object.h>
+
+#define SIZE_TOOL "size"
+
+typedef enum SizeFmt {
+ SIZE_FMT_BERKELEY,
+ SIZE_FMT_SYSV,
+} SizeFmt;
+
+typedef enum SizeRadix {
+ SIZE_RAD_HEX,
+ SIZE_RAD_DEC,
+ SIZE_RAD_OCT,
+} SizeRadix;
+
+typedef struct SizeOpts {
+ SizeFmt fmt; /* -A => SYSV, default BERKELEY */
+ SizeRadix radix; /* default hex; -d => DEC, -o => OCT */
+ int common; /* --common */
+ int totals; /* -t / --totals (berkeley only) */
+} SizeOpts;
+
+typedef struct SizeAgg {
+ uint64_t text;
+ uint64_t data;
+ uint64_t bss;
+ uint64_t total;
+} SizeAgg;
+
+static void size_classify_section(const CfreeObjSecInfo *sec, int *is_text,
+ int *is_data, int *is_bss) {
+ int alloc = (sec->flags & CFREE_SF_ALLOC) != 0;
+ int exec = (sec->flags & CFREE_SF_EXEC) != 0;
+ int write = (sec->flags & CFREE_SF_WRITE) != 0;
+
+ *is_text = 0;
+ *is_data = 0;
+ *is_bss = 0;
+
+ if (!alloc) return;
+ if (sec->kind == CFREE_SEC_DEBUG) return;
+
+ if (sec->kind == CFREE_SEC_TEXT || exec) {
+ *is_text = 1;
+ return;
+ }
+ if (sec->kind == CFREE_SEC_BSS) {
+ *is_bss = 1;
+ return;
+ }
+ if (sec->kind == CFREE_SEC_RODATA) {
+ *is_data = 1;
+ return;
+ }
+ if (sec->kind == CFREE_SEC_DATA || write) {
+ *is_data = 1;
+ return;
+ }
+ *is_data = 1;
+}
+
+static SizeAgg size_compute_obj(CfreeObjFile *of, const SizeOpts *opts) {
+ SizeAgg a;
+ uint32_t ns, i;
+ memset(&a, 0, sizeof a);
+ ns = cfree_obj_nsections(of);
+ for (i = 0; i < ns; ++i) {
+ CfreeObjSecInfo sec;
+ int is_text, is_data, is_bss;
+ if (cfree_obj_section(of, i, &sec) != CFREE_OK) continue;
+ size_classify_section(&sec, &is_text, &is_data, &is_bss);
+ if (is_text) a.text += sec.size;
+ if (is_data) a.data += sec.size;
+ if (is_bss) a.bss += sec.size;
+ }
+ if (opts->common) {
+ CfreeObjSymIter *it = NULL;
+ if (cfree_obj_symiter_new(of, &it) == CFREE_OK) {
+ for (;;) {
+ CfreeObjSymInfo si;
+ if (cfree_obj_symiter_next(it, &si) != CFREE_ITER_ITEM) break;
+ if (si.kind == CFREE_SK_COMMON) a.bss += si.size;
+ }
+ cfree_obj_symiter_free(it);
+ }
+ }
+ a.total = a.text + a.data + a.bss;
+ return a;
+}
+
+static void size_print_val(uint64_t v, SizeRadix radix, int width,
+ char *buf, size_t buf_size) {
+ switch (radix) {
+ case SIZE_RAD_DEC:
+ snprintf(buf, buf_size, "%*llu", width, (unsigned long long)v);
+ break;
+ case SIZE_RAD_OCT:
+ snprintf(buf, buf_size, "%*llo", width, (unsigned long long)v);
+ break;
+ default: /* hex */
+ snprintf(buf, buf_size, "%*llx", width, (unsigned long long)v);
+ break;
+ }
+}
+
+static int size_width(SizeRadix radix) {
+ return (radix == SIZE_RAD_OCT) ? 10 : (radix == SIZE_RAD_DEC) ? 8 : 7;
+}
+
+static void size_print_berkeley_header(const SizeOpts *opts) {
+ int w = size_width(opts->radix);
+ driver_printf(" %-*s %-*s %-*s %8s %7s filename\n",
+ w, "text", w, "data", w, "bss", "dec", "hex");
+}
+
+static void size_print_berkeley_sums(const SizeAgg *a, const char *name,
+ const SizeOpts *opts) {
+ int w = size_width(opts->radix);
+ char text_buf[32], data_buf[32], bss_buf[32], dec_buf[32], hex_buf[32];
+ size_print_val(a->text, opts->radix, w, text_buf, sizeof text_buf);
+ size_print_val(a->data, opts->radix, w, data_buf, sizeof data_buf);
+ size_print_val(a->bss, opts->radix, w, bss_buf, sizeof bss_buf);
+ size_print_val(a->total, SIZE_RAD_DEC, 8, dec_buf, sizeof dec_buf);
+ size_print_val(a->total, SIZE_RAD_HEX, 7, hex_buf, sizeof hex_buf);
+ driver_printf(" %s %s %s %s %s %s\n",
+ text_buf, data_buf, bss_buf, dec_buf, hex_buf, name);
+}
+
+static void size_print_sysv(CfreeObjFile *of, const char *name,
+ const SizeOpts *opts) {
+ uint32_t ns, i;
+ driver_printf("%s :\n", name);
+ driver_printf("section size addr\n");
+ ns = cfree_obj_nsections(of);
+ for (i = 0; i < ns; ++i) {
+ CfreeObjSecInfo sec;
+ uint64_t addr = 0;
+ if (cfree_obj_section(of, i, &sec) != CFREE_OK) continue;
+ if (!(sec.flags & CFREE_SF_ALLOC)) continue;
+ if (sec.kind == CFREE_SEC_DEBUG) continue;
+ if (cfree_obj_section_data(of, i, NULL, NULL) == CFREE_OK) {
+ /* section has content; try to get its vaddr */
+ }
+ driver_printf("%-16.16s %08llx %08llx\n",
+ sec.name.len ? sec.name.s : "(anon)",
+ (unsigned long long)sec.size,
+ (unsigned long long)addr);
+ }
+ {
+ SizeAgg a = size_compute_obj(of, opts);
+ driver_printf("Total %08llx\n", (unsigned long long)a.total);
+ }
+}
+
+static int size_process_file(const CfreeContext *ctx, const CfreeSlice *input,
+ const char *path, const SizeOpts *opts,
+ SizeAgg *total_out, int *any_out) {
+ CfreeBinFmt fmt = cfree_detect_fmt(input->data, input->len);
+ if (fmt == CFREE_BIN_AR) {
+ CfreeArIter *it = NULL;
+ if (cfree_ar_iter_new(ctx, input, &it) != CFREE_OK) {
+ driver_errf(SIZE_TOOL, "%s: not a recognized archive", path);
+ return 1;
+ }
+ for (;;) {
+ CfreeArMember m;
+ if (cfree_ar_iter_next(it, &m) != CFREE_ITER_ITEM) break;
+ CfreeObjFile *of = NULL;
+ CfreeSlice mb;
+ mb.data = m.data;
+ mb.len = m.size;
+ if (cfree_obj_open(ctx, CFREE_SLICE_NULL, &mb, &of) == CFREE_OK) {
+ SizeAgg a = size_compute_obj(of, opts);
+ if (opts->fmt == SIZE_FMT_BERKELEY) {
+ char nmbuf[512];
+ snprintf(nmbuf, sizeof nmbuf, "%.*s(%.*s)",
+ path ? (int)strlen(path) : 0, path ? path : "",
+ (int)m.name.len, m.name.s);
+ size_print_berkeley_sums(&a, nmbuf, opts);
+ } else {
+ char nmbuf[512];
+ snprintf(nmbuf, sizeof nmbuf, "%s(%.*s)", path,
+ (int)m.name.len, m.name.s);
+ size_print_sysv(of, nmbuf, opts);
+ }
+ total_out->text += a.text;
+ total_out->data += a.data;
+ total_out->bss += a.bss;
+ total_out->total += a.total;
+ *any_out = 1;
+ cfree_obj_free(of);
+ }
+ }
+ cfree_ar_iter_free(it);
+ } else {
+ CfreeObjFile *of = NULL;
+ if (cfree_obj_open(ctx, cfree_slice_cstr(path), input, &of) != CFREE_OK) {
+ driver_errf(SIZE_TOOL, "%s: not a recognized object file", path);
+ return 1;
+ }
+ {
+ SizeAgg a = size_compute_obj(of, opts);
+ if (opts->fmt == SIZE_FMT_BERKELEY) {
+ size_print_berkeley_sums(&a, path, opts);
+ } else {
+ size_print_sysv(of, path, opts);
+ }
+ total_out->text += a.text;
+ total_out->data += a.data;
+ total_out->bss += a.bss;
+ total_out->total += a.total;
+ }
+ *any_out = 1;
+ cfree_obj_free(of);
+ }
+ return 0;
+}
+
+void driver_help_size(void) {
+ driver_printf(
+ "%.*s",
+ CFREE_SLICE_ARG(CFREE_SLICE_LIT(
+ "cfree size — display section sizes of object files\n"
+ "\n"
+ "USAGE\n"
+ " cfree size [OPTIONS] FILE...\n"
+ "\n"
+ "OPTIONS\n"
+ " -A, --format sysv SysV output (section-by-section)\n"
+ " -B, --format berkeley Berkeley output (default)\n"
+ " -d sizes in decimal\n"
+ " -o sizes in octal\n"
+ " -x sizes in hexadecimal\n"
+ " --common include COMMON symbols in bss total\n"
+ " -t, --totals print a totals line (Berkeley only)\n"
+ " -h, --help show this help\n")));
+}
+
+int driver_size(int argc, char **argv) {
+ DriverEnv env;
+ CfreeContext ctx;
+ SizeOpts opts;
+ SizeAgg totals;
+ int i, rc = 1, any_input = 0, any_output = 0;
+
+ if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ driver_help_size();
+ return 0;
+ }
+
+ memset(&opts, 0, sizeof opts);
+ memset(&totals, 0, sizeof totals);
+ opts.radix = SIZE_RAD_HEX;
+ driver_env_init(&env);
+ ctx = driver_env_to_context(&env);
+
+ for (i = 1; i < argc; ++i) {
+ const char *a = argv[i];
+ if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv")) {
+ opts.fmt = SIZE_FMT_SYSV; continue;
+ }
+ if (driver_streq(a, "-B") || driver_streq(a, "--format=berkeley")) {
+ opts.fmt = SIZE_FMT_BERKELEY; continue;
+ }
+ if (driver_streq(a, "-d")) {
+ opts.radix = SIZE_RAD_DEC; continue;
+ }
+ if (driver_streq(a, "-o")) {
+ opts.radix = SIZE_RAD_OCT; continue;
+ }
+ if (driver_streq(a, "-x")) {
+ opts.radix = SIZE_RAD_HEX; continue;
+ }
+ if (driver_streq(a, "--common")) {
+ opts.common = 1; continue;
+ }
+ if (driver_streq(a, "-t") || driver_streq(a, "--totals")) {
+ opts.totals = 1; continue;
+ }
+ if (a[0] == '-') {
+ driver_errf(SIZE_TOOL, "unknown option: %s", a);
+ rc = 2;
+ goto done;
+ }
+ any_input = 1;
+ }
+
+ if (!any_input) {
+ driver_errf(SIZE_TOOL, "no input files");
+ rc = 2;
+ goto done;
+ }
+
+ if (opts.fmt == SIZE_FMT_BERKELEY)
+ size_print_berkeley_header(&opts);
+
+ for (i = 1; i < argc; ++i) {
+ const char *a = argv[i];
+ if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv") ||
+ driver_streq(a, "-B") || driver_streq(a, "--format=berkeley") ||
+ driver_streq(a, "-d") || driver_streq(a, "-o") ||
+ driver_streq(a, "-x") || driver_streq(a, "--common") ||
+ driver_streq(a, "-t") || driver_streq(a, "--totals")) {
+ continue;
+ }
+ {
+ const char *path = a;
+ DriverLoad ld = {0};
+ CfreeSlice input;
+ if (driver_load_bytes(&env.file_io, SIZE_TOOL, path, &ld, &input) != 0) {
+ rc = 1;
+ goto done;
+ }
+ if (size_process_file(&ctx, &input, path, &opts, &totals,
+ &any_output) != 0) {
+ driver_release_bytes(&env.file_io, &ld);
+ rc = 1;
+ goto done;
+ }
+ driver_release_bytes(&env.file_io, &ld);
+ }
+ }
+
+ if (!any_output) {
+ driver_errf(SIZE_TOOL, "no input files");
+ rc = 2;
+ goto done;
+ }
+
+ if (opts.totals && opts.fmt == SIZE_FMT_BERKELEY) {
+ int w = size_width(opts.radix);
+ char text_buf[32], data_buf[32], bss_buf[32], dec_buf[32], hex_buf[32];
+ size_print_val(totals.text, opts.radix, w, text_buf, sizeof text_buf);
+ size_print_val(totals.data, opts.radix, w, data_buf, sizeof data_buf);
+ size_print_val(totals.bss, opts.radix, w, bss_buf, sizeof bss_buf);
+ size_print_val(totals.total, SIZE_RAD_DEC, 8, dec_buf, sizeof dec_buf);
+ size_print_val(totals.total, SIZE_RAD_HEX, 7, hex_buf, sizeof hex_buf);
+ driver_printf(" %s %s %s %s %s (TOTALS)\n",
+ text_buf, data_buf, bss_buf, dec_buf, hex_buf);
+ }
+
+ rc = 0;
+done:
+ driver_env_fini(&env);
+ return rc;
+}
diff --git a/include/cfree/asm_emit.h b/include/cfree/asm_emit.h
@@ -0,0 +1,9 @@
+#ifndef CFREE_ASM_EMIT_H
+#define CFREE_ASM_EMIT_H
+
+#include <cfree/core.h>
+#include <cfree/object.h>
+
+CfreeStatus cfree_obj_builder_emit_asm(CfreeObjBuilder *, CfreeWriter *);
+
+#endif
diff --git a/include/cfree/core.h b/include/cfree/core.h
@@ -195,12 +195,20 @@ typedef struct CfreePathPrefixMap {
typedef struct CfreeCodeOptions {
int opt_level; /* 0 direct; 1+ require CFREE_OPT_ENABLED */
bool debug_info; /* emit source/debug records when supported */
+ /* Run the frontend and CG validation path without emitting target code.
+ * Drivers use this for syntax/semantic checking modes. */
+ bool check_only;
/* When set, CG emits portable C source instead of machine-code bytes.
* The TU is still target-locked: the emitted source uses the configured
* triple's struct layouts and pointer width. Forces opt_level=0.
* Output is written to c_source_writer (set on the emit-side compile
* entry point); object emission is bypassed. See doc/CBACKEND.md. */
bool emit_c_source;
+ /* When set, serialize the compiled object as assembly text (.s) instead
+ * of binary object bytes (.o). Does not alter CG behavior; the normal
+ * machine-code path runs, and the assembled bytes are disassembled into
+ * GAS-syntax text after compilation. Implies compile_only / no link. */
+ bool emit_asm_source;
uint64_t epoch; /* reproducible timestamp seed; 0 means no timestamp */
const CfreePathPrefixMap* path_map;
uint32_t npath_map;
diff --git a/src/api/asm_emit.c b/src/api/asm_emit.c
@@ -0,0 +1,360 @@
+#include <cfree/asm_emit.h>
+#include <cfree/disasm.h>
+
+#include "arch/arch.h"
+#include "core/arena.h"
+#include "core/buf.h"
+#include "core/core.h"
+#include "core/heap.h"
+#include "core/pool.h"
+#include "core/slice.h"
+#include "obj/obj.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#define ASM_BYTES_PER_LINE 16u
+
+static CfreeStatus w_str(Writer* w, const char* s) {
+ return cfree_writer_write(w, s, strlen(s));
+}
+
+static CfreeStatus w_newline(Writer* w) {
+ return cfree_writer_write(w, "\n", 1);
+}
+
+static CfreeStatus w_hex_byte(Writer* w, u8 v) {
+ static const char H[] = "0123456789abcdef";
+ char buf[2];
+ buf[0] = H[(v >> 4) & 0xfu];
+ buf[1] = H[v & 0xfu];
+ return cfree_writer_write(w, buf, 2);
+}
+
+static CfreeStatus w_dec(Writer* w, u64 v) {
+ char buf[32];
+ u32 i = sizeof(buf);
+ if (v == 0) return cfree_writer_write(w, "0", 1);
+ buf[--i] = '\0';
+ while (v) {
+ buf[--i] = (char)('0' + (v % 10));
+ v /= 10;
+ }
+ return cfree_writer_write(w, buf + i, sizeof(buf) - i - 1);
+}
+
+static CfreeStatus w_sym(Writer* w, Compiler* c, Sym name) {
+ Slice s;
+ if (!name) return w_str(w, ".L0");
+ s = pool_slice(c->global, name);
+ return cfree_writer_write(w, s.s, s.len);
+}
+
+typedef struct {
+ u32 offset;
+ Sym name;
+ u16 bind;
+ u16 kind;
+ u64 size;
+} SymLabel;
+
+static int cmp_labels(const void* va, const void* vb) {
+ const SymLabel* a = (const SymLabel*)va;
+ const SymLabel* b = (const SymLabel*)vb;
+ if (a->offset < b->offset) return -1;
+ if (a->offset > b->offset) return 1;
+ return 0;
+}
+
+static SymLabel* collect_labels(Compiler* c, ObjBuilder* ob, ObjSecId sec_id,
+ u32* nlabels_out) {
+ ObjSymIter* it = obj_symiter_new(ob);
+ SymLabel* labels = NULL;
+ u32 n = 0, cap = 0;
+
+ *nlabels_out = 0;
+ if (!it) return NULL;
+
+ for (;;) {
+ ObjSymEntry e;
+ const ObjSym* sym;
+ if (!obj_symiter_next(it, &e)) break;
+ sym = e.sym;
+ if (!sym || sym->removed) continue;
+ if (sym->section_id != sec_id) continue;
+ if (sym->kind == SK_SECTION || sym->kind == SK_FILE) continue;
+ if (!sym->name) continue;
+
+ if (n == cap) {
+ u32 ncap = cap ? cap * 2 : 8;
+ SymLabel* nl = arena_array(c->tu, SymLabel, ncap);
+ if (!nl) break;
+ if (labels) memcpy(nl, labels, cap * sizeof(SymLabel));
+ labels = nl;
+ cap = ncap;
+ }
+ labels[n].offset = (u32)sym->value;
+ labels[n].name = sym->name;
+ labels[n].bind = sym->bind;
+ labels[n].kind = sym->kind;
+ labels[n].size = sym->size;
+ ++n;
+ }
+ obj_symiter_free(it);
+
+ if (n > 0) qsort(labels, n, sizeof(SymLabel), cmp_labels);
+ *nlabels_out = n;
+ return labels;
+}
+
+static CfreeStatus emit_label(Writer* w, Compiler* c, const SymLabel* lbl) {
+ if (lbl->bind == SB_GLOBAL || lbl->bind == SB_WEAK) {
+ w_str(w, " .globl ");
+ w_sym(w, c, lbl->name);
+ w_newline(w);
+ }
+ if (lbl->kind == SK_FUNC) {
+ w_str(w, " .type ");
+ w_sym(w, c, lbl->name);
+ w_str(w, ", @function");
+ w_newline(w);
+ } else if (lbl->kind == SK_OBJ || lbl->kind == SK_COMMON ||
+ lbl->kind == SK_TLS) {
+ w_str(w, " .type ");
+ w_sym(w, c, lbl->name);
+ w_str(w, ", @object");
+ w_newline(w);
+ }
+ w_sym(w, c, lbl->name);
+ w_str(w, ":");
+ return w_newline(w);
+}
+
+static CfreeStatus emit_size_directives(Writer* w, Compiler* c, ObjBuilder* ob,
+ ObjSecId sec_id) {
+ ObjSymIter* it = obj_symiter_new(ob);
+ CfreeStatus st = CFREE_OK;
+ if (!it) return CFREE_NOMEM;
+
+ for (;;) {
+ ObjSymEntry e;
+ const ObjSym* sym;
+ if (!obj_symiter_next(it, &e)) break;
+ sym = e.sym;
+ if (!sym || sym->removed) continue;
+ if (sym->section_id != sec_id) continue;
+ if (sym->kind != SK_FUNC) continue;
+ if (sym->size == 0) continue;
+
+ st = w_str(w, " .size ");
+ if (st != CFREE_OK) break;
+ st = w_sym(w, c, sym->name);
+ if (st != CFREE_OK) break;
+ st = w_str(w, ", .-");
+ if (st != CFREE_OK) break;
+ st = w_sym(w, c, sym->name);
+ if (st != CFREE_OK) break;
+ st = w_newline(w);
+ if (st != CFREE_OK) break;
+ }
+ obj_symiter_free(it);
+ return st;
+}
+
+static const char* sec_directive(const Section* sec) {
+ switch (sec->kind) {
+ case SEC_TEXT:
+ return ".text";
+ case SEC_RODATA:
+ if (sec->flags & SF_TLS) return NULL;
+ return ".section\t.rodata";
+ case SEC_DATA:
+ if (sec->flags & SF_TLS) return NULL;
+ return ".section\t.data";
+ case SEC_BSS:
+ if (sec->flags & SF_TLS) return NULL;
+ return ".section\t.bss";
+ default:
+ return NULL;
+ }
+}
+
+static CfreeStatus emit_data_range(Writer* w, const u8* data, u32 start,
+ u32 end) {
+ u32 off;
+ for (off = start; off < end; off += ASM_BYTES_PER_LINE) {
+ u32 rem = end - off;
+ u32 n = rem < ASM_BYTES_PER_LINE ? rem : ASM_BYTES_PER_LINE;
+ u32 j;
+ CfreeStatus st;
+ st = w_str(w, " .byte 0x");
+ if (st != CFREE_OK) return st;
+ st = w_hex_byte(w, data[off]);
+ if (st != CFREE_OK) return st;
+ for (j = 1; j < n; ++j) {
+ st = w_str(w, ", 0x");
+ if (st != CFREE_OK) return st;
+ st = w_hex_byte(w, data[off + j]);
+ if (st != CFREE_OK) return st;
+ }
+ st = w_newline(w);
+ if (st != CFREE_OK) return st;
+ }
+ return CFREE_OK;
+}
+
+static CfreeStatus emit_zero_range(Writer* w, u32 size) {
+ CfreeStatus st;
+ if (size == 0) return CFREE_OK;
+ st = w_str(w, " .zero ");
+ if (st != CFREE_OK) return st;
+ st = w_dec(w, (u64)size);
+ if (st != CFREE_OK) return st;
+ return w_newline(w);
+}
+
+static CfreeStatus emit_disasm_range(Writer* w, ArchDisasm* dasm,
+ const u8* data, u32 start, u32 end) {
+ u32 off = start;
+ CfreeStatus st;
+
+ while (off < end) {
+ CfreeInsn insn;
+ u64 vaddr = (u64)off;
+ u32 n = arch_disasm_decode(dasm, data + off, end - off, vaddr, &insn);
+ u32 b;
+
+ if (n == 0) {
+ st = w_str(w, " .byte 0x");
+ if (st != CFREE_OK) return st;
+ st = w_hex_byte(w, data[off]);
+ if (st != CFREE_OK) return st;
+ st = w_newline(w);
+ if (st != CFREE_OK) return st;
+ off += 1;
+ continue;
+ }
+
+ st = w_str(w, "\t");
+ if (st != CFREE_OK) return st;
+ st = cfree_writer_write(w, insn.mnemonic.s, insn.mnemonic.len);
+ if (st != CFREE_OK) return st;
+ if (insn.operands.len) {
+ st = w_str(w, "\t");
+ if (st != CFREE_OK) return st;
+ st = cfree_writer_write(w, insn.operands.s, insn.operands.len);
+ if (st != CFREE_OK) return st;
+ }
+ st = w_newline(w);
+ if (st != CFREE_OK) return st;
+
+ (void)b;
+ off += n;
+ }
+ return CFREE_OK;
+}
+
+CfreeStatus cfree_obj_builder_emit_asm(CfreeObjBuilder* builder,
+ CfreeWriter* out_w) {
+ ObjBuilder* ob = (ObjBuilder*)builder;
+ Compiler* c;
+ Writer* w;
+ u32 nsec, i;
+
+ if (!ob || !out_w) return CFREE_INVALID;
+
+ c = obj_compiler(ob);
+ w = (Writer*)out_w;
+ nsec = obj_section_count(ob);
+
+ for (i = 1; i < nsec; ++i) {
+ const Section* sec = obj_section_get(ob, (ObjSecId)i);
+ const char* dir;
+ SymLabel* labels;
+ u32 nlabels, total, off, li;
+ ArchDisasm* dasm;
+ const u8* flat_data;
+ u8* heap_data;
+
+ if (!sec || sec->removed) continue;
+ dir = sec_directive(sec);
+ if (!dir) continue;
+
+ labels = collect_labels(c, ob, (ObjSecId)i, &nlabels);
+
+ w_str(w, " ");
+ w_str(w, dir);
+ w_newline(w);
+
+ if (sec->align > 1) {
+ w_str(w, " .align ");
+ w_dec(w, (u64)sec->align);
+ w_newline(w);
+ }
+
+ if (sec->kind == SEC_BSS) {
+ total = sec->bss_size;
+ } else {
+ total = sec->bytes.total;
+ }
+
+ dasm = NULL;
+ flat_data = NULL;
+ heap_data = NULL;
+
+ if (total > 0 && (sec->flags & SF_EXEC)) {
+ Heap* heap;
+ dasm = arch_disasm_new(c);
+ heap = c->ctx->heap;
+ heap_data = (u8*)heap->alloc(heap, total, 1);
+ if (heap_data) {
+ buf_flatten(&sec->bytes, heap_data);
+ flat_data = heap_data;
+ }
+ } else if (total > 0 && sec->kind != SEC_BSS) {
+ Heap* heap = c->ctx->heap;
+ heap_data = (u8*)heap->alloc(heap, total, 1);
+ if (heap_data) {
+ buf_flatten(&sec->bytes, heap_data);
+ flat_data = heap_data;
+ }
+ }
+
+ off = 0;
+ li = 0;
+
+ while (off < total || li < nlabels) {
+ while (li < nlabels && labels[li].offset == off) {
+ emit_label(w, c, &labels[li]);
+ ++li;
+ }
+
+ if (off >= total) break;
+
+ {
+ u32 next = total;
+ if (li < nlabels && labels[li].offset > off &&
+ labels[li].offset < total)
+ next = labels[li].offset;
+
+ if (sec->kind == SEC_BSS) {
+ emit_zero_range(w, next - off);
+ } else if ((sec->flags & SF_EXEC) && dasm && flat_data) {
+ emit_disasm_range(w, dasm, flat_data, off, next);
+ } else if (flat_data) {
+ emit_data_range(w, flat_data, off, next);
+ }
+ off = next;
+ }
+ }
+
+ emit_size_directives(w, c, ob, (ObjSecId)i);
+
+ if (dasm) arch_disasm_free(dasm);
+ if (heap_data) c->ctx->heap->free(c->ctx->heap, heap_data, total);
+
+ w_newline(w);
+ }
+
+ return cfree_writer_status(out_w);
+}
diff --git a/src/arch/check_target.c b/src/arch/check_target.c
@@ -0,0 +1,576 @@
+#include "arch/arch.h"
+
+#include <string.h>
+
+#include "core/arena.h"
+
+typedef struct CheckTarget {
+ CGTarget base;
+ Compiler* c;
+ ObjBuilder* obj;
+ Label next_label;
+ FrameSlot next_slot;
+ CGScope next_scope;
+} CheckTarget;
+
+static CheckTarget* check_of(CGTarget* t) { return (CheckTarget*)t; }
+
+static void check_func_begin(CGTarget* t, const CGFuncDesc* d) {
+ (void)t;
+ (void)d;
+}
+
+static void check_func_end(CGTarget* t) { (void)t; }
+
+static void check_alias(CGTarget* t, ObjSymId a, ObjSymId b,
+ CfreeCgTypeId ty) {
+ (void)t;
+ (void)a;
+ (void)b;
+ (void)ty;
+}
+
+static FrameSlot check_frame_slot(CGTarget* t, const FrameSlotDesc* d) {
+ CheckTarget* x = check_of(t);
+ (void)d;
+ if (++x->next_slot == FRAME_SLOT_NONE) ++x->next_slot;
+ return x->next_slot;
+}
+
+static CGLocalStorage check_local(CGTarget* t, const CGLocalDesc* d) {
+ CGLocalStorage s;
+ FrameSlotDesc fsd;
+ memset(&s, 0, sizeof s);
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = d ? d->type : CFREE_CG_TYPE_NONE;
+ fsd.name = d ? d->name : 0;
+ fsd.loc = d ? d->loc : (SrcLoc){0, 0, 0};
+ fsd.size = d ? d->size : 0;
+ fsd.align = d ? d->align : 0;
+ fsd.kind = FS_LOCAL;
+ s.kind = CG_LOCAL_STORAGE_FRAME;
+ s.v.frame_slot = check_frame_slot(t, &fsd);
+ return s;
+}
+
+static CGLocalStorage check_param(CGTarget* t, const CGParamDesc* d) {
+ CGLocalStorage s;
+ FrameSlotDesc fsd;
+ memset(&s, 0, sizeof s);
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = d ? d->type : CFREE_CG_TYPE_NONE;
+ fsd.name = d ? d->name : 0;
+ fsd.loc = d ? d->loc : (SrcLoc){0, 0, 0};
+ fsd.size = d ? d->size : 0;
+ fsd.align = d ? d->align : 0;
+ fsd.kind = FS_PARAM;
+ s.kind = CG_LOCAL_STORAGE_FRAME;
+ s.v.frame_slot = check_frame_slot(t, &fsd);
+ return s;
+}
+
+static void check_local_addr(CGTarget* t, Operand dst, const CGLocalDesc* d,
+ CGLocalStorage s) {
+ (void)t;
+ (void)dst;
+ (void)d;
+ (void)s;
+}
+
+static void check_spill_reload(CGTarget* t, Operand r, FrameSlot s,
+ MemAccess m) {
+ (void)t;
+ (void)r;
+ (void)s;
+ (void)m;
+}
+
+static void check_no_regs(CGTarget* t, RegClass cls, const Reg** out, u32* n) {
+ (void)t;
+ (void)cls;
+ if (out) *out = NULL;
+ if (n) *n = 0;
+}
+
+static void check_no_phys_regs(CGTarget* t, RegClass cls,
+ const CGPhysRegInfo** out, u32* n) {
+ (void)t;
+ (void)cls;
+ if (out) *out = NULL;
+ if (n) *n = 0;
+}
+
+static int check_is_caller_saved(CGTarget* t, RegClass cls, Reg r) {
+ (void)t;
+ (void)cls;
+ (void)r;
+ return 0;
+}
+
+static u32 check_zero_call_mask(CGTarget* t, const CGCallDesc* d,
+ RegClass cls) {
+ (void)t;
+ (void)d;
+ (void)cls;
+ return 0;
+}
+
+static u32 check_zero_ret_mask(CGTarget* t, const ABIFuncInfo* f,
+ RegClass cls) {
+ (void)t;
+ (void)f;
+ (void)cls;
+ return 0;
+}
+
+static u32 check_zero_cs_mask(CGTarget* t, RegClass cls) {
+ (void)t;
+ (void)cls;
+ return 0;
+}
+
+static void check_plan_regs(CGTarget* t, RegClass cls, const Reg* regs,
+ u32 n) {
+ (void)t;
+ (void)cls;
+ (void)regs;
+ (void)n;
+}
+
+static u32 check_call_stack_size(CGTarget* t, const CGCallDesc* d) {
+ (void)t;
+ (void)d;
+ return 0;
+}
+
+static Label check_label_new(CGTarget* t) {
+ CheckTarget* x = check_of(t);
+ if (++x->next_label == LABEL_NONE) ++x->next_label;
+ return x->next_label;
+}
+
+static void check_label_place(CGTarget* t, Label l) {
+ (void)t;
+ (void)l;
+}
+
+static MCLabel check_cg_label_to_mc_label(CGTarget* t, Label l) {
+ (void)t;
+ return (MCLabel)l;
+}
+
+static void check_jump(CGTarget* t, Label l) {
+ (void)t;
+ (void)l;
+}
+
+static void check_cmp_branch(CGTarget* t, CmpOp op, Operand a, Operand b,
+ Label l) {
+ (void)t;
+ (void)op;
+ (void)a;
+ (void)b;
+ (void)l;
+}
+
+static void check_switch(CGTarget* t, const CGSwitchDesc* d) {
+ (void)t;
+ (void)d;
+}
+
+static void check_indirect_branch(CGTarget* t, Operand a, const Label* targets,
+ u32 ntargets) {
+ (void)t;
+ (void)a;
+ (void)targets;
+ (void)ntargets;
+}
+
+static void check_load_label_addr(CGTarget* t, Operand dst, Label l) {
+ (void)t;
+ (void)dst;
+ (void)l;
+}
+
+static int check_local_static_data_begin(CGTarget* t,
+ const CGLocalStaticDataDesc* d) {
+ (void)t;
+ (void)d;
+ return 1;
+}
+
+static void check_local_static_data_write(CGTarget* t, const u8* data,
+ u64 len) {
+ (void)t;
+ (void)data;
+ (void)len;
+}
+
+static void check_local_static_data_label_addr(CGTarget* t, Label target,
+ i64 addend, u32 width,
+ u32 address_space) {
+ (void)t;
+ (void)target;
+ (void)addend;
+ (void)width;
+ (void)address_space;
+}
+
+static void check_local_static_data_end(CGTarget* t) { (void)t; }
+
+static CGScope check_scope_begin(CGTarget* t, const CGScopeDesc* d) {
+ CheckTarget* x = check_of(t);
+ (void)d;
+ if (++x->next_scope == CG_SCOPE_NONE) ++x->next_scope;
+ return x->next_scope;
+}
+
+static void check_scope_else(CGTarget* t, CGScope s) {
+ (void)t;
+ (void)s;
+}
+
+static void check_scope_end(CGTarget* t, CGScope s) {
+ (void)t;
+ (void)s;
+}
+
+static void check_scope_xfer(CGTarget* t, CGScope s) {
+ (void)t;
+ (void)s;
+}
+
+static void check_load_imm(CGTarget* t, Operand dst, i64 imm) {
+ (void)t;
+ (void)dst;
+ (void)imm;
+}
+
+static void check_load_const(CGTarget* t, Operand dst, ConstBytes c) {
+ (void)t;
+ (void)dst;
+ (void)c;
+}
+
+static void check_copy(CGTarget* t, Operand dst, Operand src) {
+ (void)t;
+ (void)dst;
+ (void)src;
+}
+
+static void check_load_store(CGTarget* t, Operand a, Operand b, MemAccess m) {
+ (void)t;
+ (void)a;
+ (void)b;
+ (void)m;
+}
+
+static void check_addr_of(CGTarget* t, Operand dst, Operand src) {
+ (void)t;
+ (void)dst;
+ (void)src;
+}
+
+static void check_tls_addr_of(CGTarget* t, Operand dst, ObjSymId sym,
+ i64 addend) {
+ (void)t;
+ (void)dst;
+ (void)sym;
+ (void)addend;
+}
+
+static void check_aggregate(CGTarget* t, Operand dst, Operand src,
+ AggregateAccess a) {
+ (void)t;
+ (void)dst;
+ (void)src;
+ (void)a;
+}
+
+static void check_bitfield(CGTarget* t, Operand a, Operand b,
+ BitFieldAccess bf) {
+ (void)t;
+ (void)a;
+ (void)b;
+ (void)bf;
+}
+
+static void check_binop(CGTarget* t, BinOp op, Operand dst, Operand a,
+ Operand b) {
+ (void)t;
+ (void)op;
+ (void)dst;
+ (void)a;
+ (void)b;
+}
+
+static void check_unop(CGTarget* t, UnOp op, Operand dst, Operand a) {
+ (void)t;
+ (void)op;
+ (void)dst;
+ (void)a;
+}
+
+static void check_cmp(CGTarget* t, CmpOp op, Operand dst, Operand a,
+ Operand b) {
+ (void)t;
+ (void)op;
+ (void)dst;
+ (void)a;
+ (void)b;
+}
+
+static void check_convert(CGTarget* t, ConvKind k, Operand dst, Operand src) {
+ (void)t;
+ (void)k;
+ (void)dst;
+ (void)src;
+}
+
+static void check_call(CGTarget* t, const CGCallDesc* d) {
+ (void)t;
+ (void)d;
+}
+
+static void check_plan_call(CGTarget* t, const CGCallDesc* d, CGCallPlan* p) {
+ (void)t;
+ (void)d;
+ if (p) memset(p, 0, sizeof *p);
+}
+
+static void check_load_call_arg(CGTarget* t, Operand dst,
+ const CGCallPlanMove* m) {
+ (void)t;
+ (void)dst;
+ (void)m;
+}
+
+static void check_store_call_arg(CGTarget* t, const CGCallPlanMove* m) {
+ (void)t;
+ (void)m;
+}
+
+static void check_store_call_ret(CGTarget* t, const CGCallPlanRet* r,
+ Operand src) {
+ (void)t;
+ (void)r;
+ (void)src;
+}
+
+static void check_emit_call_plan(CGTarget* t, const CGCallPlan* p) {
+ (void)t;
+ (void)p;
+}
+
+static void check_ret(CGTarget* t, const CGABIValue* v) {
+ (void)t;
+ (void)v;
+}
+
+static void check_alloca(CGTarget* t, Operand dst, Operand size, u32 align) {
+ (void)t;
+ (void)dst;
+ (void)size;
+ (void)align;
+}
+
+static void check_va_arg(CGTarget* t, Operand dst, Operand ap,
+ CfreeCgTypeId ty) {
+ (void)t;
+ (void)dst;
+ (void)ap;
+ (void)ty;
+}
+
+static void check_one_operand(CGTarget* t, Operand a) {
+ (void)t;
+ (void)a;
+}
+
+static void check_two_operands(CGTarget* t, Operand a, Operand b) {
+ (void)t;
+ (void)a;
+ (void)b;
+}
+
+static void check_intrinsic(CGTarget* t, IntrinKind k, Operand* dsts, u32 ndst,
+ const Operand* args, u32 narg) {
+ (void)t;
+ (void)k;
+ (void)dsts;
+ (void)ndst;
+ (void)args;
+ (void)narg;
+}
+
+static void check_asm_block(CGTarget* t, const char* tmpl,
+ const AsmConstraint* outs, u32 nout,
+ Operand* out_ops, const AsmConstraint* ins,
+ u32 nin, const Operand* in_ops,
+ const Sym* clobbers, u32 nclob) {
+ (void)t;
+ (void)tmpl;
+ (void)outs;
+ (void)nout;
+ (void)out_ops;
+ (void)ins;
+ (void)nin;
+ (void)in_ops;
+ (void)clobbers;
+ (void)nclob;
+}
+
+static void check_atomic_load(CGTarget* t, Operand dst, Operand addr,
+ MemAccess m, MemOrder order) {
+ (void)t;
+ (void)dst;
+ (void)addr;
+ (void)m;
+ (void)order;
+}
+
+static void check_atomic_store(CGTarget* t, Operand addr, Operand src,
+ MemAccess m, MemOrder order) {
+ (void)t;
+ (void)addr;
+ (void)src;
+ (void)m;
+ (void)order;
+}
+
+static void check_atomic_rmw(CGTarget* t, AtomicOp op, Operand dst,
+ Operand addr, Operand val, MemAccess m,
+ MemOrder order) {
+ (void)t;
+ (void)op;
+ (void)dst;
+ (void)addr;
+ (void)val;
+ (void)m;
+ (void)order;
+}
+
+static void check_atomic_cas(CGTarget* t, Operand prior, Operand ok,
+ Operand addr, Operand expected, Operand desired,
+ MemAccess m, MemOrder success,
+ MemOrder failure) {
+ (void)t;
+ (void)prior;
+ (void)ok;
+ (void)addr;
+ (void)expected;
+ (void)desired;
+ (void)m;
+ (void)success;
+ (void)failure;
+}
+
+static void check_fence(CGTarget* t, MemOrder order) {
+ (void)t;
+ (void)order;
+}
+
+static void check_set_loc(CGTarget* t, SrcLoc loc) {
+ (void)t;
+ (void)loc;
+}
+
+static void check_finalize(CGTarget* t) { (void)t; }
+
+static void check_destroy(CGTarget* t) { (void)t; }
+
+static CGTarget* check_backend_make(Compiler* c, ObjBuilder* o,
+ const CfreeCodeOptions* opts) {
+ CheckTarget* x;
+ CGTarget* t;
+ (void)opts;
+ x = arena_new(c->tu, CheckTarget);
+ if (!x) return NULL;
+ memset(x, 0, sizeof *x);
+ x->c = c;
+ x->obj = o;
+ t = &x->base;
+ t->c = c;
+ t->obj = o;
+ t->virtual_regs = 1;
+
+ t->func_begin = check_func_begin;
+ t->func_end = check_func_end;
+ t->alias = check_alias;
+ t->frame_slot = check_frame_slot;
+ t->local = check_local;
+ t->local_addr = check_local_addr;
+ t->param = check_param;
+ t->spill_reg = check_spill_reload;
+ t->reload_reg = check_spill_reload;
+ t->get_allocable_regs = check_no_regs;
+ t->get_phys_regs = check_no_phys_regs;
+ t->get_scratch_regs = check_no_regs;
+ t->is_caller_saved = check_is_caller_saved;
+ t->call_clobber_mask = check_zero_call_mask;
+ t->return_reg_mask = check_zero_ret_mask;
+ t->callee_save_mask = check_zero_cs_mask;
+ t->plan_hard_regs = check_plan_regs;
+ t->reserve_hard_regs = check_plan_regs;
+ t->call_stack_size = check_call_stack_size;
+ t->label_new = check_label_new;
+ t->label_place = check_label_place;
+ t->cg_label_to_mc_label = check_cg_label_to_mc_label;
+ t->jump = check_jump;
+ t->cmp_branch = check_cmp_branch;
+ t->switch_ = check_switch;
+ t->indirect_branch = check_indirect_branch;
+ t->load_label_addr = check_load_label_addr;
+ t->local_static_data_begin = check_local_static_data_begin;
+ t->local_static_data_write = check_local_static_data_write;
+ t->local_static_data_label_addr = check_local_static_data_label_addr;
+ t->local_static_data_end = check_local_static_data_end;
+ t->scope_begin = check_scope_begin;
+ t->scope_else = check_scope_else;
+ t->scope_end = check_scope_end;
+ t->break_to = check_scope_xfer;
+ t->continue_to = check_scope_xfer;
+ t->load_imm = check_load_imm;
+ t->load_const = check_load_const;
+ t->copy = check_copy;
+ t->load = check_load_store;
+ t->store = check_load_store;
+ t->addr_of = check_addr_of;
+ t->tls_addr_of = check_tls_addr_of;
+ t->copy_bytes = check_aggregate;
+ t->set_bytes = check_aggregate;
+ t->bitfield_load = check_bitfield;
+ t->bitfield_store = check_bitfield;
+ t->binop = check_binop;
+ t->unop = check_unop;
+ t->cmp = check_cmp;
+ t->convert = check_convert;
+ t->call = check_call;
+ t->plan_call = check_plan_call;
+ t->load_call_arg = check_load_call_arg;
+ t->store_call_arg = check_store_call_arg;
+ t->store_call_ret = check_store_call_ret;
+ t->emit_call_plan = check_emit_call_plan;
+ t->ret = check_ret;
+ t->alloca_ = check_alloca;
+ t->va_start_ = check_one_operand;
+ t->va_arg_ = check_va_arg;
+ t->va_end_ = check_one_operand;
+ t->va_copy_ = check_two_operands;
+ t->intrinsic = check_intrinsic;
+ t->asm_block = check_asm_block;
+ t->atomic_load = check_atomic_load;
+ t->atomic_store = check_atomic_store;
+ t->atomic_rmw = check_atomic_rmw;
+ t->atomic_cas = check_atomic_cas;
+ t->fence = check_fence;
+ t->set_loc = check_set_loc;
+ t->finalize = check_finalize;
+ t->destroy = check_destroy;
+ return t;
+}
+
+const CGBackend cg_backend_check = {
+ .name = "check",
+ .make = check_backend_make,
+};
diff --git a/src/arch/registry.c b/src/arch/registry.c
@@ -30,6 +30,7 @@ extern const ArchImpl arch_impl_x64;
#if CFREE_ARCH_C_TARGET_ENABLED
extern const CGBackend cg_backend_c_target;
#endif
+extern const CGBackend cg_backend_check;
/* Arch-metadata roster. The arch_lookup_* helpers iterate this list when a
* caller needs machine-arch metadata — answers only come from backends that
@@ -66,6 +67,9 @@ const ArchImpl* arch_for_compiler(const Compiler* c) {
const CGBackend* cg_backend_for_session(const Compiler* c,
const CfreeCodeOptions* opts) {
+ if (opts && opts->check_only) {
+ return &cg_backend_check;
+ }
if (opts && opts->emit_c_source) {
#if CFREE_ARCH_C_TARGET_ENABLED
return &cg_backend_c_target;
diff --git a/src/cg/asm.c b/src/cg/asm.c
@@ -305,6 +305,7 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeCgInlineAsm asm_block) {
void cfree_cg_file_scope_asm(CfreeCg* g, CfreeSlice asm_source) {
AsmLexer* lex;
if (!g || !asm_source.s) return;
+ if (g->check_only) return;
/* The C-source backend bypasses MCEmitter entirely; file-scope asm has
* no portable C source equivalent (Phase 4 territory — see
* doc/CBACKEND.md). Panic with the same shape as other unimplemented
diff --git a/src/cg/internal.h b/src/cg/internal.h
@@ -157,6 +157,7 @@ struct CfreeCg {
u32 rodata_counter;
int opt_level;
+ u8 check_only;
ObjSecId data_sec;
ObjSymId data_sym;
diff --git a/src/cg/session.c b/src/cg/session.c
@@ -122,6 +122,7 @@ CfreeStatus cfree_cg_begin_obj(CfreeCg* g, CfreeObjBuilder* out,
g->mc = target->mc;
g->debug = target->debug;
g->opt_level = opt_level;
+ g->check_only = (opts && opts->check_only) ? 1u : 0u;
return CFREE_OK;
}
diff --git a/test/driver/run.sh b/test/driver/run.sh
@@ -551,6 +551,223 @@ else
"dbg-empty-toy-repl" "$host_os" "$host_arch"
fi
+rm -f "$work/check.o" "$work/a.out"
+if "$CFREE" check "$work/main.c" > "$work/check.out" 2> "$work/check.err"; then
+ if [ ! -e "$work/check.o" ] && [ ! -e "$work/a.out" ]; then
+ printf 'PASS %s\n' "check-no-output"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s (unexpected output file)\n' "check-no-output"
+ fail=$((fail + 1))
+ fi
+else
+ printf 'FAIL %s (cfree check failed)\n' "check-no-output"
+ sed 's/^/ | /' "$work/check.err"
+ fail=$((fail + 1))
+fi
+
+cat > "$work/check-bad.c" <<'SRC'
+int broken( { return 0; }
+SRC
+if "$CFREE" check "$work/check-bad.c" \
+ > "$work/check-bad.out" 2> "$work/check-bad.err"; then
+ printf 'FAIL %s (bad source passed)\n' "check-reports-errors"
+ fail=$((fail + 1))
+else
+ if grep -q "fatal:" "$work/check-bad.err"; then
+ printf 'PASS %s\n' "check-reports-errors"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s (missing diagnostic)\n' "check-reports-errors"
+ sed 's/^/ | /' "$work/check-bad.err"
+ fail=$((fail + 1))
+ fi
+fi
+
+# ---- nm ----
+# Compile a fresh object for the nm/size/addr2line tests.
+# Use aarch64-linux ELF so DWARF works for addr2line and symbols
+# have predictable names (no Mach-O underscore prefix).
+rm -f "$work/nm-main.o"
+nm_obj_ok=1
+if ! "$CFREE" cc -target aarch64-linux -c "$work/main.c" -o "$work/nm-main.o" \
+ > "$work/nm-cc.out" 2> "$work/nm-cc.err"; then
+ printf 'FAIL nm-setup (cfree cc -c failed)\n'
+ sed 's/^/ | /' "$work/nm-cc.err"
+ fail=$((fail + 1))
+ nm_obj_ok=0
+fi
+
+# basic: symbols in an object file
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" nm "$work/nm-main.o" > "$work/nm-basic.out" 2> "$work/nm-basic.err" &&
+ grep -q "main" "$work/nm-basic.out" &&
+ grep -q "_start" "$work/nm-basic.out"; then
+ printf 'PASS %s\n' "nm-basic"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "nm-basic"; sed 's/^/ | /' "$work/nm-basic.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "nm-basic"
+ fail=$((fail + 1))
+fi
+
+# nm -g: only global symbols.
+# Source with a local (static) function and a global one.
+cat > "$work/nm-global.c" <<'SRC'
+static int hidden(void) { return 1; }
+int visible(void) { return hidden(); }
+SRC
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" cc -target aarch64-linux -c "$work/nm-global.c" \
+ -o "$work/nm-global.o" > "$work/nm-global-cc.out" \
+ 2> "$work/nm-global-cc.err" &&
+ "$CFREE" nm "$work/nm-global.o" > "$work/nm-global-all.out" 2>/dev/null &&
+ grep -q "hidden" "$work/nm-global-all.out" &&
+ "$CFREE" nm -g "$work/nm-global.o" > "$work/nm-global-g.out" 2>/dev/null &&
+ grep -q "visible" "$work/nm-global-g.out" &&
+ ! grep -q "hidden" "$work/nm-global-g.out"; then
+ printf 'PASS %s\n' "nm-global-only"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && printf 'FAIL %s\n' "nm-global-only"
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "nm-global-only"
+ fail=$((fail + 1))
+fi
+
+# nm -u: undefined only (ELF objects may have no undefined)
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" nm -u "$work/nm-main.o" > "$work/nm-undef.out" 2>"$work/nm-undef.err"; then
+ printf 'PASS %s\n' "nm-undefined-only"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "nm-undefined-only"; sed 's/^/ | /' "$work/nm-undef.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "nm-undefined-only"
+ fail=$((fail + 1))
+fi
+
+# nm on an archive
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" ar rcs "$work/libnmtest.a" "$work/nm-main.o" \
+ > "$work/ar-nm.out" 2> "$work/ar-nm.err" &&
+ "$CFREE" nm "$work/libnmtest.a" > "$work/nm-archive.out" \
+ 2> "$work/nm-archive.err" &&
+ grep -q "main" "$work/nm-archive.out"; then
+ printf 'PASS %s\n' "nm-archive"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "nm-archive"; sed 's/^/ | /' "$work/nm-archive.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "nm-archive"
+ fail=$((fail + 1))
+fi
+
+# nm -h (help)
+if "$CFREE" nm --help > "$work/nm-help.out" 2> "$work/nm-help.err" &&
+ grep -q "USAGE" "$work/nm-help.out"; then
+ printf 'PASS %s\n' "nm-help"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s\n' "nm-help"
+ fail=$((fail + 1))
+fi
+
+# ---- size ----
+# basic: section sizes of an object file (use nm-main.o)
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" size "$work/nm-main.o" > "$work/size-basic.out" 2> "$work/size-basic.err" &&
+ grep -q "text" "$work/size-basic.out"; then
+ printf 'PASS %s\n' "size-basic"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "size-basic"; sed 's/^/ | /' "$work/size-basic.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "size-basic"
+ fail=$((fail + 1))
+fi
+
+# size -A: SysV format
+if [ "$nm_obj_ok" = 1 ] &&
+ "$CFREE" size -A "$work/nm-main.o" > "$work/size-sysv.out" 2> "$work/size-sysv.err" &&
+ grep -q "Total" "$work/size-sysv.out"; then
+ printf 'PASS %s\n' "size-sysv"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "size-sysv"; sed 's/^/ | /' "$work/size-sysv.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "size-sysv"
+ fail=$((fail + 1))
+fi
+
+# size on an archive (uses libnmtest.a from nm test)
+if [ "$nm_obj_ok" = 1 ] && [ -f "$work/libnmtest.a" ] &&
+ "$CFREE" size "$work/libnmtest.a" > "$work/size-archive.out" \
+ 2> "$work/size-archive.err" &&
+ grep -q "text" "$work/size-archive.out"; then
+ printf 'PASS %s\n' "size-archive"
+ pass=$((pass + 1))
+else
+ [ "$nm_obj_ok" = 1 ] && { printf 'FAIL %s\n' "size-archive"; sed 's/^/ | /' "$work/size-archive.err"; }
+ [ "$nm_obj_ok" != 1 ] && printf 'FAIL %s (setup)\n' "size-archive"
+ fail=$((fail + 1))
+fi
+
+# size -h (help)
+if "$CFREE" size --help > "$work/size-help.out" 2> "$work/size-help.err" &&
+ grep -q "USAGE" "$work/size-help.out"; then
+ printf 'PASS %s\n' "size-help"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s\n' "size-help"
+ fail=$((fail + 1))
+fi
+
+# ---- addr2line ----
+# Use ELF target so DWARF reader works (Mach-O debug sections not supported).
+cat > "$work/a2l.c" <<'SRC'
+int calc(int x) { return x * 2; }
+int main(void) { return calc(42); }
+SRC
+
+if [ "$nm_obj_ok" != 1 ]; then
+ printf 'FAIL addr2line-basic (setup)\n'; fail=$((fail + 1))
+ printf 'FAIL addr2line-nonzero (setup)\n'; fail=$((fail + 1))
+elif "$CFREE" cc -g -target aarch64-linux -c "$work/a2l.c" -o "$work/a2l.o" \
+ > "$work/a2l-cc.out" 2> "$work/a2l-cc.err"; then
+ calc_addr=$("$CFREE" nm "$work/a2l.o" 2>/dev/null | \
+ grep " calc$" | awk '{print $1}')
+ if [ -n "$calc_addr" ] &&
+ "$CFREE" addr2line -e "$work/a2l.o" "$calc_addr" \
+ > "$work/a2l-hit.out" 2> "$work/a2l-hit.err" &&
+ grep -q "a2l.c" "$work/a2l-hit.out"; then
+ printf 'PASS %s\n' "addr2line-basic"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s (calc_addr=%s)\n' "addr2line-basic" "$calc_addr"
+ sed 's/^/ | /' "$work/a2l-hit.err"
+ fail=$((fail + 1))
+ fi
+ # addr2line should produce output (not crash) on any address
+ if "$CFREE" addr2line -e "$work/a2l.o" 0x0 \
+ > "$work/a2l-miss.out" 2> "$work/a2l-miss.err" &&
+ [ -s "$work/a2l-miss.out" ]; then
+ printf 'PASS %s\n' "addr2line-nonzero"
+ pass=$((pass + 1))
+ else
+ printf 'FAIL %s\n' "addr2line-nonzero"
+ fail=$((fail + 1))
+ fi
+else
+ printf 'FAIL addr2line-basic (setup compile failed)\n'; fail=$((fail + 1))
+ printf 'FAIL addr2line-nonzero (setup)\n'; fail=$((fail + 1))
+fi
+
+# addr2line -h (help)
+if "$CFREE" addr2line --help > "$work/a2l-help.out" 2> "$work/a2l-help.err" &&
+ grep -q "USAGE" "$work/a2l-help.out"; then
+ printf 'PASS %s\n' "addr2line-help"
+ pass=$((pass + 1))
+else
+ printf 'FAIL %s\n' "addr2line-help"
+ fail=$((fail + 1))
+fi
+
total=$((pass + fail))
if [ "$fail" -gt 0 ]; then
printf '\ndriver: %d/%d passed\n' "$pass" "$total"