kit

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

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:
Adriver/addr2line.c | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/cc.c | 180++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mdriver/driver.h | 14+++++++++++++-
Mdriver/main.c | 30+++++++++++++++++++++++++++---
Adriver/nm.c | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adriver/size.c | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/cfree/asm_emit.h | 9+++++++++
Minclude/cfree/core.h | 8++++++++
Asrc/api/asm_emit.c | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/check_target.c | 576+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/registry.c | 4++++
Msrc/cg/asm.c | 1+
Msrc/cg/internal.h | 1+
Msrc/cg/session.c | 1+
Mtest/driver/run.sh | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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"