mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-06 15:48:49 +00:00
Compare commits
5
Commits
338f1fe46e
...
02658d3609
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02658d3609 | ||
|
|
dbc459b7e0 | ||
|
|
a704341fc6 | ||
|
|
7421b32fd7 | ||
|
|
e2eb74be19 |
Vendored
-1
@@ -138,7 +138,6 @@
|
||||
"monitor reset shellhalt",
|
||||
"load build/hello_gte.dwarf-injected.elf",
|
||||
"source scripts/gdb/gdb_tape_atoms.gdb",
|
||||
"source build/gen/hello_gte.gdbinit",
|
||||
"tbreak main",
|
||||
"continue"
|
||||
]
|
||||
|
||||
@@ -98,11 +98,11 @@ WORD_COUNT(mac_format_f3_color, 3)
|
||||
/* atom_dbg_skip */
|
||||
/* 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). */
|
||||
#define mac_gte_store_f3_post_rtpt(...) \
|
||||
#define mac_gte_store_f3(...) \
|
||||
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))
|
||||
WORD_COUNT(mac_gte_store_f3_post_rtpt, 3)
|
||||
WORD_COUNT(mac_gte_store_f3, 3)
|
||||
|
||||
#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) \
|
||||
@@ -115,25 +115,20 @@ WORD_COUNT(mac_format_g4_color, 12)
|
||||
/* 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(...) \
|
||||
* 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). */
|
||||
#define mac_gte_store_g4_p012(...) \
|
||||
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))
|
||||
WORD_COUNT(mac_gte_store_g4_p012_post_rtpt_pre_rtps, 3)
|
||||
WORD_COUNT(mac_gte_store_g4_p012, 3)
|
||||
|
||||
/* atom_dbg_skip */
|
||||
/* 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).
|
||||
* 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.
|
||||
*/
|
||||
#define mac_gte_store_g4_p3_post_rtps(...) \
|
||||
#define mac_gte_store_g4_p3(...) \
|
||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p3))
|
||||
WORD_COUNT(mac_gte_store_g4_p3_post_rtps, 1)
|
||||
WORD_COUNT(mac_gte_store_g4_p3, 1)
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(O_(Po
|
||||
|
||||
/* 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). */
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_f3_post_rtpt) {
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_f3) {
|
||||
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)),
|
||||
@@ -193,24 +193,19 @@ MipsAtomComp_Proc_(ac_format_g4_color, {
|
||||
/* 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. */
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p012_post_rtpt_pre_rtps) {
|
||||
* 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). */
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p012) {
|
||||
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).
|
||||
* 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.
|
||||
*/
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p3_post_rtps) { gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p3)) };
|
||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p3) { gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p3)) };
|
||||
|
||||
#pragma endregion Macro Atom Components
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#pragma region hello_gte_tape
|
||||
|
||||
|
||||
// --- atom: cube_g4_face (87 words) ---
|
||||
// --- atom: cube_g4_face (77 words) ---
|
||||
|
||||
#define _atom_offset_cull_cube_g4_face_exit 48
|
||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 12
|
||||
#define _atom_offset_cull_cube_g4_face_exit 42
|
||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 24
|
||||
|
||||
enum {
|
||||
atom_offset_cull_cube_g4_face_exit = _atom_offset_cull_cube_g4_face_exit,
|
||||
@@ -18,7 +18,7 @@ enum {
|
||||
// --- atom: floor_f3_face (58 words) ---
|
||||
|
||||
#define _atom_offset_culling_floor_f3_face_exit 25
|
||||
#define _atom_offset_bounds_chk_floor_f3_face_exit 13
|
||||
#define _atom_offset_bounds_chk_floor_f3_face_exit 16
|
||||
|
||||
enum {
|
||||
atom_offset_culling_floor_f3_face_exit = _atom_offset_culling_floor_f3_face_exit,
|
||||
@@ -0,0 +1,29 @@
|
||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||
// Source: C:\projects\Pikuma\ps1\code\hello_gte\hello_gte.tape.c
|
||||
#pragma once
|
||||
|
||||
#pragma region hello_gte.tape
|
||||
|
||||
|
||||
// --- atom: cube_g4_face (77 words) ---
|
||||
|
||||
#define _atom_offset_cull_cube_g4_face_exit 42
|
||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 24
|
||||
|
||||
enum {
|
||||
atom_offset_cull_cube_g4_face_exit = _atom_offset_cull_cube_g4_face_exit,
|
||||
atom_offset_bounds_chk_cube_g4_face_exit = _atom_offset_bounds_chk_cube_g4_face_exit,
|
||||
};
|
||||
|
||||
// --- atom: floor_f3_face (58 words) ---
|
||||
|
||||
#define _atom_offset_culling_floor_f3_face_exit 25
|
||||
#define _atom_offset_bounds_chk_floor_f3_face_exit 16
|
||||
|
||||
enum {
|
||||
atom_offset_culling_floor_f3_face_exit = _atom_offset_culling_floor_f3_face_exit,
|
||||
atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit,
|
||||
};
|
||||
|
||||
#pragma endregion hello_gte.tape
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#include "duffle/lottes_tape.h"
|
||||
#include "duffle/word_count.metadata.h"
|
||||
|
||||
# include "gen/gte_hello.offsets.h"
|
||||
# include "gen/hello_gte.offsets.h"
|
||||
#include "hello_gte.h"
|
||||
|
||||
#include "hello_gte_tape.c"
|
||||
#include "hello_gte.tape.c"
|
||||
|
||||
typedef U4 OrderingTable_Buffer[OrderingTbl_Len];
|
||||
typedef Array_(OrderingTable_Buffer, 2);
|
||||
@@ -4,7 +4,7 @@
|
||||
# include "duffle/atom_dsl.h"
|
||||
# include "duffle/lottes_tape.h"
|
||||
# include "duffle/word_count.metadata.h"
|
||||
# include "gen/gte_hello.offsets.h"
|
||||
# include "gen/hello_gte.offsets.h"
|
||||
# include "hello_gte.h"
|
||||
#endif
|
||||
|
||||
@@ -47,35 +47,34 @@ MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4),
|
||||
load_half_u(R_T3, R_FaceCursor, 3 * S_(S2)),
|
||||
|
||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||
nop2, gte_cmdw_rotate_translate_perspective_triple,
|
||||
nop2, gte_cmdw_nclip,
|
||||
nop2, gte_cmdw_rotate_translate_perspective_triple, // required cpu -> gte delay slot
|
||||
gte_cmdw_nclip,
|
||||
|
||||
nop2, gte_mv_from_data_r(R_T0, C2_MAC0),
|
||||
nop,
|
||||
gte_mv_from_data_r(R_T0, C2_MAC0), nop,
|
||||
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)), nop,
|
||||
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
|
||||
shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase),
|
||||
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),
|
||||
|
||||
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
|
||||
mac_format_g4_color(
|
||||
/* c0 magenta */ 0xFF, 0x00, 0xFF,
|
||||
/* c1 yellow */ 0xFF, 0xFF, 0x00,
|
||||
/* c2 cyan */ 0x00, 0xFF, 0xFF,
|
||||
/* c3 green */ 0x00, 0xFF, 0x00),
|
||||
mac_gte_store_g4_p012_post_rtpt_pre_rtps(),
|
||||
mac_gte_store_g4_p012(),
|
||||
gte_cmdw_rotate_translate_perspective_single,
|
||||
mac_gte_store_g4_p3(),
|
||||
|
||||
shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase),
|
||||
load_word(R_V0, R_AT, O_(V3_S2, x)), load_word(R_V1, R_AT, O_(V3_S2, z)),
|
||||
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
|
||||
gte_cmdw_avg_sort_z4,
|
||||
gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
|
||||
nop2, gte_cmdw_rotate_translate_perspective_single,
|
||||
mac_gte_store_g4_p3_post_rtps(),
|
||||
|
||||
nop2, gte_cmdw_avg_sort_z4,
|
||||
nop2, gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, cube_g4_face_exit)), nop,
|
||||
mac_insert_ot_tag_g4(),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, cube_g4_face_exit)), nop,
|
||||
mac_insert_ot_tag_g4(),
|
||||
mac_format_g4_color(
|
||||
/* c0 magenta */ 0xFF, 0x00, 0xFF,
|
||||
/* c1 yellow */ 0xFF, 0xFF, 0x00,
|
||||
/* c2 cyan */ 0x00, 0xFF, 0xFF,
|
||||
/* c3 green */ 0x00, 0xFF, 0x00),
|
||||
// end: branch(bounds_chk)
|
||||
// end: branch(cull)
|
||||
|
||||
atom_label(cube_g4_face_exit)
|
||||
add_ui_self(R_PrimCursor, S_(Poly_G4)), /* 9 words = Poly_G4 */
|
||||
@@ -103,11 +102,11 @@ MipsAtom_(rbind_floor_f3_face) atom_info(atom_bind(Binds_FloorTri), atom_phase(f
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
atom_dbg_skip
|
||||
// atom_dbg_skip
|
||||
internal
|
||||
MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
||||
, atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursr)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor)
|
||||
) {
|
||||
mac_load_tri_indices( R_T0, R_T1, R_T2),
|
||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||
@@ -118,8 +117,7 @@ MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
||||
gte_mv_from_data_r(R_T0, C2_MAC0),
|
||||
nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot.
|
||||
/* Format Primitive */
|
||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||
mac_gte_store_f3_post_rtpt(),
|
||||
mac_gte_store_f3(),
|
||||
|
||||
/* Calculate Depth */
|
||||
gte_avg_sort_z3,
|
||||
@@ -128,8 +126,8 @@ MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
||||
/* Insert into Ordering Table Linked List */
|
||||
mac_insert_ot_tag_f3(),
|
||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
|
||||
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
||||
// Note(Ed): No bounds checking, should be checked before atom runs.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
||||
#pragma once
|
||||
|
||||
#pragma region hello_joypad.tape
|
||||
|
||||
|
||||
// --- atom: cube_g4_face (77 words) ---
|
||||
|
||||
#define _atom_offset_cull_cube_g4_face_exit 42
|
||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 24
|
||||
|
||||
enum {
|
||||
atom_offset_cull_cube_g4_face_exit = _atom_offset_cull_cube_g4_face_exit,
|
||||
atom_offset_bounds_chk_cube_g4_face_exit = _atom_offset_bounds_chk_cube_g4_face_exit,
|
||||
};
|
||||
|
||||
// --- atom: floor_f3_face (58 words) ---
|
||||
|
||||
#define _atom_offset_culling_floor_f3_face_exit 25
|
||||
#define _atom_offset_bounds_chk_floor_f3_face_exit 16
|
||||
|
||||
enum {
|
||||
atom_offset_culling_floor_f3_face_exit = _atom_offset_culling_floor_f3_face_exit,
|
||||
atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit,
|
||||
};
|
||||
|
||||
#pragma endregion hello_joypad.tape
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
// #include "libgpu.h"
|
||||
// #include "libetc.h"
|
||||
// #include "libgte.h"
|
||||
|
||||
#include "duffle/dsl.h"
|
||||
#include "duffle/memory.h"
|
||||
#include "duffle/math.h"
|
||||
|
||||
#include "duffle/gcc_asm.h"
|
||||
#include "duffle/mips.h"
|
||||
#include "duffle/gp.h"
|
||||
#include "duffle/gte.h"
|
||||
|
||||
# include "duffle/gen/duffle.macs.h"
|
||||
# include "duffle/gen/duffle.offsets.h"
|
||||
#include "duffle/atom_dsl.h"
|
||||
#include "duffle/lottes_tape.h"
|
||||
#include "duffle/word_count.metadata.h"
|
||||
|
||||
# include "gen/hello_joypad.offsets.h"
|
||||
#include "hello_joypad.h"
|
||||
|
||||
#include "hello_joypad.tape.c"
|
||||
|
||||
typedef U4 OrderingTable_Buffer[OrderingTbl_Len];
|
||||
typedef Array_(OrderingTable_Buffer, 2);
|
||||
|
||||
typedef B1 PrimitiveBuffer[PrimitiveBuff_Len];
|
||||
typedef Array_(PrimitiveBuffer, 2);
|
||||
typedef Struct_(PrimitiveArena) {
|
||||
A2_PrimitiveBuffer buf;
|
||||
U4 used;
|
||||
};
|
||||
|
||||
#define Cube_num_verts 8
|
||||
typedef Array_(V3_S2, Cube_num_verts);
|
||||
#define Cube_num_faces 6
|
||||
typedef Array_(V4_S2, Cube_num_faces);
|
||||
I_ void ent_cube128_init(A8_V3_S2* verts, A6_V4_S2* faces) {
|
||||
LP_ A8_V3_S2 baked_verts = (A8_V3_S2) {
|
||||
{ -128, -128, -128 },
|
||||
{ 128, -128, -128 },
|
||||
{ 128, -128, 128 },
|
||||
{ -128, -128, 128 },
|
||||
{ -128, 128, -128 },
|
||||
{ 128, 128, -128 },
|
||||
{ 128, 128, 128 },
|
||||
{ -128, 128, 128 }
|
||||
};
|
||||
LP_ A6_V4_S2 baked_faces = (A6_V4_S2) {
|
||||
{ 3, 2, 0, 1 },
|
||||
{ 0, 1, 4, 5 },
|
||||
{ 4, 5, 7, 6 },
|
||||
{ 1, 2, 5, 6 },
|
||||
{ 2, 3, 6, 7 },
|
||||
{ 3, 0, 7, 4 },
|
||||
};
|
||||
mem_copy(u4_(verts), u4_(& baked_verts), S_(A8_V3_S2) );
|
||||
mem_copy(u4_(faces), u4_(& baked_faces), S_(A6_V4_S2) );
|
||||
return;
|
||||
}
|
||||
typedef Struct_(Ent_Cube) {
|
||||
V3_S4 accel;
|
||||
V3_S4 vel;
|
||||
V3_S4 pos;
|
||||
V3_S4 scale;
|
||||
V3_S2 rot;
|
||||
A8_V3_S2 verts;
|
||||
A6_V4_S2 faces;
|
||||
};
|
||||
|
||||
#define Floor_num_verts 4
|
||||
typedef Array_(V3_S2, Floor_num_verts);
|
||||
#define Floor_num_faces 2
|
||||
typedef Array_(V3_S2, Floor_num_faces);
|
||||
I_ void ent_floor_init(A4_V3_S2* verts, A2_V3_S2* faces) {
|
||||
LP_ A4_V3_S2 baked_verts = (A4_V3_S2) {
|
||||
{ -900, 0, -900 },
|
||||
{ -900, 0, 900 },
|
||||
{ 900, 0, -900 },
|
||||
{ 900, 0, 900 },
|
||||
};
|
||||
LP_ A2_V3_S2 baked_faces = (A2_V3_S2) {
|
||||
{ 0, 1, 2 },
|
||||
{ 1, 3, 2 },
|
||||
};
|
||||
mem_copy(u4_(verts), u4_(& baked_verts), S_(A4_V3_S2));
|
||||
mem_copy(u4_(faces), u4_(& baked_faces), S_(A2_V3_S2));
|
||||
};
|
||||
typedef Struct_(Ent_Floor) {
|
||||
V3_S4 accel;
|
||||
V3_S4 pos;
|
||||
V3_S4 scale;
|
||||
V3_S2 rot;
|
||||
A4_V3_S2 verts;
|
||||
A2_V3_S2 faces;
|
||||
};
|
||||
|
||||
enum {
|
||||
Scratchpad_Len = 1024,
|
||||
MemTape_Len = 512,
|
||||
};
|
||||
typedef Struct_(SMemory) {
|
||||
U4 MemTape[MemTape_Len];
|
||||
|
||||
DoubleBuffer screen_buf;
|
||||
A2_OrderingTable_Buffer ordering_tbl;
|
||||
PrimitiveArena primitives;
|
||||
S4 active_buf_id;
|
||||
|
||||
M3_S2 tform_world;
|
||||
|
||||
Ent_Cube cube;
|
||||
Ent_Floor floor;
|
||||
|
||||
U4_V scratchpad; // d-cache
|
||||
};
|
||||
global SMemory smem;
|
||||
extern SMemory smem;
|
||||
|
||||
// TODO(Ed):
|
||||
FI_ U4* spad_warm(MipsAtom atom) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
I_ B1* prim__alloc(U4 type_width, Str8 type_name) {
|
||||
gknown PrimitiveArena* pa = & smem.primitives;
|
||||
gknown B1* buf = (B1*) r_(smem.primitives.buf)[smem.active_buf_id];
|
||||
assert(pa->used + type_width < PrimitiveBuff_Len);
|
||||
B1* next = buf + pa->used;
|
||||
pa->used += type_width;
|
||||
return next;
|
||||
}
|
||||
#define prim_alloc(type) (type*)prim__alloc(S_(type), slit( stringify(type)))
|
||||
|
||||
void gp_screen_init_c11(DoubleBuffer* screen_buf, S4* active_buf_id)
|
||||
{
|
||||
reset_graph(0);
|
||||
|
||||
// Set the current initial buffer
|
||||
active_buf_id[0] = 0;
|
||||
|
||||
// Just setting env data, not interacting with console hw.
|
||||
// First buffer area
|
||||
displayenv_init(& r_(screen_buf->display)[0], 0, 0, ScreenRes_X, ScreenRes_Y);
|
||||
drawenv_init (& r_(screen_buf->draw )[0], 0, ScreenRes_Y, ScreenRes_X, ScreenRes_Y);
|
||||
// Second buffer area
|
||||
displayenv_init(& r_(screen_buf->display)[1], 0, ScreenRes_Y, ScreenRes_X, ScreenRes_Y);
|
||||
drawenv_init (& r_(screen_buf->draw )[1], 0, 0, ScreenRes_X, ScreenRes_Y);
|
||||
// Set the back/drawing buffer
|
||||
screen_buf->draw[0].enable_auto_clear = true;
|
||||
screen_buf->draw[1].enable_auto_clear = true;
|
||||
// Set the background clear color
|
||||
screen_buf->draw[0].initial_bg_color = rgb8( .r = 7, .g = 7, .b = 7 );
|
||||
screen_buf->draw[1].initial_bg_color = rgb8( .r = 7, .g = 7, .b = 7 );
|
||||
// screen_buf->draw[1].initial_bg_color = rgb8( .r = 47, .g = 13, .b = 0 );
|
||||
displayenv_put(& r_(screen_buf->display)[ active_buf_id[0] ]);
|
||||
drawenv_put (& r_(screen_buf->draw )[ active_buf_id[0] ]);
|
||||
|
||||
// Initialize and setup the GTE geometry offsets
|
||||
geom_init();
|
||||
// NOTE: geom_set_offset/geom_set_screen are kept as-is (the libgte versions
|
||||
// are known to be broken in this PSYQ 4.7 build — see report 2026-07-09).
|
||||
// The user's research wants the C-side non-tape reference to work as a
|
||||
// known-good baseline for comparison against the tape.
|
||||
geom_set_offset(ScreenRes_CenterX, ScreenRes_CenterY);
|
||||
geom_set_screen(ScreenZ);
|
||||
|
||||
set_display_enabled(1); // gp_DisplayEnabled
|
||||
}
|
||||
|
||||
void gp_display_frame(DoubleBuffer* screen_buf, S4* active_buf_id, U4* ordering_buf, PrimitiveArena* pa) {
|
||||
draw_sync(0);
|
||||
vsync(0);
|
||||
displayenv_put(& r_(screen_buf->display)[active_buf_id[0] ]);
|
||||
drawenv_put (& r_(screen_buf->draw) [active_buf_id[0] ]);
|
||||
{
|
||||
draw_orderingtbl(ordering_buf + OrderingTbl_Len - 1);
|
||||
pa->used = 0;
|
||||
}
|
||||
active_buf_id[0] = ! active_buf_id[0]; // Swap current buffer
|
||||
}
|
||||
|
||||
void render(void) {
|
||||
}
|
||||
|
||||
GCC_OPTIMIZATION_DISABLE
|
||||
void update(PrimitiveArena* pa, U4* ordering_buf)
|
||||
{
|
||||
orderingtbl_clear_reverse(ordering_buf, OrderingTbl_Len);
|
||||
|
||||
// Update the position based on acceleration and velocity
|
||||
gknown V3_S4_R pos = & smem.cube.pos;
|
||||
gknown V3_S4_R vel = & smem.cube.vel;
|
||||
gknown V3_S4_R acc = & smem.cube.accel;
|
||||
add_v3s4(vel, acc[0]);
|
||||
add_v3s4_fp(pos, vel[0]);
|
||||
// vel->x += acc->x;
|
||||
// vel->y += acc->y;
|
||||
// vel->z += acc->z;
|
||||
// pos->x += vel->x;
|
||||
// pos->y += vel->y;
|
||||
// pos->z += vel->z;
|
||||
|
||||
if (pos->y + 150 > smem.floor.pos.y) vel->y *= -1;
|
||||
|
||||
// Prep
|
||||
S4 nclip = 0;
|
||||
S4 orderingtbl_z = 0;
|
||||
A2_S2 p; //???
|
||||
S4 flag; //????
|
||||
|
||||
TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape));
|
||||
|
||||
// Draw Cube
|
||||
if (0)
|
||||
{
|
||||
m3s2_rotation (& smem.cube.rot, & smem.tform_world);
|
||||
m3s2_translation(& smem.tform_world, & smem.cube.pos);
|
||||
m3s2_scale (& smem.tform_world, & smem.cube.scale);
|
||||
// gte_matrix_set_rotation (& smem.tform_world);
|
||||
gte_matrix_set_translation(& smem.tform_world);
|
||||
for (U4 face_id = 0; face_id < Cube_num_faces; face_id += 1)
|
||||
{
|
||||
Poly_G4* quad = prim_alloc(Poly_G4); set_poly_g4(quad);
|
||||
quad->c0 = rgb8(255, 0, 255);
|
||||
quad->c1 = rgb8(255, 255, 0);
|
||||
quad->c2 = rgb8( 0, 255, 255);
|
||||
quad->c3 = rgb8( 0, 255, 0);
|
||||
|
||||
V4_S2* face = & smem.cube.faces[face_id];
|
||||
V3_S2* p0 = & smem.cube.verts[face->x];
|
||||
V3_S2* p1 = & smem.cube.verts[face->y];
|
||||
V3_S2* p2 = & smem.cube.verts[face->z];
|
||||
V3_S2* p3 = & smem.cube.verts[face->w];
|
||||
|
||||
nclip = rtp_avg_nclip_a4_v3s2(
|
||||
p0, p1, p2, p3,
|
||||
& quad->p0, & quad->p1, & quad->p2, & quad->p3,
|
||||
& p, & orderingtbl_z, & flag
|
||||
);
|
||||
if (nclip <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((orderingtbl_z > 0) && (orderingtbl_z < OrderingTbl_Len)) {
|
||||
orderingtbl_add_primitive(ordering_buf[orderingtbl_z], quad);
|
||||
}
|
||||
}
|
||||
// smem.cube.rot.x += 6;
|
||||
// smem.cube.rot.y += 8;
|
||||
// smem.cube.rot.z += 12;
|
||||
smem.cube.rot.y += 30;
|
||||
}
|
||||
// Draw cube (tape method) - two triangles per face
|
||||
if (1)
|
||||
{
|
||||
m3s2_rotation (& smem.cube.rot, & smem.tform_world);
|
||||
m3s2_translation(& smem.tform_world, & smem.cube.pos);
|
||||
m3s2_scale (& smem.tform_world, & smem.cube.scale);
|
||||
gte_matrix_set_rotation (& smem.tform_world);
|
||||
gte_matrix_set_translation(& smem.tform_world);
|
||||
|
||||
U4 prim_base = u4_(pa->buf[smem.active_buf_id]);
|
||||
U4 prim_cursor = prim_base + pa->used;
|
||||
|
||||
tb.used = 0; tb_scope(& tb) {
|
||||
tb_emit(& tb, rbind_cube_g4_face);
|
||||
tb_data(& tb, prim_cursor);
|
||||
tb_data(& tb, u4_(smem.cube.faces));
|
||||
tb_data(& tb, u4_(smem.cube.verts));
|
||||
tb_data(& tb, u4_(ordering_buf));
|
||||
|
||||
for (U4 i = 0; i < Cube_num_faces; i++) {
|
||||
// Two triangles per quad face: (x,y,z) and (x,z,w)
|
||||
tb_emit(& tb, cube_g4_face);
|
||||
}
|
||||
|
||||
tb_emit(& tb, sync_primitive_arena);
|
||||
tb_data(& tb, u4_(& pa->used));
|
||||
tb_data(& tb, prim_base);
|
||||
}
|
||||
tape_run(tb_slice(tb));
|
||||
|
||||
smem.cube.rot.y += 30;
|
||||
}
|
||||
// Draw Floor
|
||||
if (0)
|
||||
{
|
||||
m3s2_rotation (& smem.floor.rot, & smem.tform_world);
|
||||
m3s2_translation(& smem.tform_world, & smem.floor.pos);
|
||||
m3s2_scale (& smem.tform_world, & smem.floor.scale);
|
||||
gte_matrix_set_rotation (& smem.tform_world);
|
||||
gte_matrix_set_translation(& smem.tform_world);
|
||||
for (U4 face_id = 0; face_id < Floor_num_faces; face_id += 1)
|
||||
{
|
||||
Poly_F3* tri = prim_alloc(Poly_F3); set_poly_f3(tri);
|
||||
tri->color = rgb8(255, 255, 255);
|
||||
|
||||
V3_S2* face = & smem.floor.faces[face_id];
|
||||
register V3_S2* p0 rgcc(R_T4) = & smem.floor.verts[face->x];
|
||||
register V3_S2* p1 rgcc(R_T5) = & smem.floor.verts[face->y];
|
||||
register V3_S2* p2 rgcc(R_T6) = & smem.floor.verts[face->z];
|
||||
|
||||
gte_load_v0(p0, R_T4);
|
||||
/*
|
||||
asm volatile( ".word " "%0" ", %1" : :
|
||||
"i"(((op_lwc2 & OPCODE_MASK) << OPCODE_SHIFT) | ((R_T4 & REG_MASK) << RS_SHIFT) | ((gte_in_v0_xy & REG_MASK) << RT_SHIFT) | (0 & IMM_MASK)),
|
||||
"i"(((op_lwc2 & OPCODE_MASK) << OPCODE_SHIFT) | ((R_T4 & REG_MASK) << RS_SHIFT) | ((gte_in_v0_z & REG_MASK) << RT_SHIFT) | (GTE_Z_Offset & IMM_MASK)),
|
||||
"r"(p0) :
|
||||
"$2", "$8", "$9", "$31", "memory"
|
||||
);
|
||||
*/
|
||||
gte_load_v1(p1, R_T5);
|
||||
gte_load_v2(p2, R_T6);
|
||||
|
||||
gte_rtpt();
|
||||
gte_nclip();
|
||||
gte_stotz(& nclip);
|
||||
|
||||
// nclip = rtp_avg_nclip_a3_v3s2(p0, p1, p2
|
||||
// , & tri->p0, & tri->p1, & tri->p2
|
||||
// , & p, & orderingtbl_z, & flag
|
||||
// );
|
||||
// if (nclip <= 0) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (nclip > 0 ) {
|
||||
gte_stsxy3(& tri->p0, & tri->p1, & tri->p2);
|
||||
gte_avsz3();
|
||||
gte_stotz(& orderingtbl_z);
|
||||
|
||||
if ((orderingtbl_z > 0) && (orderingtbl_z < OrderingTbl_Len)) {
|
||||
orderingtbl_add_primitive(ordering_buf[orderingtbl_z], tri);
|
||||
}
|
||||
}
|
||||
}
|
||||
smem.floor.rot.y += 5;
|
||||
}
|
||||
// Draw floor tape method
|
||||
if (1)
|
||||
{
|
||||
m3s2_rotation (& smem.floor.rot, & smem.tform_world);
|
||||
m3s2_translation(& smem.tform_world, & smem.floor.pos);
|
||||
m3s2_scale (& smem.tform_world, & smem.floor.scale);
|
||||
|
||||
U4 prim_base = u4_(pa->buf[smem.active_buf_id]);
|
||||
U4 prim_cursor = prim_base + pa->used;
|
||||
|
||||
// TODO(Ed): We should do a bounds check beforehand to confirm pa can hold all tris?
|
||||
// The tape atoms in-flight should not need to care.
|
||||
|
||||
// Prepare the tape. (Push protocol to tape)
|
||||
tb.used = 0; tb_scope(& tb) {
|
||||
tb_emit(& tb, set_gte_world);
|
||||
tb_data(& tb, u4_(& smem.tform_world));
|
||||
|
||||
tb_emit(& tb, rbind_floor_f3_face);
|
||||
// TODO(Ed): Just use a single context struct ref
|
||||
tb_data(& tb, prim_cursor);
|
||||
tb_data(& tb, u4_(smem.floor.faces));
|
||||
tb_data(& tb, u4_(smem.floor.verts));
|
||||
tb_data(& tb, u4_(ordering_buf));
|
||||
for (U4 i = 0; i < Floor_num_faces; i++) {
|
||||
tb_emit(& tb, floor_f3_face);
|
||||
}
|
||||
// After floor_f3_face iterations complete, the primitive arena's used counter needs updating.
|
||||
tb_emit(& tb, sync_primitive_arena);
|
||||
tb_data(& tb, u4_(& pa->used));
|
||||
tb_data(& tb, prim_base);
|
||||
}
|
||||
tape_run(tb_slice(tb));// Fire off the tape.
|
||||
|
||||
// C-side state (pa->used) has already been updated by the tape!
|
||||
smem.floor.rot.y += 5;
|
||||
}
|
||||
// --- TAPE DIAGNOSTICS ---
|
||||
if (0)
|
||||
{
|
||||
LP_ U4 mem_temp_tape[512]; FArena tape_arena; farena_init(& tape_arena, slice_ut_arr(mem_temp_tape));
|
||||
TapeBuilder tb = tb_make_old(& tape_arena); tb_scope(& tb) {
|
||||
// Skip set_gte_world atom for diagnostics to isolate the triangle loop
|
||||
for (U4 i = 0; i < Floor_num_faces; i++) {
|
||||
// tb_emit(& tb, code_diag_yield);
|
||||
// tb_emit(& tb, code_diag_color);
|
||||
// tb_emit(& tb, code_diag_gte);
|
||||
}
|
||||
}
|
||||
B1* prim_cursor = (B1*)r_(pa->buf)[smem.active_buf_id] + pa->used;
|
||||
tape_run(tb_slice(tb));
|
||||
pa->used = (U4)prim_cursor - (U4)r_(pa->buf)[smem.active_buf_id];
|
||||
}
|
||||
}
|
||||
GCC_OPTIMIZATION_ENABLE
|
||||
|
||||
int main(void)
|
||||
{
|
||||
smem = (SMemory){0};
|
||||
smem.scratchpad = C_(U4_V, 0x1F800000);
|
||||
smem.primitives.used = 0;
|
||||
ent_cube128_init(& smem.cube.verts, & smem.cube.faces); {
|
||||
Ent_Cube* cube = & smem.cube;
|
||||
cube->rot = v3s2(0, 0, 0);
|
||||
// cube->pos = v3s4(0, 0, 900);
|
||||
cube->scale = v3s4_fp_one();
|
||||
cube->accel = v3s4(0, 1, 0);
|
||||
cube->pos = v3s4(0, -400, 1800);
|
||||
}
|
||||
ent_floor_init(& smem.floor.verts, & smem.floor.faces); {
|
||||
Ent_Floor* floor = & smem.floor;
|
||||
floor->rot = v3s2(0, 0, 0);
|
||||
floor->pos = v3s4(0, 450, 1800);
|
||||
floor->scale = v3s4_fp_one();
|
||||
}
|
||||
// gknown gp_screen_init();
|
||||
gp_screen_init_c11(& smem.screen_buf, & smem.active_buf_id);
|
||||
while (1) {
|
||||
gknown S4* active_buf_id = & smem.active_buf_id;
|
||||
gknown U4* ordering_buf = r_(smem.ordering_tbl)[active_buf_id[0]];
|
||||
gknown PrimitiveArena* pa = & smem.primitives;
|
||||
update(pa, ordering_buf);
|
||||
render();
|
||||
gp_display_frame(& smem.screen_buf, active_buf_id, ordering_buf, pa);
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#ifdef INTELLISENSE_DIRECTIVES
|
||||
# pragma once
|
||||
# include "duffle/dsl.h"
|
||||
# include "duffle/math.h"
|
||||
# include "duffle/gp.h"
|
||||
#endif
|
||||
|
||||
enum {
|
||||
PrimitiveBuff_Len = 4096,
|
||||
OrderingTbl_Len = 2048
|
||||
};
|
||||
|
||||
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; };
|
||||
typedef Struct_(DrawEnv) {
|
||||
Rect_S2 clip_area;
|
||||
A2_S2 drawing_offset;
|
||||
Rect_S2 texture_window;
|
||||
S2 texture_page;
|
||||
B1 flag_dither;
|
||||
B1 flag_draw_on_display;
|
||||
B1 enable_auto_clear;
|
||||
RGB8 initial_bg_color;
|
||||
DrawEnv_Packed dr_env; // reserved
|
||||
};
|
||||
typedef Struct_(DisplayEnv) {
|
||||
Rect_S2 display_area;
|
||||
Rect_S2 screen;
|
||||
B1 vinterlace;
|
||||
B1 color24;
|
||||
B1 pad0;
|
||||
B1 pad1;
|
||||
};
|
||||
typedef Array_(DrawEnv, 2);
|
||||
typedef Array_(DisplayEnv, 2);
|
||||
typedef Struct_(DoubleBuffer) {
|
||||
A2_DrawEnv draw;
|
||||
A2_DisplayEnv display;
|
||||
};
|
||||
|
||||
#define ScreenRes_X 320
|
||||
#define ScreenRes_Y 240
|
||||
#define ScreenZ 320
|
||||
#define ScreenRes_CenterX (ScreenRes_X >> 1)
|
||||
#define ScreenRes_CenterY (ScreenRes_Y >> 1)
|
||||
|
||||
DisplayEnv* displayenv_init(DisplayEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDispEnv");
|
||||
DrawEnv* drawenv_init (DrawEnv* env, S4 x, S4 y, S4 w, S4 h) __asm__("SetDefDrawEnv");
|
||||
|
||||
DisplayEnv* displayenv_put(DisplayEnv* env) __asm__("PutDispEnv");
|
||||
DrawEnv* drawenv_put (DrawEnv* env) __asm__("PutDrawEnv");
|
||||
|
||||
U4 geom_init(void) __asm__("InitGeom");
|
||||
void geom_set_offset(U4 x, U4 y) __asm__("SetGeomOffset");
|
||||
void geom_set_screen(U4 h) __asm__("SetGeomScreen");
|
||||
|
||||
U4* orderingtbl_clear_reverse(U4* ot, U4 len) __asm__("ClearOTagR");
|
||||
|
||||
U4 reset_graph(U4 mode) __asm__("ResetGraph");
|
||||
void set_display_enabled(U4 mask) __asm__("SetDispMask");
|
||||
|
||||
U4 draw_sync(U4 mode) __asm__("DrawSync");
|
||||
U4 vsync(U4 mode) __asm__("VSync");
|
||||
|
||||
void draw_orderingtbl(U4* buf) __asm__("DrawOTag");
|
||||
|
||||
typedef Struct_(Tile) {
|
||||
U4 tag;
|
||||
RGB8 color;
|
||||
B1 code;
|
||||
Rect_S2 rect;
|
||||
};
|
||||
|
||||
/*
|
||||
Linear Algebra
|
||||
*/
|
||||
|
||||
M3_S2* m3s2_rotation (V3_S2* vec, M3_S2* mat) __asm__("RotMatrix");
|
||||
M3_S2* m3s2_translation(M3_S2* mat, V3_S4* vec) __asm__("TransMatrix");
|
||||
M3_S2* m3s2_scale (M3_S2* mat, V3_S4* vec) __asm__("ScaleMatrix");
|
||||
|
||||
// Rotation, Translation, Perspective
|
||||
|
||||
S4 rtp_v3s2_raw(V3_S2* vec, S4* xy, S4* pp, S4* flag) __asm__("RotTransPers");
|
||||
FI_ S4 rtp_v3s2(V3_S2* vec, V2_S2* xy, A2_S2* pp, S4* flag) { return rtp_v3s2_raw(vec, C_(S4*R_, & xy->x), C_(S4*R_, pp), r_(flag)); }
|
||||
|
||||
S4 rtp_avg_nclip_a3_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, S4* xy1, S4* xy2, S4* xy3, S4* pp, S4* otz, S4* flag) __asm__("RotAverageNclip3");
|
||||
FI_ S4 rtp_avg_nclip_a3_v3s2(
|
||||
V3_S2* v0, V3_S2* v1, V3_S2* v2,
|
||||
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2,
|
||||
A2_S2* pp, S4* otz, S4* flag
|
||||
){
|
||||
return rtp_avg_nclip_a3_v3s2_raw(
|
||||
v0, v1, v2,
|
||||
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2),
|
||||
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
|
||||
);
|
||||
}
|
||||
|
||||
S4 rtp_avg_nclip_a4_v3s2_raw(V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3, S4* xy1, S4* xy2, S4* xy3, S4* xy4, S4* pp, S4* otz, S4* flag) __asm__("RotAverageNclip4");
|
||||
FI_ S4 rtp_avg_nclip_a4_v3s2(
|
||||
V3_S2* v0, V3_S2* v1, V3_S2* v2, V3_S2* v3,
|
||||
V2_S2* xy0, V2_S2* xy1, V2_S2* xy2, V2_S2* xy3,
|
||||
A2_S2* pp, S4* otz, S4* flag
|
||||
){
|
||||
return rtp_avg_nclip_a4_v3s2_raw(
|
||||
v0, v1, v2, v3,
|
||||
C_(S4*R_, xy0), C_(S4*R_, xy1), C_(S4*R_, xy2), C_(S4*R_, xy3),
|
||||
C_(S4*R_, pp), C_(S4*R_, otz), C_(S4*R_, flag)
|
||||
);
|
||||
}
|
||||
|
||||
void gte_matrix_set_rotation (M3_S2* mat) __asm__("SetRotMatrix");
|
||||
void gte_matrix_set_translation(M3_S2* mat) __asm__("SetTransMatrix");
|
||||
|
||||
enum {
|
||||
fp_one = (1 << 12),
|
||||
};
|
||||
|
||||
#define v3s4_fp_one() v3s4(fp_one, fp_one, fp_one)
|
||||
@@ -0,0 +1,154 @@
|
||||
#ifdef INTELLISENSE_DIRECTIVES
|
||||
# include "duffle/gen/duffle.macs.h"
|
||||
# include "duffle/gen/duffle.offsets.h"
|
||||
# include "duffle/atom_dsl.h"
|
||||
# include "duffle/lottes_tape.h"
|
||||
# include "duffle/word_count.metadata.h"
|
||||
# include "gen/hello_joypad.offsets.h"
|
||||
# include "hello_joypad.h"
|
||||
#endif
|
||||
|
||||
#pragma region MACs (Mips Atom components)
|
||||
|
||||
|
||||
|
||||
#pragma endregion MACs
|
||||
|
||||
#pragma region Baked Atoms
|
||||
|
||||
typedef Struct_(Binds_CubeTri) {
|
||||
U4 PrimCursor;
|
||||
V4_S2* FaceCursor;
|
||||
V3_S2* VertBase;
|
||||
U4* OtBase;
|
||||
};
|
||||
internal MipsAtom_(rbind_cube_g4_face) atom_info(atom_bind(Binds_CubeTri), atom_phase(cube_g4)
|
||||
, atom_reads(R_TapePtr)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
){
|
||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_CubeTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_CubeTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_CubeTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_CubeTri,OtBase)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_CubeTri)),
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
// cube_g4_face — Draw one cube face (Gouraud-shaded quad) via the GTE tape pipeline
|
||||
internal
|
||||
MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4),
|
||||
atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase),
|
||||
atom_writes(R_PrimCursor, R_FaceCursor)
|
||||
){
|
||||
load_half_u(R_T0, R_FaceCursor, 0 * S_(S2)),
|
||||
load_half_u(R_T1, R_FaceCursor, 1 * S_(S2)),
|
||||
load_half_u(R_T2, R_FaceCursor, 2 * S_(S2)),
|
||||
load_half_u(R_T3, R_FaceCursor, 3 * S_(S2)),
|
||||
|
||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||
nop2, gte_cmdw_rotate_translate_perspective_triple, // required cpu -> gte delay slot
|
||||
gte_cmdw_nclip,
|
||||
|
||||
gte_mv_from_data_r(R_T0, C2_MAC0), nop,
|
||||
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)), nop,
|
||||
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
|
||||
shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase),
|
||||
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),
|
||||
|
||||
mac_gte_store_g4_p012(),
|
||||
gte_cmdw_rotate_translate_perspective_single,
|
||||
mac_gte_store_g4_p3(),
|
||||
|
||||
gte_cmdw_avg_sort_z4,
|
||||
gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, cube_g4_face_exit)), nop,
|
||||
mac_insert_ot_tag_g4(),
|
||||
mac_format_g4_color(
|
||||
/* c0 magenta */ 0xFF, 0x00, 0xFF,
|
||||
/* c1 yellow */ 0xFF, 0xFF, 0x00,
|
||||
/* c2 cyan */ 0x00, 0xFF, 0xFF,
|
||||
/* c3 green */ 0x00, 0xFF, 0x00),
|
||||
// end: branch(bounds_chk)
|
||||
// end: branch(cull)
|
||||
|
||||
atom_label(cube_g4_face_exit)
|
||||
add_ui_self(R_PrimCursor, S_(Poly_G4)), /* 9 words = Poly_G4 */
|
||||
add_ui_self(R_FaceCursor, S_(S2) * 4), /* 4 × S2 = 8 bytes */
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
typedef Struct_(Binds_FloorTri) {
|
||||
U4 PrimCursor;
|
||||
V3_S2* FaceCursor;
|
||||
V3_S2* VertBase;
|
||||
U4* OtBase;
|
||||
};
|
||||
internal
|
||||
MipsAtom_(rbind_floor_f3_face) atom_info(atom_bind(Binds_FloorTri), atom_phase(floor_f3)
|
||||
, atom_reads(R_TapePtr)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
){
|
||||
/* Pop 4 arguments from the tape directly into the workspace registers */
|
||||
load_word(R_PrimCursor, R_TapePtr, O_(Binds_FloorTri,PrimCursor)),
|
||||
load_word(R_FaceCursor, R_TapePtr, O_(Binds_FloorTri,FaceCursor)),
|
||||
load_word(R_VertBase, R_TapePtr, O_(Binds_FloorTri,VertBase)),
|
||||
load_word(R_OtBase, R_TapePtr, O_(Binds_FloorTri,OtBase)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_FloorTri)),
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
// atom_dbg_skip
|
||||
internal
|
||||
MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
||||
, atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
||||
, atom_writes(R_PrimCursor, R_FaceCursor)
|
||||
) {
|
||||
mac_load_tri_indices( R_T0, R_T1, R_T2),
|
||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||
nop2, gte_cmdw_rotate_translate_perspective_triple, // 2 nops retire the final cpu -> gte writes before RTPT
|
||||
gte_cmdw_nclip,
|
||||
|
||||
/* Culling (Branch forward if Backface) */
|
||||
gte_mv_from_data_r(R_T0, C2_MAC0),
|
||||
nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot.
|
||||
/* Format Primitive */
|
||||
mac_gte_store_f3(),
|
||||
|
||||
/* Calculate Depth */
|
||||
gte_avg_sort_z3,
|
||||
gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||
/* Bounds Check OTZ < 2048 (Branch forward to skip insertion) */
|
||||
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||
set_lt_u( R_AT, R_T1, R_AT),
|
||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
|
||||
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
||||
// Note(Ed): No bounds checking, should be checked before atom runs.
|
||||
|
||||
/* Advance Input Cursor & Yield (Both branch targets land here) */
|
||||
atom_label(floor_f3_face_exit)
|
||||
add_ui_self(R_FaceCursor, S_(S2) * 4), /* Advance Face Cursor (4 * S2 = 8 bytes) */
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
typedef Struct_(Binds_SyncPrimitiveArena) { U4 used; U4 cursor; };
|
||||
internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitiveArena)
|
||||
, atom_reads( R_TapePtr, R_PrimCursor)
|
||||
, atom_writes(R_TapePtr)
|
||||
){
|
||||
load_word(R_AT, R_TapePtr, O_(Binds_SyncPrimitiveArena,used)),
|
||||
load_word(R_T0, R_TapePtr, O_(Binds_SyncPrimitiveArena,cursor)),
|
||||
add_ui_self( R_TapePtr, S_(Binds_SyncPrimitiveArena)),
|
||||
/* Calculate byte offset and store directly back to RAM */
|
||||
sub_u( R_T0, R_PrimCursor, R_T0), // R_T0 = R_PrimCursor - binds.cursor
|
||||
store_word(R_T0, R_AT, 0), // R_AT[0] = R_T0
|
||||
mac_yield()
|
||||
};
|
||||
|
||||
#pragma endregion Baked Atoms
|
||||
+107
-3
@@ -350,10 +350,10 @@ function build-graphis_hello {
|
||||
}
|
||||
# build-graphis_hello
|
||||
|
||||
function build-gte_hello {
|
||||
function build-hello_gte {
|
||||
$includes += @()
|
||||
|
||||
$path_module = join-path $path_code 'gte_hello'
|
||||
$path_module = join-path $path_code 'hello_gte'
|
||||
$path_duffle = join-path $path_code 'duffle'
|
||||
$path_atom_metadata = join-path $path_duffle 'word_count.metadata.h'
|
||||
$path_build_gen = join-path $path_build 'gen'
|
||||
@@ -458,8 +458,112 @@ function build-gte_hello {
|
||||
}
|
||||
}
|
||||
}
|
||||
build-gte_hello
|
||||
# build-hello_gte
|
||||
|
||||
function build-hello_joypad {
|
||||
$includes += @()
|
||||
|
||||
$path_module = join-path $path_code 'hello_joypad'
|
||||
$path_duffle = join-path $path_code 'duffle'
|
||||
$path_atom_metadata = join-path $path_duffle 'word_count.metadata.h'
|
||||
$path_build_gen = join-path $path_build 'gen'
|
||||
|
||||
$src_c = join-path $path_module 'hello_joypad.c'
|
||||
ps1-meta -unity_root $src_c -metadata $path_atom_metadata -out_root $path_build_gen
|
||||
|
||||
$assemble_args = @()
|
||||
$assemble_args += $f_debug
|
||||
$assemble_args += $f_optimize_none
|
||||
$assemble_args += ($f_include + $path_code)
|
||||
|
||||
$src_asm_crt = join-path $path_nugget_common 'crt0/crt0.s'
|
||||
$module_asm_crt = join-path $path_build 'crt0.o'
|
||||
assemble-unit $src_asm_crt $module_asm_crt $includes $assemble_args
|
||||
|
||||
$module_c = join-path $path_build 'hello_joypad_c.o'
|
||||
|
||||
$compile_args = @()
|
||||
$compile_args += $f_debug
|
||||
$compile_args += $f_optimize_none
|
||||
# $compile_args += $f_optimize_intrinsics
|
||||
# $compile_args += $f_optimize_size
|
||||
# $compile_args += $f_optimize_debug
|
||||
$compile_args += ($f_include + $path_code)
|
||||
compile-unit $src_c $module_c $includes $compile_args
|
||||
|
||||
$elf = join-path $path_build 'hello_joypad.elf'
|
||||
$exe = join-path $path_build 'hello_joypad.ps-exe'
|
||||
|
||||
$link_args = @()
|
||||
$link_args += $f_debug
|
||||
# $link_args += $f_optimize_size
|
||||
$link_modules = @(
|
||||
$module_asm_crt,
|
||||
$module_c
|
||||
)
|
||||
link-modules $link_modules $elf $link_args
|
||||
make-binary $elf $exe
|
||||
|
||||
# Post-link: gdb-runtime + dwarf-injection in a single Lua invocation (one luajit cold start).
|
||||
ps1-meta -unity_root $src_c -metadata $path_atom_metadata -out_root $path_build_gen -passes @('--post-link') ` -extra_args @('--elf', $elf)
|
||||
|
||||
$dwarfLineBin = join-path $path_build_gen 'hello_joypad.dwarf_line.bin'
|
||||
$dwarfArangesBin = join-path $path_build_gen 'hello_joypad.dwarf_aranges.bin'
|
||||
$dwarfRnglistsBin = join-path $path_build_gen 'hello_joypad.dwarf_rnglists.bin'
|
||||
$injectElf = join-path $path_build 'hello_joypad.dwarf-injected.elf'
|
||||
if ((Test-Path $dwarfLineBin) -and (Test-Path $dwarfArangesBin) -and (Test-Path $dwarfRnglistsBin))
|
||||
{
|
||||
Write-Host "[build] DWARF-injecting $elf -> $injectElf"
|
||||
Copy-Item -LiteralPath $elf -Destination $injectElf -Force
|
||||
# Objcopy call: 3x --update-section for (line, aranges, rnglists).
|
||||
$f_args = @(
|
||||
"--update-section=.debug_line=$dwarfLineBin",
|
||||
"--update-section=.debug_aranges=$dwarfArangesBin",
|
||||
"--update-section=.debug_rnglists=$dwarfRnglistsBin"
|
||||
)
|
||||
& $Objcopy @f_args $injectElf 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy F' splice failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
return;
|
||||
}
|
||||
|
||||
$dwarfInfoBin = join-path $path_build_gen 'hello_joypad.dwarf_info.bin'
|
||||
$dwarfAbbrevBin = join-path $path_build_gen 'hello_joypad.dwarf_abbrev.bin'
|
||||
$dwarfStrBin = join-path $path_build_gen 'hello_joypad.dwarf_str.bin'
|
||||
$dwarfLocBin = join-path $path_build_gen 'hello_joypad.dwarf_loc.bin'
|
||||
$dwarfLoclistsBin = join-path $path_build_gen 'hello_joypad.dwarf_loclists.bin'
|
||||
$g_args = @(
|
||||
"--update-section=.debug_info=$dwarfInfoBin",
|
||||
"--update-section=.debug_abbrev=$dwarfAbbrevBin",
|
||||
"--update-section=.debug_str=$dwarfStrBin",
|
||||
"--add-section=.debug_loc=$dwarfLocBin",
|
||||
"--add-section=.debug_loclists=$dwarfLoclistsBin"
|
||||
)
|
||||
& $Objcopy @g_args $injectElf 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] objcopy G' splice failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
return;
|
||||
}
|
||||
|
||||
# Baked atoms execute from RAM but are emitted as C data arrays, so their ELF sections lack SHF_EXECINSTR.
|
||||
# GDB discards line rows for non-code sections. Mark only the debug-copy sections executable.
|
||||
# The original ELF and PS-EXE remain byte/flag unchanged.
|
||||
& $Objcopy `
|
||||
--set-section-flags ".rodata=alloc,load,readonly,code,contents" `
|
||||
--set-section-flags ".data=alloc,load,data,code,contents" `
|
||||
$injectElf 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "[build] atom-section flag update failed (exit $LASTEXITCODE); removing $injectElf"
|
||||
Remove-Item -LiteralPath $injectElf -ErrorAction SilentlyContinue
|
||||
}
|
||||
else {
|
||||
Write-Host "[build] DWARF-injected ELF: $injectElf"
|
||||
}
|
||||
}
|
||||
}
|
||||
build-hello_joypad
|
||||
|
||||
# NO idea if this works yet...
|
||||
function Send-ToEmulator { param( [string]$exePath )
|
||||
|
||||
+30
-51
@@ -1180,10 +1180,9 @@ M.GTE_COMMAND_INPUTS = {
|
||||
-- * "mac_result" : generic MAC output (nclip, op, mvmva)
|
||||
--
|
||||
-- Consumers:
|
||||
-- * passes/static_analysis.lua::analyze_hardware_relations (the walker reads this after a GTE command to update
|
||||
-- `forward_state.post_command_roles` for `gte_result_position`).
|
||||
-- * passes/static_analysis.lua::check_gte_result_position (per-atom CHECK_RULES reader; renders role mismatches).
|
||||
-- This table is consumed by the hardware-relation analyzer and result-position check.
|
||||
-- * passes/static_analysis.lua::analyze_hardware_relations (the walker reads this after a GTE command to update `forward_state.post_command_roles` for `gte_role_mismatch`).
|
||||
-- * passes/static_analysis.lua::check_gte_role_mismatch (per-atom CHECK_RULES reader; renders role mismatches).
|
||||
-- This table is consumed by the hardware-relation analyzer and the gte_role_mismatch check.
|
||||
M.GTE_COMMAND_OUTPUTS = {
|
||||
-- RTPS: writes one screen coordinate (the perspective-divide result) into C2_SXY2.
|
||||
-- The FIFO side effects leave SXY0 / SXY1 untouched, so `latest_screen_xy` is C2_SXY2.
|
||||
@@ -1293,32 +1292,8 @@ M.GTE_COMMAND_LATCH_WINDOWS = {
|
||||
},
|
||||
}
|
||||
|
||||
-- GTE component result contracts (immutable; keyed by bare component name).
|
||||
--
|
||||
-- Register-role claims that the `_post_<cmd>` suffix alone cannot infer live here.
|
||||
-- The bare name (the component name stripped of the `_post_<cmd>` suffix) is the key; the row carries the expected
|
||||
-- command, the expected role, and the expected C2 register.
|
||||
--
|
||||
-- Known rows:
|
||||
-- * `gte_store_g4_p3_post_rtps`: post-RTPS polygon-emit slot reads the newest projected screen coordinate from C2_SXY2.
|
||||
-- C2_SXY0 is wrong (C2_SXY0 is an older FIFO entry, never the newest post-RTPS result).
|
||||
--
|
||||
-- Unknown `_post_<cmd>` components (a `<name>_post_<cmd>`-suffixed component whose bare `<name>` is not a row key) emit one
|
||||
-- `table_gap` info finding so downstream consumers can detect when the contract table is incomplete for an authored atom body.
|
||||
--
|
||||
-- Consumers:
|
||||
-- * passes/static_analysis.lua::check_gte_result_position (renders result-position findings).
|
||||
-- * passes/static_analysis.lua::emit_table_gap_warning (called once per atom body; surfaces the missing-row diagnostic).
|
||||
-- This table is consumed by the result-position check.
|
||||
M.GTE_COMPONENT_RESULT_CONTRACTS = {
|
||||
-- Post-RTPS g4 p3 store contract: writes the latest screen XY (C2_SXY2) into the primitive's p3 slot.
|
||||
-- Reading from C2_SXY0 is a semantic mismatch — C2_SXY0 is the oldest post-RTPS SXY, not the newest one.
|
||||
["gte_store_g4_p3_post_rtps"] = {
|
||||
command = "gte_cmdw_rtps",
|
||||
role = "latest_screen_xy",
|
||||
register = "C2_SXY2",
|
||||
},
|
||||
}
|
||||
-- GTE component result contracts were removed: the `_post_<cmd>` naming convention was a soft convention
|
||||
-- (the user did not want it formalized via static-analysis enforcement). A proper `atom_info` directive for ordering semantics is a future TODO.
|
||||
|
||||
-- Operand-class table for the COP2->GPR load-delay check.
|
||||
--
|
||||
@@ -1447,20 +1422,21 @@ M.GP0_CMD_BY_SHAPE = {
|
||||
["g4"] = 0x38, ["gt4"] = 0x3C,
|
||||
}
|
||||
|
||||
-- TODO(Ed): REMOVE THIS HARDCODE, THIS SHOULD BE RESOLVED AUTOMATICALLY
|
||||
-- Per-macro prim-buffer contribution: how many 32-bit words each macro writes to the primitive being built in main RAM.
|
||||
-- (This counts RAM-side prim-buffer words, not .text instruction words.)
|
||||
-- The sum across `mac_format_X_color` + `mac_gte_store_X_post_*` + `mac_insert_ot_tag_X` calls in an atom body must equal
|
||||
-- `GP0_CMD_SIZE[GP0_CMD_BY_SHAPE[shape]]`.
|
||||
M.GP0_MACRO_CONTRIB = {
|
||||
["mac_format_f3_color"] = 1,
|
||||
["mac_format_g3_color"] = 3,
|
||||
["mac_format_g4_color"] = 4,
|
||||
["mac_gte_store_f3_post_rtpt"] = 3,
|
||||
["mac_gte_store_g3_post_rtpt"] = 3,
|
||||
["mac_gte_store_g4_p012_post_rtpt_pre_rtps"] = 3,
|
||||
["mac_gte_store_g4_p3_post_rtps"] = 1,
|
||||
["mac_insert_ot_tag_f3"] = 1,
|
||||
["mac_insert_ot_tag_g4"] = 1,
|
||||
["mac_format_f3_color"] = 1,
|
||||
["mac_format_g3_color"] = 3,
|
||||
["mac_format_g4_color"] = 4,
|
||||
["mac_gte_store_f3"] = 3,
|
||||
["mac_gte_store_g3"] = 3,
|
||||
["mac_gte_store_g4_p012"] = 3,
|
||||
["mac_gte_store_g4_p3"] = 1,
|
||||
["mac_insert_ot_tag_f3"] = 1,
|
||||
["mac_insert_ot_tag_g4"] = 1,
|
||||
}
|
||||
|
||||
-- Per-macro cycle cost (best-case, no stalls). Used by the static-analysis pass to emit per-atom cycle budgets.
|
||||
@@ -1583,20 +1559,23 @@ M.INSTRUCTION_LATENCY = {
|
||||
["gte_load_v1"] = 2,
|
||||
["gte_load_v2"] = 2,
|
||||
["gte_load_v0v1v2"] = 6,
|
||||
|
||||
-- TODO(Ed): REMOVE THIS HARDCODE, THIS SHOULD BE RESOLVED AUTOMATICALLY
|
||||
-- mac_* helpers (cycle cost = sum of the expanded instructions)
|
||||
-- mac_yield transfers control; cycle budget is 0 (the next atom absorbs the cost).
|
||||
["mac_yield"] = 0,
|
||||
["mac_pack_color_word"] = 3, -- lui + ori + sw
|
||||
["mac_format_f3_color"] = 3, -- = mac_pack_color_word
|
||||
["mac_format_g4_color"] = 12, -- 4 x mac_pack_color_word
|
||||
["mac_load_tri_indices"] = 3, -- 3 x lhu
|
||||
["mac_gte_load_tri_verts"] = 18, -- 3 x {sll, addu, lw, lw, mtc2, mtc2}
|
||||
["mac_gte_store_f3_post_rtpt"] = 3,
|
||||
["mac_gte_store_g3_post_rtpt"] = 3,
|
||||
["mac_gte_store_g4_p012_post_rtpt_pre_rtps"] = 3,
|
||||
["mac_gte_store_g4_p3_post_rtps"] = 1,
|
||||
["mac_insert_ot_tag_f3"] = 11, -- 11 .word slots in the macro body
|
||||
["mac_insert_ot_tag_g4"] = 11,
|
||||
["mac_yield"] = 0,
|
||||
["mac_pack_color_word"] = 3, -- lui + ori + sw
|
||||
["mac_format_f3_color"] = 3, -- = mac_pack_color_word
|
||||
["mac_format_g4_color"] = 12, -- 4 x mac_pack_color_word
|
||||
["mac_load_tri_indices"] = 3, -- 3 x lhu
|
||||
["mac_gte_load_tri_verts"] = 18, -- 3 x {sll, addu, lw, lw, mtc2, mtc2}
|
||||
["mac_gte_store_f3"] = 3,
|
||||
["mac_gte_store_g3"] = 3,
|
||||
["mac_gte_store_g4_p012"] = 3,
|
||||
["mac_gte_store_g4_p3"] = 1,
|
||||
["mac_insert_ot_tag_f3"] = 11, -- 11 .word slots in the macro body
|
||||
["mac_insert_ot_tag_g4"] = 11,
|
||||
|
||||
-- Annotation markers (emit no code; pure metaprogram hints)
|
||||
["atom_label"] = 0,
|
||||
["atom_offset"] = 0,
|
||||
|
||||
@@ -153,6 +153,7 @@ local DW_AT_inline = 0x20 -- DWARF5 §7.7.1: DW_AT_inline (used by a
|
||||
local DW_AT_decl_file = 0x3A -- DWARF5 §7.7.1: DW_AT_decl_file (1-based file index into the CU's file table)
|
||||
local DW_AT_decl_line = 0x3B -- DWARF5 §7.7.1: DW_AT_decl_line
|
||||
|
||||
-- TODO(Ed): Remove this HARDCODE
|
||||
-- File index lookup table for the existing main line unit (Unit 2).
|
||||
-- Provenance paths come back with mixed slashes; we normalize to basename and look up against the line unit's actual file table.
|
||||
-- Current scope has two provenance basenames: hello_gte_tape.c (the atom's call site) and lottes_tape.h (the component definition).
|
||||
@@ -162,7 +163,8 @@ local DW_AT_decl_line = 0x3B -- DWARF5 §7.7.1: DW_AT_decl_line
|
||||
-- headers that are pure macros/typedefs (dsl.h, memory.h, math.h, mips.h, gp.h, gte.h, etc.) never appear.
|
||||
-- lottes_tape.h is the FIRST include that emits line entries (MipsAtomComp_ declarations), so it is the FIRST entry after the primary file.
|
||||
local PROVENANCE_BASENAME_TO_FILE_INDEX = {
|
||||
["hello_gte_tape.c"] = ATOM_SOURCE_FILE_INDEX, -- = 11
|
||||
["hello_joypad.tape.c"] = ATOM_SOURCE_FILE_INDEX, -- = 11
|
||||
["hello_gte.tape.c"] = ATOM_SOURCE_FILE_INDEX, -- = 11
|
||||
["lottes_tape.h"] = 2,
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
-- Load the annotation pass so we can re-validate each source against the canonical corpus projection.
|
||||
-- The annotation pass exposes `M.validate`, which returns the per-source AnnotationResult (atoms / annots / macros / binds / errors / warnings)
|
||||
-- that the report pass renders into the per-module `<dir_basename>.annotations.txt` output.
|
||||
local annotation = dofile(_bootstrap_dir .. "annotation.lua")
|
||||
local annotation = dofile(_bootstrap_dir .. "annotation.lua")
|
||||
|
||||
-- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `<module>.atoms.md` without re-walking source tokens).
|
||||
-- The pass itself emits no per-source files anymore; we only consume the two pure renderers here.
|
||||
@@ -45,8 +45,7 @@ local SECTION_HEADER_MACROS = "── Macro word-count declarations ───
|
||||
local SECTION_HEADER_ERRORS = "── Errors ──────────────────────────────────────────────"
|
||||
local SECTION_HEADER_WARNINGS = "── Warnings ────────────────────────────────────────────"
|
||||
|
||||
-- Lua pattern that captures the basename (last path segment) of a
|
||||
-- forward- or back-slash separated path.
|
||||
-- Lua pattern that captures the basename (last path segment) of a forward- or back-slash separated path.
|
||||
local BASENAME_PATTERN = "([^/\\]+)$"
|
||||
|
||||
-- Debug flag name — set to truthy in `_G` to enable verbose logging.
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
--- The report header includes `Info: N` alongside Findings / Errors / Warnings, and a dedicated
|
||||
--- `── 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.
|
||||
--- 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:
|
||||
--- `["static-analysis"] = {
|
||||
--- module = "passes.static_analysis",
|
||||
@@ -145,6 +150,38 @@ local OUTPUT_EXTENSION = ".static_analysis.txt"
|
||||
--- @field findings Finding[] -- findings for this atom
|
||||
--- @field total_cycles integer -- sum of token cycle costs
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Per-word-event helpers
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Pick the source-line field that best represents "where in the user's source file is this word?".
|
||||
--
|
||||
-- `word_events` (populated by `passes/emission_model.lua::stamp_root_provenance`) carry four line fields:
|
||||
-- * `call_line` — physical line in the ROOT atom's source (the line of the `mac_X(...)` call site that triggered this emission, or `body_line` for direct words in the atom body)
|
||||
-- * `body_line` — physical line in the body containing the emitted word (the atom body for direct words; the component body for words expanded inside `mac_X(...)`)
|
||||
-- * `def_line` — line of the COMPONENT's declaration in its source file (only meaningful for words emitted inside a component expansion)
|
||||
-- * `line` — body-relative line in the source text (not a physical source line; rarely useful in rendered findings)
|
||||
--
|
||||
-- For component-expanded words (e.g. the BD-slot nop of `jump_reg(R_AtomJmp)` inside `mac_yield()`),
|
||||
-- `body_line` points into the COMPONENT's source file (e.g. `lottes_tape.h:110` for `ac_yield`'s body).
|
||||
-- The user editing their atom body expects the line to point at THEIR source — i.e. the line where `mac_yield()`
|
||||
-- was called (e.g. `hello_gte_tape.c:35`). That line is `call_line`.
|
||||
--
|
||||
-- For direct words in the atom body (no invocation wrapping them), `call_line == body_line` already,
|
||||
-- so `call_line` works for both cases.
|
||||
local function line_for_word_event(ev)
|
||||
if ev == nil then return 0 end
|
||||
return ev.call_line or ev.body_line or ev.line or ev.def_line or 0
|
||||
end
|
||||
|
||||
-- True iff the given atom/component declaration has the bare `atom_dbg_skip` marker.
|
||||
-- Used by the structural handshake checks (`mac_yield_uniformity`, `hazard_nop_use`,
|
||||
-- `control_transfer_delay_slot_use`) to exempt runtime-helper declarations (`tape_exit`, `ac_yield`,
|
||||
-- and the `ac_*` macro components) from findings whose contract they intentionally don't satisfy.
|
||||
local function is_runtime_helper(atom)
|
||||
return atom and atom.debug_skip == true
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- classify_tokens — per-token classification
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -548,11 +585,12 @@ local function append_cu2_finding(atom, event, forward, transition,
|
||||
local event_ident = event.encoder or event.ident or "?"
|
||||
local policy = duffle.CU2_TRANSITION_POLICY or {}
|
||||
local evidence = policy.evidence or {}
|
||||
local event_line = line_for_word_event(event)
|
||||
atom.paths.hazards[#atom.paths.hazards + 1] = {
|
||||
check = "transfer_hazards",
|
||||
kind = kind,
|
||||
atom = atom.name,
|
||||
line = event.body_line or event.line or event.def_line or 0,
|
||||
line = event_line,
|
||||
source = event.def_path or event.source or "",
|
||||
relation_id = "mtc0_cu2_visibility",
|
||||
semantic = "MTC0",
|
||||
@@ -616,10 +654,11 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
||||
|
||||
local gap = ev_word - transition.producer_word - 1
|
||||
local target = transition.target_state
|
||||
local event_line = line_for_word_event(event)
|
||||
if target == "unknown" then
|
||||
append_cu2_finding(atom, event, forward, transition, gap, "info", "unknown",
|
||||
string.format("%s at line %d uses COP2 after an MTC0 Status write whose CU2 value is unknown (gap=%d, configured boundary=%d)"
|
||||
, atom.name, event.body_line or event.line or event.def_line or 0
|
||||
, atom.name, event_line
|
||||
, gap, transition.required
|
||||
)
|
||||
)
|
||||
@@ -632,7 +671,7 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
||||
local verb = target == "enabled" and "enable" or "disable"
|
||||
append_cu2_finding(atom, event, forward, transition, gap, "warning", "conservative",
|
||||
string.format("%s at line %d uses COP2 before the SR.CU2 %s transition has settled (gap=%d, required=%d; timing is conservative)"
|
||||
, atom.name, event.body_line or event.line or event.def_line or 0
|
||||
, atom.name, event_line
|
||||
, verb, gap, transition.required
|
||||
)
|
||||
)
|
||||
@@ -650,7 +689,7 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
||||
string.format(
|
||||
"%s at line %d: COP2 unavailable after SR.CU2 was disabled"
|
||||
.. " (gap=%d, required=%d)",
|
||||
atom.name, event.body_line or event.line or event.def_line or 0,
|
||||
atom.name, event_line,
|
||||
gap, transition.required))
|
||||
forward.cu2_state = "disabled"
|
||||
end
|
||||
@@ -705,7 +744,7 @@ local function analyze_hardware_relations(atom)
|
||||
|
||||
for _, ev in ipairs(events) do
|
||||
local ev_ident = ev.encoder or ev.ident or "?"
|
||||
local ev_line = ev.body_line or ev.line or ev.def_line or 0
|
||||
local ev_line = line_for_word_event(ev)
|
||||
local ev_source = ev.def_path or ev.source or ""
|
||||
local ev_args = ev.args or {}
|
||||
-- `word_events` use `i` as the 0-based word index across the entire expansion.
|
||||
@@ -864,7 +903,7 @@ local function analyze_hardware_relations(atom)
|
||||
|
||||
-- ── 4. Update semantic role state and stage post-command latch relations. ──
|
||||
-- A GTE command emits outputs with semantic roles (latest_screen_xy, otz, latest_color, etc.) per `duffle.GTE_COMMAND_OUTPUTS`.
|
||||
-- The walker records these on `forward_state.post_command_roles[<register>]` so the `gte_result_position` reader can later detect a reader that picks the wrong register.
|
||||
-- The walker records these on `forward_state.post_command_roles[<register>]` so the `gte_role_mismatch` reader can later detect a reader that picks the wrong register.
|
||||
--
|
||||
-- The walker also stages POST-COMMAND LATCH relations (kind = "command_latch_input"): a subsequent MTC2/CTC2 overwrite of a latched output before the measured boundary is a hazard.
|
||||
-- The relation kind is intentionally separate from the preceding MTC2 → command relation (`MTC2` / `CTC2` / `LWC2`).
|
||||
@@ -977,7 +1016,7 @@ local function check_gte_input_latch(atom, _pipe_ctx, findings)
|
||||
end
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
-- Check #1e: gte_result_position (READER for forward_state semantic roles).
|
||||
-- Check #1e: gte_role_mismatch (READER for forward_state semantic roles).
|
||||
--
|
||||
-- A GTE command emits outputs with semantic roles (latest_screen_xy, otz, latest_color, etc.) per `duffle.GTE_COMMAND_OUTPUTS`.
|
||||
-- The forward walker records `forward_state.post_command_roles[<register>]` after each command.
|
||||
@@ -985,43 +1024,16 @@ end
|
||||
-- A subsequent MFC2 (or any encoder that reads a C2 register) that picks the WRONG register for the active role emits a `result_role_mismatch` warning.
|
||||
-- For example, reading `C2_SXY0` after RTPS is wrong: the `latest_screen_xy` role is `C2_SXY2`.
|
||||
--
|
||||
-- Note: the OLD `gte_result_position` check also emitted table-gap info findings for `_post_<cmd>` components missing a row in `duffle.GTE_COMPONENT_RESULT_CONTRACTS`. That table-gap check was based on the `_post_<cmd>` NAMING convention rather than hardware truth, and was removed (the user did not want naming to encode ordering semantics; a proper `atom_info` directive for ordering semantics is a future TODO).
|
||||
--
|
||||
-- The first `transfer_hazards` reader comment above records the projection contract.
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local function check_gte_result_position(atom, _pipe_ctx, findings)
|
||||
local function check_gte_role_mismatch(atom, _pipe_ctx, findings)
|
||||
local forward = atom.paths and atom.paths.forward_state
|
||||
if not forward or not forward.post_command_roles then return end
|
||||
local events = atom.paths.word_events or {}
|
||||
|
||||
-- Build a set of known _post_<cmd> component names whose contract rows we have to verify
|
||||
-- (table-gap detection: a missing row key is itself an info finding).
|
||||
-- The names are the BODY-LEVEL component calls that appear in atom body text;
|
||||
-- The walker doesn't expose body tokens to the reader, so we scan the events' root_call_text.
|
||||
local contracts = duffle.GTE_COMPONENT_RESULT_CONTRACTS or {}
|
||||
local component_names_seen = {}
|
||||
for _, ev in ipairs(events) do
|
||||
local root_call = ev.root_call_text or ev.call_text or ""
|
||||
local name = root_call:match("^([%w_]+)") or ""
|
||||
if name:find("_post_") then component_names_seen[name] = true end
|
||||
end
|
||||
for component_name in pairs(component_names_seen) do
|
||||
-- Strip any trailing parenthesized argument list / whitespace.
|
||||
local bare = component_name:match("^([%w_]+)") or component_name
|
||||
if contracts[bare] == nil then
|
||||
findings[#findings + 1] = {
|
||||
check = "gte_result_position",
|
||||
kind = "info",
|
||||
atom = atom.name,
|
||||
line = 0,
|
||||
source = "",
|
||||
relation_id = "table_gap",
|
||||
component_name = bare,
|
||||
msg = string.format("%s: component %q has no GTE_COMPONENT_RESULT_CONTRACTS row (unknown _post_<cmd> contract)"
|
||||
, atom.name, bare),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- For each word event whose encoder is `gte_mv_from_data_r`, look up the register being read in `forward_state.post_command_roles`.
|
||||
-- If a role is set, the reader's register must match the role's register (the registered "latest_<role>" target).
|
||||
for _, ev in ipairs(events) do
|
||||
@@ -1044,11 +1056,12 @@ local function check_gte_result_position(atom, _pipe_ctx, findings)
|
||||
-- This is a semantic mismatch.
|
||||
if reg ~= latest_screen_xy_entry.command_register
|
||||
and (reg == "C2_SXY0" or reg == "C2_SXY1") then
|
||||
local ev_line = line_for_word_event(ev)
|
||||
findings[#findings + 1] = {
|
||||
check = "gte_result_position",
|
||||
check = "gte_role_mismatch",
|
||||
kind = "warning",
|
||||
atom = atom.name,
|
||||
line = ev.body_line or ev.line or ev.def_line or 0,
|
||||
line = ev_line,
|
||||
source = ev.def_path or ev.source or "",
|
||||
relation_id = "result_role_mismatch",
|
||||
semantic = "result_position",
|
||||
@@ -1059,7 +1072,7 @@ local function check_gte_result_position(atom, _pipe_ctx, findings)
|
||||
producer_word = latest_screen_xy_entry.producer_word,
|
||||
producer_line = latest_screen_xy_entry.producer_line,
|
||||
msg = string.format("%s at line %d: reading %s after %s but the %s role is C2_SXY2 (not %s)"
|
||||
, atom.name, ev.body_line or ev.line or ev.def_line or 0
|
||||
, atom.name, ev_line
|
||||
, reg, latest_screen_xy_entry.command
|
||||
, latest_screen_xy_entry.role
|
||||
, reg),
|
||||
@@ -1081,6 +1094,13 @@ end
|
||||
-- Branch/jump delay-slot NOPs belong to `control_transfer_delay_slot_use`, so this check leaves them unclassified.
|
||||
-- The fixed `mac_yield()` handshake (`jump_reg(R_AtomJmp), nop`) is preserved as suppressed.
|
||||
--
|
||||
-- Both classifications emit at `info` severity: `modeled-required` documents the model boundary and `modeled-redundant`
|
||||
-- is a soft observation ("you have a redundant nop; consider replacing it").
|
||||
-- Neither is a logic failure, so neither rises to `warning`.
|
||||
--
|
||||
-- `atom_dbg_skip` runtime helpers (`tape_exit`, `ac_yield`, the `ac_*` macro components) are exempt:
|
||||
-- their structural nops are part of the fixed handshake and not author choices.
|
||||
--
|
||||
-- The first `transfer_hazards` reader comment above records the projection contract.
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1088,6 +1108,9 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||
local forward = atom.paths and atom.paths.forward_state
|
||||
local events = atom.paths.word_events or {}
|
||||
if not events or #events == 0 then return end
|
||||
-- Runtime-helper atoms / components (e.g. tape_exit, ac_yield) carry `debug_skip = true` from the bare
|
||||
-- `atom_dbg_skip` marker; their structural nops are part of the fixed handshake and not author choices.
|
||||
if is_runtime_helper(atom) then return end
|
||||
|
||||
-- The walker does not currently snapshot the pending state per event; we replay the same forward walk cheaply here.
|
||||
-- The replay is observation-only (no staging); the only output is one finding per non-BD-slot nop with its classification.
|
||||
@@ -1097,20 +1120,16 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||
local ev_ident = ev.encoder or ""
|
||||
local ev_args = ev.args or {}
|
||||
local ev_word = ev.i or 0
|
||||
local ev_line = line_for_word_event(ev)
|
||||
|
||||
-- Classify the nop BEFORE its event is applied to the pending state.
|
||||
if ev_ident == "nop" and prev_ev ~= nil then
|
||||
-- Skip BD-slot nops: they are exclusively owned by control_transfer_delay_slot_use.
|
||||
local prev_ident = prev_ev.encoder or ""
|
||||
local prev_args = prev_ev.args or {}
|
||||
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||
local is_bd_slot = false
|
||||
local policy = bd_policies[prev_ident]
|
||||
if policy then
|
||||
local arg1 = prev_args[1]
|
||||
local suppressed = policy.suppress_arg1 and policy.suppress_arg1[arg1] or nil
|
||||
if not suppressed then is_bd_slot = true end
|
||||
end
|
||||
-- Every BD-slot nop is structural; this check never reports on it.
|
||||
-- (The earlier `if not suppressed then is_bd_slot = true end` form inverted the suppression — the `mac_yield()` handshake's `jump_reg(R_AtomJmp)` was incorrectly flagged.)
|
||||
local prev_ident = prev_ev.encoder or ""
|
||||
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||
local is_bd_slot = bd_policies[prev_ident] ~= nil
|
||||
if not is_bd_slot then
|
||||
-- Find a pending modeled relation that this nop would retire.
|
||||
local retired = nil
|
||||
@@ -1154,7 +1173,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||
check = "hazard_nop_use",
|
||||
kind = "info",
|
||||
atom = atom.name,
|
||||
line = ev.body_line or ev.line or ev.def_line or 0,
|
||||
line = ev_line,
|
||||
source = ev.def_path or ev.source or "",
|
||||
nop_classification = "modeled-required",
|
||||
nop_word_index = ev_word,
|
||||
@@ -1162,7 +1181,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||
producer_destination = retired.destination,
|
||||
consumer_token = would_be_consumer or "<would-be-consumer>",
|
||||
msg = string.format("%s at line %d: nop at word %d is modeled-required (retires %s for %s)"
|
||||
, atom.name, ev.body_line or ev.line or ev.def_line or 0, ev_word, retired.relation.id, retired.destination
|
||||
, atom.name, ev_line, ev_word, retired.relation.id, retired.destination
|
||||
),
|
||||
}
|
||||
else
|
||||
@@ -1170,16 +1189,16 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||
local slot_kind = "plain"
|
||||
findings[#findings + 1] = {
|
||||
check = "hazard_nop_use",
|
||||
kind = "warning",
|
||||
kind = "info",
|
||||
atom = atom.name,
|
||||
line = ev.body_line or ev.line or ev.def_line or 0,
|
||||
line = ev_line,
|
||||
source = ev.def_path or ev.source or "",
|
||||
nop_classification = "modeled-redundant",
|
||||
nop_word_index = ev_word,
|
||||
retired_relation = nil,
|
||||
slot_kind = slot_kind,
|
||||
msg = string.format("%s at line %d: nop at word %d is modeled-redundant (no pending modeled relation)"
|
||||
, atom.name, ev.body_line or ev.line or ev.def_line or 0, ev_word
|
||||
, atom.name, ev_line, ev_word
|
||||
),
|
||||
}
|
||||
end
|
||||
@@ -1251,6 +1270,9 @@ end
|
||||
-- Suppress the finding when `policy.suppress_arg1[first_arg]` is non-nil.
|
||||
-- The only current suppression is `jump_reg(R_AtomJmp)`, the fixed `mac_yield()` handshake.
|
||||
--
|
||||
-- `atom_dbg_skip` runtime helpers (`tape_exit`, `ac_yield`, the `ac_*` macro components) are exempt:
|
||||
-- their BD slots are part of the fixed handshake (`jump_reg(rret_addr), nop` for tape_exit, `jump_reg(R_AtomJmp), nop` for ac_yield).
|
||||
--
|
||||
-- `pipe_ctx` is unused; the uniform `(atom, pipe_ctx, findings)` signature is preserved so the check plugs into
|
||||
-- the existing CHECK_RULES dispatch without modifying the per-atom loop or analyze_atom_paths.
|
||||
-- `passes/emission_model` already normalizes `nop2` to two `nop` events and `atom_label` to zero events, so no special-case branching is needed for either.
|
||||
@@ -1259,6 +1281,9 @@ end
|
||||
local function check_control_transfer_delay_slot_use(atom, pipe_ctx, findings)
|
||||
local events = atom.paths.word_events or {}
|
||||
if not events or #events == 0 then return end
|
||||
-- Runtime-helper atoms / components (e.g. tape_exit, ac_yield) carry `debug_skip = true` from the bare
|
||||
-- `atom_dbg_skip` marker; their structural BD slots are part of the fixed handshake.
|
||||
if is_runtime_helper(atom) then return end
|
||||
local policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||
for event_idx, event in ipairs(events) do
|
||||
-- Canonical word_events use `encoder` as the leading identifier of the emitting token).
|
||||
@@ -1273,9 +1298,9 @@ local function check_control_transfer_delay_slot_use(atom, pipe_ctx, findings)
|
||||
local slot = events[event_idx + 1]
|
||||
local slot_ident = slot and (slot.encoder or slot.ident) or "<missing>"
|
||||
if slot == nil or (slot.encoder or slot.ident) == "nop" then
|
||||
-- Each word event carries `body_line` as the physical source line.
|
||||
-- Use `body_line`, then `def_line`, then 0.
|
||||
local ev_line = event.body_line or event.line or event.def_line or 0
|
||||
-- Prefer `call_line` (the line of the `mac_X(...)` call site in the atom body) so the rendered
|
||||
-- finding points at the user's source, not at the vendored component body.
|
||||
local ev_line = line_for_word_event(event)
|
||||
findings[#findings + 1] = {
|
||||
atom = atom.name,
|
||||
line = ev_line,
|
||||
@@ -1300,8 +1325,17 @@ end
|
||||
--- Empty bodies are not currently flagged — runtime infrastructure atoms like
|
||||
--- `MipsAtom_(yield) { mac_yield() }` and `MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }`
|
||||
--- are valid as-is; mac_yield at the end is the contract.
|
||||
---
|
||||
--- Runtime helpers carrying the bare `atom_dbg_skip` marker (`tape_exit`, `ac_yield`, the `ac_*` macro components) are exempt:
|
||||
--- they intentionally do not follow the standard "1 yield at the end" contract. `tape_exit` performs its own `jump_reg(rret_addr),
|
||||
--- nop` to return from the tape runner; `ac_yield` IS the `mac_yield()` implementation.
|
||||
--- Flagging them as "missing mac_yield" is signal noise, not a logic failure.
|
||||
---
|
||||
--- Uses the standard `(atom, pipe_ctx, findings)` signature; `pipe_ctx` is unused.
|
||||
local function check_mac_yield_uniformity(atom, pipe_ctx, findings)
|
||||
-- Runtime-helper atoms / components (e.g. tape_exit, ac_yield) carry `debug_skip = true` from the bare
|
||||
-- `atom_dbg_skip` marker; they intentionally break the "1 yield at the end" contract.
|
||||
if is_runtime_helper(atom) then return end
|
||||
-- Per-kind semantics:
|
||||
-- MipsAtom_ (baked atom): exactly 1 mac_yield at the end of the body. Control transfer is the atom's job.
|
||||
-- MipsAtomComp_ (bare static-array component): ZERO mac_yield.
|
||||
@@ -1932,7 +1966,7 @@ end
|
||||
local CHECK_RULES = {
|
||||
{ name = "transfer_hazards", per_atom = check_transfer_hazards },
|
||||
{ name = "gte_input_latch", per_atom = check_gte_input_latch },
|
||||
{ name = "gte_result_position", per_atom = check_gte_result_position },
|
||||
{ name = "gte_role_mismatch", per_atom = check_gte_role_mismatch },
|
||||
{ name = "hazard_nop_use", per_atom = check_hazard_nop_use },
|
||||
{ name = "control_transfer_delay_slot_use",per_atom = check_control_transfer_delay_slot_use},
|
||||
{ name = "mac_yield_uniformity", per_atom = check_mac_yield_uniformity },
|
||||
|
||||
Reference in New Issue
Block a user