# Handoff — dropping the registry notes, and debug information for `--x86` Branch `dev-loop`, worktree `agent-a123a91f32f4c9779`, from `957ba07`. Items **2** and **6** of `HANDOFF-x86-rt.md` §6. ## The finding that changes the brief, read this first **`.loc` does not work in this backend's output, and no amount of care makes it work.** The brief called line tables "by far the least work, since `as` will build `.debug_line` for you from `.loc` directives". That is true of a backend that emits mnemonics. This one emits `.byte` blobs — the header says so and says why — and GAS builds its line table from `dwarf2_emit_insn`, which only runs out of `md_assemble`. No instruction is ever assembled, so nothing flushes the pending `.loc`. What GAS does instead is flush the pending `.loc` when it sees the *next* `.loc`, at whatever the location counter has reached by then. Measured on `as` 2.44: ``` .file 1 "foo.flan" main: .loc 1 10 1 .byte 0x55 .byte 0x48,0x89,0xe5 .loc 1 11 1 .byte 0xb8,0x07,0x00,0x00,0x00 .loc 1 12 1 .byte 0xc9 .byte 0xc3 ``` ``` foo.flan 10 0x4 x <- should be 0x0 foo.flan 11 0x9 x <- should be 0x4 foo.flan - 0xb <- line 12 has no row at all ``` Every row is one statement late and the last statement in the function gets none. Interposing labels between the `.loc` and the `.byte` does not help; it was tested. Do **not** try to correct this by shifting the `.loc` directives back by one statement — the last-statement hole is a correctness gap, not just fragility, and the behaviour being exploited is undocumented. So the line table is written out by hand here, as raw `.byte` / `.uleb128` / `.quad` in `.debug_line`, which is the same thing this backend already does for instructions. Scope grew accordingly; see the plan below. ## Plan, in the order it is being done 1. **Item 2 — `flan_dev_reg_note` dropped in a release build.** Done; see below. 2. A hand-written `.debug_line`, plus the minimal `.debug_info` / `.debug_abbrev` CU that makes a debugger *find* it. These are one step and not two: `readelf` will read a bare `.debug_line`, but gdb reaches a line table only by walking `.debug_info` and following `DW_AT_stmt_list`. 3. `DW_TAG_subprogram` DIEs, so `break` and `list` scope by function. Note that gdb already names frames from the ELF symbol table, which this backend emits via `.globl`, so this buys less than it looks like. 4. `.cfi` — only if it can be placed correctly against `.byte` output. If it cannot, that is said plainly and gdb's prologue analyser is left to recognise `push rbp; mov rbp,rsp; sub rsp,N`, which is canonical. 5. Locals and types, only if everything above is solid and committed. ## Item 2 — done `lib/x86.ml`, `prim`'s `Tast.Rt` dispatch, one arm above the general one, mirroring `lib/emit.ml:1918` test for test including the strict `> 17`: bare `flan_dev_reg_note` is the runtime's own C entry point and is never a `Tast.Rt`; what `check.ml` builds is the `_vec`, `_map` and `_pool` wrappers. The node's type is `Unit`, so the body is `()` and `dst` is untouched. `emit.ml` is careful to drop the note *before* the arguments are walked, because emitting the address of the container only to discard the call would leave an escaped alloca that mem2reg refuses. Here the arguments are not touched until `call_rt`, so the guard is already early enough. Measured by disassembly, `objdump -d | grep -c 'call.*flan_dev_reg_note'`: | program | `--x86` release | `--x86 --dev` | LLVM release | |---|---|---|---| | `vec.flan` | 46 → **1** | 46 | 1 | | `maps.flan` | 55 → **1** | 55 | — | | `registry.flan` | 36 → **1** | 36 | — | The one that remains in every release build is not emitted code: it is inside the runtime's own `flan_dev_reg_note_vec` in `flan_rt.c`, which calls `flan_dev_reg_note`. LLVM's release build has exactly the same one, so the two backends now agree. ## Baseline Measured on this tree, not recalled. (Filled in below as it is measured.) ## Open questions (none yet)