some more review. not working still.

This commit is contained in:
ed
2026-08-10 23:04:43 -04:00
parent 4afd1af0fd
commit 144c605ad8
14 changed files with 257 additions and 925 deletions
-62
View File
@@ -175,68 +175,6 @@ WORD_COUNT(mac_gte_sqr_v3, 8)
, shift_aright_var(r_dz, r_dz, r_shift)
WORD_COUNT(mac_gte_gpf_scale, 13)
#define mac_normalize_v3s4(...) \
load_word(r_src, R_TapePtr, O_(Binds_NormalizeV3S4,src)) /* pop src ptr (scratch addr) */ \
, load_word(r_dst, R_TapePtr, O_(Binds_NormalizeV3S4,dst)) /* pop dst ptr (scratch addr) */ \
, add_ui_self( R_TapePtr, S_(Binds_NormalizeV3S4)) \
, load_word(r_sx, r_src, O_(V3_S4,x)) \
, load_word(r_sy, r_src, O_(V3_S4,y)) \
, load_word(r_sz, r_src, O_(V3_S4,z)) \
, nop /* load-delay */ /* ── 48-word normalize body (preserved verbatim from ac_normalize_v3s4) ─────── */ /* Stage 1: mtc2 src → IR1/2/3, SQR fires (MAC1/2/3 = IR², IR ← MAC saturated) */ \
, 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 */ \
, 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| */ \
, 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 */ \
, 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) /* ── I/O wrapper tail (~3 words) ───────────────────────────────────────────── */ \
, store_word(r_sx, r_dst, O_(V3_S4,x)) \
, store_word(r_sy, r_dst, O_(V3_S4,y)) \
, store_word(r_sz, r_dst, O_(V3_S4,z)) /* ── atom_reads(R_TapePtr) atom_writes(R_TapePtr) ────────────────────────── */ \
, mac_yield()
WORD_COUNT(mac_normalize_v3s4, 62)
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
load_upper_i(reg_transfer, cmd >> 16) \
, or_i_self( reg_transfer, cmd & 0xFFFF) \
+100 -109
View File
@@ -101,7 +101,8 @@ FI_ Slice_MipsCode ac_gte_gpf_scale(MipsAtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r
* 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).
* 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)
@@ -153,125 +154,115 @@ internal S2 const gte_normalize_sqr_tbl[192] align_(2) = {
};
/* ─── Full normalize (all 4 stages inline) ───
* Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav).
*
* Parameterized by caller-provided scratch base + src/dst offsets.
* The caller passes r_src_offset and r_dst_offset as compile-time constants
* (typically derived from O_ macros in the caller's struct schema, e.g., `O_(CallerBundleScratch, fwd)`).
*
* This design lets any caller (with a scratch base + struct schema) use `normalize_v3s4_proc`
* without putting magic offsets in the C-side bundle helper — the offsets come from O_ macros at the call site.
*
* Body uses 9 GPRs (r_src_ptr..r_branch_tmp):
* r_src_ptr, r_dst_ptr : src/dst pointers (computed from r_scratch + caller offsets)
* r_tmp : scratch (reserved for misc use)
* r_mac1_scratch : MAC1 result scratch (before sum into r_recip_est)
* r_mac2_scratch : MAC2 result scratch (clobbered to IR1 in stage 4)
* r_recip_est : |v|² sum + shift-input + sqrtbl[index] (the main chain)
* r_lzcr : LZCR value (consumed by stage 3 alignment calc)
* r_shift : final srav amount (consumed by stage 4 shift_aright_var)
* r_branch_tmp : scratch (shift count, branch target, sqrtbl base addr)
*
* Atom_labels are srav_path / aligned_done
* (NOT namespaced — they're internal to this proc;
* the metaprogram's per-atom-name enum emission handles any collision across different atoms/files that share the same labels).
*
* Pool cost: 11 GPRs (well within the 9-10 caller-trash GPR budget when r_scratch is a wave-context carrier).
*
* 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.
* Words: ~59 (matches libgte 0x800160a0..0x8001615c at +/- 0-2 words for BD-slot reshuffling).
* Sqrtbl: hardcoded to 0x800185B4 (libgte msc02.rel.data). Note: swapped to local.
* 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. */
/* ─── Binds_NormalizeV3S4 — declared here so the MipsAtom_Proc_ body can reference
* O_(Binds_NormalizeV3S4,*). Inlined at the proc-call site; not exposed in gen/macs.h. */
typedef Struct_(Binds_NormalizeV3S4) {
U4 src; /* V3_S4* (scratch address — read from tape) */
U4 dst; /* V3_S4* (scratch address — write to tape) */
};
/* NOTE: The bundle-specific scratchpad offset schema was intentionally kept out of this file.
* gte.atom.c is the GENERIC GTE primitives file — it exposes only the parameter-style normalize_v3s4_proc for any future caller. */
I_ void normalize_v3s4_proc(
MipsAtomBuilder_R ab
, U4 r_src /* GPR code: scratch base carrier (wave-context, e.g., R_T4) */
, U4 r_dst /* GPR code: scratch dst pointer carrier (wave-context, e.g., R_T5) */
, U4 r_sx, U4 r_sy, U4 r_sz /* GPR codes: src.x/y/z scratch (atom-local) */
, U4 r_sq_y, U4 r_sq_z /* GPR codes: MAC1/2 scratch (atom-local) */
, U4 r_recip_est /* GPR code: |v|² sum + shift-input + sqrtbl[index] (atom-local) */
, U4 r_lzcr /* GPR code: LZCR value (atom-local) */
, U4 r_shift /* GPR code: final srav amount (atom-local) */
, U4 r_tmp /* GPR code: scratch (shift count, branch target, lookup addr, table base) */
)
*/
/* MipsAtom_Proc_ wrapper: declares the static MipsCode[] body, then calls atombuilder_unroll(ab, ...) to copy the encoded instructions into the caller's MipsAtomBuilder arena. */
I_ void normalize_v3s4_proc(MipsAtomBuilder_R ab, U4 r_scratch /* GPR code: scratch base carrier (e.g., R_T4 = R_ResolveScratch) */
, U4 r_src_offset, U4 r_dst_offset /* GPR codes: PARAMETERIZED offsets (caller passes O_ macros) */
, U4 r_src_ptr, U4 r_dst_ptr, U4 r_tmp /* GPR codes: 3 scratch regs (src/dst computed + tmp) */
, U4 r_mac1_scratch, U4 r_mac2_scratch /* GPR codes: 2 more: MAC1/MAC2 scratch */
, U4 r_recip_est /* GPR code: |v|² sum + shift-input + sqrtbl[index] */
, U4 r_lzcr, U4 r_shift /* GPR codes: lzcr + final srav amount */
, U4 r_branch_tmp /* GPR code: scratch (shift count, branch target, lookup addr) */
)
MipsAtom_Proc_(normalize_v3s4, ab, {
/* ── I/O wrapper (~10 words: 3 bind-pop + 3 src-load + 1 nop + 3 dst-store) ─── */
load_word(r_src, R_TapePtr, O_(Binds_NormalizeV3S4,src)), /* pop src ptr (scratch addr) */
load_word(r_dst, R_TapePtr, O_(Binds_NormalizeV3S4,dst)), /* pop dst ptr (scratch addr) */
add_ui_self( R_TapePtr, S_(Binds_NormalizeV3S4)),
load_word(r_sx, r_src, O_(V3_S4,x)),
load_word(r_sy, r_src, O_(V3_S4,y)),
load_word(r_sz, r_src, O_(V3_S4,z)),
add_si(r_src_ptr, r_scratch, r_src_offset), /* r_src_ptr = &src */
add_si(r_dst_ptr, r_scratch, r_dst_offset), /* r_dst_ptr = &dst */
nop,
/* Load src.x/y/z from r_src_ptr (caller-determined address) into r_mac2_scratch/r_recip_est/r_branch_tmp. */
load_word(r_mac2_scratch, r_src_ptr, O_(V3_S4,x)),
load_word(r_recip_est, r_src_ptr, O_(V3_S4,y)),
load_word(r_branch_tmp, r_src_ptr, O_(V3_S4,z)),
nop, /* load-delay */
/* ── 48-word normalize body (preserved verbatim from ac_normalize_v3s4) ─────── */
// Stage 1: mtc2 src → IR1/2/3, SQR fires (MAC1/2/3 = IR², IR ← MAC saturated)
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),
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
gte_mv_to_data_r(r_mac2_scratch, C2_IR1),
gte_mv_to_data_r(r_recip_est, C2_IR2),
gte_mv_to_data_r(r_branch_tmp, C2_IR3),
nop, gte_cmdw_sqr,
// Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS
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|² */
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */
gte_mv_from_data_r(r_mac1_scratch, C2_MAC1),
gte_mv_from_data_r(r_mac2_scratch, C2_MAC2),
gte_mv_from_data_r(r_lzcr, C2_MAC3),
nop,
add_u(r_lzcr, r_lzcr, r_mac2_scratch),
add_u(r_lzcr, r_lzcr, r_mac1_scratch),
gte_mv_to_data_r(r_lzcr, C2_LZCS),
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|
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,
gte_mv_from_data_r(r_shift, C2_LZCR),
nop,
/* Stage 3: compute srav amount (r_lzcr) + align |v|² to bit 24. */
and_i( r_shift, r_shift, -2),
li_s( r_lzcr, 31),
sub_s( r_lzcr, r_lzcr, r_shift),
shift_aright(r_lzcr, r_lzcr, 1),
/* r_branch_tmp = LZCR - 24 (overwrites r_branch_tmp; src.z no longer needed after SQR) */
add_si( r_branch_tmp, r_shift, -24),
branch_lt_zero(r_branch_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
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),
shift_lleft_var(r_lzcr, r_lzcr, r_branch_tmp), /* when r_branch_tmp < 0 (LZCR < 24): shift r_lzcr left by (24-LZCR) */
atom_label(srav_path)
li_s( r_branch_tmp, 24),
sub_s( r_branch_tmp, r_branch_tmp, r_shift),
shift_aright_var(r_lzcr, r_lzcr, r_branch_tmp), /* when r_branch_tmp >= 0 (LZCR >= 24): shift r_lzcr right by (LZCR-24) */
atom_label(aligned_done)
/* r_lzcr holds |v|² aligned to bit 24. */
add_si( r_lzcr, r_lzcr, -64),
shift_lleft(r_lzcr, r_lzcr, 1),
load_upper_i(r_branch_tmp, u4_hi(& gte_normalize_sqr_tbl)),
or_i_self( r_branch_tmp, u4_lo(& gte_normalize_sqr_tbl)),
add_u(r_branch_tmp, r_branch_tmp, r_lzcr),
load_half(r_lzcr, r_branch_tmp, 0), nop,
/* ── I/O wrapper tail (~3 words) ───────────────────────────────────────────── */
store_word(r_sx, r_dst, O_(V3_S4,x)),
store_word(r_sy, r_dst, O_(V3_S4,y)),
store_word(r_sz, r_dst, O_(V3_S4,z)),
/* Stage 4: GPF + srav finalize (r_lzcr = srav_amount carried from stage 3). */
gte_mv_to_data_r(r_lzcr, C2_IR0),
gte_mv_to_data_r(r_mac2_scratch, C2_IR1),
gte_mv_to_data_r(r_recip_est, C2_IR2),
gte_mv_to_data_r(r_branch_tmp, C2_IR3),
nop2, gte_cmdw_gpf,
gte_mv_from_data_r(r_mac2_scratch, C2_MAC1),
gte_mv_from_data_r(r_recip_est, C2_MAC2),
gte_mv_from_data_r(r_branch_tmp, C2_MAC3),
shift_aright_var(r_mac2_scratch, r_mac2_scratch, r_lzcr),
shift_aright_var(r_recip_est, r_recip_est, r_lzcr),
shift_aright_var(r_branch_tmp, r_branch_tmp, r_lzcr),
/* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */
store_word(r_mac2_scratch, r_dst_ptr, O_(V3_S4,x)),
store_word(r_recip_est, r_dst_ptr, O_(V3_S4,y)),
store_word(r_branch_tmp, r_dst_ptr, O_(V3_S4,z)),
/* ── atom_reads(R_TapePtr) atom_writes(R_TapePtr) ────────────────────────── */
mac_yield()
})
+2 -2
View File
@@ -27,9 +27,9 @@
* to author and compose programs with. From here various conventions can be further applied.
* To make things easier to understand it may be better to focus on what this ABI does not have.
* It does not have have any branching within the tape but relative branches within atoms or between atoms.
* Branching nearly is always downstream. Stack usage is non-existent.
* Branching nearly is always downstream. Atuomatic stack usage is non-existent.
* Push/Pop, FIFO, or Arena/Bump data structures are used by atoms explicitly.
* In it's current form with the C11 macro dsl, the user also has fullfill manual register allocation per atom.
* In it's current form with the C11 macro DSL, the user also has fullfill manual register allocation per atom.
*
* One of the remarkable things about utilizing this ABI is its essentially interopable with CPUs, GPUs, FPGA,
* or, basically anything from the 5th generation consoles and onward.
-347
View File
@@ -39,350 +39,3 @@ WORD_COUNT(mac_put_disp_env, 5)
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port)
WORD_COUNT(mac_put_draw_env, 16)
#define mac_resolve_look_at__input_and_sub(...) \
load_word(r_target_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,target)) \
, load_word(r_eye_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,eye)) \
, load_word(r_up_in_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,up_in)) \
, add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtSub)) \
, load_word(r_scratch, R_TapePtr, O_(Binds_ResolveLookAtScratch,scratch_base)) \
, add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtScratch)) /* Stage eye.x/y/z into the scratchpad (atom 6 reads these for the translation
* column). Reuse r_tmp0/r_tmp1/r_tmp2. Offsets via O_(ResolveLookAtScratch,*). */ \
, load_word(r_tmp0, r_eye_ptr, O_(P3_S4,x)) \
, load_word(r_tmp1, r_eye_ptr, O_(P3_S4,y)) \
, load_word(r_tmp2, r_eye_ptr, O_(P3_S4,z)) \
, nop /* load-delay */ \
, store_word(r_tmp0, r_scratch, O_(ResolveLookAtScratch,eye.x)) \
, store_word(r_tmp1, r_scratch, O_(ResolveLookAtScratch,eye.y)) \
, store_word(r_tmp2, r_scratch, O_(ResolveLookAtScratch,eye.z)) /* Stage up_in.x/y/z into the scratchpad (atom 2 reads these for the outer
* product with uz). Reuse r_tmp0/r_tmp1/r_tmp2. */ \
, load_word(r_tmp0, r_up_in_ptr, O_(V3_S4,x)) \
, load_word(r_tmp1, r_up_in_ptr, O_(V3_S4,y)) \
, load_word(r_tmp2, r_up_in_ptr, O_(V3_S4,z)) \
, nop /* load-delay */ \
, store_word(r_tmp0, r_scratch, O_(ResolveLookAtScratch,up_in.x)) \
, store_word(r_tmp1, r_scratch, O_(ResolveLookAtScratch,up_in.y)) \
, store_word(r_tmp2, r_scratch, O_(ResolveLookAtScratch,up_in.z)) /* Compute fwd = target - eye. */ \
, load_word(r_tmp0, r_target_ptr, O_(P3_S4,x)) \
, load_word(r_tmp1, r_target_ptr, O_(P3_S4,y)) \
, load_word(r_tmp2, r_target_ptr, O_(P3_S4,z)) \
, load_word(r_tmp3, r_eye_ptr, O_(P3_S4,x)) \
, load_word(R_AT, r_eye_ptr, O_(P3_S4,y)) \
, load_word(R_V0, r_eye_ptr, O_(P3_S4,z)) \
, nop /* load-delay */ \
, sub_u(r_tmp0, r_tmp0, r_tmp3) \
, sub_u(r_tmp1, r_tmp1, R_AT) \
, sub_u(r_tmp2, r_tmp2, R_V0) /* Store fwd.x/y/z (atom 1 reads these as the normalize src). */ \
, store_word(r_tmp0, r_scratch, O_(ResolveLookAtScratch,fwd.x)) \
, store_word(r_tmp1, r_scratch, O_(ResolveLookAtScratch,fwd.y)) \
, store_word(r_tmp2, r_scratch, O_(ResolveLookAtScratch,fwd.z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__input_and_sub, 34)
#define mac_resolve_look_at__cross_uz_up_in_to_right(...) \
add_si(r_g, r_scratch, O_(ResolveLookAtScratch,uz)) /* r_g = &uz */ \
, add_si(r_h, r_scratch, O_(ResolveLookAtScratch,up_in)) /* r_h = &up_in */ \
, add_si(r_f, r_scratch, O_(ResolveLookAtScratch,right)) /* r_f = &right (out) */ \
, nop /* Load a (uz).x/y/z into r_a/r_b/r_c. */ \
, load_word(r_a, r_g, O_(V3_S4,x)) \
, load_word(r_b, r_g, O_(V3_S4,y)) \
, load_word(r_c, r_g, O_(V3_S4,z)) \
, nop /* Load b (up_in).x/y/z into r_d + R_AT/R_V0 (hardcoded; reusing the
* body's last two loads is fine because the load-delay slot is the nop
* after the third load, and mtc2 below doesn't read these regs). */ \
, load_word(r_d, r_h, O_(V3_S4,x)) \
, load_word(R_AT, r_h, O_(V3_S4,y)) \
, load_word(R_V0, r_h, O_(V3_S4,z)) \
, nop /* mtc2 a → IR1/2/3, b → D1/2/3 (VXY0/VZ0/VXY1). */ \
, gte_mv_to_data_r(r_a, C2_IR1) \
, gte_mv_to_data_r(r_b, C2_IR2) \
, gte_mv_to_data_r(r_c, C2_IR3) \
, gte_mv_to_data_r(r_d, C2_VXY0) /* D1 = b.x */ \
, gte_mv_to_data_r(R_AT, C2_VZ0) /* D2 = b.y */ \
, gte_mv_to_data_r(R_V0, C2_VXY1) /* D3 = b.z */ \
, nop2 /* MTC2 retirement (CPU→COP2 2-slot delay) */ \
, gte_cmdw_outer_product /* OP fires; MAC1/2/3 = a × b */ /* mfc2 MAC1/2/3 → r_a/r_b/r_c (out.x/y/z). */ \
, gte_mv_from_data_r(r_a, C2_MAC1) \
, gte_mv_from_data_r(r_b, C2_MAC2) \
, gte_mv_from_data_r(r_c, C2_MAC3) \
, nop /* MFC2 retirement */ /* Store out.x/y/z to r_f (out ptr = scratch+32). */ \
, store_word(r_a, r_f, O_(V3_S4,x)) \
, store_word(r_b, r_f, O_(V3_S4,y)) \
, store_word(r_c, r_f, O_(V3_S4,z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__cross_uz_up_in_to_right, 29)
#define mac_resolve_look_at__cross_uz_ux_to_up(...) \
add_si(r_g, r_scratch, O_(ResolveLookAtScratch,uz)) /* r_g = &uz */ \
, add_si(r_h, r_scratch, O_(ResolveLookAtScratch,ux)) /* r_h = &ux */ \
, add_si(r_f, r_scratch, O_(ResolveLookAtScratch,up)) /* r_f = &up (out) */ \
, nop /* Load a (uz).x/y/z into r_a/r_b/r_c. */ \
, load_word(r_a, r_g, O_(V3_S4,x)) \
, load_word(r_b, r_g, O_(V3_S4,y)) \
, load_word(r_c, r_g, O_(V3_S4,z)) \
, nop /* Load b (ux).x/y/z into r_d + R_AT/R_V0. */ \
, load_word(r_d, r_h, O_(V3_S4,x)) \
, load_word(R_AT, r_h, O_(V3_S4,y)) \
, load_word(R_V0, r_h, O_(V3_S4,z)) \
, nop /* mtc2 a → IR1/2/3, b → D1/2/3 (VXY0/VZ0/VXY1). */ \
, gte_mv_to_data_r(r_a, C2_IR1) \
, gte_mv_to_data_r(r_b, C2_IR2) \
, gte_mv_to_data_r(r_c, C2_IR3) \
, gte_mv_to_data_r(r_d, C2_VXY0) \
, gte_mv_to_data_r(R_AT, C2_VZ0) \
, gte_mv_to_data_r(R_V0, C2_VXY1) \
, nop2 \
, gte_cmdw_outer_product \
, gte_mv_from_data_r(r_a, C2_MAC1) \
, gte_mv_from_data_r(r_b, C2_MAC2) \
, gte_mv_from_data_r(r_c, C2_MAC3) \
, nop \
, store_word(r_a, r_f, O_(V3_S4,x)) \
, store_word(r_b, r_f, O_(V3_S4,y)) \
, store_word(r_c, r_f, O_(V3_S4,z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__cross_uz_ux_to_up, 29)
#define mac_resolve_look_at__normalize_fwd_to_uz(...) \
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,fwd)) /* r_a = &fwd */ \
, add_si(r_b, r_scratch, O_(ResolveLookAtScratch,uz)) /* r_b = &uz */ \
, nop /* Load src.x/y/z from r_a into r_e/r_f/r_i. */ \
, load_word(r_e, r_a, O_(V3_S4,x)) \
, load_word(r_f, r_a, O_(V3_S4,y)) \
, load_word(r_i, r_a, O_(V3_S4,z)) \
, nop /* load-delay */ /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop \
, gte_cmdw_sqr /* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */ \
, gte_mv_from_data_r(r_d, C2_MAC1) \
, gte_mv_from_data_r(r_g, C2_MAC2) \
, gte_mv_from_data_r(r_recip_est, C2_MAC3) \
, nop \
, add_u(r_recip_est, r_recip_est, r_g) \
, add_u(r_recip_est, r_recip_est, r_d) \
, gte_mv_to_data_r(r_recip_est, C2_LZCS) \
, nop2 \
, gte_mv_from_data_r(r_h, C2_LZCR) \
, nop /* Stage 3: compute shift amount, align |v|² to bit 24. */ \
, and_i( r_h, r_h, -2) \
, li_s( r_shift, 31) \
, sub_s( r_shift, r_shift, r_h) \
, shift_aright(r_shift, r_shift, 1) \
, add_si( r_a, r_h, -24) /* r_a = LZCR - 24 (overlapping with r_recip_est; src ptr no longer needed) */ \
, branch_lt_zero(r_a, atom_offset(srav_path_fwd_to_uz, aligned_done_fwd_to_uz)) \
, nop \
, jump_rel(atom_offset(aligned_done_fwd_to_uz, srav_path_fwd_to_uz)) \
, shift_lleft_var(r_recip_est, r_recip_est, r_a) \
, atom_label(srav_path_fwd_to_uz) \
, li_s( r_a, 24) \
, sub_s( r_a, r_a, r_h) \
, shift_aright_var(r_recip_est, r_recip_est, r_a) \
, atom_label(aligned_done_fwd_to_uz) /* r_recip_est holds |v|² aligned to bit 24. */ \
, add_si( r_recip_est, r_recip_est, -64) \
, shift_lleft(r_recip_est, r_recip_est, 1) \
, load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)) \
, or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)) \
, add_u(r_a, r_a, r_recip_est) \
, load_half(r_recip_est, r_a, 0) \
, nop /* Stage 4: GPF + srav finalize. */ \
, gte_mv_to_data_r(r_recip_est, C2_IR0) \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop2 \
, gte_cmdw_gpf \
, gte_mv_from_data_r(r_e, C2_MAC1) \
, gte_mv_from_data_r(r_f, C2_MAC2) \
, gte_mv_from_data_r(r_i, C2_MAC3) \
, shift_aright_var(r_e, r_e, r_shift) \
, shift_aright_var(r_f, r_f, r_shift) \
, shift_aright_var(r_i, r_i, r_shift) /* Store result.x/y/z to r_b (dst ptr = scratch+16). */ \
, store_word(r_e, r_b, O_(V3_S4,x)) \
, store_word(r_f, r_b, O_(V3_S4,y)) \
, store_word(r_i, r_b, O_(V3_S4,z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__normalize_fwd_to_uz, 59)
#define mac_resolve_look_at__normalize_right_to_ux(...) \
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,right)) /* r_a = &right */ \
, add_si(r_b, r_scratch, O_(ResolveLookAtScratch,ux)) /* r_b = &ux */ \
, nop \
, load_word(r_e, r_a, O_(V3_S4,x)) \
, load_word(r_f, r_a, O_(V3_S4,y)) \
, load_word(r_i, r_a, O_(V3_S4,z)) \
, nop \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop \
, gte_cmdw_sqr \
, gte_mv_from_data_r(r_d, C2_MAC1) \
, gte_mv_from_data_r(r_g, C2_MAC2) \
, gte_mv_from_data_r(r_recip_est, C2_MAC3) \
, nop \
, add_u(r_recip_est, r_recip_est, r_g) \
, add_u(r_recip_est, r_recip_est, r_d) \
, gte_mv_to_data_r(r_recip_est, C2_LZCS) \
, nop2 \
, gte_mv_from_data_r(r_h, C2_LZCR) \
, nop \
, and_i( r_h, r_h, -2) \
, li_s( r_shift, 31) \
, sub_s( r_shift, r_shift, r_h) \
, shift_aright(r_shift, r_shift, 1) \
, add_si( r_a, r_h, -24) \
, branch_lt_zero(r_a, atom_offset(srav_path_right_to_ux, aligned_done_right_to_ux)) \
, nop \
, jump_rel(atom_offset(aligned_done_right_to_ux, srav_path_right_to_ux)) \
, shift_lleft_var(r_recip_est, r_recip_est, r_a) \
, atom_label(srav_path_right_to_ux) \
, li_s( r_a, 24) \
, sub_s( r_a, r_a, r_h) \
, shift_aright_var(r_recip_est, r_recip_est, r_a) \
, atom_label(aligned_done_right_to_ux) \
, add_si( r_recip_est, r_recip_est, -64) \
, shift_lleft(r_recip_est, r_recip_est, 1) \
, load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)) \
, or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)) \
, add_u(r_a, r_a, r_recip_est) \
, load_half(r_recip_est, r_a, 0) \
, nop \
, gte_mv_to_data_r(r_recip_est, C2_IR0) \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop2 \
, gte_cmdw_gpf \
, gte_mv_from_data_r(r_e, C2_MAC1) \
, gte_mv_from_data_r(r_f, C2_MAC2) \
, gte_mv_from_data_r(r_i, C2_MAC3) \
, shift_aright_var(r_e, r_e, r_shift) \
, shift_aright_var(r_f, r_f, r_shift) \
, shift_aright_var(r_i, r_i, r_shift) \
, store_word(r_e, r_b, O_(V3_S4,x)) \
, store_word(r_f, r_b, O_(V3_S4,y)) \
, store_word(r_i, r_b, O_(V3_S4,z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__normalize_right_to_ux, 59)
#define mac_resolve_look_at__normalize_up_to_uy(...) \
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,up)) /* r_a = &up */ \
, add_si(r_b, r_scratch, O_(ResolveLookAtScratch,uy)) /* r_b = &uy */ \
, nop \
, load_word(r_e, r_a, O_(V3_S4,x)) \
, load_word(r_f, r_a, O_(V3_S4,y)) \
, load_word(r_i, r_a, O_(V3_S4,z)) \
, nop \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop \
, gte_cmdw_sqr \
, gte_mv_from_data_r(r_d, C2_MAC1) \
, gte_mv_from_data_r(r_g, C2_MAC2) \
, gte_mv_from_data_r(r_recip_est, C2_MAC3) \
, nop \
, add_u(r_recip_est, r_recip_est, r_g) \
, add_u(r_recip_est, r_recip_est, r_d) \
, gte_mv_to_data_r(r_recip_est, C2_LZCS) \
, nop2 \
, gte_mv_from_data_r(r_h, C2_LZCR) \
, nop \
, and_i( r_h, r_h, -2) \
, li_s( r_shift, 31) \
, sub_s( r_shift, r_shift, r_h) \
, shift_aright(r_shift, r_shift, 1) \
, add_si( r_a, r_h, -24) \
, branch_lt_zero(r_a, atom_offset(srav_path_up_to_uy, aligned_done_up_to_uy)) \
, nop \
, jump_rel(atom_offset(aligned_done_up_to_uy, srav_path_up_to_uy)) \
, shift_lleft_var(r_recip_est, r_recip_est, r_a) \
, atom_label(srav_path_up_to_uy) \
, li_s( r_a, 24) \
, sub_s( r_a, r_a, r_h) \
, shift_aright_var(r_recip_est, r_recip_est, r_a) \
, atom_label(aligned_done_up_to_uy) \
, add_si( r_recip_est, r_recip_est, -64) \
, shift_lleft(r_recip_est, r_recip_est, 1) \
, load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)) \
, or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)) \
, add_u(r_a, r_a, r_recip_est) \
, load_half(r_recip_est, r_a, 0) \
, nop \
, gte_mv_to_data_r(r_recip_est, C2_IR0) \
, gte_mv_to_data_r(r_e, C2_IR1) \
, gte_mv_to_data_r(r_f, C2_IR2) \
, gte_mv_to_data_r(r_i, C2_IR3) \
, nop2 \
, gte_cmdw_gpf \
, gte_mv_from_data_r(r_e, C2_MAC1) \
, gte_mv_from_data_r(r_f, C2_MAC2) \
, gte_mv_from_data_r(r_i, C2_MAC3) \
, shift_aright_var(r_e, r_e, r_shift) \
, shift_aright_var(r_f, r_f, r_shift) \
, shift_aright_var(r_i, r_i, r_shift) \
, store_word(r_e, r_b, O_(V3_S4,x)) \
, store_word(r_f, r_b, O_(V3_S4,y)) \
, store_word(r_i, r_b, O_(V3_S4,z)) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__normalize_up_to_uy, 59)
#define mac_resolve_look_at__populate_and_translate(...) \
load_word(r_look_at, R_TapePtr, O_(Binds_ResolveLookAtPopAndTrans,look_at)) \
, add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtPopAndTrans)) /* Compute the 4 scratch pointers in their dedicated GPRs. */ \
, add_si(r_pux, r_scratch, O_(ResolveLookAtScratch,ux)) /* r_pux = &ux */ \
, add_si(r_puy, r_scratch, O_(ResolveLookAtScratch,uy)) /* r_puy = &uy */ \
, add_si(r_puz, r_scratch, O_(ResolveLookAtScratch,uz)) /* r_puz = &uz */ \
, add_si(r_peye, r_scratch, O_(ResolveLookAtScratch,eye)) /* r_peye = &eye */ \
, nop /* ── m[0] = (S2)ux ── */ \
, load_word(r_tmp0, r_pux, O_(V3_S4,x)) \
, load_word(r_tmp1, r_pux, O_(V3_S4,y)) \
, load_word(r_tmp2, r_pux, O_(V3_S4,z)) \
, nop \
, store_half(r_tmp0, r_look_at, O_(MT3_S2S4,m[0][0])) \
, store_half(r_tmp1, r_look_at, O_(MT3_S2S4,m[0][1])) \
, store_half(r_tmp2, r_look_at, O_(MT3_S2S4,m[0][2])) /* ── m[1] = (S2)uy ── */ \
, load_word(r_tmp0, r_puy, O_(V3_S4,x)) \
, load_word(r_tmp1, r_puy, O_(V3_S4,y)) \
, load_word(r_tmp2, r_puy, O_(V3_S4,z)) \
, nop \
, store_half(r_tmp0, r_look_at, O_(MT3_S2S4,m[1][0])) \
, store_half(r_tmp1, r_look_at, O_(MT3_S2S4,m[1][1])) \
, store_half(r_tmp2, r_look_at, O_(MT3_S2S4,m[1][2])) /* ── m[2] = (S2)uz ── */ \
, load_word(r_tmp0, r_puz, O_(V3_S4,x)) \
, load_word(r_tmp1, r_puz, O_(V3_S4,y)) \
, load_word(r_tmp2, r_puz, O_(V3_S4,z)) \
, nop \
, store_half(r_tmp0, r_look_at, O_(MT3_S2S4,m[2][0])) \
, store_half(r_tmp1, r_look_at, O_(MT3_S2S4,m[2][1])) \
, store_half(r_tmp2, r_look_at, O_(MT3_S2S4,m[2][2])) /* ── Translation column t[i] = R * (-eye) ─────────────────────────────
* pos = -eye: load eye.x/y/z from r_peye, negate via sub_u from R_0. */ \
, load_word(r_tmp0, r_peye, O_(P3_S4,x)) \
, load_word(r_tmp1, r_peye, O_(P3_S4,y)) \
, load_word(r_tmp2, r_peye, O_(P3_S4,z)) \
, nop \
, sub_u(r_tmp0, R_0, r_tmp0) /* pos.x = -eye.x */ \
, sub_u(r_tmp1, R_0, r_tmp1) \
, sub_u(r_tmp2, R_0, r_tmp2) /* mtc2 IR1/2/3 = pos (for MVMVA — input vector registers). */ \
, gte_mv_to_data_r(r_tmp0, C2_IR1) \
, gte_mv_to_data_r(r_tmp1, C2_IR2) \
, gte_mv_to_data_r(r_tmp2, C2_IR3) \
, nop2 /* MVMVA: MAC1/2/3 = R * IR with cv=0 (no TR vector), mx=0 (rotation matrix),
* sf=0 (no shift), v=0 (V0 = IR1/2/3, no far-plane clipping). The pre-set
* rotation matrix is the one set by the preceding set_gte_world atom.
* gte_cmdw_mvmva is parameterless and defaults to cv=0/mx=0/sf=0/v=0. */ \
, gte_cmdw_mvmva \
, nop /* GTE interlock */ /* mfc2 MAC1/2/3 → r_tmp0/r_tmp1/r_tmp2 (sign-extended into 32-bit GPRs).
* MAC1/2/3 hold R*v with no TR add and no perspective divide — exactly the
* 3 distinct world-space translation values we need for t[0..2]. */ \
, gte_mv_from_data_r(r_tmp0, C2_MAC1) \
, gte_mv_from_data_r(r_tmp1, C2_MAC2) \
, gte_mv_from_data_r(r_tmp2, C2_MAC3) \
, nop \
, store_word(r_tmp0, r_look_at, O_(MT3_S2S4,t[0])) \
, store_word(r_tmp1, r_look_at, O_(MT3_S2S4,t[1])) \
, store_word(r_tmp2, r_look_at, O_(MT3_S2S4,t[2])) \
, mac_yield()
WORD_COUNT(mac_resolve_look_at__populate_and_translate, 50)
-30
View File
@@ -8,36 +8,6 @@
#pragma region hello_camera
// --- atom: resolve_look_at__normalize_fwd_to_uz (62 words) ---
#define _atom_offset_srav_path_fwd_to_uz_aligned_done_fwd_to_uz 6
#define _atom_offset_aligned_done_fwd_to_uz_srav_path_fwd_to_uz 1
enum {
atom_offset_srav_path_fwd_to_uz_aligned_done_fwd_to_uz = _atom_offset_srav_path_fwd_to_uz_aligned_done_fwd_to_uz,
atom_offset_aligned_done_fwd_to_uz_srav_path_fwd_to_uz = _atom_offset_aligned_done_fwd_to_uz_srav_path_fwd_to_uz,
};
// --- atom: resolve_look_at__normalize_right_to_ux (62 words) ---
#define _atom_offset_srav_path_right_to_ux_aligned_done_right_to_ux 6
#define _atom_offset_aligned_done_right_to_ux_srav_path_right_to_ux 1
enum {
atom_offset_srav_path_right_to_ux_aligned_done_right_to_ux = _atom_offset_srav_path_right_to_ux_aligned_done_right_to_ux,
atom_offset_aligned_done_right_to_ux_srav_path_right_to_ux = _atom_offset_aligned_done_right_to_ux_srav_path_right_to_ux,
};
// --- atom: resolve_look_at__normalize_up_to_uy (62 words) ---
#define _atom_offset_srav_path_up_to_uy_aligned_done_up_to_uy 6
#define _atom_offset_aligned_done_up_to_uy_srav_path_up_to_uy 1
enum {
atom_offset_srav_path_up_to_uy_aligned_done_up_to_uy = _atom_offset_srav_path_up_to_uy_aligned_done_up_to_uy,
atom_offset_aligned_done_up_to_uy_srav_path_up_to_uy = _atom_offset_aligned_done_up_to_uy_srav_path_up_to_uy,
};
// --- atom: pad_input_cube_rotation (60 words) ---
#define _atom_offset_dpad_left_exit_dpad_left 6
+9 -253
View File
@@ -173,24 +173,22 @@ typedef Struct_(ResolveLookAtScratch) {
};
/* ─── resolve_look_at bundle chain atoms ────────────────────────────
* 7 unique atom procs in the resolve_look_at bundle (4 chain atoms + 3 normalize variants).
* All 7 are runtime-built MipsAtom_Proc_ atoms: each function declares a static MipsCode[] body,
* then calls atombuilder_unroll() to append it to the caller's MipsAtomBuilder arena.
* resolve_look_at_init() uses this pattern to pre-build the bundle into the static arena (smem.resolve_look_at_arena).
* 4 unique atom procs in the resolve_look_at bundle (4 chain atoms + 3 calls to generic normalize_v3s4_proc).
* All 4 chain atoms are runtime-built MipsAtom_Proc_ atoms: each function declares a static MipsCode[] body,
* then calls atombuilder_unroll() to append it to the caller's MipsAtomBuilder arena. resolve_look_at_init()
* uses this pattern to pre-build the bundle into the static arena (smem.resolve_look_at_arena).
*
* Atom roster:
* 0: resolve_look_at__input_and_sub (chain atom)
* 1: resolve_look_at__normalize_fwd_to_uz (normalize wrapper)
* 1: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for fwd→uz)
* 2: resolve_look_at__cross_uz_up_in_to_right (chain atom)
* 3: resolve_look_at__normalize_right_to_ux (normalize wrapper)
* 3: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for right→ux)
* 4: resolve_look_at__cross_uz_ux_to_up (chain atom)
* 5: resolve_look_at__normalize_up_to_uy (normalize wrapper)
* 5: normalize_v3s4_proc (gte.atom.c) (generic normalize; called for up→uy)
* 6: resolve_look_at__populate_and_translate (chain atom)
*
* The 3 normalize wrappers are CHAIN-SPECIFIC — they hardcode src/dst scratch offsets in the body
* (computed via r_scratch + O_(ResolveLookAtScratch, fld)).
* The generic normalize_v3s4_proc (in gte.atom.c) takes src/dst as GPR parameters and is NOT used by this bundle.
* (Layering rule: gte.atom.c contains only generic GTE primitives; bundle-specific code are within this file.)
* The generic normalize_v3s4_proc is a parameterized 4-stage GTE normalize (SQR → mfc2 → LZCS → GPF → srav);
* it accepts scratch base + offset args so any caller (with a scratch base + struct schema) can use it.
*/
typedef Struct_(Binds_ResolveLookAtSub) {
@@ -392,248 +390,6 @@ I_ void resolve_look_at__cross_uz_ux_to_up_proc(MipsAtomBuilder_R ab, U4 r_scrat
mac_yield()
})
/* Atoms 1, 3, 5 in the bundle: chain-specific normalize wrappers around the generic normalize_v3s4_proc (gte.atom.c).
* The generic proc takes src/dst as GPR parameters; these wrappers HARDCODE src/dst via r_scratch + O_(ResolveLookAtScratch, fld)
* so the C-side bundle helper doesn't need to push scratchpad addresses via tb_data between atoms.
*
* The 4-stage normalize body (SQR → mfc2 → LZCS → GPF → srav) is identical to the generic version (GPR-renamed); cycle counts match.
* The only difference is the (src, dst) scratch offsets and the per-proc atom_label suffixes
* (srav_path_fwd_to_uz, srav_path_right_to_ux, srav_path_up_to_uy) so the per-proc-instance offsets are emitted disjointly in gen/offsets.h.
*
* GPR pool (10 free regs: R_T0..R_T3, R_T5..R_T7, R_V0, R_V1, R_AT; R_T4 reserved for R_ResolveScratch):
* r_a : src ptr (overlaps with r_recip_est carrier after the 3 src-loads)
* r_b : dst ptr (saved throughout)
* r_e/r_f/r_i : src.x/y/z → result.x/y/z (preserved across stages 1-2 via r_d/r_g/r_recip_est scratch)
* r_d/r_g : MAC1/2 scratch (dead after stage 2)
* r_h : LZCR (saved across stages 3-4)
* r_recip_est : |v|² accumulator + sqrtbl[index] + 1/|v| (saved throughout)
* r_shift : final srav amount (saved across stages 3-4)
*
* The Lua metaprogram (Task 12.10) auto-emits:
* - `mac_resolve_look_at__normalize_<from>_to_<to>` alias in gen/macs.h
* - `atom_offset__srav_path_<from>_to_<to>__aligned_done_<from>_to_<to>` defs in gen/offsets.h
*/
/* Atom 1: normalize fwd (scratch+0) → uz (scratch+16). */
I_ void resolve_look_at__normalize_fwd_to_uz_proc(MipsAtomBuilder_R ab, U4 r_scratch
, U4 r_a, U4 r_b /* src/dst scratch pointers */
, U4 r_e, U4 r_f, U4 r_i /* src.x/y/z → result.x/y/z */
, U4 r_d, U4 r_g /* MAC1/2 scratch (dead after stage 2) */
, U4 r_h /* LZCR */
, U4 r_recip_est
, U4 r_shift
) MipsAtom_Proc_(resolve_look_at__normalize_fwd_to_uz, ab, {
/* Compute src/dst pointers from r_scratch. */
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,fwd)), /* r_a = &fwd */
add_si(r_b, r_scratch, O_(ResolveLookAtScratch,uz)), /* r_b = &uz */
nop,
/* Load src.x/y/z from r_a into r_e/r_f/r_i. */
load_word(r_e, r_a, O_(V3_S4,x)),
load_word(r_f, r_a, O_(V3_S4,y)),
load_word(r_i, r_a, O_(V3_S4,z)),
nop, /* load-delay */
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop, gte_cmdw_sqr,
/* Stage 2: mfc2 MAC1/2/3, sum, mtc2 LZCS. */
gte_mv_from_data_r(r_d, C2_MAC1),
gte_mv_from_data_r(r_g, C2_MAC2),
gte_mv_from_data_r(r_recip_est, C2_MAC3),
nop,
add_u(r_recip_est, r_recip_est, r_g),
add_u(r_recip_est, r_recip_est, r_d),
gte_mv_to_data_r(r_recip_est, C2_LZCS),
nop2,
gte_mv_from_data_r(r_h, C2_LZCR),
nop,
/* Stage 3: compute shift amount, align |v|² to bit 24. */
and_i( r_h, r_h, -2),
li_s( r_shift, 31),
sub_s( r_shift, r_shift, r_h),
shift_aright(r_shift, r_shift, 1),
add_si( r_a, r_h, -24), /* r_a = LZCR - 24 (overlapping with r_recip_est; src ptr no longer needed) */
branch_lt_zero(r_a, atom_offset(srav_path_fwd_to_uz, aligned_done_fwd_to_uz)), nop,
jump_rel(atom_offset(aligned_done_fwd_to_uz, srav_path_fwd_to_uz)),
shift_lleft_var(r_recip_est, r_recip_est, r_a),
atom_label(srav_path_fwd_to_uz)
li_s( r_a, 24),
sub_s( r_a, r_a, r_h),
shift_aright_var(r_recip_est, r_recip_est, r_a),
atom_label(aligned_done_fwd_to_uz)
/* r_recip_est holds |v|² aligned to bit 24. */
add_si( r_recip_est, r_recip_est, -64),
shift_lleft(r_recip_est, r_recip_est, 1),
load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)),
or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)),
add_u(r_a, r_a, r_recip_est),
load_half(r_recip_est, r_a, 0),
nop,
/* Stage 4: GPF + srav finalize. */
gte_mv_to_data_r(r_recip_est, C2_IR0),
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop2,
gte_cmdw_gpf,
gte_mv_from_data_r(r_e, C2_MAC1),
gte_mv_from_data_r(r_f, C2_MAC2),
gte_mv_from_data_r(r_i, C2_MAC3),
shift_aright_var(r_e, r_e, r_shift),
shift_aright_var(r_f, r_f, r_shift),
shift_aright_var(r_i, r_i, r_shift),
/* Store result.x/y/z to r_b (dst ptr = scratch+16). */
store_word(r_e, r_b, O_(V3_S4,x)),
store_word(r_f, r_b, O_(V3_S4,y)),
store_word(r_i, r_b, O_(V3_S4,z)),
mac_yield()
})
/* Atom 3: normalize right (scratch+32) → ux (scratch+48). */
I_ void resolve_look_at__normalize_right_to_ux_proc(MipsAtomBuilder_R ab, U4 r_scratch
, U4 r_a, U4 r_b
, U4 r_e, U4 r_f, U4 r_i
, U4 r_d, U4 r_g
, U4 r_h
, U4 r_recip_est
, U4 r_shift
) MipsAtom_Proc_(resolve_look_at__normalize_right_to_ux, ab, {
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,right)), /* r_a = &right */
add_si(r_b, r_scratch, O_(ResolveLookAtScratch,ux)), /* r_b = &ux */
nop,
load_word(r_e, r_a, O_(V3_S4,x)),
load_word(r_f, r_a, O_(V3_S4,y)),
load_word(r_i, r_a, O_(V3_S4,z)),
nop,
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop, gte_cmdw_sqr,
gte_mv_from_data_r(r_d, C2_MAC1),
gte_mv_from_data_r(r_g, C2_MAC2),
gte_mv_from_data_r(r_recip_est, C2_MAC3),
nop,
add_u(r_recip_est, r_recip_est, r_g),
add_u(r_recip_est, r_recip_est, r_d),
gte_mv_to_data_r(r_recip_est, C2_LZCS),
nop2,
gte_mv_from_data_r(r_h, C2_LZCR),
nop,
and_i( r_h, r_h, -2),
li_s( r_shift, 31),
sub_s( r_shift, r_shift, r_h),
shift_aright(r_shift, r_shift, 1),
add_si( r_a, r_h, -24),
branch_lt_zero(r_a, atom_offset(srav_path_right_to_ux, aligned_done_right_to_ux)), nop,
jump_rel(atom_offset(aligned_done_right_to_ux, srav_path_right_to_ux)),
shift_lleft_var(r_recip_est, r_recip_est, r_a),
atom_label(srav_path_right_to_ux)
li_s( r_a, 24),
sub_s( r_a, r_a, r_h),
shift_aright_var(r_recip_est, r_recip_est, r_a),
atom_label(aligned_done_right_to_ux)
add_si( r_recip_est, r_recip_est, -64),
shift_lleft(r_recip_est, r_recip_est, 1),
load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)),
or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)),
add_u(r_a, r_a, r_recip_est),
load_half(r_recip_est, r_a, 0),
nop,
gte_mv_to_data_r(r_recip_est, C2_IR0),
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop2,
gte_cmdw_gpf,
gte_mv_from_data_r(r_e, C2_MAC1),
gte_mv_from_data_r(r_f, C2_MAC2),
gte_mv_from_data_r(r_i, C2_MAC3),
shift_aright_var(r_e, r_e, r_shift),
shift_aright_var(r_f, r_f, r_shift),
shift_aright_var(r_i, r_i, r_shift),
store_word(r_e, r_b, O_(V3_S4,x)),
store_word(r_f, r_b, O_(V3_S4,y)),
store_word(r_i, r_b, O_(V3_S4,z)),
mac_yield()
})
/* Atom 5: normalize up (scratch+64) → uy (scratch+80). */
I_ void resolve_look_at__normalize_up_to_uy_proc(MipsAtomBuilder_R ab
, U4 r_scratch
, U4 r_a, U4 r_b
, U4 r_e, U4 r_f, U4 r_i
, U4 r_d, U4 r_g
, U4 r_h
, U4 r_recip_est
, U4 r_shift
) MipsAtom_Proc_(resolve_look_at__normalize_up_to_uy, ab, {
add_si(r_a, r_scratch, O_(ResolveLookAtScratch,up)), /* r_a = &up */
add_si(r_b, r_scratch, O_(ResolveLookAtScratch,uy)), /* r_b = &uy */
nop,
load_word(r_e, r_a, O_(V3_S4,x)),
load_word(r_f, r_a, O_(V3_S4,y)),
load_word(r_i, r_a, O_(V3_S4,z)),
nop,
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop, gte_cmdw_sqr,
gte_mv_from_data_r(r_d, C2_MAC1),
gte_mv_from_data_r(r_g, C2_MAC2),
gte_mv_from_data_r(r_recip_est, C2_MAC3),
nop,
add_u(r_recip_est, r_recip_est, r_g),
add_u(r_recip_est, r_recip_est, r_d),
gte_mv_to_data_r(r_recip_est, C2_LZCS),
nop2,
gte_mv_from_data_r(r_h, C2_LZCR),
nop,
and_i( r_h, r_h, -2),
li_s( r_shift, 31),
sub_s( r_shift, r_shift, r_h),
shift_aright(r_shift, r_shift, 1),
add_si( r_a, r_h, -24),
branch_lt_zero(r_a, atom_offset(srav_path_up_to_uy, aligned_done_up_to_uy)), nop,
jump_rel(atom_offset(aligned_done_up_to_uy, srav_path_up_to_uy)),
shift_lleft_var(r_recip_est, r_recip_est, r_a),
atom_label(srav_path_up_to_uy)
li_s( r_a, 24),
sub_s( r_a, r_a, r_h),
shift_aright_var(r_recip_est, r_recip_est, r_a),
atom_label(aligned_done_up_to_uy)
add_si( r_recip_est, r_recip_est, -64),
shift_lleft(r_recip_est, r_recip_est, 1),
load_upper_i(r_a, u4_hi(& gte_normalize_sqr_tbl)),
or_i_self(r_a, u4_lo(& gte_normalize_sqr_tbl)),
add_u(r_a, r_a, r_recip_est),
load_half(r_recip_est, r_a, 0),
nop,
gte_mv_to_data_r(r_recip_est, C2_IR0),
gte_mv_to_data_r(r_e, C2_IR1),
gte_mv_to_data_r(r_f, C2_IR2),
gte_mv_to_data_r(r_i, C2_IR3),
nop2,
gte_cmdw_gpf,
gte_mv_from_data_r(r_e, C2_MAC1),
gte_mv_from_data_r(r_f, C2_MAC2),
gte_mv_from_data_r(r_i, C2_MAC3),
shift_aright_var(r_e, r_e, r_shift),
shift_aright_var(r_f, r_f, r_shift),
shift_aright_var(r_i, r_i, r_shift),
store_word(r_e, r_b, O_(V3_S4,x)),
store_word(r_f, r_b, O_(V3_S4,y)),
store_word(r_i, r_b, O_(V3_S4,z)),
mac_yield()
})
typedef Struct_(Binds_ResolveLookAtPopAndTrans) {
U4 look_at; /* U4 (MT3_S2S4* — destination matrix address) */
};
+58 -44
View File
@@ -137,16 +137,23 @@ resolve_look_at_c11(MT3_S2S4* look_at, P3_S4* eye, P3_S4* target, V3_S4* up_in)
* After this returns, the smem.resolve_look_at_atom_addrs[] array contains valid MIPS atom pointers
* for the frame-time bundle helper to emit via tb_emit(tb, captured_addr).
*
* 7 atoms are within hello_camera.atom.c:
* 4 unique procs in hello_camera.atom.c (chain atoms 0, 2, 4, 6); atoms 1, 3, 5
* share the GENERIC normalize_v3s4_proc from gte.atom.c (called 3x with different
* O_(ResolveLookAtScratch,...) offsets):
* 0: resolve_look_at__input_and_sub_proc
* 1: resolve_look_at__normalize_fwd_to_uz_proc
* 1: normalize_v3s4_proc (fwd → uz; offsets 0, 16)
* 2: resolve_look_at__cross_uz_up_in_to_right_proc
* 3: resolve_look_at__normalize_right_to_ux_proc
* 3: normalize_v3s4_proc (right → ux; offsets 32, 48)
* 4: resolve_look_at__cross_uz_ux_to_up_proc
* 5: resolve_look_at__normalize_up_to_uy_proc
* 5: normalize_v3s4_proc (up → uy; offsets 64, 80)
* 6: resolve_look_at__populate_and_translate_proc
*
* (gte.atom.c contains only normalize_v3s4_proc — bundle-specific scratch layout is no longer exposed to the GTE primitives file.)
* Task 12.16 promotion: the bundle-specific resolve_look_at__chain_normalize_proc
* has been promoted to the generic normalize_v3s4_proc (gte.atom.c), which now
* takes r_scratch + r_src_offset + r_dst_offset as U4 parameters. The 3 callers
* pass O_(ResolveLookAtScratch,...) macros as offset args. The metaprogram emits
* one set of `atom_offset__normalize_v3s4__srav_path__aligned_done` defs
* (namespaced by atom name) in duffle/gen/offsets.h, shared by all 3 callers.
*
* GPR pool per atom: 10 free GPRs (R_T0..R_T3 + R_T5..R_T7 + R_V0 + R_V1 + R_AT).
* R_T4 is reserved as the wave-context carrier (R_ResolveScratch).
@@ -168,65 +175,76 @@ internal void resolve_look_at_init(void) {
R_T2, /* r_up_in_ptr (popped from tape) */
R_T3, R_T5, R_T6, R_T7); /* r_tmp<0-3> */
/* Atom 1: resolve_look_at__normalize_fwd_to_uz — src=scratch+0, dst=scratch+16 (HARDCODED in body).
* GPR pool: r_scratch (R_T4 carrier) + 10 body GPRs = 11.
* r_a/r_b (R_T0/R_T1) : src/dst pointers (r_a overlaps r_recip_est after the loads)
* r_e/r_f/r_i (R_T2/R_T3/R_T5) : src.x/y/z → result.x/y/z
* r_d/r_g (R_T6/R_T7) : MAC1/2 scratch (dead after stage 2)
* r_h (R_V0) : LZCR
* r_recip_est (R_V1), r_shift (R_AT) : saved throughout */
/* Atom 1: normalize_v3s4_proc (generic, from gte.atom.c) — src=scratch+0=fwd, dst=scratch+16=uz.
* The proc takes r_src_offset + r_dst_offset as U4 PARAMETERS — we pass the O_(...) macros here (evaluating to numeric literals 0 and 16).
* The 4-stage body is identical across the 3 call sites (atoms 1, 3, 5); only the offset args differ.
* GPR pool: r_scratch (R_T4 carrier) + 9 body GPRs = 10.
* r_src_ptr (R_T0) : src ptr
* r_dst_ptr (R_T1) : dst ptr
* r_tmp (R_T2) : unused (reserved for symmetry)
* r_mac1_scratch (R_T3) : MAC1 scratch
* r_mac2_scratch (R_T5) : src.x → result.x (carries through stages 1-2)
* r_recip_est (R_T6) : src.y → result.y
* r_lzcr (R_T7) : |v|² accumulator + srav amount (single reg)
* r_shift (R_V0) : LZCR (saved across stages 3-4)
* r_branch_tmp (R_V1) : src.z → result.z (reused after stage 1)
*/
smem.resolve_look_at_atom_addrs[1] = (MipsAtom*)u4_v(ab->start + ab->used * sizeof(U4));
resolve_look_at__normalize_fwd_to_uz_proc(ab,
R_ResolveScratch, /* r_scratch (wave-context carrier; src/dst base) */
R_T0, R_T1, /* r_a, r_b (src/dst ptrs) */
R_T2, R_T3, R_T5, /* r_e, r_f, r_i (src components) */
R_T6, R_T7, /* r_d, r_g (MAC scratch) */
R_V0, /* r_h (LZCR) */
R_V1, /* r_recip_est */
R_AT); /* r_shift */
normalize_v3s4_proc(ab, R_ResolveScratch, /* r_scratch (wave-context carrier) */
O_(ResolveLookAtScratch, fwd), /* r_src_offset = 0 */
O_(ResolveLookAtScratch, uz), /* r_dst_offset = 16 */
R_T0, R_T1, R_T2, /* r_src_ptr, r_dst_ptr, r_tmp */
R_T3, /* r_mac1_scratch */
R_T5, /* r_mac2_scratch */
R_T6, /* r_recip_est */
R_T7, /* r_lzcr */
R_V0, /* r_shift */
R_V1); /* r_branch_tmp */
/* Atom 2: resolve_look_at__cross_uz_up_in_to_right — a=scratch+16, b=scratch+128,
* out=scratch+32 (HARDCODED in body). GPR pool: r_scratch + 7 body + R_AT + R_V0 = 10. */
smem.resolve_look_at_atom_addrs[2] = (MipsAtom*)u4_v(ab->start + ab->used * sizeof(U4));
resolve_look_at__cross_uz_up_in_to_right_proc(ab,
R_ResolveScratch, /* r_scratch (wave-context carrier; src/dst base) */
resolve_look_at__cross_uz_up_in_to_right_proc(ab, R_ResolveScratch, /* r_scratch (wave-context carrier; src/dst base) */
R_T0, R_T1, R_T2, /* r_a, r_b, r_c (a.x/y/z → out.x/y/z) */
R_T3, /* r_d (b.x) */
R_T5, /* r_f (out ptr = scratch+32) */
R_T6, /* r_g (a ptr = scratch+16) */
R_T7); /* r_h (b ptr = scratch+128) */
/* Atom 3: resolve_look_at__normalize_right_to_ux — src=scratch+32, dst=scratch+48 (HARDCODED). */
/* Atom 3: normalize_v3s4_proc (generic, from gte.atom.c) — src=scratch+32=right, dst=scratch+48=ux. */
smem.resolve_look_at_atom_addrs[3] = (MipsAtom*)u4_v(ab->start + ab->used * sizeof(U4));
resolve_look_at__normalize_right_to_ux_proc(ab,
R_ResolveScratch,
R_T0, R_T1,
R_T2, R_T3, R_T5,
R_T6, R_T7,
normalize_v3s4_proc(ab, R_ResolveScratch,
O_(ResolveLookAtScratch, right), /* r_src_offset = 32 */
O_(ResolveLookAtScratch, ux), /* r_dst_offset = 48 */
R_T0, R_T1, R_T2,
R_T3,
R_T5,
R_T6,
R_T7,
R_V0,
R_V1,
R_AT);
R_V1);
/* Atom 4: resolve_look_at__cross_uz_ux_to_up — a=scratch+16, b=scratch+48, out=scratch+64 (HARDCODED). */
smem.resolve_look_at_atom_addrs[4] = (MipsAtom*)u4_v(ab->start + ab->used * sizeof(U4));
resolve_look_at__cross_uz_ux_to_up_proc(ab,
R_ResolveScratch,
resolve_look_at__cross_uz_ux_to_up_proc(ab, R_ResolveScratch,
R_T0, R_T1, R_T2,
R_T3,
R_T5, /* r_f (out ptr = scratch+64) */
R_T6, /* r_g (a ptr = scratch+16) */
R_T7); /* r_h (b ptr = scratch+48) */
/* Atom 5: resolve_look_at__normalize_up_to_uy — src=scratch+64, dst=scratch+80 (HARDCODED). */
/* Atom 5: normalize_v3s4_proc (generic, from gte.atom.c) — src=scratch+64=up, dst=scratch+80=uy. */
smem.resolve_look_at_atom_addrs[5] = (MipsAtom*)u4_v(ab->start + ab->used * sizeof(U4));
resolve_look_at__normalize_up_to_uy_proc(ab,
R_ResolveScratch,
R_T0, R_T1,
R_T2, R_T3, R_T5,
R_T6, R_T7,
normalize_v3s4_proc(ab, R_ResolveScratch,
O_(ResolveLookAtScratch, up), /* r_src_offset = 64 */
O_(ResolveLookAtScratch, uy), /* r_dst_offset = 80 */
R_T0, R_T1, R_T2,
R_T3,
R_T5,
R_T6,
R_T7,
R_V0,
R_V1,
R_AT);
R_V1);
/* Atom 6: resolve_look_at__populate_and_translate — write look_at->m[][] from ux/uy/uz (computed from r_scratch+offset internally),
then compute translation column t[] = R * (-eye). GPR pool: r_look_at + r_scratch + 4 ptr regs + 3 tmp regs = 9. */
@@ -340,10 +358,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf)
}
// Camera look at (Tape)
{
MT3_S2S4* look_at = & smem.cam.look_at;
P3_S4* eye = & smem.cam.pos;
V3_S4* up_in = & v3s4(0, -fp_one, 0);
tb.used = 0; tb_scope_run(& tb) {
resolve_look_at(& tb, & smem.cam.look_at, & smem.cam.pos, & smem.cube.pos, & v3s4(0, -fp_one, 0));
}
+3 -3
View File
@@ -515,8 +515,7 @@ local function splice_c_lines(source)
local splice_len = nil
if byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_NEWLINE then
splice_len = 2
elseif byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_CR
and source:byte(pos + 2) == BYTE_NEWLINE then
elseif byte == BYTE_BACKSLASH and source:byte(pos + 1) == BYTE_CR and source:byte(pos + 2) == BYTE_NEWLINE then
splice_len = 3
end
@@ -2095,7 +2094,8 @@ local E_MAC_PREFIX_LEN = 4
--- * Unknown `mac_X` (not in `component_index`): fall back to `word_counts[ident]` if present; otherwise emit one opaque event so the cycle budget accounts for the word.
--- * Marker Tokens (`atom_label(...)` / `atom_offset(...)`): Zero events (they are pure metaprogram hints).
---
--- Cycle protection: a per-expansion `visiting` set tracks components currently on the expansion stack; a re-entry produces a deterministic `{kind = "cycle", ...}` error and aborts that branch (does NOT hang, does NOT recurse).
--- Cycle protection: a per-expansion `visiting` set tracks components currently on the expansion stack;
--- a re-entry produces a deterministic `{kind = "cycle", ...}` error and aborts that branch (does NOT hang, does NOT recurse).
---
--- 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.
+1 -2
View File
@@ -47,8 +47,7 @@ local function find_repo_root()
return root
end
--- Set `package.path` (for `require("duffle")` + `require("passes.X")`) and
--- `package.cpath` (for `lpeg.dll`).
--- Set `package.path` (for `require("duffle")` + `require("passes.X")`) and `package.cpath` (for `lpeg.dll`).
---
--- 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).
+4 -4
View File
@@ -144,10 +144,10 @@ local function build_user_pins(corpus)
end
-- Find every physical GPR referenced in the atom body, via EITHER:
-- (a) a hardcoded physical GPR ident (R_T\d+|R_V\d+|R_A\d+|R_S\d+) — the existing regex;
-- (b) an alias ident (R_<Alias>) resolved via alias_to_gpr back to its physical GPR ident.
-- Returns { [physical_gpr_ident] = count }. The clash-detection and source-pool-exclusion logic
-- only needs the presence of each GPR (boolean test), but keeping the count preserves the
-- (a) A hardcoded physical GPR ident (R_T\d+|R_V\d+|R_A\d+|R_S\d+) — the existing regex;
-- (b) An alias ident (R_<Alias>) resolved via alias_to_gpr back to its physical GPR ident.
-- Returns { [physical_gpr_ident] = count }. Clash-detection and source-pool-exclusion logic
-- only needs the presence of each GPR (boolean test), but keeping count preserves the
-- original find_hardcoded_rn shape so callers can switch without churn.
-- The alias pattern is sorted lexicographically to keep the regex deterministic.
local function find_used_gprs(body_text, alias_to_gpr)
+37 -35
View File
@@ -3,9 +3,12 @@
--- Ownership: `corpus.word_counts`, `corpus.components`, and `corpus.component_body_index`.
--- Scanner owns `declaration_comment` and `debug_skip` on each declaration record; this pass projects both forward.
---
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)`, `MipsAtomComp_Proc_(ac_X, { body })`, and `MipsAtom_Proc_(X, ab, { body })` declarations,
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations (kind="comp_bare" / "comp_proc"),
--- then resolves the function-args string from the preceding `FI_ Slice_MipsCode ac_X(...)` declaration via a backward walk.
---
--- `MipsAtom_Proc_(X, ab, { body })` declarations (kind="atom_proc") are ATOMS, not components, and are deliberately excluded —
--- atoms get emitted via `tb_emit(tb, code_<name>)` linker symbols, not inlined as `mac_*` macros.
---
--- Emits one `gen/macs.h` per *immediate source directory* with `#define mac_X(sig) \` macros plus `WORD_COUNT(mac_X, N)` entries for downstream offset computation.
--- All sources inside the same directory contribute to the same file (per-directory aggregation).
--- The directory itself is the namespace, so the filename does not repeat the module name.
@@ -76,7 +79,7 @@ local MACS_FILENAME = "macs.h"
--- @field args string|nil -- Function-args string (function form only)
--- @field line integer -- Source line of the declaration
--- @field comment string|nil -- Scanner-owned `declaration_comment`; the components pass reads it from the scanner record
--- @field kind string -- "comp_bare" | "comp_proc" | "atom_proc"
--- @field kind string -- "comp_bare" | "comp_proc" (atom_proc is NOT a component — see `project_components`)
--- @field debug_skip boolean -- Mirror of `a.debug_skip` (scanner-owned); true iff a bare `atom_dbg_skip` marker immediately preceded the declaration
-- ════════════════════════════════════════════════════════════════════════════
@@ -200,16 +203,17 @@ end
local function project_components(source, scan)
local out = {}
for _, a in ipairs(scan.atoms) do
if a.kind == "comp_bare" or a.kind == "comp_proc" or a.kind == "atom_proc" then
-- `MipsAtom_Proc_` atoms have no `FI_ Slice_MipsCode ac_X(...)` function-decl prelude
-- (the macro sits inside a wrapping `I_ void <proc_name>(...)` body), so the function-args
-- lookup is meaningless; signature defaults to `...` (variadic-ignored).
-- The `mac_<name>` alias expansion discards the `ab` (atom-builder) arg the same way
-- `MipsAtomComp_Proc_` components do.
local args = nil
if a.kind ~= "atom_proc" then
args = find_function_args_for(source, a.raw_name, a.ident_pos)
end
-- Only `MipsAtomComp_(ac_X)` (kind="comp_bare") and `MipsAtomComp_Proc_(ac_X, ...)` (kind="comp_proc")
-- are COMPONENTS — they get inlined via `mac_<name>` aliases inside atom bodies.
-- `MipsAtom_Proc_` (kind="atom_proc") is an ATOM (ends with `mac_yield()`); it gets emitted via
-- `tb_emit(tb, code_<name>)` (linker symbol), NOT inlined as a macro. Including `atom_proc` here
-- would incorrectly emit `mac_<name>` aliases for atoms, polluting `gen/macs.h`.
-- See `docs/duffle_dsl_primer.md` §"mac_* aliases" for the contract.
if a.kind == "comp_bare" or a.kind == "comp_proc" then
-- Function-args lookup is meaningful for `MipsAtomComp_Proc_` components
-- (the macro sits inside `FI_ Slice_MipsCode ac_X(...)`); the alias expansion
-- discards the `ab` (atom-builder) arg the same way both forms do.
local args = find_function_args_for(source, a.raw_name, a.ident_pos)
-- Comment ownership: scan_source.lua stamps `declaration_comment` on the record by walking backward past any associated bare marker.
-- The pass reads `declaration_comment` directly.
local comment = a.declaration_comment or ""
@@ -221,7 +225,7 @@ local function project_components(source, scan)
body_tokens = a.body_tokens,
args = args,
comment = comment,
kind = a.kind, -- "comp_bare" | "comp_proc" | "atom_proc"; provenance emitter reads this.
kind = a.kind, -- "comp_bare" | "comp_proc"; provenance emitter reads this.
debug_skip = a.debug_skip == true,
}
end
@@ -400,8 +404,7 @@ local function cycle_cost_rec(name, comp_by_name, latency, cache)
end
--- (internal) Recursive GP0 prim-buffer contribution. Count `store_word` / `store_half` / `store_byte`
--- calls in the component body that target `R_PrimCursor` (these are the
--- RAM-side prim-buffer words the macro contributes), recursing through nested `mac_*` calls.
--- calls in the component body that target `R_PrimCursor` (these are the RAM-side prim-buffer words the macro contributes), recursing through nested `mac_*` calls.
--- Only `R_PrimCursor`-targeting stores count. Stores targeting other registers (e.g. `R_OtBase`, heap pointers) are not prim-buffer contributions.
--- @param name string
--- @param comp_by_name table<string, Component>
@@ -484,10 +487,9 @@ end
--- Determine the macro signature: function-args list (function form) or variadic-ignored (bare form).
--- For `MipsAtomComp_Proc_` components, the leading `ab` (atom-builder) arg is dropped:
--- the generated `mac_<name>` macros are inline-expansion aliases for baked atoms; their bodies
--- don't reference `ab` (the builder is only consumed by the procedural `atombuilder_unroll` line
--- that `MipsAtomComp_Proc_` appends after the body). Inline callers therefore don't need to thread
--- a builder context.
--- the generated `mac_<name>` macros are inline-expansion aliases for baked atoms; their bodies don't reference `ab`
--- (the builder is only consumed by the procedural `atombuilder_unroll` line that `MipsAtomComp_Proc_` appends after the body).
--- Inline callers therefore don't need to thread a builder context.
--- @param args_str string|nil
--- @return string
local function signature_from_args(args_str)
@@ -544,7 +546,7 @@ local function build_component_lines(c, counts)
-- Marker comment: emitted once for every skipped component.
-- The marker is scanner-owned (declared by `atom_dbg_skip` immediately before the declaration in the source);
-- the components pass projects `c.debug_skip` and emits the marker as a generated comment.
-- This pass projects `c.debug_skip` and emits the marker as a generated comment.
if c.debug_skip then
lines[#lines + 1] = "/* atom_dbg_skip */"
end
@@ -578,8 +580,8 @@ end
--- Build the boilerplate header lines (the `#ifdef INTELLISENSE_DIRECTIVES` block,
--- the `// Auto-generated` comment, the `// Source:` line, and the self-contained `WORD_COUNT` macro definition).
--- @param dir string -- the absolute source directory
--- @param sources SourceFile[] -- sources contributing to this directory (for the header comment)
--- @param dir string -- Absolute source directory
--- @param sources SourceFile[] -- Sources contributing to this directory (for the header comment)
--- @return string[]
local function header_boilerplate(dir, sources)
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" }
@@ -610,9 +612,9 @@ end
--- Compute the per-directory output path for `.macs.h`.
--- e.g. any source in `code/duffle/` produces `code/duffle/gen/macs.h` regardless of source filename.
--- The directory name is the namespace; the filename does not repeat it.
--- @param dir string -- the absolute source directory
--- @return string -- the output directory
--- @return string -- the full output path
--- @param dir string -- Absolute source directory
--- @return string -- Output directory
--- @return string -- Full output path
local function compute_macs_h_path(dir)
local out_dir = dir .. "/" .. GEN_SUBDIR
local out_path = out_dir .. "/" .. MACS_FILENAME
@@ -622,11 +624,11 @@ end
--- Emit a per-directory `.macs.h` header with the aggregated `mac_X` macros + `WORD_COUNT` entries.
--- Writes in BINARY mode so LF line endings are preserved (the git blob is LF; Windows text-mode would emit CRLF and break the byte-identical diff).
--- @param ctx PassCtx
--- @param dir string -- the absolute source directory
--- @param sources SourceFile[] -- sources contributing to this directory (for the header comment)
--- @param components Component[] -- aggregated components from all sources in this directory
--- @param counts table<string, integer> -- precomputed word counts (from count_all_components)
--- @return string|nil -- path to the written file (nil if no components)
--- @param dir string -- Absolute source directory
--- @param sources SourceFile[] -- Sources contributing to this directory (for the header comment)
--- @param components Component[] -- Aggregated components from all sources in this directory
--- @param counts table<string, integer> -- Precomputed word counts (from count_all_components)
--- @return string|nil -- Path to the written file (nil if no components)
local function emit_component_macros_h(ctx, dir, sources, components, counts)
if #components == 0 then return nil end
local out_dir, out_path = compute_macs_h_path(dir)
@@ -665,11 +667,11 @@ local function update_canonical_word_counts(corpus, components, counts)
end
--- @class ComponentDef
--- @field name string -- bare name (without ac_/mac_ prefix)
--- @field line integer -- definition source line (line of `MipsAtomComp_(ac_X)` / `MipsAtomComp_Proc_(ac_X, ...)`)
--- @field path string -- absolute source path of the definition
--- @field kind string -- "comp_bare" | "comp_proc" | "atom_proc"
--- @field debug_skip boolean -- mirror of the scanner-owned `a.debug_skip`; consumers read this directly
--- @field name string -- Bare name (without ac_/mac_ prefix)
--- @field line integer -- Definition source line (line of `MipsAtomComp_(ac_X)` / `MipsAtomComp_Proc_(ac_X, ...)`)
--- @field path string -- Absolute source path of the definition
--- @field kind string -- "comp_bare" | "comp_proc" (atom_proc is NOT a component)
--- @field debug_skip boolean -- Mirror of the scanner-owned `a.debug_skip`; consumers read this directly
--- (internal) Populate `corpus.components` with this source's components-by-name map.
--- First declaration wins; later declarations of the same bare name are dropped and recorded as a collision via `corpus.collisions` (kind = "component").
+12 -11
View File
@@ -703,9 +703,9 @@ end
--- `{comp_name, call_file, call_line, comp_file, comp_line, start_pos, end_pos, body_lines, debug_skip}`. `body_lines[k]`
--- is the k-th word's source line within the component body.
---
--- @param corpus table -- the corpus from `ctx.shared.corpus`
--- @param corpus table -- From `ctx.shared.corpus`
--- @param addrs table -- ELF symbols keyed by atom name from `elf_dwarf.read_nm`
--- @return table[] -- list of {name, addr, size_bytes, words, entries, invocations, debug_skip?}
--- @return table[] -- List of {name, addr, size_bytes, words, entries, invocations, debug_skip?}
local function build_atom_table(corpus, addrs)
-- Cross-ref: keep only atoms present in BOTH the nm symbol table AND `corpus.atoms_by_name`. Output is sorted by ascending addr.
local atoms_by_name = corpus.atoms_by_name or {}
@@ -834,10 +834,10 @@ end
--- This is intentional: silently falling back to a hardcoded GPR would mask the missing opt-in.
---
--- Pre-tokenized: `body_tokens` is the scan-source pass's pre-split list of top-level statements (each entry is a single `load_*` call or other statement).
--- @param body_tokens table[] -- the atom's pre-tokenized body statements (from atom.body_tokens)
--- @param binds_name string -- expected Binds_X name (skip pairs with mismatching binds)
--- @param registries table -- merged registries from collect_per_source_registries
--- @return table[] -- list of {reg = <MIPS index>, field = <field name>}
--- @param body_tokens table[] -- The atom's pre-tokenized body statements (from atom.body_tokens)
--- @param binds_name string -- Expected Binds_X name (skip pairs with mismatching binds)
--- @param registries table -- Merged registries from collect_per_source_registries
--- @return table[] -- List of {reg = <MIPS index>, field = <field name>}
local function parse_body_load_pairs(body_tokens, binds_name, registries)
local pairs = {}
local reg_index_by_name = (registries and registries.register_alias_registry) or {}
@@ -880,9 +880,9 @@ end
--- The piece chain uses (DW_OP_regN, DW_OP_piece, ULEB128(field_size)).
---
--- Binds fields come from `scan.binds`; the per-source `scan.binds[i].fields` already carries the typed-field record after the scan-source generalization.
--- @param corpus table -- the corpus from `ctx.shared.corpus`
--- @param atom_table table[] -- the cross-ref'd atom table from build_atom_table
--- @param registries table -- merged registries from collect_per_source_registries
--- @param corpus table -- From `ctx.shared.corpus`
--- @param atom_table table[] -- Cross-ref'd atom table from build_atom_table
--- @param registries table -- Merged registries from collect_per_source_registries
--- @return table, table -- (rbind_atoms, rbind_structs)
local function parse_rbind_atoms(corpus, atom_table, registries)
registries = registries or {}
@@ -944,7 +944,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
binds = ai.binds,
fields = struct.fields, -- {name, offset} from scan.binds
bytes = struct.bytes,
regs = pairs, -- ordered list of {reg, field}
regs = pairs, -- Ordered list of {reg, field}
info_line = ai.info_line,
}
table.insert(struct.atom_names, atom_name)
@@ -1780,7 +1780,8 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
emit(uleb128(ABBREV_TYPED_VIEW_POINTER)) -- DW_TAG_pointer_type (abbrev 110; NOT 9; void chain target)
emit(elf_dwarf.write_u32_le(ref4_of(void_chain_offset))) -- 4-byte ref4: points at the void base_type's tag byte
-- type_chain_offsets["void|1"] is what step (f) of the per-RR_<R_Name> chain looks up.
type_chain_offsets["void|1"] = void_chain_offset -- both the base_type offset and the pointer_type are emitted consecutively; the OUTERMOST is the pointer_type. The variable's DW_AT_type must reference the pointer_type, not the base_type. Patch below.
type_chain_offsets["void|1"] = void_chain_offset -- both the base_type offset and the pointer_type are emitted consecutively; the OUTERMOST is the pointer_type.
-- The variable's DW_AT_type must reference the pointer_type, not the base_type. Patch below.
-- Capture the pointer_type's offset (the last-thing-emitted DIE start) and overwrite the lookup.
-- The pointer_type was emitted as: uleb(9) (1 byte) + 4-byte ref4 = 5 bytes. Its tag byte is at void_chain_offset + 8 (the base_type's 8 bytes: 1 tag + 5 name + 1 byte_size + 1 encoding).
local ptr_void_offset = void_chain_offset + 8
+8
View File
@@ -4,9 +4,17 @@
--- for `MipsAtom_(name)` and `MipsCode code_<name>` declarations, computes the word offset
--- from each `atom_offset(F, T)` marker to its target `atom_label(T)` declaration, and emits
--- `gen/offsets.h` with one `#define _atom_offset_F_T = N` per branch.
---
--- Per-directory aggregation: every source in the same directory contributes to the same `gen/offsets.h`.
--- The directory itself is the namespace; the filename does not repeat the module name.
---
--- (Task 12.16 note: atom-namespaced enum names — e.g., `atom_offset__normalize_v3s4__srav_path__aligned_done` —
--- were considered to prevent cross-atom label collisions, but the C-side `atom_offset(F, T)` macro in
--- `code/duffle/dsl.atom.h` doesn't know the current atom_name at expansion time, so any namespacing
--- on the metaprogram side breaks the C build. Reverted. The C-side would need a per-atom
--- `CURRENT_ATOM` #define (set by `MipsAtom_`/`MipsAtom_Proc_` macros) plus an updated `atom_offset`
--- macro that uses it. That's a coordinated refactor — deferred to a future track.)
---
--- The offset is `target_word - branch_word - 1` (the standard MIPS branch-immediate encoding: branch_offset = relative_pc_in_words - 1).
-- ════════════════════════════════════════════════════════════════════════════
+3 -3
View File
@@ -39,8 +39,8 @@
--- `── Info` section renders finding-level info between `── Warnings` and the per-atom cycle counts.
---
--- The structural handshake checks (`mac_yield_uniformity`, `hazard_nop_use`, `control_transfer_delay_slot_use`) skip atoms/components with `debug_skip == true`.
--- The `atom_dbg_skip` marker designates runtime-helper declarations whose structure is fixed by the tape runtime (e.g. `tape_exit`, `ac_yield`).
--- Flagging them as "missing mac_yield" or "BD slot is redundant" is signal noise, not a logic failure.
--- `atom_dbg_skip` marker designates runtime-helper declarations whose structure is fixed by the tape runtime (e.g. `tape_exit`, `ac_yield`).
--- Flagging them as "missing mac_yield" or "BD slot is redundant".
--- Other checks (transfer_hazards, gpu_portstore_shape, abi_handoff, enum_alias_membership, …) still apply to debug_skip declarations because real hazards / typos can still surface in them.
---
--- The orchestrator (`ps1_meta.lua`) wires this module in via the PASSES table:
@@ -452,7 +452,7 @@ local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
end
-- True iff `consumer_event` reads the GPR operand at any position the destination register occupies.
-- The read-position lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match.
-- read_pos lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match.
local function is_gpr_consumer_of(consumer_event, destination)
local consumer_token = consumer_event.encoder or consumer_event.ident
local read_pos = duffle.OPERAND_READ_POSITIONS or {}