From 640dab7e616f2d0b83cde1007088b866434f508e Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 13 Aug 2026 18:51:09 -0400 Subject: [PATCH] wip: starting to review and update lua metaprogram with more modeling of gte. --- code/duffle/gte.h | 24 ++++ code/duffle/lottes_tape.h | 40 +++++- code/hello_camera/hello_camera.atom.c | 17 +-- code/hello_camera/hello_camera.c | 61 ++------- scripts/duffle.lua | 15 ++ scripts/passes/static_analysis.lua | 188 +++++++++++++++++++++++++- 6 files changed, 284 insertions(+), 61 deletions(-) diff --git a/code/duffle/gte.h b/code/duffle/gte.h index 416e285..7950e7d 100644 --- a/code/duffle/gte.h +++ b/code/duffle/gte.h @@ -191,6 +191,30 @@ enum { gte_mask_fake_cmd = 0x1F, }; +/* --- GTE Control Register Aliases (Pitfall 1) --- + * Three pairs of aliases map to the SAME C2 control-register slot on real silicon: + * C2[24] = gte_cr_RBK (background R) | gte_cr_OFX (screen offset X) + * C2[25] = gte_cr_GBK (background G) | gte_cr_OFY (screen offset Y) + * C2[26] = gte_cr_BBK (background B) | gte_cr_H (projection plane distance H) + * Cross-alias writes inside one atom body, or across the wave-context boundary, + * silently clobber each other. The metaprogram's check_gte_cr_alias_writes + * (CHECK_RULES row) warns about each pair per source. See + * docs/gte_reference.md §"Control-register alias table" for the silicon + * rationale and the libgte outer-product convention. + */ + +/* --- RT-matrix packed-slot convention (Pitfall 4) --- + * The silicon packs two 16-bit RT elements per 32-bit C2 slot: + * C2[2] = (RT22 << 16) | RT13 (gte_cr_RT13 writes the low half, gte_cr_RT22 writes the high half) + * C2[4] = (RT33 << 16) | RT22 (gte_cr_RT22 writes the low half — clobbers prior RT22 value if RT13 was also written) + * OP and MVMVA read D1/D2/D3 from these packed slots. The libgte outer-product + * convention (see ac_apply_matrix_lv at gte.atom.c:108-122) writes C2[2] then + * C2[4] in sequence; the SECOND write's low half is RT22, not RT13. An agent + * who writes gte_cr_RT13 then gte_cr_RT22 to the SAME source GPR clobbers the + * RT13 value. See docs/gte_reference.md §"RT-matrix packed-slot convention" + * for the canonical write pattern. + */ + /* --- GTE Control Register Indices (for ctc2/cfc2) --- * Preprocessor-visible integer ids for the COP2 control register file. * Each enum value is bound to a parallel `_Code` `#define` so the preprocessor can stringify the integer (for `reg_str`/`rgcc` paths). diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index a508fb3..7d16ae7 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -103,7 +103,15 @@ enum { typedef U4 const MipsCode; // Underlying type to mips asm words. typedef Slice_(MipsCode); -typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that must terminate with an ac_yield. +typedef U4 const MipsAtom; +typedef Slice_(MipsAtom); +// Sometimes a user will define a bundle of atoms that represent a procedure of work as: +// MipsAtom* [...]; +// Unfortuantely if using slice_from_array it will make the slice's pointer: MipsAtom** so this enforce its defined as MipsAtom* +// TODO(Ed): Alternatively we can make the MipsAtom an opaque pointer to the atom... so that the blow returns 'MipsAtom'. +#define atombundle_from_array(array) (Slice_MipsAtom){.ptr=array[0],.len=Array_len(array)} + +// Underlying type to an ptr to an array of mips asm words that must terminate with an ac_yield. #define MipsAtom_(sym) MipsCode sym [] align_(4) = // Used for atoms with value-args @@ -139,7 +147,7 @@ typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that (the identifier embeds the source line, so duplicates across `#include`d files don't collide). */ #define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0 - typedef Slice_(MipsAtom); typedef Slice_MipsAtom Tape; +typedef Slice_MipsAtom Tape; /* The 'Exit' Atom */ atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }; @@ -276,6 +284,34 @@ FI_ MipsAtom* atomarena_push(AtomArena_R aa, Slice_MipsCode code) { FI_ void atomarena_reset(AtomArena_R aa) { aa->used = 0; } #pragma region Atom Arena +#pragma region RegFile (Register File Allocator) +// A specialized allocator utilized to help the user track which registers are bound to values +// that must be preserved for the arena's bounds. + +enum { + RegFileArena_Len, +}; +typedef Enum_(U4, RegFileEntry) { + // TODO(Ed): Define RF_Field, each field is maped by index + bit pos. + // the index is the upper portion of a U4 and the bit pos in the lower pos. + + regfileentry_todo_, + // TODO(Ed): Is there a trick we can do with the current register enums to + // just resolve an entry automatically when doing a pin? +}; +typedef Struct_(RegFile) { + U1 GPR[RegFileArena_Len]; + U1 GTE[RegFileArena_Len]; + U1 GP[RegFileArena_Len]; +}; + +void regfile_pin(U4 register) { + + assert(false); +} + +#pragma endregion RegFileArena (Register File Allocator) + #pragma region Mips Atom Procs #pragma endregion Mips Atom Procs diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index 4e4f79d..5433c00 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -563,17 +563,12 @@ internal MipsAtom* resolve_look_at__matrix_vector_proc(AtomArena_R aa nop, /* === Load RT matrix from look_at into C2[0..4] via ctc2 === - * Exact same sequence as set_gte_mt3s2s4 / C11's ApplyMatrixLV. */ - load_word(r_tmp0, r_look_at, 0), nop, - gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT11), - load_word(r_tmp0, r_look_at, 4), nop, - gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT12), - load_word(r_tmp0, r_look_at, 8), nop, - gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT13), - load_word(r_tmp0, r_look_at, 12), nop, - gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT21), - load_half_u(r_tmp0, r_look_at, 16), nop, - gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT22), + * Exact s ame sequence as set_gte_mt3s2s4 / C11's ApplyMatrixLV. */ + load_word( r_tmp0, r_look_at, 0), nop, gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT11), + load_word( r_tmp0, r_look_at, 4), nop, gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT12), + load_word( r_tmp0, r_look_at, 8), nop, gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT13), + load_word( r_tmp0, r_look_at, 12), nop, gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT21), + load_half_u(r_tmp0, r_look_at, 16), nop, gte_mv_to_ctrl_r(r_tmp0, gte_cr_RT22), nop2, /* CTC2 retirement (2 slots × 5 ctc2s) */ /* Load pos = -eye after the matrix load releases r_tmp0. */ diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index 784eb72..ffc1464 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -340,7 +340,7 @@ internal void resolve_look_at_init(void) { * the matrix pointer (popped from tape). It does NOT need GPR * assignment from us — it has its own internal GPR usage. * We just take its address. */ - smem.resolve_look_at_atom_addrs[9] = (MipsAtom*) & set_gte_mt3s2s4; + smem.resolve_look_at_atom_addrs[7] = (MipsAtom*) & set_gte_mt3s2s4; /* === ATOM 6b: matrix_vector (RT * (-eye) >> 12) === * Uses mac_apply_matrix_lv component macro which internally uses @@ -352,7 +352,7 @@ internal void resolve_look_at_init(void) { U4 const r_tmp0_6b = R_T2; U4 const r_tmp1_6b = R_T3; U4 const r_tmp2_6b = R_T5; - smem.resolve_look_at_atom_addrs[7] = resolve_look_at__matrix_vector_proc(& ab, + smem.resolve_look_at_atom_addrs[8] = resolve_look_at__matrix_vector_proc(& ab, r_scratch_6b, r_peye_6b, r_look_at_6b, r_tmp0_6b, r_tmp1_6b, r_tmp2_6b); @@ -361,7 +361,7 @@ internal void resolve_look_at_init(void) { U4 const r_scratch_6c = R_ResolveScratch; U4 const r_off_ptr_6c = R_T1; /* &scratch.eye (= off dst) */ U4 const r_tmp0_6c = R_T2; - smem.resolve_look_at_atom_addrs[8] = resolve_look_at__trans_matrix_proc(& ab, + smem.resolve_look_at_atom_addrs[9] = resolve_look_at__trans_matrix_proc(& ab, r_look_at_6c, r_scratch_6c, r_off_ptr_6c, r_tmp0_6c); /* Sanity check: arena didn't overflow. */ @@ -386,43 +386,30 @@ I_ void resolve_look_at( , P3_S4* target , V3_S4* up_in ){ - // tb_emit_bundle(tb, slice_from_array(MipsAtom, smem.resolve_look_at_atom_addrs)); - - /* Atom 0: input_and_sub — stages eye/up_in into scratchpad + computes fwd. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[0]); { - tb_data(tb, u4_(target)); /* Binds_ResolveLookAtSub.target (C-side P3_S4*) */ - tb_data(tb, u4_(eye)); /* Binds_ResolveLookAtSub.eye (C-side P3_S4*) */ - tb_data(tb, u4_(up_in)); /* Binds_ResolveLookAtSub.up_in (C-side V3_S4*) */ - tb_data(tb, u4_(smem.scratchpad)); /* Binds_ResolveLookAtScratch.scratch_base */ + tb_data(tb, u4_(target)); + tb_data(tb, u4_(eye)); + tb_data(tb, u4_(up_in)); + tb_data(tb, u4_(smem.scratchpad)); } - /* Atoms 1 + 2: enabled. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[1]); { } tb_emit(tb, smem.resolve_look_at_atom_addrs[2]); { } tb_emit(tb, smem.resolve_look_at_atom_addrs[3]); { } tb_emit(tb, smem.resolve_look_at_atom_addrs[4]); { } tb_emit(tb, smem.resolve_look_at_atom_addrs[5]); { } - /* Atom 6a: populate — pop look_at* for the matrix destination. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[6]); { - tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ + tb_data(tb, u4_(look_at)); } - /* Atom 6a.5: load_rt — pop look_at* again, ctc2 look_at->m[][] into GTE - * C2[0..4]. Pattern identical to set_gte_mt3s2s4 (proven correct for - * cube rendering via RTPT/RTPS). The mac_yield between atoms gives - * the GTE pipeline time to fully retire the ctc2s. */ - tb_emit(tb, smem.resolve_look_at_atom_addrs[9]); { - tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ - } - /* Atom 6b: matrix_vector — pops look_at* for mac_apply_matrix_lv (which loads - * the RT matrix from it). Reads eye from scratch, packs pos as SVECTOR, - * lwc2 into V0, RTPS (sf=1, v=0, cv=3, mx=0), stores MAC1/2/3 → scratch+96. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[7]); { - tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ + tb_data(tb, u4_(look_at)); } - /* Atom 6c: trans_matrix — pop look_at* for the matrix destination. */ tb_emit(tb, smem.resolve_look_at_atom_addrs[8]); { - tb_data(tb, u4_(look_at)); /* Binds_ResolveLookAtPopAndTrans.look_at (MT3_S2S4*) */ + tb_data(tb, u4_(look_at)); + } + tb_emit(tb, smem.resolve_look_at_atom_addrs[9]); { + tb_data(tb, u4_(look_at)); } } @@ -485,26 +472,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf) tb.used = 0; tb_scope_run(& tb) { resolve_look_at(& tb, & smem.cam.look_at, & smem.cam.pos, & smem.cube.pos, & v3s4(0, -fp_one, 0)); } - V3_S4 right, up, forward; - V3_S4 ux, uy, uz; - V3_S4 pos, off; - - ResolveLookAtScratch_V scratch = C_scratch(ResolveLookAtScratch_V); - - /* Atoms 0-5 emit into scratch; bundle dispatch for atom 6 is still - * commented at the resolve_look_at_init helper. Until atom 6 is - * enabled, populate look_at.m[][] from the wave-context outputs. */ - forward = scratch->fwd; - uz = scratch->uz; - right = scratch->right; - ux = scratch->ux; - up = scratch->up; - uy = scratch->uy; - - // pos = smem.cam.pos; mul_v3s4(& pos, v3s4(-1,-1,-1)); - - // mul_m3s2_v3s4(& smem.cam.look_at, & pos, & off); - // trans_m3s2( & smem.cam.look_at, & off); } // Draw cube @@ -620,7 +587,7 @@ int main(void) Ent_Cube* cube = & smem.cube; cube->rot = v3s2(0, 0, 0); cube->scale = v3s4_fp_one(); - cube->accel = v3s4(0, 0, 0); + cube->accel = v3s4(0, 1, 0); cube->pos = v3s4(0, -400, 1800); } ent_floor_init(& smem.floor.verts, & smem.floor.faces); { diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 33c7377..eed6a3d 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -1313,6 +1313,21 @@ M.GTE_COMMAND_LATCH_WINDOWS = { }, } +--- GTE control-register alias groups. +--- Aliases within a group write to the same C2 control-register slot on real silicon +--- (the silicon double-maps some C2 slots across multiple PSX SDK / libgte conventions). +--- Aliases across groups write to distinct C2 slots. +--- +--- Cross-alias writes inside one atom body, or across the wave-context boundary, +--- silently clobber each other. The `check_gte_cr_alias_writes` check warns about +--- each pair per source. See `docs/gte_reference.md` §"Control-register alias table" +--- for the silicon rationale and the libgte outer-product convention. +M.GTE_CR_ALIAS_GROUPS = { + { 24, { "gte_cr_RBK", "gte_cr_OFX" } }, -- background R vs screen offset X + { 25, { "gte_cr_GBK", "gte_cr_OFY" } }, -- background G vs screen offset Y + { 26, { "gte_cr_BBK", "gte_cr_H" } }, -- background B vs projection plane distance H +} + -- 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. diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index d084d39..7be1a9d 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -2388,6 +2388,184 @@ end -- ════════════════════════════════════════════════════════════════════════════ +-- ════════════════════════════════════════════════════════════════════════════ +-- GTE control-register alias + RT-diagonal + TR-naming helpers and checks +-- ════════════════════════════════════════════════════════════════════════════ + +--- Resolve a `gte_cr_` ident to its alias-group entry, or nil if the alias +--- is in a distinct-slot group (or the alias name is not a known C2 control-register alias). +--- Reads `M.GTE_CR_ALIAS_GROUPS` from `duffle.lua`. +local function find_alias_pair_for(alias_name, duffle) + local groups = (duffle and duffle.GTE_CR_ALIAS_GROUPS) or {} + for _, group in ipairs(groups) do + for _, name in ipairs(group[2] or {}) do + if name == alias_name then return group end + end + end + return nil +end + +-- True iff `c` (a TokClass entry) is a CPU→COP2 control-register transfer +-- (`gte_mv_to_ctrl_r` / `gte_mv_from_ctrl_r`). +local function is_ctrl_r_transfer(c) + if c == nil then return false end + return c.ident == "gte_mv_to_ctrl_r" or c.ident == "gte_mv_from_ctrl_r" +end + +-- Resolve a token's source line. The per-token `line` is the body-relative +-- line; `atom.line` is the source line of the atom declaration; `line_in_body` +-- (atom.paths) maps a body-relative line to its source line. The arithmetic +-- `atom.line + line_in_body[tok.rel] - 1` matches the convention used by +-- check_abi_handoff and check_control_transfer_delay_slot_use elsewhere. +local function atom_body_token_source_line(atom, token, line_in_body) + if line_in_body == nil or token == nil or token.rel == nil then + return atom.line or 0 + end + local body_line = line_in_body[token.rel] + if body_line == nil then return atom.line or 0 end + return (atom.line or 0) + body_line - 1 +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 +-- same C2 control-register slot on real silicon; cross-alias writes inside +-- one atom body silently clobber each other. +-- +-- Severity: warning. Build continues. The libgte outer-product convention +-- uses only RT-row aliases (which are NOT in `M.GTE_CR_ALIAS_GROUPS`), so +-- the canonical convention does not trigger this check. +local function check_gte_cr_alias_writes(atom, pipe_ctx, findings) + local groups = pipe_ctx.gte_cr_alias_groups or {} + if not next(groups) then return end + + local tokens = atom.paths and atom.paths.tokens or {} + local tc = atom.paths and atom.paths.tok_class or {} + local line_in_body = atom.paths and atom.paths.line_in_body + if not next(tokens) then return end + + -- Build a per-group set of (alias, source_line) pairs touched in this atom body. + -- Walks every token; when the token is a ctrl-r transfer, the alias is at + -- position tok_idx + 2 (rt, alias, [imm-or-arg]). The pre-classified + -- `tc` table tells us whether the token is a ctrl-r transfer and what its + -- source line is. + local touched = {} + for tok_idx, token in ipairs(tokens) do + local c = tc[tok_idx] + if is_ctrl_r_transfer(c) and tokens[tok_idx + 2] then + local alias = tokens[tok_idx + 2].tok + local group = find_alias_pair_for(alias, pipe_ctx.duffle) + if group then + touched[group[1]] = touched[group[1]] or {} + touched[group[1]][#touched[group[1]] + 1] = { + alias = alias, + line = atom_body_token_source_line(atom, token, line_in_body), + } + end + end + end + + -- Fire one warning per group touched with 2+ distinct aliases. + for slot, hits in pairs(touched) do + local seen = {} + local distinct = {} + for _, h in ipairs(hits) do + if not seen[h.alias] then + seen[h.alias] = true + distinct[#distinct + 1] = h + end + end + if #distinct >= 2 then + local aliases = {} + for _, d in ipairs(distinct) do aliases[#aliases + 1] = d.alias end + findings[#findings + 1] = { + atom = atom.name or "", + line = distinct[1].line, + check = "gte_cr_alias_writes", + kind = "warning", + msg = string.format( + "atom '%s' touches %d aliases that share C2[%d]: %s; verify the intent" + , atom.name or "", #distinct, slot, table.concat(aliases, ", ")), + } + end + end +end + +-- Check #N+1: rtdiagonal_completeness +-- Fires one info per atom body when the bare `gte_cmdw_mvmva` macro is used. +-- The bare macro encodes only the cmd field; the canonical libgte-2-pass +-- shape uses `gte_cmdw_mvmva_c11_pass2_exact = 0x4A49E012` (gte.h:430). +-- +-- Severity: info by default. Escalates to warning when +-- `GTE_RT_DIAGONAL_STRICT=1` env var is set (CI / production builds). +-- +-- The bare macro IS the right call for the canonical libgte outer-product +-- convention, so this is an opt-out hint rather than a hard warning. +local function check_rtdiagonal_completeness(atom, _pipe_ctx, findings) + local tokens = atom.paths and atom.paths.tokens or {} + local tc = atom.paths and atom.paths.tok_class or {} + local line_in_body = atom.paths and atom.paths.line_in_body + if not next(tokens) then return end + local strict = os.getenv("GTE_RT_DIAGONAL_STRICT") == "1" + for tok_idx, token in ipairs(tokens) do + local c = tc[tok_idx] + if c and c.ident == "gte_cmdw_mvmva" then + findings[#findings + 1] = { + atom = atom.name or "", + line = atom_body_token_source_line(atom, token, line_in_body), + check = "rtdiagonal_completeness", + kind = strict and "warning" or "info", + msg = string.format( + "atom '%s' uses the bare gte_cmdw_mvmva macro; " + .. "the canonical libgte-2-pass shape is gte_cmdw_mvmva_c11_pass2_exact = 0x4A49E012 " + .. "(gte.h:430). The bare macro does not encode RT23/RT31/RT32/RT33; " + .. "for a full 3x3 matrix, use the dedicated literal or hand-build via enc_gte_*()." + , atom.name or ""), + } + end + end +end + +-- Check #N+2: gte_cr_TR_naming +-- Fires one info per atom body when a `gte_cr_TR[XYZ]` alias is used. +-- Translation-vector registers are the only 3-letter-suffix C2 aliases +-- (`TRX/TRY/TRZ`); an agent who reads `TRX` might typo it as `RT_X` or +-- `RTX0` and either get a compile error (best case) or a build that +-- links but routes the `ctc2` write to the wrong C2 slot. +-- +-- Severity: info. The convention is correct; this is a documentation-pointer check. +local function check_gte_cr_TR_naming(atom, _pipe_ctx, findings) + local tokens = atom.paths and atom.paths.tokens or {} + local tc = atom.paths and atom.paths.tok_class or {} + local line_in_body = atom.paths and atom.paths.line_in_body + if not next(tokens) then return end + local touched = false + local first_line = 0 + for tok_idx, token in ipairs(tokens) do + local c = tc[tok_idx] + if c and c.ident and c.ident:match("^gte_cr_TR[XYZ]$") then + touched = true + if first_line == 0 then + first_line = atom_body_token_source_line(atom, token, line_in_body) + end + end + end + if touched then + findings[#findings + 1] = { + atom = atom.name or "", + line = first_line, + check = "gte_cr_TR_naming", + kind = "info", + msg = string.format( + "atom '%s' uses gte_cr_TR[XYZ]; translation-vector registers are the only " + .. "3-letter-suffix C2 aliases (TRX/TRY/TRZ). See docs/gte_reference.md §" + .. "\"The `gte_cmdw_mvmva_c11_pass2_exact` literal\" for the libgte outer-product " + .. "convention that uses these names." + , atom.name or ""), + } + end +end + -- CHECK_RULES — data-driven check dispatch (Muratori: data over control flow) -- ════════════════════════════════════════════════════════════════════════════ @@ -2414,6 +2592,9 @@ local CHECK_RULES = { { name = "abi_handoff", per_atom = check_abi_handoff }, { 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 = "rtdiagonal_completeness", per_atom = check_rtdiagonal_completeness }, + { name = "gte_cr_TR_naming", per_atom = check_gte_cr_TR_naming }, { name = "enum_alias_membership", per_source = check_enum_alias_membership }, { name = "atom_type_consistency", per_source = check_atom_type_consistency }, { name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref }, @@ -2451,12 +2632,17 @@ local function build_corpus_pipe_ctx(ctx) atoms_by_name = corpus.atoms_by_name or {}, -- Per-component metadata (cycle_cost + gp0_contrib) auto-derived from the original -- `MipsAtomComp_` body by `passes/components.lua::compute_components_metadata`. - -- Keyed by bare name (e.g. `format_f3_color`, `gte_store_f3`); the `mac_` prefix at call sites is stripped before lookup. + -- Keyed by bare name (e.g. `format_f3_color`, `gte_store_f3`); the `mac_` prefix at call sites is stripped before lookup. components_by_name = corpus.components or {}, -- Corpus-wide ordered list of atom_info records (source-order + duplicates). atom_infos_list = corpus.atom_infos or {}, -- Corpus-wide collisions (recorded by scan_source.merge_corpus_registries). collisions = corpus.collisions or {}, + -- GTE control-register alias groups (from `duffle.GTE_CR_ALIAS_GROUPS`). + -- The three new per_atom checks (gte_cr_alias_writes, rtdiagonal_completeness, + -- gte_cr_TR_naming) read from this view. `duffle` is exposed alongside so + -- `find_alias_pair_for` can resolve alias → group without a separate registry. + gte_cr_alias_groups = duffle.GTE_CR_ALIAS_GROUPS or {}, } end