From a16c727db22b18e61b491ebc2a3cbbcea2da4e25 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 15 Aug 2026 19:52:42 -0400 Subject: [PATCH] updates to lua program to furhter support new constructs and correct report errors. --- .../tape-atom-syntax/atomasm-psx-0.3.0.vsix | Bin 11927 -> 12002 bytes .vscode/tape-atom-syntax/classifier.js | 2 +- .../syntaxes/tape_atom.tmLanguage.json | 2 +- .../tape-atom-syntax/test/classifier.test.js | 10 + scripts/duffle.lua | 49 ++- scripts/elf_dwarf.lua | 4 +- scripts/gdb/gdb_tape_atoms.gdb | 2 +- scripts/passes/annotation.lua | 4 +- scripts/passes/atoms_source_map.lua | 12 +- scripts/passes/dwarf_injection.lua | 7 +- scripts/passes/report.lua | 117 ++++-- scripts/passes/scan_source.lua | 214 +++++++---- scripts/passes/static_analysis.lua | 350 ++++++++++++++---- 13 files changed, 592 insertions(+), 181 deletions(-) diff --git a/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix b/.vscode/tape-atom-syntax/atomasm-psx-0.3.0.vsix index e42f4f3533f04d57089cf3e65301d036760f9a83..94f0d96cb206473cb54aceaa497043ec5eab3940 100644 GIT binary patch delta 10630 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 sP4=&5Ir&e&1v!VZ1t^fc4 diff --git a/.vscode/tape-atom-syntax/classifier.js b/.vscode/tape-atom-syntax/classifier.js index d4ad0a3..4db0fef 100644 --- a/.vscode/tape-atom-syntax/classifier.js +++ b/.vscode/tape-atom-syntax/classifier.js @@ -49,7 +49,7 @@ const DSL_KEYWORDS = new Set([ "u1_r", "u2_r", "u4_r", "u8_r", "u1_v", "u2_v", "u4_v", "u8_v", ]); -const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_"]); +const DELAY_SLOT_KEYWORDS = new Set(["LdSlot_", "BdSlot_", "DmaSlot_", "GteDelay_"]); const CONTROL_FLOW_PREFIXES = /^(?:branch_|jump_|call_)/; diff --git a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json index fa768a2..d42df06 100644 --- a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json +++ b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json @@ -56,7 +56,7 @@ "name": "support.function.duffle.annotation" }, "delay-slots": { - "match": "\\b(LdSlot_|BdSlot_)\\b", + "match": "\\b(LdSlot_|BdSlot_|DmaSlot_|GteDelay_)\\b", "name": "keyword.operator.duffle.delayslot" }, "types": { diff --git a/.vscode/tape-atom-syntax/test/classifier.test.js b/.vscode/tape-atom-syntax/test/classifier.test.js index 9a67fb3..39d4895 100644 --- a/.vscode/tape-atom-syntax/test/classifier.test.js +++ b/.vscode/tape-atom-syntax/test/classifier.test.js @@ -105,6 +105,16 @@ test("document-local declarations override an empty workspace index", () => { assert.equal(byText(result, "mac_new_component")[0].type, "tapeComponentInstruction"); }); +test("delay slot markers share the tapeDelaySlot token", () => { + const source = "LdSlot_ nop, BdSlot_ nop, DmaSlot_ nop2, GteDelay_ nop"; + const result = classifyDocument(source, "C:/x/code/duffle/gte.atom.c", createIndex()); + + assert.equal(byText(result, "LdSlot_")[0].type, "tapeDelaySlot"); + assert.equal(byText(result, "BdSlot_")[0].type, "tapeDelaySlot"); + assert.equal(byText(result, "DmaSlot_")[0].type, "tapeDelaySlot"); + assert.equal(byText(result, "GteDelay_")[0].type, "tapeDelaySlot"); +}); + test("classifier returns ordered non-overlapping spans and partial malformed output", () => { const source = "atom_reads(R_A /* broken"; const result = classifyDocument(source, "C:/x/code/test.atom.c", createIndex()); diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 12c4730..4813245 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -1014,14 +1014,20 @@ end -- Section 7: domain tables -- ════════════════════════════════════════════════════════════════════════════ --- The annotation DSL has been reduced to a single annotation macro: atom_info(atom_bind(Binds_X), atom_reads(...), atom_writes(...)) --- All phase / region / cadence / async / resource / group tokens have been dropped. --- They may be reintroduced later as optional sub-calls of atom_info; --- For now, the parser only recognizes atom_info + its three sub-calls (atom_bind, atom_reads, atom_writes). +-- atom_info sub-calls: atom_bind, atom_reads, atom_writes, atom_view, atom_reg_types, atom_ctx, atom_phase. M.TAPE_ATOM_MACROS = { ["atom_info"] = { kind = "info", binds = false }, } +-- Empty C macros that prefix the next encoder. Zero words. +-- BdSlot_ nop is one nop word. The marker is not the BD instruction. +M.DELAY_MARKERS = { + ["GteDelay_"] = true, + ["LdSlot_"] = true, + ["BdSlot_"] = true, + ["DmaSlot_"] = true, +} + -- GTE command-alias resolution table. -- -- Maps every source-side GTE command macro to its canonical short ident. @@ -1328,6 +1334,13 @@ M.GTE_CR_ALIAS_GROUPS = { { 26, { "gte_cr_BBK", "gte_cr_H" } }, -- background B vs projection plane distance H } +-- Packed RT slots named by the gte.h packed-slot comment. +-- first must be written before second. +M.GTE_PACKED_SLOT_RELATIONS = { + { slot = 2, first = "gte_cr_RT13", second = "gte_cr_RT22" }, + { slot = 4, first = "gte_cr_RT22", second = "gte_cr_RT33" }, +} + -- Operand-class table for the COP2->GPR load-delay check. -- Maps each emitting-token ident to the set of GPR operand positions it reads. -- Covers the current encoder vocabulary (`code/duffle/mips.h` + `code/duffle/gte.h`); add rows here as new encoders land. @@ -2455,6 +2468,15 @@ local function _project_emission_inner(root_body_entry, ctx_table) pos = (next_pos > pos) and next_pos or (pos + 1) goto continue_loop end + if M.DELAY_MARKERS[ident] then + local arg_pos = nil + if consuming_encoder and consuming_paren then + arg_pos = count_top_level_commas(tok, consuming_paren + 1, pos) + 1 + end + emit_marker("delay", ident, nil, tok_line, nil, nil, consuming_encoder, arg_pos) + pos = after + goto continue_loop + end if ident ~= "atom_label" and ident ~= "atom_offset" then -- Ordinary ident; nothing to emit, step past the ident only. pos = after @@ -2601,9 +2623,18 @@ local function _project_emission_inner(root_body_entry, ctx_table) local function process_token(bt) local tok = M.trim(bt.tok or "") if tok == "" then return end - local ident = M.read_ident(tok, 1) or "?" + local ident, after = M.read_ident(tok, 1) + if not ident then ident = "?" end local _, args = token_ident_and_args(tok) local tok_line = line_of(body_off + bt.rel) or 0 + if M.DELAY_MARKERS[ident] then + emit_marker("delay", ident, nil, tok_line) + local rest = M.trim(tok:sub(after or (#tok + 1))) + if rest ~= "" then + process_token({ tok = rest, rel = bt.rel }) + end + return + end -- embedded markers live only in non-marker tokens. -- Pass `ident` as the consuming instruction so `emit_embedded_markers` can compute each marker's arg position + record the consuming_encoder for the offsets pass. -- Canonicalize `jump_rel` to `branch_equal` (its preprocessor-expanded form) so the `consuming_encoder` metadata in marker records is canonical. @@ -2751,6 +2782,7 @@ end --- `nop2` is normalized to encoder `nop` (per the spec). --- * `atom_label(F)` markers: one `label` item with `name = "F"`, `word_index = current word_idx`; zero-width (does NOT advance word_idx). --- * `atom_offset(B, T)` markers: one `offset` item with `name = "B"`, `target = "T"`, `word_index = current word_idx`; zero-width. +--- * Delay markers (`GteDelay_` / `LdSlot_` / `BdSlot_` / `DmaSlot_`): one `delay` item; zero-width. The following encoder is the next token. --- * `mac_X(...)` calls: emit `invoke_begin` (zero-width), recurse into the component body, emit `invoke_end` (zero-width). --- The component body's words land between the begin/end pair; one invocation record is allocated per call (monotonic ID per atom). --- * Unknown uncounted macros emit 1 opaque word + one warning per occurrence. @@ -2950,12 +2982,7 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) if source:sub(next_pos, next_pos) == "(" then local inner = M.read_parens(source, next_pos) if inner then - local proc_suffix = "_proc" - local atom_name = ident - if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then - atom_name = ident:sub(1, #ident - #proc_suffix) - end - return atom_name, inner, ident + return ident, inner, ident end end -- ident not followed by "(" — it's a qualifier; skip it diff --git a/scripts/elf_dwarf.lua b/scripts/elf_dwarf.lua index b645721..1f0483a 100644 --- a/scripts/elf_dwarf.lua +++ b/scripts/elf_dwarf.lua @@ -612,13 +612,13 @@ function M.read_elf_sections(elf_path, section_names) end --- Read ELF symbol addresses by walking the `.symtab` + `.strtab` sections directly (no `nm` subprocess). ---- Returns a map `{name -> {addr, size_bytes}}` for every `code_` symbol. +--- Returns a map `{name -> {addr, size_bytes}}` for every defined symbol. --- --- **Conventions:** --- - ELF32 symtab entry = 16 bytes (`st_name:4 + st_value:4 + st_size:4 + st_info:1 + st_other:1 + st_shndx:2`); offsets within each entry are zero-based wire offsets. --- - Direct Lua `string.byte`/`string.sub`/`string.find` boundaries receive `+ 1`. --- - We filter on STB_GLOBAL (high nibble of st_info = 1) to match `nm`'s default (external symbols only). STB_WEAK excluded. ---- - The `code_` prefix is stripped (MipsAtom_ macros emit bare atom names, no `code_` prefix). +--- - Keys are the ELF symbol names as written (the C ident). --- - `st_size > 0` filter excludes undefined/imported symbols. --- --- @param elf_path Path diff --git a/scripts/gdb/gdb_tape_atoms.gdb b/scripts/gdb/gdb_tape_atoms.gdb index 54f0dbe..d047efb 100644 --- a/scripts/gdb/gdb_tape_atoms.gdb +++ b/scripts/gdb/gdb_tape_atoms.gdb @@ -16,7 +16,7 @@ define tape_atoms 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_) with its .rodata address and word count. + List every tape atom symbol in the loaded ELF with its .rodata address and word count. STUB state: runtime file not sourced. Run build_psyq.ps1 to regenerate. end diff --git a/scripts/passes/annotation.lua b/scripts/passes/annotation.lua index 04ec968..895d6f4 100644 --- a/scripts/passes/annotation.lua +++ b/scripts/passes/annotation.lua @@ -477,8 +477,8 @@ local function validate(ctx, src, corpus_pipe_ctx) -- Project the pre-scanned atoms to the AtomEntry shape this pass needs. local atoms = {} for _, a in ipairs(scan.atoms) do - if a.kind == "atom" then - atoms[#atoms + 1] = { line = a.line, name = a.raw_name } + if a.kind == "atom" or a.kind == "atom_proc" then + atoms[#atoms + 1] = { line = a.line, name = a.raw_name or a.name } end end diff --git a/scripts/passes/atoms_source_map.lua b/scripts/passes/atoms_source_map.lua index 1518d3b..92f2d8b 100644 --- a/scripts/passes/atoms_source_map.lua +++ b/scripts/passes/atoms_source_map.lua @@ -261,12 +261,12 @@ local function append_gdb_commands(lines, matched) 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', + lines[#lines + 1] = string.format(' printf " %%-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_) with .rodata addr + word count." + lines[#lines + 1] = " List every tape atom symbol in the loaded ELF with .rodata addr + word count." lines[#lines + 1] = "end" lines[#lines + 1] = "" @@ -285,10 +285,10 @@ local function append_gdb_commands(lines, matched) 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] = string.format(' printf " Breakpoint set at %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] = string.format(" Set a breakpoint at %s.", a.name) lines[#lines + 1] = "end" lines[#lines + 1] = "" end @@ -323,7 +323,7 @@ local function append_gdb_commands(lines, matched) -- Precompute end_addr (gdb 12.1's expression evaluator chokes on `addr + words*4`). lines[#lines + 1] = string.format(" set $__end_%d = $__atom_addr_%d + $__atom_words_%d * 4", a.idx, a.idx, a.idx) lines[#lines + 1] = string.format(" if $__pc >= $__atom_addr_%d && $__pc < $__end_%d", a.idx, a.idx) - lines[#lines + 1] = string.format(' printf "atom: code_%%s\\n", $__atom_name_%d', a.idx) + lines[#lines + 1] = string.format(' printf "atom: %%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) @@ -541,7 +541,7 @@ function M.render_atom_provenance(atom, wc, rel_path) return table.concat(lines, "\n") .. "\n" end ---- Pass entry. For each source that declares at least one `MipsAtom_(name)` / `MipsCode code_`, +--- Pass entry. For each source that declares at least one tape atom, --- emit two files in `/`: `.atoms.sourcemap.txt` (per-word call-site map) and `.atoms.provenance.txt` --- (per-word definition + body line, resolved via the outermost `mac_X(...)` invocation). --- When `ctx.flags.gdb_runtime` is true and `ctx.flags.elf_path` exists, also emit the post-link gdb script `/gdb_tape_atoms_runtime.gdb`. diff --git a/scripts/passes/dwarf_injection.lua b/scripts/passes/dwarf_injection.lua index b76b4bb..a0171e2 100644 --- a/scripts/passes/dwarf_injection.lua +++ b/scripts/passes/dwarf_injection.lua @@ -2,7 +2,7 @@ --- --- Reads the post-link ELF directly (io.open; walks the ELF32 section header table to find --- `.debug_info` + `.debug_abbrev` + `.debug_str` + `.debug_line` + `.debug_aranges` + `.debug_rnglists`), ---- APPENDS synthetic DWARF line-program sequences for every `code_` atom, EXTENDS the `.debug_aranges` +--- APPENDS synthetic DWARF line-program sequences for every tape atom, EXTENDS the `.debug_aranges` --- and main-CU range tables with the atom ranges, and INSERTS synthetic atom/component DIE children into the --- existing main compilation unit in `.debug_info` (no second compilation unit). --- Per-atom `DW_TAG_subprogram` + per-register `DW_TAG_variable` entries make @@ -1344,7 +1344,7 @@ local function build_new_abbrev() attr( DW_AT_name, DW_FORM_string) .. attr(DW_AT_low_pc, DW_FORM_addr) .. attr(DW_AT_high_pc, DW_FORM_addr) - .. attr(DW_AT_linkage_name, DW_FORM_string)) -- equals DW_AT_name; lets gdb's symbol-table lookup resolve to our subprogram (not the gcc global `code_` const U4 array) + .. attr(DW_AT_linkage_name, DW_FORM_string)) -- equals DW_AT_name; gdb resolves the subprogram, not the gcc global array local abbrev_variable = abbrev(ABBREV_VARIABLE, DW_TAG_variable, false, -- DW_CHILDREN_no attr( DW_AT_name, DW_FORM_string) @@ -1857,7 +1857,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta end -- 4) Emit per-atom DW_TAG_subprograms (children of main CU). - -- Subprogram names match nm symbols without a `code_` prefix. + -- Subprogram names match the written C ident (the ELF symbol). -- The gcc global `[]` is a DW_TAG_variable without children; our subprogram has the wave-context var children. -- gdb's symbol resolution picks our subprogram (it has low_pc/high_pc + children) over the gcc global for function-context lookups. for _, atom in ipairs(atom_table) do @@ -2313,5 +2313,6 @@ end M.compute_loclists_offsets_for_test = compute_loclists_offsets M.build_debug_loclists_section_for_test = build_debug_loclists_section M.tape_piece_size_for_test = tape_piece_size +M.build_atom_table_for_test = build_atom_table return M diff --git a/scripts/passes/report.lua b/scripts/passes/report.lua index cabec9a..7a55b53 100644 --- a/scripts/passes/report.lua +++ b/scripts/passes/report.lua @@ -259,6 +259,27 @@ local function slot_suffix(key) return key:match("([^:]+)$") end +local function decl_names(view) + local names = {} + for _, a in ipairs(view.decls or {}) do + if a.name then names[a.name] = true end + end + return names +end + +local function path_in_module(path, view) + if type(path) ~= "string" or path == "" then return false end + local norm = path:gsub("\\", "/") + local dir = (view.dir or ""):gsub("\\", "/") + if dir ~= "" and (norm == dir or norm:sub(1, #dir + 1) == dir .. "/") then + return true + end + for _, src in ipairs(view.sources or {}) do + if (src.path or ""):gsub("\\", "/") == norm then return true end + end + return false +end + local function build_module_view(dir, dir_sources, corpus) local decls = {} for _, src in ipairs(dir_sources or {}) do @@ -352,7 +373,16 @@ local function render_section_reguse(add, view) end add("") end - local errors = (view.corpus and view.corpus.reg_use_errors) or {} + local bound = {} + for _, schema in ipairs(view.schemas or {}) do + if schema.name then bound[schema.name] = true end + end + local errors = {} + for _, err in ipairs((view.corpus and view.corpus.reg_use_errors) or {}) do + if bound[err.schema_name] or path_in_module(err.source_file, view) then + errors[#errors + 1] = err + end + end if #errors > 0 then wrote = true add("### parse errors") @@ -394,12 +424,8 @@ local function render_section_binds(add, view) for _, src in ipairs(view.sources) do for _, b in ipairs((src.scan and src.scan.binds) or {}) do wrote = true - local line = b.line or 0 - if src.scan.line_of and type(b.line) == "number" then - line = src.scan.line_of(b.line) or b.line - end add(string.format("### %s (%s:%s, %s bytes)", - b.name, source_basename(src.path), tostring(line), tostring(b.bytes or "—"))) + b.name, source_basename(src.path), tostring(b.line or 0), tostring(b.bytes or "—"))) for _, f in ipairs(b.fields or {}) do add(string.format("- `+%s %s`", tostring(f.offset or "?"), f.name or "?")) end @@ -411,46 +437,75 @@ end local function render_section_phases(add, view) local corpus = view.corpus or {} - local wrote = false + local names = decl_names(view) + local wrote = false for phase, entry in pairs(corpus.atom_phases or {}) do - wrote = true - add(string.format("- phase `%s`: %s", phase, table.concat(entry.atoms or {}, ", "))) + local here = {} + for _, atom_name in ipairs(entry.atoms or {}) do + if names[atom_name] then here[#here + 1] = atom_name end + end + if #here > 0 then + wrote = true + add(string.format("- phase `%s`: %s", phase, table.concat(here, ", "))) + end end for name, entry in pairs(corpus.atom_views or {}) do - wrote = true - add(string.format("- view `%s` binds `%s`", name, entry.binds_name or "—")) + if names[name] then + wrote = true + add(string.format("- view `%s` binds `%s`", name, entry.binds_name or "—")) + end end for name, entry in pairs(corpus.atom_ctxs or {}) do - wrote = true - add(string.format("- ctx `%s` rbind `%s`", name, entry.rbind_atom or "—")) + if names[name] then + wrote = true + add(string.format("- ctx `%s` rbind `%s`", name, entry.rbind_atom or "—")) + end end if not wrote then add("_(none)_") end add("") end local function render_section_aliases(add, view) - local reg = (view.corpus and view.corpus.register_alias_registry) or {} local names = {} - for name in pairs(reg) do names[#names + 1] = name end + local seen = {} + for _, src in ipairs(view.sources or {}) do + for name, entry in pairs((src.scan and src.scan.register_alias_registry) or {}) do + if not seen[name] then + seen[name] = entry + names[#names + 1] = name + end + end + end table.sort(names) if #names == 0 then add("_(none)_"); add(""); return end add("| alias | type |") add("|-------|------|") for _, name in ipairs(names) do - local e = reg[name] + local e = seen[name] add(string.format("| %s | %s |", name, (e and e.default_type) or "—")) end add("") end local function render_section_autoreg(add, view) - local corpus = view.corpus or {} + local allowed = decl_names(view) + for phase, entry in pairs((view.corpus and view.corpus.atom_phases) or {}) do + for _, atom_name in ipairs(entry.atoms or {}) do + if allowed[atom_name] then allowed[phase] = true end + end + end local wrote = false + local seen = {} local function dump(label, table_map) local scopes = {} - for scope in pairs(table_map or {}) do scopes[#scopes + 1] = scope end + for scope in pairs(table_map or {}) do + if allowed[scope] and not seen[label .. "\0" .. scope] then + scopes[#scopes + 1] = scope + end + end table.sort(scopes) for _, scope in ipairs(scopes) do + seen[label .. "\0" .. scope] = true wrote = true local syms = {} for sym, gpr in pairs(table_map[scope] or {}) do @@ -464,16 +519,28 @@ local function render_section_autoreg(add, view) add(string.format("- %s `%s`: %s", label, scope, table.concat(syms, ", "))) end end + local corpus = view.corpus or {} dump("atom", corpus.atom_auto_regs) dump("phase", corpus.phase_auto_regs) + for _, src in ipairs(view.sources or {}) do + dump("atom", src.scan and src.scan.atom_auto_regs) + dump("phase", src.scan and src.scan.phase_auto_regs) + end if not wrote then add("_(none)_") end add("") end local function render_section_collisions(add, view) - local cols = (view.corpus and view.corpus.collisions) or {} - if #cols == 0 then add("_(none)_"); add(""); return end - for _, c in ipairs(cols) do + local rows = {} + for _, c in ipairs((view.corpus and view.corpus.collisions) or {}) do + local first = c.first_site or {} + local other = c.conflicting_site or {} + if path_in_module(first.path, view) or path_in_module(other.path, view) then + rows[#rows + 1] = c + end + end + if #rows == 0 then add("_(none)_"); add(""); return end + for _, c in ipairs(rows) do local first = c.first_site or {} local other = c.conflicting_site or {} add(string.format("- `%s` `%s` first %s:%s conflict %s:%s", @@ -547,11 +614,13 @@ local function render_section_forward(add, view) local wrote = false for _, a in ipairs(view.decls) do local gpr = a.paths and a.paths.forward_state and a.paths.forward_state.gpr_values - if gpr and next(gpr) ~= nil then + local keys = {} + for k in pairs(gpr or {}) do + if k ~= "R_0" then keys[#keys + 1] = k end + end + if #keys > 0 then wrote = true add("### " .. a.name) - local keys = {} - for k in pairs(gpr) do keys[#keys + 1] = k end table.sort(keys) for _, k in ipairs(keys) do local slot = gpr[k] diff --git a/scripts/passes/scan_source.lua b/scripts/passes/scan_source.lua index 93211f1..88e33e5 100644 --- a/scripts/passes/scan_source.lua +++ b/scripts/passes/scan_source.lua @@ -1252,6 +1252,80 @@ local function parse_atom_dbg_reg_default(source, pos, ident_end, line_of, out) return after_paren end +--- Lookahead for `atom_info(...)` after a declaration's closing paren. +--- Records into `dest` (atom_infos or component_atom_infos). Returns the position after the info, or after_paren if none. +local function parse_atom_info_after_decl(source, after_paren, raw_name, line_of, out, dest) + local lookahead = duffle.skip_ws_and_cmt(source, after_paren) + local look_ident, look_end = duffle.read_ident(source, lookahead) + if look_ident ~= "atom_info" then return after_paren end + local info_open = duffle.skip_ws_and_cmt(source, look_end) + if source:sub(info_open, info_open) ~= "(" then return after_paren end + local info_inner, info_after = duffle.read_parens(source, info_open) + if not info_inner then return after_paren end + local info_line = line_of(info_open) + local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line) + dest = dest or out.atom_infos + dest[#dest + 1] = { + atom_name = raw_name or "?", binds = ai_binds, + reads = ai_reads or {}, writes = ai_writes or {}, + view = ai_view, + reg_type_overrides = ai_overrides, + ctx_atom = ai_ctx, + phase = ai_phase, + info_line = line_of(lookahead), + } + if ai_view and raw_name then + out.atom_views[raw_name] = { + atom_name = raw_name, + binds_name = ai_view, + reg_type_overrides = ai_overrides, + info_line = line_of(lookahead), + } + elseif raw_name and ai_overrides then + out.atom_views[raw_name] = out.atom_views[raw_name] or { atom_name = raw_name, binds_name = nil, reg_type_overrides = nil, info_line = line_of(lookahead) } + out.atom_views[raw_name].reg_type_overrides = ai_overrides + end + if raw_name then + if ai_ctx then + out.atom_ctxs = out.atom_ctxs or {} + out.atom_ctxs[raw_name] = { rbind_atom = ai_ctx, info_line = line_of(lookahead), source = source } + end + if ai_phase then + out.atom_phases = out.atom_phases or {} + out.atom_phases[ai_phase] = out.atom_phases[ai_phase] or { atoms = {} } + out.atom_phases[ai_phase].atoms[#out.atom_phases[ai_phase].atoms + 1] = raw_name + end + end + return info_after +end + +--- Parse `atom_info(...)` immediately before `before_pos` (the MipsAtom_Proc_ token). +local function parse_atom_info_before(source, before_pos, raw_name, line_of, out, dest) + local i = before_pos - 1 + while i >= 1 and source:sub(i, i):match("%s") do i = i - 1 end + if source:sub(i, i) ~= ")" then return end + local depth = 0 + local j = i + while j >= 1 do + local c = source:sub(j, j) + if c == ")" then + depth = depth + 1 + elseif c == "(" then + depth = depth - 1 + if depth == 0 then break end + end + j = j - 1 + end + if j < 1 then return end + local k = j - 1 + while k >= 1 and source:sub(k, k):match("%s") do k = k - 1 end + local ident_end = k + while k >= 1 and source:sub(k, k):match("[%w_]") do k = k - 1 end + local ident_start = k + 1 + if source:sub(ident_start, ident_end) ~= "atom_info" then return end + parse_atom_info_after_decl(source, ident_start, raw_name, line_of, out, dest) +end + --- Parse: `MipsAtom_() [atom_info(, , )] { }` --- @param source string --- @param pos integer @@ -1265,53 +1339,8 @@ local function parse_mips_atom(source, pos, ident_end, line_of, out) local raw_name = duffle.read_ident(inner, 1) - -- Lookahead for atom_info(...) between `)` and `{`. Captures sub-calls; updates brace search start. - local brace_search_pos = after_paren - local lookahead = duffle.skip_ws_and_cmt(source, after_paren) - local look_ident, look_end = duffle.read_ident(source, lookahead) - if look_ident == "atom_info" then - local info_open = duffle.skip_ws_and_cmt(source, look_end) - if source:sub(info_open, info_open) == "(" then - local info_inner, info_after = duffle.read_parens(source, info_open) - -- info_line feeds the per-atom reg_type_overrides table. - local info_line = line_of(info_open) - local ai_binds, ai_reads, ai_writes, ai_view, ai_overrides, ai_ctx, ai_phase = scan_atom_info_subcalls(info_inner, info_line) - out.atom_infos[#out.atom_infos + 1] = { - atom_name = raw_name or "?", binds = ai_binds, - reads = ai_reads or {}, writes = ai_writes or {}, - view = ai_view, - reg_type_overrides = ai_overrides, - ctx_atom = ai_ctx, - phase = ai_phase, - info_line = line_of(lookahead), - } - if ai_view and raw_name then - out.atom_views[raw_name] = { - atom_name = raw_name, - binds_name = ai_view, - reg_type_overrides = ai_overrides, - info_line = line_of(lookahead), - } - elseif raw_name and ai_overrides then - -- Record per-atom overrides even without atom_view. - out.atom_views[raw_name] = out.atom_views[raw_name] or { atom_name = raw_name, binds_name = nil, reg_type_overrides = nil, info_line = line_of(lookahead) } - out.atom_views[raw_name].reg_type_overrides = ai_overrides - end - -- Project the per-atom atom_ctx / atom_phase declarations onto the global phase index. - if raw_name then - if ai_ctx then - out.atom_ctxs = out.atom_ctxs or {} - out.atom_ctxs[raw_name] = { rbind_atom = ai_ctx, info_line = line_of(lookahead), source = source } - end - if ai_phase then - out.atom_phases = out.atom_phases or {} - out.atom_phases[ai_phase] = out.atom_phases[ai_phase] or { atoms = {} } - out.atom_phases[ai_phase].atoms[#out.atom_phases[ai_phase].atoms + 1] = raw_name - end - end - brace_search_pos = info_after - end - end + -- Lookahead for atom_info(...) between `)` and `{`. + local brace_search_pos = parse_atom_info_after_decl(source, after_paren, raw_name, line_of, out, out.atom_infos) local body, after_brace, body_off = find_body_braces(source, brace_search_pos, open_paren + 1) if not body then return after_brace end @@ -1336,7 +1365,9 @@ local function parse_mips_atom_comp(source, pos, ident_end, line_of, out) local raw_name = duffle.read_ident(inner, 1) if not raw_name then return open_paren + 1 end - local body, after_brace, body_off = find_body_braces(source, after_paren, open_paren + 1) + out.component_atom_infos = out.component_atom_infos or {} + local brace_search_pos = parse_atom_info_after_decl(source, after_paren, strip_ac_prefix(raw_name), line_of, out, out.component_atom_infos) + local body, after_brace, body_off = find_body_braces(source, brace_search_pos, open_paren + 1) if not body then return after_brace end local name = strip_ac_prefix(raw_name) register_atom(out, "comp_bare", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source) @@ -1407,15 +1438,9 @@ local function parse_mips_atom_comp_proc_map(source, pos, ident_end, line_of, ou return after_paren end ---- Parse: `MipsAtom_Proc_(, , { })` — body is inside the LAST `{` in args. ---- Per Task 12.10: full support for the runtime-proc atom form. Registers the atom ---- with kind `"atom_proc"` so offsets.lua / components.lua can emit ---- * `mac_` aliases in `gen/macs.h` (the components pass) ---- * `atom_offset__X__Y` defs in `gen/offsets.h` (the offsets pass) ---- The atom name is the FIRST ident of the args (the second arg `ab` is the ---- atom-builder, not the name). Unlike `MipsAtomComp_Proc_`, there is no `ac_` ---- prefix on the symbol — `MipsAtom_Proc_` is the runtime-proc wrapper, so the ---- symbol IS the bare atom name (e.g. `normalize_v3s4`, not `ac_normalize_v3s4`). +--- Parse: `MipsAtom_Proc_(aa, { body })` — body is inside the LAST `{` in args. +--- Kind is `atom_proc`. The name is the preceding function ident as written. +--- Offsets walk this kind. Components do not emit a `mac_*` alias for it. --- @param source string --- @param pos integer --- @param ident_end integer @@ -1439,13 +1464,12 @@ local function parse_mips_atom_proc(source, pos, ident_end, line_of, out) local body, close_pos = duffle.read_braces(inner, last_brace_pos) if close_pos > #inner + 1 then return after_paren end - -- The atom name is derived from the preceding function declaration - -- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which - -- is now `aa`). The backward walk finds the function decl before open_paren - -- and strips the `_proc` suffix. + -- The atom name is the preceding function ident as written + -- (`internal MipsAtom* X(...)`). The first macro arg is the arena. local raw_name, args_inner, func_ident = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN) if not raw_name then raw_name = "?" end local name = strip_ac_prefix(raw_name) + parse_atom_info_before(source, pos, name, line_of, out, out.atom_infos) local reg_use_schema_name = nil local reg_use_param_name = nil if args_inner then @@ -1593,7 +1617,32 @@ local function register_typedef_alias(underlying, name, pos, line_of, out) } end -local function parse_reg_use_schema_body(body) +-- Layout of Reg_ only. Never the C data struct (V3_S4 has a pad field). +local REG_ALLOC_FIELDS = { + Reg_V3_S4 = { "x", "y", "z" }, + Reg_P3_S4 = { "x", "y", "z" }, + Reg_V3_S2 = { "x", "y", "z" }, +} + +local parse_reg_use_schema_body + +local function fields_for_reg_type(type_name, type_registry) + local reg_name = "Reg_" .. type_name + local entry = type_registry and type_registry[reg_name] + if entry and entry.body and parse_reg_use_schema_body then + local schema = parse_reg_use_schema_body(entry.body, type_registry) + if schema and schema.slots then + local names = {} + for _, slot in ipairs(schema.slots) do + if slot.name then names[#names + 1] = slot.name end + end + if #names > 0 then return names end + end + end + return REG_ALLOC_FIELDS[reg_name] +end + +parse_reg_use_schema_body = function(body, type_registry) local slots = {} local alias_to_slot = {} local slot_names = {} @@ -1728,7 +1777,25 @@ local function parse_reg_use_schema_body(body) after_close = duffle.skip_ws_and_cmt(body, after_close) if body:sub(after_close, after_close) == ";" then after_close = after_close + 1 end pos = after_close - elseif first == "Reg" then + elseif first == "Reg" or first == "Reg_" then + local typed_fields = nil + if first == "Reg_" then + if body:sub(after, after) ~= "(" then + errors[#errors + 1] = { kind = "reguse_malformed" } + return nil, errors + end + local type_inner, after_paren = duffle.read_parens(body, after) + if not type_inner then + errors[#errors + 1] = { kind = "reguse_malformed" } + return nil, errors + end + typed_fields = fields_for_reg_type(duffle.trim(type_inner), type_registry) + if not typed_fields then + errors[#errors + 1] = { kind = "reguse_malformed" } + return nil, errors + end + after = duffle.skip_ws_and_cmt(body, after_paren) + end local readonly = false local maybe_const, maybe_end = duffle.read_ident(body, after) if maybe_const == "const" then @@ -1741,8 +1808,16 @@ local function parse_reg_use_schema_body(body) return nil, errors end for _, n in ipairs(names) do - if not add_alias(n, n) then return nil, errors end - if not add_slot(n, { n }, readonly) then return nil, errors end + if typed_fields then + for _, field in ipairs(typed_fields) do + local path = n .. "." .. field + if not add_alias(path, path) then return nil, errors end + if not add_slot(path, { path }, readonly) then return nil, errors end + end + else + if not add_alias(n, n) then return nil, errors end + if not add_slot(n, { n }, readonly) then return nil, errors end + end end pos = new_pos else @@ -1792,7 +1867,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out) if not body then return after_brace end register_struct_type(body, name, pos, line_of, out) if name:sub(1, 7) == "RegUse_" then - local schema, schema_errors = parse_reg_use_schema_body(body) + local schema, schema_errors = parse_reg_use_schema_body(body, out.type_name_registry) if schema then schema.name = name schema.source_file = out._source_file @@ -2174,6 +2249,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies) raw_atoms = {}, binds = {}, atom_infos = {}, + component_atom_infos = {}, macros = {}, -- Raw marker evidence for annotation validation. The `debug_skip` boolean -- is stamped on the declaration record itself; the projection lives on AtomEntry.debug_skip. @@ -2429,6 +2505,7 @@ local function merge_corpus_registries(corpus) corpus.atom_ctxs = corpus.atom_ctxs or {} corpus.atom_phases = corpus.atom_phases or {} corpus.atom_infos = corpus.atom_infos or {} + corpus.component_atom_infos = corpus.component_atom_infos or {} corpus.atom_auto_regs = corpus.atom_auto_regs or {} corpus.phase_auto_regs = corpus.phase_auto_regs or {} corpus.collisions = corpus.collisions or {} @@ -2440,7 +2517,7 @@ local function merge_corpus_registries(corpus) for _, key in ipairs({ "register_alias_registry", "type_name_registry", "binds_by_name", "atoms_by_name", "atom_views", "atom_ctxs", "atom_phases", - "atom_infos", "collisions", "reg_use_schemas", "reg_use_errors", + "atom_infos", "component_atom_infos", "collisions", "reg_use_schemas", "reg_use_errors", }) do corpus[key] = {} end @@ -2533,6 +2610,9 @@ local function merge_corpus_registries(corpus) for _, info in ipairs(scan.atom_infos or {}) do corpus.atom_infos[#corpus.atom_infos + 1] = info end + for _, info in ipairs(scan.component_atom_infos or {}) do + corpus.component_atom_infos[#corpus.component_atom_infos + 1] = info + end for name, schema in pairs(scan.reg_use_schemas or {}) do if corpus.reg_use_schemas[name] == nil then diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index 97b6be2..273cd7e 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -279,6 +279,16 @@ local function classify_tokens(tokens) for tok_idx, t in ipairs(tokens) do local tok = t.tok local ident = tok:match("^([%w_]+)") or "?" + local is_delay_marker = false + local delay_marker = nil + if duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[ident] then + is_delay_marker = true + delay_marker = ident + local rest = tok:match("^[%w_]+%s+(.*)$") + if rest and rest ~= "" then + ident = rest:match("^([%w_]+)") or ident + end + end local nop_words = 0 if ident == "nop" then nop_words = 1 elseif ident == "nop2" then nop_words = 2 end @@ -343,6 +353,8 @@ local function classify_tokens(tokens) tc[tok_idx] = { ident = ident, + is_delay_marker = is_delay_marker, + delay_marker = delay_marker, nop_words = nop_words, nop_prefix = nop_run, is_yield = is_yield, @@ -961,6 +973,7 @@ local function analyze_hardware_relations(atom) semantic = relation.semantic, producer_word = prod.word, consumer_word = ev_word, + destination = prod.destination, gap = gap, required = prod.required, satisfied = satisfied, @@ -1697,9 +1710,10 @@ end --- and the tape runtime would jump to garbage. --- --- Rules: ---- 1. Every `mac_yield_load()` must be in a branch BD-slot (the immediately preceding token must be a branch). ---- 2. Every `mac_yield_tail()` must be the first instruction after an `atom_label()`, AND ---- at least one branch targeting that label must have `mac_yield_load()` in its BD-slot. +--- 1. Every `mac_yield_load()` must be in a branch BD-slot, or sit between two `atom_label`s. +--- Delay-marker prefixes are skipped when reading prev/next tokens. +--- 2. `mac_yield_tail()` is valid if every path that reaches it has already executed a `mac_yield_load()`. +--- A load in a branch BD slot always runs. Later branches that target the tail label may carry `nop`. --- 3. `mac_yield_tail()` as the atom-end terminator (last token) is a WARNING, not an error --- (the safe default for atom-endings is `mac_yield()` which re-loads `R_AtomJmp`). --- @@ -1718,53 +1732,76 @@ local function check_yield_load_tail_pairing(atom, _pipe_ctx, findings) return atom.line + line_in_body[tokens[idx].rel] end - -- ── Rule 1: every `mac_yield_load()` must be in a branch BD-slot, OR sit between two `atom_label`s (natural fall-through load pattern). - -- When the pattern is satisfied, the check stays silent; only violations emit findings. + local function is_delay_only(c) + return c and duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[c.ident] == true + end + + local function skip_delay(idx, step) + local i = idx + while i >= 1 and i <= n and is_delay_only(tc[i]) do + i = i + step + end + if i < 1 or i > n then return nil end + return i + end + + -- ── Rule 1: every `mac_yield_load()` must be in a branch BD-slot, OR sit between two `atom_label`s. for tok_idx = 1, n do local c = tc[tok_idx] if c.ident == "mac_yield_load" then - local prev_tc = (tok_idx >= 2) and tc[tok_idx - 1] or nil - -- Look for the next `atom_label()` token (skip `atom_offset` markers; check immediately-adjacent first). - local next_label_tc = (tok_idx + 1 <= n) and tc[tok_idx + 1] or nil - if next_label_tc and next_label_tc.ident ~= "atom_label" then - next_label_tc = nil - for j = tok_idx + 1, n do - local t = tc[j] - if t.ident == "atom_label" then - next_label_tc = t - break - end + local prev_i = skip_delay(tok_idx - 1, -1) + local prev_tc = prev_i and tc[prev_i] or nil + local next_label_tc = nil + local j = skip_delay(tok_idx + 1, 1) + while j do + local t = tc[j] + if t.ident == "atom_label" then + next_label_tc = t + break end + if t.ident ~= "atom_offset" then break end + j = skip_delay(j + 1, 1) end local natural_fallthrough = prev_tc and prev_tc.is_atom_label and next_label_tc ~= nil if not natural_fallthrough then - if tok_idx < 2 or not prev_tc.is_branch then + if not prev_tc or not prev_tc.is_branch then local prev_ident = prev_tc and (prev_tc.ident or "?") or "" local next_ident = next_label_tc and (next_label_tc.ident .. "(" .. (next_label_tc.label_name or "?") .. ")") or "" findings[#findings + 1] = { atom = atom.name, - line = tok_idx >= 2 and line_for(tok_idx) or atom.line, + line = prev_i and line_for(tok_idx) or atom.line, check = "yield_load_tail_pairing", kind = "error", msg = string.format( "%s at line %d has `mac_yield_load()` at word %d but the previous token is `%s`, not a branch — and the next `atom_label()` token is `%s` — `mac_yield_load()` must fill a branch BD-slot or sit between two `atom_label`s for the natural fall-through load." - , atom.name, tok_idx >= 2 and line_for(tok_idx) or atom.line, tok_idx, prev_ident, next_ident), + , atom.name, prev_i and line_for(tok_idx) or atom.line, tok_idx, prev_ident, next_ident), } end end end end - -- ── Rule 2: every `mac_yield_tail()` must be at a labeled target whose branch BD-slot is `mac_yield_load()`. + -- ── Rule 2: `mac_yield_tail()` is valid if every path that reaches it already ran `mac_yield_load()`. + local function load_covers_tail(tail_idx) + for i = 1, tail_idx - 1 do + if tc[i].ident == "mac_yield_load" then + local prev_i = skip_delay(i - 1, -1) + local prev = prev_i and tc[prev_i] or nil + if prev and (prev.is_branch or prev.is_atom_label) then + return true + end + end + end + return false + end + for tok_idx = 1, n do local c = tc[tok_idx] if c.ident ~= "mac_yield_tail" then goto continue end - -- The immediately preceding token must be an `atom_label()` (no instructions between them). - local prev_idx = tok_idx - 1 - if prev_idx < 1 or not tc[prev_idx].is_atom_label then + local prev_idx = skip_delay(tok_idx - 1, -1) + if not prev_idx or not tc[prev_idx].is_atom_label then if tok_idx == n then - -- Atom-ending case: last token is `mac_yield_tail()` without a preceding label. WARNING. findings[#findings + 1] = { atom = atom.name, line = line_for(tok_idx), @@ -1789,36 +1826,14 @@ local function check_yield_load_tail_pairing(atom, _pipe_ctx, findings) end local label_name = tc[prev_idx].label_name - -- Find at least one branch targeting `label_name` whose BD-slot is `mac_yield_load()`. - local found_pairing = false - for branch_idx = 1, n do - local bt = tc[branch_idx] - if bt.is_branch and bt.branch_label == label_name then - local bd_idx = branch_idx + 1 - local bd_tc = bd_idx <= n and tc[bd_idx] or nil - if bd_tc and bd_tc.ident == "mac_yield_load" then - found_pairing = true - else - findings[#findings + 1] = { - atom = atom.name, - line = line_for(branch_idx), - check = "yield_load_tail_pairing", - kind = "error", - msg = string.format( - "%s at line %d has `mac_yield_tail()` at label `%s` (word %d) but the branch targeting it (at word %d) has BD-slot `%s` instead of `mac_yield_load()`." - , atom.name, line_for(branch_idx), label_name, tok_idx, branch_idx, bd_tc and bd_tc.ident or "?"), - } - end - end - end - if not found_pairing then + if not load_covers_tail(tok_idx) then findings[#findings + 1] = { atom = atom.name, line = line_for(tok_idx), check = "yield_load_tail_pairing", kind = "error", msg = string.format( - "%s at line %d has `mac_yield_tail()` at label `%s` but no branch in the body targets this label with `mac_yield_load()` in its BD-slot — R_AtomJmp would not be loaded." + "%s at line %d has `mac_yield_tail()` at label `%s` but no path that reaches it has executed `mac_yield_load()` — R_AtomJmp would not be loaded." , atom.name, line_for(tok_idx), label_name), } end @@ -1972,6 +1987,28 @@ local function check_gpu_portstore_shape(atom, pipe_ctx, findings) end end + -- Token-name gp0_contrib is 0 when bodies use gte_sw. Count expanded prim-buffer stores. + if contrib == 0 then + for _, ev in ipairs(atom.paths.word_events or {}) do + local enc = ev.encoder or "" + if enc == "store_word" or enc == "store_half" or enc == "store_byte" or enc == "gte_sw" then + local text = (ev.call_text or "") .. " " .. (ev.root_call_text or "") + local hit = text:find("R_PrimCursor", 1, true) + if not hit then + for _, arg in ipairs(ev.args or {}) do + if tostring(arg):find("R_PrimCursor", 1, true) then + hit = true + break + end + end + end + if hit then + contrib = contrib + 1 + end + end + end + end + if not cmd_byte then if saw_prim_write and not saw_format then findings[#findings + 1] = { @@ -2048,7 +2085,9 @@ local function analyze_atom_paths(atom, pipe_ctx) local c = tc[tok_idx] local ident = c.ident local cost - if ident:sub(1, #"mac_") == "mac_" then + if duffle.DELAY_MARKERS and duffle.DELAY_MARKERS[ident] then + cost = 0 + elseif ident:sub(1, #"mac_") == "mac_" then -- `mac_*` token: lookup corpus.components[bare_name].cycle_cost. local bare = ident:sub(#"mac_" + 1) local comp = pipe_ctx.components_by_name and pipe_ctx.components_by_name[bare] @@ -2227,13 +2266,11 @@ end -- Per-source rule (called once per source via the CHECK_RULES dispatch). -- Signature matches the per_source shape established by check_semantic_reg_defaults. -- --- Severity: WARNING (build continues). --- The rule is intentionally permissive because the production `code/duffle/` and `code/gte_hello/` --- sources use R_* aliases in atom_reads / atom_writes that may not yet be opted in via the bare `atom_reg` marker. --- R_TapePtr / R_AtomJmp / R_PrimCursor / R_FaceCursor / R_VertBase / R_OtBase ARE opted in. --- Raw C-ABI aliases like R_T0..R_T3 require explicit opt-in; the prototype keeps wave-context registration explicit. --- no auto-include of wave-context; explicit opt-in only). --- Warnings keep the build green and report aliases that need explicit registration. +-- Physical GPRs are members. Opt-in atom_reg stays for aliases. +local function is_physical_gpr(reg) + return type(reg) == "string" and (reg:match("^R_T[0-7]$") ~= nil or reg:match("^R_V[01]$") ~= nil) +end + local function check_enum_alias_membership(_src, pipe_ctx, findings) local reg_registry = pipe_ctx.register_alias_registry or {} @@ -2271,7 +2308,7 @@ local function check_enum_alias_membership(_src, pipe_ctx, findings) end end for _, reg in ipairs(ai.reads or {}) do - if not reg_registry[reg] then + if not reg_registry[reg] and not is_physical_gpr(reg) then findings[#findings + 1] = { atom = atom_name, line = info_line, check = "enum_alias_membership", kind = "warning", @@ -2281,7 +2318,7 @@ local function check_enum_alias_membership(_src, pipe_ctx, findings) end end for _, reg in ipairs(ai.writes or {}) do - if not reg_registry[reg] then + if not reg_registry[reg] and not is_physical_gpr(reg) then findings[#findings + 1] = { atom = atom_name, line = info_line, check = "enum_alias_membership", kind = "warning", @@ -2352,6 +2389,18 @@ local function find_field_by_name(type_entry, field_name) return nil end +-- Walk typedef aliases to the struct that owns the fields table. Depth matches propagate_type_sizes. +local function resolve_type_with_fields(type_name, type_registry, depth) + if depth > 8 then return nil end + local entry = type_registry[type_name] + if not entry then return nil end + if entry.fields then return entry end + if entry.kind == "typedef" and entry.underlying_type and entry.underlying_type ~= "" then + return resolve_type_with_fields(entry.underlying_type, type_registry, depth + 1) + end + return entry +end + -- True iff a (field, type_registry) pair is a leaf scalar (safe to dereference as a tape-payload field). -- Pointer-to-X is always a leaf; non-pointer struct members fail the leaf test. local function is_field_leaf(field, type_registry) @@ -2379,8 +2428,12 @@ local function check_binds_no_substruct_deref(_src, pipe_ctx, findings) local field_name = tc_entry.o_arg2 local body_line = a.line + (line_in_body[tokens[ti].rel] or 0) - local type_entry = type_registry[type_name] - if not type_entry or not type_entry.fields then + local type_entry = resolve_type_with_fields(type_name, type_registry, 1) + -- PSYQ opaques such as DisplayEnv have no fields table. Do not invent the layout. + local skip_opaque = type_name == "DisplayEnv" and (not type_entry or not type_entry.fields) + if skip_opaque then + -- leave this token + elseif not type_entry or not type_entry.fields then findings[#findings + 1] = { atom = a.name, line = body_line, check = "binds_no_substruct_deref", kind = "warning", @@ -2450,6 +2503,44 @@ local function atom_body_token_source_line(atom, token, line_in_body) return (atom.line or 0) + body_line - 1 end +local function ctrl_alias_from_text(text) + return tostring(text or ""):match("gte_cr_[%w_]+") +end + +local function ctrl_writes_in_atom(atom) + local out = {} + for _, ev in ipairs((atom.paths and atom.paths.word_events) or {}) do + if (ev.encoder or "") == "gte_mv_to_ctrl_r" then + local alias = ev.args and ev.args[2] + if type(alias) ~= "string" or not alias:match("^gte_cr_") then + alias = ctrl_alias_from_text(ev.call_text) or ctrl_alias_from_text(ev.root_call_text) + end + local src = ev.args and ev.args[1] + if type(src) == "string" then src = src:match("[%w_]+") end + if alias then + out[#out + 1] = { + alias = alias, + src = src, + line = ev.line or atom.line, + } + end + end + end + if #out == 0 then + for _, t in ipairs((atom.paths and atom.paths.tokens) or {}) do + local tok = t.tok or "" + if tok:match("^gte_mv_to_ctrl_r") then + local alias = ctrl_alias_from_text(tok) + local src = tok:match("%(%s*([%w_]+)") + if alias then + out[#out + 1] = { alias = alias, src = src, line = atom.line } + end + end + end + end + return out +end + -- Check #N: gte_cr_alias_writes -- Fires one warning per atom per alias-group when the atom body touches two -- distinct aliases from the same group. Aliases within a group write to the @@ -2590,6 +2681,135 @@ local function check_gte_cr_TR_naming(atom, _pipe_ctx, findings) end end +local function check_gte_cr_alias_writes_xatom(_src, pipe_ctx, findings) + local slot_state = {} + for _, atom in ipairs(pipe_ctx.atoms or {}) do + atom.paths = atom.paths or {} + atom.paths.forward_state = atom.paths.forward_state or {} + local outgoing = {} + for slot, prev in pairs(slot_state) do + outgoing[slot] = prev + end + for _, w in ipairs(ctrl_writes_in_atom(atom)) do + local group = find_alias_pair_for(w.alias, duffle) + if group then + local slot = group[1] + local prev = slot_state[slot] + if prev and prev.alias ~= w.alias and prev.atom ~= atom.name then + findings[#findings + 1] = { + atom = atom.name or "", + line = w.line, + check = "gte_cr_alias_writes_xatom", + kind = "warning", + msg = string.format( + "atom '%s' writes %s to C2[%d]; atom '%s' already wrote %s" + , atom.name or "", w.alias, slot, prev.atom, prev.alias), + } + end + slot_state[slot] = { alias = w.alias, atom = atom.name, line = w.line } + outgoing[slot] = slot_state[slot] + end + end + atom.paths.forward_state.ctrl_writes_by_slot = outgoing + end +end + +local function check_gte_packed_writes(atom, _pipe_ctx, findings) + local writes = ctrl_writes_in_atom(atom) + local first_idx = {} + for i, w in ipairs(writes) do + if first_idx[w.alias] == nil then first_idx[w.alias] = i end + end + for _, rel in ipairs(duffle.GTE_PACKED_SLOT_RELATIONS or {}) do + local i1 = first_idx[rel.first] + local i2 = first_idx[rel.second] + if i1 and i2 and i2 < i1 then + findings[#findings + 1] = { + atom = atom.name or "", + line = writes[i2].line, + check = "gte_packed_writes", + kind = "warning", + msg = string.format( + "atom '%s' writes %s before %s on packed C2[%d]" + , atom.name or "", rel.second, rel.first, rel.slot), + } + end + end +end + +local function check_ctc2_chain_source_preservation(atom, _pipe_ctx, findings) + local live = {} + local function mark_live(src, alias) + if src and alias and alias:match("^gte_cr_RT") then + live[src] = true + end + end + for _, ev in ipairs((atom.paths and atom.paths.word_events) or {}) do + local enc = ev.encoder or "" + if enc == "gte_mv_to_ctrl_r" then + local src = ev.args and ev.args[1] + if type(src) == "string" then src = src:match("[%w_]+") end + local alias = ev.args and ev.args[2] + if type(alias) ~= "string" or not alias:match("^gte_cr_") then + alias = ctrl_alias_from_text(ev.call_text) + end + mark_live(src, alias) + elseif enc == "load_word" then + local dest = ev.args and ev.args[1] + if type(dest) == "string" then dest = dest:match("[%w_]+") end + if dest and live[dest] == true then + live[dest] = "clobbered" + end + elseif enc:match("^gte_cmdw_") then + for gpr, state in pairs(live) do + if state == "clobbered" then + findings[#findings + 1] = { + atom = atom.name or "", + line = ev.line or atom.line, + check = "ctc2_chain_source_preservation", + kind = "warning", + msg = string.format( + "atom '%s' reloads %s after ctc2 into RT and before %s" + , atom.name or "", gpr, enc), + } + end + end + live = {} + end + end + if not next((atom.paths and atom.paths.word_events) or {}) then + local pending = {} + for _, t in ipairs((atom.paths and atom.paths.tokens) or {}) do + local tok = t.tok or "" + local ident = tok:match("^([%w_]+)") or "" + if ident == "gte_mv_to_ctrl_r" then + mark_live(tok:match("%(%s*([%w_]+)"), ctrl_alias_from_text(tok)) + elseif ident == "load_word" then + local dest = tok:match("%(%s*([%w_]+)") + if dest and live[dest] == true then live[dest] = "clobbered" end + elseif ident:match("^gte_cmdw_") then + for gpr, state in pairs(live) do + if state == "clobbered" then + pending[#pending + 1] = { gpr = gpr, enc = ident } + end + end + live = {} + end + end + for _, p in ipairs(pending) do + findings[#findings + 1] = { + atom = atom.name or "", + line = atom.line, + check = "ctc2_chain_source_preservation", + kind = "warning", + msg = string.format( + "atom '%s' reloads %s after ctc2 into RT and before %s" + , atom.name or "", p.gpr, p.enc), + } + end + end +end + -- check_immediate_field_width — flags integer literals passed to instruction -- macros that exceed the immediate field width. Reads `IMMEDIATE_FIELD_WIDTHS` -- from duffle.lua. Only fires on parseable integer literals; register names, @@ -2670,12 +2890,13 @@ local function check_immediate_field_width(atom, pipe_ctx, findings) "%s: immediate %d at arg %d overflows %d-bit unsigned field (valid 0..%d)", ev_ident, value, rule.arg, width, field_max), } - end - end - end end end end + ::continue_token:: + end + end +end end end end @@ -2707,6 +2928,9 @@ local CHECK_RULES = { { name = "gpu_portstore_shape", per_atom = check_gpu_portstore_shape }, { name = "per_atom_cycle_budget", per_atom = check_per_atom_cycle_budget }, { name = "gte_cr_alias_writes", per_atom = check_gte_cr_alias_writes }, + { name = "gte_cr_alias_writes_xatom", per_source = check_gte_cr_alias_writes_xatom }, + { name = "gte_packed_writes", per_atom = check_gte_packed_writes }, + { name = "ctc2_chain_source_preservation", per_atom = check_ctc2_chain_source_preservation }, { name = "rtdiagonal_completeness", per_atom = check_rtdiagonal_completeness }, { name = "gte_cr_TR_naming", per_atom = check_gte_cr_TR_naming }, { name = "immediate_field_width", per_atom = check_immediate_field_width },