Reviewing. Successfuly reworked register allocation for tape runs.

This commit is contained in:
ed
2026-08-18 00:26:36 -04:00
parent 5a4bfb1224
commit da007d342e
19 changed files with 841 additions and 1787 deletions
+1
View File
@@ -1,6 +1,7 @@
#ifdef INTELLISENSE_DIRECTIVES #ifdef INTELLISENSE_DIRECTIVES
# pragma once # pragma once
#endif #endif
enum { enum {
bios_init_pad_2 = 0x12, bios_init_pad_2 = 0x12,
bios_start_pad_2 = 0x13, bios_start_pad_2 = 0x13,
+12 -12
View File
@@ -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_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \ , gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \ , 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_cmdw_gpf \
, gte_mv_from_data_r(r_dx, C2_MAC1) \ , gte_mv_from_data_r(r_dx, C2_MAC1) \
, gte_mv_from_data_r(r_dy, C2_MAC2) \ , 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_dx, r_dx, r_shift) \
, shift_aright_var(r_dy, r_dy, r_shift) \ , shift_aright_var(r_dy, r_dy, r_shift) \
, shift_aright_var(r_dz, r_dz, 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) \ #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_t0, r_off, O_(V3_S4,x)) \
, load_word(r_t1, r_off, O_(V3_S4,y)) \ , load_word( r_t1, r_off, O_(V3_S4,y)) \
, load_word(r_t2, r_off, O_(V3_S4,z)) \ , load_word( r_t2, r_off, O_(V3_S4,z)) \
, store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])) \ , 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_t1, r_mtx, O_(MT3_S2S4,t[1])) \
, store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])) , 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) \ #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) \ and_i(r_shift, r_shift, gte_lzcr_even_mask) \
, or_u(r_mag_sq_copy, r_mag_sq, 0) \ , or_u(r_mag_sq_copy, r_mag_sq, 0) \
, li_s(r_mag_sq, 31) \ , li_s( r_mag_sq, 31) \
, sub_s(r_mag_sq, r_mag_sq, r_shift) \ , sub_s( r_mag_sq, r_mag_sq, r_shift) \
, shift_aright(r_mag_sq, r_mag_sq, 1) , shift_aright(r_mag_sq, r_mag_sq, 1)
WORD_COUNT(mac_lzcr_round_even_half_shift, 5) 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 */ /* atom_dbg_skip */
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \ #define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
mac_load_word_imm(reg_transfer, cmd) \ 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) WORD_COUNT(mac_gcmd_push, 3)
/* atom_dbg_skip */ /* atom_dbg_skip */
@@ -383,8 +383,8 @@ WORD_COUNT(mac_insert_ot_tag, 11)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_pad_set_centered_axes(state, scratch) \ #define mac_pad_set_centered_axes(state, scratch) \
load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF) \ load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF) \
, or_i_self( scratch, PadAxis_Centered & 0xFFFF) /* mac_load_word_imm(scratch, PadAxis_Centered), */ \ , or_i_self( scratch, PadAxis_Centered & 0xFFFF) /* mac_load_word_imm(scratch, PadAxis_Centered), */ \
, store_word( scratch, state, O_(PadState,axes)) , store_word( scratch, state, O_(PadState,axes))
WORD_COUNT(mac_pad_set_centered_axes, 3) WORD_COUNT(mac_pad_set_centered_axes, 3)
/* atom_dbg_skip */ /* atom_dbg_skip */
@@ -401,7 +401,7 @@ WORD_COUNT(mac_pad_set_status, 2)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_pad_store_inverted_buttons(r_buttons, r_pad_state) \ #define mac_pad_store_inverted_buttons(r_buttons, r_pad_state) \
nor_u( r_buttons, r_buttons, R_0) \ nor_u( r_buttons, r_buttons, R_0) \
, store_half( r_buttons, r_pad_state, O_(PadState,buttons)) , store_half(r_buttons, r_pad_state, O_(PadState,buttons))
WORD_COUNT(mac_pad_store_inverted_buttons, 2) WORD_COUNT(mac_pad_store_inverted_buttons, 2)
+1 -1
View File
@@ -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) 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, { atom_dbg_skip MipsAtomComp_Proc_(ab, {
mac_load_word_imm(reg_transfer, cmd), 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) FI_ Slice_MipsCode ac_store_rgb8(AtomBuilder_R ab, U1 rr, U1 rg, U1 rb, U4 base, U4 offset)
+29 -23
View File
@@ -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_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2), gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3), 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_cmdw_gpf,
gte_mv_from_data_r(r_dx, C2_MAC1), gte_mv_from_data_r(r_dx, C2_MAC1),
gte_mv_from_data_r(r_dy, C2_MAC2), 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_mtx, U4 r_off
, U4 r_t0, U4 r_t1, U4 r_t2 , U4 r_t0, U4 r_t1, U4 r_t2
) MipsAtomComp_Proc_(ab, { ) MipsAtomComp_Proc_(ab, {
load_word(r_t0, r_off, O_(V3_S4,x)), load_word( r_t0, r_off, O_(V3_S4,x)),
load_word(r_t1, r_off, O_(V3_S4,y)), load_word( r_t1, r_off, O_(V3_S4,y)),
load_word(r_t2, r_off, O_(V3_S4,z)), load_word( r_t2, r_off, O_(V3_S4,z)),
store_word(r_t0, r_mtx, O_(MT3_S2S4,t[0])), 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_t1, r_mtx, O_(MT3_S2S4,t[1])),
store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])), store_word(r_t2, r_mtx, O_(MT3_S2S4,t[2])),
}) })
/* ─── LZCR ROUND EVEN + HALF-SHIFT ─── /* ─── LZCR ROUND EVEN + HALF-SHIFT ───
* Takes the raw LZCR leading-zero/ones count (from mfc2 C2_LZCR, range 1..32 * 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).
* per PSX-SPX cop2r31) and the |v|² sum (in r_mag_sq from the MAC1+MAC2+MAC3 * Produces:
* add). Produces:
* r_shift ← LZCR rounded down to even (clear bit 0) * 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_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 * 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 * Rounding to even ensures (31 - LZCR) is always odd, so the >> 1 division is consistent — no 0.5 loss.
* is consistent — no 0.5 loss. The caller branches on LZCR < 24 to decide * The caller branches on LZCR < 24 to decide left-shift vs right-shift of r_mag_sq_copy, then saves the shift count.
* 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 * Note: C2_LZCR (cop2r31) is a fixed read-only C2 data register — the caller must read it via mfc2 from C2_LZCR;
* must read it via mfc2 from C2_LZCR; there is no register choice at the * there is no register choice at the hardware level. Only the GPR that holds the result is caller-determined. */
* hardware level. Only the GPR that holds the result is caller-determined. */
FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab, FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab,
U4 r_shift, U4 r_shift,
U4 r_mag_sq, 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, { atom_dbg_skip MipsAtomComp_Proc_(ab, {
and_i(r_shift, r_shift, gte_lzcr_even_mask), and_i(r_shift, r_shift, gte_lzcr_even_mask),
or_u(r_mag_sq_copy, r_mag_sq, 0), or_u(r_mag_sq_copy, r_mag_sq, 0),
li_s(r_mag_sq, 31), li_s( r_mag_sq, 31),
sub_s(r_mag_sq, r_mag_sq, r_shift), sub_s( r_mag_sq, r_mag_sq, r_shift),
shift_aright(r_mag_sq, r_mag_sq, 1), 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). ─── /* ─── Local copy of PSYQ's sqrtbl (1/sqrt lookup table for VectorNormal). ───
* Source: PSYQ 4.7 libgte sqrtbl at 0x800185B4 in hello_camera.elf. * Source: PSYQ 4.7 libgte sqrtbl at 0x800185B4 in hello_camera.elf.
* objdump -s --start-address=0x800185B4 --stop-address=0x800185F4 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).
* → 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). * 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)), load_word(R_T3, R_TapePtr, O_(Binds_SetGteMT3S2S4,transform)),
add_ui_self( R_TapePtr, S_(Binds_SetGteMT3S2S4)), add_ui_self( R_TapePtr, S_(Binds_SetGteMT3S2S4)),
/* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */ /* 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), load_word(R_T0, R_T3, 0),
gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12), load_word(R_T1, R_T3, 4),
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_RT11),
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), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12),
load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28), load_word(R_T0, R_T3, 8),
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_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() mac_yield()
}; };
+7 -14
View File
@@ -16,9 +16,6 @@
* gte_mv_to_data_r (gte + mv + to + data + register) * gte_mv_to_data_r (gte + mv + to + data + register)
* gte_lw_v0_xy(base) (gte + lw + v0 + xy) * gte_lw_v0_xy(base) (gte + lw + v0 + xy)
* load_upper_i (load-upper + immediate, unique verb) * 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 #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). */ /* 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)) #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 /* RTPS with sf=1 (12-bit shift, no translation): matches the output of libgte's ApplyMatrixLV when the GTE pipeline expects R*pos >> 12.
* ApplyMatrixLV when the GTE pipeline expects R*pos >> 12. The shift produces * The shift produces values like (-270, 710, 1713) which match the C11 reference path. */
* 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)) #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. /* 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) */ * 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) #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) /* Mask to round LZCR (leading-zero/ones count, range 1..32 per PSX-SPX cop2r31) down to even.
* down to even. The normalize_v3s4 half-shift logic computes (31 - LZCR) >> 1; * 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). */
* clearing bit 0 ensures the subtraction result is always odd,
* so the >> 1 division is consistent (no 0.5 loss). */
enum { enum {
gte_lzcr_even_mask = 0xFFFE, /* all bits except bit 0 */ 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. /* 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, * Loads all three GTE input vectors (6 words) from three separate pointers, one per GTE vector register, each loaded from its own base GPR.
* each loaded from its own base GPR. Caller must bind each `pN` to `bN` via a register variable. * 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* p0 rgcc(R_T4) = verts[0].ptr; // → __asm__("$12")
* register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13") * register V3_S2* p1 rgcc(R_T5) = verts[1].ptr; // → __asm__("$13")
* register V3_S2* p2 rgcc(R_T6) = verts[2].ptr; // → __asm__("$14") * 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. * 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. * 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 * 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] | ...).
* [RT_xx, RT_xy] | [RT_xz, pad] | ...).
* *
* Generated MIPS (mirrors the source macro): * Generated MIPS (mirrors the source macro):
* lw $12, 0( %0 ) ; word 0 * lw $12, 0( %0 ) ; word 0
+126 -88
View File
@@ -66,40 +66,53 @@
* */ * */
/* Register Allocation Info */ /* Register Allocation Info */
enum { enum {
R_AtomJmp = R_T8 atom_reg, /* debug-visible; tape yield handshake scratch */ R_ScratchBase = R_SP atom_reg, /* Scratchpad base address (host frame top) */
R_TapePtr = R_T9 atom_reg, /* The Instruction Stream Pointer */ 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. */ /* Stringification codes for the GCC inline assembler clobber lists. */
#define R_AtomJmp_Code R_T8_Code #define R_ScratchBase_Code R_SP_Code
#define R_TapePtr_Code R_T9_Code #define R_AtomJmp_Code R_FP_Code
#define R_TapePtr_Code R_RA_Code
// R_InCursor = R_T4, // R_InCursor = R_T4,
// #define R_InCursor_Code R_T4_Code // #define R_InCursor_Code R_T4_Code
// Reserved Registers (Callee-saved): // Reserved Registers (Callee-saved across the host ABI transition):
// - R_T9: Holds the Tape Ptr which we need to increment // - R_SP: Holds the scratchpad base while tape code executes.
// - R_RA: Return address register // - R_FP: Holds the next atom target.
// Needed by ac_yield but can be used as atom scratch: // - R_RA: Holds the tape cursor.
// - R_T8: Will be used as the atom jump register. // 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_PsuedoVolatile = R_AT, // Assembler temporary; never allocate.
R_TScratchVolatile = R_AT, // This one is reserved for psuedo instructions, but you can technically use it.
R_TScratch0 = R_T0, // Atom Allocation Pool
R_TScratch1 = R_T1, R_Atom0 = R_T0,
R_TScratch2 = R_T2, R_Atom1 = R_T1,
R_TScratch3 = R_T3, R_Atom2 = R_T2,
R_TScratch4 = R_T4, R_Atom3 = R_T3,
R_TScratch5 = R_T5, R_Atom4 = R_T4,
R_TScratch6 = R_T6, R_Atom5 = R_T5,
R_TScratch7 = R_T7, R_Atom6 = R_T6,
R_TScratch8 = R_T8, // Clobbered by the yield on a per-atom boundary. R_Atom7 = R_T7,
R_TScratch10 = R_V0, // Tend to be used with gte DMAs R_Atom8 = R_T8,
R_TScratch11 = R_V1, // Tend to be used with gte DMAs R_Atom9 = R_T9,
// Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck. R_Atom10 = R_V0, // Tend to be used with gte DMAs
// A 0-2 R_Atom11 = R_V1, // Tend to be used with gte DMAs
// S 0-7 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 typedef U2 Reg; // Register parameter used with atom or atom component procedures
@@ -110,6 +123,7 @@ typedef Slice_(MipsCode);
typedef U4 const MipsAtom; // Underlying type to a mips atom defnition 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: // Sometimes a user will define a bundle of atoms that represent a procedure of work as:
// MipsAtom* <identifier>[...]; // MipsAtom* <identifier>[...];
// Unfortuantely if using slice_from_array it will make the slice's pointer: MipsAtom** so this enforce its defined 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. Files containing only atoms and atom components.
Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms. 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. 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. 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). */
#define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0 #define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0
typedef Slice_MipsAtom Tape; typedef Slice_MipsAtom Tape;
/* The 'Exit' Atom */ typedef Struct_(TapeHostFrame) {
atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(R_RA), nop }; 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) */ atom_dbg_skip MipsAtom_(tape_enter) {
FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile( mac_load_word_imm(R_V0, u4_(TapeHostFrame_Loc)),
asm_words( store_word(R_S0, R_V0, O_(TapeHostFrame,s0)),
load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ store_word(R_S1, R_V0, O_(TapeHostFrame,s1)),
, add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ store_word(R_S2, R_V0, O_(TapeHostFrame,s2)),
, call_reg( R_AtomJmp) /* jalr $t8 */ store_word(R_S3, R_V0, O_(TapeHostFrame,s3)),
, BdSlot_ nop /* Branch delay slot */ store_word(R_S4, R_V0, O_(TapeHostFrame,s4)),
) store_word(R_S5, R_V0, O_(TapeHostFrame,s5)),
asm_rpins, r_use(tape_ptr) store_word(R_S6, R_V0, O_(TapeHostFrame,s6)),
asm_clobber: store_word(R_S7, R_V0, O_(TapeHostFrame,s7)),
rlit(R_AT), store_word(R_FP, R_V0, O_(TapeHostFrame,fp)),
rlit(R_V0), rlit(R_V1), // We clobber these for GTE ACs (that don't expose register selection, might expose them in the future...) store_word(R_SP, R_V0, O_(TapeHostFrame,sp)),
rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4), store_word(R_RA, R_V0, O_(TapeHostFrame,ra)),
rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), add_ui(R_TapePtr, R_A0, 0),
clb_mem_drain 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) */ atom_dbg_skip MipsAtom_(tape_exit) {
FI_ void tape_run_a02_s07(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile( mac_load_word_imm(R_V0, u4_(TapeHostFrame_Loc)),
asm_words( load_word(R_S0, R_V0, O_(TapeHostFrame,s0)),
load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ load_word(R_S1, R_V0, O_(TapeHostFrame,s1)),
, add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */ load_word(R_S2, R_V0, O_(TapeHostFrame,s2)),
, call_reg( R_AtomJmp) /* jalr $t8 */ load_word(R_S3, R_V0, O_(TapeHostFrame,s3)),
, BdSlot_ nop /* Branch delay slot */ load_word(R_S4, R_V0, O_(TapeHostFrame,s4)),
) load_word(R_S5, R_V0, O_(TapeHostFrame,s5)),
asm_rpins, r_use(tape_ptr) load_word(R_S6, R_V0, O_(TapeHostFrame,s6)),
asm_clobber: load_word(R_S7, R_V0, O_(TapeHostFrame,s7)),
rlit(R_AT), load_word(R_RA, R_V0, O_(TapeHostFrame,ra)),
rlit(R_V0), rlit(R_V1), rlit(R_A0), rlit(R_A1), rlit(R_A2), load_word(R_FP, R_V0, O_(TapeHostFrame,fp)),
rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4), load_word(R_SP, R_V0, O_(TapeHostFrame,sp)),
rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8), jump_reg(R_RA), BdSlot_ nop,
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 typedef void Proc_(TapeEntryFn)(MipsAtom* tape_ptr);
); }
FI_ void tape_run(Tape tape) { C_(TapeEntryFn*, tape_enter)(tape.ptr); }
// Procedural authoring of tapes: // Procedural authoring of tapes:
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; 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) { atom_dbg_skip MipsAtomComp_(ac_yield_tail) {
add_ui_self(R_TapePtr, S_(MipsCode)), add_ui_self(R_TapePtr, S_(MipsCode)),
jump_reg( R_AtomJmp), jump_reg( R_AtomJmp), BdSlot_ nop,
BdSlot_ nop,
}; };
#pragma endregion Macro Atom Components #pragma endregion Macro Atom Components
#pragma region Atom Builder #pragma region Atom Builder
// This helps with runtime procedural authoring of mips atoms. // 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; }; typedef Relative_(FArena) Struct_(AtomBuilder) { U4 start; U4 capacity; U4 used; };
// Usual way to resolve an atom after the bulder is done. // 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 #pragma region Atom Arena
// Just a dedicated FArena that is meant to mem_copy and return atom definitions made with MipsAtom_Proc_ // 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; }; typedef Relative_(FArena) Struct_(AtomArena) { U4 start; U4 capacity; U4 used; };
#define atomarena_unused_start(ab) ((ab).start + (ab).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. // 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 <Scope_Prefix> or something per register allocation boundary. // Unless we follow a convention for #define <Scope_Prefix> 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 = U4 const regfile_abi_mask =
(1u << R_0) | (1u << R_AT) | (1u << R_0) | (1u << R_AT) |
(1u << R_K0) | (1u << R_K1) | (1u << R_K0) | (1u << R_K1) |
(1u << R_GP) | (1u << R_SP) | (1u << R_GP) | (1u << R_SP) |
(1u << R_FP) | (1u << R_RA) | (1u << R_FP) | (1u << R_RA);
(1u << R_T8) | (1u << R_T9); /* AtomJmp + TapePtr */
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) { typedef Struct_(RegFile) {
A2_U2 GPR; 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); Reg result = 0; RegFile_RInfo info = regfile_rinfo(file, r_id);
if (info.occupied == false) { if (info.occupied == false) {
info.section[0] |= info.mask; info.section[0] |= info.mask;
result = r_id; result = r_id;
} }
return result; 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) { I_ Reg regfile_alloc(RegFile_R rf) {
U2 allocated = 0; Reg allocated = 0;
for index_iter(Reg, r_id, R_T0, <=, R_T7) { for index_iter(U4, idx, 0, <, Array_len(regfile_alloc_order)) {
allocated = regfile__alloc_helper(rf->GPR, r_id); Jmp_nZero_(allocated,resolved); 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); assert(allocated != 0);
resolved: return allocated; resolved: return allocated;
} }
@@ -383,9 +423,7 @@ FI_ void regfile_reset_to_mask(RegFile_R rf, U4 mask) {
#pragma region Mips Atom Procs #pragma region Mips Atom Procs
/* RegUse structs are a convention to organize register allocations for a mips atom procedure. /* 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 Unlike the usual enum-based declarations, they provide a namespaced scope and have view types via union declarations. */
and have view types via union declarations.
*/
#define RegUse_(proc_name) (tmpl(RegUse,proc_name)) #define RegUse_(proc_name) (tmpl(RegUse,proc_name))
typedef Struct_(RegUse_example_atom_proc) { typedef Struct_(RegUse_example_atom_proc) {
+4 -2
View File
@@ -134,10 +134,12 @@ FI_ U4 farena_unused_start(FArena arena) { return arena.start + arena.used; }
#pragma region BIOS Scratchpad #pragma region BIOS Scratchpad
/* BIOS scratchpad location. 1 KB at 0x1F800000. /* 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. * TapeHostFrame occupies the final 44 bytes while tape code executes.
* The Tape runtime uses scratch region along with explicit data structures instead of the stack-based scratch from the C-Runtime. */ * Atom scratch is bounded by the TapeHostFrame_Loc declaration in lottes_tape.h. */
enum { enum {
Scratchpad_Loc = 0x1F800000, Scratchpad_Loc = 0x1F800000,
Scratchpad_Len = 0x400, /* 1 KB */
Scratchpad_End = Scratchpad_Loc + Scratchpad_Len, /* 0x1F800400 */
}; };
#define C_scratch(type) C_(type, Scratchpad_Loc) #define C_scratch(type) C_(type, Scratchpad_Loc)
#pragma endregion BIOS Scratchpad #pragma endregion BIOS Scratchpad
+9 -10
View File
@@ -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, { 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), load_upper_i(scratch, (PadAxis_Centered >> 16) & 0xFFFF),
or_i_self( scratch, PadAxis_Centered & 0xFFFF), or_i_self( scratch, PadAxis_Centered & 0xFFFF), // mac_load_word_imm(scratch, PadAxis_Centered),
// mac_load_word_imm(scratch, PadAxis_Centered), store_word( scratch, state, O_(PadState,axes)),
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, { 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 * 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). */ * 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, { 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), nor_u( r_buttons, r_buttons, R_0),
store_half( r_buttons, r_pad_state, O_(PadState,buttons)), store_half(r_buttons, r_pad_state, O_(PadState,buttons)),
}) })
#pragma endregion MACs (Mips Atom Components) #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. */ * 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. */ atom_label(disconnected) /* === Disconnected body. */
mac_pad_set_status(R_T4, R_PadState, PadStatus_Disconnected), mac_pad_set_status(R_T4, R_PadState, PadStatus_Disconnected),
store_half( R_0, R_PadState, O_(PadState,buttons)), store_half( R_0, R_PadState, O_(PadState,buttons)),
mac_pad_set_centered_axes(R_PadState, R_T4), mac_pad_set_centered_axes(R_PadState, R_T4),
mac_pad_set_id_byte(R_PadState, R_RawId, PadRawStatus_Timeout), mac_pad_set_id_byte(R_PadState, R_RawId, PadRawStatus_Timeout),
jump_rel(atom_offset(disconnected, snap_end)), 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. */ * 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). */ atom_label(pending) /* === Pending body (status=0, id=0 — pre-IRQ-empty buffer). */
mac_pad_set_status(R_T4, R_PadState, PadStatus_Pending), mac_pad_set_status(R_T4, R_PadState, PadStatus_Pending),
store_half( R_0, R_PadState, O_(PadState,buttons)), store_half( R_0, R_PadState, O_(PadState,buttons)),
mac_pad_set_centered_axes(R_PadState, R_T4), mac_pad_set_centered_axes(R_PadState, R_T4),
store_byte(R_RawId, R_PadState, O_(PadState,id)), store_byte(R_RawId, R_PadState, O_(PadState,id)),
jump_rel(atom_offset(pending, snap_end)), 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. */ * R_T5 is then "dead" — only consumed at the analog_pad range check downstream. */
mac_pad_set_status(R_T4, R_PadState, PadStatus_Digital), mac_pad_set_status(R_T4, R_PadState, PadStatus_Digital),
load_half_u( R_T4, R_PadRaw, O_(PadBiosRaw, buttons)), /* R_T4 = raw_buttons; */ 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), // 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 */ 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) */ 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) */
+8 -8
View File
@@ -101,14 +101,14 @@ MipsAtomComp_Proc_(ab, {
#pragma region resolve_look_at #pragma region resolve_look_at
/* ─── resolve_look_at bundle chain atoms ──────────────────────────── */ /* ─── resolve_look_at bundle chain atoms ──────────────────────────── */
typedef AtomBundle_(resolve_look_at) { MipsAtom* typedef AtomBundle_(resolve_look_at) { MipsAtom
input_and_sub, *input_and_sub,
normalize_fwd_uz, *normalize_fwd_uz,
cross_to_right, *cross_to_right,
normalize_right_ux, *normalize_right_ux,
cross_to_up, *cross_to_up,
normalize_up_uy, *normalize_up_uy,
pop_mv_trans; *pop_mv_trans;
}; };
enum { enum {
+64 -116
View File
@@ -53,7 +53,6 @@
#pragma endregion Hello Joypad TUs #pragma endregion Hello Joypad TUs
enum { enum {
Scratchpad_Len = 1024,
MemTape_Len = 512, MemTape_Len = 512,
ResolveLookAtArena_Words = 1024, ResolveLookAtArena_Words = 1024,
@@ -86,11 +85,7 @@ typedef Struct_(SMemory) {
U1 ct_init_atom_mem[CT_InitAtomMem_Size]; U1 ct_init_atom_mem[CT_InitAtomMem_Size];
MipsAtom* normalize_v3s4; MipsAtom* normalize_v3s4;
MipsAtom* gte_cross_v3s4; /* GTE OP OuterProduct12 (a × b → out). MipsAtom* gte_cross_v3s4;
* 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. */
U1 resolve_look_at_mem[ResolveLookAtArena_Size]; U1 resolve_look_at_mem[ResolveLookAtArena_Size];
MipsAtom* resolve_look_at_bundle[AtomBundle_Len(resolve_look_at)]; 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); } 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) { 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)); AtomArena ab = atomarena_make(slice_ut_arr(smem.ct_init_atom_mem));
RegFile rf = regfile(regfile_abi_mask); 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) === smem.gte_cross_v3s4 = gte_cross_v3s4(& ab,
* Reused by both cross call sites in resolve_look_at. No scratch carrier. RegUse_(gte_cross_v3s4) {
* GPR pool: 9 allocatable (no carrier). Pool R_T0..R_T7, R_V0, R_V1 minus ABI = 9. .a = ralloc_v3(),
* Fits exactly. */ .b = ralloc_v3(),
{ .x = ralloc(),
smem.gte_cross_v3s4 = gte_cross_v3s4(& ab, .y = ralloc(),
RegUse_(gte_cross_v3s4) { .z = ralloc(),
.a = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, });
.b = { regfile_alloc(& rf), regfile_alloc(& rf), regfile_alloc(& rf) }, regfile_reset(& 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);
}
/* === build_normalize_v3s4 — Generic 4-stage GTE normalize === smem.normalize_v3s4 = build_normalize_v3s4(& ab,
* Reused by all 3 normalize call sites in resolve_look_at. Reads scratch + src/dst RegUse_(build_normalize_v3s4) {
* offsets from tape (no carrier — atom is fully self-contained per call). .scratch = ralloc(),
* GPR pool: no carrier, 10 allocatable (R_T0..R_T7, R_V0, R_V1 minus ABI pins). .src_ptr = ralloc(),
* 10 fields (scratch + src_ptr + dst_ptr + recip_est + norm + shift + src_x + .dst_ptr = ralloc(),
* t3 + t4 + t5) → just fits. Union members t5.src_offset, t3.dst_offset, etc. .recip_est = ralloc(),
* share GPRs via lifetime discipline. */ .norm = ralloc(),
{ .shift = ralloc(),
RegFile rf = regfile(regfile_abi_mask); .src_x = ralloc(),
smem.normalize_v3s4 = build_normalize_v3s4(& ab, .t3 = ralloc(),
RegUse_(build_normalize_v3s4) { .t4 = ralloc(),
.scratch = regfile_alloc(& rf), .t5 = ralloc(),
.src_ptr = regfile_alloc(& rf), });
.dst_ptr = regfile_alloc(& rf), regfile_reset(& 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),
});
}
assert(ab.used <= CT_InitAtomMem_Size); assert(ab.used <= CT_InitAtomMem_Size);
#undef ralloc
#undef ralloc_v3
} }
internal void compile_resolve_look_at(void) { 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));
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);
TapeBuilder tb = tb_make(slice_ut_arr(smem.resolve_look_at_bundle));
U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch); U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch);
RegFile rf = regfile(pin_mask); RegFile rf = regfile(pin_mask);
#define ralloc() regfile_alloc(& rf) #define ralloc() regfile_alloc(& rf)
#define ralloc_v3() { ralloc(), ralloc(), ralloc() } #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) { RegUse_(resolve_look_at_input_and_sub) {
.scratch = R_ResolveScratch, .scratch = R_ResolveScratch,
.target = ralloc(), .target = ralloc(),
@@ -212,37 +189,25 @@ internal void compile_resolve_look_at(void) {
.t2 = ralloc(), .t2 = ralloc(),
.t3 = ralloc(), .t3 = ralloc(),
.t4 = ralloc(), .t4 = ralloc(),
} });
));
regfile_reset_to_mask(& rf, pin_mask); regfile_reset_to_mask(& rf, pin_mask);
/* === ATOM 1: normalize fwd→uz === */ bundle->normalize_fwd_uz = smem.normalize_v3s4;
smem.resolve_look_at_bundle[1] = 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) === */ bundle->pop_mv_trans = resolve_look_at__pop_mv_trans(& ab,
smem.resolve_look_at_bundle[2] = smem.gte_cross_v3s4; RegUse_(resolve_look_at__pop_mv_trans){
.scratch = R_ResolveScratch,
/* === ATOM 3: normalize right→ux === */ .look_at = ralloc(),
smem.resolve_look_at_bundle[3] = smem.normalize_v3s4; .eye = ralloc(),
.row = ralloc_v3(),
/* === ATOM 4: cross uz×ux→up (reuses smem.gte_cross_v3s4) === */ .t6 = ralloc(),
smem.resolve_look_at_bundle[4] = smem.gte_cross_v3s4; .t7 = ralloc(),
.t8 = ralloc(),
/* === 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. */
/* Sanity check: arena didn't overflow. */ /* Sanity check: arena didn't overflow. */
assert(ab.used <= ResolveLookAtArena_Size); 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. */ /* Typed view of the scratchpad for field-address arithmetic. */
ResolveLookAtScratch* sp = C_scratch(ResolveLookAtScratch*); 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_(target));
tb_data(tb, u4_(eye)); tb_data(tb, u4_(eye));
tb_data(tb, u4_(up_in)); tb_data(tb, u4_(up_in));
tb_data(tb, u4_(smem.scratchpad)); tb_data(tb, u4_(smem.scratchpad));
} }
tb_emit(tb, bundle->normalize_fwd_uz); {
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_data(tb, u4_(O_(ResolveLookAtScratch, fwd) | (O_(ResolveLookAtScratch, uz) << 16))); tb_data(tb, u4_(O_(ResolveLookAtScratch, fwd) | (O_(ResolveLookAtScratch, uz) << 16)));
} }
tb_emit(tb, smem.resolve_look_at_bundle[2]); { tb_emit(tb, bundle->cross_to_right); {
/* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ tb_data(tb, u4_(& sp->uz));
tb_data(tb, u4_(& sp->uz)); /* src_a */ tb_data(tb, u4_(& sp->up_in));
tb_data(tb, u4_(& sp->up_in)); /* src_b */ tb_data(tb, u4_(& sp->right));
tb_data(tb, u4_(& sp->right)); /* out */
} }
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_data(tb, u4_(O_(ResolveLookAtScratch, right) | (O_(ResolveLookAtScratch, ux) << 16)));
} }
tb_emit(tb, smem.resolve_look_at_bundle[4]); { tb_emit(tb, bundle->cross_to_up); {
/* Binds_gte_cross_v3s4: src_a, src_b, out (3 pointers). */ tb_data(tb, u4_(& sp->uz));
tb_data(tb, u4_(& sp->uz)); /* src_a */ tb_data(tb, u4_(& sp->ux));
tb_data(tb, u4_(& sp->ux)); /* src_b */ tb_data(tb, u4_(& sp->up));
tb_data(tb, u4_(& sp->up)); /* out */
} }
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))); tb_data(tb, u4_(O_(ResolveLookAtScratch, up) | (O_(ResolveLookAtScratch, uy) << 16)));
} }
tb_emit(tb, bundle->pop_mv_trans); {
/* === FUSED atom: populate + matrix_vector + trans_matrix (replaces 4 separate emits) === */
tb_emit(tb, smem.resolve_look_at_bundle[6]); {
tb_data(tb, u4_(look_at)); 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; gknown V3_S4_R acc = & smem.cube.accel;
add_v3s4(vel, acc[0]); add_v3s4(vel, acc[0]);
add_v3s4_fp(pos, vel[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; if (pos->y + 150 > smem.floor.pos.y) vel->y *= -1;
// Prep // Prep
S4 nclip = 0; S4 nclip = 0;
S4 orderingtbl_z = 0; S4 orderingtbl_z = 0;
A2_S2 p; //??? A2_S2 p; //???
S4 flag; //???? S4 flag; //????
B4 use_c11_path = false; 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_rotation (& smem.tform_view);
gte_matrix_set_translation(& 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_base = u4_(pa->buf[smem.active_buf_id]);
U4 prim_cursor = prim_base + pa->used; 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, u4_(& pa->used));
tb_data(& tb, prim_base); 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; // 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, u4_(& pa->used));
tb_data(& tb, prim_base); 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! // C-side state (pa->used) has already been updated by the tape!
// smem.floor.rot.y += 5; // smem.floor.rot.y += 5;
+16 -16
View File
@@ -2,7 +2,7 @@
local scan = require("duffle_scan") local scan = require("duffle_scan")
local isa = require("duffle_isa") local isa = require("duffle_isa")
local emit = require("duffle_emit") local emit = require("duffle_emit")
local M = {} local M = {}
local function merge(src, label) local function merge(src, label)
for k, v in pairs(src) do for k, v in pairs(src) do
@@ -18,30 +18,30 @@ merge(isa, "duffle_isa")
merge(emit, "duffle_emit") merge(emit, "duffle_emit")
function M.corpus_view(ctx) 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 if not corpus then error("requires ctx.shared.corpus", 0) end
return { return {
register_alias_registry = corpus.register_alias_registry or {}, register_alias_registry = corpus.register_alias_registry or {},
type_name_registry = corpus.type_name_registry or {}, type_name_registry = corpus.type_name_registry or {},
atom_views = corpus.atom_views or {}, atom_views = corpus.atom_views or {},
atom_ctxs = corpus.atom_ctxs or {}, atom_ctxs = corpus.atom_ctxs or {},
atom_phases = corpus.atom_phases or {}, atom_phases = corpus.atom_phases or {},
binds_by_name = corpus.binds_by_name or {}, binds_by_name = corpus.binds_by_name or {},
atoms_by_name = corpus.atoms_by_name or {}, atoms_by_name = corpus.atoms_by_name or {},
atom_infos = corpus.atom_infos or {}, atom_infos = corpus.atom_infos or {},
components = corpus.components or {}, components = corpus.components or {},
component_atom_infos = corpus.component_atom_infos or {}, component_atom_infos = corpus.component_atom_infos or {},
component_body_index = corpus.component_body_index or {}, component_body_index = corpus.component_body_index or {},
tape_chains = corpus.tape_chains or {}, tape_chains = corpus.tape_chains or {},
source_order = corpus.source_order or {}, source_order = corpus.source_order or {},
collisions = corpus.collisions or {}, collisions = corpus.collisions or {},
} }
end end
function M.run_check_rules(rules, phase, item, pipe_ctx, findings) function M.run_check_rules(rules, phase, item, pipe_ctx, findings)
for _, rule in ipairs(rules) do for _, rule in ipairs(rules) do
local fn = rule[phase] local fn = rule[phase]
if fn then fn(item, pipe_ctx, findings) end if fn then fn(item, pipe_ctx, findings) end
end end
end end
+18 -21
View File
@@ -74,11 +74,11 @@ end
--- @param tok string --- @param tok string
--- @return string, string[] --- @return string, string[]
local function token_ident_and_args(tok) 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 if not ident then return "?", {} end
local paren_pos = M.skip_ws_and_cmt(tok, after) local paren_pos = M.skip_ws_and_cmt(tok, after)
if tok:sub(paren_pos, paren_pos) ~= "(" then return ident, {} end 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 if not inner then return ident, {} end
return ident, split_call_args(inner) return ident, split_call_args(inner)
end 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. --- 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. --- 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 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 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 word_counts table -- macro name → emitted-word count (from `ctx.shared.word_counts`)
--- @return WordEvent[], WordEventError[] --- @return WordEvent[], WordEventError[]
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
@@ -775,18 +775,16 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- find_function_decl_for — backward walk for MipsAtomComp_Proc_ name extraction. -- find_function_decl_for — backward walk for MipsAtomComp_Proc_ name extraction.
-- --
-- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name -- After the `sym` arg was dropped from MipsAtomComp_Proc_, the component name is derived from the preceding
-- is derived from the preceding `FI_ Slice_MipsCode ac_X(args)` function -- `FI_ Slice_MipsCode ac_X(args)` function declaration. This function walks backward from `before_pos` to find it.
-- declaration. This function walks backward from `before_pos` to find it.
-- --
-- Returns (raw_name, args_inner) or (nil, nil). -- Returns (raw_name, args_inner) or (nil, nil).
-- raw_name — e.g. "ac_load_word_imm" -- raw_name — e.g. "ac_load_word_imm"
-- args_inner — e.g. "AtomBuilder_R ab, Reg dst, U4 imm" -- args_inner — e.g. "AtomBuilder_R ab, Reg dst, U4 imm"
-- --
-- The walk finds the LAST "Slice_MipsCode" before before_pos, then skips -- The walk finds the LAST "Slice_MipsCode" before before_pos, then skips whitespace + qualifiers
-- whitespace + qualifiers (FI_, atom_dbg_skip, comments) until it finds an -- (FI_, atom_dbg_skip, comments) until it finds an ident followed by "(".
-- ident followed by "(". That ident is the function name; the parens contents -- That ident is the function name; the parens contents are the args.
-- are the args.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function M.find_function_decl_for(source, before_pos, slice_mips_code_len) function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
local search_pos = 1 local search_pos = 1
@@ -819,13 +817,13 @@ function M.find_function_decl_for(source, before_pos, slice_mips_code_len)
end end
-- skip block comments -- skip block comments
if source:sub(pos, pos + 1) == "/*" then 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 if not close then break end
pos = close + 2 pos = close + 2
goto continue goto continue
end end
-- try to read an ident -- 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 if not ident then break end
-- check if the next non-ws char after ident is "(" -- check if the next non-ws char after ident is "("
local next_pos = M.skip_ws_and_cmt(source, ident_end) 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, ..." -- args_inner — e.g. "AtomArena_R aa, U4 r_scratch, ..."
-- after_paren — source position after the function `)` -- after_paren — source position after the function `)`
-- --
-- The walk finds the LAST "MipsAtom*" before before_pos, then skips -- The walk finds the LAST "MipsAtom*" before before_pos, then skips whitespace + qualifiers (internal, I_, FI_, comments)
-- whitespace + qualifiers (internal, I_, FI_, comments) until it finds an -- until it finds an ident followed by "(".
-- ident followed by "(". That ident is the name. The parens contents are the args.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
local search_pos = 1 local search_pos = 1
local last_match = nil local last_match = nil
while true do while true do
-- plain=true: "*" is literal, no escaping needed -- 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 if not found or found >= before_pos then break end
last_match = found last_match = found
search_pos = found + mips_atom_ptr_len 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 end
-- skip block comments -- skip block comments
if source:sub(pos, pos + 1) == "/*" then 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 if not close then break end
pos = close + 2 pos = close + 2
goto continue goto continue
end end
-- try to read an ident -- 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 if not ident then break end
-- check if the next non-ws char after ident is "(" -- check if the next non-ws char after ident is "("
local next_pos = M.skip_ws_and_cmt(source, ident_end) 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 return nil, nil
end end
return M return M
+228 -1118
View File
File diff suppressed because it is too large Load Diff
+9 -15
View File
@@ -22,12 +22,10 @@ local M = {}
local CACHE_KEY = "__duffle_repo_root__" local CACHE_KEY = "__duffle_repo_root__"
--- Resolve the repo root from this script's own path. Zero shell spawn. --- Resolve the repo root from this script's own path. Zero shell spawn.
--- `duffle_paths.lua` always lives at `<repo>/scripts/duffle_paths.lua`, so the repo root is the --- `duffle_paths.lua` always lives at `<repo>/scripts/duffle_paths.lua`, so the repo root is the parent of the directory containing this script.
--- parent of the directory containing this script. We derive it directly from `debug.getinfo(1, "S").source` --- We derive it directly from `debug.getinfo(1, "S").source` (returns `@<path>` for the currently-running chunk).
--- (returns `@<path>` for the currently-running chunk).
--- ---
--- If `debug.getinfo` can't parse this script's path (shouldn't happen — dofile always populates source), --- 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 nil and let `M.setup()` fail loud.
--- @return string|nil --- @return string|nil
local function find_repo_root() local function find_repo_root()
if package.loaded[CACHE_KEY] then return package.loaded[CACHE_KEY] end 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. --- 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). --- 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/`, --- 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).
--- which we wire into `package.cpath` here (so `require("lpeg")` from `duffle.lua` resolves without any global state).
function M.setup() function M.setup()
local repo_root = find_repo_root() local repo_root = find_repo_root()
if not repo_root then if not repo_root then
-- Unreachable in practice: find_repo_root() derives the repo root from this script's -- 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).
-- 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 <repo>/scripts/duffle_paths.lua layout — a packaging bug, not a "missing git repo" condition.
-- A nil return means the source path did not match the expected -- os.exit(2) is retained so a real failure surfaces loud rather than silently producing an unconfigured module table.
-- <repo>/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) os.exit(2)
end end
@@ -86,6 +80,6 @@ end
-- Run the setup as a side effect. -- Run the setup as a side effect.
M.setup() M.setup()
-- Now that package.path includes scripts/, `require("duffle")` resolves. Return the duffle module -- Now that package.path includes scripts/, `require("duffle")` resolves.
-- so callers can do `local duffle = dofile(...duffle_paths.lua)` in one line. -- Return the duffle module so callers can do `local duffle = dofile(...duffle_paths.lua)` in one line.
return require("duffle") return require("duffle")
+22 -23
View File
@@ -422,10 +422,10 @@ end
--- should use this directly rather than going through `read_form_value`, --- 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. --- which only exposes the low 4 bytes to preserve its existing (value, next_pos) return shape.
--- @param buf string --- @param buf string
--- @param pos integer -- zero-based wire offset --- @param pos integer -- zero-based wire offset
--- @return integer -- low 4 bytes (LE), the type signature --- @return integer -- low 4 bytes (LE), the type signature
--- @return integer -- high 4 bytes (LE), the offset within the matching type unit --- @return integer -- high 4 bytes (LE), the offset within the matching type unit
--- @return integer -- cursor after the 8-byte value --- @return integer -- cursor after the 8-byte value
function M.read_ref_sig8(buf, pos) function M.read_ref_sig8(buf, pos)
return M.read_u32_le(buf, pos), M.read_u32_le(buf, pos + 4), pos + 8 return M.read_u32_le(buf, pos), M.read_u32_le(buf, pos + 4), pos + 8
end end
@@ -440,10 +440,10 @@ end
--- unit_length(4) + version(2) + unit_type(1) + address_size(1) + debug_abbrev_offset(4) --- 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) --- 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). --- 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 info string -- the .debug_info section bytes
--- @param target_sig_lo integer -- low 4 bytes (LE) of the desired signature --- @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 --- @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 --- @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) function M.find_type_unit_by_signature(info, target_sig_lo, target_sig_hi)
local pos = 0 local pos = 0
local section_len = #info 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 return nil, nil -- malformed
end end
-- Per DWARF5 §7.5.6, the type_unit (DW_UT_type = 0x02) body layout is: -- Per DWARF5 §7.5.6, the type_unit (DW_UT_type = 0x02) body layout is:
-- 0: version (2) -- 0: version (2)
-- 2: unit_type (1) -- DW_UT_type = 0x02 -- 2: unit_type (1) -- DW_UT_type = 0x02
-- 3: address_size (1) -- 3: address_size (1)
-- 4: debug_abbrev_offset (4) -- 4: debug_abbrev_offset (4)
-- 8: type_signature (8) -- 8: type_signature (8)
-- 16: type_offset (4) -- 16: type_offset (4)
-- 20: <children> -- 20: <children>
if body_end - body_start >= 20 then if body_end - body_start >= 20 then
-- read_ref_sig8 / write_u32_le / etc. are 1-indexed (string:byte); -- read_ref_sig8 / write_u32_le / etc. are 1-indexed (string:byte);
-- pos / body_start / body_end are 0-based wire offsets, so the -- 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).
-- 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: -- Per DWARF5 §7.5.6, the type_unit body is laid out as:
-- byte 0-1: version (2) -- byte 0-1: version (2)
-- byte 2: unit_type (1) -- DW_UT_type = 0x02 -- byte 2: unit_type (1) -- DW_UT_type = 0x02
-- byte 3: address_size (1) -- byte 3: address_size (1)
-- byte 4-7: debug_abbrev_offset (4) -- byte 4-7: debug_abbrev_offset (4)
-- byte 8-15: type_signature (8) -- byte 8-15: type_signature (8)
-- byte 16-19: type_offset (4) -- byte 16-19: type_offset (4)
local unit_type = info:byte(body_start + 2 + 1) -- 0-based +2 = unit_type in 1-indexed 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 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 local sig_lo, sig_hi, _ = M.read_ref_sig8(info, body_start + 8) -- 0-based +8 = type_signature in 1-indexed
+10 -10
View File
@@ -1,10 +1,9 @@
# scripts/launch_pcsx_debug.ps1 # scripts/launch_pcsx_debug.ps1
# #
# One-shot launcher for debug sessions: starts pcsx-redux with the .ps-exe # One-shot launcher for debug sessions:
# loaded, the gdb stub enabled, AND the pcsx_debug_helper Lua plugin loaded # Starts pcsx-redux with the .ps-exe loaded, the gdb stub enabled,
# so external CLI tools (gdb's `shell` command, etc.) # 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 # can read GTE state via http://localhost:8080/api/v1/lua/gte (the gdb stub doesn't expose COP2 at all).
# (the gdb stub doesn't expose COP2 at all).
# #
# usage: # usage:
# .\scripts\launch_pcsx_debug.ps1 # .\scripts\launch_pcsx_debug.ps1
@@ -19,11 +18,11 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$PcsxPath = (Join-Path $PSScriptRoot '..\toolchain\pcsx-redux\vsprojects\x64\Release\pcsx-redux.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]$ExePath = (Join-Path $PSScriptRoot '..\build\hello_gte.ps-exe'),
[string]$HelperZip = (Join-Path $PSScriptRoot 'pcsx_debug_helper.zip'), [string]$HelperZip = (Join-Path $PSScriptRoot 'pcsx_debug_helper.zip'),
[int] $GdbPort = 3333, [int] $GdbPort = 3333,
[int] $WebPort = 8080 [int] $WebPort = 8080
) )
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
@@ -84,7 +83,8 @@ try {
$r = Invoke-WebRequest -Uri "http://localhost:$WebPort/api/v1/lua/gte" -UseBasicParsing -TimeoutSec 5 $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] $firstLine = ([System.Text.Encoding]::UTF8.GetString($r.Content) -split "`n")[0]
Write-Host "GTE handler OK: $firstLine" -ForegroundColor Green Write-Host "GTE handler OK: $firstLine" -ForegroundColor Green
} catch { }
catch {
Write-Warning "GTE handler NOT responding: $_" Write-Warning "GTE handler NOT responding: $_"
Write-Host "Check the pcsx-redux Lua Console for debug cli messages." -ForegroundColor Yellow Write-Host "Check the pcsx-redux Lua Console for debug cli messages." -ForegroundColor Yellow
} }
+249 -261
View File
@@ -27,50 +27,34 @@
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
--- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
--- THE GPR ALLOCATION POOL — what is allocatable, and (more importantly) WHY -- 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 auto-reg pass picks physical GPRs for `atom_auto_reg(...)` / `phase_auto_reg(...)` markers.
--- It allocates from a FIXED 10-register pool. -- The 24-register pool covers R2-R25 (the user/atom allocatable surface):
--- This comment block makes the inclusion AND exclusion criteria obvious so a reader doesn't have -- R_T0..R_T7, R_V0..R_V1, R_A0..A3, R_S0..S7, R_T8..T9.
--- to grep lottes_tape.h + mips.h to understand the design. -- Excluded (and never added to the pool):
--- -- R_0 (code 0) — hardwired zero. Cannot be written.
--- ── WHAT'S IN THE POOL (10 GPRs, all caller-trash per the O32 ABI) ──────── -- R_AT (code 1) — assembler temporary. Reserved by the MIPS O32 ABI.
--- R_T0..R_T7 (GPR codes 8..15), R_V0..R_V1 (GPR codes 2..3) -- R_A0..A3 — explicitly omitted above even though their integer codes
--- The workhorse of every atom body. The uesr should be aware of atom allocation across atoms they chain. -- map to POOL entries; the pool-construction loop below
--- If they have a collision it means either they didn't saturate the register file optimally for a phase, -- only references the POOL string literals, never the
--- or the may have made the workload to large for the run. -- integer codes, so they are NOT auto-allocated by default.
--- -- (A0-A3 become available when the user adds them to
--- ── WHAT'S NOT IN THE POOL — and WHY (the "obvious exclusions") ──────────── -- POOL or hardcodes an R_A0 reference in the atom body.)
--- R_T9 (GPR code 25) — R_TapePtr, the tape instruction stream pointer. -- R_K0/K1 (codes 26-27) — kernel / interrupt handler reserves. Never touched by user code.
--- Owned by the tape runtime (in tape_run / tape_run_a02_s07). -- R_GP/SP/FP/RA (codes 28-31) — R_SP/R_FP/R_RA are tape-runtime carriers between
--- `rgcc(R_TapePtr)` register-variable ties the C compiler's view to $t9 across the whole tape_run. -- tape_enter and tape_exit; R_GP stays the host global pointer.
--- 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.
--- ---
local POOL = { local POOL = {
"R_T0", "R_T1", "R_T2", "R_T3", "R_T0", "R_T1", "R_T2", "R_T3",
"R_T4", "R_T5", "R_T6", "R_T7", "R_T4", "R_T5", "R_T6", "R_T7",
"R_V0", "R_V1", "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. -- 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) -- (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. -- are deliberately omitted — see the comment block above for the WHY of each exclusion.
local INT_CODE_TO_POOL_GPR = { local INT_CODE_TO_POOL_GPR = {
[2] = "R_V0", [3] = "R_V1", [2] = "R_V0", [3] = "R_V1",
[8] = "R_T0", [9] = "R_T1", [10] = "R_T2", [11] = "R_T3", [4] = "R_A0", [5] = "R_A1", [6] = "R_A2", [7] = "R_A3",
[12] = "R_T4", [13] = "R_T5", [14] = "R_T6", [15] = "R_T7", [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. -- Stable sort for deterministic allocation order.
local function stable_sort_keys(tbl) local function stable_sort_keys(tbl)
local keys = {} local keys = {}
for k in pairs(tbl) do keys[#keys + 1] = k end for k in pairs(tbl) do keys[#keys + 1] = k end
table.sort(keys) table.sort(keys)
return keys return keys
end end
-- Allocate one phase's auto-reg mappings. -- Allocate one phase's auto-reg mappings.
-- Returns (allocated_map, errors). On pool exhaustion, errors is populated and the function halts. -- Returns (allocated_map, errors). On pool exhaustion, errors is populated and the function halts.
local function allocate_phase(phase_label, decls) 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) }` -- 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), -- 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. -- which corrupts the pool into `{ {R_T0, R_T1, ...} }` — making `table.remove(pool, 1)` return the inner table on iteration.
local pool = {} local pool = {}
for i = 1, #POOL do pool[i] = POOL[i] end for i = 1, #POOL do pool[i] = POOL[i] end
local result = {} local result = {}
local errors = {} local errors = {}
for _, sym in ipairs(stable_sort_keys(decls)) do for _, sym in ipairs(stable_sort_keys(decls)) do
local next_gpr = table.remove(pool, 1) local next_gpr = table.remove(pool, 1)
if not next_gpr then if not next_gpr then
errors[#errors + 1] = { errors[#errors + 1] = {
line = 0, line = 0,
msg = string.format("phase_register_pool_exhausted: " msg = string.format("phase_register_pool_exhausted: "
.. "phase '%s' requested symbol '%s' but the pool has no remaining registers " .. "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." .. "(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), , phase_label, sym),
} }
return result, errors return result, errors
end end
result[sym] = next_gpr result[sym] = next_gpr
end end
return result, errors return result, errors
end end
-- Build two projections from corpus.register_alias_registry: -- 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 — -- 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. -- they don't affect the auto_reg pool, and they're already excluded from POOL above.
local function build_user_pins(corpus) local function build_user_pins(corpus)
local user_pinned = {} local user_pinned = {}
local alias_to_gpr = {} local alias_to_gpr = {}
if not corpus.register_alias_registry then return user_pinned, alias_to_gpr end 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 for alias_name, alias_entry in pairs(corpus.register_alias_registry) do
if alias_entry.has_atom_reg and alias_entry.code then if alias_entry.has_atom_reg and alias_entry.code then
local gpr = INT_CODE_TO_POOL_GPR[alias_entry.code] local gpr = INT_CODE_TO_POOL_GPR[alias_entry.code]
if gpr then if gpr then
user_pinned[gpr] = true user_pinned[gpr] = true
alias_to_gpr[alias_name] = gpr alias_to_gpr[alias_name] = gpr
end end
end end
end end
return user_pinned, alias_to_gpr return user_pinned, alias_to_gpr
end end
-- Find every physical GPR referenced in the atom body, via EITHER: -- 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. -- original find_hardcoded_rn shape so callers can switch without churn.
-- The alias pattern is sorted lexicographically to keep the regex deterministic. -- The alias pattern is sorted lexicographically to keep the regex deterministic.
local function find_used_gprs(body_text, alias_to_gpr) local function find_used_gprs(body_text, alias_to_gpr)
local found = {} local found = {}
-- (a) Hardcoded physical GPRs (R_T0..R_T7, R_V0..R_V1, R_A0..R_A3, R_S0..R_S7). -- (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 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 found[gpr] = (found[gpr] or 0) + 1
end end
-- (b) Alias references (R_<Alias>) resolved to physical GPRs via the registry. -- (b) Alias references (R_<Alias>) resolved to physical GPRs via the registry.
-- Sorted by name so the regex is byte-stable across runs. -- Sorted by name so the regex is byte-stable across runs.
if alias_to_gpr and next(alias_to_gpr) then if alias_to_gpr and next(alias_to_gpr) then
local aliases = {} local aliases = {}
for alias_name in pairs(alias_to_gpr) do for alias_name in pairs(alias_to_gpr) do
aliases[#aliases + 1] = alias_name aliases[#aliases + 1] = alias_name
end end
table.sort(aliases) table.sort(aliases)
local pattern = "(" .. table.concat(aliases, "|") .. ")" local pattern = "(" .. table.concat(aliases, "|") .. ")"
for alias_name in body_text:gmatch(pattern) do for alias_name in body_text:gmatch(pattern) do
local gpr = alias_to_gpr[alias_name] local gpr = alias_to_gpr[alias_name]
if gpr and not found[gpr] then if gpr and not found[gpr] then
found[gpr] = 1 found[gpr] = 1
end end
end end
end end
return found return found
end end
-- Emit one gen/auto_reg.h header per directory. -- Emit one gen/auto_reg.h header per directory.
local function emit_auto_reg_h(out_dir, dir, sources, mappings) local function emit_auto_reg_h(out_dir, dir, sources, mappings)
if not mappings or next(mappings) == nil then return end if not mappings or next(mappings) == nil then return end
local out_path = out_dir .. "/" .. "auto_reg.h" local out_path = out_dir .. "/" .. "auto_reg.h"
duffle.ensure_dir(out_dir) duffle.ensure_dir(out_dir)
local lines = { local lines = {
"#ifdef INTELLISENSE_DIRECTIVES", "#ifdef INTELLISENSE_DIRECTIVES",
"#pragma once", "#pragma once",
"#endif", "#endif",
"// Auto-generated by ps1_meta.lua (passes/auto_reg.lua) — DO NOT EDIT", "// Auto-generated by ps1_meta.lua (passes/auto_reg.lua) — DO NOT EDIT",
"// Directory: " .. dir:gsub("/", "\\"), "// Directory: " .. dir:gsub("/", "\\"),
} }
for _, src in ipairs(sources) do for _, src in ipairs(sources) do
lines[#lines + 1] = "// source: " .. src.path lines[#lines + 1] = "// source: " .. src.path
end end
lines[#lines + 1] = "// Per-phase register allocations resolved by the lua pass." lines[#lines + 1] = "// Per-phase register allocations resolved by the lua pass."
lines[#lines + 1] = "// R_<Sym>_Code = <chosen GPR's _Code constant> for every marker in this directory." lines[#lines + 1] = "// R_<Sym>_Code = <chosen GPR's _Code constant> for every marker in this directory."
lines[#lines + 1] = "" lines[#lines + 1] = ""
for _, sym in ipairs(stable_sort_keys(mappings)) do for _, sym in ipairs(stable_sort_keys(mappings)) do
local gpr = mappings[sym] local gpr = mappings[sym]
local gpr_code = gpr .. "_Code" local gpr_code = gpr .. "_Code"
lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code lines[#lines + 1] = "#define " .. sym .. "_Code " .. gpr_code
end end
lines[#lines + 1] = "" lines[#lines + 1] = ""
duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n") duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n")
print(" -> " .. out_path) print(" -> " .. out_path)
return out_path return out_path
end end
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
@@ -213,143 +201,143 @@ local M = {}
--- @param ctx PassCtx --- @param ctx PassCtx
--- @return AutoRegResult --- @return AutoRegResult
function M.run(ctx) function M.run(ctx)
local outputs = {} local outputs = {}
local errors = {} local errors = {}
local warnings = {} local warnings = {}
local corpus = ctx.shared and ctx.shared.corpus local corpus = ctx.shared and ctx.shared.corpus
if type(corpus) ~= "table" then if type(corpus) ~= "table" then
error("auto_reg.run requires ctx.shared.corpus", 0) error("auto_reg.run requires ctx.shared.corpus", 0)
end end
-- 0. Build the user-pinned GPR exclusion set + alias-to-GPR resolution map. -- 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) -- 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. -- 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. -- 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. -- 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) local user_pinned, alias_to_gpr = build_user_pins(corpus)
-- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations). -- 1. Allocate phase pools first (phase declarations take precedence over per-atom declarations).
local phase_allocations = {} local phase_allocations = {}
for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do for phase_label, decls in pairs(corpus.phase_auto_regs or {}) do
local mapping, errs = allocate_phase(phase_label, decls) local mapping, errs = allocate_phase(phase_label, decls)
for sym, gpr in pairs(mapping) do for sym, gpr in pairs(mapping) do
phase_allocations[phase_label] = phase_allocations[phase_label] or {} phase_allocations[phase_label] = phase_allocations[phase_label] or {}
phase_allocations[phase_label][sym] = gpr phase_allocations[phase_label][sym] = gpr
end end
for _, e in ipairs(errs) do for _, e in ipairs(errs) do
errors[#errors + 1] = e errors[#errors + 1] = e
end end
end end
-- 2. Allocate per-atom auto-regs. If the atom scope matches a phase, reuse the phase pool. -- 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. -- 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(<phase>)` -- The phase membership is in `corpus.atom_phases[phase_label].atoms` (an array of atom names declared via `atom_phase(<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. -- 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 = {} local atom_name_to_phase = {}
for phase_label, entry in pairs(corpus.atom_phases or {}) do for phase_label, entry in pairs(corpus.atom_phases or {}) do
for _, atom_name in ipairs(entry.atoms or {}) do for _, atom_name in ipairs(entry.atoms or {}) do
atom_name_to_phase[atom_name] = phase_label atom_name_to_phase[atom_name] = phase_label
end end
end end
local atom_allocations = {} local atom_allocations = {}
for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do for atom_scope, decls in pairs(corpus.atom_auto_regs or {}) do
local phase_label = atom_name_to_phase[atom_scope] local phase_label = atom_name_to_phase[atom_scope]
-- Build the atom's source pool: start with the full POOL, subtract: -- Build the atom's source pool: start with the full POOL, subtract:
-- (a) every GPR already committed (phase allocations + prior atom allocations) -- (a) every GPR already committed (phase allocations + prior atom allocations)
-- (b) every USER-PINNED GPR (wave-context carriers + file-scope pinned aliases) -- (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 -- (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) -- (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; -- 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 -- 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)` -- 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`. -- returned nil and every atom-with-phase marker errored with `phase_register_pool_exhausted`.
local used = {} local used = {}
for _, m in pairs(phase_allocations) do for _, gpr in pairs(m) do used[gpr] = true end end 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 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. -- (c) Body references — scan the atom body for hardcoded + alias-resolved GPRs.
-- Folded into `used` so the source_pool exclusion is a single check. -- 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] local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope]
if atom and atom.body then if atom and atom.body then
local body_used = find_used_gprs(atom.body, alias_to_gpr) local body_used = find_used_gprs(atom.body, alias_to_gpr)
for gpr in pairs(body_used) do used[gpr] = true end for gpr in pairs(body_used) do used[gpr] = true end
end end
local source_pool = {} local source_pool = {}
for _, gpr in ipairs(POOL) do for _, gpr in ipairs(POOL) do
-- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers -- Exclude (a) prior commitments, (b) USER-PINNED GPRs (wave-context carriers
-- declared via atom_reg + _Code defs, preserved across atoms globally). -- declared via atom_reg + _Code defs, preserved across atoms globally).
if not used[gpr] and not user_pinned[gpr] then if not used[gpr] and not user_pinned[gpr] then
source_pool[#source_pool + 1] = gpr source_pool[#source_pool + 1] = gpr
end end
end end
local result = {} local result = {}
for _, sym in ipairs(stable_sort_keys(decls)) do for _, sym in ipairs(stable_sort_keys(decls)) do
local next_gpr = table.remove(source_pool, 1) local next_gpr = table.remove(source_pool, 1)
if not next_gpr then if not next_gpr then
errors[#errors + 1] = { errors[#errors + 1] = {
line = 0, line = 0,
msg = string.format("phase_register_pool_exhausted: atom '%s' requested symbol '%s' " msg = string.format("phase_register_pool_exhausted: atom '%s' requested symbol '%s' "
.. "but no free registers remain in its scope pool." .. "but no free registers remain in its scope pool."
, atom_scope, sym), , atom_scope, sym),
} }
else else
result[sym] = next_gpr result[sym] = next_gpr
end end
end end
atom_allocations[atom_scope] = result atom_allocations[atom_scope] = result
end end
-- 3. Conflict-with-hardcoded detection (defensive — should be unreachable now). -- 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 -- 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_<Alias>). -- and body-referenced GPRs (hardcoded R_Tn OR alias R_<Alias>).
-- An auto-reg allocation that matched an existing body reference would be impossible by construction. -- 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 -- 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). -- (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 each resolved (scope, sym) -> R_Tn mapping, scan the atom body source for used GPRs.
for atom_scope, decls in pairs(atom_allocations) do for atom_scope, decls in pairs(atom_allocations) do
local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope] local atom = corpus.atoms_by_name and corpus.atoms_by_name[atom_scope]
if atom and atom.body then if atom and atom.body then
local used_in_body = find_used_gprs(atom.body, alias_to_gpr) local used_in_body = find_used_gprs(atom.body, alias_to_gpr)
for sym, allocated_gpr in pairs(decls) do for sym, allocated_gpr in pairs(decls) do
if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then if used_in_body[allocated_gpr] and used_in_body[allocated_gpr] > 0 then
warnings[#warnings + 1] = { warnings[#warnings + 1] = {
line = atom.line or 0, 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' " 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." .. "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), , atom_scope, allocated_gpr, sym, allocated_gpr),
} }
end end
end end
end end
end end
-- 4. Emit per-directory gen/auto_reg.h. -- 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. -- 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 {} local sources_by_dir = corpus.sources_by_dir or {}
for dir, sources in pairs(sources_by_dir) do for dir, sources in pairs(sources_by_dir) do
local per_dir_mappings = {} local per_dir_mappings = {}
for _, src in ipairs(sources) do for _, src in ipairs(sources) do
-- Collect every (sym -> gpr) entry that originated from a source in this directory. -- 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"). -- `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, -- 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. -- 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 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 for sym, gpr in pairs(atom_allocations[scope_name] or {}) do
per_dir_mappings[sym] = gpr per_dir_mappings[sym] = gpr
end end
end end
for scope_name in pairs(src.scan and src.scan.phase_auto_regs or {}) do 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 for sym, gpr in pairs(phase_allocations[scope_name] or {}) do
per_dir_mappings[sym] = gpr per_dir_mappings[sym] = gpr
end end
end end
end end
local out_dir = dir .. "/gen" local out_dir = dir .. "/gen"
local out_path = emit_auto_reg_h(out_dir, dir, sources, per_dir_mappings) 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 if out_path then outputs[#outputs + 1] = { auto_reg_h = out_path } end
end end
return { outputs = outputs, errors = errors, warnings = warnings } return { outputs = outputs, errors = errors, warnings = warnings }
end end
return M return M
+13 -23
View File
@@ -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 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 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 no_fields = not type_entry or not type_entry.fields or #type_entry.fields == 0
local raw_entry = type_registry[type_name] local raw_entry = type_registry[type_name]
local is_typedef_to_struct = raw_entry local is_typedef_to_struct = raw_entry
and raw_entry.kind == "typedef" and raw_entry.kind == "typedef"
and raw_entry.underlying_type and raw_entry.underlying_type
@@ -2506,13 +2506,11 @@ local function check_binds_no_substruct_deref(_src, pipe_ctx, findings)
end end
-- ════════════════════════════════════════════════════════════════════════════
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
-- GTE control-register alias + RT-diagonal + TR-naming helpers and checks -- GTE control-register alias + RT-diagonal + TR-naming helpers and checks
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
--- Resolve a `gte_cr_<Alias>` ident to its alias-group entry, or nil if the alias --- Resolve a `gte_cr_<Alias>` 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).
--- 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`. --- Reads `M.GTE_CR_ALIAS_GROUPS` from `duffle.lua`.
local function find_alias_pair_for(alias_name, duffle) local function find_alias_pair_for(alias_name, duffle)
local groups = (duffle and duffle.GTE_CR_ALIAS_GROUPS) or {} 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" return c.ident == "gte_mv_to_ctrl_r" or c.ident == "gte_mv_from_ctrl_r"
end end
-- Resolve a token's source line. The per-token `line` is the body-relative -- Resolve a token's source line.
-- line; `atom.line` is the source line of the atom declaration; `line_in_body` -- 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.
-- (atom.paths) maps a body-relative line to its source line. The arithmetic -- 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.
-- `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) 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 if line_in_body == nil or token == nil or token.rel == nil then
return atom.line or 0 return atom.line or 0
@@ -2584,14 +2580,11 @@ local function ctrl_writes_in_atom(atom)
end end
-- Check #N: gte_cr_alias_writes -- Check #N: gte_cr_alias_writes
-- Fires one warning per atom per alias-group when the atom body touches two -- Fires one warning per atom per alias-group when the atom body touches two distinct aliases from the same group.
-- distinct aliases from the same group. Aliases within a group write to the -- 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.
-- 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 -- Severity: warning. Build continues.
-- uses only RT-row aliases (which are NOT in `M.GTE_CR_ALIAS_GROUPS`), so -- 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.
-- the canonical convention does not trigger this check.
local function check_gte_cr_alias_writes(atom, pipe_ctx, findings) local function check_gte_cr_alias_writes(atom, pipe_ctx, findings)
local groups = pipe_ctx.gte_cr_alias_groups or {} local groups = pipe_ctx.gte_cr_alias_groups or {}
if not next(groups) then return end if not next(groups) then return end
@@ -2650,14 +2643,11 @@ end
-- Check #N+1: rtdiagonal_completeness -- Check #N+1: rtdiagonal_completeness
-- Fires one info per atom body when the bare `gte_cmdw_mvmva` macro is used. -- 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 -- 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).
-- shape uses `gte_cmdw_mvmva_c11_pass2_exact = 0x4A49E012` (gte.h:430).
-- --
-- Severity: info by default. Escalates to warning when -- Severity: info by default. Escalates to warning when `GTE_RT_DIAGONAL_STRICT=1` env var is set (CI / production builds).
-- `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 -- 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.
-- convention, so this is an opt-out hint rather than a hard warning.
local function check_rtdiagonal_completeness(atom, _pipe_ctx, findings) local function check_rtdiagonal_completeness(atom, _pipe_ctx, findings)
local tokens = atom.paths and atom.paths.tokens or {} local tokens = atom.paths and atom.paths.tokens or {}
local tc = atom.paths and atom.paths.tok_class or {} local tc = atom.paths and atom.paths.tok_class or {}
+14 -25
View File
@@ -91,11 +91,9 @@ if (-not (Test-Path -LiteralPath $path_pcsx_packages)) {
New-Item -ItemType Directory -Path $path_pcsx_packages -Force | Out-Null New-Item -ItemType Directory -Path $path_pcsx_packages -Force | Out-Null
} }
# Download anything missing. Skip the package entirely if its dir already has # Download anything missing.
# any contents (the legacy packages.config style means the targets file # Skip the package entirely if its dir already has any contents (the legacy packages.config style means the targets file location varies per package
# location varies per package — `luajit.native` puts it at build/native/, # — `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).
# `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 Add-Type -AssemblyName System.IO.Compression.FileSystem
foreach ($pkg in $required_packages.Values) { foreach ($pkg in $required_packages.Values) {
$pkgDir = Join-Path $path_pcsx_packages ('{0}.{1}' -f $pkg.id, $pkg.version) $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 # isoffi.lua size guard — `core.vcxproj` #includes src/core/isoffi.lua into luaiso.cc via the `-- lualoader, R"EOF(...)EOF"` trick.
# luaiso.cc via the `-- lualoader, R"EOF(...)EOF"` trick. The raw string # 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).
# literal between R"EOF(-- and -- )EOF" must stay under ~16,379 bytes or # 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.
# 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. # Idempotent — only writes when the raw string exceeds the limit.
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
$path_isoffi = join-path $path_pcsx_redux 'src\core\isoffi.lua' $path_isoffi = join-path $path_pcsx_redux 'src\core\isoffi.lua'
if (Test-Path -LiteralPath $path_isoffi) { if (Test-Path -LiteralPath $path_isoffi) {
$content = Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8 $content = Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8
$startMarker = $content.IndexOf('R"EOF(--') $startMarker = $content.IndexOf('R"EOF(--')
$endMarker = $content.IndexOf('-- )EOF"') $endMarker = $content.IndexOf('-- )EOF"')
$literalLen = if ($startMarker -ge 0 -and $endMarker -gt $startMarker) { $literalLen = if ($startMarker -ge 0 -and $endMarker -gt $startMarker) { $endMarker - ($startMarker + 8) } else { -1 }
$endMarker - ($startMarker + 8)
} else { -1 }
# Effective MSVC raw-string limit for the lualoader prefix is 16379 bytes. # Effective MSVC raw-string limit for the lualoader prefix is 16379 bytes.
if ($literalLen -gt 16379) { if ($literalLen -gt 16379) {
Write-Host "isoffi.lua raw string is $literalLen bytes (>16379); trimming for MSVC C2026 limit." 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 += $line
} }
($newLines -join "`n") | Out-File -LiteralPath $path_isoffi -Encoding utf8 -NoNewline ($newLines -join "`n") | Out-File -LiteralPath $path_isoffi -Encoding utf8 -NoNewline
$newLen = ((Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8) ` $newLen = ((Get-Content -LiteralPath $path_isoffi -Raw -Encoding utf8) -replace '.*R"EOF\(--', '' -replace '-- \)EOF".*', '').Length
-replace '.*R"EOF\(--', '' -replace '-- \)EOF".*', '').Length
Write-Host "isoffi.lua trimmed: $literalLen -> $newLen bytes of raw string content." 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' $path_openbios = join-path $path_pcsx_redux 'src\mips\openbios'
# Wipe stale *.dep files across src\mips. These cache absolute paths to the # Wipe stale *.dep files across src\mips.
# GCC headers directory; if the toolchain was upgraded (e.g. v14.2.0 → v16.1.0) # 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 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 # `make clean` in openbios only clears its own dir — subdirs like common/crt0/, modplayer/, and shell/ keep their stale .dep files.
# common/crt0/, modplayer/, and shell/ keep their stale .dep files. Easier to # Easier to just delete the lot before each build than to teach every Makefile about deepclean recursion.
# 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 | Get-ChildItem -Path (join-path $path_pcsx_redux 'src\mips') -Recurse -Filter '*.dep' -ErrorAction SilentlyContinue |
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force } ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force }