diff --git a/code/duffle/bios.h b/code/duffle/bios.h index 1946132..227c5fa 100644 --- a/code/duffle/bios.h +++ b/code/duffle/bios.h @@ -1,6 +1,7 @@ #ifdef INTELLISENSE_DIRECTIVES # pragma once #endif + enum { bios_init_pad_2 = 0x12, bios_start_pad_2 = 0x13, diff --git a/code/duffle/gen/macs.h b/code/duffle/gen/macs.h index 5a86e1d..0c7e3e0 100644 --- a/code/duffle/gen/macs.h +++ b/code/duffle/gen/macs.h @@ -283,7 +283,7 @@ WORD_COUNT(mac_gte_sqr_v3s4, 5) , gte_mv_to_data_r(r_sx, C2_IR1) \ , gte_mv_to_data_r(r_sy, C2_IR2) \ , gte_mv_to_data_r(r_sz, C2_IR3) \ -, nop2 /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ \ +, GteDelay_ nop2 /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ \ , gte_cmdw_gpf \ , gte_mv_from_data_r(r_dx, C2_MAC1) \ , gte_mv_from_data_r(r_dy, C2_MAC2) \ @@ -291,12 +291,12 @@ WORD_COUNT(mac_gte_sqr_v3s4, 5) , shift_aright_var(r_dx, r_dx, r_shift) \ , shift_aright_var(r_dy, r_dy, r_shift) \ , shift_aright_var(r_dz, r_dz, r_shift) -WORD_COUNT(mac_gte_gpf_scale, 13) +WORD_COUNT(mac_gte_gpf_scale, 12) #define mac_trans_mt3s3s4(r_mtx, r_off, r_t0, r_t1, r_t2) \ - load_word(r_t0, r_off, O_(V3_S4,x)) \ -, load_word(r_t1, r_off, O_(V3_S4,y)) \ -, load_word(r_t2, r_off, O_(V3_S4,z)) \ + load_word( r_t0, r_off, O_(V3_S4,x)) \ +, load_word( r_t1, r_off, O_(V3_S4,y)) \ +, load_word( r_t2, r_off, O_(V3_S4,z)) \ , store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])) \ , store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])) \ , store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])) @@ -306,8 +306,8 @@ WORD_COUNT(mac_trans_mt3s3s4, 6) #define mac_lzcr_round_even_half_shift(r_shift, r_mag_sq, r_mag_sq_copy) \ and_i(r_shift, r_shift, gte_lzcr_even_mask) \ , or_u(r_mag_sq_copy, r_mag_sq, 0) \ -, li_s(r_mag_sq, 31) \ -, sub_s(r_mag_sq, r_mag_sq, r_shift) \ +, li_s( r_mag_sq, 31) \ +, sub_s( r_mag_sq, r_mag_sq, r_shift) \ , shift_aright(r_mag_sq, r_mag_sq, 1) WORD_COUNT(mac_lzcr_round_even_half_shift, 5) @@ -337,7 +337,7 @@ WORD_COUNT(mac_gte_mv_from_mac123_v3s4, 3) /* atom_dbg_skip */ #define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \ mac_load_word_imm(reg_transfer, cmd) \ -, store_word( reg_transfer, reg_base, port) +, store_word( reg_transfer, reg_base, port) WORD_COUNT(mac_gcmd_push, 3) /* atom_dbg_skip */ @@ -383,8 +383,8 @@ WORD_COUNT(mac_insert_ot_tag, 11) /* atom_dbg_skip */ #define mac_pad_set_centered_axes(state, scratch) \ load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF) \ -, or_i_self( scratch, PadAxis_Centered & 0xFFFF) /* mac_load_word_imm(scratch, PadAxis_Centered), */ \ -, store_word( scratch, state, O_(PadState,axes)) +, or_i_self( scratch, PadAxis_Centered & 0xFFFF) /* mac_load_word_imm(scratch, PadAxis_Centered), */ \ +, store_word( scratch, state, O_(PadState,axes)) WORD_COUNT(mac_pad_set_centered_axes, 3) /* atom_dbg_skip */ @@ -401,7 +401,7 @@ WORD_COUNT(mac_pad_set_status, 2) /* atom_dbg_skip */ #define mac_pad_store_inverted_buttons(r_buttons, r_pad_state) \ - nor_u( r_buttons, r_buttons, R_0) \ -, store_half( r_buttons, r_pad_state, O_(PadState,buttons)) + nor_u( r_buttons, r_buttons, R_0) \ +, store_half(r_buttons, r_pad_state, O_(PadState,buttons)) WORD_COUNT(mac_pad_store_inverted_buttons, 2) diff --git a/code/duffle/gp.atom.c b/code/duffle/gp.atom.c index 619f513..79b36ae 100644 --- a/code/duffle/gp.atom.c +++ b/code/duffle/gp.atom.c @@ -11,7 +11,7 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(gp_atom_c); FI_ Slice_MipsCode ac_gcmd_push(AtomBuilder_R ab, U4 cmd, U4 reg_transfer, U4 reg_base, U2 port) atom_dbg_skip MipsAtomComp_Proc_(ab, { mac_load_word_imm(reg_transfer, cmd), - store_word( reg_transfer, reg_base, port), + store_word( reg_transfer, reg_base, port), }) FI_ Slice_MipsCode ac_store_rgb8(AtomBuilder_R ab, U1 rr, U1 rg, U1 rb, U4 base, U4 offset) diff --git a/code/duffle/gte.atom.c b/code/duffle/gte.atom.c index ea1923f..438c0ad 100644 --- a/code/duffle/gte.atom.c +++ b/code/duffle/gte.atom.c @@ -118,7 +118,7 @@ atom_dbg_skip MipsAtomComp_Proc_(ab, { gte_mv_to_data_r(r_sx, C2_IR1), gte_mv_to_data_r(r_sy, C2_IR2), gte_mv_to_data_r(r_sz, C2_IR3), - nop2, /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ + GteDelay_ nop2, /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ gte_cmdw_gpf, gte_mv_from_data_r(r_dx, C2_MAC1), gte_mv_from_data_r(r_dy, C2_MAC2), @@ -137,29 +137,26 @@ FI_ Slice_MipsCode ac_trans_mt3s3s4(AtomBuilder_R ab , U4 r_mtx, U4 r_off , U4 r_t0, U4 r_t1, U4 r_t2 ) MipsAtomComp_Proc_(ab, { - load_word(r_t0, r_off, O_(V3_S4,x)), - load_word(r_t1, r_off, O_(V3_S4,y)), - load_word(r_t2, r_off, O_(V3_S4,z)), + load_word( r_t0, r_off, O_(V3_S4,x)), + load_word( r_t1, r_off, O_(V3_S4,y)), + load_word( r_t2, r_off, O_(V3_S4,z)), store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])), store_word(r_t1, r_mtx, O_(MT3_S2S4,t[1])), store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])), }) /* ─── LZCR ROUND EVEN + HALF-SHIFT ─── - * Takes the raw LZCR leading-zero/ones count (from mfc2 C2_LZCR, range 1..32 - * per PSX-SPX cop2r31) and the |v|² sum (in r_mag_sq from the MAC1+MAC2+MAC3 - * add). Produces: + * Takes the raw LZCR leading-zero/ones count (from mfc2 C2_LZCR, range 1..32 per PSX-SPX cop2r31) and the |v|² sum (in r_mag_sq from the MAC1+MAC2+MAC3 add). + * Produces: * r_shift ← LZCR rounded down to even (clear bit 0) * r_mag_sq_copy ← |v|² sum (moved out of r_mag_sq before it's overwritten) * r_mag_sq ← (31 - even_LZCR) / 2 = the final srav/GPF shift amount * - * Rounding to even ensures (31 - LZCR) is always odd, so the >> 1 division - * is consistent — no 0.5 loss. The caller branches on LZCR < 24 to decide - * left-shift vs right-shift of r_mag_sq_copy, then saves the shift count. + * Rounding to even ensures (31 - LZCR) is always odd, so the >> 1 division is consistent — no 0.5 loss. + * The caller branches on LZCR < 24 to decide left-shift vs right-shift of r_mag_sq_copy, then saves the shift count. * - * Note: C2_LZCR (cop2r31) is a fixed read-only C2 data register — the caller - * must read it via mfc2 from C2_LZCR; there is no register choice at the - * hardware level. Only the GPR that holds the result is caller-determined. */ + * Note: C2_LZCR (cop2r31) is a fixed read-only C2 data register — the caller must read it via mfc2 from C2_LZCR; + * there is no register choice at the hardware level. Only the GPR that holds the result is caller-determined. */ FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab, U4 r_shift, U4 r_mag_sq, @@ -167,8 +164,8 @@ FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab, atom_dbg_skip MipsAtomComp_Proc_(ab, { and_i(r_shift, r_shift, gte_lzcr_even_mask), or_u(r_mag_sq_copy, r_mag_sq, 0), - li_s(r_mag_sq, 31), - sub_s(r_mag_sq, r_mag_sq, r_shift), + li_s( r_mag_sq, 31), + sub_s( r_mag_sq, r_mag_sq, r_shift), shift_aright(r_mag_sq, r_mag_sq, 1), }) @@ -206,8 +203,7 @@ FI_ Slice_MipsCode ac_gte_mv_from_mac123_v3s4(AtomBuilder_R ab, Reg_(V3_S4) v) M /* ─── Local copy of PSYQ's sqrtbl (1/sqrt lookup table for VectorNormal). ─── * Source: PSYQ 4.7 libgte sqrtbl at 0x800185B4 in hello_camera.elf. - * objdump -s --start-address=0x800185B4 --stop-address=0x800185F4 hello_camera.elf - * → 192 entries × 16-bit signed, in 1.12 fixed-point (max value 0x1000 = 1.0). + * objdump -s --start-address=0x800185B4 --stop-address=0x800185F4 hello_camera.elf → 192 entries × 16-bit signed, in 1.12 fixed-point (max value 0x1000 = 1.0). * * Data is identical to the libgte original (byte-for-byte verified). * @@ -400,12 +396,22 @@ internal MipsAtom_(set_gte_mt3s2s4) atom_info( load_word(R_T3, R_TapePtr, O_(Binds_SetGteMT3S2S4,transform)), add_ui_self( R_TapePtr, S_(Binds_SetGteMT3S2S4)), /* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */ - load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4), - gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12), - load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16), - gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22), - load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28), - gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ), + load_word(R_T0, R_T3, 0), + load_word(R_T1, R_T3, 4), + gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), + gte_mv_to_ctrl_r(R_T1, gte_cr_RT12), + load_word(R_T0, R_T3, 8), + load_word(R_T1, R_T3, 12), + load_word(R_T2, R_T3, 16), + gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), + gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), + gte_mv_to_ctrl_r(R_T2, gte_cr_RT22), + load_word(R_T0, R_T3, 20), + load_word(R_T1, R_T3, 24), + load_word(R_T2, R_T3, 28), + gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), + gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), + gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ), mac_yield() }; diff --git a/code/duffle/gte.h b/code/duffle/gte.h index ea31cc4..bdd398a 100644 --- a/code/duffle/gte.h +++ b/code/duffle/gte.h @@ -16,9 +16,6 @@ * gte_mv_to_data_r (gte + mv + to + data + register) * gte_lw_v0_xy(base) (gte + lw + v0 + xy) * load_upper_i (load-upper + immediate, unique verb) - * - * Vendor mnemonics (gte_mtc2, gte_mfc2, gte_lwc2, gte_swc2, etc.) are NOT in this header. - * They are in the opt-in `gte_vendor_sym.h` for users who prefer the textbook MIPS assembly mnemonics. * ============================================================================ */ #ifdef INTELLISENSE_DIRECTIVES @@ -442,9 +439,8 @@ enum { _C2_TX_SUBS_ = 0 /* MVMVA: sf=1 (>>12), mx=0 (RT matrix), v=0 (V0), cv=3 (no TR). */ #define gte_cmdw_mvmva_sf1_mx0_v0_cv3 (gte_cmd_base | enc_gte_sf(1) | enc_gte_cv(3) | enc_gte_v(0) | enc_gte_mx(0) | enc_gte_cmd(gte_cmd_mvmva)) -/* RTPS with sf=1 (12-bit shift, no translation): matches the output of libgte's - * ApplyMatrixLV when the GTE pipeline expects R*pos >> 12. The shift produces - * values like (-270, 710, 1713) which match the C11 reference path. */ +/* RTPS with sf=1 (12-bit shift, no translation): matches the output of libgte's ApplyMatrixLV when the GTE pipeline expects R*pos >> 12. + * The shift produces values like (-270, 710, 1713) which match the C11 reference path. */ #define gte_cmdw_rtps_sf1 (gte_cmd_base | enc_gte_sf(1) | enc_gte_cv(3) | enc_gte_cmd(gte_cmd_rtps)) /* SQR / GPF cosmetic-bits compat helpers. @@ -477,10 +473,8 @@ enum { _C2_TX_SUBS_ = 0 * bits 24-20 = 0x19 (libgte "nonsense SDK command number" signature) */ #define gte_cmdw_gpf (gte_cmd_base | enc_gte_cmd(gte_cmd_gpf) | gte_cmdw_gpf_fake_sig) -/* Mask to round LZCR (leading-zero/ones count, range 1..32 per PSX-SPX cop2r31) - * down to even. The normalize_v3s4 half-shift logic computes (31 - LZCR) >> 1; - * clearing bit 0 ensures the subtraction result is always odd, - * so the >> 1 division is consistent (no 0.5 loss). */ +/* Mask to round LZCR (leading-zero/ones count, range 1..32 per PSX-SPX cop2r31) down to even. + * The normalize_v3s4 half-shift logic computes (31 - LZCR) >> 1; clearing bit 0 ensures the subtraction result is always odd, so the >> 1 division is consistent (no 0.5 loss). */ enum { gte_lzcr_even_mask = 0xFFFE, /* all bits except bit 0 */ }; @@ -587,8 +581,8 @@ enum { /* gte_load_v0v1v2(p0, p1, p2, b0, b1, b2) — prelude to gte_cmd_rtpt. * - * Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register, - * each loaded from its own base GPR. Caller must bind each `pN` to `bN` via a register variable. + * Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register, each loaded from its own base GPR. + * Caller must bind each `pN` to `bN` via a register variable. * register V3_S2* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12") * register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13") * register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14") @@ -682,8 +676,7 @@ enum { * Loads the 3x3 rotation matrix at `r0` into the GTE's rotation-matrix control registers (RT11..RT22, indices 0..4) via ctc2. * * Memory layout at r0: five contiguous 32-bit words (offsets 0..16), each holding two packed 16-bit matrix elements. - * The first 1.5 rows of a standard PSX SDK MATRIX struct (where each row is laid out as - * [RT_xx, RT_xy] | [RT_xz, pad] | ...). + * The first 1.5 rows of a standard PSX SDK MATRIX struct (where each row is laid out as [RT_xx, RT_xy] | [RT_xz, pad] | ...). * * Generated MIPS (mirrors the source macro): * lw $12, 0( %0 ) ; word 0 diff --git a/code/duffle/lottes_tape.h b/code/duffle/lottes_tape.h index ac56ded..1572047 100644 --- a/code/duffle/lottes_tape.h +++ b/code/duffle/lottes_tape.h @@ -66,40 +66,53 @@ * */ /* Register Allocation Info */ enum { - R_AtomJmp = R_T8 atom_reg, /* debug-visible; tape yield handshake scratch */ - R_TapePtr = R_T9 atom_reg, /* The Instruction Stream Pointer */ + R_ScratchBase = R_SP atom_reg, /* Scratchpad base address (host frame top) */ + R_AtomJmp = R_FP atom_reg, /* Next atom target (yield handshake scratch) */ + R_TapePtr = R_RA atom_reg, /* The Instruction Stream Pointer */ /* Stringification codes for the GCC inline assembler clobber lists. */ -#define R_AtomJmp_Code R_T8_Code -#define R_TapePtr_Code R_T9_Code +#define R_ScratchBase_Code R_SP_Code +#define R_AtomJmp_Code R_FP_Code +#define R_TapePtr_Code R_RA_Code // R_InCursor = R_T4, // #define R_InCursor_Code R_T4_Code -// Reserved Registers (Callee-saved): -// - R_T9: Holds the Tape Ptr which we need to increment -// - R_RA: Return address register -// Needed by ac_yield but can be used as atom scratch: -// - R_T8: Will be used as the atom jump register. +// Reserved Registers (Callee-saved across the host ABI transition): +// - R_SP: Holds the scratchpad base while tape code executes. +// - R_FP: Holds the next atom target. +// - R_RA: Holds the tape cursor. +// All atom-body allocations must stay out of these. +// Atom bodies may freely use R2-R25. -// All allocatable registers for mips atoms: +// All allocatable registers for atom bodies (R2-R25, 24 registers): - // TODO(Ed): Make this the R_AtomJmp register since its better to clobber across atoms. - R_TScratchVolatile = R_AT, // This one is reserved for psuedo instructions, but you can technically use it. + R_PsuedoVolatile = R_AT, // Assembler temporary; never allocate. - R_TScratch0 = R_T0, - R_TScratch1 = R_T1, - R_TScratch2 = R_T2, - R_TScratch3 = R_T3, - R_TScratch4 = R_T4, - R_TScratch5 = R_T5, - R_TScratch6 = R_T6, - R_TScratch7 = R_T7, - R_TScratch8 = R_T8, // Clobbered by the yield on a per-atom boundary. - R_TScratch10 = R_V0, // Tend to be used with gte DMAs - R_TScratch11 = R_V1, // Tend to be used with gte DMAs - // Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck. - // A 0-2 - // S 0-7 + // Atom Allocation Pool + R_Atom0 = R_T0, + R_Atom1 = R_T1, + R_Atom2 = R_T2, + R_Atom3 = R_T3, + R_Atom4 = R_T4, + R_Atom5 = R_T5, + R_Atom6 = R_T6, + R_Atom7 = R_T7, + R_Atom8 = R_T8, + R_Atom9 = R_T9, + R_Atom10 = R_V0, // Tend to be used with gte DMAs + R_Atom11 = R_V1, // Tend to be used with gte DMAs + R_Atom12 = R_A0, + R_Atom13 = R_A1, + R_Atom14 = R_A2, + R_Atom15 = R_A3, + R_Atom16 = R_S0, + R_Atom17 = R_S1, + R_Atom18 = R_S2, + R_Atom19 = R_S3, + R_Atom20 = R_S4, + R_Atom21 = R_S5, + R_Atom22 = R_S6, + R_Atom23 = R_S7, }; typedef U2 Reg; // Register parameter used with atom or atom component procedures @@ -109,7 +122,8 @@ typedef U4 const MipsCode; // Underlying type to mips asm words. typedef Slice_(MipsCode); typedef U4 const MipsAtom; // Underlying type to a mips atom defnition -typedef Slice_(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* @@ -153,53 +167,71 @@ typedef Slice_(MipsAtom); Files containing only atoms and atom components. Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms. Macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table. - The constant is in `.rodata` so the linker may eliminate it. - Two-level concat + `__LINE__` suffix makes the identifier unique per call site - (identifier embeds the source line, so duplicates across `#include`d files don't collide). */ + The constant is in `.rodata` so the linker may eliminate it. */ #define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0 typedef Slice_MipsAtom Tape; -/* The 'Exit' Atom */ -atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(R_RA), nop }; +typedef Struct_(TapeHostFrame) { + U4 s0; + U4 s1; + U4 s2; + U4 s3; + U4 s4; + U4 s5; + U4 s6; + U4 s7; + U4 fp; + U4 sp; + U4 ra; +}; -// TODO(Ed): When we have a substantial workload/throughput, profile each of these to see impact at ABI boundaries. +enum { + TapeHostFrame_Loc = Scratchpad_End - S_(TapeHostFrame), + TapeScratch_Len = TapeHostFrame_Loc - Scratchpad_Loc, +}; +static_assert(S_(TapeHostFrame) == 11 * S_(U4)); +static_assert(TapeHostFrame_Loc == 0x1F8003D4); -/* Tape Runner (Default) */ -FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile( - asm_words( - load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ - , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ - , call_reg( R_AtomJmp) /* jalr $t8 */ - , BdSlot_ nop /* Branch delay slot */ - ) - asm_rpins, r_use(tape_ptr) - asm_clobber: - rlit(R_AT), - rlit(R_V0), rlit(R_V1), // We clobber these for GTE ACs (that don't expose register selection, might expose them in the future...) - rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4), - rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), - clb_mem_drain -); } +atom_dbg_skip MipsAtom_(tape_enter) { + mac_load_word_imm(R_V0, u4_(TapeHostFrame_Loc)), + store_word(R_S0, R_V0, O_(TapeHostFrame,s0)), + store_word(R_S1, R_V0, O_(TapeHostFrame,s1)), + store_word(R_S2, R_V0, O_(TapeHostFrame,s2)), + store_word(R_S3, R_V0, O_(TapeHostFrame,s3)), + store_word(R_S4, R_V0, O_(TapeHostFrame,s4)), + store_word(R_S5, R_V0, O_(TapeHostFrame,s5)), + store_word(R_S6, R_V0, O_(TapeHostFrame,s6)), + store_word(R_S7, R_V0, O_(TapeHostFrame,s7)), + store_word(R_FP, R_V0, O_(TapeHostFrame,fp)), + store_word(R_SP, R_V0, O_(TapeHostFrame,sp)), + store_word(R_RA, R_V0, O_(TapeHostFrame,ra)), + add_ui(R_TapePtr, R_A0, 0), + load_upper_i(R_ScratchBase, u4_hi(Scratchpad_Loc)), + load_word(R_AtomJmp, R_TapePtr, 0), + add_ui_self( R_TapePtr, S_(MipsAtom)), + jump_reg(R_AtomJmp), BdSlot_ nop, +}; -/* Tape Runner (Static and Arg Clobbers) */ -FI_ void tape_run_a02_s07(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile( - asm_words( - load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ - , add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ - , call_reg( R_AtomJmp) /* jalr $t8 */ - , BdSlot_ nop /* Branch delay slot */ - ) - asm_rpins, r_use(tape_ptr) - asm_clobber: - rlit(R_AT), - rlit(R_V0), rlit(R_V1), rlit(R_A0), rlit(R_A1), rlit(R_A2), - rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4), - rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), - rlit(R_S0), rlit(R_S1), rlit(R_S2), rlit(R_S3), rlit(R_S4), - rlit(R_S5), rlit(R_S6), rlit(R_S7), - clb_mem_drain -); } +atom_dbg_skip MipsAtom_(tape_exit) { + mac_load_word_imm(R_V0, u4_(TapeHostFrame_Loc)), + load_word(R_S0, R_V0, O_(TapeHostFrame,s0)), + load_word(R_S1, R_V0, O_(TapeHostFrame,s1)), + load_word(R_S2, R_V0, O_(TapeHostFrame,s2)), + load_word(R_S3, R_V0, O_(TapeHostFrame,s3)), + load_word(R_S4, R_V0, O_(TapeHostFrame,s4)), + load_word(R_S5, R_V0, O_(TapeHostFrame,s5)), + load_word(R_S6, R_V0, O_(TapeHostFrame,s6)), + load_word(R_S7, R_V0, O_(TapeHostFrame,s7)), + load_word(R_RA, R_V0, O_(TapeHostFrame,ra)), + load_word(R_FP, R_V0, O_(TapeHostFrame,fp)), + load_word(R_SP, R_V0, O_(TapeHostFrame,sp)), + jump_reg(R_RA), BdSlot_ nop, +}; + +typedef void Proc_(TapeEntryFn)(MipsAtom* tape_ptr); + +FI_ void tape_run(Tape tape) { C_(TapeEntryFn*, tape_enter)(tape.ptr); } // Procedural authoring of tapes: typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; @@ -242,18 +274,13 @@ atom_dbg_skip MipsAtomComp_(ac_yield_load) { atom_dbg_skip MipsAtomComp_(ac_yield_tail) { add_ui_self(R_TapePtr, S_(MipsCode)), - jump_reg( R_AtomJmp), - BdSlot_ nop, + jump_reg( R_AtomJmp), BdSlot_ nop, }; #pragma endregion Macro Atom Components #pragma region Atom Builder // This helps with runtime procedural authoring of mips atoms. - -typedef Struct_(FMipsAtom512) { U4 data[512]; U4 used; }; - -// FArena Related typedef Relative_(FArena) Struct_(AtomBuilder) { U4 start; U4 capacity; U4 used; }; // Usual way to resolve an atom after the bulder is done. @@ -274,7 +301,6 @@ FI_ void tb_emit_atombuilder(TapeBuilder_R tb, AtomBuilder_R ab) { tb_emit(tb, a #pragma region Atom Arena // Just a dedicated FArena that is meant to mem_copy and return atom definitions made with MipsAtom_Proc_ - typedef Relative_(FArena) Struct_(AtomArena) { U4 start; U4 capacity; U4 used; }; #define atomarena_unused_start(ab) ((ab).start + (ab).used) @@ -299,13 +325,24 @@ FI_ void atomarena_reset(AtomArena_R aa) { aa->used = 0; } // TODO(Ed): Technically we can do this at comp-time with the metaprogram, but we may have namespace conflicts. // Unless we follow a convention for #define or something per register allocation boundary. -/* ABI + tape reserves that are never handed out by alloc. */ +/* ABI reserves that are never handed out by alloc. + * R_AT is the assembler temporary (per the MIPS O32 ABI). + * R_K0/K1 are kernel reserves. + * R_GP stays the host global pointer. + * R_SP/R_FP/R_RA are tape runtime carriers between tape_enter and tape_exit. */ U4 const regfile_abi_mask = - (1u << R_0) | (1u << R_AT) | - (1u << R_K0) | (1u << R_K1) | - (1u << R_GP) | (1u << R_SP) | - (1u << R_FP) | (1u << R_RA) | - (1u << R_T8) | (1u << R_T9); /* AtomJmp + TapePtr */ + (1u << R_0) | (1u << R_AT) | + (1u << R_K0) | (1u << R_K1) | + (1u << R_GP) | (1u << R_SP) | + (1u << R_FP) | (1u << R_RA); + +internal Reg const regfile_alloc_order[] = { + R_V0, R_V1, + R_A0, R_A1, R_A2, R_A3, + R_T0, R_T1, R_T2, R_T3, R_T4, R_T5, R_T6, R_T7, + R_S0, R_S1, R_S2, R_S3, R_S4, R_S5, R_S6, R_S7, + R_T8, R_T9, +}; typedef Struct_(RegFile) { A2_U2 GPR; @@ -336,17 +373,20 @@ FI_ Reg regfile__alloc_helper(A2_U2 file, Reg r_id) { Reg result = 0; RegFile_RInfo info = regfile_rinfo(file, r_id); if (info.occupied == false) { info.section[0] |= info.mask; - result = r_id; + result = r_id; } return result; } +/* regfile_alloc picks the next free GPR from regfile_alloc_order. + * The table is the first-fit allocation order: T0..T7, V0..V1, A0..A3, + * S0..S7, T8..T9. The 24 entries leave room for the tape program to use + * any of them while R0, R1, R26-R31 remain reserved. */ I_ Reg regfile_alloc(RegFile_R rf) { - U2 allocated = 0; - for index_iter(Reg, r_id, R_T0, <=, R_T7) { - allocated = regfile__alloc_helper(rf->GPR, r_id); Jmp_nZero_(allocated,resolved); + Reg allocated = 0; + for index_iter(U4, idx, 0, <, Array_len(regfile_alloc_order)) { + allocated = regfile__alloc_helper(rf->GPR, idx); + Jmp_nZero_(allocated,resolved); } - allocated = regfile__alloc_helper(rf->GPR, R_V0); Jmp_nZero_(allocated,resolved); - allocated = regfile__alloc_helper(rf->GPR, R_V1); assert(allocated != 0); resolved: return allocated; } @@ -383,9 +423,7 @@ FI_ void regfile_reset_to_mask(RegFile_R rf, U4 mask) { #pragma region Mips Atom Procs /* RegUse structs are a convention to organize register allocations for a mips atom procedure. - Unlike the usual enum-based declarations, they provide a namespaced scope - and have view types via union declarations. -*/ + Unlike the usual enum-based declarations, they provide a namespaced scope and have view types via union declarations. */ #define RegUse_(proc_name) (tmpl(RegUse,proc_name)) typedef Struct_(RegUse_example_atom_proc) { diff --git a/code/duffle/memory.h b/code/duffle/memory.h index badd008..1834182 100644 --- a/code/duffle/memory.h +++ b/code/duffle/memory.h @@ -134,10 +134,12 @@ FI_ U4 farena_unused_start(FArena arena) { return arena.start + arena.used; } #pragma region BIOS Scratchpad /* BIOS scratchpad location. 1 KB at 0x1F800000. - * The PS1 BIOS A-functions use this region for inter-call communication and as a temp storage area. - * The Tape runtime uses scratch region along with explicit data structures instead of the stack-based scratch from the C-Runtime. */ + * TapeHostFrame occupies the final 44 bytes while tape code executes. + * Atom scratch is bounded by the TapeHostFrame_Loc declaration in lottes_tape.h. */ enum { Scratchpad_Loc = 0x1F800000, + Scratchpad_Len = 0x400, /* 1 KB */ + Scratchpad_End = Scratchpad_Loc + Scratchpad_Len, /* 0x1F800400 */ }; #define C_scratch(type) C_(type, Scratchpad_Loc) #pragma endregion BIOS Scratchpad diff --git a/code/duffle/pad.atom.c b/code/duffle/pad.atom.c index 4c2ef6c..1f0548c 100644 --- a/code/duffle/pad.atom.c +++ b/code/duffle/pad.atom.c @@ -13,9 +13,8 @@ ATOM_FILE_DEBUGGER_LINE_MARKER(pad_atom_c); FI_ Slice_MipsCode ac_pad_set_centered_axes(AtomBuilder_R ab, Reg state, Reg scratch) atom_dbg_skip MipsAtomComp_Proc_(ab, { load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF), - or_i_self( scratch, PadAxis_Centered & 0xFFFF), - // mac_load_word_imm(scratch, PadAxis_Centered), - store_word( scratch, state, O_(PadState,axes)), + or_i_self( scratch, PadAxis_Centered & 0xFFFF), // mac_load_word_imm(scratch, PadAxis_Centered), + store_word( scratch, state, O_(PadState,axes)), }) FI_ Slice_MipsCode ac_pad_set_id_byte(AtomBuilder_R ab, Reg state, Reg r_id, U1 id_value) atom_dbg_skip MipsAtomComp_Proc_(ab, { @@ -32,8 +31,8 @@ FI_ Slice_MipsCode ac_pad_set_status(AtomBuilder_R ab, U4 r_tmp, U1 r_state, U4 * r_buttons must already be loaded (the caller is responsible for filling the load-delay slot of * the preceding load_half_u with an instruction that doesn't read r_buttons). */ FI_ Slice_MipsCode ac_pad_store_inverted_buttons(AtomBuilder_R ab, U1 r_buttons, U1 r_pad_state) atom_dbg_skip MipsAtomComp_Proc_(ab, { - nor_u( r_buttons, r_buttons, R_0), - store_half( r_buttons, r_pad_state, O_(PadState,buttons)), + nor_u( r_buttons, r_buttons, R_0), + store_half(r_buttons, r_pad_state, O_(PadState,buttons)), }) #pragma endregion MACs (Mips Atom Components) @@ -91,8 +90,8 @@ atom_label(snap_root) /* === Case 1: Disconnected (status == 0xFF). */ * If branch NOT taken (fall through to pending/id_dispatch), R_T4 is overwritten by the next case body's add_ui — harmless. */ atom_label(disconnected) /* === Disconnected body. */ - mac_pad_set_status(R_T4, R_PadState, PadStatus_Disconnected), - store_half( R_0, R_PadState, O_(PadState,buttons)), + mac_pad_set_status(R_T4, R_PadState, PadStatus_Disconnected), + store_half( R_0, R_PadState, O_(PadState,buttons)), mac_pad_set_centered_axes(R_PadState, R_T4), mac_pad_set_id_byte(R_PadState, R_RawId, PadRawStatus_Timeout), jump_rel(atom_offset(disconnected, snap_end)), @@ -108,8 +107,8 @@ atom_label(skip_disconnected) * If branch NOT taken (fall through to id_dispatch), R_T4 is overwritten by the digital/analog body add_ui - harmless. */ atom_label(pending) /* === Pending body (status=0, id=0 — pre-IRQ-empty buffer). */ - mac_pad_set_status(R_T4, R_PadState, PadStatus_Pending), - store_half( R_0, R_PadState, O_(PadState,buttons)), + mac_pad_set_status(R_T4, R_PadState, PadStatus_Pending), + store_half( R_0, R_PadState, O_(PadState,buttons)), mac_pad_set_centered_axes(R_PadState, R_T4), store_byte(R_RawId, R_PadState, O_(PadState,id)), jump_rel(atom_offset(pending, snap_end)), @@ -125,7 +124,7 @@ atom_label(id_dispatch) /* === Case 3-6: ID dispatch */ * R_T5 is then "dead" — only consumed at the analog_pad range check downstream. */ mac_pad_set_status(R_T4, R_PadState, PadStatus_Digital), load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw, buttons)), /* R_T4 = raw_buttons; */ - mac_load_word_imm(R_T5, PadAxis_Centered), /* fills the buttons-load's delay slot (doesn't read R_T4) */ + mac_load_word_imm( R_T5, PadAxis_Centered), /* fills the buttons-load's delay slot (doesn't read R_T4) */ // load_upper_i(R_T5, PadAxis_Centered_Hi), or_i_self(R_T5, PadAxis_Centered_Lo), mac_pad_store_inverted_buttons(R_T4, R_PadState), /* R_T4 settled: nor + sh writes ~raw_buttons to state.buttons */ store_word(R_T5, R_PadState, O_(PadState, axes)), /* single sw writes the 4-byte axes block at offset 8 (left_x, left_y, right_x, right_y) */ diff --git a/code/hello_camera/hello_camera.atom.c b/code/hello_camera/hello_camera.atom.c index 882a3d3..ac1d4d1 100644 --- a/code/hello_camera/hello_camera.atom.c +++ b/code/hello_camera/hello_camera.atom.c @@ -101,14 +101,14 @@ MipsAtomComp_Proc_(ab, { #pragma region resolve_look_at /* ─── resolve_look_at bundle chain atoms ──────────────────────────── */ -typedef AtomBundle_(resolve_look_at) { MipsAtom* - input_and_sub, - normalize_fwd_uz, - cross_to_right, - normalize_right_ux, - cross_to_up, - normalize_up_uy, - pop_mv_trans; +typedef AtomBundle_(resolve_look_at) { MipsAtom + *input_and_sub, + *normalize_fwd_uz, + *cross_to_right, + *normalize_right_ux, + *cross_to_up, + *normalize_up_uy, + *pop_mv_trans; }; enum { diff --git a/code/hello_camera/hello_camera.c b/code/hello_camera/hello_camera.c index 8445bf4..94d0cd1 100644 --- a/code/hello_camera/hello_camera.c +++ b/code/hello_camera/hello_camera.c @@ -53,7 +53,6 @@ #pragma endregion Hello Joypad TUs enum { - Scratchpad_Len = 1024, MemTape_Len = 512, ResolveLookAtArena_Words = 1024, @@ -86,11 +85,7 @@ typedef Struct_(SMemory) { U1 ct_init_atom_mem[CT_InitAtomMem_Size]; MipsAtom* normalize_v3s4; - MipsAtom* gte_cross_v3s4; /* GTE OP OuterProduct12 (a × b → out). - * Baked once at init from compile_gte_cross_v3s4() into ct_init_atom_mem. - * One instance serves both cross call sites in resolve_look_at; - * per-frame tape emits push src_a/src_b/out (3 pointers = 12 bytes) - * for Binds_gte_cross_v3s4. */ + MipsAtom* gte_cross_v3s4; U1 resolve_look_at_mem[ResolveLookAtArena_Size]; MipsAtom* resolve_look_at_bundle[AtomBundle_Len(resolve_look_at)]; @@ -138,70 +133,52 @@ I_ void resolve_look_at_c11(MT3_S2S4* look_at, P3_S4* eye, P3_S4* target, V3_S4* } FI_ void camera_look_at_c11(Camera* c, P3_S4* target, V3_S4* up_in) { resolve_look_at_c11(& c->look_at, & c->pos, target, up_in); } - - internal void compile_init_atoms(void) { - /* Single shared arena: each compile_* function pushes its atom into the same - * ct_init_atom_mem backing. If each function called atomarena_make() locally, - * the second call would reset arena.used to 0 and overwrite the first atom. - */ AtomArena ab = atomarena_make(slice_ut_arr(smem.ct_init_atom_mem)); RegFile rf = regfile(regfile_abi_mask); +#define ralloc() regfile_alloc(& rf) +#define ralloc_v3() { ralloc(), ralloc(), ralloc() } - /* === gte_cross_v3s4 — GTE OP OuterProduct12 (a × b → out) === - * Reused by both cross call sites in resolve_look_at. No scratch carrier. - * GPR pool: 9 allocatable (no carrier). Pool R_T0..R_T7, R_V0, R_V1 minus ABI = 9. - * Fits exactly. */ - { - smem.gte_cross_v3s4 = gte_cross_v3s4(& ab, - RegUse_(gte_cross_v3s4) { - .a = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, - .b = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, - .x = regfile_alloc(& rf), /* out / t0 shared */ - .y = regfile_alloc(& rf), /* src_a / t1 / rt11 shared */ - .z = regfile_alloc(& rf), /* src_b / t2 / rt22 shared */ - }); - regfile_reset(& rf); - } + smem.gte_cross_v3s4 = gte_cross_v3s4(& ab, + RegUse_(gte_cross_v3s4) { + .a = ralloc_v3(), + .b = ralloc_v3(), + .x = ralloc(), + .y = ralloc(), + .z = ralloc(), + }); + regfile_reset(& rf); - /* === build_normalize_v3s4 — Generic 4-stage GTE normalize === - * Reused by all 3 normalize call sites in resolve_look_at. Reads scratch + src/dst - * offsets from tape (no carrier — atom is fully self-contained per call). - * GPR pool: no carrier, 10 allocatable (R_T0..R_T7, R_V0, R_V1 minus ABI pins). - * 10 fields (scratch + src_ptr + dst_ptr + recip_est + norm + shift + src_x + - * t3 + t4 + t5) → just fits. Union members t5.src_offset, t3.dst_offset, etc. - * share GPRs via lifetime discipline. */ - { - RegFile rf = regfile(regfile_abi_mask); - smem.normalize_v3s4 = build_normalize_v3s4(& ab, - RegUse_(build_normalize_v3s4) { - .scratch = regfile_alloc(& rf), - .src_ptr = regfile_alloc(& rf), - .dst_ptr = regfile_alloc(& rf), - .recip_est = regfile_alloc(& rf), - .norm = regfile_alloc(& rf), - .shift = regfile_alloc(& rf), - .src_x = regfile_alloc(& rf), - .t3 = regfile_alloc(& rf), - .t4 = regfile_alloc(& rf), - .t5 = regfile_alloc(& rf), - }); - } + smem.normalize_v3s4 = build_normalize_v3s4(& ab, + RegUse_(build_normalize_v3s4) { + .scratch = ralloc(), + .src_ptr = ralloc(), + .dst_ptr = ralloc(), + .recip_est = ralloc(), + .norm = ralloc(), + .shift = ralloc(), + .src_x = ralloc(), + .t3 = ralloc(), + .t4 = ralloc(), + .t5 = ralloc(), + }); + regfile_reset(& rf); assert(ab.used <= CT_InitAtomMem_Size); +#undef ralloc +#undef ralloc_v3 } internal void compile_resolve_look_at(void) { - /* Wrap the static arena in a MipsAtomBuilder. */ - AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem)); - TapeBuilder tb = tb_make(slice_ut_arr(smem.resolve_look_at_bundle)); + AtomArena ab = atomarena_make(slice_ut_arr(smem.resolve_look_at_mem)); + AtomBundle_resolve_look_at_R bundle = C_(void*, smem.resolve_look_at_bundle); U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch); RegFile rf = regfile(pin_mask); #define ralloc() regfile_alloc(& rf) #define ralloc_v3() { ralloc(), ralloc(), ralloc() } - tb_emit_(AtomBundleEntry_(resolve_look_at, input_and_sub)(& ab, + bundle->input_and_sub = AtomBundleEntry_(resolve_look_at, input_and_sub)(& ab, RegUse_(resolve_look_at_input_and_sub) { .scratch = R_ResolveScratch, .target = ralloc(), @@ -212,37 +189,25 @@ internal void compile_resolve_look_at(void) { .t2 = ralloc(), .t3 = ralloc(), .t4 = ralloc(), - } - )); + }); regfile_reset_to_mask(& rf, pin_mask); - /* === ATOM 1: normalize fwd→uz === */ - smem.resolve_look_at_bundle[1] = smem.normalize_v3s4; + bundle->normalize_fwd_uz = smem.normalize_v3s4; + bundle->cross_to_right = smem.gte_cross_v3s4; + bundle->normalize_right_ux = smem.normalize_v3s4; + bundle->cross_to_up = smem.gte_cross_v3s4; + bundle->normalize_up_uy = smem.normalize_v3s4; - /* === ATOM 2: cross uz×up_in→right (reuses smem.gte_cross_v3s4) === */ - smem.resolve_look_at_bundle[2] = smem.gte_cross_v3s4; - - /* === ATOM 3: normalize right→ux === */ - smem.resolve_look_at_bundle[3] = smem.normalize_v3s4; - - /* === ATOM 4: cross uz×ux→up (reuses smem.gte_cross_v3s4) === */ - smem.resolve_look_at_bundle[4] = smem.gte_cross_v3s4; - - /* === ATOM 5: normalize up→uy === */ - smem.resolve_look_at_bundle[5] = smem.normalize_v3s4; - - /* === ATOM 6 (FUSED): populate + ctc2 RT + MVMVA + trans_matrix → look_at === */ - smem.resolve_look_at_bundle[6] = resolve_look_at__pop_mv_trans(& ab, - RegUse_(resolve_look_at__pop_mv_trans){ - .scratch = R_ResolveScratch, - .look_at = ralloc(), /* T0 */ - .eye = ralloc(), /* T1 — allocated BEFORE row so it doesn't alias row.y */ - .row = ralloc_v3(), /* T2 T3 T5 */ - .t6 = ralloc(), /* T6 = ux (populate) / v_x (matrix_vector) */ - .t7 = ralloc(), /* T7 = uy (populate) / v_y (matrix_vector) */ - .t8 = ralloc(), /* V0 = uz (populate) / v_z (matrix_vector) */ - }); - /* No regfile_reset between phases: the fused atom uses all 9 GPRs throughout. */ + bundle->pop_mv_trans = resolve_look_at__pop_mv_trans(& ab, + RegUse_(resolve_look_at__pop_mv_trans){ + .scratch = R_ResolveScratch, + .look_at = ralloc(), + .eye = ralloc(), + .row = ralloc_v3(), + .t6 = ralloc(), + .t7 = ralloc(), + .t8 = ralloc(), + }); /* Sanity check: arena didn't overflow. */ assert(ab.used <= ResolveLookAtArena_Size); @@ -258,42 +223,34 @@ I_ void resolve_look_at(TapeBuilder_R tb ){ /* Typed view of the scratchpad for field-address arithmetic. */ ResolveLookAtScratch* sp = C_scratch(ResolveLookAtScratch*); + AtomBundle_resolve_look_at_R bundle = C_(void*, smem.resolve_look_at_bundle); - tb_emit(tb, smem.resolve_look_at_bundle[0]); { + tb_emit(tb, bundle->input_and_sub); { tb_data(tb, u4_(target)); tb_data(tb, u4_(eye)); tb_data(tb, u4_(up_in)); tb_data(tb, u4_(smem.scratchpad)); } - - tb_emit(tb, smem.resolve_look_at_bundle[1]); { - /* Binds_NormalizeV3S4: src_offset (low 16) | dst_offset (high 16). Both U2s - * packed into a single U4 because the atom body reads at byte offsets 0 and 2 - * from R_TapePtr. Scratch base is baked into the atom (load_word_imm of - * Scratchpad_Loc). */ + tb_emit(tb, bundle->normalize_fwd_uz); { tb_data(tb, u4_(O_(ResolveLookAtScratch, fwd) | (O_(ResolveLookAtScratch, uz) << 16))); } - tb_emit(tb, smem.resolve_look_at_bundle[2]); { - /* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ - tb_data(tb, u4_(& sp->uz)); /* src_a */ - tb_data(tb, u4_(& sp->up_in)); /* src_b */ - tb_data(tb, u4_(& sp->right)); /* out */ + tb_emit(tb, bundle->cross_to_right); { + tb_data(tb, u4_(& sp->uz)); + tb_data(tb, u4_(& sp->up_in)); + tb_data(tb, u4_(& sp->right)); } - tb_emit(tb, smem.resolve_look_at_bundle[3]); { + tb_emit(tb, bundle->normalize_right_ux); { tb_data(tb, u4_(O_(ResolveLookAtScratch, right) | (O_(ResolveLookAtScratch, ux) << 16))); } - tb_emit(tb, smem.resolve_look_at_bundle[4]); { - /* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ - tb_data(tb, u4_(& sp->uz)); /* src_a */ - tb_data(tb, u4_(& sp->ux)); /* src_b */ - tb_data(tb, u4_(& sp->up)); /* out */ + tb_emit(tb, bundle->cross_to_up); { + tb_data(tb, u4_(& sp->uz)); + tb_data(tb, u4_(& sp->ux)); + tb_data(tb, u4_(& sp->up)); } - tb_emit(tb, smem.resolve_look_at_bundle[5]); { + tb_emit(tb, bundle->normalize_up_uy); { tb_data(tb, u4_(O_(ResolveLookAtScratch, up) | (O_(ResolveLookAtScratch, uy) << 16))); } - - /* === FUSED atom: populate + matrix_vector + trans_matrix (replaces 4 separate emits) === */ - tb_emit(tb, smem.resolve_look_at_bundle[6]); { + tb_emit(tb, bundle->pop_mv_trans); { tb_data(tb, u4_(look_at)); } } @@ -333,19 +290,13 @@ void update(PrimitiveArena* pa, U4* ordering_buf) gknown V3_S4_R acc = & smem.cube.accel; add_v3s4(vel, acc[0]); add_v3s4_fp(pos, vel[0]); - // vel->x += acc->x; - // vel->y += acc->y; - // vel->z += acc->z; - // pos->x += vel->x; - // pos->y += vel->y; - // pos->z += vel->z; if (pos->y + 150 > smem.floor.pos.y) vel->y *= -1; // Prep S4 nclip = 0; S4 orderingtbl_z = 0; - A2_S2 p; //??? + A2_S2 p; //??? S4 flag; //???? B4 use_c11_path = false; @@ -371,9 +322,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf) gte_matrix_set_rotation (& smem.tform_view); gte_matrix_set_translation(& smem.tform_view); - // gte_matrix_set_rotation (& smem.tform_world); - // gte_matrix_set_translation(& smem.tform_world); - U4 prim_base = u4_(pa->buf[smem.active_buf_id]); U4 prim_cursor = prim_base + pa->used; @@ -393,7 +341,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf) tb_data(& tb, u4_(& pa->used)); tb_data(& tb, prim_base); } - tape_run_a02_s07(tb_slice(tb));// Fire off the tape (bigger-clobber variant). + tape_run(tb_slice(tb));// Fire off the tape (bigger-clobber variant). // smem.cube.rot.y += 30; } @@ -435,7 +383,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf) tb_data(& tb, u4_(& pa->used)); tb_data(& tb, prim_base); } - tape_run_a02_s07(tb_slice(tb));// Fire off the tape (bigger-clobber variant). + tape_run(tb_slice(tb));// Fire off the tape (bigger-clobber variant). // C-side state (pa->used) has already been updated by the tape! // smem.floor.rot.y += 5; diff --git a/scripts/duffle.lua b/scripts/duffle.lua index b20c2ef..364141f 100644 --- a/scripts/duffle.lua +++ b/scripts/duffle.lua @@ -2,7 +2,7 @@ local scan = require("duffle_scan") local isa = require("duffle_isa") local emit = require("duffle_emit") -local M = {} +local M = {} local function merge(src, label) for k, v in pairs(src) do @@ -18,30 +18,30 @@ merge(isa, "duffle_isa") merge(emit, "duffle_emit") function M.corpus_view(ctx) - local corpus = ctx and ctx.shared and ctx.shared.corpus + local corpus = ctx and ctx.shared and ctx.shared.corpus if not corpus then error("requires ctx.shared.corpus", 0) end return { register_alias_registry = corpus.register_alias_registry or {}, - type_name_registry = corpus.type_name_registry or {}, - atom_views = corpus.atom_views or {}, - atom_ctxs = corpus.atom_ctxs or {}, - atom_phases = corpus.atom_phases or {}, - binds_by_name = corpus.binds_by_name or {}, - atoms_by_name = corpus.atoms_by_name or {}, - atom_infos = corpus.atom_infos or {}, - components = corpus.components or {}, - component_atom_infos = corpus.component_atom_infos or {}, - component_body_index = corpus.component_body_index or {}, - tape_chains = corpus.tape_chains or {}, - source_order = corpus.source_order or {}, - collisions = corpus.collisions or {}, + type_name_registry = corpus.type_name_registry or {}, + atom_views = corpus.atom_views or {}, + atom_ctxs = corpus.atom_ctxs or {}, + atom_phases = corpus.atom_phases or {}, + binds_by_name = corpus.binds_by_name or {}, + atoms_by_name = corpus.atoms_by_name or {}, + atom_infos = corpus.atom_infos or {}, + components = corpus.components or {}, + component_atom_infos = corpus.component_atom_infos or {}, + component_body_index = corpus.component_body_index or {}, + tape_chains = corpus.tape_chains or {}, + source_order = corpus.source_order or {}, + collisions = corpus.collisions or {}, } end function M.run_check_rules(rules, phase, item, pipe_ctx, findings) for _, rule in ipairs(rules) do local fn = rule[phase] - if fn then fn(item, pipe_ctx, findings) end + if fn then fn(item, pipe_ctx, findings) end end end diff --git a/scripts/duffle_emit.lua b/scripts/duffle_emit.lua index c9f6496..c92e800 100644 --- a/scripts/duffle_emit.lua +++ b/scripts/duffle_emit.lua @@ -74,11 +74,11 @@ end --- @param tok string --- @return string, string[] local function token_ident_and_args(tok) - local ident, after = M.read_ident(tok, 1) + local ident, after = M.read_ident(tok, 1) if not ident then return "?", {} end local paren_pos = M.skip_ws_and_cmt(tok, after) if tok:sub(paren_pos, paren_pos) ~= "(" then return ident, {} end - local inner = M.read_parens(tok, paren_pos) + local inner = M.read_parens(tok, paren_pos) if not inner then return ident, {} end return ident, split_call_args(inner) end @@ -104,9 +104,9 @@ local E_MAC_PREFIX_LEN = 4 --- --- Pure: reads `body_entry` / `component_index` / `word_counts`. Memoization is the caller's responsibility. --- Callers wanting `word_events` / `word_event_errors` precomputed for many atoms should memoize them per atom. ---- @param body_entry table -- `{body_tokens, body_off, line_of, source, declaration}` (declaration = root atom's atom.line) ---- @param component_index table -- the bare-name → ComponentBodyEntry map from M.get_component_body_index ---- @param word_counts table -- macro name → emitted-word count (from `ctx.shared.word_counts`) +--- @param body_entry table -- `{body_tokens, body_off, line_of, source, declaration}` (declaration = root atom's atom.line) +--- @param component_index table -- the bare-name → ComponentBodyEntry map from M.get_component_body_index +--- @param word_counts table -- macro name → emitted-word count (from `ctx.shared.word_counts`) --- @return WordEvent[], WordEventError[] -- ════════════════════════════════════════════════════════════════════════════ @@ -775,18 +775,16 @@ end ------------------------------------------------------------------------------- -- find_function_decl_for — backward walk for MipsAtomComp_Proc_ name extraction. -- --- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name --- is derived from the preceding `FI_ Slice_MipsCode ac_X(args)` function --- declaration. This function walks backward from `before_pos` to find it. +-- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name is derived from the preceding +-- `FI_ Slice_MipsCode ac_X(args)` function declaration. This function walks backward from `before_pos` to find it. -- -- Returns (raw_name, args_inner) or (nil, nil). -- raw_name — e.g. "ac_load_word_imm" -- args_inner — e.g. "AtomBuilder_R ab, Reg dst, U4 imm" -- --- The walk finds the LAST "Slice_MipsCode" before before_pos, then skips --- whitespace + qualifiers (FI_, atom_dbg_skip, comments) until it finds an --- ident followed by "(". That ident is the function name; the parens contents --- are the args. +-- The walk finds the LAST "Slice_MipsCode" before before_pos, then skips whitespace + qualifiers +-- (FI_, atom_dbg_skip, comments) until it finds an ident followed by "(". +-- That ident is the function name; the parens contents are the args. ------------------------------------------------------------------------------- function M.find_function_decl_for(source, before_pos, slice_mips_code_len) local search_pos = 1 @@ -819,13 +817,13 @@ function M.find_function_decl_for(source, before_pos, slice_mips_code_len) end -- skip block comments if source:sub(pos, pos + 1) == "/*" then - local close = source:find("*/", pos + 2, true) + local close = source:find("*/", pos + 2, true) if not close then break end pos = close + 2 goto continue end -- try to read an ident - local ident, ident_end = M.read_ident(source, pos) + local ident, ident_end = M.read_ident(source, pos) if not ident then break end -- check if the next non-ws char after ident is "(" local next_pos = M.skip_ws_and_cmt(source, ident_end) @@ -853,16 +851,15 @@ end -- args_inner — e.g. "AtomArena_R aa, U4 r_scratch, ..." -- after_paren — source position after the function `)` -- --- The walk finds the LAST "MipsAtom*" before before_pos, then skips --- whitespace + qualifiers (internal, I_, FI_, comments) until it finds an --- ident followed by "(". That ident is the name. The parens contents are the args. +-- The walk finds the LAST "MipsAtom*" before before_pos, then skips whitespace + qualifiers (internal, I_, FI_, comments) +-- until it finds an ident followed by "(". ------------------------------------------------------------------------------- function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) local search_pos = 1 local last_match = nil while true do -- plain=true: "*" is literal, no escaping needed - local found = source:find("MipsAtom*", search_pos, true) + local found = source:find("MipsAtom*", search_pos, true) if not found or found >= before_pos then break end last_match = found search_pos = found + mips_atom_ptr_len @@ -889,13 +886,13 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) end -- skip block comments if source:sub(pos, pos + 1) == "/*" then - local close = source:find("*/", pos + 2, true) + local close = source:find("*/", pos + 2, true) if not close then break end pos = close + 2 goto continue end -- try to read an ident - local ident, ident_end = M.read_ident(source, pos) + local ident, ident_end = M.read_ident(source, pos) if not ident then break end -- check if the next non-ws char after ident is "(" local next_pos = M.skip_ws_and_cmt(source, ident_end) @@ -912,4 +909,4 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) return nil, nil end - return M +return M diff --git a/scripts/duffle_isa.lua b/scripts/duffle_isa.lua index 923813e..96f8dfd 100644 --- a/scripts/duffle_isa.lua +++ b/scripts/duffle_isa.lua @@ -20,870 +20,122 @@ M.DELAY_MARKERS = { -- One row per encoder. Old table names are load-time views (build_isa_views). M.INSTRUCTION = { - - ["BdSlot_"] = { - cycles = 0, - kind = "marker", - }, - ["LdSlot_"] = { - cycles = 0, - kind = "marker", - }, - ["add_s"] = { - cycles = 1, - kind = "alu", - }, - ["add_si"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["add_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["add_u_self"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - value = { - dest = 1, - op = "add_u", - sources = { 1, 2 }, - }, - }, - ["add_ui"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 3, - op = "add_ui", - source = 2, - }, - }, - ["add_ui_self"] = { - cycles = 1, - kind = "alu", - reads = { 1 }, - writes = { 1 }, - imm = { - { - arg = 2, - signed = true, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 2, - op = "add_ui", - source = 1, - }, - }, - ["and"] = { - cycles = 1, - kind = "alu", - }, - ["and_i"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 3, - op = "and_i", - source = 2, - }, - }, - ["and_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["atom_bind"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["atom_info"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["atom_label"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["atom_offset"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["atom_reads"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["atom_writes"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["branch_equal"] = { - cycles = 2, - kind = "branch", - reads = { 1, 2 }, - writes = {}, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["branch_ge_zero"] = { - cycles = 2, - kind = "branch", - reads = { 1 }, - writes = {}, - imm = { - { - arg = 2, - signed = true, - width = 16, - }, - }, - }, - ["branch_gt_zero"] = { - cycles = 2, - kind = "branch", - reads = { 1 }, - writes = {}, - imm = { - { - arg = 2, - signed = true, - width = 16, - }, - }, - }, - ["branch_le_zero"] = { - cycles = 2, - kind = "branch", - reads = { 1 }, - writes = {}, - imm = { - { - arg = 2, - signed = true, - width = 16, - }, - }, - }, - ["branch_lt_zero"] = { - cycles = 2, - kind = "branch", - reads = { 1 }, - writes = {}, - imm = { - { - arg = 2, - signed = true, - width = 16, - }, - }, - }, - ["branch_ne"] = { - cycles = 2, - kind = "branch", - reads = { 1, 2 }, - writes = {}, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["call_addr"] = { - cycles = 2, - kind = "call", - reads = {}, - writes = { 1 }, - }, - ["call_reg"] = { - cycles = 2, - kind = "call", - reads = { 1 }, - writes = { 2 }, - }, - ["div_s"] = { - cycles = 35, - kind = "alu", - reads = { 1, 2 }, - writes = {}, - }, - ["div_u"] = { - cycles = 35, - kind = "alu", - reads = { 1, 2 }, - writes = {}, - }, - ["gte_load_v0"] = { - cycles = 2, - kind = "cop2_xfer", - reads = { 2 }, - writes = {}, - }, - ["gte_load_v0v1v2"] = { - cycles = 6, - kind = "cop2_xfer", - reads = { 2 }, - writes = {}, - }, - ["gte_load_v1"] = { - cycles = 2, - kind = "cop2_xfer", - reads = { 2 }, - writes = {}, - }, - ["gte_load_v2"] = { - cycles = 2, - kind = "cop2_xfer", - reads = { 2 }, - writes = {}, - }, - ["gte_lw"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = {}, - }, - ["gte_lwc2"] = { - cycles = 1, - kind = "load", - }, - ["gte_mv_from_ctrl_r"] = { - cycles = 1, - kind = "cop2_xfer", - reads = {}, - writes = { 1 }, - }, - ["gte_mv_from_data_r"] = { - cycles = 1, - kind = "cop2_xfer", - reads = {}, - writes = { 1 }, - }, - ["gte_mv_to_ctrl_r"] = { - cycles = 1, - kind = "cop2_xfer", - reads = { 1 }, - writes = {}, - }, - ["gte_mv_to_data_r"] = { - cycles = 1, - kind = "cop2_xfer", - reads = { 1 }, - writes = {}, - }, - ["gte_stotz"] = { - cycles = 1, - kind = "cop2_xfer", - reads = {}, - writes = {}, - }, - ["gte_stsxy3"] = { - cycles = 1, - kind = "cop2_xfer", - reads = {}, - writes = {}, - }, - ["gte_sw"] = { - cycles = 1, - kind = "store", - reads = { 2 }, - writes = {}, - }, - ["gte_swc2"] = { - cycles = 1, - kind = "store", - }, - ["jump"] = { - cycles = 2, - kind = "jump", - reads = {}, - writes = {}, - }, - ["jump_link"] = { - cycles = 2, - kind = "call", - reads = { 1 }, - writes = { 2 }, - }, - ["jump_reg"] = { - cycles = 2, - kind = "jump", - reads = { 1 }, - writes = {}, - suppress_arg1 = { - R_AtomJmp = "fixed mac_yield handshake", - }, - }, - ["jump_rel"] = { - cycles = 2, - kind = "branch", - delay_slot = false, - }, - ["li_s"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - value = { - dest = 1, - immediate = 3, - op = "add_ui", - source = 2, - }, - }, - ["load_byte"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["load_byte_u"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["load_half"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["load_half_u"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["load_imm"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_imm_1w"] = { - cycles = 1, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_imm_1w_s0"] = { - cycles = 1, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_imm_2w"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_imm_2w_addi_forced"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_imm_2w_ori_forced"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_ui"] = { - cycles = 1, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["load_upper_i"] = { - cycles = 1, - kind = "alu", - reads = {}, - writes = { 1 }, - imm = { - { - arg = 2, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 2, - op = "load_upper_i", - }, - }, - ["load_word"] = { - cycles = 1, - kind = "load", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["mac_yield"] = { - cycles = 0, - kind = "marker", - reads = {}, - writes = {}, - }, - ["mask_upper"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - }, - ["mov_from_high"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["mov_from_low"] = { - cycles = 2, - kind = "alu", - reads = {}, - writes = { 1 }, - }, - ["mov_to_high"] = { - cycles = 1, - kind = "alu", - reads = { 1 }, - writes = {}, - }, - ["mov_to_low"] = { - cycles = 1, - kind = "alu", - reads = { 1 }, - writes = {}, - }, - ["mult_s"] = { - cycles = 12, - kind = "alu", - reads = { 1, 2 }, - writes = {}, - }, - ["mult_u"] = { - cycles = 12, - kind = "alu", - reads = { 1, 2 }, - writes = {}, - }, - ["nop"] = { - cycles = 1, - kind = "nop", - reads = {}, - writes = {}, - }, - ["nop2"] = { - cycles = 2, - kind = "nop", - reads = {}, - writes = {}, - }, - ["nor_u"] = { - cycles = 1, - kind = "alu", - }, - ["or_i"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 3, - op = "or_i", - source = 2, - }, - }, - ["or_i_self"] = { - cycles = 1, - kind = "alu", - reads = { 1 }, - writes = { 1 }, - imm = { - { - arg = 2, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 2, - op = "or_i", - source = 1, - }, - }, - ["or_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["or_u_self"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - value = { - dest = 1, - op = "or", - sources = { 1, 2 }, - }, - }, - ["set_lt_s"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["set_lt_si"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - }, - ["set_lt_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["set_lt_ui"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - }, - ["shift_aright"] = { - cycles = 1, - kind = "alu", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 5, - }, - }, - }, - ["shift_aright_var"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 5, - }, - }, - }, - ["shift_lleft"] = { - cycles = 1, - kind = "alu", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 5, - }, - }, - }, - ["shift_lleft_self"] = { - cycles = 1, - kind = "alu", - reads = { 1 }, - writes = { 1 }, - imm = { - { - arg = 2, - width = 5, - }, - }, - value = { - dest = 1, - immediate = 2, - op = "shift_lleft", - source = 1, - }, - }, - ["shift_lleft_var"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["shift_lright"] = { - cycles = 1, - kind = "alu", - reads = { 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 5, - }, - }, - }, - ["slt_s"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["slt_si"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["slt_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["slt_ui"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["store_byte"] = { - cycles = 1, - kind = "store", - reads = { 1, 2 }, - writes = {}, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["store_half"] = { - cycles = 1, - kind = "store", - reads = { 1, 2 }, - writes = {}, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["store_word"] = { - cycles = 1, - kind = "store", - reads = { 1, 2 }, - writes = {}, - imm = { - { - arg = 3, - signed = true, - width = 16, - }, - }, - }, - ["sub_s"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["sub_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, - ["sys_mov_from_cop0"] = { - cycles = 1, - kind = "cop0_xfer", - reads = {}, - writes = { 1 }, - }, - ["sys_mov_to_cop0"] = { - cycles = 1, - kind = "cop0_xfer", - reads = { 1 }, - writes = {}, - }, - ["xor_i"] = { - cycles = 1, - kind = "alu", - reads = { 1, 2 }, - writes = { 1 }, - imm = { - { - arg = 3, - width = 16, - }, - }, - value = { - dest = 1, - immediate = 3, - op = "xor_i", - source = 2, - }, - }, - ["xor_u"] = { - cycles = 1, - kind = "alu", - reads = { 2, 3 }, - writes = { 1 }, - }, + ["BdSlot_"] = { cycles = 0, kind = "marker", }, + ["LdSlot_"] = { cycles = 0, kind = "marker", }, + ["add_s"] = { cycles = 1, kind = "alu", }, + ["add_si"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, },}, }, + ["add_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["add_u_self"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, value = { dest = 1, op = "add_u", sources = { 1, 2 }, }, }, + ["add_ui"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, value = { dest = 1, immediate = 3, op = "add_ui", source = 2, }, }, + ["add_ui_self"] = { cycles = 1, kind = "alu", reads = { 1 }, writes = { 1 }, imm = { { arg = 2, signed = true, width = 16, }, }, value = { dest = 1, immediate = 2, op = "add_ui", source = 1, }, }, + ["and"] = { cycles = 1, kind = "alu", }, + ["and_i"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, width = 16, }, }, value = { dest = 1, immediate = 3, op = "and_i", source = 2, }, }, + ["and_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["atom_bind"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["atom_info"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["atom_label"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["atom_offset"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["atom_reads"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["atom_writes"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["branch_equal"] = { cycles = 2, kind = "branch", reads = { 1, 2 }, writes = {}, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["branch_ge_zero"] = { cycles = 2, kind = "branch", reads = { 1 }, writes = {}, imm = { { arg = 2, signed = true, width = 16, }, }, }, + ["branch_gt_zero"] = { cycles = 2, kind = "branch", reads = { 1 }, writes = {}, imm = { { arg = 2, signed = true, width = 16, }, }, }, + ["branch_le_zero"] = { cycles = 2, kind = "branch", reads = { 1 }, writes = {}, imm = { { arg = 2, signed = true, width = 16, }, }, }, + ["branch_lt_zero"] = { cycles = 2, kind = "branch", reads = { 1 }, writes = {}, imm = { { arg = 2, signed = true, width = 16, }, }, }, + ["branch_ne"] = { cycles = 2, kind = "branch", reads = { 1, 2 }, writes = {}, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["call_addr"] = { cycles = 2, kind = "call", reads = {}, writes = { 1 }, }, + ["call_reg"] = { cycles = 2, kind = "call", reads = { 1 }, writes = { 2 }, }, + ["div_s"] = { cycles = 35, kind = "alu", reads = { 1, 2 }, writes = {}, }, + ["div_u"] = { cycles = 35, kind = "alu", reads = { 1, 2 }, writes = {}, }, + ["gte_load_v0"] = { cycles = 2, kind = "cop2_xfer", reads = { 2 }, writes = {}, }, + ["gte_load_v0v1v2"] = { cycles = 6, kind = "cop2_xfer", reads = { 2 }, writes = {}, }, + ["gte_load_v1"] = { cycles = 2, kind = "cop2_xfer", reads = { 2 }, writes = {}, }, + ["gte_load_v2"] = { cycles = 2, kind = "cop2_xfer", reads = { 2 }, writes = {}, }, + ["gte_lw"] = { cycles = 1, kind = "load", reads = { 2 }, writes = {}, }, + ["gte_lwc2"] = { cycles = 1, kind = "load", }, + ["gte_mv_from_ctrl_r"] = { cycles = 1, kind = "cop2_xfer", reads = {}, writes = { 1 }, }, + ["gte_mv_from_data_r"] = { cycles = 1, kind = "cop2_xfer", reads = {}, writes = { 1 }, }, + ["gte_mv_to_ctrl_r"] = { cycles = 1, kind = "cop2_xfer", reads = { 1 }, writes = {}, }, + ["gte_mv_to_data_r"] = { cycles = 1, kind = "cop2_xfer", reads = { 1 }, writes = {}, }, + ["gte_stotz"] = { cycles = 1, kind = "cop2_xfer", reads = {}, writes = {}, }, + ["gte_stsxy3"] = { cycles = 1, kind = "cop2_xfer", reads = {}, writes = {}, }, + ["gte_sw"] = { cycles = 1, kind = "store", reads = { 2 }, writes = {}, }, + ["gte_swc2"] = { cycles = 1, kind = "store", }, + ["jump"] = { cycles = 2, kind = "jump", reads = {}, writes = {}, }, + ["jump_link"] = { cycles = 2, kind = "call", reads = { 1 }, writes = { 2 }, }, + ["jump_reg"] = { cycles = 2, kind = "jump", reads = { 1 }, writes = {}, suppress_arg1 = { R_AtomJmp = "fixed mac_yield handshake", }, }, + ["jump_rel"] = { cycles = 2, kind = "branch", delay_slot = true, }, + ["li_s"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, value = { dest = 1, immediate = 3, op = "add_ui", source = 2, }, }, + ["load_byte"] = { cycles = 1, kind = "load", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["load_byte_u"] = { cycles = 1, kind = "load", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["load_half"] = { cycles = 1, kind = "load", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["load_half_u"] = { cycles = 1, kind = "load", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["load_imm"] = { cycles = 2, kind = "alu", reads = {}, writes = { 1 }, }, + ["load_ui"] = { cycles = 1, kind = "alu", reads = {}, writes = { 1 }, }, + ["load_upper_i"] = { cycles = 1, kind = "alu", reads = {}, writes = { 1 }, imm = { { arg = 2, width = 16, }, }, value = { dest = 1, immediate = 2, op = "load_upper_i", }, }, + ["load_word"] = { cycles = 1, kind = "load", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["mac_yield"] = { cycles = 0, kind = "marker", reads = {}, writes = {}, }, + ["mask_upper"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, }, + ["mov_from_high"] = { cycles = 2, kind = "alu", reads = {}, writes = { 1 }, }, + ["mov_from_low"] = { cycles = 2, kind = "alu", reads = {}, writes = { 1 }, }, + ["mov_to_high"] = { cycles = 1, kind = "alu", reads = { 1 }, writes = {}, }, + ["mov_to_low"] = { cycles = 1, kind = "alu", reads = { 1 }, writes = {}, }, + ["mult_s"] = { cycles = 12, kind = "alu", reads = { 1, 2 }, writes = {}, }, + ["mult_u"] = { cycles = 12, kind = "alu", reads = { 1, 2 }, writes = {}, }, + ["nop"] = { cycles = 1, kind = "nop", reads = {}, writes = {}, }, + ["nop2"] = { cycles = 2, kind = "nop", reads = {}, writes = {}, }, + ["nor_u"] = { cycles = 1, kind = "alu", }, + ["or_i"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, width = 16, }, }, value = { dest = 1, immediate = 3, op = "or_i", source = 2, }, }, + ["or_i_self"] = { cycles = 1, kind = "alu", reads = { 1 }, writes = { 1 }, imm = { { arg = 2, width = 16, }, }, value = { dest = 1, immediate = 2, op = "or_i", source = 1, }, }, + ["or_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["or_u_self"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, value = { dest = 1, op = "or", sources = { 1, 2 }, }, }, + ["set_lt_s"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["set_lt_si"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, }, + ["set_lt_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["set_lt_ui"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, }, + ["shift_aright"] = { cycles = 1, kind = "alu", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, width = 5, }, }, }, + ["shift_aright_var"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, imm = { { arg = 3, width = 5, }, }, }, + ["shift_lleft"] = { cycles = 1, kind = "alu", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, width = 5, }, }, }, + ["shift_lleft_self"] = { cycles = 1, kind = "alu", reads = { 1 }, writes = { 1 }, imm = { { arg = 2, width = 5, }, }, value = { dest = 1, immediate = 2, op = "shift_lleft", source = 1, }, }, + ["shift_lleft_var"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["shift_lright"] = { cycles = 1, kind = "alu", reads = { 2 }, writes = { 1 }, imm = { { arg = 3, width = 5, }, }, }, + ["slt_s"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["slt_si"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["slt_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["slt_ui"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, signed = true, width = 16,}, }, }, + ["store_byte"] = { cycles = 1, kind = "store", reads = { 1, 2 }, writes = {}, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["store_half"] = { cycles = 1, kind = "store", reads = { 1, 2 }, writes = {}, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["store_word"] = { cycles = 1, kind = "store", reads = { 1, 2 }, writes = {}, imm = { { arg = 3, signed = true, width = 16, }, }, }, + ["sub_s"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["sub_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, + ["sys_mov_from_cop0"] = { cycles = 1, kind = "cop0_xfer", reads = {}, writes = { 1 }, }, + ["sys_mov_to_cop0"] = { cycles = 1, kind = "cop0_xfer", reads = { 1 }, writes = {}, }, + ["xor_i"] = { cycles = 1, kind = "alu", reads = { 1, 2 }, writes = { 1 }, imm = { { arg = 3, width = 16, }, }, value = { dest = 1, immediate = 3, op = "xor_i", source = 2, }, }, + ["xor_u"] = { cycles = 1, kind = "alu", reads = { 2, 3 }, writes = { 1 }, }, } -- One row per GTE command. Alias cycle numbers live here, not on INSTRUCTION. M.GTE_COMMAND = { - ["gte_cmdw_avsz3"] = { aliases = { "gte_avg_sort_z3", "gte_avsz3", "gte_cmdw_avg_sort_z3" }, - cycles = 5, - inputs = { "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_ZSF3" }, + cycles = 5, + inputs = { "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_ZSF3" }, outputs = { - { - register = "C2_OTZ", - role = "otz", - }, + { register = "C2_OTZ", role = "otz", }, }, latch = { - { - register = "C2_OTZ", - required = 4, - }, + { register = "C2_OTZ", required = 4, }, }, }, ["gte_cmdw_avsz4"] = { aliases = { "gte_avg_sort_z4", "gte_avsz4", "gte_cmdw_avg_sort_z4" }, - cycles = 6, - inputs = { "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_ZSF4" }, + cycles = 6, + inputs = { "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_ZSF4" }, outputs = { - { - register = "C2_OTZ", - role = "otz", - }, + { register = "C2_OTZ", role = "otz", }, }, latch = { - { - register = "C2_OTZ", - required = 4, - }, + { register = "C2_OTZ", required = 4, }, }, }, ["gte_cmdw_gpf"] = { @@ -891,301 +143,162 @@ M.GTE_COMMAND = { cycles = 5, inputs = { "C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3" }, outputs = { - { - register = "C2_MAC1", - role = "mac_result", - }, - { - register = "C2_MAC2", - role = "mac_result", - }, - { - register = "C2_MAC3", - role = "mac_result", - }, - { - register = "C2_IR1", - role = "latest_color", - }, - { - register = "C2_IR2", - role = "latest_color", - }, - { - register = "C2_IR3", - role = "latest_color", - }, + { register = "C2_MAC1", role = "mac_result", }, + { register = "C2_MAC2", role = "mac_result", }, + { register = "C2_MAC3", role = "mac_result", }, + { register = "C2_IR1", role = "latest_color", }, + { register = "C2_IR2", role = "latest_color", }, + { register = "C2_IR3", role = "latest_color", }, }, latch = { - { - register = "C2_MAC1", - required = 4, - }, - { - register = "C2_MAC2", - required = 4, - }, - { - register = "C2_MAC3", - required = 4, - }, - { - register = "C2_IR1", - required = 4, - }, - { - register = "C2_IR2", - required = 4, - }, - { - register = "C2_IR3", - required = 4, - }, + { register = "C2_MAC1", required = 4, }, + { register = "C2_MAC2", required = 4, }, + { register = "C2_MAC3", required = 4, }, + { register = "C2_IR1", required = 4, }, + { register = "C2_IR2", required = 4, }, + { register = "C2_IR3", required = 4, }, }, }, ["gte_cmdw_mvmva"] = { aliases = {}, cycles = 8, - inputs = { "C2_VXY0", "C2_VZ0", "C2_VXY1", "C2_VZ1", "C2_VXY2", "C2_VZ2", "C2_IR1", "C2_IR2", "C2_IR3", "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ" }, + inputs = { + "C2_VXY0", "C2_VZ0", + "C2_VXY1", "C2_VZ1", + "C2_VXY2", "C2_VZ2", + "C2_IR1", "C2_IR2", "C2_IR3", + "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", + "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", + "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", + "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ" + }, outputs = { - { - register = "C2_IR1", - role = "latest_color", - }, - { - register = "C2_IR2", - role = "latest_color", - }, - { - register = "C2_IR3", - role = "latest_color", - }, + { register = "C2_IR1", role = "latest_color", }, + { register = "C2_IR2", role = "latest_color", }, + { register = "C2_IR3", role = "latest_color", }, }, latch = { - { - register = "C2_IR1", - required = 4, - }, - { - register = "C2_IR2", - required = 4, - }, - { - register = "C2_IR3", - required = 4, - }, + { register = "C2_IR1", required = 4, }, + { register = "C2_IR2", required = 4, }, + { register = "C2_IR3", required = 4, }, }, }, ["gte_cmdw_nclip"] = { aliases = { "gte_nclip" }, - cycles = 8, - inputs = { "C2_SXY0", "C2_SXY1", "C2_SXY2" }, + cycles = 8, + inputs = { "C2_SXY0", "C2_SXY1", "C2_SXY2" }, outputs = { - { - register = "C2_SZ3", - role = "mac_result", - }, + { register = "C2_SZ3", role = "mac_result", }, }, latch = { - { - register = "C2_SZ3", - required = 4, - }, + { register = "C2_SZ3", required = 4, }, }, }, ["gte_cmdw_op"] = { aliases = { "gte_cmdw_outer_product", "gte_cmdw_wedge" }, - cycles = 6, - inputs = {}, + cycles = 6, + inputs = {}, outputs = { - { - register = "C2_IR1", - role = "latest_color", - }, - { - register = "C2_IR2", - role = "latest_color", - }, - { - register = "C2_IR3", - role = "latest_color", - }, + { register = "C2_IR1", role = "latest_color", }, + { register = "C2_IR2", role = "latest_color", }, + { register = "C2_IR3", role = "latest_color", }, }, latch = { - { - register = "C2_IR1", - required = 4, - }, - { - register = "C2_IR2", - required = 4, - }, - { - register = "C2_IR3", - required = 4, - }, + { register = "C2_IR1", required = 4, }, + { register = "C2_IR2", required = 4, }, + { register = "C2_IR3", required = 4, }, }, }, ["gte_cmdw_rtps"] = { aliases = { "gte_cmdw_rotate_translate_perspective_single", "gte_rtps" }, cycles = 15, - inputs = { "C2_VXY0", "C2_VZ0", "C2_VXY1", "C2_VZ1", "C2_VXY2", "C2_VZ2", "C2_RGB", "C2_OTZ", "C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3", "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ", "gte_cr_OFX", "gte_cr_OFY", "gte_cr_H", "gte_cr_DQA", "gte_cr_DQB" }, + inputs = { + "C2_VXY0", "C2_VZ0", + "C2_VXY1", "C2_VZ1", + "C2_VXY2", "C2_VZ2", + "C2_RGB", "C2_OTZ", + "C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3", + "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", + "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", + "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", + "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", + "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ", + "gte_cr_OFX", "gte_cr_OFY", + "gte_cr_H", + "gte_cr_DQA", "gte_cr_DQB" + }, outputs = { - { - register = "C2_SXY2", - role = "latest_screen_xy", - }, - { - register = "C2_SZ2", - role = "latest_screen_z", - }, - { - register = "C2_OTZ", - role = "otz", - }, - { - register = "C2_IR0", - role = "latest_color", - }, + { register = "C2_SXY2", role = "latest_screen_xy", }, + { register = "C2_SZ2", role = "latest_screen_z", }, + { register = "C2_OTZ", role = "otz", }, + { register = "C2_IR0", role = "latest_color", }, }, latch = { - { - register = "C2_SXY2", - required = 4, - }, - { - register = "C2_SZ2", - required = 4, - }, - { - register = "C2_OTZ", - required = 4, - }, - { - register = "C2_IR0", - required = 4, - }, + { register = "C2_SXY2", required = 4, }, + { register = "C2_SZ2", required = 4, }, + { register = "C2_OTZ", required = 4, }, + { register = "C2_IR0", required = 4, }, }, }, ["gte_cmdw_rtpt"] = { aliases = { "gte_cmdw_rotate_translate_perspective_triple", "gte_rtpt" }, cycles = 23, - inputs = { "C2_VXY0", "C2_VZ0", "C2_VXY1", "C2_VZ1", "C2_VXY2", "C2_VZ2", "C2_RGB", "C2_OTZ", "C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3", "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ", "gte_cr_OFX", "gte_cr_OFY", "gte_cr_H", "gte_cr_DQA", "gte_cr_DQB" }, + inputs = { + "C2_VXY0", "C2_VZ0", + "C2_VXY1", "C2_VZ1", + "C2_VXY2", "C2_VZ2", + "C2_RGB", "C2_OTZ", + "C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3", + "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", + "gte_cr_RT11", "gte_cr_RT12", "gte_cr_RT13", + "gte_cr_RT21", "gte_cr_RT22", "gte_cr_RT23", + "gte_cr_RT31", "gte_cr_RT32", "gte_cr_RT33", + "gte_cr_TRX", "gte_cr_TRY", "gte_cr_TRZ", + "gte_cr_OFX", "gte_cr_OFY", + "gte_cr_H", + "gte_cr_DQA", "gte_cr_DQB" + }, outputs = { - { - register = "C2_SXY0", - role = "screen_xy[0]", - }, - { - register = "C2_SXY1", - role = "screen_xy[1]", - }, - { - register = "C2_SXY2", - role = "latest_screen_xy", - }, - { - register = "C2_SZ3", - role = "latest_screen_z", - }, - { - register = "C2_OTZ", - role = "otz", - }, + { register = "C2_SXY0", role = "screen_xy[0]", }, + { register = "C2_SXY1", role = "screen_xy[1]", }, + { register = "C2_SXY2", role = "latest_screen_xy", }, + { register = "C2_SZ3", role = "latest_screen_z", }, + { register = "C2_OTZ", role = "otz", }, }, latch = { - { - register = "C2_SXY0", - required = 4, - }, - { - register = "C2_SXY1", - required = 4, - }, - { - register = "C2_SXY2", - required = 4, - }, - { - register = "C2_SZ3", - required = 4, - }, - { - register = "C2_OTZ", - required = 4, - }, + { register = "C2_SXY0", required = 4, }, + { register = "C2_SXY1", required = 4, }, + { register = "C2_SXY2", required = 4, }, + { register = "C2_SZ3", required = 4, }, + { register = "C2_OTZ", required = 4, }, }, }, ["gte_cmdw_sqr"] = { aliases = {}, - cycles = 5, - inputs = { "C2_IR1", "C2_IR2", "C2_IR3" }, + cycles = 5, + inputs = { "C2_IR1", "C2_IR2", "C2_IR3" }, outputs = { - { - register = "C2_MAC1", - role = "mac_result", - }, - { - register = "C2_MAC2", - role = "mac_result", - }, - { - register = "C2_MAC3", - role = "mac_result", - }, - { - register = "C2_IR1", - role = "latest_color", - }, - { - register = "C2_IR2", - role = "latest_color", - }, - { - register = "C2_IR3", - role = "latest_color", - }, + { register = "C2_MAC1", role = "mac_result", }, + { register = "C2_MAC2", role = "mac_result", }, + { register = "C2_MAC3", role = "mac_result", }, + { register = "C2_IR1", role = "latest_color", }, + { register = "C2_IR2", role = "latest_color", }, + { register = "C2_IR3", role = "latest_color", }, }, latch = { - { - register = "C2_MAC1", - required = 4, - }, - { - register = "C2_MAC2", - required = 4, - }, - { - register = "C2_MAC3", - required = 4, - }, - { - register = "C2_IR1", - required = 4, - }, - { - register = "C2_IR2", - required = 4, - }, - { - register = "C2_IR3", - required = 4, - }, + { register = "C2_MAC1", required = 4, }, + { register = "C2_MAC2", required = 4, }, + { register = "C2_MAC3", required = 4, }, + { register = "C2_IR1", required = 4, }, + { register = "C2_IR2", required = 4, }, + { register = "C2_IR3", required = 4, }, }, }, } -function M.instr(ident) - return M.INSTRUCTION[ident] -end - -function M.gte_canon(ident) - return M.ALIAS_TO_CANONICAL[ident] or ident -end - -function M.gte(ident) - return M.GTE_COMMAND[M.gte_canon(ident)] -end +function M.instr (ident) return M.INSTRUCTION [ident] end +function M.gte_canon(ident) return M.ALIAS_TO_CANONICAL [ident] or ident end +function M.gte (ident) return M.GTE_COMMAND[M.gte_canon(ident)] end local function build_isa_views() M.ALIAS_TO_CANONICAL = {} @@ -1195,10 +308,10 @@ local function build_isa_views() M.ALIAS_TO_CANONICAL[alias] = canon end end - M.INSTRUCTION_LATENCY = {} + M.INSTRUCTION_LATENCY = {} M.INSTRUCTION_GPR_EFFECTS = {} - M.IMMEDIATE_FIELD_WIDTHS = {} - M.GPR_VALUE_RULES = {} + M.IMMEDIATE_FIELD_WIDTHS = {} + M.GPR_VALUE_RULES = {} M.CONTROL_TRANSFER_DELAY_SLOT_POLICIES = {} for name, row in pairs(M.INSTRUCTION) do M.INSTRUCTION_LATENCY[name] = row.cycles @@ -1208,31 +321,31 @@ local function build_isa_views() writes = row.writes or {}, } end - if row.imm then M.IMMEDIATE_FIELD_WIDTHS[name] = row.imm end - if row.value then M.GPR_VALUE_RULES[name] = row.value end + if row.imm then M.IMMEDIATE_FIELD_WIDTHS[name] = row.imm end + if row.value then M.GPR_VALUE_RULES [name] = row.value end if (row.kind == "branch" or row.kind == "jump" or row.kind == "call") and row.delay_slot ~= false then M.CONTROL_TRANSFER_DELAY_SLOT_POLICIES[name] = { - family = row.kind, + family = row.kind, suppress_arg1 = row.suppress_arg1, } end end M.GTE_COMMAND_ALIASES = {} - M.GTE_COMMAND_INPUTS = {} + M.GTE_COMMAND_INPUTS = {} M.GTE_COMMAND_OUTPUTS = {} M.GTE_COMMAND_LATCH_WINDOWS = {} for canon, row in pairs(M.GTE_COMMAND) do - M.GTE_COMMAND_ALIASES[canon] = canon - M.INSTRUCTION_LATENCY[canon] = row.cycles + M.GTE_COMMAND_ALIASES [canon] = canon + M.INSTRUCTION_LATENCY [canon] = row.cycles M.INSTRUCTION_GPR_EFFECTS[canon] = { reads = {}, writes = {} } for _, alias in ipairs(row.aliases or {}) do - M.GTE_COMMAND_ALIASES[alias] = canon - M.INSTRUCTION_LATENCY[alias] = row.cycles + M.GTE_COMMAND_ALIASES [alias] = canon + M.INSTRUCTION_LATENCY [alias] = row.cycles M.INSTRUCTION_GPR_EFFECTS[alias] = { reads = {}, writes = {} } end - M.GTE_COMMAND_INPUTS[canon] = row.inputs - M.GTE_COMMAND_OUTPUTS[canon] = row.outputs + M.GTE_COMMAND_INPUTS [canon] = row.inputs + M.GTE_COMMAND_OUTPUTS [canon] = row.outputs M.GTE_COMMAND_LATCH_WINDOWS[canon] = row.latch end end @@ -1240,22 +353,19 @@ build_isa_views() --- 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 within a group write to the same C2 control-register slot (the HW 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. +--- 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 HW 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 } --- Packed RT slots named by the gte.h packed-slot comment. --- first must be written before second. +-- 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" }, } @@ -1265,8 +375,8 @@ M.GTE_PACKED_SLOT_RELATIONS = { -- Covers the current encoder vocabulary (`code/duffle/mips.h` + `code/duffle/gte.h`); add rows here as new encoders land. -- -- Semantics: --- * A "GPR operand position" is the textual slot in the macro's argument list, 1-based; e.g. `load_word(rt, base, off)` has --- positional operands 1 (rt), 2 (base), 3 (off). The table reads operands 1 + 2 + 3 to find what GPRs the macro touches. +-- * A "GPR operand position" is the textual slot in the macro's argument list, 1-based; e.g. `load_word(rt, base, off)` has positional operands 1 (rt), 2 (base), 3 (off). +-- The table reads operands 1 + 2 + 3 to find what GPRs the macro touches. -- * The check tracks one entry per destination GPR per MFC2 / CFC2 event. -- A subsequent event counts as a "use" iff any of its read operand positions reference that destination GPR's ident (e.g. `R_T0`). -- * Branch delay slots are out of scope (MIPS control-flow; tracked separately). @@ -1405,9 +515,9 @@ M.UNKNOWN_INSTRUCTION_CYCLES = 1 -- -- Visibility semantics: -- * `kind = "post_producer_words"` means the consumer observes the producer's effect after `required` independent emitted words that are --- strictly between the producer and the consumer. The producer's own emitted slot is implicit (it counts as the slot of issue, not toward --- `required`) — per the PSX-SPX rule: "Store delays are counted in numbers of clock cycles (not in numbers of opcodes). For 3 cycle delay, --- one must usually insert 3 cached opcodes (or one uncached opcode)." +-- strictly between the producer and the consumer. The producer's own emitted slot is implicit (it counts as the slot of issue, not toward `required`) +-- per the PSX-SPX rule: "Store delays are counted in numbers of clock cycles (not in numbers of opcodes). +-- For 3 cycle delay, one must usually insert 3 cached opcodes (or one uncached opcode)." -- * `required` is the minimum count of intervening emitted words between producer and consumer. -- `required = 0` permits the consumer on the very next slot; `required < 0` would place the consumer on the same slot as the producer -- and is reserved for future "self-retires" relations. diff --git a/scripts/duffle_paths.lua b/scripts/duffle_paths.lua index cdd6fe7..5aee365 100644 --- a/scripts/duffle_paths.lua +++ b/scripts/duffle_paths.lua @@ -22,12 +22,10 @@ local M = {} local CACHE_KEY = "__duffle_repo_root__" --- Resolve the repo root from this script's own path. Zero shell spawn. ---- `duffle_paths.lua` always lives at `/scripts/duffle_paths.lua`, so the repo root is the ---- parent of the directory containing this script. We derive it directly from `debug.getinfo(1, "S").source` ---- (returns `@` for the currently-running chunk). +--- `duffle_paths.lua` always lives at `/scripts/duffle_paths.lua`, so the repo root is the parent of the directory containing this script. +--- We derive it directly from `debug.getinfo(1, "S").source` (returns `@` for the currently-running chunk). --- ---- If `debug.getinfo` can't parse this script's path (shouldn't happen — dofile always populates source), ---- return nil and let `M.setup()` fail loud. +--- If `debug.getinfo` can't parse this script's path (shouldn't happen — dofile always populates source), return nil and let `M.setup()` fail loud. --- @return string|nil local function find_repo_root() if package.loaded[CACHE_KEY] then return package.loaded[CACHE_KEY] end @@ -51,17 +49,13 @@ end --- --- This script does NOT touch the OS environment: no `os.setenv`, no `os.putenv`, no `$PATH` mods. --- It just sets `package.path` and `package.cpath` (the standard Lua way to register module search dirs). ---- lpeg is built by `update_deps.ps1` to `toolchain/lpeg/`, ---- which we wire into `package.cpath` here (so `require("lpeg")` from `duffle.lua` resolves without any global state). +--- lpeg is built by `update_deps.ps1` to `toolchain/lpeg/`, which we wire into `package.cpath` here (so `require("lpeg")` from `duffle.lua` resolves without any global state). function M.setup() local repo_root = find_repo_root() if not repo_root then - -- Unreachable in practice: find_repo_root() derives the repo root from this script's - -- own source path via debug.getinfo(1, "S").source (no subprocess, no git CLI, <1ms). - -- A nil return means the source path did not match the expected - -- /scripts/duffle_paths.lua layout — a packaging bug, not a "missing git repo" - -- condition. os.exit(2) is retained so a real failure surfaces loud rather than - -- silently producing an unconfigured module table. + -- Unreachable in practice: find_repo_root() derives the repo root from this script's own source path via debug.getinfo(1, "S").source (no subprocess, no git CLI, <1ms). + -- A nil return means the source path did not match the expected /scripts/duffle_paths.lua layout — a packaging bug, not a "missing git repo" condition. + -- os.exit(2) is retained so a real failure surfaces loud rather than silently producing an unconfigured module table. os.exit(2) end @@ -86,6 +80,6 @@ end -- Run the setup as a side effect. M.setup() --- Now that package.path includes scripts/, `require("duffle")` resolves. Return the duffle module --- so callers can do `local duffle = dofile(...duffle_paths.lua)` in one line. +-- Now that package.path includes scripts/, `require("duffle")` resolves. +-- Return the duffle module so callers can do `local duffle = dofile(...duffle_paths.lua)` in one line. return require("duffle") diff --git a/scripts/elf_dwarf.lua b/scripts/elf_dwarf.lua index 1f0483a..c17cc24 100644 --- a/scripts/elf_dwarf.lua +++ b/scripts/elf_dwarf.lua @@ -422,10 +422,10 @@ end --- should use this directly rather than going through `read_form_value`, --- which only exposes the low 4 bytes to preserve its existing (value, next_pos) return shape. --- @param buf string ---- @param pos integer -- zero-based wire offset ---- @return integer -- low 4 bytes (LE), the type signature ---- @return integer -- high 4 bytes (LE), the offset within the matching type unit ---- @return integer -- cursor after the 8-byte value +--- @param pos integer -- zero-based wire offset +--- @return integer -- low 4 bytes (LE), the type signature +--- @return integer -- high 4 bytes (LE), the offset within the matching type unit +--- @return integer -- cursor after the 8-byte value function M.read_ref_sig8(buf, pos) return M.read_u32_le(buf, pos), M.read_u32_le(buf, pos + 4), pos + 8 end @@ -440,10 +440,10 @@ end --- unit_length(4) + version(2) + unit_type(1) + address_size(1) + debug_abbrev_offset(4) --- followed by type_unit_specific fields: type_signature(8) + type_offset(4) --- The type_signature is at byte offset 8 of the body (right after debug_abbrev_offset). ---- @param info string -- the .debug_info section bytes ---- @param target_sig_lo integer -- low 4 bytes (LE) of the desired signature ---- @param target_sig_hi integer -- high 4 bytes (LE) of the desired signature ---- @return integer|nil, integer|nil -- unit offset, type_offset within the unit +--- @param info string -- the .debug_info section bytes +--- @param target_sig_lo integer -- low 4 bytes (LE) of the desired signature +--- @param target_sig_hi integer -- high 4 bytes (LE) of the desired signature +--- @return integer|nil, integer|nil -- unit offset, type_offset within the unit function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi) local pos = 0 local section_len = #info @@ -459,24 +459,23 @@ function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi) return nil, nil -- malformed end -- Per DWARF5 §7.5.6, the type_unit (DW_UT_type = 0x02) body layout is: - -- 0: version (2) - -- 2: unit_type (1) -- DW_UT_type = 0x02 - -- 3: address_size (1) - -- 4: debug_abbrev_offset (4) - -- 8: type_signature (8) - -- 16: type_offset (4) - -- 20: + -- 0: version (2) + -- 2: unit_type (1) -- DW_UT_type = 0x02 + -- 3: address_size (1) + -- 4: debug_abbrev_offset (4) + -- 8: type_signature (8) + -- 16: type_offset (4) + -- 20: if body_end - body_start >= 20 then -- read_ref_sig8 / write_u32_le / etc. are 1-indexed (string:byte); - -- pos / body_start / body_end are 0-based wire offsets, so the - -- 1-indexed byte at 0-based wire offset X is string:byte(X + 1). + -- pos / body_start / body_end are 0-based wire offsets, so the 1-indexed byte at 0-based wire offset X is string:byte(X + 1). -- Per DWARF5 §7.5.6, the type_unit body is laid out as: - -- byte 0-1: version (2) - -- byte 2: unit_type (1) -- DW_UT_type = 0x02 - -- byte 3: address_size (1) - -- byte 4-7: debug_abbrev_offset (4) - -- byte 8-15: type_signature (8) - -- byte 16-19: type_offset (4) + -- byte 0-1: version (2) + -- byte 2: unit_type (1) -- DW_UT_type = 0x02 + -- byte 3: address_size (1) + -- byte 4-7: debug_abbrev_offset (4) + -- byte 8-15: type_signature (8) + -- byte 16-19: type_offset (4) local unit_type = info:byte(body_start + 2 + 1) -- 0-based +2 = unit_type in 1-indexed if unit_type == 0x02 then -- DW_UT_type local sig_lo, sig_hi, _ = M.read_ref_sig8(info, body_start + 8) -- 0-based +8 = type_signature in 1-indexed diff --git a/scripts/launch_pcsx_debug.ps1 b/scripts/launch_pcsx_debug.ps1 index 10952d8..22871e1 100644 --- a/scripts/launch_pcsx_debug.ps1 +++ b/scripts/launch_pcsx_debug.ps1 @@ -1,10 +1,9 @@ # scripts/launch_pcsx_debug.ps1 # -# One-shot launcher for debug sessions: starts pcsx-redux with the .ps-exe -# loaded, the gdb stub enabled, AND the pcsx_debug_helper Lua plugin loaded -# so external CLI tools (gdb's `shell` command, etc.) -# can read GTE state via http://localhost:8080/api/v1/lua/gte -# (the gdb stub doesn't expose COP2 at all). +# One-shot launcher for debug sessions: +# Starts pcsx-redux with the .ps-exe loaded, the gdb stub enabled, +# AND the pcsx_debug_helper Lua plugin loaded so external CLI tools (gdb's `shell` command, etc.) +# can read GTE state via http://localhost:8080/api/v1/lua/gte (the gdb stub doesn't expose COP2 at all). # # usage: # .\scripts\launch_pcsx_debug.ps1 @@ -19,11 +18,11 @@ [CmdletBinding()] param( - [string]$PcsxPath = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.exe'), - [string]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'), + [string]$PcsxPath = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.exe'), + [string]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'), [string]$HelperZip = (Join-Path $PSScriptRoot 'pcsx_debug_helper.zip'), - [int] $GdbPort = 3333, - [int] $WebPort = 8080 + [int] $GdbPort = 3333, + [int] $WebPort = 8080 ) $ErrorActionPreference = 'Stop' @@ -84,7 +83,8 @@ try { $r = Invoke-WebRequest -Uri "http://localhost:$WebPort/api/v1/lua/gte" -UseBasicParsing -TimeoutSec 5 $firstLine = ([System.Text.Encoding]::UTF8.GetString($r.Content) -split "`n")[0] Write-Host "GTE handler OK: $firstLine" -ForegroundColor Green -} catch { +} +catch { Write-Warning "GTE handler NOT responding: $_" Write-Host "Check the pcsx-redux Lua Console for debug cli messages." -ForegroundColor Yellow } diff --git a/scripts/passes/auto_reg.lua b/scripts/passes/auto_reg.lua index 2d40b3f..b1fb482 100644 --- a/scripts/passes/auto_reg.lua +++ b/scripts/passes/auto_reg.lua @@ -27,50 +27,34 @@ local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---- ════════════════════════════════════════════════════════════════════════════ ---- THE GPR ALLOCATION POOL — what is allocatable, and (more importantly) WHY ---- ════════════════════════════════════════════════════════════════════════════ ---- ---- The auto-reg pass picks physical GPRs for `atom_auto_reg(...)` / `phase_auto_reg(...)` markers. ---- It allocates from a FIXED 10-register pool. ---- This comment block makes the inclusion AND exclusion criteria obvious so a reader doesn't have ---- to grep lottes_tape.h + mips.h to understand the design. ---- ---- ── WHAT'S IN THE POOL (10 GPRs, all caller-trash per the O32 ABI) ──────── ---- R_T0..R_T7 (GPR codes 8..15), R_V0..R_V1 (GPR codes 2..3) ---- The workhorse of every atom body. The uesr should be aware of atom allocation across atoms they chain. ---- If they have a collision it means either they didn't saturate the register file optimally for a phase, ---- or the may have made the workload to large for the run. ---- ---- ── WHAT'S NOT IN THE POOL — and WHY (the "obvious exclusions") ──────────── ---- R_T9 (GPR code 25) — R_TapePtr, the tape instruction stream pointer. ---- Owned by the tape runtime (in tape_run / tape_run_a02_s07). ---- `rgcc(R_TapePtr)` register-variable ties the C compiler's view to $t9 across the whole tape_run. ---- The auto-reg pass MUST NOT clobber this; doing so would desync the C-side tape pointer from the ---- hardware pointer and crash on the next tape_run. ---- ---- R_T8 (GPR code 24) — R_AtomJmp, the atom-jump register used by the 4-word yield handshake. ---- Every `mac_yield()` / `mac_yield_tail` does `load_word R_AtomJmp, R_TapePtr, 0` then ---- `jump_reg R_AtomJmp`. The auto-reg pass MUST NOT clobber this either, or the atom dispatcher breaks. ---- Owned by the tape runtime, same family as R_TapePtr. ---- ---- R_AT (GPR code 1) — Assembler temporary. Reserved by the MIPS O32 ABI for pseudoinstruction expansion ---- (lottes_tape.h:86, mips.h:93). The ISA's psuedo instructions use it as a scratch temporary. ---- ---- R_A0..A3 (codes 4..7) — Function arguments. Used in tape_run_a02_s07, see below. ---- R_S0..S7 (codes 16..23) — Callee-saved. Preserved across C-ABI calls by convention. ---- The `tape_run_a02_s07` variant clobbers them deliberately, but the default `tape_run` does NOT. ---- Kept out of POOL to preserve the conservative default. ---- Add them in a separate "big clobber" pool if/when needed. ---- ---- R_K0/K1 (codes 26..27) — Kernel / interrupt handler reserves. Never touched by user code; OS-internal. ---- R_GP/SP/FP/RA (codes 28..31) — Stack frame + return-address. Owned by the C compiler; never allocatable. ---- R_0 (code 0) — Hardwired zero. Cannot be written. +-- ════════════════════════════════════════════════════════════════════════════ +-- THE GPR ALLOCATION POOL — what's allocatable, and (more importantly) WHY +-- ════════════════════════════════════════════════════════════════════════════ +-- +-- The auto-reg pass picks physical GPRs for `atom_auto_reg(...)` / `phase_auto_reg(...)` markers. +-- The 24-register pool covers R2-R25 (the user/atom allocatable surface): +-- R_T0..R_T7, R_V0..R_V1, R_A0..A3, R_S0..S7, R_T8..T9. +-- Excluded (and never added to the pool): +-- R_0 (code 0) — hardwired zero. Cannot be written. +-- R_AT (code 1) — assembler temporary. Reserved by the MIPS O32 ABI. +-- R_A0..A3 — explicitly omitted above even though their integer codes +-- map to POOL entries; the pool-construction loop below +-- only references the POOL string literals, never the +-- integer codes, so they are NOT auto-allocated by default. +-- (A0-A3 become available when the user adds them to +-- POOL or hardcodes an R_A0 reference in the atom body.) +-- R_K0/K1 (codes 26-27) — kernel / interrupt handler reserves. Never touched by user code. +-- R_GP/SP/FP/RA (codes 28-31) — R_SP/R_FP/R_RA are tape-runtime carriers between +-- tape_enter and tape_exit; R_GP stays the host global pointer. --- local POOL = { - "R_T0", "R_T1", "R_T2", "R_T3", - "R_T4", "R_T5", "R_T6", "R_T7", - "R_V0", "R_V1", + "R_T0", "R_T1", "R_T2", "R_T3", + "R_T4", "R_T5", "R_T6", "R_T7", + "R_V0", "R_V1", + "R_A0", "R_A1", "R_A2", "R_A3", + "R_S0", "R_S1", "R_S2", "R_S3", + "R_S4", "R_S5", "R_S6", "R_S7", + "R_T8", "R_T9", } -- Map from integer MIPS GPR code (the `code` field on AliasEntry) to the physical GPR ident in POOL. @@ -79,44 +63,48 @@ local POOL = { -- (R_AT=1, R_A0..A3=4..7, R_T8=24, R_T9=25, R_K0/K1=26..27, R_GP/SP/FP/RA=28..31) -- are deliberately omitted — see the comment block above for the WHY of each exclusion. local INT_CODE_TO_POOL_GPR = { - [2] = "R_V0", [3] = "R_V1", - [8] = "R_T0", [9] = "R_T1", [10] = "R_T2", [11] = "R_T3", - [12] = "R_T4", [13] = "R_T5", [14] = "R_T6", [15] = "R_T7", + [2] = "R_V0", [3] = "R_V1", + [4] = "R_A0", [5] = "R_A1", [6] = "R_A2", [7] = "R_A3", + [8] = "R_T0", [9] = "R_T1", [10] = "R_T2", [11] = "R_T3", + [12] = "R_T4", [13] = "R_T5", [14] = "R_T6", [15] = "R_T7", + [16] = "R_S0", [17] = "R_S1", [18] = "R_S2", [19] = "R_S3", + [20] = "R_S4", [21] = "R_S5", [22] = "R_S6", [23] = "R_S7", + [24] = "R_T8", [25] = "R_T9", } -- Stable sort for deterministic allocation order. local function stable_sort_keys(tbl) - local keys = {} - for k in pairs(tbl) do keys[#keys + 1] = k end - table.sort(keys) - return keys + local keys = {} + for k in pairs(tbl) do keys[#keys + 1] = k end + table.sort(keys) + return keys end -- Allocate one phase's auto-reg mappings. -- Returns (allocated_map, errors). On pool exhaustion, errors is populated and the function halts. local function allocate_phase(phase_label, decls) - -- Deep-copy POOL into a fresh sequence table. The original `table.unpack and table.unpack(POOL) or { unpack(POOL) }` - -- idiom wraps the unpacked values in a single inner table under LuaJIT 5.1 (`table.unpack` is nil; the `or` returns one value), - -- which corrupts the pool into `{ {R_T0, R_T1, ...} }` — making `table.remove(pool, 1)` return the inner table on iteration. - local pool = {} - for i = 1, #POOL do pool[i] = POOL[i] end - local result = {} - local errors = {} - for _, sym in ipairs(stable_sort_keys(decls)) do - local next_gpr = table.remove(pool, 1) - if not next_gpr then - errors[#errors + 1] = { - line = 0, - msg = string.format("phase_register_pool_exhausted: " - .. "phase '%s' requested symbol '%s' but the pool has no remaining registers " - .. "(max 10 per phase: R_T0..R_T7 + R_V0..R_V1). Split the phase or use hardcoded GPRs." - , phase_label, sym), - } - return result, errors - end - result[sym] = next_gpr - end - return result, errors + -- Deep-copy POOL into a fresh sequence table. The original `table.unpack and table.unpack(POOL) or { unpack(POOL) }` + -- idiom wraps the unpacked values in a single inner table under LuaJIT 5.1 (`table.unpack` is nil; the `or` returns one value), + -- which corrupts the pool into `{ {R_T0, R_T1, ...} }` — making `table.remove(pool, 1)` return the inner table on iteration. + local pool = {} + for i = 1, #POOL do pool[i] = POOL[i] end + local result = {} + local errors = {} + for _, sym in ipairs(stable_sort_keys(decls)) do + local next_gpr = table.remove(pool, 1) + if not next_gpr then + errors[#errors + 1] = { + line = 0, + msg = string.format("phase_register_pool_exhausted: " + .. "phase '%s' requested symbol '%s' but the pool has no remaining registers " + .. "(max 24 per phase: R_T0..R_T7 + R_V0..R_V1 + R_A0..R_A3 + R_S0..R_S7 + R_T8..R_T9). Split the phase or use hardcoded GPRs." + , phase_label, sym), + } + return result, errors + end + result[sym] = next_gpr + end + return result, errors end -- Build two projections from corpus.register_alias_registry: @@ -128,19 +116,19 @@ end -- Aliases whose `code` points to a non-POOL GPR (e.g. R_S0, R_T8, R_K1) are ignored — -- they don't affect the auto_reg pool, and they're already excluded from POOL above. local function build_user_pins(corpus) - local user_pinned = {} - local alias_to_gpr = {} - if not corpus.register_alias_registry then return user_pinned, alias_to_gpr end - for alias_name, alias_entry in pairs(corpus.register_alias_registry) do - if alias_entry.has_atom_reg and alias_entry.code then - local gpr = INT_CODE_TO_POOL_GPR[alias_entry.code] - if gpr then - user_pinned[gpr] = true - alias_to_gpr[alias_name] = gpr - end - end - end - return user_pinned, alias_to_gpr + local user_pinned = {} + local alias_to_gpr = {} + if not corpus.register_alias_registry then return user_pinned, alias_to_gpr end + for alias_name, alias_entry in pairs(corpus.register_alias_registry) do + if alias_entry.has_atom_reg and alias_entry.code then + local gpr = INT_CODE_TO_POOL_GPR[alias_entry.code] + if gpr then + user_pinned[gpr] = true + alias_to_gpr[alias_name] = gpr + end + end + end + return user_pinned, alias_to_gpr end -- Find every physical GPR referenced in the atom body, via EITHER: @@ -151,57 +139,57 @@ end -- original find_hardcoded_rn shape so callers can switch without churn. -- The alias pattern is sorted lexicographically to keep the regex deterministic. local function find_used_gprs(body_text, alias_to_gpr) - local found = {} - -- (a) Hardcoded physical GPRs (R_T0..R_T7, R_V0..R_V1, R_A0..R_A3, R_S0..R_S7). - for gpr in body_text:gmatch("(R_T%d+|R_V%d+|R_A%d+|R_S%d+)") do - found[gpr] = (found[gpr] or 0) + 1 - end - -- (b) Alias references (R_) resolved to physical GPRs via the registry. - -- Sorted by name so the regex is byte-stable across runs. - if alias_to_gpr and next(alias_to_gpr) then - local aliases = {} - for alias_name in pairs(alias_to_gpr) do - aliases[#aliases + 1] = alias_name - end - table.sort(aliases) - local pattern = "(" .. table.concat(aliases, "|") .. ")" - for alias_name in body_text:gmatch(pattern) do - local gpr = alias_to_gpr[alias_name] - if gpr and not found[gpr] then - found[gpr] = 1 - end - end - end - return found + local found = {} + -- (a) Hardcoded physical GPRs (R_T0..R_T7, R_V0..R_V1, R_A0..R_A3, R_S0..R_S7). + for gpr in body_text:gmatch("(R_T%d+|R_V%d+|R_A%d+|R_S%d+)") do + found[gpr] = (found[gpr] or 0) + 1 + end + -- (b) Alias references (R_) resolved to physical GPRs via the registry. + -- Sorted by name so the regex is byte-stable across runs. + if alias_to_gpr and next(alias_to_gpr) then + local aliases = {} + for alias_name in pairs(alias_to_gpr) do + aliases[#aliases + 1] = alias_name + end + table.sort(aliases) + local pattern = "(" .. table.concat(aliases, "|") .. ")" + for alias_name in body_text:gmatch(pattern) do + local gpr = alias_to_gpr[alias_name] + if gpr and not found[gpr] then + found[gpr] = 1 + end + end + end + return found end -- Emit one gen/auto_reg.h header per directory. local function emit_auto_reg_h(out_dir, dir, sources, mappings) - if not mappings or next(mappings) == nil then return end - local out_path = out_dir .. "/" .. "auto_reg.h" - duffle.ensure_dir(out_dir) - local lines = { - "#ifdef INTELLISENSE_DIRECTIVES", - "#pragma once", - "#endif", - "// Auto-generated by ps1_meta.lua (passes/auto_reg.lua) — DO NOT EDIT", - "// Directory: " .. dir:gsub("/", "\\"), - } - for _, src in ipairs(sources) do - lines[#lines + 1] = "// source: " .. src.path - end - lines[#lines + 1] = "// Per-phase register allocations resolved by the lua pass." - lines[#lines + 1] = "// R__Code = for every marker in this directory." - lines[#lines + 1] = "" - for _, sym in ipairs(stable_sort_keys(mappings)) do - local gpr = mappings[sym] - local gpr_code = gpr .. "_Code" - lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code - end - lines[#lines + 1] = "" - duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n") - print(" -> " .. out_path) - return out_path + if not mappings or next(mappings) == nil then return end + local out_path = out_dir .. "/" .. "auto_reg.h" + duffle.ensure_dir(out_dir) + local lines = { + "#ifdef INTELLISENSE_DIRECTIVES", + "#pragma once", + "#endif", + "// Auto-generated by ps1_meta.lua (passes/auto_reg.lua) — DO NOT EDIT", + "// Directory: " .. dir:gsub("/", "\\"), + } + for _, src in ipairs(sources) do + lines[#lines + 1] = "// source: " .. src.path + end + lines[#lines + 1] = "// Per-phase register allocations resolved by the lua pass." + lines[#lines + 1] = "// R__Code = for every marker in this directory." + lines[#lines + 1] = "" + for _, sym in ipairs(stable_sort_keys(mappings)) do + local gpr = mappings[sym] + local gpr_code = gpr .. "_Code" + lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code + end + lines[#lines + 1] = "" + duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n") + print(" -> " .. out_path) + return out_path end -- ════════════════════════════════════════════════════════════════════════════ @@ -213,143 +201,143 @@ local M = {} --- @param ctx PassCtx --- @return AutoRegResult function M.run(ctx) - local outputs = {} - local errors = {} - local warnings = {} + local outputs = {} + local errors = {} + local warnings = {} - local corpus = ctx.shared and ctx.shared.corpus - if type(corpus) ~= "table" then - error("auto_reg.run requires ctx.shared.corpus", 0) - end + local corpus = ctx.shared and ctx.shared.corpus + if type(corpus) ~= "table" then + error("auto_reg.run requires ctx.shared.corpus", 0) + end - -- 0. Build the user-pinned GPR exclusion set + alias-to-GPR resolution map. - -- Wave-context carriers (e.g. `R_ResolveScratch = R_T4 atom_reg` in hello_camera.atom.c) - -- MUST NOT be allocated to any auto-reg marker — they're preserved across atoms by the wave-context discipline. - -- The corpus's register_alias_registry is the source of truth for these opt-in pins. - -- Body references to those aliases (via alias_to_gpr) are also excluded on a per-atom basis in step 2 below. - local user_pinned, alias_to_gpr = build_user_pins(corpus) + -- 0. Build the user-pinned GPR exclusion set + alias-to-GPR resolution map. + -- Wave-context carriers (e.g. `R_ResolveScratch = R_T4 atom_reg` in hello_camera.atom.c) + -- MUST NOT be allocated to any auto-reg marker — they're preserved across atoms by the wave-context discipline. + -- The corpus's register_alias_registry is the source of truth for these opt-in pins. + -- Body references to those aliases (via alias_to_gpr) are also excluded on a per-atom basis in step 2 below. + local user_pinned, alias_to_gpr = build_user_pins(corpus) - -- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations). - local phase_allocations = {} - for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do - local mapping, errs = allocate_phase(phase_label, decls) - for sym, gpr in pairs(mapping) do - phase_allocations[phase_label] = phase_allocations[phase_label] or {} - phase_allocations[phase_label][sym] = gpr - end - for _, e in ipairs(errs) do - errors[#errors + 1] = e - end - end + -- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations). + local phase_allocations = {} + for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do + local mapping, errs = allocate_phase(phase_label, decls) + for sym, gpr in pairs(mapping) do + phase_allocations[phase_label] = phase_allocations[phase_label] or {} + phase_allocations[phase_label][sym] = gpr + end + for _, e in ipairs(errs) do + errors[#errors + 1] = e + end + end - -- 2. Allocate per-atom auto-regs. If the atom scope matches a phase, reuse the phase pool. - -- Otherwise, allocate a private pool for the atom. - -- The phase membership is in `corpus.atom_phases[phase_label].atoms` (an array of atom names declared via `atom_phase()` - -- in the atom's `atom_info` line). Build a reverse map `atom_name -> phase_label` so the lookup is O(1) per atom scope. - local atom_name_to_phase = {} - for phase_label, entry in pairs(corpus.atom_phases or {}) do - for _, atom_name in ipairs(entry.atoms or {}) do - atom_name_to_phase[atom_name] = phase_label - end - end + -- 2. Allocate per-atom auto-regs. If the atom scope matches a phase, reuse the phase pool. + -- Otherwise, allocate a private pool for the atom. + -- The phase membership is in `corpus.atom_phases[phase_label].atoms` (an array of atom names declared via `atom_phase()` + -- in the atom's `atom_info` line). Build a reverse map `atom_name -> phase_label` so the lookup is O(1) per atom scope. + local atom_name_to_phase = {} + for phase_label, entry in pairs(corpus.atom_phases or {}) do + for _, atom_name in ipairs(entry.atoms or {}) do + atom_name_to_phase[atom_name] = phase_label + end + end - local atom_allocations = {} - for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do - local phase_label = atom_name_to_phase[atom_scope] - -- Build the atom's source pool: start with the full POOL, subtract: - -- (a) every GPR already committed (phase allocations + prior atom allocations) - -- (b) every USER-PINNED GPR (wave-context carriers + file-scope pinned aliases) - -- (c) every GPR referenced in the atom's body — either hardcoded R_X or alias R_Xxx - -- (the latter resolved via alias_to_gpr; this catches cases where the user wrote R_ResolveScratch instead of R_T4 directly) - -- Atoms whose scope matches a phase share the global pool with the phase allocations; - -- the original `source_pool = phase_allocations[phase_label]` form used the phase - -- allocation MAP as a pool, but that map has no array part, so `table.remove(source_pool, 1)` - -- returned nil and every atom-with-phase marker errored with `phase_register_pool_exhausted`. - local used = {} - for _, m in pairs(phase_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end - for _, m in pairs(atom_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end - -- (c) Body references — scan the atom body for hardcoded + alias-resolved GPRs. - -- Folded into `used` so the source_pool exclusion is a single check. - local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] - if atom and atom.body then - local body_used = find_used_gprs(atom.body, alias_to_gpr) - for gpr in pairs(body_used) do used[gpr] = true end - end - local source_pool = {} - for _, gpr in ipairs(POOL) do - -- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers - -- declared via atom_reg + _Code defs, preserved across atoms globally). - if not used[gpr] and not user_pinned[gpr] then - source_pool[#source_pool + 1] = gpr - end - end - local result = {} - for _, sym in ipairs(stable_sort_keys(decls)) do - local next_gpr = table.remove(source_pool, 1) - if not next_gpr then - errors[#errors + 1] = { - line = 0, - msg = string.format("phase_register_pool_exhausted: atom '%s' requested symbol '%s' " - .. "but no free registers remain in its scope pool." - , atom_scope, sym), - } - else - result[sym] = next_gpr - end - end - atom_allocations[atom_scope] = result - end + local atom_allocations = {} + for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do + local phase_label = atom_name_to_phase[atom_scope] + -- Build the atom's source pool: start with the full POOL, subtract: + -- (a) every GPR already committed (phase allocations + prior atom allocations) + -- (b) every USER-PINNED GPR (wave-context carriers + file-scope pinned aliases) + -- (c) every GPR referenced in the atom's body — either hardcoded R_X or alias R_Xxx + -- (the latter resolved via alias_to_gpr; this catches cases where the user wrote R_ResolveScratch instead of R_T4 directly) + -- Atoms whose scope matches a phase share the global pool with the phase allocations; + -- the original `source_pool = phase_allocations[phase_label]` form used the phase + -- allocation MAP as a pool, but that map has no array part, so `table.remove(source_pool, 1)` + -- returned nil and every atom-with-phase marker errored with `phase_register_pool_exhausted`. + local used = {} + for _, m in pairs(phase_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end + for _, m in pairs(atom_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end + -- (c) Body references — scan the atom body for hardcoded + alias-resolved GPRs. + -- Folded into `used` so the source_pool exclusion is a single check. + local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] + if atom and atom.body then + local body_used = find_used_gprs(atom.body, alias_to_gpr) + for gpr in pairs(body_used) do used[gpr] = true end + end + local source_pool = {} + for _, gpr in ipairs(POOL) do + -- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers + -- declared via atom_reg + _Code defs, preserved across atoms globally). + if not used[gpr] and not user_pinned[gpr] then + source_pool[#source_pool + 1] = gpr + end + end + local result = {} + for _, sym in ipairs(stable_sort_keys(decls)) do + local next_gpr = table.remove(source_pool, 1) + if not next_gpr then + errors[#errors + 1] = { + line = 0, + msg = string.format("phase_register_pool_exhausted: atom '%s' requested symbol '%s' " + .. "but no free registers remain in its scope pool." + , atom_scope, sym), + } + else + result[sym] = next_gpr + end + end + atom_allocations[atom_scope] = result + end - -- 3. Conflict-with-hardcoded detection (defensive — should be unreachable now). - -- The source_pool exclusion in step 2 (b) + (c) already accounts for both user-pinned GPRs - -- and body-referenced GPRs (hardcoded R_Tn OR alias R_). - -- An auto-reg allocation that matched an existing body reference would be impossible by construction. - -- This warning is kept as a defensive safety net for cases the body scanner might miss - -- (e.g. macros that expand to register references the scanner cannot resolve). - -- For each resolved (scope, sym) -> R_Tn mapping, scan the atom body source for used GPRs. - for atom_scope, decls in pairs(atom_allocations) do - local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] - if atom and atom.body then - local used_in_body = find_used_gprs(atom.body, alias_to_gpr) - for sym, allocated_gpr in pairs(decls) do - if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then - warnings[#warnings + 1] = { - line = atom.line or 0, - msg = string.format("phase_register_clash: atom '%s' has hardcoded '%s' in its body AND an auto-reg marker '%s' " - .. "that was allocated to '%s' (same phase). Resolve by removing the hardcoded reference or renaming the auto-reg." - , atom_scope, allocated_gpr, sym, allocated_gpr), - } - end - end - end - end + -- 3. Conflict-with-hardcoded detection (defensive — should be unreachable now). + -- The source_pool exclusion in step 2 (b) + (c) already accounts for both user-pinned GPRs + -- and body-referenced GPRs (hardcoded R_Tn OR alias R_). + -- An auto-reg allocation that matched an existing body reference would be impossible by construction. + -- This warning is kept as a defensive safety net for cases the body scanner might miss + -- (e.g. macros that expand to register references the scanner cannot resolve). + -- For each resolved (scope, sym) -> R_Tn mapping, scan the atom body source for used GPRs. + for atom_scope, decls in pairs(atom_allocations) do + local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] + if atom and atom.body then + local used_in_body = find_used_gprs(atom.body, alias_to_gpr) + for sym, allocated_gpr in pairs(decls) do + if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then + warnings[#warnings + 1] = { + line = atom.line or 0, + msg = string.format("phase_register_clash: atom '%s' has hardcoded '%s' in its body AND an auto-reg marker '%s' " + .. "that was allocated to '%s' (same phase). Resolve by removing the hardcoded reference or renaming the auto-reg." + , atom_scope, allocated_gpr, sym, allocated_gpr), + } + end + end + end + end - -- 4. Emit per-directory gen/auto_reg.h. - -- For each source directory that has atom_auto_regs or phase_auto_regs entries, emit one header. - local sources_by_dir = corpus.sources_by_dir or {} - for dir, sources in pairs(sources_by_dir) do - local per_dir_mappings = {} - for _, src in ipairs(sources) do - -- Collect every (sym -> gpr) entry that originated from a source in this directory. - -- `src.scan.atom_auto_regs` is keyed by ATOM SCOPE NAME; `pairs(t)` iterates KEYS so `scope_name` here is the scope ident (e.g. "cube_g4_face"). - -- The previous `for _, scan_atom_auto` form silently assigned the VALUE (a `{sym = sym}` table) to the variable, - -- which made `atom_allocations[scan_atom_auto]` a table-indexed lookup that never resolved. - for scope_name in pairs(src.scan and src.scan.atom_auto_regs or {}) do - for sym, gpr in pairs(atom_allocations[scope_name] or {}) do - per_dir_mappings[sym] = gpr - end - end - for scope_name in pairs(src.scan and src.scan.phase_auto_regs or {}) do - for sym, gpr in pairs(phase_allocations[scope_name] or {}) do - per_dir_mappings[sym] = gpr - end - end - end - local out_dir = dir .. "/gen" - local out_path = emit_auto_reg_h(out_dir, dir, sources, per_dir_mappings) - if out_path then outputs[#outputs + 1] = { auto_reg_h = out_path } end - end - return { outputs = outputs, errors = errors, warnings = warnings } + -- 4. Emit per-directory gen/auto_reg.h. + -- For each source directory that has atom_auto_regs or phase_auto_regs entries, emit one header. + local sources_by_dir = corpus.sources_by_dir or {} + for dir, sources in pairs(sources_by_dir) do + local per_dir_mappings = {} + for _, src in ipairs(sources) do + -- Collect every (sym -> gpr) entry that originated from a source in this directory. + -- `src.scan.atom_auto_regs` is keyed by ATOM SCOPE NAME; `pairs(t)` iterates KEYS so `scope_name` here is the scope ident (e.g. "cube_g4_face"). + -- The previous `for _, scan_atom_auto` form silently assigned the VALUE (a `{sym = sym}` table) to the variable, + -- which made `atom_allocations[scan_atom_auto]` a table-indexed lookup that never resolved. + for scope_name in pairs(src.scan and src.scan.atom_auto_regs or {}) do + for sym, gpr in pairs(atom_allocations[scope_name] or {}) do + per_dir_mappings[sym] = gpr + end + end + for scope_name in pairs(src.scan and src.scan.phase_auto_regs or {}) do + for sym, gpr in pairs(phase_allocations[scope_name] or {}) do + per_dir_mappings[sym] = gpr + end + end + end + local out_dir = dir .. "/gen" + local out_path = emit_auto_reg_h(out_dir, dir, sources, per_dir_mappings) + if out_path then outputs[#outputs + 1] = { auto_reg_h = out_path } end + end + return { outputs = outputs, errors = errors, warnings = warnings } end return M diff --git a/scripts/passes/static_analysis.lua b/scripts/passes/static_analysis.lua index 0b49c38..bef05a3 100644 --- a/scripts/passes/static_analysis.lua +++ b/scripts/passes/static_analysis.lua @@ -2465,8 +2465,8 @@ local function check_binds_no_substruct_deref(_src, pipe_ctx, findings) local body_line = a.line + (line_in_body[tokens[ti].rel] or 0) local type_entry = resolve_type_with_fields(type_name, type_registry, 1) - local no_fields = not type_entry or not type_entry.fields or #type_entry.fields == 0 - local raw_entry = type_registry[type_name] + local no_fields = not type_entry or not type_entry.fields or #type_entry.fields == 0 + local raw_entry = type_registry[type_name] local is_typedef_to_struct = raw_entry and raw_entry.kind == "typedef" and raw_entry.underlying_type @@ -2506,13 +2506,11 @@ local function check_binds_no_substruct_deref(_src, pipe_ctx, findings) 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). +--- 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 {} @@ -2531,11 +2529,9 @@ local function is_ctrl_r_transfer(c) 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. +-- 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 @@ -2584,14 +2580,11 @@ local function ctrl_writes_in_atom(atom) 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. +-- 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. +-- 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 @@ -2650,14 +2643,11 @@ 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). +-- 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). +-- 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. +-- 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 {} diff --git a/scripts/update_deps.ps1 b/scripts/update_deps.ps1 index 5149c7c..a81f0d6 100644 --- a/scripts/update_deps.ps1 +++ b/scripts/update_deps.ps1 @@ -91,11 +91,9 @@ if (-not (Test-Path -LiteralPath $path_pcsx_packages)) { New-Item -ItemType Directory -Path $path_pcsx_packages -Force | Out-Null } -# Download anything missing. Skip the package entirely if its dir already has -# any contents (the legacy packages.config style means the targets file -# location varies per package — `luajit.native` puts it at build/native/, -# `glfw` puts it elsewhere — so we can't probe a specific path; just check -# whether the dir is non-empty). +# Download anything missing. +# Skip the package entirely if its dir already has any contents (the legacy packages.config style means the targets file location varies per package +# — `luajit.native` puts it at build/native/, `glfw` puts it elsewhere — so we can't probe a specific path; just check whether the dir is non-empty). Add-Type -AssemblyName System.IO.Compression.FileSystem foreach ($pkg in $required_packages.Values) { $pkgDir = Join-Path $path_pcsx_packages ('{0}.{1}' -f $pkg.id, $pkg.version) @@ -122,24 +120,18 @@ foreach ($pkg in $required_packages.Values) { } } -# ════════════════════════════════════════════════════════════════════════════ -# isoffi.lua size guard — `core.vcxproj` #includes src/core/isoffi.lua into -# luaiso.cc via the `-- lualoader, R"EOF(...)EOF"` trick. The raw string -# literal between R"EOF(-- and -- )EOF" must stay under ~16,379 bytes or -# MSVC (19.44) fails with C2026 (its actual raw-string limit is 16,384, -# minus 5 bytes for the `-- lualoader, ` prefix). If the upstream file -# grows past that, trim it: remove license header, trailing whitespace, -# blank separators, inline comments, and shrink 4-space indent to 2-space. +# ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ +# isoffi.lua size guard — `core.vcxproj` #includes src/core/isoffi.lua into luaiso.cc via the `-- lualoader, R"EOF(...)EOF"` trick. +# The raw string literal between R"EOF(-- and -- )EOF" must stay under ~16,379 bytes or MSVC (19.44) fails with C2026 (its actual raw-string limit is 16,384, minus 5 bytes for the `-- lualoader, ` prefix). +# If the upstream file grows past that, trim it: remove license header, trailing whitespace, blank separators, inline comments, and shrink 4-space indent to 2-space. # Idempotent — only writes when the raw string exceeds the limit. -# ════════════════════════════════════════════════════════════════════════════ +# ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ $path_isoffi = join-path $path_pcsx_redux 'src\core\isoffi.lua' if (Test-Path -LiteralPath $path_isoffi) { $content = Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8 $startMarker = $content.IndexOf('R"EOF(--') $endMarker = $content.IndexOf('-- )EOF"') - $literalLen = if ($startMarker -ge 0 -and $endMarker -gt $startMarker) { - $endMarker - ($startMarker + 8) - } else { -1 } + $literalLen = if ($startMarker -ge 0 -and $endMarker -gt $startMarker) { $endMarker - ($startMarker + 8) } else { -1 } # Effective MSVC raw-string limit for the lualoader prefix is 16379 bytes. if ($literalLen -gt 16379) { Write-Host "isoffi.lua raw string is $literalLen bytes (>16379); trimming for MSVC C2026 limit." @@ -168,8 +160,7 @@ if (Test-Path -LiteralPath $path_isoffi) { $newLines += $line } ($newLines -join "`n") | Out-File -LiteralPath $path_isoffi -Encoding utf8 -NoNewline - $newLen = ((Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8) ` - -replace '.*R"EOF\(--', '' -replace '-- \)EOF".*', '').Length + $newLen = ((Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8) -replace '.*R"EOF\(--', '' -replace '-- \)EOF".*', '').Length Write-Host "isoffi.lua trimmed: $literalLen -> $newLen bytes of raw string content." } } @@ -231,13 +222,11 @@ $lfs_dll_import = join-path $luajit_lib_dir 'libluajit-5.1.dll.a' $path_openbios = join-path $path_pcsx_redux 'src\mips\openbios' -# Wipe stale *.dep files across src\mips. These cache absolute paths to the -# GCC headers directory; if the toolchain was upgraded (e.g. v14.2.0 → v16.1.0) +# Wipe stale *.dep files across src\mips. +# These cache absolute paths to the GCC headers directory; if the toolchain was upgraded (e.g. v14.2.0 → v16.1.0) # Make reads the stale paths and aborts with "no rule to make target .../stddef.h". -# `make clean` in openbios only clears its own dir — subdirs like -# common/crt0/, modplayer/, and shell/ keep their stale .dep files. Easier to -# just delete the lot before each build than to teach every Makefile about -# deepclean recursion. +# `make clean` in openbios only clears its own dir — subdirs like common/crt0/, modplayer/, and shell/ keep their stale .dep files. +# Easier to just delete the lot before each build than to teach every Makefile about deepclean recursion. Get-ChildItem -Path (join-path $path_pcsx_redux 'src\mips') -Recurse -Filter '*.dep' -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force }