mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-13 12:51:26 -07:00
exploring gdb support for the atom asm dsl.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# scripts/gdb/gdb_tape_atoms.gdb
|
||||
#
|
||||
# Wrapper for the tape-atom step-debug helpers. The 9 user commands are defined
|
||||
# here as STUBS (degraded-state messages). The real implementations + the
|
||||
# per-atom data tables are emitted by `passes/atoms_source_map.lua` (post-link
|
||||
# invocation: `ps1_meta.lua --atoms-source-map --gdb-runtime --elf <elf>`) into
|
||||
# `build/gen/gdb_tape_atoms_runtime.gdb`. Sourcing that file RE-DEFINES the
|
||||
# commands with real implementations.
|
||||
#
|
||||
# If `build/gen/gdb_tape_atoms_runtime.gdb` is missing or stale, the stubs
|
||||
# remain (E1: no source map). The user just needs to re-run `build_psyq.ps1`
|
||||
# to regenerate. No exceptions; no crashes.
|
||||
#
|
||||
# Why a wrapper + separate runtime file?
|
||||
# - The runtime file is auto-generated per-build; not in git.
|
||||
# - The wrapper is checked into git; always works.
|
||||
# - This split keeps the script trivial and the data plumbing out of git.
|
||||
#
|
||||
# Compatible with every gdb build (no Python, no Tcl, no Guile required) —
|
||||
# pure gdb command scripting + `set $var = val` + `define ... end`.
|
||||
#
|
||||
# Generated by track gdb_tape_atom_debugging_20260711 — see
|
||||
# C:\projects\Pikuma\ps1-ai\docs\gdb_tape_atom_debugging.md for the manual.
|
||||
|
||||
# ── Stub commands (defined here so they're always present, even if the
|
||||
# runtime file is missing). The runtime file overrides these if sourced. ──
|
||||
|
||||
define tape_atoms
|
||||
echo "[gdb_tape_atoms] STUB: runtime file build/gen/gdb_tape_atoms_runtime.gdb not found."
|
||||
echo "[gdb_tape_atoms] STUB: run .\\build_psyq.ps1 to regenerate, then re-source this file."
|
||||
end
|
||||
document tape_atoms
|
||||
List every tape atom symbol in the loaded ELF (code_<name>) with its .rodata address and word count.
|
||||
STUB state: runtime file not sourced. Run build_psyq.ps1 to regenerate.
|
||||
end
|
||||
|
||||
define break_atom
|
||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||
end
|
||||
document break_atom
|
||||
Set a breakpoint at the start of tape atom <name>. STUB state.
|
||||
end
|
||||
|
||||
define step_atom
|
||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||
end
|
||||
document step_atom
|
||||
Resume execution until the next atom boundary. STUB state.
|
||||
end
|
||||
|
||||
define next_atom
|
||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||
end
|
||||
document next_atom
|
||||
Alias for step_atom. STUB state.
|
||||
end
|
||||
|
||||
define where_in_atom
|
||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||
end
|
||||
document where_in_atom
|
||||
Report current atom name, .rodata addr, word offset, and source line (if known). STUB state.
|
||||
end
|
||||
|
||||
define stepi_inside_atom
|
||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||
end
|
||||
document stepi_inside_atom
|
||||
One MIPS-instruction step, then where_in_atom. STUB state.
|
||||
end
|
||||
|
||||
define show_c2
|
||||
printf "C2[ 0] 0x%08x\n", $c2_data[0]
|
||||
printf "C2[ 7] 0x%08x [otz]\n", $c2_data[7]
|
||||
printf "C2[12] 0x%08x [sxy0]\n", $c2_data[12]
|
||||
printf "C2[13] 0x%08x [sxy1]\n", $c2_data[13]
|
||||
printf "C2[14] 0x%08x [sxy2]\n", $c2_data[14]
|
||||
printf "C2[24] 0x%08x [mac0]\n", $c2_data[24]
|
||||
printf "...\n"
|
||||
echo "(STUB state: only 7 representative regs shown. Run build_psyq.ps1 for full dump.)"
|
||||
end
|
||||
document show_c2
|
||||
Pretty-print all 32 C2 data registers as hex + named alias. STUB state (7 reg subset).
|
||||
end
|
||||
|
||||
define show_c2ctl
|
||||
printf "C2CTL[ 0] 0x%08x\n", $c2_control[0]
|
||||
printf "...\n"
|
||||
echo "(STUB state: only 1 reg shown. Run build_psyq.ps1 for full dump.)"
|
||||
end
|
||||
document show_c2ctl
|
||||
Pretty-print all 32 C2 control registers. STUB state (1 reg subset).
|
||||
end
|
||||
|
||||
define wave_ctx
|
||||
printf "$t4 = R_FaceCursor 0x%08x\n", $t4
|
||||
printf "$t5 = R_VertBase 0x%08x\n", $t5
|
||||
printf "$t6 = R_OtBase 0x%08x\n", $t6
|
||||
printf "$t7 = R_PrimCursor 0x%08x\n", $t7
|
||||
end
|
||||
document wave_ctx
|
||||
Pretty-print the 4 wave-context GPRs ($t4..$t7). (wave_ctx works in stub state too.)
|
||||
end
|
||||
|
||||
|
||||
# ── Source the runtime file (re-defines commands with real impls + data). ──
|
||||
|
||||
# Try to source from project-root-relative path first (the typical case).
|
||||
# If the user is in a different CWD, the source will fail and stubs remain.
|
||||
# The runtime file path is computed relative to the ELF's source map convention
|
||||
# (build/gen/gdb_tape_atoms_runtime.gdb).
|
||||
echo [gdb_tape_atoms] Wrapper loaded. Sourcing runtime file...
|
||||
# Suppress the "Redefine command" prompts that would otherwise appear when the
|
||||
# runtime file overrides the 9 stub commands defined above. The runtime's
|
||||
# `define` blocks are intended to overwrite — there's no ambiguity to confirm.
|
||||
set confirm off
|
||||
source build/gen/gdb_tape_atoms_runtime.gdb
|
||||
set confirm on
|
||||
echo [gdb_tape_atoms] Runtime sourced successfully (9 commands now have real implementations).
|
||||
@@ -0,0 +1,95 @@
|
||||
# scripts/launch_debug_session.ps1
|
||||
#
|
||||
# Wrapper around pcsx-redux.exe + gdb-multiarch that auto-launches the
|
||||
# gdb_tape_atoms.gdb helper and (optionally) hot-reloads the .ps-exe.
|
||||
#
|
||||
# Generated by track gdb_tape_atom_debugging_20260711 — see
|
||||
# C:\projects\Pikuma\ps1-ai\docs\gdb_tape_atom_debugging.md for the manual.
|
||||
#
|
||||
# usage:
|
||||
# .\launch_debug_session.ps1 # bare gdb attach (assumes pcsx-redux is up)
|
||||
# .\launch_debug_session.ps1 -AutoLoadAtoms # also source gdb_tape_atoms.gdb on startup
|
||||
# .\launch_debug_session.ps1 -AutoResetEmu # hot-reload the .ps-exe before attaching
|
||||
# .\launch_debug_session.ps1 -AutoStartEmu # launch pcsx-redux.exe if not running
|
||||
#
|
||||
# All three flags can be combined: -AutoStartEmu -AutoResetEmu -AutoLoadAtoms
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$AutoLoadAtoms,
|
||||
[switch]$AutoResetEmu,
|
||||
[switch]$AutoStartEmu,
|
||||
[string]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'),
|
||||
[string]$ElfPath = (Join-Path $PSScriptRoot '..\build\hello_gte.elf')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ── Pre-checks (E10: pcsx-redux on :3333, E11: gdb-multiarch in PATH) ──
|
||||
|
||||
function Test-Prerequisites {
|
||||
# E11: gdb-multiarch must be in PATH.
|
||||
if (-not (Get-Command gdb-multiarch -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "gdb-multiarch not found in PATH. Install via scoop or .\update_deps.ps1." -ErrorAction Stop
|
||||
exit 1
|
||||
}
|
||||
|
||||
# E10: pcsx-redux must be reachable on :3333 (or we auto-start).
|
||||
$tcp = New-Object System.Net.Sockets.TcpClient
|
||||
try {
|
||||
$async = $tcp.BeginConnect('localhost', 3333, $null, $null)
|
||||
$connected = $async.AsyncWaitHandle.WaitOne(2000, $false) -and -not $async.IsFaulted
|
||||
} catch {
|
||||
$connected = $false
|
||||
}
|
||||
$tcp.Close()
|
||||
|
||||
if (-not $connected) {
|
||||
if ($AutoStartEmu) {
|
||||
Write-Host "pcsx-redux not running; auto-starting..." -ForegroundColor Cyan
|
||||
$pcsxExe = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.exe')
|
||||
if (-not (Test-Path $pcsxExe)) {
|
||||
Write-Error "pcsx-redux.exe not found at $pcsxExe. Build it first, or pass -ExePath + start pcsx-redux manually." -ErrorAction Stop
|
||||
exit 1
|
||||
}
|
||||
Start-Process -FilePath $pcsxExe -PassThru | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
} else {
|
||||
Write-Error "Cannot connect to pcsx-redux at localhost:3333. Start pcsx-redux first (or pass -AutoStartEmu)." -ErrorAction Stop
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Optional: hot-reload .ps-exe via pcsx-redux's web server (port 8080) ──
|
||||
|
||||
function Invoke-HotReload {
|
||||
$absolutePath = [System.IO.Path]::GetFullPath($ExePath)
|
||||
$uri = 'http://localhost:8080/api/v1/load-exec'
|
||||
$body = @{ filename = $absolutePath } | ConvertTo-Json
|
||||
try {
|
||||
Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType 'application/json' | Out-Null
|
||||
Write-Host "Hot-reloaded $absolutePath into pcsx-redux." -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Warning "Could not hot-reload via pcsx-redux web server (port 8080): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# ── Main ──
|
||||
|
||||
Test-Prerequisites
|
||||
if ($AutoResetEmu) { Invoke-HotReload }
|
||||
|
||||
$gdbScript = Join-Path $PSScriptRoot 'gdb\gdb_tape_atoms.gdb'
|
||||
|
||||
$gdbArgs = @(
|
||||
'-q'
|
||||
'-ex', "file $ElfPath"
|
||||
'-ex', 'target remote localhost:3333'
|
||||
)
|
||||
if ($AutoLoadAtoms) {
|
||||
$gdbArgs += @('-ex', "source $gdbScript")
|
||||
}
|
||||
|
||||
Write-Host "Launching gdb-multiarch (script: $gdbScript, elf: $ElfPath)..." -ForegroundColor Cyan
|
||||
& gdb-multiarch @gdbArgs
|
||||
@@ -0,0 +1,615 @@
|
||||
--- passes/atoms_source_map.lua — Per-.word source-line map emitter for tape atoms.
|
||||
---
|
||||
--- Reads the pre-scanned SourceScan payload (produced once upstream by `duffle.scan_source`)
|
||||
--- for `MipsAtom_(name)` (kind="atom"), `MipsAtomComp_` / `MipsAtomComp_Proc_` (kind="comp_*"),
|
||||
--- and `MipsCode code_<name>` (kind="raw_atom") declarations. Walks each atom's
|
||||
--- pre-tokenized body (`{{tok=string, rel=integer}, ...}` from `duffle.tokenize_body`), counts
|
||||
--- per-token word contributions via `ctx.shared.word_counts`, and emits one
|
||||
--- `WORD N LINE L TEXT T` line per `.word` to `gen/<basename>.atoms.sourcemap.txt`.
|
||||
---
|
||||
--- **Two output forms** (per the workspace's per-emission-form pattern from
|
||||
--- `guide_metaprogram_ssdl.md`):
|
||||
--- 1. **Canonical text form** — `<source_dir>/gen/<basename>.atoms.sourcemap.txt`.
|
||||
--- Always emitted. Format-version-tagged for forward-compat.
|
||||
--- 2. **gdb-runtime form** — `<ctx.out_root>/gdb_tape_atoms_runtime.gdb` (pure
|
||||
--- gdb command script; addresses pre-computed via `nm`; the 9 user commands
|
||||
--- defined as `define ... end` blocks). Emitted ONLY when
|
||||
--- `ctx.flags.gdb_runtime` is true AND `ctx.flags.elf_path` points to an
|
||||
--- existing ELF. The gdb runtime form lets `gdb-multiarch --without-python`
|
||||
--- users (the common case on Windows MinGW builds) load the source-map data
|
||||
--- via `source <path>` — no Python/Tcl/Guile required.
|
||||
---
|
||||
--- **Output format** (canonical text form):
|
||||
--- ```
|
||||
--- # FORMAT_VERSION 1
|
||||
--- # auto-generated by ps1_meta.lua (passes/atoms_source_map.lua) — DO NOT EDIT
|
||||
--- ATOM <name> "<abs-source-path>" <total_words>
|
||||
--- WORD 0 LINE 49 TEXT load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
|
||||
--- WORD 1 LINE 49 TEXT load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
|
||||
--- ... (one WORD line per .word emitted by the atom body) ...
|
||||
--- ENDATOM
|
||||
--- ATOM <next-name> "<abs-source-path>" <total_words>
|
||||
--- ...
|
||||
--- ENDATOM
|
||||
--- ```
|
||||
---
|
||||
--- Marker calls (`atom_label(...)`, `atom_offset(...)`) emit 0 `.word`s. They share the
|
||||
--- same walking convention as `passes/offsets.lua :: scan_atom_body`: markers do NOT
|
||||
--- advance the word-offset counter, but if a marker is bundled on the same token with a
|
||||
--- trailing instruction (e.g. `atom_label(foo) load_half_u(...)`), the trailing
|
||||
--- instruction's word count is added. This matches `offsets.lua :: count_marker_rest`.
|
||||
---
|
||||
--- **Conventions:** tabs (1/level), EmmyLua annotations, no regex,
|
||||
--- Lua 5.3 compatible. See "lua.md" in the ps1-ai styleguides.
|
||||
---
|
||||
--- Provenance: generated 2026-07-11 by track gdb_tape_atom_debugging_20260711.
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Module-scope requires + package.path setup
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works
|
||||
-- both standalone + when require'd). `duffle_paths.lua` sets package.path then
|
||||
-- returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
local word_count_eval = require("word_count_eval")
|
||||
local count_token_words = word_count_eval.count_token_words
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Constants
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Format version emitted as the first line. Bump + add a migration test if the
|
||||
-- format changes; the gdb runtime loader rejects mismatches (E2).
|
||||
local FORMAT_VERSION = 1
|
||||
|
||||
-- Marker-call identifiers (mirrors offsets.lua:33-34).
|
||||
local LABEL_MARKER = "atom_label"
|
||||
local OFFSET_MARKER = "atom_offset"
|
||||
|
||||
-- gdb-runtime file header + atom-count default for C2 alias table (32 entries).
|
||||
local C2_DATA_ALIASES = {
|
||||
"v0.xy", "v0.z", "v1.xy", "v1.z", "v2.xy", "v2.z",
|
||||
"rgb", "otz",
|
||||
"ir0", "ir1", "ir2", "ir3",
|
||||
"sxy0", "sxy1", "sxy2", "sxyp",
|
||||
"sz0", "sz1", "sz2", "sz3",
|
||||
"rgb_fifo", "mac0_in",
|
||||
"?", "?", "?", "?",
|
||||
"mac0", "mac1", "mac2", "mac3",
|
||||
"mac4", "mac5", "mac6", "mac7",
|
||||
}
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Type declarations
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- @class AtomSourceMapCtx
|
||||
--- @field sources table[] -- SourceScan payload per source (from `ctx.sources`)
|
||||
--- @field shared table -- `ctx.shared`
|
||||
--- @field shared.word_counts table -- macro name -> word count (populated by word-counts + components passes)
|
||||
--- @field out_root string -- output root (e.g. "build/gen")
|
||||
--- @field dry_run boolean -- if true, compute but don't write
|
||||
--- @field flags table -- `ctx.flags`; reads `flags.gdb_runtime` + `flags.elf_path`
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Helpers
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- True iff the leading identifier of `tok` is a marker call (`atom_label` / `atom_offset`).
|
||||
--- Mirrors `passes/offsets.lua :: is_marker_token` (which is file-local there).
|
||||
--- @param tok string
|
||||
--- @return boolean
|
||||
local function is_marker_token(tok)
|
||||
local leading = duffle.read_ident(tok, 1)
|
||||
return leading == LABEL_MARKER or leading == OFFSET_MARKER
|
||||
end
|
||||
|
||||
--- Count words contributed by the non-marker portion of `tok` (after the marker's
|
||||
--- closing `)`). Mirrors offsets.lua:182 `count_marker_rest`. Returns 0 if there's
|
||||
--- no trailing content after the marker call.
|
||||
--- @param tok string
|
||||
--- @param wc table
|
||||
--- @return integer
|
||||
local function count_marker_rest(tok, wc)
|
||||
local marker_end = duffle.find_marker_call_end(tok)
|
||||
if not marker_end or marker_end >= #tok then return 0 end
|
||||
local rest = duffle.trim(tok:sub(marker_end))
|
||||
if rest == "" then return 0 end
|
||||
return count_token_words(rest, wc)
|
||||
end
|
||||
|
||||
--- Compute per-word entries for an atom. Shared between the canonical text form
|
||||
--- (per-source `.atoms.sourcemap.txt`) and the gdb-runtime form
|
||||
--- (`gdb_tape_atoms_runtime.gdb`).
|
||||
---
|
||||
--- Returns a list of `{pos, line, text}` entries + the total word count.
|
||||
--- Markers contribute 0 entries (the marker call emits 0 `.word`s).
|
||||
--- @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
|
||||
--- @return table[], integer
|
||||
local function compute_word_entries(atom, src, wc)
|
||||
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
|
||||
|
||||
if words > 0 then
|
||||
-- Source line for THIS token = line containing byte offset `atom.body_off + rel`.
|
||||
-- `src.scan.line_of(...)` is O(log N) via LineIndex.
|
||||
local line = src.scan.line_of(atom.body_off + rel)
|
||||
-- Flatten newlines + tabs in TEXT to spaces so each WORD entry fits on
|
||||
-- one physical line. The gdb Python parser (or our pure-gdb parser)
|
||||
-- does line-based splits; multi-line TEXT would break it.
|
||||
local text = duffle.trim(tok):gsub("[\t\r\n]+", " ")
|
||||
for _ = 1, words do
|
||||
entries[#entries + 1] = { pos = pos, line = line, text = text }
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return entries, pos
|
||||
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
|
||||
--- @param atom table
|
||||
--- @param wc table
|
||||
--- @return string[], integer
|
||||
local function emit_atom_stanza(src, atom, wc)
|
||||
local lines = {}
|
||||
local rel_path = src.path:gsub("\\", "/")
|
||||
local entries, total = compute_word_entries(atom, src, wc)
|
||||
|
||||
-- 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 _, we in ipairs(entries) do
|
||||
lines[#lines + 1] = string.format("WORD %d LINE %d TEXT %s",
|
||||
we.pos, we.line, we.text)
|
||||
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 source map file content for one source (one .atoms.sourcemap.txt per source).
|
||||
--- Mirrors offsets.lua's `project_atoms` shape: scan.atoms + scan.raw_atoms, no kind filter.
|
||||
--- @param src table
|
||||
--- @param wc table
|
||||
--- @return string
|
||||
local function render_source_map(src, wc)
|
||||
local lines = {}
|
||||
lines[#lines + 1] = "# FORMAT_VERSION " .. FORMAT_VERSION
|
||||
lines[#lines + 1] = "# auto-generated by ps1_meta.lua (passes/atoms_source_map.lua) — DO NOT EDIT"
|
||||
|
||||
for _, atom in ipairs(src.scan.atoms or {}) do
|
||||
local stanza = emit_atom_stanza(src, atom, wc)
|
||||
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_atom_stanza(src, atom, wc)
|
||||
for _, line in ipairs(stanza) do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n") .. "\n"
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- gdb-runtime emission (post-link, addresses via nm)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- Read ELF symbol addresses via `mipsel-none-elf-nm -S`. Returns map: name -> {addr, size_bytes}.
|
||||
--- @param elf_path string
|
||||
--- @return table
|
||||
local function read_nm(elf_path)
|
||||
local addrs = {}
|
||||
-- Use io.popen (subprocess spawn ~50ms on Windows). For frequent calls,
|
||||
-- we'd memoize via package.loaded — but this only runs in the post-link pass.
|
||||
local f = io.popen('mipsel-none-elf-nm -S "' .. elf_path .. '" 2>nul')
|
||||
if not f then return addrs end
|
||||
for line in f:lines() do
|
||||
-- Format: "80015eac 0000015c r code_cube_g4_face"
|
||||
-- The symbol-type letter (`r` for read-only .rodata) sits between size and name.
|
||||
local addr_hex, size_hex, name = line:match("^(%x+)%s+(%x+)%s+%a%s+code_(%S+)")
|
||||
if addr_hex and size_hex and name then
|
||||
addrs[name] = { tonumber(addr_hex, 16), tonumber(size_hex, 16) }
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
return addrs
|
||||
end
|
||||
|
||||
--- Escape a string for embedding in a gdb `set $var = "..."` literal.
|
||||
--- gdb uses C-style escaping; we escape `\` and `"` (newlines were flattened earlier).
|
||||
--- @param s string
|
||||
--- @return string
|
||||
local function gdb_escape(s)
|
||||
return (s:gsub("\\", "\\\\"):gsub('"', '\\"'))
|
||||
end
|
||||
|
||||
--- Build the list of atoms with addresses + word entries. Shared helper for the
|
||||
--- gdb-runtime file emission.
|
||||
--- @param ctx PassCtx
|
||||
--- @return table[] -- list of {idx, name, src_path, file_base, addr, size_bytes, words, entries}
|
||||
local function build_atom_table(ctx)
|
||||
local wc = (ctx.shared and ctx.shared.word_counts) or {}
|
||||
local addrs = read_nm(ctx.flags.elf_path)
|
||||
|
||||
local matched = {}
|
||||
for _, src in ipairs(ctx.sources) do
|
||||
if src.scan then
|
||||
local file_base = src.path:match("([^/\\]+)$") or src.path
|
||||
for _, atom in ipairs(src.scan.atoms or {}) do
|
||||
if atom.kind == nil or atom.kind == "atom" then
|
||||
local name = atom.raw_name or atom.name
|
||||
local info = addrs[name]
|
||||
if info then
|
||||
local entries, total = compute_word_entries(atom, src, wc)
|
||||
matched[#matched + 1] = {
|
||||
name = name,
|
||||
src_path = src.path,
|
||||
file_base = file_base,
|
||||
addr = info[1],
|
||||
size_bytes = info[2],
|
||||
words = total,
|
||||
entries = entries,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, atom in ipairs(src.scan.raw_atoms or {}) do
|
||||
local name = atom.name
|
||||
local info = addrs[name]
|
||||
if info then
|
||||
local entries, total = compute_word_entries(atom, src, wc)
|
||||
matched[#matched + 1] = {
|
||||
name = name,
|
||||
src_path = src.path,
|
||||
file_base = file_base,
|
||||
addr = info[1],
|
||||
size_bytes = info[2],
|
||||
words = total,
|
||||
entries = entries,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Deterministic order: sort by address (matches `nm` output ordering).
|
||||
table.sort(matched, function(a, b) return a.addr < b.addr end)
|
||||
for i, a in ipairs(matched) do
|
||||
a.idx = i - 1
|
||||
end
|
||||
return matched
|
||||
end
|
||||
|
||||
--- Append the 9 gdb command definitions to `lines`. Pure gdb scripting —
|
||||
--- no Python, no Tcl, no Guile required. **Fully hardcoded per-atom** because
|
||||
--- gdb doesn't do nested `$` substitution in var names — `$__atom_name_$__i`
|
||||
--- inside a `while` loop is treated as one literal identifier, not a concat.
|
||||
---
|
||||
--- Each command is a static sequence of `printf` / `tbreak` / `if ... end`
|
||||
--- blocks. The Lua pass emits N atoms' worth of lines — no runtime iteration.
|
||||
--- With 7 atoms + ~200 word entries, the runtime file is ~2000 lines, all
|
||||
--- auto-generated, no human edit ever.
|
||||
--- @param lines table -- output line buffer (mutated in place)
|
||||
--- @param matched table -- list of atom records from `build_atom_table`
|
||||
local function append_gdb_commands(lines, matched)
|
||||
-- ── tape_atoms ──
|
||||
-- Hardcoded one printf per atom. No loop.
|
||||
lines[#lines + 1] = "define tape_atoms"
|
||||
for _, a in ipairs(matched) do
|
||||
-- gdb 12.1 quirk: literals in printf args require an attached target.
|
||||
-- Use the per-atom convenience vars set above as printf args.
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf " code_%%-32s @ 0x%%08x %%4d words\\n", $__atom_name_%d, $__atom_addr_%d, $__atom_words_%d',
|
||||
a.idx, a.idx, a.idx)
|
||||
end
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document tape_atoms"
|
||||
lines[#lines + 1] = " List every tape atom symbol in the loaded ELF (code_<name>) with .rodata addr + word count."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── break_atom (generic) + per-atom break_atom_X ──
|
||||
lines[#lines + 1] = "define break_atom"
|
||||
lines[#lines + 1] = ' echo "Usage: break_atom_<exact_name> (pick from the list below)"'
|
||||
for _, a in ipairs(matched) do
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf " break_atom_%%-32s\\n", $__atom_name_%d', a.idx)
|
||||
end
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document break_atom"
|
||||
lines[#lines + 1] = " Generic help: lists the per-atom break_atom_<name> commands."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
for _, a in ipairs(matched) do
|
||||
lines[#lines + 1] = string.format("define break_atom_%s", a.name)
|
||||
lines[#lines + 1] = string.format(" break *$__atom_addr_%d", a.idx)
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf " Breakpoint set at code_%s (0x%%08x)\\n", $__atom_addr_%d', a.name, a.idx)
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = string.format("document break_atom_%s", a.name)
|
||||
lines[#lines + 1] = string.format(" Set a breakpoint at code_%s.", a.name)
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
end
|
||||
|
||||
-- ── step_atom / next_atom ──
|
||||
-- Hardcoded one tbreak per atom. No loop.
|
||||
lines[#lines + 1] = "define step_atom"
|
||||
for _, a in ipairs(matched) do
|
||||
lines[#lines + 1] = string.format(" tbreak *$__atom_addr_%d", a.idx)
|
||||
end
|
||||
lines[#lines + 1] = " continue"
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document step_atom"
|
||||
lines[#lines + 1] = " Set one-shot BPs at every atom + continue. Stops at the next atom boundary."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
lines[#lines + 1] = "define next_atom"
|
||||
lines[#lines + 1] = " step_atom"
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document next_atom"
|
||||
lines[#lines + 1] = " Alias for step_atom."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── where_in_atom ──
|
||||
-- Hardcoded one outer-if per atom; inside, one inner-if per WORD entry.
|
||||
lines[#lines + 1] = "define where_in_atom"
|
||||
lines[#lines + 1] = " set $__pc = (unsigned int)$pc"
|
||||
lines[#lines + 1] = " set $__matched = 0"
|
||||
for _, a in ipairs(matched) do
|
||||
local end_addr = a.addr + a.words * 4
|
||||
lines[#lines + 1] = string.format(
|
||||
" if $__pc >= $__atom_addr_%d && $__pc < $__atom_addr_%d + $__atom_words_%d * 4",
|
||||
a.idx, a.idx, a.idx)
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf "atom: code_%%s\\n", $__atom_name_%d', a.idx)
|
||||
lines[#lines + 1] = ' printf "addr: 0x%08x\\n", $__pc'
|
||||
lines[#lines + 1] = string.format(
|
||||
" set $__word = ($__pc - $__atom_addr_%d) / 4", a.idx)
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf "word: %%d/%%d\\n", $__word, $__atom_words_%d', a.idx)
|
||||
-- One inner-if per WORD entry. Each word's line + text hardcoded.
|
||||
for _, we in ipairs(a.entries) do
|
||||
lines[#lines + 1] = string.format(
|
||||
" if $__word == %d", we.pos)
|
||||
-- Escape TEXT for printf format string.
|
||||
local escaped_text = we.text:gsub("%%", "%%%%"):gsub('"', '\\"')
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf "source: %%s:%%d %%s\\n", $__atom_file_%d, %d, "%s"',
|
||||
a.idx, we.line, escaped_text)
|
||||
lines[#lines + 1] = " end"
|
||||
end
|
||||
-- Fallback for words beyond the source map (shouldn't happen if nm matches).
|
||||
local max_word = 0
|
||||
if #a.entries > 0 then max_word = a.entries[#a.entries].pos end
|
||||
lines[#lines + 1] = string.format(
|
||||
' if $__word > %d', max_word)
|
||||
lines[#lines + 1] = ' printf "source: (no source-map entry for word %%d; map may be stale)\\n", $__word'
|
||||
lines[#lines + 1] = " end"
|
||||
lines[#lines + 1] = " set $__matched = 1"
|
||||
lines[#lines + 1] = " end"
|
||||
end
|
||||
lines[#lines + 1] = " if !$__matched"
|
||||
lines[#lines + 1] = ' echo PC is not inside any known atom (in .text or unmapped region).'
|
||||
lines[#lines + 1] = " end"
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document where_in_atom"
|
||||
lines[#lines + 1] = " Report current atom name, .rodata addr, word offset, and source line."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── stepi_inside_atom ──
|
||||
-- Hardcoded one if-containment-check per atom (no loop).
|
||||
lines[#lines + 1] = "define stepi_inside_atom"
|
||||
lines[#lines + 1] = " set $__in_atom = 0"
|
||||
lines[#lines + 1] = " set $__did_step = 0"
|
||||
for _, a in ipairs(matched) do
|
||||
local end_addr = a.addr + a.words * 4
|
||||
lines[#lines + 1] = string.format(
|
||||
" if $pc >= $__atom_addr_%d && $pc < $__atom_addr_%d + $__atom_words_%d * 4",
|
||||
a.idx, a.idx, a.idx)
|
||||
lines[#lines + 1] = " set $__in_atom = 1"
|
||||
lines[#lines + 1] = " stepi"
|
||||
lines[#lines + 1] = " set $__did_step = 1"
|
||||
lines[#lines + 1] = " end"
|
||||
end
|
||||
lines[#lines + 1] = " if !$__did_step"
|
||||
lines[#lines + 1] = ' echo [gdb_tape_atoms] stepi_inside_atom: PC is not inside any atom; refusing to step.'
|
||||
lines[#lines + 1] = " end"
|
||||
lines[#lines + 1] = " where_in_atom"
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document stepi_inside_atom"
|
||||
lines[#lines + 1] = " One MIPS-instruction step, then where_in_atom. The step-and-see-source-line workflow."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── show_c2 (32 entries) ──
|
||||
-- Hardcoded with `$c2_data[N]` (COP2 data regs). Requires target attached.
|
||||
lines[#lines + 1] = "define show_c2"
|
||||
for i = 0, 31 do
|
||||
local alias = C2_DATA_ALIASES[i + 1] or "?"
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf "C2[%%2d] 0x%%08x [%%s]\\n", %d, $c2_data[%d], "%s"',
|
||||
i, i, alias)
|
||||
end
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document show_c2"
|
||||
lines[#lines + 1] = " Pretty-print all 32 C2 data registers as hex + named alias. Requires target attached."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── show_c2ctl (32 entries) ──
|
||||
lines[#lines + 1] = "define show_c2ctl"
|
||||
for i = 0, 31 do
|
||||
lines[#lines + 1] = string.format(
|
||||
' printf "C2CTL[%%2d] 0x%%08x\\n", %d, $c2_control[%d]', i, i)
|
||||
end
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document show_c2ctl"
|
||||
lines[#lines + 1] = " Pretty-print all 32 C2 control registers as hex. Requires target attached."
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- ── wave_ctx ──
|
||||
lines[#lines + 1] = "define wave_ctx"
|
||||
lines[#lines + 1] = ' printf "$t4 = R_FaceCursor 0x%08x\\n", $t4'
|
||||
lines[#lines + 1] = ' printf "$t5 = R_VertBase 0x%08x\\n", $t5'
|
||||
lines[#lines + 1] = ' printf "$t6 = R_OtBase 0x%08x\\n", $t6'
|
||||
lines[#lines + 1] = ' printf "$t7 = R_PrimCursor 0x%08x\\n", $t7'
|
||||
lines[#lines + 1] = "end"
|
||||
lines[#lines + 1] = "document wave_ctx"
|
||||
lines[#lines + 1] = " Pretty-print the 4 wave-context GPRs ($t4=R_FaceCursor, $t5=R_VertBase, $t6=R_OtBase, $t7=R_PrimCursor). Requires target attached."
|
||||
lines[#lines + 1] = "end"
|
||||
end
|
||||
|
||||
--- Emit the gdb-runtime file (post-link). Pure gdb scripting — no Python.
|
||||
--- Reads ELF addresses via `mipsel-none-elf-nm -S`, embeds them in
|
||||
--- `<ctx.out_root>/gdb_tape_atoms_runtime.gdb` so gdb loads the data via
|
||||
--- `set $var = ...` + `define ... end` blocks at source-time.
|
||||
--- @param ctx PassCtx
|
||||
local function emit_gdb_runtime(ctx)
|
||||
if not (ctx.flags and ctx.flags.gdb_runtime) then return end
|
||||
local elf_path = ctx.flags.elf_path
|
||||
if not elf_path or elf_path == "" then
|
||||
io.stderr:write("[atoms_source_map] --gdb-runtime requires --elf <elf>\n")
|
||||
return
|
||||
end
|
||||
if lfs.attributes(elf_path, "mode") ~= "file" then
|
||||
io.stderr:write(string.format(
|
||||
"[atoms_source_map] --gdb-runtime: ELF not found at %s\n", elf_path))
|
||||
return
|
||||
end
|
||||
|
||||
local matched = build_atom_table(ctx)
|
||||
if #matched == 0 then
|
||||
io.stderr:write("[atoms_source_map] --gdb-runtime: no atoms matched against nm symbols (stale scan?).\n")
|
||||
return
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
lines[#lines + 1] = "# Auto-generated by ps1_meta.lua (passes/atoms_source_map.lua)"
|
||||
lines[#lines + 1] = "# DO NOT EDIT — re-run ps1_meta.lua --atoms-source-map --gdb-runtime to regenerate"
|
||||
lines[#lines + 1] = "# Sourced by scripts/gdb/gdb_tape_atoms.gdb (the wrapper)."
|
||||
lines[#lines + 1] = "# Pure gdb scripting — no Python, no Tcl, no Guile required."
|
||||
lines[#lines + 1] = "# Commands are FULLY HARDCODED per-atom because gdb doesn't do nested"
|
||||
lines[#lines + 1] = "# `$` substitution in var names (`$foo_$i` is one literal identifier)."
|
||||
lines[#lines + 1] = "# Per-atom convenience vars ($__atom_name_<i> etc.) are set so gdb's"
|
||||
lines[#lines + 1] = "# `printf` has valid expression args (gdb 12.1 quirks: literals in"
|
||||
lines[#lines + 1] = "# printf args require an attached target; convenience-var args do not)."
|
||||
lines[#lines + 1] = string.format("# %d atoms from ELF: %s", #matched, elf_path)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- Format version + count + ELF path (the latter is referenced by the load-line).
|
||||
lines[#lines + 1] = "set $__atom_format_version = " .. FORMAT_VERSION
|
||||
lines[#lines + 1] = string.format("set $__atom_count = %d", #matched)
|
||||
lines[#lines + 1] = string.format('set $__elf_path = "%s"', gdb_escape(elf_path))
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- Per-atom convenience vars (used as printf args; literals aren't accepted
|
||||
-- without an attached target on gdb 12.1).
|
||||
for _, a in ipairs(matched) do
|
||||
lines[#lines + 1] = string.format('set $__atom_name_%d = "%s"', a.idx, gdb_escape(a.name))
|
||||
lines[#lines + 1] = string.format("set $__atom_addr_%d = 0x%x", a.idx, a.addr)
|
||||
lines[#lines + 1] = string.format("set $__atom_words_%d = %d", a.idx, a.words)
|
||||
lines[#lines + 1] = string.format('set $__atom_file_%d = "%s"', a.idx, gdb_escape(a.file_base))
|
||||
end
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- The 9 commands (each `define ... end` overrides the wrapper's stub).
|
||||
lines[#lines + 1] = "# ── 9 user commands (overrides wrapper stubs) ──"
|
||||
append_gdb_commands(lines, matched)
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
-- Confirmation line for the source operator.
|
||||
lines[#lines + 1] = 'printf "[gdb_tape_atoms] runtime loaded %d atoms from %s\\n", $__atom_count, $__elf_path'
|
||||
|
||||
local out_path = ctx.out_root .. "/gdb_tape_atoms_runtime.gdb"
|
||||
if not ctx.dry_run then
|
||||
duffle.ensure_dir(duffle.dirname(out_path))
|
||||
duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n")
|
||||
end
|
||||
io.stderr:write(string.format(
|
||||
"[atoms_source_map] wrote %s (%d atoms)\n", out_path, #matched))
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- M — module exports
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Pass entry: emit one `gen/<basename>.atoms.sourcemap.txt` per source file
|
||||
--- that contains at least one `MipsAtom_(name)` / `MipsCode code_<name>` declaration.
|
||||
--- Optionally also emit `<ctx.out_root>/gdb_tape_atoms_runtime.gdb` when
|
||||
--- `ctx.flags.gdb_runtime` is true.
|
||||
--- @param ctx PassCtx
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
local outputs = {}
|
||||
local errors = {}
|
||||
local warnings = {}
|
||||
|
||||
-- word-counts + components passes must have populated shared.word_counts.
|
||||
-- If absent, the orchestrator wired the deps wrong — fail loud.
|
||||
local wc = (ctx.shared and ctx.shared.word_counts) or {}
|
||||
if not wc or not next(wc) then
|
||||
warnings[#warnings + 1] = {
|
||||
line = 0,
|
||||
msg = "atoms_source_map: ctx.shared.word_counts is empty; the word-counts + components passes may not have populated it. Check the PASSES dep edges.",
|
||||
}
|
||||
end
|
||||
|
||||
-- Always emit the canonical text form (per-source).
|
||||
for _, src in ipairs(ctx.sources) do
|
||||
if src.scan then
|
||||
local n_atoms = src.scan.atoms and #src.scan.atoms or 0
|
||||
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)
|
||||
local out_path = src.dir .. "/gen/" .. basename .. ".atoms.sourcemap.txt"
|
||||
local content = render_source_map(src, wc)
|
||||
|
||||
if not ctx.dry_run then
|
||||
duffle.ensure_dir(duffle.dirname(out_path))
|
||||
duffle.write_file_lf(out_path, content)
|
||||
end
|
||||
|
||||
outputs[#outputs + 1] = { kind = "report", path = out_path }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Optionally emit the gdb-runtime form (post-link, one file per build).
|
||||
if ctx.flags and ctx.flags.gdb_runtime then
|
||||
emit_gdb_runtime(ctx)
|
||||
end
|
||||
|
||||
return { outputs = outputs, errors = errors, warnings = warnings }
|
||||
end
|
||||
|
||||
return M
|
||||
+27
-10
@@ -148,6 +148,13 @@ local PASSES = {
|
||||
desc = "[FUTURE] GTE pipeline-fill, mac_yield uniformity, etc.",
|
||||
out = { { kind = "report", path_template = "<out_root>/<basename>.static_analysis.txt" } },
|
||||
},
|
||||
["atoms-source-map"] = {
|
||||
module = "passes.atoms_source_map",
|
||||
kind = "header-output",
|
||||
deps = {"word-counts", "components"},
|
||||
desc = "Emit gen/<basename>.atoms.sourcemap.txt (per-.word C source line map for gdb debugging)",
|
||||
out = { { kind = "header", path_template = "<source_dir>/gen/<basename>.atoms.sourcemap.txt" } },
|
||||
},
|
||||
report = {
|
||||
module = "passes.report",
|
||||
kind = "report",
|
||||
@@ -167,19 +174,20 @@ local PASS_KIND_STOP_ON_ERROR = {
|
||||
|
||||
-- Closed set of CLI flags -> pass names.
|
||||
local PASS_FLAG_TO_NAME = {
|
||||
["--word-counts"] = "word-counts",
|
||||
["--components"] = "components",
|
||||
["--validate"] = "annotation",
|
||||
["--offsets"] = "offsets",
|
||||
["--static-analysis"] = "static-analysis",
|
||||
["--report"] = "report",
|
||||
["--scan-source"] = "scan-source",
|
||||
["--all"] = ALL_PASSES_SENTINEL,
|
||||
["--word-counts"] = "word-counts",
|
||||
["--components"] = "components",
|
||||
["--validate"] = "annotation",
|
||||
["--offsets"] = "offsets",
|
||||
["--static-analysis"] = "static-analysis",
|
||||
["--atoms-source-map"] = "atoms-source-map",
|
||||
["--report"] = "report",
|
||||
["--scan-source"] = "scan-source",
|
||||
["--all"] = ALL_PASSES_SENTINEL,
|
||||
}
|
||||
|
||||
local ALL_PASS_NAMES = {
|
||||
"scan-source", "word-counts", "components", "annotation",
|
||||
"offsets", "static-analysis", "report",
|
||||
"offsets", "static-analysis", "atoms-source-map", "report",
|
||||
}
|
||||
|
||||
--- Append every pass name to args.requested_set. Used by --all and by the "default to --all if no pass flags were given" fallback.
|
||||
@@ -212,6 +220,7 @@ PASS_FLAGS (pick one or more, or use --all):
|
||||
--components Generate <module>/gen/<basename>.macs.h
|
||||
--validate Run atom annotation DSL validation
|
||||
--offsets Generate <module>/gen/<basename>.offsets.h
|
||||
--atoms-source-map Generate <basename>.atoms.sourcemap.txt per source
|
||||
--static-analysis [FUTURE] GTE pipeline-fill, mac_yield uniformity
|
||||
--report Render per-project summary
|
||||
--all Equivalent to all 6 flags above (default)
|
||||
@@ -221,6 +230,8 @@ COMMON_FLAGS:
|
||||
--metadata PATH Path to metadata.h (required)
|
||||
--out-root DIR Output root for reports (default: build/gen)
|
||||
--project-root DIR Project root for .macs.h scan (default: dirname(metadata))
|
||||
--gdb-runtime Also emit <out_root>/gdb_tape_atoms_runtime.gdb (post-link, requires --elf)
|
||||
--elf PATH Path to linked .elf (for --gdb-runtime address resolution)
|
||||
--dry-run Print dep order + ASCII graph; exit 0 without running
|
||||
--verbose Print per-pass debug output
|
||||
--help Show this help and exit
|
||||
@@ -253,6 +264,12 @@ FLAG_HANDLERS["--metadata"] = function(args, argv, arg_idx) args.metadata
|
||||
FLAG_HANDLERS["--out-root"] = function(args, argv, arg_idx) args.out_root = argv[arg_idx + 1]; return arg_idx + 1 end
|
||||
FLAG_HANDLERS["--project-root"] = function(args, argv, arg_idx) args.project_root = argv[arg_idx + 1]; return arg_idx + 1 end
|
||||
|
||||
-- Per-pass stash flags. Read by `passes/atoms_source_map.lua` to opt into the
|
||||
-- post-link gdb-runtime emission. Same shape as the existing per-flag handlers:
|
||||
-- mutates `args.flags` (which propagates into `ctx.flags`).
|
||||
FLAG_HANDLERS["--gdb-runtime"] = function(args) args.flags = args.flags or {}; args.flags.gdb_runtime = true end
|
||||
FLAG_HANDLERS["--elf"] = function(args, argv, arg_idx) args.flags = args.flags or {}; args.flags.elf_path = argv[arg_idx + 1]; return arg_idx + 1 end
|
||||
|
||||
-- Pass-flag handler. Reads the closed-set table, expands --all, appends to requested_set. Single-statement, no nesting.
|
||||
FLAG_HANDLERS[PASS_FLAG_DISPATCH_KEY] = function(args, a)
|
||||
local name = PASS_FLAG_TO_NAME[a]
|
||||
@@ -368,7 +385,7 @@ local function build_ctx(args)
|
||||
upstream = {},
|
||||
out_root = args.out_root,
|
||||
project_root = args.project_root,
|
||||
flags = {},
|
||||
flags = args.flags or {},
|
||||
dry_run = args.dry_run,
|
||||
verbose = args.verbose,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user