WIP: preparing for major changes to atoms to fullfill needs of resolve_look_at and atom ported normalize_v3s4.

This commit is contained in:
ed
2026-08-09 18:49:59 -04:00
parent 69f2c0d036
commit e42c75a26a
14 changed files with 625 additions and 97 deletions
+3
View File
@@ -169,6 +169,8 @@ def_signed_ops(le, <=)
#undef def_signed_ops #undef def_signed_ops
#undef def_signed_op #undef def_signed_op
// Unused, we arent' doing any C-like asm since we have the asm dsl. We'll keep the non-generics if we somehow do.
#if 0
#define def_generic_sop(op, a, ...) _Generic((a), U1: op ## _s1, U2: op ## _s2, U4: op ## _s4) (a, __VA_ARGS__) #define def_generic_sop(op, a, ...) _Generic((a), U1: op ## _s1, U2: op ## _s2, U4: op ## _s4) (a, __VA_ARGS__)
#define add_s(a,b) def_generic_sop(add,a,b) #define add_s(a,b) def_generic_sop(add,a,b)
#define sub_s(a,b) def_generic_sop(sub,a,b) #define sub_s(a,b) def_generic_sop(sub,a,b)
@@ -178,6 +180,7 @@ def_signed_ops(le, <=)
#define ge_s(a,b) def_generic_sop(ge, a,b) #define ge_s(a,b) def_generic_sop(ge, a,b)
#define le_s(a,b) def_generic_sop(le, a,b) #define le_s(a,b) def_generic_sop(le, a,b)
#undef def_generic_sop #undef def_generic_sop
#endif
#define alignas _Alignas #define alignas _Alignas
#define alignof _Alignof #define alignof _Alignof
+80
View File
@@ -147,6 +147,86 @@ WORD_COUNT(mac_gte_store_g4_p012, 3)
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3))
WORD_COUNT(mac_gte_store_g4_p3, 1) WORD_COUNT(mac_gte_store_g4_p3, 1)
/* atom_dbg_skip */
#define mac_gte_sqr_v3(r_sx, r_sy, r_sz, r_sq_x, r_sq_y, r_sq_z) \
gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \
, nop \
, gte_cmdw_sqr \
, gte_mv_from_data_r(r_sq_x, C2_MAC1) \
, gte_mv_from_data_r(r_sq_y, C2_MAC2) \
, gte_mv_from_data_r(r_sq_z, C2_MAC3)
WORD_COUNT(mac_gte_sqr_v3, 8)
/* atom_dbg_skip */
#define mac_gte_gpf_scale(r_sx, r_sy, r_sz, r_recip_est, r_shift, r_dx, r_dy, r_dz) \
gte_mv_to_data_r(r_recip_est, C2_IR0) \
, gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \
, nop2 /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */ \
, gte_cmdw_gpf \
, gte_mv_from_data_r(r_dx, C2_MAC1) \
, gte_mv_from_data_r(r_dy, C2_MAC2) \
, gte_mv_from_data_r(r_dz, C2_MAC3) \
, shift_aright_var(r_dx, r_dx, r_shift) \
, shift_aright_var(r_dy, r_dy, r_shift) \
, shift_aright_var(r_dz, r_dz, r_shift)
WORD_COUNT(mac_gte_gpf_scale, 13)
/* atom_dbg_skip */
#define mac_normalize_v3s4(r_sx, r_sy, r_sz, r_sq_y, r_sq_z, r_recip_est, r_lzcr, r_shift, r_tmp) \
gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \
, nop \
, gte_cmdw_sqr /* ─── Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS ─── // Note: r_recip_est first used as the sum accumulator (= |v|²), which is also what LZCS needs. */ \
, gte_mv_from_data_r(r_sq_y, C2_MAC1) /* r_sq_y = MAC1 = sx² */ \
, gte_mv_from_data_r(r_sq_z, C2_MAC2) /* r_sq_z = MAC2 = sy² */ \
, gte_mv_from_data_r(r_recip_est, C2_MAC3) /* r_recip_est = MAC3 = sz² */ \
, nop /* MFC2→GPR load delay (1 slot) */ \
, add_u(r_recip_est, r_recip_est, r_sq_z) /* r_recip_est += sy² */ \
, add_u(r_recip_est, r_recip_est, r_sq_y) /* r_recip_est += sx² (sum = |v|²) */ \
, gte_mv_to_data_r( r_recip_est, C2_LZCS) /* LZCS = |v|² */ \
, nop2 \
, gte_mv_from_data_r(r_lzcr, C2_LZCR) /* r_lzcr = LZCR (count of leading bits) */ \
, nop /* MFC2→GPR load delay (1 slot) */ /* ─── Stage 3: compute shift amount, align |v|² to bit 24, lookup 1/|v| ─── // Matches libgte `bltz +0x10 ; nop ; b +0x14 ; sllv t4,v0,t3` pattern: // - bltz TAKEN → nop (BD), jump to srav_path; sllv SKIPPED // - bltz !TAKEN → nop (BD), b +0x14 jumps to aligned_done; sllv (BD of b) executes */ \
, and_i( r_lzcr, r_lzcr, -2) /* r_lzcr &= ~1 (force even for halving) */ \
, li_s( r_shift, 31) /* r_shift = 31 */ \
, sub_s( r_shift, r_shift, r_lzcr) /* r_shift = 31 - LZCR */ \
, shift_aright( r_shift, r_shift, 1) /* r_shift = (31 - LZCR) / 2 */ \
, add_si( r_tmp, r_lzcr, -24) /* r_tmp = LZCR - 24 (signed, for branch) */ \
, branch_lt_zero(r_tmp, atom_offset(srav_path, aligned_done)) \
, nop \
, jump_rel( atom_offset(aligned_done, srav_path)) \
, shift_lleft_var(r_recip_est, r_recip_est, r_tmp) /* BD-slot of branch_equal: r_recip_est = |v|² << (LZCR - 24) */ \
, atom_label(srav_path) /* SRAV path: |v|² is small (top bit < bit 24) */ \
, li_s( r_tmp, 24) \
, sub_s( r_tmp, r_tmp, r_lzcr) /* r_tmp = 24 - LZCR */ \
, shift_aright_var(r_recip_est, r_recip_est, r_tmp) /* r_recip_est = |v|² >> (24 - LZCR) */ \
, atom_label(aligned_done) /* Both paths converge here with |v|² aligned to bit 24 */ /* r_recip_est now holds |v|² aligned to bit 24 — convert to byte offset, -64 to skip zero pad. */ \
, add_si( r_recip_est, r_recip_est, -64) \
, shift_lleft( r_recip_est, r_recip_est, 1) /* r_recip_est *= 2 (half-word index) */ /* Reference OUR local sqrtbl via &-address split. Compiler/linker resolves both halves. */ \
, load_upper_i( r_tmp, u4_hi(& gte_normalize_sqr_tbl)) /* lui */ \
, or_i_self( r_tmp, u4_lo(& gte_normalize_sqr_tbl)) /* ori */ \
, add_u( r_tmp, r_tmp, r_recip_est) /* r_tmp = sqrtbl base + byte offset (matches libgte 0x80016118: addu t5,t5,t4) */ \
, load_half( r_recip_est, r_tmp, 0) /* r_recip_est = sqrtbl[r_recip_est] = 1/|v| estimate */ \
, nop /* retire load_half before MTC2 (matches libgte 0x80016120: nop) */ /* ─── Stage 4: mtc2 IR0..3, GPF (MAC = IR0*IR), mfc2 MAC, srav finalize ─── // Componentized equivalent: mac_gte_gpf_scale. */ \
, gte_mv_to_data_r(r_recip_est, C2_IR0) /* IR0 = 1/|v| estimate */ \
, gte_mv_to_data_r(r_sx, C2_IR1) /* IR1 = src.x */ \
, gte_mv_to_data_r(r_sy, C2_IR2) /* IR2 = src.y */ \
, gte_mv_to_data_r(r_sz, C2_IR3) /* IR3 = src.z */ \
, nop2 /* COP2 transfer latency (2 slots) */ \
, gte_cmdw_gpf \
, gte_mv_from_data_r(r_sx, C2_MAC1) /* MAC1 → r_sx (overwrites src.x with raw reciprocal-scaled) */ \
, gte_mv_from_data_r(r_sy, C2_MAC2) \
, gte_mv_from_data_r(r_sz, C2_MAC3) \
, shift_aright_var(r_sx, r_sx, r_shift) \
, shift_aright_var(r_sy, r_sy, r_shift) \
, shift_aright_var(r_sz, r_sz, r_shift)
WORD_COUNT(mac_normalize_v3s4, 48)
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \ #define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
load_upper_i(reg_transfer, cmd >> 16) \ load_upper_i(reg_transfer, cmd >> 16) \
, or_i_self( reg_transfer, cmd & 0xFFFF) \ , or_i_self( reg_transfer, cmd & 0xFFFF) \
+10
View File
@@ -25,6 +25,16 @@
#pragma region duffle #pragma region duffle
// --- atom: ac_normalize_v3s4 (48 words) ---
#define _atom_offset_srav_path_aligned_done 6
#define _atom_offset_aligned_done_srav_path 1
enum {
atom_offset_srav_path_aligned_done = _atom_offset_srav_path_aligned_done,
atom_offset_aligned_done_srav_path = _atom_offset_aligned_done_srav_path,
};
// --- atom: pad_bios_snapshot (84 words) --- // --- atom: pad_bios_snapshot (84 words) ---
#define _atom_offset_snap_root_skip_disconnected 10 #define _atom_offset_snap_root_skip_disconnected 10
-6
View File
@@ -21,8 +21,6 @@ FI_ Slice_MipsCode ac_store_rgb8(U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_d
store_byte(rb, base, offset + O_(RGB8,b)), store_byte(rb, base, offset + O_(RGB8,b)),
}) })
/* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given
* byte offset. Internal helper used by the *_format_*_color macros. */
FI_ Slice_MipsCode ac_pack_color_word(U4 r_base, U4 off, U4 cmd, U1 r, U1 g, U1 b) FI_ Slice_MipsCode ac_pack_color_word(U4 r_base, U4 off, U4 cmd, U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, { atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
load_upper_i(R_AT, (cmd) << 8 | (b)), load_upper_i(R_AT, (cmd) << 8 | (b)),
@@ -30,13 +28,9 @@ atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
store_word( R_AT, r_base, (off)), store_word( R_AT, r_base, (off)),
}) })
/* Words: 3; Emits the F3 command+color word (cmd byte | BLUE | GREEN | RED)
* Args: _r, _g, _b are 8-bit RGB byte values (not raw 16-bit fields). */
FI_ Slice_MipsCode ac_format_f3_color(U4 r_base, U1 r, U1 g, U1 b) FI_ Slice_MipsCode ac_format_f3_color(U4 r_base, U1 r, U1 g, U1 b)
atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) }) atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
/* Words: 12; Emits the four (code|color) words of a Poly_G4.
* Args: rN,gN,bN are 8-bit RGB byte values for each of the 4 vertices. */
FI_ Slice_MipsCode ac_format_g4_color(U4 r_prim_cursor, FI_ Slice_MipsCode ac_format_g4_color(U4 r_prim_cursor,
U1 r0, U1 g0, U1 b0, U1 r0, U1 g0, U1 b0,
U1 r1, U1 g1, U1 b1, U1 r1, U1 g1, U1 b1,
+208
View File
@@ -49,6 +49,214 @@ FI_ Slice_MipsCode ac_gte_store_g4_p012(U4 r_primitive_cursor) atom_dbg_skip Mip
*/ */
FI_ Slice_MipsCode ac_gte_store_g4_p3(U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p3, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) }) FI_ Slice_MipsCode ac_gte_store_g4_p3(U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p3, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) })
/* ─── STAGE 1 of normalize: SQR + mfc2 MAC1/2/3 ───
* Emits squared magnitude per component (in MAC1/2/3) into caller-provided scratch regs.
* Stage 2 of normalize consumes these directly.
* Words: 8. Clobbers: IR1/2/3, MAC1/2/3. Uses gte_cmdw_sqr (sf=0, lm=1). */
FI_ Slice_MipsCode ac_gte_sqr_v3(U4 r_sx, U4 r_sy, U4 r_sz, U4 r_sq_x, U4 r_sq_y, U4 r_sq_z) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_sqr_v3, {
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3),
nop, gte_cmdw_sqr,
gte_mv_from_data_r(r_sq_x, C2_MAC1),
gte_mv_from_data_r(r_sq_y, C2_MAC2),
gte_mv_from_data_r(r_sq_z, C2_MAC3),
})
/* ─── STAGE 4 of normalize: mtc2 IR0..3 + GPF + mfc2 MAC + srav finalize ───
* Reusable standalone — given an IR0 = 1/|v| estimate (typically from a sqrtbl lookup) and a shift count
* (typically (31 - LZCR)/2), multiplies IR0*IR[i] via GPF and shifts right to produce the normalized output.
* Used standalone for "scale vector by scalar".
* Words: 11. Clobbers: IR0..3, MAC1..3. Uses gte_cmdw_gpf (sf=0, lm=0). */
FI_ Slice_MipsCode ac_gte_gpf_scale(U4 r_sx, U4 r_sy, U4 r_sz, U4 r_recip_est, U4 r_shift, U4 r_dx, U4 r_dy, U4 r_dz) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_gpf_scale, {
gte_mv_to_data_r(r_recip_est, C2_IR0),
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3),
nop2, /* retire IR0..IR3 → GPF input pre-fill (matches libgte 0x80016134..0x80016138) */
gte_cmdw_gpf,
gte_mv_from_data_r(r_dx, C2_MAC1),
gte_mv_from_data_r(r_dy, C2_MAC2),
gte_mv_from_data_r(r_dz, C2_MAC3),
shift_aright_var(r_dx, r_dx, r_shift),
shift_aright_var(r_dy, r_dy, r_shift),
shift_aright_var(r_dz, r_dz, r_shift),
})
/* ─── Local copy of PSYQ's sqrtbl (1/sqrt lookup table for VectorNormal). ───
* Source: PSYQ 4.7 libgte sqrtbl at 0x800185B4 in hello_camera.elf.
* objdump -s --start-address=0x800185B4 --stop-address=0x800185F4 hello_camera.elf
* → 192 entries × 16-bit signed, in 1.12 fixed-point (max value 0x1000 = 1.0).
*
* Data is identical to the libgte original (byte-for-byte verified).
*
* ─── Per-entry semantics (decoded from libgte msc02 VectorNormal) ───
* Each entry is `1/sqrt(x)` in 1.12 fixed point (value / 4096).
* The 192 entries span 4 octaves of the input magnitude, with 48 entries per octave:
* Octave 0 (entries 0- 47): mantissa in [0x8000, 0x10000) output ~[1.000, 0.707]
* Octave 1 (entries 48- 95): mantissa in [0x10000, 0x20000) output ~[0.707, 0.500]
* Octave 2 (entries 96-143): mantissa in [0x20000, 0x40000) output ~[0.500, 0.354]
* Octave 3 (entries144-191): mantissa in [0x40000, 0x80000) output ~[0.354, 0.251]
* Within each octave, 8 sub-entries interpolate over the 8 fractional bits of the
* mantissa (the byte `(0x80 | (i mod 8))` for the lower-byte of the aligned value).
* Sampling the first value of each octave:
* [0] 0x1000 = 1.0000 ; 1 / sqrt(1.0000)
* [48] 0x0e4f = 0.8940 ; 1 / sqrt(1.2500)
* [96] 0x0d10 = 0.8164 ; 1 / sqrt(1.5000)
* [144] 0x0c0a = 0.7520 ; 1 / sqrt(1.7500)
* And representative sub-entries within octave 0 (mantissa in [0x8000, 0x8100)):
* [0] 0x1000 = 1.0000 ; 1 / sqrt(0x8000)
* [1] 0x0fe0 = 0.9922 ; 1 / sqrt(0x8100)
* [2] 0x0fc1 = 0.9846 ; 1 / sqrt(0x8200)
* [3] 0x0fa3 = 0.9773 ; 1 / sqrt(0x8300)
* [4] 0x0f85 = 0.9700 ; 1 / sqrt(0x8400)
* [5] 0x0f68 = 0.9629 ; 1 / sqrt(0x8500)
* [6] 0x0f4c = 0.9561 ; 1 / sqrt(0x8600)
* [7] 0x0f30 = 0.9492 ; 1 / sqrt(0x8700)
*
* The algorithm's `addi -64 / sll 1 / lh` selects the entry at `(aligned - 64) * 2` for the case where `aligned` has its top bit at bit 24.
* After the sllv/srav pair, `aligned` always lands in `[0x80, 0x100)`
* (with top bit at bit 24 → after `sub $aligned - 64`, the index sits in `[0x40, 0x80) * 2 = [0x80, 0x100)` bytes = entries [64, 128) within the sqrtbl).
* The earlier 64 entries (octave 0) are reached when the magnitude after shifting puts the top bit below bit 24 (the `sllv` branch),
* and the load upper_halves of the table bracket the input range.
* The later 64 entries (octaves 2-3) are the `srav` branch when the magnitude's top bit is well above bit 24.
*
* 192-entry table is reproduced verbatim from libgte (verified against libpsn00b/psxgte/vector.s:100-123 — 24 rows × 8 halfwords, last entry 0x0804). */
internal S2 const gte_normalize_sqr_tbl[192] align_(2) = {
0x1000, 0x0fe0, 0x0fc1, 0x0fa3, 0x0f85, 0x0f68, 0x0f4c, 0x0f30,
0x0f15, 0x0efb, 0x0ee1, 0x0ec7, 0x0eae, 0x0e96, 0x0e7e, 0x0e66,
0x0e4f, 0x0e38, 0x0e22, 0x0e0c, 0x0df7, 0x0de2, 0x0dcd, 0x0db9,
0x0da5, 0x0d91, 0x0d7e, 0x0d6b, 0x0d58, 0x0d45, 0x0d33, 0x0d21,
0x0d10, 0x0cff, 0x0cee, 0x0cdd, 0x0ccc, 0x0cbc, 0x0cac, 0x0c9c,
0x0c8d, 0x0c7d, 0x0c6e, 0x0c5f, 0x0c51, 0x0c42, 0x0c34, 0x0c26,
0x0c18, 0x0c0a, 0x0bfd, 0x0bef, 0x0be2, 0x0bd5, 0x0bc8, 0x0bbb,
0x0baf, 0x0ba2, 0x0b96, 0x0b8a, 0x0b7e, 0x0b72, 0x0b67, 0x0b5b,
0x0b50, 0x0b45, 0x0b39, 0x0b2e, 0x0b24, 0x0b19, 0x0b0e, 0x0b04,
0x0af9, 0x0aef, 0x0ae5, 0x0adb, 0x0ad1, 0x0ac7, 0x0abd, 0x0ab4,
0x0aaa, 0x0aa1, 0x0a97, 0x0a8e, 0x0a85, 0x0a7c, 0x0a73, 0x0a6a,
0x0a61, 0x0a59, 0x0a50, 0x0a47, 0x0a3f, 0x0a37, 0x0a2e, 0x0a26,
0x0a1e, 0x0a16, 0x0a0e, 0x0a06, 0x09fe, 0x09f6, 0x09ef, 0x09e7,
0x09e0, 0x09d8, 0x09d1, 0x09c9, 0x09c2, 0x09bb, 0x09b4, 0x09ad,
0x09a5, 0x099e, 0x0998, 0x0991, 0x098a, 0x0983, 0x097c, 0x0976,
0x096f, 0x0969, 0x0962, 0x095c, 0x0955, 0x094f, 0x0949, 0x0943,
0x093c, 0x0936, 0x0930, 0x092a, 0x0924, 0x091e, 0x0918, 0x0912,
0x090d, 0x0907, 0x0901, 0x08fb, 0x08f6, 0x08f0, 0x08eb, 0x08e5,
0x08e0, 0x08da, 0x08d5, 0x08cf, 0x08ca, 0x08c5, 0x08bf, 0x08ba,
0x08b5, 0x08b0, 0x08ab, 0x08a6, 0x08a1, 0x089c, 0x0897, 0x0892,
0x088d, 0x0888, 0x0883, 0x087e, 0x087a, 0x0875, 0x0870, 0x086b,
0x0867, 0x0862, 0x085e, 0x0859, 0x0855, 0x0850, 0x084c, 0x0847,
0x0843, 0x083e, 0x083a, 0x0836, 0x0831, 0x082d, 0x0829, 0x0824,
0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804,
};
/* ─── Full normalize (all 4 stages inline) ───
* Direct port of PSYQ libgte msc02.rel.text VectorNormal disassembly (0x800160a0..0x8001615c).
*
* Component variants that could apply:
* - `ac_gte_sqr_v3` (line ~56) covers stage 1's `mtc2 IR1/2/3 + nop + gte_cmdw_sqr`.
* We do NOT call it because the inlined version of stage 1 is followed immediately by stage 2's `mfc2 MAC1/2/3` chain
* (the operands of `ac_gte_sqr_v3`'s r_sq_x/r_sq_y/r_sq_z would each require an explicit GPR to receive the MAC result,
* then a move to land in r_recip_est for the partial-sum chain).
* Inlining saves ~3 cycles of `or`-merge + register pressure
* (squared MAC3 lands DIRECTLY in r_recip_est which doubles as the partial-sum accumulator and the LZCS input — see r_recip_est row below).
* - `ac_gte_gpf_scale` (line ~71) covers stage 4's `mtc2 IR0..3 + nop2 + gte_cmdw_gpf + mfc2 MAC1/2/3 + sra`.
* We do NOT call it for the symmetric reason: the normalize in-place semantics overwrite the input regs (r_sx/r_sy/r_sz) with the normalized output,
* which `ac_gte_gpf_scale`'s r_dx/r_dy/r_dz output GPRs would not match.
* `gte_cmdw_sqr` and `gte_cmdw_gpf` primitive macros ARE used in the inlined body, so changes to those primitives
* (e.g., the libgte `fake_cmd` signature bits) propagate automatically. The components remain available for callers that want the explicit GPR-shape variants.
*
* Argument aliasing (9 unique physical regs needed, can drop to 8 with r_sq_y ≡ r_lzcr):
* r_sx, r_sy, r_sz : src components in regs (clobbered by mtc2 → IR1/2/3 in stage 1, then by mfc2 MAC1/2/3 in stage 4 — in-place semantics)
* r_sq_y, r_sq_z : MAC2, MAC3 → DIE after stage 2 accumulate (r_sq_y can alias r_lzcr after stage 2 to save one reg)
* r_recip_est : ≡ r_sqmag — multi-purpose (holds |v|² in stage 2, shift-input in stage 3, sqrtbl[index] in stage 4)
* r_lzcr : LZCR value, alive across stage 3 (srav path needs `24 - LZCR`)
* r_shift : (31 - LZCR & ~1) >> 1 — final srav amount (stages 3-4)
* r_tmp : scratch (shift count, branch target, lookup addr, table base)
*
* GPR ccount peak: 9.
* Pipeline: clobbers IR0..3, MAC1..3, LZCS, LZCR.
* Words: ~35 (pending re-gen; matches libgte 0x800160a0..0x8001615c at +/- 0-2 words for BD-slot reshuffling).
* Sqrtbl: hardcoded to 0x800185B4 (libgte msc02.rel.data). Note: swapped to local. */
I_ Slice_MipsCode ac_normalize_v3s4(U4 r_sx, U4 r_sy, U4 r_sz, U4 r_sq_y, U4 r_sq_z, U4 r_recip_est, U4 r_lzcr, U4 r_shift, U4 r_tmp)
atom_dbg_skip MipsAtomComp_Proc_(ac_normalize_v3s4, {
/* 9-arg signature — must be on one line so the metaprogram captures the full arg list.
* r_sx, r_sy, r_sz : in/out — src components, overwritten with normalized
* r_sq_y, r_sq_z : scratch — MAC2, MAC3 → die after stage 2 accumulate (r_sq_y may alias r_lzcr post-stage-2)
* r_recip_est : ≡ r_sqmag — multi-purpose (|v|² → shift-input → sqrtbl entry)
* r_lzcr : LZCR value (alive across stage 3 srav path)
* r_shift : (31 - LZCR & ~1) / 2 — final srav amount (stages 3-4)
* r_tmp : scratch — shift count, branch target, lookup addr, table base
*
* GPR ccount peak: 9.
* Pipeline: clobbers IR0..3, MAC1..3, LZCS, LZCR.
* Words: ~35 (pending re-gen; matches libgte 0x800160a0..0x8001615c at +/- 0-2 words).
*
* Sqrtbl address: link-time constant `&gte_normalize_sqrtbl`, split via >>16 and &0xFFFF. */
// ─── Stage 1: mtc2 src → IR1/2/3, SQR fires (MAC1/2/3 = IR², IR ← MAC saturated) ───
// Componentized equivalent: mac_gte_sqr_v3(r_sx, r_sy, r_sz, r_sq_x, r_sq_y, r_sq_z).
// We inline for GPR-pressure reasons (see file-level comment).
gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3),
nop, gte_cmdw_sqr,
// ─── Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS ───
// Note: r_recip_est first used as the sum accumulator (= |v|²), which is also what LZCS needs.
gte_mv_from_data_r(r_sq_y, C2_MAC1), /* r_sq_y = MAC1 = sx² */
gte_mv_from_data_r(r_sq_z, C2_MAC2), /* r_sq_z = MAC2 = sy² */
gte_mv_from_data_r(r_recip_est, C2_MAC3), /* r_recip_est = MAC3 = sz² */
nop, /* MFC2→GPR load delay (1 slot) */
add_u(r_recip_est, r_recip_est, r_sq_z), /* r_recip_est += sy² */
add_u(r_recip_est, r_recip_est, r_sq_y), /* r_recip_est += sx² (sum = |v|²) */
gte_mv_to_data_r( r_recip_est, C2_LZCS), /* LZCS = |v|² */
nop2,
gte_mv_from_data_r(r_lzcr, C2_LZCR), /* r_lzcr = LZCR (count of leading bits) */
nop, /* MFC2→GPR load delay (1 slot) */
// ─── Stage 3: compute shift amount, align |v|² to bit 24, lookup 1/|v| ───
// Matches libgte `bltz +0x10 ; nop ; b +0x14 ; sllv t4,v0,t3` pattern:
// - bltz TAKEN → nop (BD), jump to srav_path; sllv SKIPPED
// - bltz !TAKEN → nop (BD), b +0x14 jumps to aligned_done; sllv (BD of b) executes
and_i( r_lzcr, r_lzcr, -2), /* r_lzcr &= ~1 (force even for halving) */
li_s( r_shift, 31), /* r_shift = 31 */
sub_s( r_shift, r_shift, r_lzcr), /* r_shift = 31 - LZCR */
shift_aright( r_shift, r_shift, 1), /* r_shift = (31 - LZCR) / 2 */
add_si( r_tmp, r_lzcr, -24), /* r_tmp = LZCR - 24 (signed, for branch) */
branch_lt_zero(r_tmp, atom_offset(srav_path, aligned_done)), nop,
jump_rel( atom_offset(aligned_done, srav_path)),
shift_lleft_var(r_recip_est, r_recip_est, r_tmp), /* BD-slot of branch_equal: r_recip_est = |v|² << (LZCR - 24) */
atom_label(srav_path) /* SRAV path: |v|² is small (top bit < bit 24) */
li_s( r_tmp, 24),
sub_s( r_tmp, r_tmp, r_lzcr), /* r_tmp = 24 - LZCR */
shift_aright_var(r_recip_est, r_recip_est, r_tmp), /* r_recip_est = |v|² >> (24 - LZCR) */
atom_label(aligned_done) /* Both paths converge here with |v|² aligned to bit 24 */
/* r_recip_est now holds |v|² aligned to bit 24 — convert to byte offset, -64 to skip zero pad. */
add_si( r_recip_est, r_recip_est, -64),
shift_lleft( r_recip_est, r_recip_est, 1), /* r_recip_est *= 2 (half-word index) */
/* Reference OUR local sqrtbl via &-address split. Compiler/linker resolves both halves. */
load_upper_i( r_tmp, u4_hi(& gte_normalize_sqr_tbl)), /* lui */
or_i_self( r_tmp, u4_lo(& gte_normalize_sqr_tbl)), /* ori */
add_u( r_tmp, r_tmp, r_recip_est), /* r_tmp = sqrtbl base + byte offset (matches libgte 0x80016118: addu t5,t5,t4) */
load_half( r_recip_est, r_tmp, 0), /* r_recip_est = sqrtbl[r_recip_est] = 1/|v| estimate */
nop, /* retire load_half before MTC2 (matches libgte 0x80016120: nop) */
// ─── Stage 4: mtc2 IR0..3, GPF (MAC = IR0*IR), mfc2 MAC, srav finalize ───
// Componentized equivalent: mac_gte_gpf_scale.
gte_mv_to_data_r(r_recip_est, C2_IR0), /* IR0 = 1/|v| estimate */
gte_mv_to_data_r(r_sx, C2_IR1), /* IR1 = src.x */
gte_mv_to_data_r(r_sy, C2_IR2), /* IR2 = src.y */
gte_mv_to_data_r(r_sz, C2_IR3), /* IR3 = src.z */
nop2, /* COP2 transfer latency (2 slots) */
gte_cmdw_gpf,
gte_mv_from_data_r(r_sx, C2_MAC1), /* MAC1 → r_sx (overwrites src.x with raw reciprocal-scaled) */
gte_mv_from_data_r(r_sy, C2_MAC2),
gte_mv_from_data_r(r_sz, C2_MAC3),
shift_aright_var(r_sx, r_sx, r_shift),
shift_aright_var(r_sy, r_sy, r_shift),
shift_aright_var(r_sz, r_sz, r_shift),
})
#pragma endregion MACs (Mips Atom Components) #pragma endregion MACs (Mips Atom Components)
#pragma region Bsked Atoms #pragma region Bsked Atoms
+55 -18
View File
@@ -161,6 +161,8 @@ enum {
gte_cmd_nclip = 0x06, /* Normal Clipping (Backface culling) */ gte_cmd_nclip = 0x06, /* Normal Clipping (Backface culling) */
gte_cmd_op = 0x0C, /* Outer Product */ gte_cmd_op = 0x0C, /* Outer Product */
gte_cmd_mvmva = 0x12, /* Matrix Vector Multiply & Add (Custom math) */ gte_cmd_mvmva = 0x12, /* Matrix Vector Multiply & Add (Custom math) */
gte_cmd_sqr = 0x28, /* Square vector — MAC[i] = IR[i]²; IR[i] ← MAC[i] saturated */
gte_cmd_gpf = 0x3D, /* General-purpose Interpolation — MAC[i] = IR0 * IR[i] */
/* --- GTE Command Bit-Field Layout --- /* --- GTE Command Bit-Field Layout ---
* A GTE command word (sent to COP2 with RS=1) is laid out as: * A GTE command word (sent to COP2 with RS=1) is laid out as:
@@ -171,17 +173,22 @@ enum {
* +------------+--+-----+------+------+------+------+---+--------+----------+ * +------------+--+-----+------+------+------+------+---+--------+----------+
* \_____ GTE_PAYLOAD _____/ \__ GTE_CMD __/ * \_____ GTE_PAYLOAD _____/ \__ GTE_CMD __/
* *
* Shifts/masks below are the *bit positions* and *bit widths* of each * Shifts/masks below are the *bit positions* and *bit widths* of each configurable field, used by the ENC_GTE_CMD encoder.
* configurable field, used by the ENC_GTE_CMD encoder.
* Mirrors the OPCODE_SHIFT / RS_SHIFT convention used in mips.h. * Mirrors the OPCODE_SHIFT / RS_SHIFT convention used in mips.h.
*/ */
gte_shift_sf = 19, gte_width_sf = 1, gte_mask_sf = 0x1, gte_shift_sf = 19, gte_width_sf = 1, gte_mask_sf = 0x1,
gte_shift_mx = 17, gte_width_mx = 2, gte_mask_mx = 0x3, gte_shift_mx = 17, gte_width_mx = 2, gte_mask_mx = 0x3,
gte_shift_v = 15, gte_width_v = 2, gte_mask_v = 0x3, gte_shift_v = 15, gte_width_v = 2, gte_mask_v = 0x3,
gte_shift_cv = 13, gte_width_cv = 2, gte_mask_cv = 0x3, gte_shift_cv = 13, gte_width_cv = 2, gte_mask_cv = 0x3,
gte_shift_lm = 10, gte_width_lm = 1, gte_mask_lm = 0x1, gte_shift_lm = 10, gte_width_lm = 1, gte_mask_lm = 0x1,
gte_shift_cmd = 0, gte_width_cmd = 6, gte_mask_cmd = 0x3F, gte_shift_cmd = 0, gte_width_cmd = 6, gte_mask_cmd = 0x3F,
/* Fake command number (bits 24-20) — IGNORED by the GTE hardware per PSX-SPX `geometrytransformationenginegte.md` line 48.
* libgte's compiler emits non-zero values in this field as a disassembly signature. */
gte_shift_fake_cmd = 20,
gte_width_fake_cmd = 5,
gte_mask_fake_cmd = 0x1F,
}; };
/* --- GTE Control Register Indices (for ctc2/cfc2) --- /* --- GTE Control Register Indices (for ctc2/cfc2) ---
@@ -243,10 +250,10 @@ enum { _C2_OPS_ = 0
* bit 1 (0x02): register class — 0 = data, 1 = control * bit 1 (0x02): register class — 0 = data, 1 = control
* bit 2 (0x04): direction — 0 = read, 1 = write * bit 2 (0x04): direction — 0 = read, 1 = write
* *
* The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit numbers as the general MIPS `cop_mf` / `cop_mt` defined in mips.h * The values 0x00 (sub_mfc2) and 0x04 (sub_mtc2) are the same 5-bit numbers as general MIPS `cop_mf` / `cop_mt` defined in mips.h
* (which target the data register file on any coprocessor). * (which target the data register file on any coprocessor).
* They are re-aliased here so the four-way table reads like the spec mnemonics (MFC2 / CFC2 / MTC2 / CTC2) * They are re-aliased here so the four-way table reads like the spec mnemonics (MFC2 / CFC2 / MTC2 / CTC2)
* and so the encoding lives next to its only consumer (this header). * and so the encoding is next to its only consumer (this header).
* *
* Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) live in gte_vendor_sym.h. */ * Vendor mnemonic aliases (gte_mfc2 / gte_mtc2 / gte_cfc2 / gte_ctc2) live in gte_vendor_sym.h. */
enum { _C2_TX_SUBS_ = 0 enum { _C2_TX_SUBS_ = 0
@@ -309,23 +316,24 @@ enum { _C2_TX_SUBS_ = 0
/* GTE Command Format /* GTE Command Format
* Opcode is always MIPS_OP_COP2, RS is always 1 (CO). * Opcode is always MIPS_OP_COP2, RS is always 1 (CO).
* The lower 25 bits are the GTE-specific command payload. * Lower 25 bits are GTE-specific command payload.
* *
* The granular `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` pattern in mips.h: * The `enc_gte_<field>(x)` macros below mirror the `enc_op`/`enc_rs` pattern in mips.h:
* Each one self-masks and shifts its own field, so a caller can build up a GTE command piece by piece * Each one self-masks and shifts its own field, so a caller can build up a GTE command piece by piece
* (handy for state-driven MVMVA emitters that vary one field at a time). * (handy for state-driven MVMVA emitters that vary one field at a time).
* *
* `ENC_GTE_CMD` is the all-in-one convenience for emitting a full command word in one go. * `ENC_GTE_CMD` is an all-in-one convenience for emitting a full command word.
* It just ORs the per-field encoders together. */ * It just ORs the per-field encoders together. */
#define gte_cmd_base (enc_op(op_cop2) | (1 << 25)) #define gte_cmd_base (enc_op(op_cop2) | (1 << 25))
/* Per-field encoders. Each one does (value & mask) << shift on its own. */ /* Per-field encoders. Each one does (value & mask) << shift on its own. */
#define enc_gte_sf(sf) (((sf) & gte_mask_sf ) << gte_shift_sf ) #define enc_gte_sf(sf) (((sf) & gte_mask_sf ) << gte_shift_sf )
#define enc_gte_mx(mx) (((mx) & gte_mask_mx ) << gte_shift_mx ) #define enc_gte_mx(mx) (((mx) & gte_mask_mx ) << gte_shift_mx )
#define enc_gte_v(v) (((v) & gte_mask_v ) << gte_shift_v ) #define enc_gte_v(v) (((v) & gte_mask_v ) << gte_shift_v )
#define enc_gte_cv(cv) (((cv) & gte_mask_cv ) << gte_shift_cv ) #define enc_gte_cv(cv) (((cv) & gte_mask_cv ) << gte_shift_cv )
#define enc_gte_lm(lm) (((lm) & gte_mask_lm ) << gte_shift_lm ) #define enc_gte_lm(lm) (((lm) & gte_mask_lm ) << gte_shift_lm )
#define enc_gte_cmd(cmd) (((cmd) & gte_mask_cmd) << gte_shift_cmd) #define enc_gte_cmd(cmd) (((cmd) & gte_mask_cmd ) << gte_shift_cmd )
#define enc_gte_fake_cmd(x) (((x) & gte_mask_fake_cmd) << gte_shift_fake_cmd)
/* Composite: all six GTE fields + the COP2/CO base. */ /* Composite: all six GTE fields + the COP2/CO base. */
#define enc_gte_cmdw(sf, mx, v, cv, lm, cmd) ( \ #define enc_gte_cmdw(sf, mx, v, cv, lm, cmd) ( \
@@ -363,11 +371,11 @@ enum { _C2_TX_SUBS_ = 0
* (the perspective divide happens regardless of `sf`). * (the perspective divide happens regardless of `sf`).
* *
* If we emit a strictly-spec-compliant word (`sf=0`, reserved bits clear), * If we emit a strictly-spec-compliant word (`sf=0`, reserved bits clear),
* PCSX-Redux's GTE checks those bits more strictly than the silicon does and RTPT silently no-ops * PCSX-Redux's GTE checks those bits more strictly than the silicon does and RTPT silently no-ops.
* the floor's screen coordinates come out as raw projection-of-rotation (Z never divided), * The floor's screen coordinates come out as raw projection-of-rotation (Z never divided),
* `nclip` ends up wrong, and the triangle is culled. * `nclip` ends up wrong, and the triangle is culled.
* *
* So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to match the working bit pattern everyone has shipped for 25 years. * So for RTPS and RTPT we OR-in the `0x28` "PsyQ compat" pattern to match the working bit pattern.
* NCLIP / OP / MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source. * NCLIP / OP / MVMVA stay spec-clean — their reserved bits really are zero in the original PsyQ source.
* -------------------------------------------------------------------------- * --------------------------------------------------------------------------
*/ */
@@ -383,6 +391,36 @@ enum { _C2_TX_SUBS_ = 0
* The wedge alias is the 3D complement interpretation of the same 3 scalars (MAC1..MAC3). */ * The wedge alias is the 3D complement interpretation of the same 3 scalars (MAC1..MAC3). */
#define gte_cmdw_mvmva (gte_cmd_base | enc_gte_cmd(gte_cmd_mvmva)) #define gte_cmdw_mvmva (gte_cmd_base | enc_gte_cmd(gte_cmd_mvmva))
/* SQR / GPF cosmetic-bits compat helpers.
* Each command's `_compat` macro ORs in the `fake_cmd` field value libgte happens to emit.
* The hardware ignores these bits (per PSX-SPX line 48). */
#define gte_cmdw_sqr_fake_sig enc_gte_fake_cmd(0x0A)
#define gte_cmdw_gpf_fake_sig enc_gte_fake_cmd(0x19)
/* SQR — Square Vector.
* PSX-SPX `geometrytransformationenginegte.md` §"SQR":
* [MAC1,MAC2,MAC3] = [IR1*IR1, IR2*IR2, IR3*IR3] SHR (sf*12)
* [IR1,IR2,IR3] = [MAC1,MAC2,MAC3] (saturated to 0x7FFF when lm=1)
* Sourced verbatim from libgte msc02 VectorNormal disassembly at 0x800160b0:
* 0x4AA00428 = gte_cmd_base | gte_cmdw_sqr_compat | enc_gte_lm(1) | enc_gte_cmd(0x28)
* bit 19 sf=0
* bit 10 lm=1
* bits 5-0 cmd=0x28=SQR
* bits 24-20 = 0x0A (libgte "nonsense SDK command number" signature) */
#define gte_cmdw_sqr (gte_cmd_base | enc_gte_cmd(gte_cmd_sqr) | enc_gte_lm(1) | gte_cmdw_sqr_fake_sig)
/* GPF — General-purpose Interpolation.
* PSX-SPX `geometrytransformationenginegte.md` §"GPF":
* [MAC1,MAC2,MAC3] = (([IR1,IR2,IR3] * IR0) + [MAC1,MAC2,MAC3]) SAR (sf*12)
* [IR1,IR2,IR3] = [MAC1,MAC2,MAC3]
* Sourced verbatim from libgte msc02 VectorNormal disassembly at 0x8001613c:
* 0x4B90003D = gte_cmd_base | gte_cmdw_gpf_compat | enc_gte_cmd(0x3D)
* bit 19 sf=0
* bit 10 lm=0
* bits 5-0 cmd=0x3D=GPF
* 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_rotate_translate_perspective_single gte_cmdw_rtps #define gte_cmdw_rotate_translate_perspective_single gte_cmdw_rtps
#define gte_cmdw_rotate_translate_perspective_triple gte_cmdw_rtpt #define gte_cmdw_rotate_translate_perspective_triple gte_cmdw_rtpt
/* RGA(Lengyel): RTPS/RTPT consume the matrix expansion of a rigid transformation (rotation matrix + translation vector) loaded into the RT/TR control registers. /* RGA(Lengyel): RTPS/RTPT consume the matrix expansion of a rigid transformation (rotation matrix + translation vector) loaded into the RT/TR control registers.
@@ -437,7 +475,6 @@ enum {
#define gte_lw_v2_z(base) enc_gte_lw(gte_in_v2_z, (base), GTE_Z_Offset) #define gte_lw_v2_z(base) enc_gte_lw(gte_in_v2_z, (base), GTE_Z_Offset)
/* gte_load_vN(r_ptr, base) — placeholder-punned lwc2 loaders /* gte_load_vN(r_ptr, base) — placeholder-punned lwc2 loaders
*
* Emits `.word` constants encoding `lwc2 $N, off(<base>)` for the chosen GTE vector register, where `<base>` is the GPR number you pass in * Emits `.word` constants encoding `lwc2 $N, off(<base>)` for the chosen GTE vector register, where `<base>` is the GPR number you pass in
* (typically one of R_T4..R_T9 for the standard "3-pointer" pattern). * (typically one of R_T4..R_T9 for the standard "3-pointer" pattern).
* *
+54 -13
View File
@@ -106,6 +106,12 @@ typedef Slice_(MipsCode);
typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that must terminate with an ac_yield. typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that must terminate with an ac_yield.
#define MipsAtom_(sym) MipsCode sym [] align_(4) = #define MipsAtom_(sym) MipsCode sym [] align_(4) =
// Used for atoms with value-args
// FI_ void ac_X(args) MipsAtomComp_Proc_(ac_X, { body })
// expands to:
// FI_ void ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return ac_X; }
#define MipsAtom_Proc_(sym, abuilder, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; atombuilder_unroll(abuilder, slice_from_array(MipsCode, sym)); }
// Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names). // Used for components with no args (e.g., ac_load_tri_indices) or identifier-args (hardcoded register names).
// MipsAtomComp_(ac_X) { body } // MipsAtomComp_(ac_X) { body }
// expands to: // expands to:
@@ -118,12 +124,18 @@ typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that
// FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); } // FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); }
#define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); } #define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); }
/* Line-table anchor: gcc only adds a file to the .debug_line file table when the // Used for components with value-args (e.g., ac_format_f3_color).
file contains line-numbered content. Files containing only: // FI_ Slice_MipsCode ac_X(args) MipsAtomComp_Proc_(ac_X, { body })
- `MipsAtomComp_` static-array declarations, or // expands to:
- `MipsAtomComp_Proc_` (force-inline) function bodies whose line info gets // FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); }
attributed to the call site at the include point are otherwise omitted from the file table, // #define MipsAtomComp_Proc_(sym, abuilder, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; atombuilder_unroll(abuilder, slice_from_array(MipsCode, sym)); }
which breaks the DWARF injection when it tries to resolve atom-component provenance paths.
/* Line-table anchor: gcc only adds a file to the .debug_line file table when the contains line-numbered content.
Files containing only:
- `MipsAtomComp_` static-array declarations, or
- `MipsAtomComp_Proc_` (force-inline) function bodies whose line info gets
attributed to the call site at the include point are otherwise omitted from the file table,
which breaks the DWARF injection when it tries to resolve atom-component provenance paths.
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.
The macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table. The macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table.
@@ -181,11 +193,13 @@ FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start
FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; } FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ mem.ptr, mem.len, 0 }; } FI_ TapeBuilder tb_make(Slice mem) { return (TapeBuilder){ mem.ptr, mem.len, 0 }; }
FI_ void tb_emit(TapeBuilder* tb, MipsCode* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; } FI_ void tb_emit(TapeBuilder* tb, MipsAtom* atom) { u4_r(tb->ptr)[tb->used] = u4_(atom); ++ tb->used; }
FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; } FI_ void tb_data(TapeBuilder* tb, U4 data) { u4_r(tb->ptr)[tb->used] = u4_(data); ++ tb->used; }
#define tb_emit_(atom) tb_emit(& tb, atom) #define tb_emit_(atom) tb_emit(& tb, atom)
#define tb_data_(field, data) tb_data(& tb, u4_(data)) #define tb_data_(field, data) tb_data(& tb, u4_(data))
FI_ void tb_emit_bundle(TapeBuilder_R tb, Slice_MipsAtom atoms) { mem_copy(u4_(tb->ptr), u4_(atoms.ptr), tb->used); tb->used += atoms.len; }
FI_ Tape tb_end (TapeBuilder* tb) { tb_emit(tb,tape_exit); return (Tape){ C_(U4*,tb->ptr), tb->used }; } FI_ Tape tb_end (TapeBuilder* tb) { tb_emit(tb,tape_exit); return (Tape){ C_(U4*,tb->ptr), tb->used }; }
FI_ Tape tb_slice(TapeBuilder tb) { return (Tape){ C_(U4*,tb.ptr), tb.used }; } FI_ Tape tb_slice(TapeBuilder tb) { return (Tape){ C_(U4*,tb.ptr), tb.used }; }
#define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,tape_exit)) #define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,tape_exit))
@@ -233,22 +247,49 @@ typedef Relative_(FArena) Struct_(MipsAtomBuilder) { U4 start; U4 capacity; U4 u
// Whatever the builder is writting to should most likely coresspond // Whatever the builder is writting to should most likely coresspond
// to something that can fit within instruction cache? // to something that can fit within instruction cache?
FI_ void atombuilder_unroll(MipsAtomBuilder_R ab, Slice_MipsCode_R code) { FI_ void atombuilder_unroll(MipsAtomBuilder_R ab, Slice_MipsCode code) {
assert(ab->capacity - ab->used - code->len); assert(ab->capacity - ab->used - code.len);
mem_copy(ab->start, u4_(code->ptr), code->len); mem_copy(ab->start, u4_(code.ptr), code.len);
mem_bump(ab->start, ab->capacity, & ab->used, code->len); mem_bump(ab->start, ab->capacity, & ab->used, code.len);
} }
#define atombuilder_unroll_mac(ab, mac) atombuilder_unroll(ab, slice_arg_from_array(Slice_MipsCode, mac)) #define atombuilder_unroll_mac(ab, mac) atombuilder_unroll(ab, slice_arg_from_array(Slice_MipsCode, mac))
// When done authoring, utilize this to cap-off the atom // When done authoring, utilize this to cap-off the atom (if not utilizing a MipsAtom_Proc).
FI_ void atombuilder_end(MipsAtomBuilder_R ab) { FI_ void atombuilder_end(MipsAtomBuilder_R ab) {
mem_copy(ab->start, u4_(ac_yield), S_(ac_yield)); mem_copy(ab->start, u4_(ac_yield), S_(ac_yield));
mem_bump(ab->start, ab->capacity, & ab->used, S_(ac_yield)); mem_bump(ab->start, ab->capacity, & ab->used, S_(ac_yield));
} }
#define mipsatom_from_builder(ab) (Slice_MipsCode){ab.start, ab.used} #define mipsatom_from_builder(ab) C_(MipsAtom*, (ab).start)
#pragma endregion Mips Atom Builder #pragma endregion Mips Atom Builder
#pragma region Mips Atom Procs
#if 0
typedef Struct_(Binds_SyncPrimitiveArena) { U4 used; U4 cursor; };
FI_ void sync_prim_arean_proc_demo(MipsAtomBuilder_R ab, U4 r_extra, U4 add_amnt_extra)
MipsAtom_Proc_(sync_primitive_arena_proc_demo, ab, atom_info(atom_bind(Binds_SyncPrimitiveArena)
, atom_reads( R_TapePtr, R_PrimCursor)
, atom_writes(R_TapePtr)
){
load_word(R_AT, R_TapePtr, O_(Binds_SyncPrimitiveArena,used)),
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimitiveArena,cursor)),
add_ui_self( R_TapePtr, S_(Binds_SyncPrimitiveArena)),
/* Calculate byte offset and store directly back to RAM */
sub_u( R_T0, R_PrimCursor, R_T0), // R_T0 = R_PrimCursor - binds.cursor
store_word(R_T0, R_AT, 0), // R_AT[0] = R_T0
add_ui_self(r_extra, add_amnt_extra), // extra op for demonstration purposes.
mac_yield()
})
void demo_make_make_and_emit_atom(TapeBuilder* tb, MipsAtomBuilder* ab){
sync_prim_arean_proc_demo(ab, R_T4, 4);
tb_emit(tb, mipsatom_from_builder(ab[0]));
}
#endif
#pragma endregion Mips Atom Procs
#pragma region Baked Mips Atoms #pragma region Baked Mips Atoms
// These atoms are resolved at compile time and are (usually) statically linked readonly data. // These atoms are resolved at compile time and are (usually) statically linked readonly data.
+12 -11
View File
@@ -348,6 +348,12 @@ enum { _BitOffsets = 0
#define shift_lright(rd, rt, shamt) enc_r(op_special, R_0, (rt), (rd), (shamt), fc_srl) #define shift_lright(rd, rt, shamt) enc_r(op_special, R_0, (rt), (rd), (shamt), fc_srl)
#define shift_aright(rd, rt, shamt) enc_r(op_special, R_0, (rt), (rd), (shamt), fc_sra) #define shift_aright(rd, rt, shamt) enc_r(op_special, R_0, (rt), (rd), (shamt), fc_sra)
/* Shift Variable — register-shift forms.
* shift_lleft_var(rd, rt, rs) → sllv rd, rt, rs (shamt in low 5 bits of rs)
* shift_aright_var(rd, rt, rs) → srav rd, rt, rs */
#define shift_lleft_var(rd, rt, rs) enc_r(op_special, (rs), (rt), (rd), 0, fc_sllv)
#define shift_aright_var(rd, rt, rs) enc_r(op_special, (rs), (rt), (rd), 0, fc_srav)
#define shift_lleft_self(rd_rt, shamt) enc_r(op_special, R_0, (rd_rt), (rd_rt), (shamt), fc_sll) #define shift_lleft_self(rd_rt, shamt) enc_r(op_special, R_0, (rd_rt), (rd_rt), (shamt), fc_sll)
#define mask_upper(rd, rt, shamt) shift_lleft(rd, rt, shamt), shift_lright(rd, rt, shamt) #define mask_upper(rd, rt, shamt) shift_lleft(rd, rt, shamt), shift_lright(rd, rt, shamt)
@@ -366,20 +372,18 @@ enum { _BitOffsets = 0
* WARNING: `jump(off)` CANNOT BE USED for within-atom jumps in the current pipeline. * WARNING: `jump(off)` CANNOT BE USED for within-atom jumps in the current pipeline.
* The MIPS j opcode encodes `(target_addr >> 2)` in its 26-bit immediate field; an ABSOLUTE byte address, not a relative word offset. * The MIPS j opcode encodes `(target_addr >> 2)` in its 26-bit immediate field; an ABSOLUTE byte address, not a relative word offset.
* The metaprogram computes `off` as a relative word offset (`target_word_idx - branch_word_idx - 1`), which the assembler/linker does NOT resolve. * The metaprogram computes `off` as a relative word offset (`target_word_idx - branch_word_idx - 1`), which the assembler/linker does NOT resolve.
*
* `jump(off)` is only safe when the BUILD PIPELINE owns the absolute position of the emitted code — i.e. when: s * `jump(off)` is only safe when the BUILD PIPELINE owns the absolute position of the emitted code — i.e. when: s
* - the build emits a symbol-relative `.word` expression that the linker resolvess via `R_MIPS_26`, OR * - the build emits a symbol-relative `.word` expression that the linker resolvess via `R_MIPS_26`, OR
* - the code is hand-assembled with explicit absolute targets, OR a custom post-build patcher resolves the 26-bit field. * - the code is hand-assembled with explicit absolute targets, OR a custom post-build patcher resolves the 26-bit field.
* TODO(Ed): Review this.. technically we can resolve aboslute jumps on baked atoms? (Even proedurally generated ones...)
*/ */
#define jump(off) enc_i(op_j, R_0, R_0, (off)) #define jump(off) enc_i(op_j, R_0, R_0, (off))
/* jump_rel off — unconditional relative jump (the within-atom-safe `jump`). /* jump_rel off — unconditional relative jump (the within-atom-safe `jump`).
* MIPS I R3000A has no "branch always" opcode. The idiom for an unconditional relative jump is `beq $0, $0, off`. * MIPS I R3000A has no "branch always" opcode. The idiom for an unconditional relative jump is `beq $0, $0, off`. */
*/
#define jump_rel(off) branch_equal(R_0, R_0, (off)) #define jump_rel(off) branch_equal(R_0, R_0, (off))
/* call_addr off — jump-and-link to immediate address. /* call_addr off — jump-and-link to immediate address.
*
* Same WARNING as `jump(off)` above: the jal opcode also encodes an absolute 26-bit target. * Same WARNING as `jump(off)` above: the jal opcode also encodes an absolute 26-bit target.
* For within-atom calls, the current pipeline has no equivalent always-taken call-and-link idiom. * For within-atom calls, the current pipeline has no equivalent always-taken call-and-link idiom.
* Workaround: `branch_link` (always-taken branch + explicit `la $ra, next_word_addr; jr $ra`), or just use `call_reg($tmp)` after loading the target into a register. * Workaround: `branch_link` (always-taken branch + explicit `la $ra, next_word_addr; jr $ra`), or just use `call_reg($tmp)` after loading the target into a register.
@@ -397,13 +401,7 @@ enum { _BitOffsets = 0
* sub_s / sub_u → sub / subu * sub_s / sub_u → sub / subu
* mult_s / mult_u → mult / multu (writes HI/LO; result in LO) * mult_s / mult_u → mult / multu (writes HI/LO; result in LO)
* div_s / div_u → div / divu (LO = quot, HI = rem) * div_s / div_u → div / divu (LO = quot, HI = rem)
* */
* NOTE: dsl.h defines `add_s`/`sub_s`/`mut_s`/`gt_s`/etc. as _Generic-based signed integer-arithmetic helpers for U1/U2/U4.
* Those live in a different conceptual layer (generic arithmetic on DSL types) and would collide with the instruction encoders here.
* The `#undef` below lets the gas-style names below win; if a file needs both, the dsl.h versions can be reached via their long forms
* (e.g. `def_signed_op`-style or the underlying `add_s1/s2/s4`). */
#undef add_s
#undef sub_s
#define add_s(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_add) #define add_s(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_add)
#define add_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_addu) #define add_u(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_addu)
#define sub_s(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_sub) #define sub_s(rd, rs, rt) enc_r(op_special, (rs), (rt), (rd), 0, fc_sub)
@@ -458,6 +456,9 @@ enum { _BitOffsets = 0
#define nop shift_lleft(rdiscard, rdiscard, 0) #define nop shift_lleft(rdiscard, rdiscard, 0)
#define nop2 nop, nop #define nop2 nop, nop
// li_s — load signed 16-bit immediate into GPR (addiu rt, $0, imm — sign-extends).
#define li_s(rt, imm) add_ui((rt), R_0, (imm))
#define load_imm_1w(rt, imm) add_ui((rt), R_0, (imm)) #define load_imm_1w(rt, imm) add_ui((rt), R_0, (imm))
#define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm)) #define load_imm_1w_s0(rt, imm) add_si((rt)), R_0, (imm))
+9
View File
@@ -54,6 +54,15 @@ WORD_COUNT(gte_sw, 1)
WORD_COUNT(gte_cmdw_rtpt, 1) WORD_COUNT(gte_cmdw_rtpt, 1)
WORD_COUNT(gte_cmdw_nclip, 1) WORD_COUNT(gte_cmdw_nclip, 1)
WORD_COUNT(gte_avg_sort_z3, 1) WORD_COUNT(gte_avg_sort_z3, 1)
WORD_COUNT(gte_cmdw_sqr, 1)
WORD_COUNT(gte_cmdw_gpf, 1)
WORD_COUNT(shift_lleft_var, 1)
WORD_COUNT(shift_aright_var, 1)
WORD_COUNT(li_s, 1)
WORD_COUNT(and_i, 1)
WORD_COUNT(add_si, 1)
WORD_COUNT(branch_lt_zero, 1)
WORD_COUNT(sub_s, 1)
WORD_COUNT(sub_u, 1) WORD_COUNT(sub_u, 1)
WORD_COUNT(nop2, 2) WORD_COUNT(nop2, 2)
+8 -2
View File
@@ -384,7 +384,13 @@ internal MipsAtom_(resolve_look_at) atom_info(atom_bind(Binds_ResolveLookAt)) {
mac_sub_v3s4( R_LkAt_Fwdx, R_LkAt_Fwdy, R_LkAt_Fwdz, mac_sub_v3s4( R_LkAt_Fwdx, R_LkAt_Fwdy, R_LkAt_Fwdz,
R_Eye_x, R_Eye_y, R_Eye_z), R_Eye_x, R_Eye_y, R_Eye_z),
// ac_normalize_v3s4(9 args): in-place normalize direction → unit vector.
// Reg-aliasing across the 4 stages: R_T7 = r_sq_y → r_lzcr, R_T8 = r_sq_z → r_shift,
// R_V0 = r_recip_est (always), R_V1 = r_tmp. r_sx/r_sy/r_sz = R_LkAt_Fwdx/y/z (in-place).
// mac_normalize_v3s4(R_LkAt_Fwdx, R_LkAt_Fwdy, R_LkAt_Fwdz,
// R_T7, R_T8,
// R_V0,
// R_T7, R_T8, R_V1),
mac_yield(), mac_yield(),
}; };
@@ -438,7 +444,7 @@ MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4),
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)), branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)),
/* BD-slot: write the prim tag (R_0=0; overwrites the legacy tag word in the prim_buffer). /* BD-slot: write the prim tag (R_0=0; overwrites the legacy tag word in the prim_buffer).
* If branch IS taken (face culled), the body is skipped and this 0-tag is stranded — * If branch IS taken (face culled), the body is skipped and this 0-tag is stranded —
* harmless because the OT entry that points to this prim is created later, only on the body path. */ * harmless because the OT entry that points to this prim is created later. */
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)), store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase), shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase),
load_word(R_V0, R_AT, O_(V3_S2, x)), load_word(R_V1, R_AT, O_(V3_S2, z)), load_word(R_V0, R_AT, O_(V3_S2, x)), load_word(R_V1, R_AT, O_(V3_S2, z)),
+33 -5
View File
@@ -172,12 +172,12 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
S4 flag; //???? S4 flag; //????
// Camera Look at // Camera Look at
if (1) if (0)
{ {
camera_look_at_c11(& smem.cam, & smem.cube.pos, & v3s4(0, -fp_one, 0)); camera_look_at_c11(& smem.cam, & smem.cube.pos, & v3s4(0, -fp_one, 0));
} }
// Camera look at (Tape) // Camera look at (Tape)
if (0) if (1)
{ {
MT3_S2S4* look_at = & smem.cam.look_at; MT3_S2S4* look_at = & smem.cam.look_at;
P3_S4* eye = & smem.cam.pos; P3_S4* eye = & smem.cam.pos;
@@ -188,8 +188,36 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
V3_S4 pos, off; V3_S4 pos, off;
tb.used = 0; tb_scope_run(& tb) { tb.used = 0; tb_scope_run(& tb) {
tb_emit_(resolve_look_at); // tb_emit_bundle(resolve_look_at);
// tb_data_(); {
tb_emit_(resolve_look_at); {
tb_data_(look_at, & smem.cam.look_at);
tb_data_(eye, & smem.cam.pos);
tb_data_(target, & smem.cube.pos);
tb_data_(up_in, up_in);
// tb_emit(a_normalize_v3s4(/*Todo: resolve dependent register allocation*/));
// tb_data_(fwd_out);
}
#if 0
{
tb_emit_(resolve_look_at__resolve_right); {
//...
tb_emit_(a_normalize_v3s4(...));
tb_data_(right_out);
}
tb_emit(resolve_look_at__resolve_up); {
//...
tb_emit_(ac_normalize_v3s4(...));
tb_data_(up_out);
}
tb_emit(world_to_cam_expand_mt3_s2s4(...)); {
tb_data(look_at, & smem.cam.look_at);
}
tb_emit_(resolve_look_at__final); {
}
}
#endif
}
} }
// forward = target[0]; sub_v3s4(& forward, eye[0]); // RGA(Lengyel): Affine point - point = zero-weight direction. // forward = target[0]; sub_v3s4(& forward, eye[0]); // RGA(Lengyel): Affine point - point = zero-weight direction.
@@ -205,7 +233,7 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
pos = eye[0]; mul_v3s4(& pos, v3s4(-1,-1,-1)); // RGA(Lengyel): -eye in world coordinates (spatial bulk only; implicit weight is dropped). pos = eye[0]; mul_v3s4(& pos, v3s4(-1,-1,-1)); // RGA(Lengyel): -eye in world coordinates (spatial bulk only; implicit weight is dropped).
// RGA(Lengyel): R * (-eye) is the full matrix translation column. // RGA(Lengyel): R * (-eye) -- full matrix translation column.
// Motor translator would store half this displacement in m.xyz; GTE consumes full column. // Motor translator would store half this displacement in m.xyz; GTE consumes full column.
mul_m3s2_v3s4(look_at, & pos, & off); mul_m3s2_v3s4(look_at, & pos, & off);
trans_m3s2( look_at, & off); trans_m3s2( look_at, & off);
+64 -10
View File
@@ -1053,6 +1053,8 @@ M.GTE_COMMAND_ALIASES = {
-- gte_avg_sort_z3 / gte_avg_sort_z4 are the duffle-side aliases for AVSZ3/4. -- gte_avg_sort_z3 / gte_avg_sort_z4 are the duffle-side aliases for AVSZ3/4.
["gte_avg_sort_z3"] = "gte_cmdw_avsz3", ["gte_avg_sort_z3"] = "gte_cmdw_avsz3",
["gte_avg_sort_z4"] = "gte_cmdw_avsz4", ["gte_avg_sort_z4"] = "gte_cmdw_avsz4",
["gte_cmdw_sqr"] = "gte_cmdw_sqr",
["gte_cmdw_gpf"] = "gte_cmdw_gpf",
} }
-- GTE command input-set table. -- GTE command input-set table.
@@ -1136,6 +1138,14 @@ M.GTE_COMMAND_INPUTS = {
"C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3", "C2_SZ0", "C2_SZ1", "C2_SZ2", "C2_SZ3",
"gte_cr_ZSF4", "gte_cr_ZSF4",
}, },
-- SQR: reads IR1..IR3 (per PSX-SPX gte.md SQR section; libgte disassembly 0x800160b0).
["gte_cmdw_sqr"] = {
"C2_IR1", "C2_IR2", "C2_IR3",
},
-- GPF: reads IR0 + IR1..IR3 (per PSX-SPX gte.md GPF section; libgte disassembly 0x8001613c).
["gte_cmdw_gpf"] = {
"C2_IR0", "C2_IR1", "C2_IR2", "C2_IR3",
},
} }
-- GTE command output-set + semantic role table. -- GTE command output-set + semantic role table.
@@ -1208,6 +1218,22 @@ M.GTE_COMMAND_OUTPUTS = {
{ register = "C2_IR2", role = "latest_color" }, { register = "C2_IR2", role = "latest_color" },
{ register = "C2_IR3", role = "latest_color" }, { register = "C2_IR3", role = "latest_color" },
}, },
["gte_cmdw_sqr"] = {
{ register = "C2_MAC1", role = "mac_result" },
{ register = "C2_MAC2", role = "mac_result" },
{ register = "C2_MAC3", role = "mac_result" },
{ register = "C2_IR1", role = "latest_color" },
{ register = "C2_IR2", role = "latest_color" },
{ register = "C2_IR3", role = "latest_color" },
},
["gte_cmdw_gpf"] = {
{ register = "C2_MAC1", role = "mac_result" },
{ register = "C2_MAC2", role = "mac_result" },
{ register = "C2_MAC3", role = "mac_result" },
{ register = "C2_IR1", role = "latest_color" },
{ register = "C2_IR2", role = "latest_color" },
{ register = "C2_IR3", role = "latest_color" },
},
} }
-- GTE command/post-command latch-window table. -- GTE command/post-command latch-window table.
@@ -1270,6 +1296,22 @@ M.GTE_COMMAND_LATCH_WINDOWS = {
{ register = "C2_IR2", required = 4 }, { register = "C2_IR2", required = 4 },
{ register = "C2_IR3", required = 4 }, { register = "C2_IR3", required = 4 },
}, },
["gte_cmdw_sqr"] = {
{ register = "C2_MAC1", required = 4 },
{ register = "C2_MAC2", required = 4 },
{ register = "C2_MAC3", required = 4 },
{ register = "C2_IR1", required = 4 },
{ register = "C2_IR2", required = 4 },
{ register = "C2_IR3", required = 4 },
},
["gte_cmdw_gpf"] = {
{ register = "C2_MAC1", required = 4 },
{ register = "C2_MAC2", required = 4 },
{ register = "C2_MAC3", required = 4 },
{ register = "C2_IR1", required = 4 },
{ register = "C2_IR2", required = 4 },
{ register = "C2_IR3", required = 4 },
},
} }
-- Operand-class table for the COP2->GPR load-delay check. -- Operand-class table for the COP2->GPR load-delay check.
@@ -1285,6 +1327,7 @@ M.GTE_COMMAND_LATCH_WINDOWS = {
M.OPERAND_READ_POSITIONS = { M.OPERAND_READ_POSITIONS = {
-- CPU ALU with one or two GPR operands. Reads every GPR operand. -- CPU ALU with one or two GPR operands. Reads every GPR operand.
["add_ui"] = {1, 2}, ["add_ui"] = {1, 2},
["li_s"] = {1, 2}, -- rt (write), imm16 (immediate)
["add_ui_self"] = {1}, ["add_ui_self"] = {1},
["add_si"] = {1, 2}, ["add_si"] = {1, 2},
["add_u"] = {1, 2, 3}, ["add_u"] = {1, 2, 3},
@@ -1354,6 +1397,8 @@ M.OPERAND_READ_POSITIONS = {
["gte_mv_to_ctrl_r"] = {}, ["gte_mv_to_ctrl_r"] = {},
["gte_lw"] = {}, ["gte_lw"] = {},
["gte_sw"] = {}, ["gte_sw"] = {},
["shift_lleft_var"] = {1, 2, 3}, -- rd, rt, rs (variable shift amount)
["shift_aright_var"] = {1, 2, 3},
} }
-- GP0 packet sizes (total words including the 1-word tag) per GP0 cmd byte. -- GP0 packet sizes (total words including the 1-word tag) per GP0 cmd byte.
@@ -1435,8 +1480,10 @@ M.INSTRUCTION_LATENCY = {
["xor_i"] = 1, ["xor_u"] = 1, ["xor_i"] = 1, ["xor_u"] = 1,
["nor_u"] = 1, ["nor_u"] = 1,
["shift_lleft"] = 1, ["shift_lleft_self"] = 1, ["shift_lleft"] = 1, ["shift_lleft_self"] = 1,
["shift_lleft_var"] = 1, -- sllv: 1 cycle
["shift_lright"] = 1, ["shift_lright"] = 1,
["shift_aright"] = 1, ["shift_aright"] = 1,
["shift_aright_var"] = 1, -- srav: 1 cycle
["mask_upper"] = 1, ["mask_upper"] = 1,
["mov_from_high"] = 2, -- mfhi: 2 cycles ["mov_from_high"] = 2, -- mfhi: 2 cycles
["mov_from_low"] = 2, -- mflo: 2 cycles ["mov_from_low"] = 2, -- mflo: 2 cycles
@@ -1454,6 +1501,7 @@ M.INSTRUCTION_LATENCY = {
["load_half_u"] = 1, ["load_half"] = 1, ["load_half_u"] = 1, ["load_half"] = 1,
["load_byte_u"] = 1, ["load_byte"] = 1, ["load_byte_u"] = 1, ["load_byte"] = 1,
["load_upper_i"] = 1, ["load_upper_i"] = 1,
["li_s"] = 1, -- aliased to add_ui(rt, R_0, imm); 1 cycle
-- 2-word loads (lui + ori) used for >16-bit immediates -- 2-word loads (lui + ori) used for >16-bit immediates
["load_imm"] = 2, ["load_imm"] = 2,
["load_imm_1w"] = 1, ["load_imm_1w"] = 1,
@@ -1497,6 +1545,8 @@ M.INSTRUCTION_LATENCY = {
["gte_cmdw_op"] = 6, -- OP: 6 cycles (PSX-SPX) ["gte_cmdw_op"] = 6, -- OP: 6 cycles (PSX-SPX)
["gte_cmdw_outer_product"] = 6, -- alias for OP ["gte_cmdw_outer_product"] = 6, -- alias for OP
["gte_cmdw_wedge"] = 6, -- alias for OP ["gte_cmdw_wedge"] = 6, -- alias for OP
["gte_cmdw_sqr"] = 5, -- SQR(sf): 5 cycles (PSX-SPX); +2 nops for pre-fill if sf=0/1
["gte_cmdw_gpf"] = 5, -- GPF(sf,lm): 5 cycles (PSX-SPX); +2 nops for pre-fill if needed
-- Long-form aliases (same cycle cost as their short form) -- Long-form aliases (same cycle cost as their short form)
["gte_cmdw_rotate_translate_perspective_single"] = 15, -- alias for rtps ["gte_cmdw_rotate_translate_perspective_single"] = 15, -- alias for rtps
["gte_cmdw_rotate_translate_perspective_triple"] = 23, -- alias for rtpt ["gte_cmdw_rotate_translate_perspective_triple"] = 23, -- alias for rtpt
@@ -1777,6 +1827,7 @@ M.CU2_TRANSITION_POLICY = {
M.INSTRUCTION_GPR_EFFECTS = { M.INSTRUCTION_GPR_EFFECTS = {
-- CPU ALU with one or two GPR operands. Reads every GPR operand position. -- CPU ALU with one or two GPR operands. Reads every GPR operand position.
add_ui = { reads = {1, 2}, writes = {1} }, add_ui = { reads = {1, 2}, writes = {1} },
li_s = { reads = {1, 2}, writes = {1} }, -- RMW: rt is both read + written
add_ui_self = { reads = {1}, writes = {1} }, add_ui_self = { reads = {1}, writes = {1} },
add_si = { reads = {1, 2}, writes = {1} }, add_si = { reads = {1, 2}, writes = {1} },
add_u = { reads = {2, 3}, writes = {1} }, add_u = { reads = {2, 3}, writes = {1} },
@@ -1893,6 +1944,8 @@ M.INSTRUCTION_GPR_EFFECTS = {
atom_writes = { reads = {}, writes = {} }, atom_writes = { reads = {}, writes = {} },
-- mac_yield transfers control to the next atom; zero GPR effects. -- mac_yield transfers control to the next atom; zero GPR effects.
mac_yield = { reads = {}, writes = {} }, mac_yield = { reads = {}, writes = {} },
shift_lleft_var = { reads = {2, 3}, writes = {1} },
shift_aright_var = { reads = {2, 3}, writes = {1} },
} }
-- Bounded GPR-value rules consumed by the same forward event walk as `INSTRUCTION_GPR_EFFECTS`. -- Bounded GPR-value rules consumed by the same forward event walk as `INSTRUCTION_GPR_EFFECTS`.
@@ -1903,18 +1956,19 @@ M.INSTRUCTION_GPR_EFFECTS = {
-- * passes/static_analysis.lua::apply_gpr_effects -- * passes/static_analysis.lua::apply_gpr_effects
-- No second `bounded_value_pass` is permitted. -- No second `bounded_value_pass` is permitted.
M.GPR_VALUE_RULES = { M.GPR_VALUE_RULES = {
load_upper_i = { op = "load_upper_i", dest = 1, immediate = 2, }, load_upper_i = { op = "load_upper_i", dest = 1, immediate = 2, },
add_ui = { op = "add_ui", dest = 1, source = 2, immediate = 3, }, add_ui = { op = "add_ui", dest = 1, source = 2, immediate = 3, },
or_i = { op = "or_i", dest = 1, source = 2, immediate = 3, }, li_s = { op = "add_ui", dest = 1, source = 2, immediate = 3 }, -- R_0 + sign-ext(imm) folds into a constant
and_i = { op = "and_i", dest = 1, source = 2, immediate = 3, }, or_i = { op = "or_i", dest = 1, source = 2, immediate = 3, },
xor_i = { op = "xor_i", dest = 1, source = 2, immediate = 3, }, and_i = { op = "and_i", dest = 1, source = 2, immediate = 3, },
add_ui_self = { op = "add_ui", dest = 1, source = 1, immediate = 2, }, xor_i = { op = "xor_i", dest = 1, source = 2, immediate = 3, },
or_i_self = { op = "or_i", dest = 1, source = 1, immediate = 2, }, add_ui_self = { op = "add_ui", dest = 1, source = 1, immediate = 2, },
or_i_self = { op = "or_i", dest = 1, source = 1, immediate = 2, },
-- Present register-form self variants. They are included here so a -- Present register-form self variants. They are included here so a
-- known value is not needlessly lost when these encoders are used. -- known value is not needlessly lost when these encoders are used.
add_u_self = { op = "add_u", dest = 1, sources = {1, 2}, }, add_u_self = { op = "add_u", dest = 1, sources = {1, 2}, },
or_u_self = { op = "or", dest = 1, sources = {1, 2}, }, or_u_self = { op = "or", dest = 1, sources = {1, 2}, },
shift_lleft_self = { op = "shift_lleft", dest = 1, source = 1, immediate = 2, }, shift_lleft_self = { op = "shift_lleft", dest = 1, source = 1, immediate = 2, },
} }
-- Control-transfer (branch/jump/call) delay-slot policy table. -- Control-transfer (branch/jump/call) delay-slot policy table.
+3 -1
View File
@@ -299,7 +299,9 @@ local function word_count_rec(name, comp_by_name, wc, cache)
local trimmed = t.tok local trimmed = t.tok
if trimmed ~= "" then if trimmed ~= "" then
local lookup = strip_mac_prefix(duffle.read_ident(trimmed, 1)) local lookup = strip_mac_prefix(duffle.read_ident(trimmed, 1))
if lookup and comp_by_name[lookup] then if lookup == "atom_label" or lookup == "atom_offset" then
-- Pure metaprogram anchors; emit zero words.
elseif lookup and comp_by_name[lookup] then
-- It's a `mac_X(...)` call. Recurse. -- It's a `mac_X(...)` call. Recurse.
n = n + word_count_rec(lookup, comp_by_name, wc, cache) n = n + word_count_rec(lookup, comp_by_name, wc, cache)
elseif lookup and wc and wc[lookup] then elseif lookup and wc and wc[lookup] then
+85 -30
View File
@@ -256,8 +256,21 @@ local BRANCH_PATTERN = "^branch_[%w_]+%s*%("
-- The C preprocessor expands it BEFORE the metaprogram sees the source, but for source-level metadata consistency we still match it here and classify it as a branch_equal. -- The C preprocessor expands it BEFORE the metaprogram sees the source, but for source-level metadata consistency we still match it here and classify it as a branch_equal.
-- This keeps `consuming_encoder` canonical for any downstream tooling that consults the metadata field. -- This keeps `consuming_encoder` canonical for any downstream tooling that consults the metadata field.
local JUMP_REL_PATTERN = "^jump_rel%s*%(" local JUMP_REL_PATTERN = "^jump_rel%s*%("
local UNCOND_JUMP_PATTERN = "^%f[%w](jump|call_addr)%f[%W]" local UNCOND_JUMP_PATTERNS = {
local TERMINAL_JUMP_PATTERN = "^%f[%w](jump_reg|call_reg|jump_link)%f[%W]" "^%f[%w]jump%f[%W]",
"^%f[%w]call_addr%f[%W]",
}
local TERMINAL_JUMP_PATTERNS = {
"^%f[%w]jump_reg%f[%W]",
"^%f[%w]call_reg%f[%W]",
"^%f[%w]jump_link%f[%W]",
}
local function matches_any(tok, patterns)
for i = 1, #patterns do
if tok:match(patterns[i]) then return true end
end
return false
end
local function classify_tokens(tokens) local function classify_tokens(tokens)
local n = #tokens local n = #tokens
@@ -301,13 +314,13 @@ local function classify_tokens(tokens)
-- Both encode a 16-bit signed relative word offset. -- Both encode a 16-bit signed relative word offset.
is_branch = true is_branch = true
branch_label = tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)") or false branch_label = tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)") or false
elseif tok:match(UNCOND_JUMP_PATTERN) then elseif matches_any(tok, UNCOND_JUMP_PATTERNS) then
-- Unconditional absolute jump / call: `jump(off)` / `call_addr(off)`. -- Unconditional absolute jump / call: `jump(off)` / `call_addr(off)`.
-- One immediate offset field; can carry an `atom_offset(F, T)` marker (the offsets pass dispatches on `consuming_encoder` — see `passes/offsets.lua::compute_offsets`). -- One immediate offset field; can carry an `atom_offset(F, T)` marker (the offsets pass dispatches on `consuming_encoder` — see `passes/offsets.lua::compute_offsets`).
is_branch = true is_branch = true
is_unconditional_jump = true is_unconditional_jump = true
branch_label = tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)") or false branch_label = tok:match("atom_offset%s*%([^,]+,%s*([%w_]+)%s*%)") or false
elseif tok:match(TERMINAL_JUMP_PATTERN) then elseif matches_any(tok, TERMINAL_JUMP_PATTERNS) then
-- Register-form jump / call: no offset field; `atom_offset` is invalid here (the offsets pass will error if one is supplied). -- Register-form jump / call: no offset field; `atom_offset` is invalid here (the offsets pass will error if one is supplied).
-- Transfers control OUT of the current atom — the CFG treats this as a path terminator. -- Transfers control OUT of the current atom — the CFG treats this as a path terminator.
is_terminal_jump = true is_terminal_jump = true
@@ -567,13 +580,31 @@ local function evaluate_gpr_value_rule(rule, ev_args, gpr_values)
return shift_left_u4(immediate % 0x10000, 16) return shift_left_u4(immediate % 0x10000, 16)
end end
local source = nil -- Encoders that take `R_0` implicitly (e.g. `li_s(rt, imm)` which is `add_ui(rt, R_0, imm)`) have a non-GPR operand at the source position.
-- Fall back to R_0 = 0.
-- The implicit-R_0 macros also use a different immediate position (e.g. `li_s`'s `add_ui` rule has source = 2 / immediate = 3
-- but the macro takes 2 args); when the configured immediate position is out of bounds.
-- Fall back instead to scanning the macro's args for the first integer literal and use that as the immediate.
local source = 0
if rule.source then if rule.source then
source = constant_for_operand(gpr_values, ev_args[rule.source]) if is_gpr_operand(ev_args[rule.source]) then
if source == nil then return nil end source = constant_for_operand(gpr_values, ev_args[rule.source])
if source == nil then return nil end
end
-- Non-GPR at source position = implicit R_0; source stays 0.
end
local immediate = nil
if rule.immediate and ev_args[rule.immediate] ~= nil then
immediate = parse_integer_literal(ev_args[rule.immediate])
if immediate == nil then return nil end
elseif rule.immediate then
-- Immediate position out of bounds: scan for the first integer literal in the args.
for _, arg in ipairs(ev_args) do
immediate = parse_integer_literal(arg)
if immediate ~= nil then break end
end
if immediate == nil then return nil end
end end
local immediate = rule.immediate and parse_integer_literal(ev_args[rule.immediate]) or nil
if rule.immediate and immediate == nil then return nil end
if operation == "add_ui" then return wrap_u4( source + sign_extend_i16(immediate)) if operation == "add_ui" then return wrap_u4( source + sign_extend_i16(immediate))
elseif operation == "or_i" then return bit_binary( source, immediate % 0x10000, "or") elseif operation == "or_i" then return bit_binary( source, immediate % 0x10000, "or")
elseif operation == "and_i" then return bit_binary( source, immediate % 0x10000, "and") elseif operation == "and_i" then return bit_binary( source, immediate % 0x10000, "and")
@@ -1433,17 +1464,20 @@ end
--- The register becomes non-volatile again at word N+2 (the load has retired), OR sooner if a non-load instruction overwrites the register --- The register becomes non-volatile again at word N+2 (the load has retired), OR sooner if a non-load instruction overwrites the register
--- (the overwriter's write is the fresh producer; the load's value is shadowed and never observed by any reader). --- (the overwriter's write is the fresh producer; the load's value is shadowed and never observed by any reader).
--- ---
--- Runtime-helper atoms / components (`debug_skip == true`) are exempt: their internal load-then-use sequences --- Runtime-helper atoms / components (`debug_skip == true`) are exempt from some checks, but load-delay
--- are part of the fixed handshake (e.g. `ac_load_tri_indices` loads into R_T0..R_T2, but those are caller-supplied). --- safety applies to their emitted instructions as well.
--- ---
--- The walker reads `duffle.OPERAND_READ_POSITIONS[event.encoder]` to determine which args are read-source --- The walker reads `duffle.OPERAND_READ_POSITIONS[event.encoder]` to determine which args are read-source
--- (the destination of a load is in `writes`, not `reads` — see `duffle.INSTRUCTION_GPR_EFFECTS`). --- (the destination of a load is in `writes`, not `reads` — see `duffle.INSTRUCTION_GPR_EFFECTS`).
--- The check is purely structural; it does not consult the GPR-value lattice (no constant propagation needed for load-delay detection — the volatility window is unconditional). --- The check is purely structural; it does not consult the GPR-value lattice
--- (no constant propagation needed for load-delay detection — the volatility window is unconditional).
local function check_load_delay_slots(atom, pipe_ctx, findings) local function check_load_delay_slots(atom, pipe_ctx, findings)
-- The load-delay check applies to every atom and component body, including debug-skipped components (`ac_*` and `atom_dbg_skip MipsAtom_(...)`).
-- The `atom_dbg_skip` marker controls debugger stepping, not instruction safety.
local p = atom.paths or {}
if atom.kind ~= "atom" then return end if atom.kind ~= "atom" then return end
local events = atom.paths.word_events or {} local events = p.word_events or {}
if #events == 0 then return end if #events == 0 then return end
if is_runtime_helper(atom) then return end
local gpr_effects = duffle.INSTRUCTION_GPR_EFFECTS or {} local gpr_effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
local read_positions = duffle.OPERAND_READ_POSITIONS or {} local read_positions = duffle.OPERAND_READ_POSITIONS or {}
@@ -1656,21 +1690,39 @@ local function check_yield_load_tail_pairing(atom, _pipe_ctx, findings)
return atom.line + line_in_body[tokens[idx].rel] return atom.line + line_in_body[tokens[idx].rel]
end end
-- ── Rule 1: every `mac_yield_load()` must be in a branch BD-slot. -- ── Rule 1: every `mac_yield_load()` must be in a branch BD-slot, OR sit between two `atom_label`s (natural fall-through load pattern).
-- When the pattern is satisfied, the check stays silent; only violations emit findings.
for tok_idx = 1, n do for tok_idx = 1, n do
local c = tc[tok_idx] local c = tc[tok_idx]
if c.ident == "mac_yield_load" then if c.ident == "mac_yield_load" then
if tok_idx < 2 or not tc[tok_idx - 1].is_branch then local prev_tc = (tok_idx >= 2) and tc[tok_idx - 1] or nil
local prev_ident = (tok_idx >= 2) and (tc[tok_idx - 1].ident or "?") or "<none>" -- Look for the next `atom_label()` token (skip `atom_offset` markers; check immediately-adjacent first).
findings[#findings + 1] = { local next_label_tc = (tok_idx + 1 <= n) and tc[tok_idx + 1] or nil
atom = atom.name, if next_label_tc and next_label_tc.ident ~= "atom_label" then
line = tok_idx >= 2 and line_for(tok_idx) or atom.line, next_label_tc = nil
check = "yield_load_tail_pairing", for j = tok_idx + 1, n do
kind = "error", local t = tc[j]
msg = string.format( if t.ident == "atom_label" then
"%s at line %d has `mac_yield_load()` at word %d but the previous token is `%s`, not a branch — `mac_yield_load()` must fill a branch BD-slot." next_label_tc = t
, atom.name, tok_idx >= 2 and line_for(tok_idx) or atom.line, tok_idx, prev_ident), break
} end
end
end
local natural_fallthrough = prev_tc and prev_tc.is_atom_label and next_label_tc ~= nil
if not natural_fallthrough then
if tok_idx < 2 or not prev_tc.is_branch then
local prev_ident = prev_tc and (prev_tc.ident or "?") or "<none>"
local next_ident = next_label_tc and (next_label_tc.ident .. "(" .. (next_label_tc.label_name or "?") .. ")") or "<no following label>"
findings[#findings + 1] = {
atom = atom.name,
line = tok_idx >= 2 and line_for(tok_idx) or atom.line,
check = "yield_load_tail_pairing",
kind = "error",
msg = string.format(
"%s at line %d has `mac_yield_load()` at word %d but the previous token is `%s`, not a branch — and the next `atom_label()` token is `%s` — `mac_yield_load()` must fill a branch BD-slot or sit between two `atom_label`s for the natural fall-through load."
, atom.name, tok_idx >= 2 and line_for(tok_idx) or atom.line, tok_idx, prev_ident, next_ident),
}
end
end end
end end
end end
@@ -2015,8 +2067,9 @@ local function analyze_atom_paths(atom, pipe_ctx)
succ[#succ + 1] = label_pos + 1 succ[#succ + 1] = label_pos + 1
end end
end end
-- For literal-offset jumps (label == false), the target is a non-tracked address; conservatively omit. -- For literal-offset jumps (label == false), control transfers out unconditionally.
return succ, nil -- Treat as a terminator so the path is recorded (NOT as a silent fall-through to the next token, which is unreachable in this atom's execution).
return {}, tok_idx
end end
-- Conditional branch: BD slot absorbed; two successors — fall-through (tok_idx+2) + taken (if known). -- Conditional branch: BD slot absorbed; two successors — fall-through (tok_idx+2) + taken (if known).
if tok_idx + 2 <= n then if tok_idx + 2 <= n then
@@ -2032,9 +2085,11 @@ local function analyze_atom_paths(atom, pipe_ctx)
-- Return (succ, nil), the second value is the terminator marker (nil = not a terminator). -- Return (succ, nil), the second value is the terminator marker (nil = not a terminator).
return succ, nil return succ, nil
end end
-- Normal token: just the next one -- Normal token: just the next one.
-- The final ordinary word of the body has no successor and terminates the path;
-- record it as an implicit endpoint so the cycle budget for non-yield components is not silently zeroed.
if tok_idx + 1 <= n then return { tok_idx + 1 }, nil end if tok_idx + 1 <= n then return { tok_idx + 1 }, nil end
return {}, nil return {}, tok_idx
end end
-- DFS through all paths. Track the current cycle sum, a visited set scoped to the current path (to detect loops), and a count of paths. -- DFS through all paths. Track the current cycle sum, a visited set scoped to the current path (to detect loops), and a count of paths.