kit

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

commit b8876feffff2156a0e73be9e0b48281bf496a370
parent 48f724456723e62594cc35cef5b5a9bab33c61ba
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 21:30:43 -0700

Emit Toy local debug info from CG

Diffstat:
Mdoc/DBG_TODO.md | 12++++++++----
Msrc/arch/aa64/native.c | 19+++++++++++++++++++
Msrc/arch/native_target.h | 4++++
Msrc/cg/cgtarget.h | 26++++++++++++++++++++++++++
Msrc/cg/local.c | 6+++++-
Msrc/cg/native_direct_target.c | 12++++++++++++
Msrc/cg/session.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/dbg/cases/toy-file-line-step/expected | 4++--
Mtest/dbg/cases/toy-local-inspection/expected | 12++++++------
Dtest/dbg/cases/toy-local-inspection/xfail | 1-
10 files changed, 135 insertions(+), 14 deletions(-)

diff --git a/doc/DBG_TODO.md b/doc/DBG_TODO.md @@ -16,6 +16,12 @@ experience is solid. source-level resume modes. - Toy supports REPL top-level snippets and expression/block thunks. Persistent Toy declarations already work across `jit` and `expr` commands. +- CG now owns debug-info emission for named source locals and parameters: + frontends pass normal CG local/param metadata, CG emits the variable DIEs at + function finalization, and targets provide finalized storage locations through + `local_debug_loc` / native frame-slot debug-location hooks. This keeps Toy out + of the debug producer API and leaves architecture-specific frame math behind + target interfaces. - C can append normal translation-unit snippets, but raw C expressions/blocks are not implemented as REPL thunks yet. @@ -69,9 +75,7 @@ experience is solid. - [ ] `sl`, `finish` - [x] `bt` - [x] `info reg` - - [ ] `p`, `set`, `info locals`, `info args` - (`toy-local-inspection` red transcript is in place; Toy needs - usable local/argument DWARF locations) + - [x] `p`, `set`, `info locals`, `info args` - [x] `x ADDR [count]` - [ ] Ctrl-C/interrupt behavior where the host can test it reliably - [ ] Improve Toy REPL output: @@ -88,7 +92,7 @@ experience is solid. - [ ] Improve Toy source/debug info: - [ ] stable synthetic file names for REPL snippets - [ ] useful `list` output for synthetic snippets - - [ ] local/argument names and values at expected source stops + - [x] local/argument names and values at expected source stops ## Toy Transactional Frontend State diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -1566,6 +1566,24 @@ static NativeFrameSlot aa_frame_slot(NativeTarget* t, return a->nslots; } +static int aa_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot, + CGDebugLoc* out) { + AANativeTarget* a = aa_of(t); + AANativeSlot* s; + i32 fp_off; + if (!out) return 0; + memset(out, 0, sizeof *out); + if (slot == NATIVE_FRAME_SLOT_NONE || slot > a->nslots) return 0; + s = aa_slot(a, slot); + fp_off = aa_fp_off_slot(a, s->off); + out->kind = CG_DEBUG_LOC_FRAME; + /* The hosted dbg stop snapshot currently carries x29/fp as the frame base + * for variable materialization, so report the same FP-relative slot offset + * used by native memory operands. */ + out->v.frame_ofs = fp_off; + return 1; +} + /* Optimizer entry point: the full frame is supplied up front, so the prologue, * entry saves, slim-form eligibility, allocas, and tail epilogues are all final * the moment they are emitted — no back-patching (aa_func_end skips the patch @@ -3542,6 +3560,7 @@ NativeTarget* aa64_native_target_new(Compiler* c, ObjBuilder* obj, t->store_zero_reg = 31u; /* wzr/xzr in the Rt position of a store */ t->func_end = aa_func_end; t->frame_slot = aa_frame_slot; + t->frame_slot_debug_loc = aa_frame_slot_debug_loc; t->bind_param = aa_bind_native_param; t->label_new = aa_label_new; t->label_place = aa_label_place; diff --git a/src/arch/native_target.h b/src/arch/native_target.h @@ -330,6 +330,10 @@ struct NativeTarget { void (*func_end)(NativeTarget*); NativeFrameSlot (*frame_slot)(NativeTarget*, const NativeFrameSlotDesc*); + /* Optional post-finalization query for a native frame slot's debug location. + * Each arch owns the frame layout math and returns the coordinate system its + * debugger/unwinder path can materialize. */ + int (*frame_slot_debug_loc)(NativeTarget*, NativeFrameSlot, CGDebugLoc*); /* Place the incoming parameter into `dst`. The caller (which has run register * allocation) chooses the destination: a hard register (NATIVE_LOC_REG) for a * register-allocated scalar param, a frame slot (NATIVE_LOC_FRAME) for an diff --git a/src/cg/cgtarget.h b/src/cg/cgtarget.h @@ -380,6 +380,27 @@ typedef struct CGLocalStaticDataDesc { u32 align; } CGLocalStaticDataDesc; +typedef enum CGDebugLocKind { + CG_DEBUG_LOC_NONE, + CG_DEBUG_LOC_FRAME, + CG_DEBUG_LOC_REG, + CG_DEBUG_LOC_GLOBAL, +} CGDebugLocKind; + +typedef struct CGDebugLoc { + u8 kind; /* CGDebugLocKind */ + u8 pad[3]; + union { + /* Offset in the same target-defined frame-base coordinate system that the + * target/debugger pair uses to materialize frame-relative variables. CG + * treats this as opaque target data and only maps it into the debug + * producer's generic frame-location form. */ + i32 frame_ofs; + u32 reg; + ObjSymId global; + } v; +} CGDebugLoc; + /* 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; @@ -417,6 +438,11 @@ struct CgTarget { CGLocal (*local)(CgTarget*, const CGLocalDesc*); void (*local_addr)(CgTarget*, Operand dst, const CGLocalDesc*, CGLocal); CGLocal (*param)(CgTarget*, const CGParamDesc*); + /* Optional debug-info query after function frame layout is finalized. + * Targets return a target-authored location for semantic local storage; CG + * owns deciding which source locals/params get emitted and translating the + * target-neutral CGDebugLoc into the debug producer API. */ + int (*local_debug_loc)(CgTarget*, CGLocal, CGDebugLoc*); /* ---- labels and control flow ---- */ Label (*label_new)(CgTarget*); diff --git a/src/cg/local.c b/src/cg/local.c @@ -2,7 +2,11 @@ int api_local_requires_memory(CfreeCg* g, CfreeCgTypeId ty, CfreeCgLocalAttrs attrs) { - (void)attrs; + u32 hidden_flags = CFREE_CG_LOCAL_ARTIFICIAL | + CFREE_CG_LOCAL_OPTIMIZED_OUT | + CFREE_CG_LOCAL_COMPILER_TEMP; + if (g && g->debug && attrs.name && (attrs.flags & hidden_flags) == 0) + return 1; /* Aggregates (records, arrays), wide16 (f128/i128), vararg state, and any * non-scalar type must live in memory. */ if (api_is_wide16_scalar_type(g->c, ty)) return 1; diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -977,6 +977,17 @@ static CGLocal nd_param(CgTarget* t, const CGParamDesc* desc) { return id; } +static int nd_local_debug_loc(CgTarget* t, CGLocal local, CGDebugLoc* out) { + NativeDirectTarget* d = nd_of(t); + NativeDirectLocal* l; + if (!out) return 0; + memset(out, 0, sizeof *out); + if (!d->native || !d->native->frame_slot_debug_loc) return 0; + l = nd_local(d, local); + if (l->home == NATIVE_FRAME_SLOT_NONE) return 0; + return d->native->frame_slot_debug_loc(d->native, l->home, out); +} + static Label nd_label_new(CgTarget* t) { return nd_label_new_raw(nd_of(t)); } static void nd_label_place(CgTarget* t, Label label) { @@ -1764,6 +1775,7 @@ CgTarget* native_direct_target_new(Compiler* c, ObjBuilder* obj, d->base.local = nd_local_new; d->base.local_addr = nd_local_addr; d->base.param = nd_param; + d->base.local_debug_loc = nd_local_debug_loc; d->base.label_new = nd_label_new; d->base.label_place = nd_label_place; d->base.jump = nd_jump; diff --git a/src/cg/session.c b/src/cg/session.c @@ -333,10 +333,63 @@ void cfree_cg_func_begin(CfreeCg* g, CfreeCgSym cg_sym) { cfree_cg_func_begin_attrs(g, cg_sym, attrs); } +static int api_source_local_debug_visible(const ApiSourceLocal* rec) { + u32 hidden_flags = CFREE_CG_LOCAL_ARTIFICIAL | + CFREE_CG_LOCAL_OPTIMIZED_OUT | + CFREE_CG_LOCAL_COMPILER_TEMP; + return rec && rec->name && (rec->attrs.flags & hidden_flags) == 0; +} + +static int api_debug_var_loc_from_cg(CGDebugLoc in, DebugVarLoc* out) { + if (!out) return 0; + memset(out, 0, sizeof *out); + switch ((CGDebugLocKind)in.kind) { + case CG_DEBUG_LOC_FRAME: + out->kind = DVL_FRAME; + out->v.frame_ofs = in.v.frame_ofs; + return 1; + case CG_DEBUG_LOC_REG: + out->kind = DVL_REG; + out->v.reg = in.v.reg; + return 1; + case CG_DEBUG_LOC_GLOBAL: + out->kind = DVL_GLOBAL; + out->v.global = in.v.global; + return 1; + default: + return 0; + } +} + +static void api_debug_emit_source_locals(CfreeCg* g) { + u32 i; + if (!g || !g->debug || !g->target || !g->target->local_debug_loc) return; + for (i = 0; i < g->nlocals; ++i) { + ApiSourceLocal* rec = &g->locals[i]; + CGDebugLoc cg_loc; + DebugVarLoc dbg_loc; + DebugTypeId dbg_type; + if (!api_source_local_debug_visible(rec)) continue; + memset(&cg_loc, 0, sizeof cg_loc); + if (!g->target->local_debug_loc(g->target, rec->storage, &cg_loc)) + continue; + if (!api_debug_var_loc_from_cg(cg_loc, &dbg_loc)) continue; + dbg_type = api_debug_type(g, rec->type); + if (dbg_type == DEBUG_TYPE_NONE) continue; + if (rec->kind == API_SOURCE_LOCAL_PARAM) { + debug_param(g->debug, (Sym)rec->name, dbg_type, rec->loc, + rec->param_index, dbg_loc); + } else { + debug_local(g->debug, (Sym)rec->name, dbg_type, rec->loc, dbg_loc); + } + } +} + void cfree_cg_func_end(CfreeCg* g) { if (!g) return; api_temp_locals_finish(g); g->target->func_end(g->target); + api_debug_emit_source_locals(g); if (g->debug) debug_func_end(g->debug); g->fn_ret_type = CFREE_CG_TYPE_NONE; g->fn_result_types[0] = CFREE_CG_TYPE_NONE; diff --git a/test/dbg/cases/toy-file-line-step/expected b/test/dbg/cases/toy-file-line-step/expected @@ -1,6 +1,6 @@ cfree dbg — 'h' for help, 'q' to quit Breakpoint 1 at 0xADDR (@CASE@/main.toy:2) Breakpoint 1 (@CASE@/main.toy:2) hit at 0xADDR <main+0x60> at @CASE@/main.toy:2:16 -Breakpoint hit at 0xADDR <main+0x64> at @CASE@/main.toy:3:20 -Breakpoint hit at 0xADDR <main+0x68> at @CASE@/main.toy:4:10 +Breakpoint hit at 0xADDR <main+0x64> at @CASE@/main.toy:2:16 +Breakpoint hit at 0xADDR <main+0x68> at @CASE@/main.toy:3:20 Program exited with code 42 diff --git a/test/dbg/cases/toy-local-inspection/expected b/test/dbg/cases/toy-local-inspection/expected @@ -1,9 +1,9 @@ cfree dbg — 'h' for help, 'q' to quit Breakpoint 1 at 0xADDR (@CASE@/main.toy:3) -Breakpoint 1 (@CASE@/main.toy:3) hit at 0xADDR <helper+0x64> at @CASE@/main.toy:3:10 - v = 40 (0x28) - n = 42 (0x2a) -v = 40 (0x28) -n = 42 (0x2a) -n = 7 (0x7) +Breakpoint 1 (@CASE@/main.toy:3) hit at 0xADDR <helper+0x78> at @CASE@/main.toy:3:10 + v = 40 + n = 42 +v = 40 +n = 42 +n = 7 Program exited with code 7 diff --git a/test/dbg/cases/toy-local-inspection/xfail b/test/dbg/cases/toy-local-inspection/xfail @@ -1 +0,0 @@ -Toy does not yet emit usable local/argument DWARF locations for dbg p/set/info.