From 8a825a59c7fab847d757accbf32e0fa3538695c6 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 15 Aug 2026 15:51:06 -0400 Subject: [PATCH] Add RegUse_ support to the lua metaprogram. Ideated further on type mapping atom comonents to their base component op (math distinctions annotated in the asm). --- code/duffle/dsl.h | 19 +- code/duffle/gen/macs.h | 38 +- code/duffle/gen/offsets.h | 2 +- code/duffle/gte.atom.c | 16 +- code/duffle/lottes_tape.h | 5 +- code/duffle/{math.atom.c => math.atom.h} | 33 +- code/hello_camera/hello_camera.atom.c | 28 +- code/hello_camera/hello_camera.c | 54 +- scripts/duffle.lua | 11 +- scripts/passes/atoms_source_map.lua | 14 +- scripts/passes/report.lua | 608 ++++++++++++++--------- scripts/passes/scan_source.lua | 28 ++ scripts/passes/static_analysis.lua | 12 +- 13 files changed, 551 insertions(+), 317 deletions(-) rename code/duffle/{math.atom.c => math.atom.h} (52%) diff --git a/code/duffle/dsl.h b/code/duffle/dsl.h index dbf70eb..c11935c 100644 --- a/code/duffle/dsl.h +++ b/code/duffle/dsl.h @@ -140,16 +140,17 @@ enum { false = 0, true = 1, true_overflow, }; typedef void Proc_(VoidFn) (void); -#define Kilo_(n) (C_(U4, n) << 10) -#define Mega_(n) (C_(U4, n) << 20) -#define Giga_(n) (C_(U4, n) << 30) -#define Tera_(n) (C_(U4, n) << 40) +#define Kilo_(n) (C_(U4, n) << 10) +#define Mega_(n) (C_(U4, n) << 20) +#define Giga_(n) (C_(U4, n) << 30) +#define Tera_(n) (C_(U4, n) << 40) -#define null C_(U4, 0) -#define nullptr C_(void*, 0) -#define O_(type, field) C_(U4, & C_(type*,0)->field) -#define OT_(field) O_(typeof_ptr(& field), field)) -#define S_(data) C_(U4, sizeof(data)) +#define null C_(U4, 0) +#define nullptr C_(void*, 0) +#define O_(type, field) C_(U4, & C_(type*,0)->field) +#define OA_(type, aexpr) C_(U4, & C_(type*,0) aexpr) +#define OT_(field) O_(typeof_ptr(& field), field)) +#define S_(data) C_(U4, sizeof(data)) #define sop_1(op,a,b) C_(U1, s1_(a) op s1_(b)) #define sop_2(op,a,b) C_(U2, s2_(a) op s2_(b)) diff --git a/code/duffle/gen/macs.h b/code/duffle/gen/macs.h index b099074..9829c34 100644 --- a/code/duffle/gen/macs.h +++ b/code/duffle/gen/macs.h @@ -17,7 +17,7 @@ // source: C:\projects\Pikuma\ps1\code\duffle\bios.h // source: C:\projects\Pikuma\ps1\code\duffle\psyq.h // source: C:\projects\Pikuma\ps1\code\duffle\pad.c -// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c +// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.h // source: C:\projects\Pikuma\ps1\code\duffle\mips.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gte.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c @@ -71,19 +71,35 @@ WORD_COUNT(mac_load_v2s2, 2) WORD_COUNT(mac_store_v2s2, 2) /* atom_dbg_skip */ -#define mac_load_v3s4(rs_x, rs_y, rs_z, r_base, offset) \ - load_word( rs_x, r_base, offset + O_(V3_S4,x)) \ -, load_word( rs_y, r_base, offset + O_(V3_S4,y)) \ -, load_word( rs_z, r_base, offset + O_(V3_S4,z)) +#define mac_load_word_v3(tx, ty, tz, base, offset) \ + load_word(tx, base, offset + OA_(U4,[0])) \ +, load_word(ty, base, offset + OA_(U4,[1])) \ +, load_word(tz, base, offset + OA_(U4,[2])) +WORD_COUNT(mac_load_word_v3, 3) + +#define mac_load_v3s4(transfer, base, offset) \ + mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset) WORD_COUNT(mac_load_v3s4, 3) +#define mac_load_p3s4(transfer, base, offset) \ + mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset) +WORD_COUNT(mac_load_p3s4, 3) + /* atom_dbg_skip */ -#define mac_store_v3s4(rt_x, rt_y, rt_z, base, offset) \ - store_word(rt_x, base, offset + O_(V3_S4,x)) \ -, store_word(rt_y, base, offset + O_(V3_S4,y)) \ -, store_word(rt_z, base, offset + O_(V3_S4,z)) +#define mac_store_word_v3(tx, ty, tz, base, offset) \ + store_word(tx, base, offset + OA_(U4,[0])) \ +, store_word(ty, base, offset + OA_(U4,[1])) \ +, store_word(tz, base, offset + OA_(U4,[2])) +WORD_COUNT(mac_store_word_v3, 3) + +#define mac_store_v3s4(transfer, base, offset) \ + mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset) WORD_COUNT(mac_store_v3s4, 3) +#define mac_store_p3s4(transfer, base, offset) \ + mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset) +WORD_COUNT(mac_store_p3s4, 3) + /* atom_dbg_skip */ #define mac_add_si_v3s4(rt_x, rt_y, rt_z, base, offset) \ add_si(rt_x, base, O_(V3_S4,x)) \ @@ -234,8 +250,8 @@ WORD_COUNT(mac_lzcr_round_even_half_shift, 5) , gte_mv_to_data_r(to_ir1, C2_IR1) /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ \ , gte_mv_to_data_r(to_ir2, C2_IR2) \ , gte_mv_to_data_r(to_ir3, C2_IR3) /* IR3 = src.z (reloaded) */ \ -, LdSlot_ nop_slot1 \ -, LdSlot_ nop_slot2 \ +, DmaSlot_ nop_slot1 \ +, DmaSlot_ nop_slot2 \ , gte_cmdw_gpf \ , gte_mv_from_data_r(fr_mac1, C2_MAC1) \ , gte_mv_from_data_r(fr_mac2, C2_MAC2) \ diff --git a/code/duffle/gen/offsets.h b/code/duffle/gen/offsets.h index 91c1b9c..8da9966 100644 --- a/code/duffle/gen/offsets.h +++ b/code/duffle/gen/offsets.h @@ -14,7 +14,7 @@ // source: C:\projects\Pikuma\ps1\code\duffle\bios.h // source: C:\projects\Pikuma\ps1\code\duffle\psyq.h // source: C:\projects\Pikuma\ps1\code\duffle\pad.c -// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c +// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.h // source: C:\projects\Pikuma\ps1\code\duffle\mips.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gte.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c diff --git a/code/duffle/gte.atom.c b/code/duffle/gte.atom.c index 58c1b9e..69226f0 100644 --- a/code/duffle/gte.atom.c +++ b/code/duffle/gte.atom.c @@ -150,8 +150,8 @@ MipsAtomComp_Proc_(ab, { gte_mv_to_data_r(to_ir1, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ gte_mv_to_data_r(to_ir2, C2_IR2), gte_mv_to_data_r(to_ir3, C2_IR3), /* IR3 = src.z (reloaded) */ - LdSlot_ nop_slot1, - LdSlot_ nop_slot2, + DmaSlot_ nop_slot1, + DmaSlot_ nop_slot2, gte_cmdw_gpf, gte_mv_from_data_r(fr_mac1, C2_MAC1), gte_mv_from_data_r(fr_mac2, C2_MAC2), @@ -284,7 +284,7 @@ MipsAtom_Proc_(aa, { /* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp. * r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */ - mac_load_v3s4(r.src_x, r.recip_est, r.t5.src_z, r.src_ptr, 0), + mac_load_word_v3(r.src_x, r.recip_est, r.t5.src_z, r.src_ptr, 0), /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.t5.src_z, LdSlot_ nop), @@ -293,8 +293,8 @@ MipsAtom_Proc_(aa, { mac_gte_mv_from_data_r_mac123(r.t3.mac1_scratch, r.t4.mac2_scratch, r.norm), LdSlot_ nop, add_u_self( r.norm, r.t3.mac1_scratch), add_u_self( r.norm, r.t4.mac2_scratch), - gte_mv_to_data_r( r.norm, C2_LZCS), LdSlot_ nop2, - gte_mv_from_data_r(r.shift, C2_LZCR), LdSlot_ nop, + gte_mv_to_data_r( r.norm, C2_LZCS), DmaSlot_ nop2, + gte_mv_from_data_r(r.shift, C2_LZCR), DmaSlot_ nop, /* Stage 3: round LZCR to even, compute half-shift, align |v|² to bit 24. * r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2. @@ -328,13 +328,13 @@ MipsAtom_Proc_(aa, { r.recip_est, r.t5.src_z, /* IR3 = src.z (reloaded) */ r.t4.mac2_scratch, r.recip_est, r.t5.src_z, - LdSlot_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here. - LdSlot_ nop + DmaSlot_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here. + DmaSlot_ nop ), /* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */ mac_shift_aright_var_v3_self(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.shift), /* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */ - mac_store_v3s4(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.dst_ptr, 0), + mac_store_word_v3(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.dst_ptr, 0), mac_yield() }) diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index 7723981..7e435fe 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -101,6 +101,7 @@ enum { }; typedef U2 Reg; // Register parameter used with atom or atom component procedures +#define Reg_(type) tmpl(Reg,type) // Just a way to template register allocations of C-struct types. typedef U4 const MipsCode; // Underlying type to mips asm words. typedef Slice_(MipsCode); @@ -143,6 +144,8 @@ typedef Slice_(MipsAtom); // Inline-only callers (the generated `mac_` aliases) skip the `ab` arg via metaprogram filtering; escape callers (ac_ invoked as a function) pass a long-lived builder. #define MipsAtomComp_Proc_(ab, ...) { MipsCode atom_comp_code[] align_(4) = __VA_ARGS__; atombuilder_push(ab, slice_from_array(MipsCode, atom_comp_code)); } +#define MipsAtomComp_ProcMap_(ab, base_command) atom_dbg_skip MipsAtomComp_Proc_(ab, {base_command }) + /* Line-table anchor: gcc only adds a file to the .debug_line file table when the contains line-numbered content. Files containing only atoms and atom components. Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms. @@ -372,7 +375,7 @@ FI_ void regfile_reset(RegFile_R rf) { rf->GPR[0] = u4_lo(regfile_abi_mask); rf->GPR[1] = u4_hi(regfile_abi_mask); } -FI_ void regfile_reset_mask(RegFile_R rf, U4 mask) { +FI_ void regfile_reset_to_mask(RegFile_R rf, U4 mask) { rf->GPR[0] = u4_lo(mask); rf->GPR[1] = u4_hi(mask); } diff --git a/code/duffle/math.atom.c b/code/duffle/math.atom.h similarity index 52% rename from code/duffle/math.atom.c rename to code/duffle/math.atom.h index c118287..369d79d 100644 --- a/code/duffle/math.atom.c +++ b/code/duffle/math.atom.h @@ -7,9 +7,10 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(math_atom_c); -#pragma region MACs (Mips Atom Component) +typedef Struct_(Reg_V3_S4) { Reg x, y, z; }; // Register allocation of a V3_S4 +typedef Struct_(Reg_P3_S4) { Reg x, y, z; }; // Register allocation of a P3_S4 -// FI_ Slice_MipsCode ac_load_imm +#pragma region MACs (Mips Atom Component) FI_ Slice_MipsCode ac_load_v2s2(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { load_half( rs_x, r_base, offset + O_(V3_S2,x)), @@ -21,23 +22,23 @@ FI_ Slice_MipsCode ac_store_v2s2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 base, U4 store_half(rt_y, base, offset + O_(V2_S2,y)), }) -FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 rs_z, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { - load_word( rs_x, r_base, offset + O_(V3_S4,x)), - load_word( rs_y, r_base, offset + O_(V3_S4,y)), - load_word( rs_z, r_base, offset + O_(V3_S4,z)), +FI_ Slice_MipsCode ac_load_word_v3(AtomBuilder_R ab, Reg tx, Reg ty, Reg tz, Reg base, U2 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { + load_word(tx, base, offset + OA_(U4,[0])), + load_word(ty, base, offset + OA_(U4,[1])), + load_word(tz, base, offset + OA_(U4,[2])), }) -// TODO(Ed): we could generate these mappings properly.. -#define ac_load_p3s4 ac_load_v3s4 -#define mac_load_p3s4 mac_load_v3s4 -FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_z, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { - store_word(rt_x, base, offset + O_(V3_S4,x)), - store_word(rt_y, base, offset + O_(V3_S4,y)), - store_word(rt_z, base, offset + O_(V3_S4,z)), +FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, Reg_(V3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset)) +FI_ Slice_MipsCode ac_load_p3s4(AtomBuilder_R ab, Reg_(P3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset)) + +FI_ Slice_MipsCode ac_store_word_v3(AtomBuilder_R ab, U4 tx, U4 ty, U4 tz, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { + store_word(tx, base, offset + OA_(U4,[0])), + store_word(ty, base, offset + OA_(U4,[1])), + store_word(tz, base, offset + OA_(U4,[2])), }) -// TODO(Ed): we could generate these mappings properly.. -#define ac_store_p3s4 ac_store_v3s4 -#define mac_store_p3s4 mac_store_v3s4 + +FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, Reg_(V3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset)) +FI_ Slice_MipsCode ac_store_p3s4(AtomBuilder_R ab, Reg_(P3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset)) FI_ Slice_MipsCode ac_add_si_v3s4(AtomBuilder_R ab, Reg rt_x, Reg rt_y, Reg rt_z, Reg base, U2 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, { diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index f2325c8..668525c 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -10,7 +10,7 @@ # include "duffle/pad.h" # include "duffle/word_count.metadata.h" # include "duffle/psyq.h" -# include "duffle/math.atom.c" +# include "duffle/math.atom.h" # include "duffle/mips.atom.c" # include "duffle/gte.atom.c" # include "duffle/gp.atom.c" @@ -129,7 +129,7 @@ typedef Struct_(Binds_ResolveLookAtSub) { }; typedef Struct_(RegUse_resolve_look_at__input_and_sub_proc) { - Reg const scratch; + Reg scratch; Reg target; Reg eye; Reg up_in; Reg t0; Reg t1; Reg t2; Reg t3; Reg t4; }; @@ -152,7 +152,7 @@ typedef Struct_(RegUse_resolve_look_at__input_and_sub_proc) { * Pool cost: 8 GPRs + R_T4 (carrier) + R_AT + R_V0 (hardcoded) = 11 GPRs. */ internal MipsAtom* resolve_look_at__input_and_sub_proc(AtomArena_R aa, RegUse_resolve_look_at__input_and_sub_proc r) -MipsAtom_Proc_(aa, atom_info(atom_bind(Binds_ResolveLookAtSub)){ +atom_info(atom_bind(Binds_ResolveLookAtSub)) MipsAtom_Proc_(aa, { load_word(r.target, R_TapePtr, O_(Binds_ResolveLookAtSub,target)), load_word(r.eye, R_TapePtr, O_(Binds_ResolveLookAtSub,eye)), load_word(r.up_in, R_TapePtr, O_(Binds_ResolveLookAtSub,up_in)), @@ -160,20 +160,20 @@ MipsAtom_Proc_(aa, atom_info(atom_bind(Binds_ResolveLookAtSub)){ add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtSub)), /* Stage up_in.x/y/z into the scratchpad. */ - mac_load_p3s4( r.t0, r.t1, r.t2, r.up_in, 0), - mac_store_p3s4(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,up_in)), + mac_load_word_v3( r.t0, r.t1, r.t2, r.up_in, 0), + mac_store_word_v3(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,up_in)), // Stage eye.x/y/z into the scratchpad (atom 6 reads these for the translation column). - mac_load_p3s4( r.t0, r.t1, r.t2, r.eye, 0), - mac_store_p3s4(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,eye)), + mac_load_word_v3( r.t0, r.t1, r.t2, r.eye, 0), + mac_store_word_v3(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,eye)), /* Compute fwd = target - eye. */ // mac_load_p3s4(t3, R_AT, t4, r.eye, 0), - mac_load_p3s4(r.t3, R_AT, r.t4, r.target, 0), + mac_load_word_v3(r.t3, R_AT, r.t4, r.target, 0), mac_sub_v3s4( r.t3, R_AT, r.t4, r.t0, r.t1, r.t2), - mac_store_v3s4(r.t3, R_AT, r.t4, r.scratch, O_(ResolveLookAtScratch,fwd)), + mac_store_word_v3(r.t3, R_AT, r.t4, r.scratch, O_(ResolveLookAtScratch,fwd)), mac_yield() }) @@ -199,9 +199,9 @@ internal MipsAtom* resolve_look_at__cross_uz_up_into_right_proc(AtomArena_R aa, nop, /* Load a (uz).x/y/z into r_a/r_b/r_c. */ - mac_load_v3s4(r.a, r.b, r.c, r.g, 0), + mac_load_word_v3(r.a, r.b, r.c, r.g, 0), /* Load b (up_in).x/y/z into r_d + R_AT/R_V0 (R_AT/R_V0 are hardcoded scratch). */ - mac_load_v3s4(r.d, R_AT, r.t0, r.h, 0), LdSlot_ + mac_load_word_v3(r.d, R_AT, r.t0, r.h, 0), LdSlot_ // (taken by gte_mv_from_ctrl_r) /* Save the two RT control-register slots OP will clobber. We reuse r_g/r_h (scratch pointers, no longer needed) as the save targets. */ gte_mv_from_ctrl_r(r.target0, gte_cr_RT11), /* r_g = C2 r0 (RT11|RT12) */ @@ -228,8 +228,7 @@ internal MipsAtom* resolve_look_at__cross_uz_up_into_right_proc(AtomArena_R aa, gte_mv_to_data_r(r.t0, C2_IR3), /* IR3 = up_in.z */ DmaSlot_ nop2, /* MTC2 retirement (CPU→COP2 2-slot delay) */ - // gte_cmdw_cross, /* OP: MAC1/2/3 = uz × up_in - gte_cmdw_outer_product, /* OP: MAC1/2/3 = uz × up_in + gte_cmdw_cross, /* OP: MAC1/2/3 = uz × up_in * MAC1 = IR3*D2 - IR2*D3 = up_in.z*uz.y.high - up_in.y*uz.z.high * MAC2 = IR1*D3 - IR3*D1 = up_in.x*uz.z.high - up_in.z*uz.x * MAC3 = IR2*D1 - IR1*D2 = up_in.y*uz.x - up_in.x*uz.y.high @@ -249,9 +248,8 @@ internal MipsAtom* resolve_look_at__cross_uz_up_into_right_proc(AtomArena_R aa, /* Right-shift MAC by 12 to convert from GTE's S12.20 fixed-point scale back to libpsyx OuterProduct12 convention (S12.0, fp_one=4096=1<<12). * Without this, MAC values (~16M for unit-vector cross products) overflow the GTE's 16-bit IR registers when atom 3 normalizes via mtc2. */ mac_shift_aright_v3_self(r.a, r.b, r.c, 12), - /* Store out.x/y/z to r_f (out ptr = scratch+32). */ - mac_store_v3s4(r.a, r.b, r.c, r.f, 0), + mac_store_word_v3(r.a, r.b, r.c, r.f, 0), mac_yield() }) diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index 727e93b..5b7584b 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -32,7 +32,7 @@ #pragma region Duffle TUs #include "duffle/pad.c" -#include "duffle/math.atom.c" +#include "duffle/math.atom.h" #include "duffle/mips.atom.c" #include "duffle/gte.atom.c" #include "duffle/gp.atom.c" @@ -149,20 +149,22 @@ internal void resolve_look_at_init(void) { U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch); RegFile rf = regfile(pin_mask); +#define ralloc() regfile_alloc(& rf) smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab, RegUse_(resolve_look_at__input_and_sub_proc) { .scratch = R_ResolveScratch, - .target = regfile_alloc(& rf), - .eye = regfile_alloc(& rf), - .up_in = regfile_alloc(& rf), - .t0 = regfile_alloc(& rf), - .t1 = regfile_alloc(& rf), - .t2 = regfile_alloc(& rf), - .t3 = regfile_alloc(& rf), - .t4 = regfile_alloc(& rf), + .target = ralloc(), + .eye = ralloc(), + .up_in = ralloc(), + .t0 = ralloc(), + .t1 = ralloc(), + .t2 = ralloc(), + .t3 = ralloc(), + .t4 = ralloc(), } ); + regfile_reset_to_mask(& rf, pin_mask); /* === ATOM 1: normalize fwd→uz === */ U2 src_offset = O_(ResolveLookAtScratch, fwd); @@ -170,27 +172,30 @@ internal void resolve_look_at_init(void) { smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab, src_offset, dst_offset, RegUse_(normalize_v3s4_proc){ .scratch = R_ResolveScratch, - .src_ptr = R_T0, - .dst_ptr = R_T1, - .recip_est = R_T6, - .norm = R_T7, - .shift = R_V0, - .src_x = R_T2, - .t3 = R_T3, - .t4 = R_T5, - .t5 = R_V1, + .src_ptr = ralloc(), + .dst_ptr = ralloc(), + .recip_est = ralloc(), + .norm = ralloc(), + .shift = ralloc(), + .src_x = ralloc(), + .t3 = ralloc(), + .t4 = ralloc(), + .t5 = ralloc(), }); + regfile_reset_to_mask(& rf, pin_mask); /* === ATOM 2: cross uz×up_in→right === */ smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_into_right_proc(& ab, RegUse_(resolve_look_at__cross_uz_up_into_right_proc) { .scratch = R_ResolveScratch, - .a = R_T0, .b = R_T1, .c = R_T2, - .d = R_T3, - .f = R_T5, - .t1 = R_T6, - .t2 = R_T7, - .t0 = R_V0, + .a = ralloc(), + .b = ralloc(), + .c = ralloc(), + .d = ralloc(), + .f = ralloc(), + .t1 = ralloc(), + .t2 = ralloc(), + .t0 = ralloc(), }); /* === ATOM 3: normalize right→ux === */ @@ -285,6 +290,7 @@ internal void resolve_look_at_init(void) { /* Sanity check: arena didn't overflow. */ assert(ab.used <= ResolveLookAtArena_Size); +#undef ralloc } /* Emit the resolve_look_at bundle into the tape. Called once per frame from update(). diff --git a/scripts/duffle.lua b/scripts/duffle.lua index 80470ac..12c4730 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -2248,8 +2248,15 @@ local function _project_emission_inner(root_body_entry, ctx_table) end local function apply_sub(sub_map, operand) - if sub_map and type(operand) == "string" and sub_map[operand] then - return sub_map[operand] + if not (sub_map and type(operand) == "string") then return operand end + if sub_map[operand] then return sub_map[operand] end + local dot = operand:find(".", 1, true) + if dot then + local head = operand:sub(1, dot - 1) + local mapped = sub_map[head] + if type(mapped) == "string" then + return mapped .. operand:sub(dot) + end end return operand end diff --git a/scripts/passes/atoms_source_map.lua b/scripts/passes/atoms_source_map.lua index e881971..1518d3b 100644 --- a/scripts/passes/atoms_source_map.lua +++ b/scripts/passes/atoms_source_map.lua @@ -83,6 +83,7 @@ local function canonical_word_entries(atom) line = event.call_line or item.line or 0, text = event.call_text or item.call_text or "", body_line = event.body_line or item.body_line or item.line or 0, + gpr_keys = event.gpr_keys, invocation = (event.outermost_invocation_id and paths.invocations and paths.invocations[event.outermost_invocation_id]) or nil, @@ -491,8 +492,19 @@ function M.render_atom_source_map(atom) local lines = {} lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total) for _, entry in ipairs(entries) do - lines[#lines + 1] = string.format("WORD %d LINE %d TEXT %s", + local word_line = string.format("WORD %d LINE %d TEXT %s", entry.pos, entry.line, entry.text) + local keys = {} + for pos = 1, 16 do + local k = entry.gpr_keys and entry.gpr_keys[pos] + if type(k) == "string" and k:sub(1, 7) == "reguse:" then + keys[#keys + 1] = k + end + end + if #keys > 0 then + word_line = word_line .. " KEYS " .. table.concat(keys, ",") + end + lines[#lines + 1] = word_line end lines[#lines + 1] = "ENDATOM" return table.concat(lines, "\n") .. "\n" diff --git a/scripts/passes/report.lua b/scripts/passes/report.lua index eceb0dd..cabec9a 100644 --- a/scripts/passes/report.lua +++ b/scripts/passes/report.lua @@ -4,8 +4,8 @@ --- - `build/gen/.annotations.txt` — one per source-directory containing atoms; aggregates across all sources in the directory. --- - `build/gen/annotation_validation.txt` — the project summary. --- ---- The annotation pass emits `errors.h` files per module and the canonical `corpus.sources_by_dir` projection groups sources by directory. ---- This pass iterates the dir projection directly and re-validates each source via `annotation.validate()` to get the detailed per-source results. +--- The canonical `corpus.sources_by_dir` projection groups sources by directory. +--- This pass builds one ModuleView per directory and walks SECTION_RENDERERS. -- ════════════════════════════════════════════════════════════════════════════ -- Module-scope requires + package.path setup @@ -20,11 +20,6 @@ local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") --- Load the annotation pass so we can re-validate each source against the canonical corpus projection. --- The annotation pass exposes `M.validate`, which returns the per-source AnnotationResult (atoms / annots / macros / binds / errors / warnings) --- that the report pass renders into the per-module `.annotations.txt` output. -local annotation = dofile(_bootstrap_dir .. "annotation.lua") - -- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `.atoms.md` without re-walking source tokens). -- The pass itself emits no per-source files anymore; we only consume the two pure renderers here. -- Defined BEFORE the renderer functions below so their upvalues resolve to this local (not the global `atoms_source_map`, which is nil). @@ -225,7 +220,7 @@ local function render_module_atoms_md(dir, dir_sources, wc) for _, atom in ipairs(atoms_list) do lines[#lines + 1] = string.format( "### atom: %s (line %d, %d words)", - atom.name, atom.line or 0, #(atom.paths.items or {})) + atom.name, atom.line or 0, #((atom.paths or {}).word_events or {})) lines[#lines + 1] = "" lines[#lines + 1] = "**Sourcemap** — per-word call site:" lines[#lines + 1] = "```" @@ -246,17 +241,349 @@ local function render_module_atoms_md(dir, dir_sources, wc) return table.concat(lines, "\n") .. "\n" end ---- Render the consolidated per-module markdown (`build/.atom_meta_report.md`). ---- Aggregates annotation + static-analysis content across all sources in `dir`. ---- Annotations come from re-running `annotation.validate()` per source (the existing pattern); ---- static-analysis comes from `corpus.static_analysis_results[dir_basename]` (populated by `static_analysis.lua` — no second corpus_pipe_ctx build). ---- @param dir string ---- @param dir_sources SourceFile[] ---- @param annot_results AnnotationResult[] ---- @param sa_results table -- corpus.static_analysis_results[dir_basename] ---- @return string -local function render_module_meta_report(dir, dir_sources, annot_results, sa_results) +local function decl_words(atom) + local p = atom.paths or {} + return #(p.word_events or {}) +end + +local function count_kinds(decls) + local n = { atom = 0, atom_proc = 0, comp_bare = 0, comp_proc = 0 } + for _, a in ipairs(decls or {}) do + if n[a.kind] ~= nil then n[a.kind] = n[a.kind] + 1 end + end + return n +end + +local function slot_suffix(key) + if type(key) ~= "string" or key:sub(1, 7) ~= "reguse:" then return nil end + return key:match("([^:]+)$") +end + +local function build_module_view(dir, dir_sources, corpus) + local decls = {} + for _, src in ipairs(dir_sources or {}) do + for _, a in ipairs((src.scan and src.scan.atoms) or {}) do + if not a.source_path then a.source_path = src.path end + decls[#decls + 1] = a + end + end local dir_basename = source_basename(dir) + local sa = (corpus.static_analysis_results or {})[dir_basename] or {} + local schemas = {} + for name, schema in pairs(corpus.reg_use_schemas or {}) do + for _, a in ipairs(decls) do + if a.reg_use_schema_name == name then + schemas[#schemas + 1] = schema + break + end + end + end + return { + dir = dir, + sources = dir_sources or {}, + decls = decls, + schemas = schemas, + findings = sa.findings or {}, + sa = sa, + corpus = corpus, + } +end + +local function render_section_declarations(add, view) + if #view.decls == 0 then add("_(none)_"); add(""); return end + add("| kind | name | source | line | words | min | max | branches | paths |") + add("|------|------|--------|------|-------|-----|-----|----------|-------|") + for _, a in ipairs(view.decls) do + local p = a.paths or {} + add(string.format("| %s | %s | %s | %d | %d | %s | %s | %s | %s |", + a.kind or "?", + a.name or "?", + source_basename(a.source_path or ""), + a.line or 0, + decl_words(a), + tostring(p.cycles_min or "—"), + tostring(p.cycles_max or "—"), + tostring(p.branches or "—"), + tostring(p.paths or "—"))) + end + add("") +end + +local function render_section_components(add, view) + local rows = {} + local index = (view.corpus and view.corpus.component_body_index) or {} + for _, a in ipairs(view.decls) do + if a.kind == "comp_bare" or a.kind == "comp_proc" then + local idx = index[a.name] or {} + local args = idx.arg_names or {} + rows[#rows + 1] = { + name = a.name, + kind = a.kind, + args = table.concat(args, ", "), + words = decl_words(a), + map = a.map_command or "—", + } + end + end + if #rows == 0 then add("_(none)_"); add(""); return end + add("| name | kind | arg_names | words | map |") + add("|------|------|-----------|-------|-----|") + for _, r in ipairs(rows) do + add(string.format("| %s | %s | %s | %d | %s |", + r.name, r.kind, r.args ~= "" and r.args or "—", r.words, r.map)) + end + add("") +end + +local function render_section_reguse(add, view) + local wrote = false + for _, schema in ipairs(view.schemas or {}) do + wrote = true + add(string.format("### %s", schema.name or "?")) + for _, slot in ipairs(schema.slots or {}) do + local aliases = table.concat(slot.aliases or { slot.name }, ", ") + local ro = slot.readonly and " readonly" or "" + add(string.format("- slot `%s` aliases %s%s", slot.name, aliases, ro)) + end + for _, a in ipairs(view.decls) do + if a.reg_use_schema_name == schema.name then + add(string.format("- bound `%s` param `%s`", a.name, a.reg_use_param_name or "?")) + end + end + add("") + end + local errors = (view.corpus and view.corpus.reg_use_errors) or {} + if #errors > 0 then + wrote = true + add("### parse errors") + for _, err in ipairs(errors) do + add(string.format("- `%s` %s", err.kind or "?", err.schema_name or "")) + end + add("") + end + if not wrote then add("_(none)_"); add("") end +end + +local function render_section_annotations(add, view) + local rows = {} + for _, src in ipairs(view.sources) do + for _, info in ipairs((src.scan and src.scan.atom_infos) or {}) do + rows[#rows + 1] = { + source = source_basename(src.path), + line = info.info_line or 0, + name = info.atom_name or "?", + binds = info.binds or "—", + reads = (#(info.reads or {}) > 0 and table.concat(info.reads, ",")) or "—", + writes = (#(info.writes or {}) > 0 and table.concat(info.writes, ",")) or "—", + phase = info.phase or "—", + } + end + end + if #rows == 0 then add("_(none)_"); add(""); return end + add("| source | line | name | binds | reads | writes | phase |") + add("|--------|------|------|-------|-------|--------|-------|") + for _, r in ipairs(rows) do + add(string.format("| %s | %d | %s | %s | %s | %s | %s |", + r.source, r.line, r.name, r.binds, r.reads, r.writes, r.phase)) + end + add("") +end + +local function render_section_binds(add, view) + local wrote = false + 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 "—"))) + for _, f in ipairs(b.fields or {}) do + add(string.format("- `+%s %s`", tostring(f.offset or "?"), f.name or "?")) + end + add("") + end + end + if not wrote then add("_(none)_"); add("") end +end + +local function render_section_phases(add, view) + local corpus = view.corpus or {} + 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 {}, ", "))) + 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 "—")) + 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 "—")) + 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 + 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] + 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 wrote = false + local function dump(label, table_map) + local scopes = {} + for scope in pairs(table_map or {}) do scopes[#scopes + 1] = scope end + table.sort(scopes) + for _, scope in ipairs(scopes) do + wrote = true + local syms = {} + for sym, gpr in pairs(table_map[scope] or {}) do + if type(gpr) == "string" and gpr ~= sym then + syms[#syms + 1] = string.format("%s → %s", sym, gpr) + else + syms[#syms + 1] = tostring(sym) + end + end + table.sort(syms) + add(string.format("- %s `%s`: %s", label, scope, table.concat(syms, ", "))) + end + end + dump("atom", corpus.atom_auto_regs) + dump("phase", corpus.phase_auto_regs) + 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 first = c.first_site or {} + local other = c.conflicting_site or {} + add(string.format("- `%s` `%s` first %s:%s conflict %s:%s", + c.kind or "?", c.name or "?", + tostring(first.path or "?"), tostring(first.line or "?"), + tostring(other.path or "?"), tostring(other.line or "?"))) + end + add("") +end + +local function render_section_findings(add, view) + local by_atom = {} + for _, f in ipairs(view.findings or {}) do + local key = f.atom or "?" + by_atom[key] = by_atom[key] or {} + by_atom[key][#by_atom[key] + 1] = f + end + if next(by_atom) == nil then add("_(none)_"); add(""); return end + local seen = {} + local function emit(name, fs) + add("### " .. name) + for _, f in ipairs(fs) do + local msg = f.msg or "" + local slot = slot_suffix(f.gpr_key or f.producer_destination) + if slot and not msg:find("(slot ", 1, true) then + msg = msg .. " (slot " .. slot .. ")" + end + add(string.format("- `[%s/%s] %s`", f.kind or "info", f.check or "?", msg)) + end + add("") + end + for _, a in ipairs(view.decls) do + if by_atom[a.name] then + seen[a.name] = true + emit(a.name, by_atom[a.name]) + end + end + local leftovers = {} + for name in pairs(by_atom) do + if not seen[name] then leftovers[#leftovers + 1] = name end + end + table.sort(leftovers) + for _, name in ipairs(leftovers) do emit(name, by_atom[name]) end +end + +local function render_section_relations(add, view) + local wrote = false + for _, a in ipairs(view.decls) do + local rels = (a.paths and a.paths.relations) or {} + if #rels > 0 then + wrote = true + add("### " .. a.name) + for _, rel in ipairs(rels) do + local dest = rel.destination or rel.producer_destination or "—" + local slot = slot_suffix(dest) + local dest_s = tostring(dest) + if slot then dest_s = dest_s .. " (slot " .. slot .. ")" end + add(string.format("- `%s` words %s → %s dest %s", + rel.semantic or "?", + tostring(rel.producer_word or "?"), + tostring(rel.consumer_word or "?"), + dest_s)) + end + add("") + end + end + if not wrote then add("_(none)_"); add("") end +end + +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 + 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] + add(string.format("- `%s` %s", k, (slot and slot.kind) or "unknown")) + end + add("") + end + end + if not wrote then add("_(none)_"); add("") end +end + +local SECTION_RENDERERS = { + { header = "## Declarations", render = render_section_declarations }, + { header = "## Components", render = render_section_components }, + { header = "## RegUse schemas", render = render_section_reguse }, + { header = "## Annotations", render = render_section_annotations }, + { header = "## Binds_* structs", render = render_section_binds }, + { header = "## Phases / views / ctx", render = render_section_phases }, + { header = "## Register aliases", render = render_section_aliases }, + { header = "## Auto-reg", render = render_section_autoreg }, + { header = "## Collisions", render = render_section_collisions }, + { header = "## Findings", render = render_section_findings }, + { header = "## Relations", render = render_section_relations }, + { header = "## Forward GPR", render = render_section_forward }, +} + +--- Render the consolidated per-module markdown (`build/.atom_meta_report.md`). +--- One ModuleView from the corpus; SECTION_RENDERERS walks it. +--- @param view table +--- @return string +local function render_module_meta_report(view) + local dir_basename = source_basename(view.dir) local lines = { "# " .. dir_basename .. " — atom meta report", "> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.", @@ -264,199 +591,41 @@ local function render_module_meta_report(dir, dir_sources, annot_results, sa_res } local function add(s) lines[#lines + 1] = s end - -- Module summary table. - local n_atoms = 0 - local n_annot = 0 - local n_binds = 0 - local n_macros = 0 - local n_bare, n_proc = 0, 0 - for _, r in ipairs(annot_results) do - n_atoms = n_atoms + #r.atoms - n_annot = n_annot + #r.annots - n_binds = n_binds + #r.binds - n_macros = n_macros + #r.macros + local kinds = count_kinds(view.decls) + local n_annot, n_binds, n_macros = 0, 0, 0 + for _, src in ipairs(view.sources) do + n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {}) + n_binds = n_binds + #((src.scan and src.scan.binds) or {}) + n_macros = n_macros + #((src.scan and src.scan.macros) or {}) end - for _, a in ipairs(sa_results.atoms or {}) do - if a.kind == "comp_bare" then n_bare = n_bare + 1 - elseif a.kind == "comp_proc" then n_proc = n_proc + 1 + local n_err, n_warn, n_info = 0, 0, 0 + for _, f in ipairs(view.findings or {}) do + if f.kind == "error" then n_err = n_err + 1 + elseif f.kind == "warning" then n_warn = n_warn + 1 + else n_info = n_info + 1 end end add("## Module summary"); add("") add("| metric | value |"); add("|--------|-------|") - add(string.format("| sources | %d |", #dir_sources)) - add(string.format("| atoms | %d (atoms: %d, comp_bare: %d, comp_proc: %d) |", - #(sa_results.atoms or {}), - #(sa_results.atoms or {}) - n_bare - n_proc, n_bare, n_proc)) + add(string.format("| sources | %d |", #view.sources)) + add(string.format("| decls | %d (atom: %d, atom_proc: %d, comp_bare: %d, comp_proc: %d) |", + #view.decls, kinds.atom, kinds.atom_proc, kinds.comp_bare, kinds.comp_proc)) add(string.format("| annotations | %d |", n_annot)) add(string.format("| binds structs | %d |", n_binds)) add(string.format("| macro decls | %d |", n_macros)) add(string.format("| findings | %d (errors: %d, warnings: %d, info: %d) |", - #(sa_results.findings or {}), - #(sa_results.errors or {}), - #(sa_results.warnings or {}), - #(sa_results.info or {}))) + #(view.findings or {}), n_err, n_warn, n_info)) add("") - -- Sources add("## Sources"); add("") - for _, s in ipairs(dir_sources) do add("- `" .. s.path .. "`") end + for _, s in ipairs(view.sources) do add("- `" .. s.path .. "`") end add("") - -- Atoms (annotation) - add("## Atoms"); add("") - add("| kind | name | source | line |"); add("|------|------|--------|------|") - for _, r in ipairs(annot_results) do - local src_name = source_basename(r.source) - for _, a in ipairs(r.atoms) do - add(string.format("| atom | %s | %s | %d |", a.name, src_name, a.line)) - end + for _, row in ipairs(SECTION_RENDERERS) do + add(row.header); add("") + row.render(add, view) end - add("") - - -- Annotations - add("## Annotations"); add("") - if #annot_results == 0 then - add("_(none)_") - else - add("| source | line | name | binds | reads | writes |") - add("|--------|------|------|-------|-------|--------|") - for _, r in ipairs(annot_results) do - local src_name = source_basename(r.source) - for _, a in ipairs(r.annots) do - local binds = a.binds or "—" - local reads = (#a.reads > 0 and table.concat(a.reads, ",")) or "—" - local writes = (#a.writes > 0 and table.concat(a.writes, ",")) or "—" - add(string.format("| %s | %d | %s | %s | %s | %s |" - , src_name, a.line, a.name, binds, reads, writes)) - end - end - end - add("") - - -- Binds_* structs - add("## Binds_* structs"); add("") - if #annot_results == 0 then - add("_(none)_") - else - for _, r in ipairs(annot_results) do - local src_name = source_basename(r.source) - for _, b in ipairs(r.binds) do - add(string.format("### %s (%s:%d, %d bytes)", - b.name, src_name, b.line, b.bytes)) - for _, f in ipairs(b.fields) do - add(string.format("- `+%d %s`", f.offset, f.name)) - end - add("") - end - end - end - - -- Macro decls - add("## Macro word-count declarations"); add("") - if #annot_results == 0 then - add("_(none)_") - else - add("| source | line | macro declaration |") - add("|--------|------|-------------------|") - for _, r in ipairs(annot_results) do - local src_name = source_basename(r.source) - for _, m in ipairs(r.macros) do - add(string.format("| %s | %d | %s |", - src_name, m.line, m.name)) - end - end - end - add("") - - -- Findings by atom (static-analysis) - add("## Static analysis — findings by atom"); add("") - local by_atom = {} - for _, f in ipairs(sa_results.findings or {}) do - by_atom[f.atom] = by_atom[f.atom] or {} - by_atom[f.atom][#by_atom[f.atom] + 1] = f - end - if next(by_atom) == nil then - add("_(no findings)_") - else - for _, a in ipairs(sa_results.atoms or {}) do - local fs = by_atom[a.name] - if fs then - add(string.format("### %s", a.name)) - for _, f in ipairs(fs) do - add(string.format("- `[%s] %s`", f.check, f.msg)) - end - add("") - end - end - end - - -- Errors / Warnings / Info - local function add_findings(label, entries) - add(string.format("## %s", label)) - if #entries == 0 then - add("_(none)_") - else - for _, e in ipairs(entries) do - add(string.format("- line %d %s", e.line, e.msg)) - end - end - add("") - end - add_findings("Errors", sa_results.errors or {}) - add_findings("Warnings", sa_results.warnings or {}) - add_findings("Info", sa_results.info or {}) - - -- Per-atom cycle counts (path-aware) - add("## Per-atom cycle counts (path-aware, best case, no stalls)"); add("") - add("| atom | source | min | max | branches | paths | notes |") - add("|------|--------|-----|-----|----------|-------|-------|") - local sorted = {} - for _, a in ipairs(sa_results.atoms or {}) do sorted[#sorted + 1] = a end - table.sort(sorted, function(x, y) - return ((x.paths or {}).cycles_max or 0) > ((y.paths or {}).cycles_max or 0) - end) - for _, a in ipairs(sorted) do - local p = a.paths or {} - local src_name = a.source_path and source_basename(a.source_path) or "" - local notes = "" - if p.has_loops then notes = notes .. " [loop!]" end - if p.unknown_macros and #p.unknown_macros > 0 then - notes = notes .. " [unknown: " .. table.concat(p.unknown_macros, ", ") .. "]" - end - add(string.format("| %s | %s | %d | %d | %d | %d | %s |", - a.name, src_name, - p.cycles_min or 0, p.cycles_max or 0, - p.branches or 0, p.paths or 0, notes)) - end - add("") - - -- Per-source scan summary - add("## Per-source scan summary"); add("") - for _, src in ipairs(dir_sources) do - local src_atoms = {} - for _, a in ipairs(sa_results.atoms or {}) do - if a.source_path == src.path then src_atoms[#src_atoms + 1] = a end - end - if #src_atoms > 0 then - local mn, mx = math.huge, -1 - for _, a in ipairs(src_atoms) do - local p = a.paths or {} - if (p.cycles_min or 0) < mn then mn = p.cycles_min or 0 end - if (p.cycles_max or 0) > mx then mx = p.cycles_max or 0 end - end - local path_str - if mx > 0 then - path_str = string.format(" cycles=%d..%d", mn, mx) - else - path_str = string.format(" %d cycles", mn) - end - add(string.format("- `%s` — %d atom%s%s", - src.basename, #src_atoms, - #src_atoms == 1 and "" or "s", path_str)) - end - end - add("") return table.concat(lines, "\n") .. "\n" end @@ -474,19 +643,8 @@ local REPORT_RENDERERS = { basename = function(dir_basename) return dir_basename .. ".atom_meta_report" end, once = false, gather = function(ctx, dir, dir_sources) - -- Annotations: re-run `annotation.validate()` per source (the existing pattern). - local annot_results = {} - for _, src in ipairs(dir_sources) do - if src.scan then - local r = annotation.validate(ctx, src, nil) - r.source = src.path - annot_results[#annot_results + 1] = r - end - end - -- Static-analysis: read stashed projection (no re-validate). - local dir_basename = dir:match("([^/\\]+)$") or dir - local sa_results = (ctx.shared.corpus.static_analysis_results or {})[dir_basename] or {} - return render_module_meta_report(dir, dir_sources, annot_results, sa_results) + local corpus = ctx.shared.corpus + return render_module_meta_report(build_module_view(dir, dir_sources, corpus)) end, }, { @@ -554,32 +712,30 @@ function M.run(ctx) end end - -- For the summary, compute per-module totals once (re-validating annotations per source — same pattern as the meta_report renderer). - local annot_results = {} + local view = build_module_view(dir, dir_sources, corpus) + local n_annot, n_binds, n_macros = 0, 0, 0 for _, src in ipairs(dir_sources) do - if src.scan then - local r = annotation.validate(ctx, src, nil) - r.source = src.path - annot_results[#annot_results + 1] = r + n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {}) + n_binds = n_binds + #((src.scan and src.scan.binds) or {}) + n_macros = n_macros + #((src.scan and src.scan.macros) or {}) + end + local n_err, n_warn, n_info = 0, 0, 0 + for _, f in ipairs(view.findings or {}) do + if f.kind == "error" then n_err = n_err + 1 + elseif f.kind == "warning" then n_warn = n_warn + 1 + else n_info = n_info + 1 end end - local n_annot, n_binds, n_macros = 0, 0, 0 - for _, r in ipairs(annot_results) do - n_annot = n_annot + #r.annots - n_binds = n_binds + #r.binds - n_macros = n_macros + #r.macros - end - local sa_results = (corpus.static_analysis_results or {})[dir_basename] or {} all_modules[#all_modules + 1] = { module = dir_basename, - atoms = #(sa_results.atoms or {}), + atoms = #view.decls, annots = n_annot, binds = n_binds, macros = n_macros, - findings = #(sa_results.findings or {}), - errors = #(sa_results.errors or {}), - warnings = #(sa_results.warnings or {}), - info = #(sa_results.info or {}), + findings = #(view.findings or {}), + errors = n_err, + warnings = n_warn, + info = n_info, } end diff --git a/scripts/passes/scan_source.lua b/scripts/passes/scan_source.lua index 29c986a..93211f1 100644 --- a/scripts/passes/scan_source.lua +++ b/scripts/passes/scan_source.lua @@ -6,6 +6,7 @@ --- MipsAtom_Proc_ (kind = "atom_proc", body inside last {}) --- MipsAtomComp_ (kind = "comp_bare") --- MipsAtomComp_Proc_ (kind = "comp_proc", body inside last {}) +--- MipsAtomComp_ProcMap_ (kind = "comp_proc", body is the one command) --- atom_dbg_skip — bare whole-atom/component debug-step marker; following declaration disambiguates --- MipsCode code_ (kind = "raw_atom", offsets pass only) --- typedef Struct_(Binds_X) { fields } @@ -1380,6 +1381,32 @@ local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out) return after_paren end +--- Parse: `MipsAtomComp_ProcMap_(ab, command)` — body is the one command (second arg). +--- Reuses the proc name walk. Kind is `comp_proc`. The C expansion wraps +--- `atom_dbg_skip MipsAtomComp_Proc_(ab, {command })`; source-as-written is the map. +--- @param source string +--- @param pos integer +--- @param ident_end integer +--- @param line_of fun(pos: integer): integer +--- @param out SourceScan +--- @return integer +local function parse_mips_atom_comp_proc_map(source, pos, ident_end, line_of, out) + local inner, after_paren, open_paren = read_parens_after(source, ident_end) + if not inner then return after_paren end + local args = duffle.split_top_level_commas(inner) + if #args < 2 then return after_paren end + local command = duffle.trim(args[2]) + if command == "" then return after_paren end + local raw_name = duffle.find_function_decl_for(source, open_paren, SLICE_MIPS_CODE_LEN) + if not raw_name then raw_name = "?" end + local name = strip_ac_prefix(raw_name) + local body_off = open_paren + 1 + (inner:find(command, 1, true) or 1) - 1 + register_atom(out, "comp_proc", line_of(pos), name, command, body_off, raw_name, pos, after_paren, source) + local entry = out.atoms[#out.atoms] + entry.map_command = command + 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 @@ -2109,6 +2136,7 @@ local DECL_PARSERS = { MipsAtom_Proc_ = parse_mips_atom_proc, MipsAtomComp_ = parse_mips_atom_comp, MipsAtomComp_Proc_ = parse_mips_atom_comp_proc, + MipsAtomComp_ProcMap_ = parse_mips_atom_comp_proc_map, -- `atom_dbg_skip` is the only debug-skip parser entry. Every other -- identifier follows the ordinary unrelated-token path; there is no alias. atom_dbg_skip = parse_dbg_skip_marker, diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index a570c4e..97b6be2 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -1322,8 +1322,14 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings) if is_load_delay then -- Determine the destination register from the load's `writes` field. local prev_writes = gpr_effects[prev_ident] and gpr_effects[prev_ident].writes or {} - local prev_args = prev_ev.args or {} - local load_dest = prev_writes[1] and prev_args[prev_writes[1]] or "" + local dest_pos = prev_writes[1] + local load_dest = dest_pos and (gpr_identity(prev_ev, dest_pos) or (prev_ev.args or {})[dest_pos]) or "" + local authored = dest_pos and (prev_ev.args or {})[dest_pos] or load_dest + local shown = authored + if type(load_dest) == "string" and load_dest:sub(1, 7) == "reguse:" then + local slot = load_dest:match("([^:]+)$") + if slot then shown = authored .. " (slot " .. slot .. ")" end + end findings[#findings + 1] = { check = "hazard_nop_use", kind = "info", @@ -1336,7 +1342,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings) producer_destination = load_dest, consumer_token = "", msg = string.format("%s at line %d: nop at word %d is modeled-required (load-delay slot for %s)" - , atom.name, ev_line, ev_word, load_dest + , atom.name, ev_line, ev_word, shown ), } else