compiles..

This commit is contained in:
ed
2026-06-01 22:58:29 -04:00
parent d776d71574
commit 28bb20d6fe
3 changed files with 220 additions and 216 deletions
+99 -68
View File
@@ -5,6 +5,64 @@
# include "mips.h"
#endif
/* ============================================================================
* gte.h — Geometry Transformation Engine (COP2) for the PS1
* ============================================================================
*
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word`
* constants from C. No GCC inline-assembly string syntax in the code body.
*
* PHILOSOPHY
* ----------
* 1. A 32-bit instruction word is composed from per-field encoders. Each
* encoder knows only its own bit range; the composite ORs them together.
* No magic numbers inside any encoder body — every shift and mask is a
* named constant from the bitfield-layout enum below.
*
* 2. Pure (compile-time) instructions — every GTE *command* (RTPS, RTPT,
* NCLIP, MVMVA, …) and every COP2 *transfer* (ctc2/cfc2) with a constant
* rs/rt/rd — are emitted as a single integer constant via
* `asm_inline(...)` from gcc_asm.h. The C compiler constant-folds
* these into `.word` directives in .rodata.
*
* 3. Runtime-base-register instructions (lwc2, swc2, lw, sw, …) cannot be
* a pure compile-time word because the `rs` field is chosen by the
* compiler at codegen. For these we use a "placeholder-pun" pattern:
* a fixed register number (R_T4 = $12) is baked into the rs field of
* the `.word` constant, and the macro declares a `"r"(arg)` input
* constraint plus a clobber on the same register. The compiler is
* therefore *forced* to bind `arg` to that exact register, and the
* constant is correct.
*
* USAGE
* -----
* // Pure command sequence — all bits compile-time:
* asm volatile(
* asm_inline( gte_cmd_rtpt , gte_cmd_nclip , gte_cmd_avsz3 )
* asm_clobber( clb_system )
* );
*
* // Runtime-base-register load — uses R_T4 ($12) under the hood:
* gte_load_v0( my_svector_ptr );
*
* STYLE NOTES
* -----------
* - Per-field encoders are named `enc_gte_<field>(value)` and each one
* self-masks its argument before shifting. Mirrors the `enc_op / enc_rs
* / enc_rt / ...` family in mips.h.
* - The composite `enc_gte_cmdw(sf, mx, v, cv, lm, cmd)` is a flat OR of
* the per-field encoders, plus the COP2/CO base.
* - Pre-baked shortcuts (`gte_cmd_rtpt`, `gte_cmd_rtps`, …) are defined
* for the common cases so call sites read like assembly source.
* - All register/field values are enums (not `#define`s) so they show up
* in debugger symbol tables and IDE autocomplete.
*
* SEE ALSO
* --------
* - gcc_asm.h: the `.word` emitter (`asm_inline`, `asm_clobber`, clobbers)
* - mips.h: the MIPS encoder layer this builds on
*/
/* C2 data registers */
/* --- GTE Data Registers (Coprocessor 2) --- */
@@ -199,86 +257,58 @@ enum { _C2_OPS_ = 0
#define gte_lwc2_v2_RT4 enc_cop2_lwc2(gte_in_v2_xy, R_T4, 0)
#define gte_lwc2_v2z_RT4 enc_cop2_lwc2(gte_in_v2_z, R_T4, 4)
/* The actual call-site macros — zero string syntax in the .word body.
/* gte_load_vN(r_ptr) — placeholder-punned lwc2 loaders
*
* The "r"(r_ptr) input constraint is the irreducible GCC-syntax bit: the
* base register of lwc2 is a runtime value, so the compiler must allocate
* one for us. The "$12" clobber + the .word constants having rs=R_T4 ($12)
* hardwired form the "placeholder-pun" — GCC is forced to bind r_ptr to
* $12, which is exactly the register the .word constants expect.
* Each emits a small sequence of `.word` constants that encode `lwc2 $N,
* off($12)` for the chosen GTE vector register. The base register is
* forced to be R_T4 ($12) at runtime via:
* - `"r"(r_ptr)`: GCC picks a GPR for `r_ptr`
* - `"$12"` in the clobber list: GCC can't put any other live value in $12
* - Net effect: GCC must place `r_ptr` in $12, the register the .word
* constants expect.
*
* Uses asm_block_4(code, outs, ins, clb) from gcc_asm.h.
* asm_inline(...) produces the 2-colon code:outputs:inputs body
* "r"(r_ptr) is the runtime-input section body
* "$2", ..., "$12" is the clobber section body
* asm_block_4() joins them with 3 colons and wraps in asm volatile
* Shape of the generated `asm volatile (...)`:
* code section : ".word %0, %1" (from asm_inline)
* outputs section : (empty, the 2nd colon)
* inputs section : "i"(w0), "i"(w1), "r"(r_ptr)
* clobbers section : "$2", "$8", ..., "$12" (from asm_clobber)
* 3 colons total, GCC-legal. No string-syntax mnemonics in the .word body.
*
* The parens `(...)` around each section let the preprocessor treat the
* section's contents as a single arg (shielding internal commas), and
* the `_strip` helper inside asm_block_4 removes those parens so the
* final C code is clean.
*/
* The `asm_clobber(...)` helper from gcc_asm.h prepends the colon that
* starts the clobbers section. */
#define gte_load_v0(r_ptr) \
asm_block_4( \
(asm_inline( gte_lwc2_v0_RT4, gte_lwc2_v0z_RT4 )), \
(), \
("r"(r_ptr)), \
("$2", "$8", "$9", "$31", "memory", "$12") \
asm volatile( \
asm_inline( gte_lwc2_v0_RT4, gte_lwc2_v0z_RT4 ) \
, "r"(r_ptr) \
asm_clobber( "$2", "$8", "$9", "$31", "memory", "$12" ) \
)
#define gte_load_v1(r_ptr) \
asm_block_4( \
(asm_inline( gte_lwc2_v1_RT4, gte_lwc2_v1z_RT4 )), \
(), \
("r"(r_ptr)), \
("$2", "$8", "$9", "$31", "memory", "$12") \
asm volatile( \
asm_inline( gte_lwc2_v1_RT4, gte_lwc2_v1z_RT4 ) \
, "r"(r_ptr) \
asm_clobber( "$2", "$8", "$9", "$31", "memory", "$12" ) \
)
#define gte_load_v2(r_ptr) \
asm_block_4( \
(asm_inline( gte_lwc2_v2_RT4, gte_lwc2_v2z_RT4 )), \
(), \
("r"(r_ptr)), \
("$2", "$8", "$9", "$31", "memory", "$12") \
asm volatile( \
asm_inline( gte_lwc2_v2_RT4, gte_lwc2_v2z_RT4 ) \
, "r"(r_ptr) \
asm_clobber( "$2", "$8", "$9", "$31", "memory", "$12" ) \
)
/* All three at once -- the canonical prelude to gte_cmd_rtpt. */
/* gte_load_v0v1v2(r_ptr) — the canonical prelude to gte_cmd_rtpt.
* Loads all three GTE input vectors (6 words) from a contiguous array
* of three SVECTORs (24 bytes total). */
#define gte_load_v0v1v2(r_ptr) \
asm_block_4( \
(asm_inline( gte_lwc2_v0_RT4, gte_lwc2_v0z_RT4, \
gte_lwc2_v1_RT4, gte_lwc2_v1z_RT4, \
gte_lwc2_v2_RT4, gte_lwc2_v2z_RT4 )), \
(), \
("r"(r_ptr)), \
("$2", "$8", "$9", "$31", "memory", "$12") \
asm volatile( \
asm_inline( gte_lwc2_v0_RT4, gte_lwc2_v0z_RT4, \
gte_lwc2_v1_RT4, gte_lwc2_v1z_RT4, \
gte_lwc2_v2_RT4, gte_lwc2_v2z_RT4 ) \
, "r"(r_ptr) \
asm_clobber( "$2", "$8", "$9", "$31", "memory", "$12" ) \
)
/**
* @brief Loads a single SVECTOR to GTE vector register V1
*
* @details Loads values from an SVECTOR struct to GTE data registers C2_VXY1
* and C2_VZ1.
*/
// #define gte_load_v1( r0 ) __asm__ volatile ( \
// "lwc2 $2, 0( %0 );" \
// "lwc2 $3, 4( %0 );" \
// : \
// : "r"( r0 ) \
// : "$t0" )
/**
* @brief Loads a single SVECTOR to GTE vector register V2
*
* @details Loads values from an SVECTOR struct to GTE data registers C2_VXY2
* and C2_VZ2.
*/
// #define gte_load_v2( r0 ) __asm__ volatile ( \
// "lwc2 $4, 0( %0 );" \
// "lwc2 $5, 4( %0 );" \
// : \
// : "r"( r0 ) \
// : "$t0" )
#define gte_ldv0(r0) \
__asm__ volatile( \
"lwc2 $0, 0( %0 );" \
@@ -352,9 +382,10 @@ enum { _C2_OPS_ = 0
* ctc2 $13, $3 ; → C2_RT21
* ctc2 $14, $4 ; → C2_RT22
*
* Uses string-style GCC inline asm with `%0` substitution because the
* base register `r0` is a runtime GPR — the `lw` offsets use literal
* values (0, 4, 8, ...) so only the base register needs substitution.
* Uses the placeholder-pun: R_T4 ($12) is hard-wired into the `lw` base
* field of every `.word` constant, and the `"r"(r0)` constraint + `"$12"`
* clobber force GCC to put `r0` in $12 at runtime. The `lw` offsets are
* literal values (0, 4, 8, ...) so the only runtime GPR in play is $12.
*
* WARNING: Incomplete by design. The source macro only writes RT11..RT22
* (5 of 9 rotation elements); RT23 and the entire RT3x row are left