Skip to content

feat(#45): debug info and unwind, locations, DWARF 5, line and frame records - #355

Merged
octalide merged 6 commits into
devfrom
feat/45
Oct 17, 2026
Merged

octalide merged 6 commits into
devfrom
feat/45

Conversation

@octalide

@octalide octalide commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #45

What

J9: variable locations carried through selection, allocation and frames, composite bindings, a DWARF 5 producer, and line and frame facts handed to masc. Absorbs #46, #184, #197 and #200. It carries on from #249, which held only an empty start commit and is superseded by this PR.

Plan

  • The variable location is now mirl.debug.Where, named apart from ir.debug.Location.
  • Composite bindings: an IR record can give a piece of bits (bind v piece <offset> <size> = ...). Records at one point compose, the verifier refuses overlapping pieces, and body.split turns one record into one record per part. Wide integer legalization uses it, so an i128 on RV64 keeps two pieces.
  • Machine value binding records: selection places them at the first instruction of each root. Allocation maps a register to its location or its spill slot. Frame, copies, configure and loop expansion carry them to where their instructions went. A storage binding becomes a slot record at the entry.
  • mirl.machine.range: a forward dataflow over the framed function. It finds where each variable is per block, with pieces composed. A register write or a clobbering call ends a range.
  • Handover:
    • Frame facts go to masc through cfi_start, cfi and cfi_end, in order at their instructions.
    • Every change of source location goes to masc's line records, in code order. An instruction with no location is line 0 without the statement flag.
    • Labels are placed at range ends and at the edges of inlined runs.
  • mirl.debug.dwarf: writes .debug_info, abbrev, str, str_offsets, addr, loclists and rnglists through the builder.
    • Sizes and member offsets come from the layout routine.
    • A tagged union is written as a DW_TAG_variant_part.
    • Each inlined site is a DW_TAG_inlined_subroutine of an abstract origin.
    • A variable with no location is optimised out.
    • Locations are admitted through the DWARF producer row.
  • No .debug_line is written here. mink lowers it.

Choices

  • A composite binding is a run of piece records at one point, not a new record kind.
  • Ranges are computed per block before layout, on the framed function, and placed by labels in the handover.
  • The allocator never splits an interval. So a "spilled mid range" value is one whose whole home is its spill slot. Its range is the slot, and the register it is reloaded into is not a second range. Two ranges arise wherever a record or a write ends one location and another record begins.
  • The unit's language is DW_LANG_C11, since the IR names no source language.

Moved to #342

  • llvm-dwarfdump --verify over the examples
  • the i128 split on RV64 keeping two pieces
  • the composite binding round trip and its verifier refusals
  • a tagged union written as a variant part
  • type sizes matching the layout routine
  • frame fact directives at their instructions
  • two-line location runs in code order
  • a variable spilled mid range getting two ranges
  • an inlined call recording its site
  • a value with no location shown as optimised out

Limits

  • At the pinned mink, every writer refuses an object with frame records (mink#201). So no object with a framed function is written yet, and nothing here is linked or run. PE and Mach-O images are gated by mink#70 and mink#73.
  • DW_AT_stmt_list is offset 0 with no relocation. That holds for one unit per object.
  • These are not stated, so the variable reads as optimised out there:
    • a slot whose offset scales
    • salvage steps over a slot
    • a slot of a realigned frame or the outgoing area
    • a constant wider than a word
  • The variables of an inlined body are scoped at the subprogram, not the inlined subroutine.
  • A record at an instruction a stage dropped moves to the next instruction.
  • The listing is still refused for an object with line facts.
  • Mach-O debug sections reference each other by section-relative datums, which mink must lower for that format.

Ran

mach fmt ., a clean mach check . -a '*' and mach build . -a cli. No new tests were added, per the v7 process.

Checklist

  • Linked to an issue
  • Conventional Commit messages
  • Build and tests pass

The tag a producer reads for a variable's place is mirl.debug.Where, apart
from the ir's source location mirl.ir.debug.Location, and a piece carries
the salvage steps over what its location holds.
A value binding record gives a piece of its variable, a bit offset and
size, or the whole. The records of one variable at one point, each of a
piece, are its composite binding, so every edit of a record works on one
piece and leaves the others. The text form writes a piece as
`bind v0 piece 64 64 = %x`, the verifier refuses pieces that share a bit
or reach past a base type's width, and body.split turns one record into
several pieces at its point. Wide splitting binds every word of a split
value, so a split alone leaves no record unavailable.
Selection gives each ir record a machine record before the first machine
instruction made for the instruction it stood before, reading the value's
virtual register or the constant. Allocation reads each register's home,
the location it holds over its life or the slot a value in pieces lives
in, and every later stage that writes a function anew carries the records
to the instruction their own now begins at.
writes .debug_info, abbrev, str, str_offsets, addr, loclists and
rnglists through masc's builder. types take their sizes and member
offsets from the layout routine, a tagged union is a variant part,
inlined code is an inlined subroutine of an abstract origin, and a
variable's location list has an entry per range it is somewhere.
ranges are computed over the framed function from its value binding
records, storage bindings become slot records at the entry, and the
handover places labels at range ends and inline runs, hands each frame
fact to masc at its instruction, states each change of source location
to masc's line records, and writes the dwarf sections before finishing.
@octalide
octalide marked this pull request as ready for review October 17, 2026 00:52
@octalide
octalide enabled auto-merge October 17, 2026 00:53
@octalide
octalide added this pull request to the merge queue Oct 17, 2026
Merged via the queue into dev with commit 82b5fc2 Oct 17, 2026
2 checks passed
@octalide
octalide deleted the feat/45 branch October 17, 2026 01:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mirl-J9: Debug info and unwind, locations, DWARF 5, line and frame records

1 participant