kit

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

commit ef28221b5e51646d11fd54686a23349b93007d02
parent df67c1c69c18c95b176eaf6dc0c256e3e1046428
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 27 May 2026 11:20:43 -0700

aa64: wire DWARF debug emission through the native_direct path

The semantic-CgTarget cutover dropped the -g wiring on the aa64 backend
(the only compiled native target), so cc -g produced no .debug_* sections:

- CgTarget lost its debug field and begin_obj hardcoded g->debug=NULL;
  restore the field and read it back from the backend-made target.
- aa64 make() discarded the Debug object; keep it on the target.
- aa_emit32 now records a line row per instruction (as x64/rv64 do).
- aa_func_end now reports the function PC range, so emit_section_line
  no longer skips every function.
- The producer seeds the CU's primary source file before emitting the
  root DIE, so DW_AT_name/comp_dir are populated.

addr2line now resolves cc -g objects; verified via test/driver addr2line
cases and CFREE_OPT_LEVELS=0 CFREE_TEST_PATHS=R toy run (254/0).

Diffstat:
Msrc/arch/aa64/arch.c | 2+-
Msrc/arch/aa64/native.c | 16++++++++++++++++
Msrc/cg/cgtarget.h | 10++++++++++
Msrc/cg/session.c | 6+++++-
Msrc/debug/debug_emit.c | 8++++++++
5 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/src/arch/aa64/arch.c b/src/arch/aa64/arch.c @@ -118,13 +118,13 @@ static CgTarget* aa64_backend_make(Compiler* c, ObjBuilder* o, NativeTarget* native; NativeDirectTargetConfig cfg; if (cg_mc_debug_new(c, o, opts, &mc, &debug) != CFREE_OK) return NULL; - (void)debug; native = aa64_native_target_new(c, o, mc); if (!native) return NULL; memset(&cfg, 0, sizeof cfg); cfg.native = native; cfg.ops = aa64_native_direct_ops(); t = native_direct_target_new(c, o, &cfg); + if (t) t->debug = debug; return t; } diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -129,10 +129,20 @@ static void aa_panic(AANativeTarget* a, const char* msg) { compiler_panic(a->base.c, a->loc, "aarch64 native target: %s", msg); } +/* Declared locally rather than pulling in debug/debug.h, keeping the + * backend's dependency on the Debug producer to this one entry point — + * same pattern as the x64/rv64 emit TUs (see arch/mc.h). */ +extern void debug_emit_row(Debug*, ObjSecId text_section, u32 offset, SrcLoc); +extern void debug_func_pc_range(Debug*, ObjSecId text_section, u32 begin_ofs, + u32 end_ofs); + static void aa_emit32(MCEmitter* mc, u32 word) { u8 b[4]; + u32 ofs = obj_pos(mc->obj, mc->section_id); wr_u32_le(b, word); mc->emit_bytes(mc, b, sizeof b); + /* Record one line-table row per instruction start (no-op when not -g). */ + if (mc->debug) debug_emit_row(mc->debug, mc->section_id, ofs, mc->loc); } static void aa_patch32(ObjBuilder* obj, ObjSecId sec, u32 off, u32 word) { @@ -1054,6 +1064,12 @@ static void aa_func_end(NativeTarget* t) { obj_atom_define(t->obj, a->func->text_section_id, a->func_start, mc->pos(mc) - a->func_start, a->func->sym, 0); } + /* Hand the function's PC range to the Debug producer so its line program + * (and DW_AT_low_pc/high_pc) cover this function — emit_section_line skips + * functions without a recorded range. */ + if (mc->debug) + debug_func_pc_range(mc->debug, a->func->text_section_id, a->func_start, + mc->pos(mc)); if (mc->cfi_endproc) mc->cfi_endproc(mc); mc_end_function(mc); a->func = NULL; diff --git a/src/cg/cgtarget.h b/src/cg/cgtarget.h @@ -380,12 +380,22 @@ typedef struct CGLocalStaticDataDesc { u32 align; } CGLocalStaticDataDesc; +/* Forward-declared (same as arch/mc.h) so a CgTarget can carry an optional + * Debug producer without this header depending on debug/debug.h. */ +typedef struct Debug Debug; + typedef struct CgTarget CgTarget; struct CgTarget { /* Typed IR lowering context. Subclasses extend. */ Compiler* c; ObjBuilder* obj; + /* Optional DWARF producer, created by the backend's `make` when + * opts->debug_info is set (else NULL). The session reads this back into + * its own g->debug to drive func/line/emit; the backend's MCEmitter + * shares the same object for line-row emission. */ + Debug* debug; + /* ---- function lifecycle ---- */ void (*func_begin)(CgTarget*, const CGFuncDesc*); void (*func_end)(CgTarget*); diff --git a/src/cg/session.c b/src/cg/session.c @@ -106,6 +106,11 @@ CfreeStatus cfree_cg_begin_obj(CfreeCg* g, CfreeObjBuilder* out, if (!backend) return CFREE_UNSUPPORTED; target = backend->make((Compiler*)c, (ObjBuilder*)out, opts); if (!target) return CFREE_UNSUPPORTED; + /* The backend's make() creates the DWARF producer when opts->debug_info + * is set (NULL otherwise); capture it before any optimizer wrapper so the + * session can drive func/line/emit. The backend's MCEmitter holds the + * same Debug for line-row emission. */ + g->debug = target->debug; #if CFREE_OPT_ENABLED if (opt_level > 0) { target = opt_cgtarget_new((Compiler*)c, target, opt_level); @@ -114,7 +119,6 @@ CfreeStatus cfree_cg_begin_obj(CfreeCg* g, CfreeObjBuilder* out, #endif g->obj = (ObjBuilder*)out; g->target = target; - g->debug = NULL; g->opt_level = opt_level; g->check_only = (opts && opts->check_only) ? 1u : 0u; g->function_sections = (opts && opts->function_sections) ? 1u : 0u; diff --git a/src/debug/debug_emit.c b/src/debug/debug_emit.c @@ -1290,6 +1290,14 @@ void debug_emit(Debug* d) { ec.ssym_str_off = mk_section_sym(&ec, ec.sec_str_off); producer_sym = pool_intern_slice(pool, SLICE_LIT("cfree 0.1")); + /* Ensure the CU's primary source file occupies file-table slot 0 before + * we read it for DW_AT_name/comp_dir. debug_file() is otherwise first + * invoked later (child-DIE decl_file / line program), so without this the + * CU name and comp_dir come out empty. Seed it from the first function's + * declaration site. */ + if (d->nfiles == 0 && d->nfuncs > 0) { + (void)debug_file(d, d->funcs[0].decl.file_id); + } if (d->nfiles > 0) { primary_dir = d->files[0].dir; primary_base = d->files[0].base;