#ifdef INTELLISENSE_DIRECTIVES # pragma once # include "dsl.h" # include "gcc_asm.h" # include "mips.h" # include "gte.h" # include "memory.h" # include "atom_dsl.h" #endif typedef U4 const MipsCode; #define MipsAtom_(sym) MipsCode tmpl(code,sym) [] align_(4) = #pragma region Tape Drive /* --------------------------------------------------------------------------- * TAPE DRIVE ABI & REGISTER ALIASES * --------------------------------------------------------------------------- * We map the MIPS temporary registers to a persistent global workspace. * The C compiler is completely unaware of these bindings. * ---------------------------------------------------------------------------*/ enum { R_AtomJmp = R_T9, R_TapePtr = R_T8, /* The Instruction Stream Pointer */ R_InCursor = R_T4, /* Input data cursor */ R_PrimCursor = R_T7, /* VRAM output cursor (primitive buffer) */ R_FaceCursor = R_T4, /* Input data cursor (indices/faces) */ R_VertBase = R_T5, /* Base address of the vertex array */ R_OtBase = R_T6, /* Base address of the Ordering Table */ /* Stringification codes for the GCC inline assembler clobber lists */ #define R_TapePtr_Code R_T8_Code #define R_InCursor_Code R_T4_Code #define R_PrimCursor_Code R_T7_Code #define R_FaceCursor_Code R_T4_Code #define R_VertBase_Code R_T5_Code #define R_OtBase_Code R_T6_Code }; /* The 'Exit' Atom */ MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }; /* Generalized Tape Engine Runner */ FI_ void tape_run(Slice_U4 tape) { register U4* tp rgcc(R_TapePtr) = tape.ptr; asm volatile( asm_words( add_ui( R_SP, R_SP, -MipsStackAlignment) /* Allocate stack space */ , store_word( R_RA, R_SP, 0) /* Safely backup $ra to the stack */ , load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */ , add_ui_self(R_TapePtr, S_(MipsCode)) /* Advance tape */ , call_reg( R_AtomJmp) /* jalr $t9 */ , nop /* Branch delay slot */ , load_word( R_RA, R_SP, 0) /* Restore $ra from stack */ , add_ui_self(R_SP, MipsStackAlignment) /* Deallocate stack space */ ) asm_rpins, r_use(tp) asm_clobber: rlit(R_AT) , rlit(R_V0), rlit(R_V1) , rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3) /* Tell GCC the tape engine owns and destroys the workspace registers */ , rlit(R_PrimCursor), rlit(R_FaceCursor), rlit(R_VertBase), rlit(R_OtBase) , rlit(R_T9) , clb_mem_drain ); } typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; }; FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 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 }; } #define tb_emit_(tb, atom) tb_emit(tb, tmpl(code,atom)) FI_ void tb_emit(TapeBuilder* tb, MipsCode* 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_ Slice_U4 tb_end (TapeBuilder* tb) { tb_emit(tb,code_tape_exit); return (Slice_U4){ C_(U4*,tb->ptr), tb->used }; } FI_ Slice_U4 tb_slice(TapeBuilder tb) { return (Slice_U4){ C_(U4*,tb.ptr), tb.used }; } #define tb_scope(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_emit(tb,code_tape_exit)) #pragma endregion Tape Drive #pragma region Macro Mips Atom Components /* --------------------------------------------------------------------------- * MACRO ATOM Components (Reusable Assembly Components) * These do NOT yield. They are expanded inline inside Tape Atoms. * ---------------------------------------------------------------------------*/ /* The 'Yield' sequence for Tape Atoms. * Loads the next pointer from the tape, advances the tape, and jumps. * Cost: ~ 4 cycles */ #define mac_yield() \ load_word(R_AtomJmp, R_TapePtr, 0) \ , add_ui_self( R_TapePtr, S_(MipsCode)) \ , jump_reg( R_AtomJmp) \ , nop /* Words: 3; Loads 3 S2 indices from the face array */ #define mac_load_tri_indices(rId_0, rId_1, rId_2) \ load_half_u(rId_0, R_FaceCursor, 0 * S_(S2)) \ , load_half_u(rId_1, R_FaceCursor, 1 * S_(S2)) \ , load_half_u(rId_2, R_FaceCursor, 2 * S_(S2)) /* Words: 18; Translates indices to vertex addresses and pushes them to GTE */ #define mac_load_tri_verts(rId_0, rId_1, rId_2) \ shift_lleft(R_AT, rId_0, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0) \ , shift_lleft(R_AT, rId_1, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1) \ , shift_lleft(R_AT, rId_2, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2) /* Words: 11; Correctly inserts a primitive into the Ordering Table linked list */ #define mac_insert_ot_tag(r_otz, prim_type) \ shift_lleft( R_T1, r_otz, S_(U4)/2) /* T1 = r_otz * S_(U4) */ \ , add_u_self( R_T1, R_OtBase) /* T1 = & OrderingTable[OTZ] */ \ , load_word( R_AT, R_T1, O_(PolyTag,bf_addr_len)) /* AT = old_ot_head */ \ , load_upper_i(R_V0, (S_(prim_type)/S_(U4) - S_(PolyTag)/S_(U4)) << polytag_len_bits) /* V0 = S_(prim_type without tag field) */ \ , mask_upper( R_AT, R_AT, S_(polytag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \ , or_u( R_AT, R_AT, R_V0) /* Merge length */ \ , store_word( R_AT, R_PrimCursor, O_(PolyTag,bf_addr_len)) /* prim->tag = packed(prim_length, old_addr) */ \ , shift_lleft( R_AT, R_PrimCursor, S_(polytag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \ , shift_lright(R_AT, R_AT, S_(polytag_len_bits)) \ , store_word( R_AT, R_T1, O_(PolyTag,bf_addr_len)) /* OrderingTable[OTZ] = PrimCursor */ /* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given * byte offset. Internal helper used by the *_format_*_color macros. * Args: off = U4 byte offset, code = GP0 cmd byte (0 for c1/c2/c3 of * a Poly_G4), r/g/b = 8-bit RGB byte values. */ #define mac_pack_color_word(off, code, r,g,b) \ load_upper_i(R_AT, (code) << 8 | (b)) \ , or_i_self( R_AT, ((g) << 8) | (r)) \ , store_word( R_AT, R_PrimCursor, (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). * Migrated from hello_gte_tape.c; takes RGB form per the Phase 3 * convention. */ #define mac_format_f3_color(r,g,b) mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r,g,b) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3. * PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). * The macro name declares the pipeline position; check #6 (GTE state- * machine validation) verifies the call site matches the declaration. */ #define mac_gte_store_f3_post_rtpt() \ gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_F3,p0)) \ , gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)) \ , gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2)) /* 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. */ #define mac_format_g4_color(r0,g0,b0, r1,g1,b1, r2,g2,b2, r3,g3,b3) \ mac_pack_color_word(O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0) \ , mac_pack_color_word(O_(Poly_G4,c1), 0, r1,g1,b1) \ , mac_pack_color_word(O_(Poly_G4,c2), 0, r2,g2,b2) \ , mac_pack_color_word(O_(Poly_G4,c3), 0, r3,g3,b3) /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the * G4 triangle portion to p0/p1/p2. * PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, * SXY2=v2.screen). MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 * get overwritten with v3 (RTPS writes only to SXY2, but to keep the * three registers aligned with v0/v1/v2 you must store before RTPS). * The macro name declares the pipeline position; check #6 (GTE state- * machine validation) verifies the call site matches the declaration. */ #define mac_gte_store_g4_p012_post_rtpt_pre_rtps() \ gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_G4,p0)) \ , gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)) \ , gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2)) /* Words: 1; Stores the V3 screen coord to the G4's p3 slot. * PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its * single-vertex result to SXY2; SXY0 still holds v0.screen from the * earlier RTPT — DO NOT read SXY0 here, that's the bug this name * prevents). * The macro name declares the pipeline position; check #6 (GTE state- * machine validation) verifies the call site matches the declaration. * * History: this macro was named `mac_gte_store_g4_p3` until 2026-07-09 * when it was discovered to be reading C2_SXY0 (which held v0.screen) * instead of C2_SXY2 (which holds v3.screen after RTPS). The rename * encodes the pipeline position in the name so the next bug of this * class is impossible. */ #define mac_gte_store_g4_p3_post_rtps() gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p3)) #pragma endregion Macro Atom Components #pragma region Mips Atom Builder // This allows for runtime procedural authoring of mips atoms. typedef Struct_(FMipsAtom512) { U4 data[512]; U4 used; }; typedef Slice_(MipsCode); typedef Slice_MipsCode MipsAtom; // FArena Related typedef Relative_(FArena) Struct_(MipsAtomBuilder) { U4 start; U4 capacity; U4 used; }; // Whatever the builder is writting to should most likely coresspond // to something that can fit within instruction cache? FI_ void atombuilder_unroll(MipsAtomBuilder_R ab, Slice_MipsCode_R code) { assert(ab->capacity - ab->used - code->len); mem_copy(ab->start, u4_(code->ptr), 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)) // When done authoring, utilize this to cap-off the atom FI_ void atombuilder_end(MipsAtomBuilder_R ab) { LP_ MipsAtom_(yield) { mac_yield() }; mem_copy(ab->start, u4_(code_yield), S_(code_yield)); mem_bump(ab->start, ab->capacity, & ab->used, S_(code_yield)); } #define mipsatom_from_builder(ab) (MipsAtom){ab.start, ab.used} #pragma endregion Mips Atom Builder #pragma region Baked Mips Atoms // These atoms are resolved at compile time and are (usually) statically linked readonly data. enum { bios_flushcache = 0x44, bios_table_addr = 0xA0, }; /* Flushes the Instruction Cache (PSX A-function 0x44 via BIOS stub at 0xA0). * * Sequence (per MIPS ABI; arguments in arg registers, RA pushed to stack): * 1. sp -= 8; sw $ra, 4($sp) ; save RA * 2. $a0 = bios_flushcache (arg0) * 3. $t0 = bios_table_addr ; t0 = &BIOS A-function table * 4. jalr $t0, $ra ; call BIOS(flushcache) * nop ; branch delay slot * 5. lw $ra, 4($sp); jr $ra ; restore & return * 6. sp += 8 */ internal MipsAtom_(mips_flush_icache) { add_ui(rstack_ptr, rstack_ptr, -MipsStackAlignment) /* sp -= 8 */ , store_word(rret_addr, rstack_ptr, S_(U4)) /* sw $ra, 4($sp) */ , add_ui(rret_0, rdiscard, bios_flushcache) /* addiu $a0, $0, 0x44 */ , add_ui(rtmp_0, rdiscard, bios_table_addr) /* addiu $t0, $0, 0xA0 */ , jump_link(rtmp_0, rret_addr) /* jalr $t0, $ra */ , nop /* BD slot */ , load_word(rret_addr, rstack_ptr, S_(U4)) /* lw $ra, 4($sp) */ , jump_reg(rret_addr) /* jr $ra */ , add_ui(rstack_ptr, rstack_ptr, MipsStackAlignment) /* sp += 8 (BD) */ , mac_yield() }; typedef Struct_(Binds_SetGteWorld) { U4 transform; }; internal MipsAtom_(set_gte_world) { /* Pop matrix address from tape into R_T3 ($11) */ load_word(R_T3, R_TapePtr, O_(Binds_SetGteWorld,transform)), add_ui_self( R_TapePtr, S_(Binds_SetGteWorld)), /* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */ load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4), gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12), load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16), gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22), load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28), gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ), mac_yield() }; // TODO(Ed): I'm not sure yet if the bindings are redundant with the floortri atom yet. /* DIAGNOSTIC 1: Pure tape loop test */ internal MipsAtom_(diag_yield) { mac_yield() }; // TODO(Ed): Reduce magic numbers/offsets /* DIAGNOSTIC 2: Pure memory test (No GTE). Draws a fixed cyan triangle. */ internal MipsAtom_(diag_color) { store_word( R_0, R_T7, 0), load_upper_i(R_AT, gp0_cmd_poly_f3 << 8 | 0xFF), /* High: MipsCode Poly_F3(0x20) + Color B:FF */ or_i_self( R_AT, 0xFF00), /* Low: Color G:FF, R:00 (Cyan) */ store_word( R_AT, R_T7, 4), /* Fake coordinates - Swapped winding order to prevent GPU culling! */ load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 8), /* (16, 16) */ load_upper_i(R_AT, 0x0050), or_i_self(R_AT, 0x0010), store_word(R_AT, R_T7, 12), /* (80, 16) */ load_upper_i(R_AT, 0x0010), or_i_self(R_AT, 0x0050), store_word(R_AT, R_T7, 16), /* (16, 80) */ add_ui( R_T1, R_0, 10), shift_lleft_self(R_T1, S_(U4)/2), add_u_self( R_T1, R_T6), load_word( R_AT, R_T1, 0), load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << polytag_len_bits), store_word( R_AT, R_T7, 0), shift_lleft(R_AT, R_T7, S_(polytag_len_bits)), shift_lright(R_AT, R_AT, S_(polytag_len_bits)), or_u_self( R_AT, R_V0), store_word( R_AT, R_T1, 0), add_ui(R_T7, R_T7, 20), mac_yield() }; // TODO(Ed): Reduce magic numbers/offsets /* DIAGNOSTIC 3: Pure GTE test (No Memory Writes) */ internal MipsAtom_(diag_gte) { /* Load 3 indices */ load_half_u(R_T0, R_T4, 0), load_half_u(R_T1, R_T4, 2), load_half_u(R_T2, R_T4, 4), /* Load Vertices into GTE */ shift_lleft( R_AT, R_T0, 3), add_u( R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0), shift_lleft( R_AT, R_T1, 3), add_u(R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1), shift_lleft(R_AT, R_T2, 3), add_u(R_AT, R_AT, R_T5), load_word(R_V0, R_AT, 0), load_word(R_V1, R_AT, 4), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2), /* Run Math */ nop2, gte_cmdw_rtpt, nop2, gte_cmdw_nclip, nop2, /* Advance Face Cursor and Yield */ add_ui(R_T4, R_T4, 8), mac_yield() }; #pragma endregion Baked Mips Atoms