TODO: need to review snapshot

This commit is contained in:
2026-07-14 12:16:00 -04:00
parent 2d901003f9
commit 7d5b13aadb
14 changed files with 2461 additions and 124 deletions
+173 -8
View File
@@ -148,6 +148,153 @@ local function compute_word_entries(atom, src, wc)
return entries, pos
end
-- ════════════════════════════════════════════════════════════════════════════
-- Provenance emission (Phase 3 — debug_ux)
-- ════════════════════════════════════════════════════════════════════════════
-- Component-macro invocation prefix (mirrors components.lua's MAC_PREFIX).
local MAC_PREFIX = "mac_"
local MAC_PREFIX_LEN = 4
--- Strip the `mac_` prefix from a token's leading identifier. Returns nil
--- if the identifier doesn't start with `mac_` (so non-component tokens
--- like `load_half_u`, `nop2`, `gte_cmdw_*` fall through cleanly).
--- @param tok string
--- @return string|nil
local function strip_mac_prefix_from_token(tok)
local leading = duffle.read_ident(tok, 1)
if not leading then return nil end
if leading:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
return leading:sub(MAC_PREFIX_LEN + 1)
end
return nil
end
--- Compute per-word provenance entries for an atom. Mirrors `compute_word_entries`
--- but additionally classifies each emitted `.word` as either:
--- - `RAW` — emitted by a direct instruction token (no component provenance)
--- - `MACRO X` — emitted by a `mac_X(...)` component invocation, with the
--- component's definition file:line resolved from `ctx.shared.components`.
---
--- Returns a list of `{pos, line, text, comp_name, comp_line, comp_path}` entries + the
--- total word count. `comp_name` is nil for RAW rows.
--- @param atom table -- one entry of scan.atoms / scan.raw_atoms
--- @param src table -- SourceFile (has .scan with .line_of(), .path)
--- @param wc table -- shared.word_counts
--- @param comp table -- shared.components map: bare_name -> {name=, line=, path=, kind=}
--- @return table[], integer
local function compute_provenance_entries(atom, src, wc, comp)
local entries = {}
local pos = 0
for _, t in ipairs(atom.body_tokens) do
local tok = t.tok
local rel = t.rel
local words
if is_marker_token(tok) then
words = count_marker_rest(tok, wc)
else
words = count_token_words(tok, wc)
end
-- Resolve component provenance for this token (if any).
local comp_name = nil
local comp_line = nil
local comp_path = nil
local comp_kind = nil
local bare = strip_mac_prefix_from_token(tok)
if bare and comp and comp[bare] then
comp_name = bare
comp_line = comp[bare].line
comp_path = comp[bare].path
comp_kind = comp[bare].kind
end
if words > 0 then
local line = src.scan.line_of(atom.body_off + rel)
local text = duffle.trim(tok):gsub("[\t\r\n]+", " ")
for _ = 1, words do
entries[#entries + 1] = {
pos = pos,
line = line,
text = text,
comp_name = comp_name,
comp_line = comp_line,
comp_path = comp_path,
comp_kind = comp_kind,
}
pos = pos + 1
end
end
end
return entries, pos
end
--- Render one atom's provenance stanza. Format:
--- `WORD N CALL <src-path>:<src-line> MACRO <name> "<def-path>:<def-line>"` (for component words)
--- `WORD N CALL <src-path>:<src-line> RAW` (for direct instructions)
--- Returns (lines, total_words).
--- @param src table
--- @param atom table
--- @param wc table
--- @param comp table -- shared.components map
--- @return string[], integer
local function emit_provenance_stanza(src, atom, wc, comp)
local lines = {}
local rel_path = src.path:gsub("\\", "/")
local entries, total = compute_provenance_entries(atom, src, wc, comp)
-- ATOM header line with placeholder total (patched after we know it).
lines[#lines + 1] = string.format('ATOM %s "%s" 0', atom.raw_name or atom.name, rel_path)
for _, pe in ipairs(entries) do
if pe.comp_name then
lines[#lines + 1] = string.format(
'WORD %d CALL %s:%d MACRO %s "%s:%d"',
pe.pos, rel_path, pe.line, pe.comp_name, pe.comp_path, pe.comp_line)
else
lines[#lines + 1] = string.format(
"WORD %d CALL %s:%d RAW",
pe.pos, rel_path, pe.line)
end
end
-- Patch the placeholder total in the ATOM header line.
lines[1] = lines[1]:gsub(" 0$", " " .. tostring(total))
lines[#lines + 1] = "ENDATOM"
return lines, total
end
--- Render the full provenance file content for one source (one `.atoms.provenance.txt` per source).
--- @param src table
--- @param wc table
--- @param comp table -- shared.components map
--- @return string
local function render_provenance(src, wc, comp)
local lines = {}
lines[#lines + 1] = "# FORMAT_VERSION 1"
lines[#lines + 1] = "# auto-generated by ps1_meta.lua (passes/atoms_source_map.lua) — DO NOT EDIT"
lines[#lines + 1] = "# Per-.word provenance: maps each emitted .word to its call site (atom body"
lines[#lines + 1] = "# file:line) and, when the word was emitted by a `mac_X(...)` component invocation,"
lines[#lines + 1] = "# the component's definition file:line. Used by dwarf_injection (Phase 3) to synthesize"
lines[#lines + 1] = "# DW_TAG_inlined_subroutine instances for component step-into."
for _, atom in ipairs(src.scan.atoms or {}) do
local stanza = emit_provenance_stanza(src, atom, wc, comp)
for _, line in ipairs(stanza) do
lines[#lines + 1] = line
end
end
for _, atom in ipairs(src.scan.raw_atoms or {}) do
local stanza = emit_provenance_stanza(src, atom, wc, comp)
for _, line in ipairs(stanza) do
lines[#lines + 1] = line
end
end
return table.concat(lines, "\n") .. "\n"
end
--- Render one atom's stanza for the canonical text form
--- (ATOM header line, N WORD lines, ENDATOM marker). Returns (lines, total_words).
--- @param src table
@@ -533,6 +680,9 @@ local M = {}
--- Pass entry: emit one `<out_root>/<basename>.atoms.sourcemap.txt` per source file
--- that contains at least one `MipsAtom_(name)` / `MipsCode code_<name>` declaration.
--- Also emits `<out_root>/<basename>.atoms.provenance.txt` (Phase 3 — debug_ux):
--- per-.word provenance with `mac_X(...)` component resolution back to the
--- component's definition file:line.
--- Optionally also emit `<ctx.out_root>/gdb_tape_atoms_runtime.gdb` when
--- `ctx.flags.gdb_runtime` is true.
--- @param ctx PassCtx
@@ -552,6 +702,12 @@ function M.run(ctx)
}
end
-- Phase 3 (debug_ux): shared.components map is populated by `passes/components.lua`.
-- Used to attribute each emitted `.word` to either a component macro or the
-- enclosing atom body. If absent, all words fall through as RAW (correct
-- behavior — provenance is additive).
local comp = (ctx.shared and ctx.shared.components) or {}
-- Always emit the canonical text form (per-source).
for _, src in ipairs(ctx.sources) do
if src.scan then
@@ -559,18 +715,27 @@ function M.run(ctx)
local n_raw_atoms = src.scan.raw_atoms and #src.scan.raw_atoms or 0
if n_atoms + n_raw_atoms > 0 then
local basename = duffle.basename_no_ext(src.path)
-- Build report, NOT compile artifact: live in <out_root> alongside the other reports
-- (annotation.lua's *.errors.h, static_analysis.lua's *.static_analysis.txt, gdb_tape_atoms_runtime.gdb).
-- The per-source <source_dir>/gen/ is reserved for headers actually #included by C.
local out_path = ctx.out_root .. "/" .. basename .. ".atoms.sourcemap.txt"
local content = render_source_map(src, wc)
-- (1) atoms.sourcemap.txt — per-.word line map (unchanged contract).
local sourcemap_path = ctx.out_root .. "/" .. basename .. ".atoms.sourcemap.txt"
local sourcemap_body = render_source_map(src, wc)
-- (2) atoms.provenance.txt — Phase 3 (debug_ux): per-.word provenance
-- with `mac_X(...)` component resolution back to the component's
-- definition file:line. Consumed by `passes/dwarf_injection.lua`
-- to synthesize `DW_TAG_inlined_subroutine` instances for
-- source-level Step Into on component invocations.
local prov_path = ctx.out_root .. "/" .. basename .. ".atoms.provenance.txt"
local prov_body = render_provenance(src, wc, comp)
if not ctx.dry_run then
duffle.ensure_dir(duffle.dirname(out_path))
duffle.write_file_lf(out_path, content)
duffle.ensure_dir(duffle.dirname(sourcemap_path))
duffle.write_file_lf(sourcemap_path, sourcemap_body)
duffle.write_file_lf(prov_path, prov_body)
end
outputs[#outputs + 1] = { kind = "report", path = out_path }
outputs[#outputs + 1] = { kind = "report", path = sourcemap_path }
outputs[#outputs + 1] = { kind = "report", path = prov_path }
end
end
end