mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
Reviewing. Successfuly reworked register allocation for tape runs.
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+127
-89
@@ -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
|
||||||
@@ -109,7 +122,8 @@ typedef U4 const MipsCode; // Underlying type to mips asm words.
|
|||||||
typedef Slice_(MipsCode);
|
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) {
|
||||||
|
|||||||
@@ -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
@@ -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) */
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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 }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user