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:
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.