mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-07 16:18:51 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02658d3609 | ||
|
|
dbc459b7e0 | ||
|
|
a704341fc6 | ||
|
|
7421b32fd7 | ||
|
|
e2eb74be19 | ||
|
|
338f1fe46e |
@@ -17,3 +17,5 @@ toolchain/PSn00bSDK
|
|||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
toolchain/lfs
|
toolchain/lfs
|
||||||
toolchain/lpeg
|
toolchain/lpeg
|
||||||
|
|
||||||
|
scratch
|
||||||
|
|||||||
Vendored
-1
@@ -138,7 +138,6 @@
|
|||||||
"monitor reset shellhalt",
|
"monitor reset shellhalt",
|
||||||
"load build/hello_gte.dwarf-injected.elf",
|
"load build/hello_gte.dwarf-injected.elf",
|
||||||
"source scripts/gdb/gdb_tape_atoms.gdb",
|
"source scripts/gdb/gdb_tape_atoms.gdb",
|
||||||
"source build/gen/hello_gte.gdbinit",
|
|
||||||
"tbreak main",
|
"tbreak main",
|
||||||
"continue"
|
"continue"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -98,11 +98,11 @@ WORD_COUNT(mac_format_f3_color, 3)
|
|||||||
/* atom_dbg_skip */
|
/* atom_dbg_skip */
|
||||||
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3.
|
/* 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). */
|
* 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_SXY0, R_PrimCursor, O_(Poly_F3,p0)) \
|
||||||
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)) \
|
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)) \
|
||||||
, gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2))
|
, 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) \
|
#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,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
|
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
|
||||||
* G4 triangle portion to p0/p1/p2.
|
* G4 triangle portion to p0/p1/p2.
|
||||||
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
||||||
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2
|
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
|
||||||
* get overwritten with v3 (RTPS writes only to SXY2, but to keep the
|
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
|
||||||
* three registers aligned with v0/v1/v2 you must store before RTPS).
|
#define mac_gte_store_g4_p012(...) \
|
||||||
* 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_SXY0, R_PrimCursor, O_(Poly_G4,p0)) \
|
||||||
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)) \
|
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)) \
|
||||||
, gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2))
|
, 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 */
|
/* atom_dbg_skip */
|
||||||
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
||||||
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its
|
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
|
||||||
* single-vertex result to SXY2; SXY0 still holds v0.screen from the
|
* SXY0 still holds v0.screen from the earlier RTPT.
|
||||||
* earlier RTPT — DO NOT read SXY0 here, that's the bug this name
|
|
||||||
* prevents).
|
|
||||||
*/
|
*/
|
||||||
#define mac_gte_store_g4_p3_post_rtps(...) \
|
#define mac_gte_store_g4_p3(...) \
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_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.
|
/* 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). */
|
* 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_SXY0, R_PrimCursor, O_(Poly_F3,p0)),
|
||||||
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)),
|
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)),
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2)),
|
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
|
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
|
||||||
* G4 triangle portion to p0/p1/p2.
|
* G4 triangle portion to p0/p1/p2.
|
||||||
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
||||||
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2
|
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
|
||||||
* get overwritten with v3 (RTPS writes only to SXY2, but to keep the
|
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
|
||||||
* three registers aligned with v0/v1/v2 you must store before RTPS).
|
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p012) {
|
||||||
* 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) {
|
|
||||||
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_G4,p0)),
|
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_G4,p0)),
|
||||||
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)),
|
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)),
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2)),
|
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2)),
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
||||||
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its
|
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
|
||||||
* single-vertex result to SXY2; SXY0 still holds v0.screen from the
|
* SXY0 still holds v0.screen from the earlier RTPT.
|
||||||
* earlier RTPT — DO NOT read SXY0 here, that's the bug this name
|
|
||||||
* prevents).
|
|
||||||
*/
|
*/
|
||||||
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
|
#pragma endregion Macro Atom Components
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
#pragma region hello_gte_tape
|
#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_cull_cube_g4_face_exit 42
|
||||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 12
|
#define _atom_offset_bounds_chk_cube_g4_face_exit 24
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
atom_offset_cull_cube_g4_face_exit = _atom_offset_cull_cube_g4_face_exit,
|
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) ---
|
// --- atom: floor_f3_face (58 words) ---
|
||||||
|
|
||||||
#define _atom_offset_culling_floor_f3_face_exit 25
|
#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 {
|
enum {
|
||||||
atom_offset_culling_floor_f3_face_exit = _atom_offset_culling_floor_f3_face_exit,
|
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/lottes_tape.h"
|
||||||
#include "duffle/word_count.metadata.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.h"
|
||||||
|
|
||||||
#include "hello_gte_tape.c"
|
#include "hello_gte.tape.c"
|
||||||
|
|
||||||
typedef U4 OrderingTable_Buffer[OrderingTbl_Len];
|
typedef U4 OrderingTable_Buffer[OrderingTbl_Len];
|
||||||
typedef Array_(OrderingTable_Buffer, 2);
|
typedef Array_(OrderingTable_Buffer, 2);
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
# include "duffle/atom_dsl.h"
|
# include "duffle/atom_dsl.h"
|
||||||
# include "duffle/lottes_tape.h"
|
# include "duffle/lottes_tape.h"
|
||||||
# include "duffle/word_count.metadata.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.h"
|
||||||
#endif
|
#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)),
|
load_half_u(R_T3, R_FaceCursor, 3 * S_(S2)),
|
||||||
|
|
||||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
||||||
nop2, gte_cmdw_rotate_translate_perspective_triple,
|
nop2, gte_cmdw_rotate_translate_perspective_triple, // required cpu -> gte delay slot
|
||||||
nop2, gte_cmdw_nclip,
|
gte_cmdw_nclip,
|
||||||
|
|
||||||
nop2, gte_mv_from_data_r(R_T0, C2_MAC0),
|
gte_mv_from_data_r(R_T0, C2_MAC0), nop,
|
||||||
nop,
|
|
||||||
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)), nop,
|
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)), nop,
|
||||||
|
|
||||||
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
|
store_word(R_0, R_PrimCursor, O_(Poly_G4, tag)),
|
||||||
|
shift_lleft(R_AT, R_T3, v3s2_byteoff), add_u(R_AT, R_AT, R_VertBase),
|
||||||
|
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(
|
mac_format_g4_color(
|
||||||
/* c0 magenta */ 0xFF, 0x00, 0xFF,
|
/* c0 magenta */ 0xFF, 0x00, 0xFF,
|
||||||
/* c1 yellow */ 0xFF, 0xFF, 0x00,
|
/* c1 yellow */ 0xFF, 0xFF, 0x00,
|
||||||
/* c2 cyan */ 0x00, 0xFF, 0xFF,
|
/* c2 cyan */ 0x00, 0xFF, 0xFF,
|
||||||
/* c3 green */ 0x00, 0xFF, 0x00),
|
/* c3 green */ 0x00, 0xFF, 0x00),
|
||||||
mac_gte_store_g4_p012_post_rtpt_pre_rtps(),
|
// end: branch(bounds_chk)
|
||||||
|
// end: branch(cull)
|
||||||
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),
|
|
||||||
|
|
||||||
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(),
|
|
||||||
|
|
||||||
atom_label(cube_g4_face_exit)
|
atom_label(cube_g4_face_exit)
|
||||||
add_ui_self(R_PrimCursor, S_(Poly_G4)), /* 9 words = Poly_G4 */
|
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()
|
mac_yield()
|
||||||
};
|
};
|
||||||
|
|
||||||
atom_dbg_skip
|
// atom_dbg_skip
|
||||||
internal
|
internal
|
||||||
MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
MipsAtom_(floor_f3_face) atom_info(atom_phase(floor_f3)
|
||||||
, atom_reads( R_PrimCursor, R_FaceCursor, R_VertBase, R_OtBase)
|
, 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_load_tri_indices( R_T0, R_T1, R_T2),
|
||||||
mac_gte_load_tri_verts(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),
|
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.
|
nop, branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop, // required gte -> cpu load-delay slot.
|
||||||
/* Format Primitive */
|
/* Format Primitive */
|
||||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
mac_gte_store_f3(),
|
||||||
mac_gte_store_f3_post_rtpt(),
|
|
||||||
|
|
||||||
/* Calculate Depth */
|
/* Calculate Depth */
|
||||||
gte_avg_sort_z3,
|
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),
|
add_ui( R_AT, R_0, OrderingTbl_Len),
|
||||||
set_lt_u( R_AT, R_T1, R_AT),
|
set_lt_u( R_AT, R_T1, R_AT),
|
||||||
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
branch_equal(R_AT, R_0, atom_offset(bounds_chk, floor_f3_face_exit)), nop,
|
||||||
/* Insert into Ordering Table Linked List */
|
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||||
mac_insert_ot_tag_f3(),
|
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
|
||||||
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
add_ui_self(R_PrimCursor, S_(Poly_F3)), /* Advance Prim Cursor (5 words) */
|
||||||
// Note(Ed): No bounds checking, should be checked before atom runs.
|
// 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
|
# build-graphis_hello
|
||||||
|
|
||||||
function build-gte_hello {
|
function build-hello_gte {
|
||||||
$includes += @()
|
$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_duffle = join-path $path_code 'duffle'
|
||||||
$path_atom_metadata = join-path $path_duffle 'word_count.metadata.h'
|
$path_atom_metadata = join-path $path_duffle 'word_count.metadata.h'
|
||||||
$path_build_gen = join-path $path_build 'gen'
|
$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...
|
# NO idea if this works yet...
|
||||||
function Send-ToEmulator { param( [string]$exePath )
|
function Send-ToEmulator { param( [string]$exePath )
|
||||||
|
|||||||
+17
-38
@@ -1180,10 +1180,9 @@ M.GTE_COMMAND_INPUTS = {
|
|||||||
-- * "mac_result" : generic MAC output (nclip, op, mvmva)
|
-- * "mac_result" : generic MAC output (nclip, op, mvmva)
|
||||||
--
|
--
|
||||||
-- Consumers:
|
-- Consumers:
|
||||||
-- * passes/static_analysis.lua::analyze_hardware_relations (the walker reads this after a GTE command to update
|
-- * 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`).
|
||||||
-- `forward_state.post_command_roles` for `gte_result_position`).
|
-- * passes/static_analysis.lua::check_gte_role_mismatch (per-atom CHECK_RULES reader; renders role mismatches).
|
||||||
-- * 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 the gte_role_mismatch check.
|
||||||
-- This table is consumed by the hardware-relation analyzer and result-position check.
|
|
||||||
M.GTE_COMMAND_OUTPUTS = {
|
M.GTE_COMMAND_OUTPUTS = {
|
||||||
-- RTPS: writes one screen coordinate (the perspective-divide result) into C2_SXY2.
|
-- 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.
|
-- 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).
|
-- 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.
|
||||||
-- 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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Operand-class table for the COP2->GPR load-delay check.
|
-- Operand-class table for the COP2->GPR load-delay check.
|
||||||
--
|
--
|
||||||
@@ -1447,6 +1422,7 @@ M.GP0_CMD_BY_SHAPE = {
|
|||||||
["g4"] = 0x38, ["gt4"] = 0x3C,
|
["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.
|
-- 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.)
|
-- (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
|
-- The sum across `mac_format_X_color` + `mac_gte_store_X_post_*` + `mac_insert_ot_tag_X` calls in an atom body must equal
|
||||||
@@ -1455,10 +1431,10 @@ M.GP0_MACRO_CONTRIB = {
|
|||||||
["mac_format_f3_color"] = 1,
|
["mac_format_f3_color"] = 1,
|
||||||
["mac_format_g3_color"] = 3,
|
["mac_format_g3_color"] = 3,
|
||||||
["mac_format_g4_color"] = 4,
|
["mac_format_g4_color"] = 4,
|
||||||
["mac_gte_store_f3_post_rtpt"] = 3,
|
["mac_gte_store_f3"] = 3,
|
||||||
["mac_gte_store_g3_post_rtpt"] = 3,
|
["mac_gte_store_g3"] = 3,
|
||||||
["mac_gte_store_g4_p012_post_rtpt_pre_rtps"] = 3,
|
["mac_gte_store_g4_p012"] = 3,
|
||||||
["mac_gte_store_g4_p3_post_rtps"] = 1,
|
["mac_gte_store_g4_p3"] = 1,
|
||||||
["mac_insert_ot_tag_f3"] = 1,
|
["mac_insert_ot_tag_f3"] = 1,
|
||||||
["mac_insert_ot_tag_g4"] = 1,
|
["mac_insert_ot_tag_g4"] = 1,
|
||||||
}
|
}
|
||||||
@@ -1583,6 +1559,8 @@ M.INSTRUCTION_LATENCY = {
|
|||||||
["gte_load_v1"] = 2,
|
["gte_load_v1"] = 2,
|
||||||
["gte_load_v2"] = 2,
|
["gte_load_v2"] = 2,
|
||||||
["gte_load_v0v1v2"] = 6,
|
["gte_load_v0v1v2"] = 6,
|
||||||
|
|
||||||
|
-- TODO(Ed): REMOVE THIS HARDCODE, THIS SHOULD BE RESOLVED AUTOMATICALLY
|
||||||
-- mac_* helpers (cycle cost = sum of the expanded instructions)
|
-- 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 transfers control; cycle budget is 0 (the next atom absorbs the cost).
|
||||||
["mac_yield"] = 0,
|
["mac_yield"] = 0,
|
||||||
@@ -1591,12 +1569,13 @@ M.INSTRUCTION_LATENCY = {
|
|||||||
["mac_format_g4_color"] = 12, -- 4 x mac_pack_color_word
|
["mac_format_g4_color"] = 12, -- 4 x mac_pack_color_word
|
||||||
["mac_load_tri_indices"] = 3, -- 3 x lhu
|
["mac_load_tri_indices"] = 3, -- 3 x lhu
|
||||||
["mac_gte_load_tri_verts"] = 18, -- 3 x {sll, addu, lw, lw, mtc2, mtc2}
|
["mac_gte_load_tri_verts"] = 18, -- 3 x {sll, addu, lw, lw, mtc2, mtc2}
|
||||||
["mac_gte_store_f3_post_rtpt"] = 3,
|
["mac_gte_store_f3"] = 3,
|
||||||
["mac_gte_store_g3_post_rtpt"] = 3,
|
["mac_gte_store_g3"] = 3,
|
||||||
["mac_gte_store_g4_p012_post_rtpt_pre_rtps"] = 3,
|
["mac_gte_store_g4_p012"] = 3,
|
||||||
["mac_gte_store_g4_p3_post_rtps"] = 1,
|
["mac_gte_store_g4_p3"] = 1,
|
||||||
["mac_insert_ot_tag_f3"] = 11, -- 11 .word slots in the macro body
|
["mac_insert_ot_tag_f3"] = 11, -- 11 .word slots in the macro body
|
||||||
["mac_insert_ot_tag_g4"] = 11,
|
["mac_insert_ot_tag_g4"] = 11,
|
||||||
|
|
||||||
-- Annotation markers (emit no code; pure metaprogram hints)
|
-- Annotation markers (emit no code; pure metaprogram hints)
|
||||||
["atom_label"] = 0,
|
["atom_label"] = 0,
|
||||||
["atom_offset"] = 0,
|
["atom_offset"] = 0,
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
# Wrapper for the tape-atom step-debug helpers.
|
# Wrapper for the tape-atom step-debug helpers.
|
||||||
# The 9 user commands are defined here as STUBS (degraded-state messages).
|
# The 9 user commands are defined here as STUBS (degraded-state messages).
|
||||||
# The real implementations + the per-atom data tables are emitted by `passes/atoms_source_map.lua`
|
# The real implementations + the per-atom data tables are emitted by `passes/atoms_source_map.lua`
|
||||||
# (post-link invocation: `ps1_meta.lua --atoms-source-map --gdb-runtime --elf <elf>`) into `build/gen/gdb_tape_atoms_runtime.gdb`.
|
# (post-link invocation: `ps1_meta.lua --atoms-source-map --gdb-runtime --elf <elf>`) into `build/gdb_tape_atoms_runtime.gdb`.
|
||||||
# Sourcing that file RE-DEFINES the commands with real implementations.
|
# Sourcing that file RE-DEFINES the commands with real implementations.
|
||||||
#
|
#
|
||||||
# If `build/gen/gdb_tape_atoms_runtime.gdb` is missing or stale, the stubs remain (E1: no source map).
|
# If `build/gdb_tape_atoms_runtime.gdb` is missing or stale, the stubs remain (E1: no source map).
|
||||||
# The user just needs to re-run `build_psyq.ps1` to regenerate.
|
# The user just needs to re-run `build_psyq.ps1` to regenerate.
|
||||||
|
|
||||||
# ── Stub commands (defined here so they're always present, even if the runtime file is missing). The runtime file overrides these if sourced. ──
|
# ?? Stub commands (defined here so they're always present, even if the runtime file is missing). The runtime file overrides these if sourced. ??
|
||||||
|
|
||||||
define tape_atoms
|
define tape_atoms
|
||||||
echo "[gdb_tape_atoms] STUB: runtime file build/gen/gdb_tape_atoms_runtime.gdb not found."
|
echo "[gdb_tape_atoms] STUB: runtime file build/gdb_tape_atoms_runtime.gdb not found."
|
||||||
echo "[gdb_tape_atoms] STUB: run .\\build_psyq.ps1 to regenerate, then re-source this file."
|
echo "[gdb_tape_atoms] STUB: run .\\build_psyq.ps1 to regenerate, then re-source this file."
|
||||||
end
|
end
|
||||||
document tape_atoms
|
document tape_atoms
|
||||||
@@ -21,35 +21,35 @@ document tape_atoms
|
|||||||
end
|
end
|
||||||
|
|
||||||
define break_atom
|
define break_atom
|
||||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
echo "[gdb_tape_atoms] STUB: build/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||||
end
|
end
|
||||||
document break_atom
|
document break_atom
|
||||||
Set a breakpoint at the start of tape atom <name>. STUB state.
|
Set a breakpoint at the start of tape atom <name>. STUB state.
|
||||||
end
|
end
|
||||||
|
|
||||||
define step_atom
|
define step_atom
|
||||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
echo "[gdb_tape_atoms] STUB: build/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||||
end
|
end
|
||||||
document step_atom
|
document step_atom
|
||||||
Resume execution until the next atom boundary. STUB state.
|
Resume execution until the next atom boundary. STUB state.
|
||||||
end
|
end
|
||||||
|
|
||||||
define next_atom
|
define next_atom
|
||||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
echo "[gdb_tape_atoms] STUB: build/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||||
end
|
end
|
||||||
document next_atom
|
document next_atom
|
||||||
Alias for step_atom. STUB state.
|
Alias for step_atom. STUB state.
|
||||||
end
|
end
|
||||||
|
|
||||||
define where_in_atom
|
define where_in_atom
|
||||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
echo "[gdb_tape_atoms] STUB: build/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||||
end
|
end
|
||||||
document where_in_atom
|
document where_in_atom
|
||||||
Report current atom name, .rodata addr, word offset, and source line (if known). STUB state.
|
Report current atom name, .rodata addr, word offset, and source line (if known). STUB state.
|
||||||
end
|
end
|
||||||
|
|
||||||
define stepi_inside_atom
|
define stepi_inside_atom
|
||||||
echo "[gdb_tape_atoms] STUB: build/gen/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
echo "[gdb_tape_atoms] STUB: build/gdb_tape_atoms_runtime.gdb not sourced. Run build_psyq.ps1."
|
||||||
end
|
end
|
||||||
document stepi_inside_atom
|
document stepi_inside_atom
|
||||||
One MIPS-instruction step, then where_in_atom. STUB state.
|
One MIPS-instruction step, then where_in_atom. STUB state.
|
||||||
@@ -89,17 +89,17 @@ document wave_ctx
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# ── Source the runtime file (re-defines commands with real impls + data). ──
|
# ?? Source the runtime file (re-defines commands with real impls + data). ??
|
||||||
|
|
||||||
# Try to source from project-root-relative path first (the typical case).
|
# Try to source from project-root-relative path first (the typical case).
|
||||||
# If the user is in a different CWD, the source will fail and stubs remain.
|
# If the user is in a different CWD, the source will fail and stubs remain.
|
||||||
# The runtime file path is computed relative to the ELF's source map convention (build/gen/gdb_tape_atoms_runtime.gdb).
|
# The runtime file path is computed relative to the ELF's source map convention (build/gdb_tape_atoms_runtime.gdb).
|
||||||
echo [gdb_tape_atoms] Wrapper loaded. Sourcing runtime file...
|
echo [gdb_tape_atoms] Wrapper loaded. Sourcing runtime file...
|
||||||
# Suppress the "Redefine command" prompts that would otherwise appear when the runtime file overrides the 9 stub commands defined above.
|
# Suppress the "Redefine command" prompts that would otherwise appear when the runtime file overrides the 9 stub commands defined above.
|
||||||
# The runtime's `define` blocks are intended to overwrite — there's no ambiguity to confirm.
|
# The runtime's `define` blocks are intended to overwrite ? there's no ambiguity to confirm.
|
||||||
set confirm off
|
set confirm off
|
||||||
|
|
||||||
# Source the runtime file (re-defines commands with real impls + data).
|
# Source the runtime file (re-defines commands with real impls + data).
|
||||||
source build/gen/gdb_tape_atoms_runtime.gdb
|
source build/gdb_tape_atoms_runtime.gdb
|
||||||
set confirm on
|
set confirm on
|
||||||
echo [gdb_tape_atoms] Runtime sourced successfully (9 commands now have real implementations).
|
echo [gdb_tape_atoms] Runtime sourced successfully (9 commands now have real implementations).
|
||||||
|
|||||||
@@ -7,18 +7,11 @@
|
|||||||
---
|
---
|
||||||
--- Ownership: the canonical `ctx.shared.corpus` supplies cross-source registries, while each `src.scan` supplies its source's declarations and bodies.
|
--- Ownership: the canonical `ctx.shared.corpus` supplies cross-source registries, while each `src.scan` supplies its source's declarations and bodies.
|
||||||
--- A context without `ctx.shared.corpus` is rejected with an explicit canonical-corpus message.
|
--- A context without `ctx.shared.corpus` is rejected with an explicit canonical-corpus message.
|
||||||
---
|
|
||||||
--- Writes `<ctx.out_root>/<dir_basename>.errors.h` once per module, with `#error` directives for findings that the C compile surfaces.
|
|
||||||
--- `passes/report.lua` renders annotations.txt from `corpus.sources_by_dir`, re-validating each source through `M.validate()`.
|
|
||||||
---
|
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex, Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- Bootstrap follows the entry scripts; `scripts/duffle_paths.lua` sets package.path and package.cpath. See `ps1_meta.lua` for the rationale.
|
-- Bootstrap follows the entry scripts; `scripts/duffle_paths.lua` sets package.path and package.cpath. See `ps1_meta.lua` for the rationale.
|
||||||
-- `debug.getinfo(1, "S").source` locates this file for standalone and orchestrated runs, then `duffle_paths.lua` returns the loaded `duffle` module.
|
-- `debug.getinfo(1, "S").source` locates this file for standalone and orchestrated runs, then `duffle_paths.lua` returns the loaded `duffle` module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||||
local write_file = duffle.write_file
|
|
||||||
local ensure_dir = duffle.ensure_dir
|
|
||||||
|
|
||||||
-- The annotation pass reads the source-derived registries from scan_source:
|
-- The annotation pass reads the source-derived registries from scan_source:
|
||||||
-- * pipe_ctx.register_alias_registry — for atom_dbg_reg_default(R_X, ...) and atom_reg_types(R_X, ...) member-identity checks
|
-- * pipe_ctx.register_alias_registry — for atom_dbg_reg_default(R_X, ...) and atom_reg_types(R_X, ...) member-identity checks
|
||||||
@@ -605,40 +598,6 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
-- Per-DIRECTORY (per-module) output: errors.h + annotations.txt
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
--- Render `<dir_basename>.errors.h` with `#error` directives for every error found across all sources in the directory.
|
|
||||||
--- Empty directories (no errors, no atoms) produce no file.
|
|
||||||
local function emit_module_errors_h(ctx, dir_basename, atoms_count, errors, sources)
|
|
||||||
if atoms_count == 0 and #errors == 0 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local out_path = ctx.out_root .. "/" .. dir_basename .. ".errors.h"
|
|
||||||
local lines = {
|
|
||||||
"// Auto-generated by ps1_meta.lua (passes/annotation.lua) — DO NOT EDIT",
|
|
||||||
string.format("// Module: %s Sources: %d", dir_basename, #sources),
|
|
||||||
"#pragma once",
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
if #errors == 0 then
|
|
||||||
lines[#lines + 1] = "// annotation pass OK"
|
|
||||||
else
|
|
||||||
for _, e in ipairs(errors) do
|
|
||||||
local src_tag = ""
|
|
||||||
if e.source then
|
|
||||||
local src_name = e.source:match("([^/\\]+)$") or e.source
|
|
||||||
src_tag = src_name .. ": "
|
|
||||||
end
|
|
||||||
lines[#lines + 1] = string.format('#error "%s%s (line %d)"', src_tag, e.msg, e.line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ensure_dir(ctx.out_root)
|
|
||||||
write_file(out_path, table.concat(lines, "\n") .. "\n")
|
|
||||||
return out_path
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- M.run — orchestrator entry
|
-- M.run — orchestrator entry
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -683,11 +642,6 @@ function M.run(ctx)
|
|||||||
warnings [#warnings + 1] = { line = w.line, msg = w.msg }
|
warnings [#warnings + 1] = { line = w.line, msg = w.msg }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local err_path = emit_module_errors_h(ctx, dir_basename, dir_atoms, dir_errors, dir_sources)
|
|
||||||
if err_path then
|
|
||||||
table.insert(outputs, { errors_h = err_path })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return { outputs = outputs, errors = errors, warnings = warnings }
|
return { outputs = outputs, errors = errors, warnings = warnings }
|
||||||
|
|||||||
@@ -4,20 +4,18 @@
|
|||||||
--- `passes/dwarf_injection.lua` (synthesizes DW_TAG_inlined_subroutine + per-word line program rows) and the gdb-runtime
|
--- `passes/dwarf_injection.lua` (synthesizes DW_TAG_inlined_subroutine + per-word line program rows) and the gdb-runtime
|
||||||
--- wrapper at `scripts/gdb/gdb_tape_atoms.gdb` (loads the source map via `source <path>`).
|
--- wrapper at `scripts/gdb/gdb_tape_atoms.gdb` (loads the source map via `source <path>`).
|
||||||
---
|
---
|
||||||
--- Inputs from `atom.paths`: the ordered `items` stream, dense `word_events`, `invocations` views. Outputs: one
|
--- Inputs from `atom.paths`: the ordered `items` stream, dense `word_events`, `invocations` views. Outputs:
|
||||||
--- `WORD N LINE L TEXT T` line per emitted `.word`, plus the per-word provenance form that DWARF synthesis consumes.
|
--- one `WORD N LINE L TEXT T` line per emitted `.word`, plus the per-word provenance form that DWARF synthesis consumes.
|
||||||
---
|
---
|
||||||
--- **Two output forms** (per the workspace's per-emission-form pattern from `guide_metaprogram_ssdl.md`):
|
--- Two output forms:
|
||||||
--- 1. **Sourcemap.txt form** — `<out_root>/<basename>.atoms.sourcemap.txt`. Format-version-tagged for forward-compat.
|
--- 1. Markdown form: Handled by `passes/report.lua` (writes `<module>.atoms.md`).
|
||||||
--- Lives in `<out_root>/` (build/gen). Mirrors the convention used by `annotation.lua`
|
--- The render functions `render_source_map` + `render_provenance` are exported for `report.lua` to call directly.
|
||||||
--- (`<out_root>/<basename>.errors.h`) and `static_analysis.lua` (`<out_root>/<basename>.static_analysis.txt`).
|
|
||||||
--- Compile artifacts (`*.macs.h`, `*.offsets.h`) stay in `<source_dir>/gen/`.
|
--- Compile artifacts (`*.macs.h`, `*.offsets.h`) stay in `<source_dir>/gen/`.
|
||||||
--- 2. **gdb-runtime form** — `<ctx.out_root>/gdb_tape_atoms_runtime.gdb`. A pure gdb command script — addresses come
|
--- 2. `gdb_tape_atoms_runtime.gdb`: Post-link opt-in (`ctx.flags.gdb_runtime`),
|
||||||
--- from `nm`, the 9 user commands are static `define ... end` blocks. Emitted when `ctx.flags.gdb_runtime` is true
|
--- so the gdb wrapper script + the generated runtime script share the same canonical location.
|
||||||
--- AND `ctx.flags.elf_path` points to an existing ELF. Useful for `gdb-multiarch --without-python` users
|
--- Triggered by `--post-link` or `--gdb-runtime`.
|
||||||
--- (the common case on Windows MinGW builds) — `source <path>` loads it with no Python / Tcl / Guile required.
|
|
||||||
---
|
---
|
||||||
--- **Output format** (sourcemap.txt form):
|
--- Output forma (sourcemap.txt form):
|
||||||
--- ```
|
--- ```
|
||||||
--- # FORMAT_VERSION 1
|
--- # FORMAT_VERSION 1
|
||||||
--- # auto-generated by ps1_meta.lua (passes/atoms_source_map.lua) — DO NOT EDIT
|
--- # auto-generated by ps1_meta.lua (passes/atoms_source_map.lua) — DO NOT EDIT
|
||||||
@@ -32,8 +30,6 @@
|
|||||||
--- ```
|
--- ```
|
||||||
---
|
---
|
||||||
--- Marker records are zero-width in `atom.paths.items`, so they emit no WORD rows in the dense word view.
|
--- Marker records are zero-width in `atom.paths.items`, so they emit no WORD rows in the dense word view.
|
||||||
---
|
|
||||||
--- **Conventions:** tabs (1/level), EmmyLua annotations, no regex, Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
@@ -458,7 +454,22 @@ local function emit_gdb_runtime(ctx)
|
|||||||
-- Confirmation line for the source operator.
|
-- Confirmation line for the source operator.
|
||||||
lines[#lines + 1] = 'printf "[gdb_tape_atoms] runtime loaded %d atoms from %s\\n", $__atom_count, $__elf_path'
|
lines[#lines + 1] = 'printf "[gdb_tape_atoms] runtime loaded %d atoms from %s\\n", $__atom_count, $__elf_path'
|
||||||
|
|
||||||
local out_path = ctx.out_root .. "/gdb_tape_atoms_runtime.gdb"
|
local out_path
|
||||||
|
-- Move out of `<out_root>/gdb_tape_atoms_runtime.gdb` to `<out_root>/../gdb_tape_atoms_runtime.gdb` when the conventional `<out_root>` is `<build>/gen`
|
||||||
|
-- (any equivalent spelling — relative, absolute backslash, absolute forward-slash, trailing-separator variants).
|
||||||
|
-- This puts the gdb runtime alongside the ELF at `build/` rather than under the report subdir.
|
||||||
|
local function ends_with_gen_dir(p)
|
||||||
|
if type(p) ~= "string" then return false end
|
||||||
|
return p:match("[/\\]gen[/\\]?$") ~= nil or p == "build/gen" or p == "build\\gen"
|
||||||
|
end
|
||||||
|
if ends_with_gen_dir(ctx.out_root) then
|
||||||
|
-- Strip the trailing `/gen` segment, then write the runtime script under `build/`.
|
||||||
|
-- e.g. "C:/projects/Pikuma/ps1/build/gen" -> "C:/projects/Pikuma/ps1/build".
|
||||||
|
local parent = ctx.out_root:gsub("[/\\]gen[/\\]?$", "")
|
||||||
|
out_path = parent .. "/gdb_tape_atoms_runtime.gdb"
|
||||||
|
else
|
||||||
|
out_path = ctx.out_root .. "/gdb_tape_atoms_runtime.gdb"
|
||||||
|
end
|
||||||
duffle.ensure_dir(duffle.dirname(out_path))
|
duffle.ensure_dir(duffle.dirname(out_path))
|
||||||
duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n")
|
duffle.write_file_lf(out_path, table.concat(lines, "\n") .. "\n")
|
||||||
-- io.stderr:write(string.format("[atoms_source_map] wrote %s (%d atoms)\n", out_path, #matched))
|
-- io.stderr:write(string.format("[atoms_source_map] wrote %s (%d atoms)\n", out_path, #matched))
|
||||||
@@ -470,10 +481,62 @@ end
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- Pass entry. For each source that declares at least one `MipsAtom_(name)` / `MipsCode code_<name>`, emit two files
|
-- Expose the pure render functions so `report.lua` and the focused tests can call them directly without triggering the file-emit path.
|
||||||
--- in `<out_root>/`: `<basename>.atoms.sourcemap.txt` (per-word call-site map) and `<basename>.atoms.provenance.txt`
|
M.render_source_map = render_source_map
|
||||||
--- (per-word definition + body line, resolved via the outermost `mac_X(...)` invocation). When `ctx.flags.gdb_runtime`
|
M.render_provenance = render_provenance
|
||||||
--- is true and `ctx.flags.elf_path` exists, also emit the post-link gdb script `<ctx.out_root>/gdb_tape_atoms_runtime.gdb`.
|
|
||||||
|
--- Render ONE atom's sourcemap stanza.
|
||||||
|
--- @param atom table -- atom record (must have `atom.paths` populated)
|
||||||
|
--- @return string
|
||||||
|
function M.render_atom_source_map(atom)
|
||||||
|
assert(type(atom) == "table", "render_atom_source_map: atom must be a table")
|
||||||
|
assert(type(atom.paths) == "table", "render_atom_source_map: atom.paths must be a table")
|
||||||
|
local entries, total = canonical_word_entries(atom)
|
||||||
|
local lines = {}
|
||||||
|
lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total)
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
lines[#lines + 1] = string.format("WORD %d LINE %d TEXT %s",
|
||||||
|
entry.pos, entry.line, entry.text)
|
||||||
|
end
|
||||||
|
lines[#lines + 1] = "ENDATOM"
|
||||||
|
return table.concat(lines, "\n") .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Render ONE atom's provenance stanza — no per-file format header, no enumeration of other atoms.
|
||||||
|
---
|
||||||
|
--- `rel_path` is the source path (forward-slashes) embedded in every `CALL` line.
|
||||||
|
--- The .md caller (report.lua) is expected to derive this once per `## <source>` heading and pass it down for each atom in that source.
|
||||||
|
--- @param atom table -- atom record (must have `atom.paths` populated)
|
||||||
|
--- @param wc table -- identity alias of `corpus.word_counts`
|
||||||
|
--- @param rel_path string -- source path (forward-slashes) for `CALL` fields
|
||||||
|
--- @return string
|
||||||
|
function M.render_atom_provenance(atom, wc, rel_path)
|
||||||
|
assert(type(atom) == "table", "render_atom_provenance: atom must be a table")
|
||||||
|
assert(type(atom.paths) == "table", "render_atom_provenance: atom.paths must be a table")
|
||||||
|
assert(type(rel_path) == "string", "render_atom_provenance: rel_path must be a string")
|
||||||
|
local entries, total = canonical_word_entries(atom)
|
||||||
|
local lines = {}
|
||||||
|
lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total)
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
local inv = entry.invocation
|
||||||
|
local macro_count = inv and wc and wc["mac_" .. inv.component_name]
|
||||||
|
if inv and macro_count ~= nil then
|
||||||
|
lines[#lines + 1] = string.format(
|
||||||
|
'WORD %d CALL %s:%d MACRO %s "%s:%d" BODY %d',
|
||||||
|
entry.pos, rel_path, entry.line, inv.component_name,
|
||||||
|
inv.def_path or "", inv.def_line or 0, entry.body_line)
|
||||||
|
else
|
||||||
|
lines[#lines + 1] = string.format(
|
||||||
|
"WORD %d CALL %s:%d RAW", entry.pos, rel_path, entry.line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n") .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Pass entry. For each source that declares at least one `MipsAtom_(name)` / `MipsCode code_<name>`,
|
||||||
|
--- emit two files in `<out_root>/`: `<basename>.atoms.sourcemap.txt` (per-word call-site map) and `<basename>.atoms.provenance.txt`
|
||||||
|
--- (per-word definition + body line, resolved via the outermost `mac_X(...)` invocation).
|
||||||
|
--- When `ctx.flags.gdb_runtime` is true and `ctx.flags.elf_path` exists, also emit the post-link gdb script `<ctx.out_root>/gdb_tape_atoms_runtime.gdb`.
|
||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @return PassResult
|
--- @return PassResult
|
||||||
function M.run(ctx)
|
function M.run(ctx)
|
||||||
@@ -495,34 +558,8 @@ function M.run(ctx)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Always emit the text form (per-source).
|
-- atoms.sourcemap.txt + atoms.provenance.txt content moved to report.lua via `<module>.atoms.md` markdown file.
|
||||||
for _, src in ipairs(corpus.source_order) do
|
-- This pass emits only the post-link gdb_runtime artifact (see emit_gdb_runtime below).
|
||||||
local has_projection = false
|
|
||||||
for _, atom in ipairs((src.scan or {}).atoms or {}) do
|
|
||||||
if (atom.kind == "atom" or atom.kind == "raw_atom") and atom.paths then
|
|
||||||
has_projection = true; break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not has_projection then
|
|
||||||
for _, atom in ipairs((src.scan or {}).raw_atoms or {}) do
|
|
||||||
if atom.paths then has_projection = true; break end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if has_projection then
|
|
||||||
local basename = duffle.basename_no_ext(src.path)
|
|
||||||
-- (1) atoms.sourcemap.txt — format-1 per-word call-site map.
|
|
||||||
local sourcemap_path = ctx.out_root .. "/" .. basename .. ".atoms.sourcemap.txt"
|
|
||||||
local sourcemap_body = render_source_map(src)
|
|
||||||
-- (2) atoms.provenance.txt — format-1 per-word definition/body map.
|
|
||||||
local prov_path = ctx.out_root .. "/" .. basename .. ".atoms.provenance.txt"
|
|
||||||
local prov_body = render_provenance(src, wc)
|
|
||||||
duffle.ensure_dir(duffle.dirname(sourcemap_path))
|
|
||||||
duffle.write_file_lf(sourcemap_path, sourcemap_body)
|
|
||||||
duffle.write_file_lf(prov_path, prov_body)
|
|
||||||
outputs[#outputs + 1] = { kind = "report", path = sourcemap_path }
|
|
||||||
outputs[#outputs + 1] = { kind = "report", path = prov_path }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Optionally emit the gdb-runtime form (post-link, one file per build).
|
-- Optionally emit the gdb-runtime form (post-link, one file per build).
|
||||||
if ctx.flags and ctx.flags.gdb_runtime then
|
if ctx.flags and ctx.flags.gdb_runtime then
|
||||||
|
|||||||
@@ -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_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
|
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).
|
-- 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.
|
-- 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).
|
-- 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.
|
-- 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.
|
-- 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 = {
|
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,
|
["lottes_tape.h"] = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+376
-253
@@ -7,9 +7,6 @@
|
|||||||
---
|
---
|
||||||
--- The annotation pass emits `errors.h` files per module and the canonical `corpus.sources_by_dir` projection groups sources by directory.
|
--- The annotation pass emits `errors.h` files per module and the canonical `corpus.sources_by_dir` projection groups sources by directory.
|
||||||
--- This pass iterates the canonical dir projection directly and re-validates each source via `annotation.validate()` to get the detailed per-source results.
|
--- This pass iterates the canonical dir projection directly and re-validates each source via `annotation.validate()` to get the detailed per-source results.
|
||||||
---
|
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
|
||||||
--- Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
@@ -18,8 +15,7 @@
|
|||||||
-- Resolve `arg[0]` to an absolute-ish script directory so that `require("duffle")` resolves against `scripts/` regardless of CWD.
|
-- Resolve `arg[0]` to an absolute-ish script directory so that `require("duffle")` resolves against `scripts/` regardless of CWD.
|
||||||
-- Bootstrap: see `ps1_meta.lua` for the rationale.
|
-- Bootstrap: see `ps1_meta.lua` for the rationale.
|
||||||
-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath).
|
-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath).
|
||||||
-- Uses `debug.getinfo` to find this file's own directory, so it works
|
-- Uses `debug.getinfo` to find this file's own directory, so it works both standalone and when require'd from the orchestrator.
|
||||||
-- both standalone and when require'd from the orchestrator.
|
|
||||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||||
@@ -30,6 +26,11 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
|||||||
-- that the report pass renders into the per-module `<dir_basename>.annotations.txt` output.
|
-- 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.
|
||||||
|
-- Defined BEFORE the renderer functions below so their upvalues resolve to this local (not the global `atoms_source_map`, which is nil).
|
||||||
|
local atoms_source_map = dofile(_bootstrap_dir .. "atoms_source_map.lua")
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Constants
|
-- Constants
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -44,8 +45,7 @@ local SECTION_HEADER_MACROS = "── Macro word-count declarations ───
|
|||||||
local SECTION_HEADER_ERRORS = "── Errors ──────────────────────────────────────────────"
|
local SECTION_HEADER_ERRORS = "── Errors ──────────────────────────────────────────────"
|
||||||
local SECTION_HEADER_WARNINGS = "── Warnings ────────────────────────────────────────────"
|
local SECTION_HEADER_WARNINGS = "── Warnings ────────────────────────────────────────────"
|
||||||
|
|
||||||
-- Lua pattern that captures the basename (last path segment) of a
|
-- Lua pattern that captures the basename (last path segment) of a forward- or back-slash separated path.
|
||||||
-- forward- or back-slash separated path.
|
|
||||||
local BASENAME_PATTERN = "([^/\\]+)$"
|
local BASENAME_PATTERN = "([^/\\]+)$"
|
||||||
|
|
||||||
-- Debug flag name — set to truthy in `_G` to enable verbose logging.
|
-- Debug flag name — set to truthy in `_G` to enable verbose logging.
|
||||||
@@ -148,328 +148,451 @@ local function source_basename(path)
|
|||||||
return path:match(BASENAME_PATTERN) or path
|
return path:match(BASENAME_PATTERN) or path
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Format a single annotation entry as one rendered line.
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
--- @param a AnnotEntry
|
-- Markdown renderers (consolidated-report-files refactor, 2026-07-26)
|
||||||
--- @param src_name string
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
--- Render the thin project-wide summary (`build/atom_meta_report.summary.md`).
|
||||||
|
--- @param all_results { module:string, atoms:integer, annots:integer, binds:integer,
|
||||||
|
--- macros:integer, findings:integer, errors:integer,
|
||||||
|
--- warnings:integer, info:integer }[]
|
||||||
--- @return string
|
--- @return string
|
||||||
local function format_annot_line(a, src_name)
|
local function render_project_summary(all_results)
|
||||||
if a.error then
|
local lines = {
|
||||||
return string.format(" ✗ line %d %s [ERROR: %s] [%s]", a.line, a.macro or "?", a.error, src_name)
|
"# Project summary",
|
||||||
|
"> Auto-generated by ps1_meta.lua (passes/report.lua).",
|
||||||
|
"",
|
||||||
|
"| module | atoms | annots | binds | macros | findings | errors | warnings | info |",
|
||||||
|
"|--------|-------|--------|-------|--------|----------|--------|----------|------|",
|
||||||
|
}
|
||||||
|
local totals = { atoms = 0, annots = 0, binds = 0, macros = 0,
|
||||||
|
findings = 0, errors = 0, warnings = 0, info = 0 }
|
||||||
|
for _, e in ipairs(all_results) do
|
||||||
|
lines[#lines + 1] = string.format(
|
||||||
|
"| %s | %d | %d | %d | %d | %d | %d | %d | %d |",
|
||||||
|
e.module, e.atoms, e.annots, e.binds, e.macros,
|
||||||
|
e.findings, e.errors, e.warnings, e.info)
|
||||||
|
totals.atoms = totals.atoms + e.atoms
|
||||||
|
totals.annots = totals.annots + e.annots
|
||||||
|
totals.binds = totals.binds + e.binds
|
||||||
|
totals.macros = totals.macros + e.macros
|
||||||
|
totals.findings = totals.findings + e.findings
|
||||||
|
totals.errors = totals.errors + e.errors
|
||||||
|
totals.warnings = totals.warnings + e.warnings
|
||||||
|
totals.info = totals.info + e.info
|
||||||
end
|
end
|
||||||
local line = string.format(" ● line %d %s [%s]", a.line, a.name, src_name)
|
lines[#lines + 1] = string.format(
|
||||||
if a.binds then line = line .. " binds=" .. a.binds end
|
"| **TOTAL** | %d | %d | %d | %d | %d | %d | %d | %d |",
|
||||||
if #a.reads > 0 then line = line .. " reads={" .. table.concat(a.reads, ",") .. "}" end
|
totals.atoms, totals.annots, totals.binds, totals.macros,
|
||||||
if #a.writes > 0 then line = line .. " writes={" .. table.concat(a.writes, ",") .. "}" end
|
totals.findings, totals.errors, totals.warnings, totals.info)
|
||||||
return line
|
return table.concat(lines, "\n") .. "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
--- (internal) Tally totals across all results in a module.
|
--- Render the per-module verbose source-map markdown (`build/<module>.atoms.md`).
|
||||||
--- @param results AnnotationResult[]
|
--- Per-source sub-section, per-atom stanza with sourcemap + provenance rows.
|
||||||
--- @return integer, integer, integer, integer, integer, integer
|
--- Pulls sourcemap + provenance from `atoms_source_map` (no second source walk).
|
||||||
local function tally_module_totals(results)
|
--- @param dir string
|
||||||
local total_atoms, total_annots, total_binds, total_macros = 0, 0, 0, 0
|
--- @param dir_sources SourceFile[]
|
||||||
local total_errors, total_warnings = 0, 0
|
--- @param wc table<string, integer>
|
||||||
for _, r in ipairs(results) do
|
--- @return string
|
||||||
total_atoms = total_atoms + #r.atoms
|
local function render_module_atoms_md(dir, dir_sources, wc)
|
||||||
total_annots = total_annots + #r.annots
|
local dir_basename = source_basename(dir)
|
||||||
total_binds = total_binds + #r.binds
|
local lines = {
|
||||||
total_macros = total_macros + #r.macros
|
"# " .. dir_basename .. " — atoms (verbose source map)",
|
||||||
total_errors = total_errors + #r.errors
|
"> Per-word call-site + provenance. Auto-generated.",
|
||||||
total_warnings = total_warnings + #r.warnings
|
"",
|
||||||
|
}
|
||||||
|
for _, src in ipairs(dir_sources) do
|
||||||
|
local src_name = source_basename(src.path)
|
||||||
|
lines[#lines + 1] = "## " .. src_name
|
||||||
|
lines[#lines + 1] = ""
|
||||||
|
-- For each atom with a projection, render its sourcemap + provenance.
|
||||||
|
local atoms_list = {}
|
||||||
|
for _, atom in ipairs((src.scan or {}).atoms or {}) do
|
||||||
|
if atom.paths then atoms_list[#atoms_list + 1] = atom end
|
||||||
end
|
end
|
||||||
return total_atoms, total_annots, total_binds, total_macros, total_errors, total_warnings
|
for _, atom in ipairs((src.scan or {}).raw_atoms or {}) do
|
||||||
|
if atom.paths then atoms_list[#atoms_list + 1] = atom end
|
||||||
|
end
|
||||||
|
if #atoms_list == 0 then
|
||||||
|
lines[#lines + 1] = "_(no atom projections)_"
|
||||||
|
lines[#lines + 1] = ""
|
||||||
|
else
|
||||||
|
-- Per-source forward-slash path (same one `emit_atom_stanza` / `emit_provenance_stanza` would derive;
|
||||||
|
-- computed once per `## <source>` heading and reused by each atom's `WORD N CALL ...` field).
|
||||||
|
local rel_path = src.path:gsub("\\\\", "/")
|
||||||
|
for _, atom in ipairs(atoms_list) do
|
||||||
|
lines[#lines + 1] = string.format(
|
||||||
|
"### atom: %s (line %d, %d words)",
|
||||||
|
atom.name, atom.line or 0, #(atom.paths.items or {}))
|
||||||
|
lines[#lines + 1] = ""
|
||||||
|
lines[#lines + 1] = "**Sourcemap** — per-word call site:"
|
||||||
|
lines[#lines + 1] = "```"
|
||||||
|
-- Per-atom invariant: call the per-atom renderers, NOT the per-source ones.
|
||||||
|
-- The per-source renderers enumerate every atom in `src`;
|
||||||
|
-- calling them in a per-atom loop would repeat the whole source under every `### atom:` heading.
|
||||||
|
lines[#lines + 1] = atoms_source_map.render_atom_source_map(atom):gsub("\n+$", "")
|
||||||
|
lines[#lines + 1] = "```"
|
||||||
|
lines[#lines + 1] = ""
|
||||||
|
lines[#lines + 1] = "**Provenance** — per-word definition + body:"
|
||||||
|
lines[#lines + 1] = "```"
|
||||||
|
lines[#lines + 1] = atoms_source_map.render_atom_provenance(atom, wc, rel_path):gsub("\n+$", "")
|
||||||
|
lines[#lines + 1] = "```"
|
||||||
|
lines[#lines + 1] = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(lines, "\n") .. "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source atom declarations.
|
--- Render the consolidated per-module markdown (`build/<module>.atom_meta_report.md`).
|
||||||
local function render_module_atoms_section(add, results)
|
--- Aggregates annotation + static-analysis content across all sources in `dir`.
|
||||||
add(SECTION_HEADER_ATOMS)
|
--- Annotations come from re-running `annotation.validate()` per source (the existing pattern);
|
||||||
for _, r in ipairs(results) do
|
--- static-analysis comes from `corpus.static_analysis_results[dir_basename]` (populated by `static_analysis.lua` — no second corpus_pipe_ctx build).
|
||||||
|
--- @param dir string
|
||||||
|
--- @param dir_sources SourceFile[]
|
||||||
|
--- @param annot_results AnnotationResult[]
|
||||||
|
--- @param sa_results table -- corpus.static_analysis_results[dir_basename]
|
||||||
|
--- @return string
|
||||||
|
local function render_module_meta_report(dir, dir_sources, annot_results, sa_results)
|
||||||
|
local dir_basename = source_basename(dir)
|
||||||
|
local lines = {
|
||||||
|
"# " .. dir_basename .. " — atom meta report",
|
||||||
|
"> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
local function add(s) lines[#lines + 1] = s end
|
||||||
|
|
||||||
|
-- Module summary table.
|
||||||
|
local n_atoms = 0
|
||||||
|
local n_annot = 0
|
||||||
|
local n_binds = 0
|
||||||
|
local n_macros = 0
|
||||||
|
local n_bare, n_proc = 0, 0
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
|
n_atoms = n_atoms + #r.atoms
|
||||||
|
n_annot = n_annot + #r.annots
|
||||||
|
n_binds = n_binds + #r.binds
|
||||||
|
n_macros = n_macros + #r.macros
|
||||||
|
end
|
||||||
|
for _, a in ipairs(sa_results.atoms or {}) do
|
||||||
|
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
||||||
|
elseif a.kind == "comp_proc" then n_proc = n_proc + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
add("## Module summary"); add("")
|
||||||
|
add("| metric | value |"); add("|--------|-------|")
|
||||||
|
add(string.format("| sources | %d |", #dir_sources))
|
||||||
|
add(string.format("| atoms | %d (atoms: %d, comp_bare: %d, comp_proc: %d) |",
|
||||||
|
#(sa_results.atoms or {}),
|
||||||
|
#(sa_results.atoms or {}) - n_bare - n_proc, n_bare, n_proc))
|
||||||
|
add(string.format("| annotations | %d |", n_annot))
|
||||||
|
add(string.format("| binds structs | %d |", n_binds))
|
||||||
|
add(string.format("| macro decls | %d |", n_macros))
|
||||||
|
add(string.format("| findings | %d (errors: %d, warnings: %d, info: %d) |",
|
||||||
|
#(sa_results.findings or {}),
|
||||||
|
#(sa_results.errors or {}),
|
||||||
|
#(sa_results.warnings or {}),
|
||||||
|
#(sa_results.info or {})))
|
||||||
|
add("")
|
||||||
|
|
||||||
|
-- Sources
|
||||||
|
add("## Sources"); add("")
|
||||||
|
for _, s in ipairs(dir_sources) do add("- `" .. s.path .. "`") end
|
||||||
|
add("")
|
||||||
|
|
||||||
|
-- Atoms (annotation)
|
||||||
|
add("## Atoms"); add("")
|
||||||
|
add("| kind | name | source | line |"); add("|------|------|--------|------|")
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
local src_name = source_basename(r.source)
|
local src_name = source_basename(r.source)
|
||||||
for _, a in ipairs(r.atoms) do
|
for _, a in ipairs(r.atoms) do
|
||||||
add(string.format(" MipsAtom_(%s) line %d [%s]", a.name, a.line, src_name))
|
add(string.format("| atom | %s | %s | %d |", a.name, src_name, a.line))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source annotation entries.
|
-- Annotations
|
||||||
local function render_module_annots_section(add, results)
|
add("## Annotations"); add("")
|
||||||
add(SECTION_HEADER_ANNOTS)
|
if #annot_results == 0 then
|
||||||
for _, r in ipairs(results) do
|
add("_(none)_")
|
||||||
|
else
|
||||||
|
add("| source | line | name | binds | reads | writes |")
|
||||||
|
add("|--------|------|------|-------|-------|--------|")
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
local src_name = source_basename(r.source)
|
local src_name = source_basename(r.source)
|
||||||
for _, a in ipairs(r.annots) do
|
for _, a in ipairs(r.annots) do
|
||||||
add(format_annot_line(a, src_name))
|
local binds = a.binds or "—"
|
||||||
|
local reads = (#a.reads > 0 and table.concat(a.reads, ",")) or "—"
|
||||||
|
local writes = (#a.writes > 0 and table.concat(a.writes, ",")) or "—"
|
||||||
|
add(string.format("| %s | %d | %s | %s | %s | %s |",
|
||||||
|
src_name, a.line, a.name, binds, reads, writes))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source Binds_* struct declarations.
|
-- Binds_* structs
|
||||||
local function render_module_binds_section(add, results)
|
add("## Binds_* structs"); add("")
|
||||||
add(SECTION_HEADER_BINDS)
|
if #annot_results == 0 then
|
||||||
for _, r in ipairs(results) do
|
add("_(none)_")
|
||||||
|
else
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
local src_name = source_basename(r.source)
|
local src_name = source_basename(r.source)
|
||||||
for _, b in ipairs(r.binds) do
|
for _, b in ipairs(r.binds) do
|
||||||
add(string.format(" %s line %d %d bytes [%s]", b.name, b.line, b.bytes, src_name))
|
add(string.format("### %s (%s:%d, %d bytes)",
|
||||||
|
b.name, src_name, b.line, b.bytes))
|
||||||
for _, f in ipairs(b.fields) do
|
for _, f in ipairs(b.fields) do
|
||||||
add(string.format(" +%2d: %s", f.offset, f.name))
|
add(string.format("- `+%d %s`", f.offset, f.name))
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source macro word-count declarations.
|
-- Macro decls
|
||||||
local function render_module_macros_section(add, results)
|
add("## Macro word-count declarations"); add("")
|
||||||
add(SECTION_HEADER_MACROS)
|
if #annot_results == 0 then
|
||||||
for _, r in ipairs(results) do
|
add("_(none)_")
|
||||||
|
else
|
||||||
|
add("| source | line | macro declaration |")
|
||||||
|
add("|--------|------|-------------------|")
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
local src_name = source_basename(r.source)
|
local src_name = source_basename(r.source)
|
||||||
for _, m in ipairs(r.macros) do
|
for _, m in ipairs(r.macros) do
|
||||||
add(string.format(" %s line %d words=%d [%s]", m.name, m.line, m.words, src_name))
|
add(string.format("| %s | %d | %s |",
|
||||||
|
src_name, m.line, m.name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source errors (one-line + "(none)" if empty).
|
-- Findings by atom (static-analysis)
|
||||||
local function render_module_errors_section(add, results, total_errors)
|
add("## Static analysis — findings by atom"); add("")
|
||||||
add(SECTION_HEADER_ERRORS)
|
local by_atom = {}
|
||||||
if total_errors == 0 then
|
for _, f in ipairs(sa_results.findings or {}) do
|
||||||
add(" (none)")
|
by_atom[f.atom] = by_atom[f.atom] or {}
|
||||||
|
by_atom[f.atom][#by_atom[f.atom] + 1] = f
|
||||||
|
end
|
||||||
|
if next(by_atom) == nil then
|
||||||
|
add("_(no findings)_")
|
||||||
else
|
else
|
||||||
for _, r in ipairs(results) do
|
for _, a in ipairs(sa_results.atoms or {}) do
|
||||||
local src_name = source_basename(r.source)
|
local fs = by_atom[a.name]
|
||||||
for _, e in ipairs(r.errors) do
|
if fs then
|
||||||
add(string.format(" ✗ line %d %s [%s]", e.line, e.msg, src_name))
|
add(string.format("### %s", a.name))
|
||||||
end
|
for _, f in ipairs(fs) do
|
||||||
end
|
add(string.format("- `[%s] %s`", f.check, f.msg))
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- (internal) Section renderer: per-source warnings (one-line + "(none)" if empty).
|
-- Errors / Warnings / Info
|
||||||
local function render_module_warnings_section(add, results, total_warnings)
|
local function add_findings(label, entries)
|
||||||
add(SECTION_HEADER_WARNINGS)
|
add(string.format("## %s", label))
|
||||||
if total_warnings == 0 then
|
if #entries == 0 then
|
||||||
add(" (none)")
|
add("_(none)_")
|
||||||
else
|
else
|
||||||
for _, r in ipairs(results) do
|
for _, e in ipairs(entries) do
|
||||||
local src_name = source_basename(r.source)
|
add(string.format("- line %d %s", e.line, e.msg))
|
||||||
for _, w in ipairs(r.warnings) do
|
|
||||||
add(string.format(" ⚠ line %d %s [%s]", w.line, w.msg, src_name))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
end
|
end
|
||||||
|
add_findings("Errors", sa_results.errors or {})
|
||||||
|
add_findings("Warnings", sa_results.warnings or {})
|
||||||
|
add_findings("Info", sa_results.info or {})
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- Per-atom cycle counts (path-aware)
|
||||||
-- SECTION_RENDERERS — data-driven section dispatch (the plex pattern)
|
add("## Per-atom cycle counts (path-aware, best case, no stalls)"); add("")
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
add("| atom | source | min | max | branches | paths | notes |")
|
||||||
--
|
add("|------|--------|-----|-----|----------|-------|-------|")
|
||||||
-- Each entry maps a section to its (header, render_fn). The render_fn signature:
|
local sorted = {}
|
||||||
-- render_fn(add, results, totals)
|
for _, a in ipairs(sa_results.atoms or {}) do sorted[#sorted + 1] = a end
|
||||||
-- add -- the `add(line)` closure from the surrounding report renderer
|
table.sort(sorted, function(x, y)
|
||||||
-- results -- AnnotationResult[] (per-source results)
|
return ((x.paths or {}).cycles_max or 0) > ((y.paths or {}).cycles_max or 0)
|
||||||
-- totals -- {atoms, annots, binds, macros, errors, warnings} counts
|
end)
|
||||||
--
|
for _, a in ipairs(sorted) do
|
||||||
-- Sections that need to render "(none)" vs iterate use totals.errors / totals.warnings;
|
local p = a.paths or {}
|
||||||
-- other sections ignore the totals arg.
|
local src_name = a.source_path and source_basename(a.source_path) or ""
|
||||||
-- Adding a new section = 1 row here + 1 render_<thing>_section function.
|
local notes = ""
|
||||||
local SECTION_RENDERERS = {
|
if p.has_loops then notes = notes .. " [loop!]" end
|
||||||
{ header = SECTION_HEADER_ATOMS, render = render_module_atoms_section },
|
if p.unknown_macros and #p.unknown_macros > 0 then
|
||||||
{ header = SECTION_HEADER_ANNOTS, render = render_module_annots_section },
|
notes = notes .. " [unknown: " .. table.concat(p.unknown_macros, ", ") .. "]"
|
||||||
{ header = SECTION_HEADER_BINDS, render = render_module_binds_section },
|
|
||||||
{ header = SECTION_HEADER_MACROS, render = render_module_macros_section },
|
|
||||||
{ header = SECTION_HEADER_ERRORS, render = function(add, results, totals) return render_module_errors_section(add, results, totals.errors) end },
|
|
||||||
{ header = SECTION_HEADER_WARNINGS, render = function(add, results, totals) return render_module_warnings_section(add, results, totals.warnings) end },
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Render the per-MODULE annotation report (one `<dir_basename>.annotations.txt`).
|
|
||||||
--- @param dir string -- module directory path
|
|
||||||
--- @param sources SourceFile[] -- sources in this module
|
|
||||||
--- @param results AnnotationResult[] -- per-source validate() results
|
|
||||||
--- @return string -- the rendered report text
|
|
||||||
local function render_module_report(dir, sources, results)
|
|
||||||
local lines = {}
|
|
||||||
local function add(s) lines[#lines + 1] = s end
|
|
||||||
|
|
||||||
add(RULE_THICK)
|
|
||||||
add("ANNOTATION PASS — module " .. source_basename(dir))
|
|
||||||
add(RULE_THICK)
|
|
||||||
add(string.format("Sources: %d", #sources))
|
|
||||||
for _, s in ipairs(sources) do add(" " .. s.path) end
|
|
||||||
add("")
|
|
||||||
|
|
||||||
local total_atoms, total_annots, total_binds, total_macros, total_errors, total_warnings = tally_module_totals(results)
|
|
||||||
add(string.format("Atoms: %d Annotations: %d Binds structs: %d Macro decls: %d",
|
|
||||||
total_atoms, total_annots, total_binds, total_macros))
|
|
||||||
add("")
|
|
||||||
|
|
||||||
-- Bundle the totals so the section renderers don't need separate parameter lists.
|
|
||||||
-- Errors/warnings sections need their total count to decide "(none)" vs iterate.
|
|
||||||
-- Sections without totals (atoms/annots/binds/macros) ignore this arg.
|
|
||||||
local totals = {
|
|
||||||
atoms = total_atoms, annots = total_annots, binds = total_binds,
|
|
||||||
macros = total_macros, errors = total_errors, warnings = total_warnings,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- THE per-section dispatch. ONE loop over SECTION_RENDERERS.
|
|
||||||
-- Each renderer writes its header + content via the `add` closure (pre-bound above).
|
|
||||||
-- Adding a new section = 1 row here + 1 render_<thing>_section function.
|
|
||||||
for _, section in ipairs(SECTION_RENDERERS) do
|
|
||||||
section.render(add, results, totals)
|
|
||||||
end
|
end
|
||||||
|
add(string.format("| %s | %s | %d | %d | %d | %d | %s |",
|
||||||
|
a.name, src_name,
|
||||||
|
p.cycles_min or 0, p.cycles_max or 0,
|
||||||
|
p.branches or 0, p.paths or 0, notes))
|
||||||
|
end
|
||||||
|
add("")
|
||||||
|
|
||||||
|
-- Per-source scan summary
|
||||||
|
add("## Per-source scan summary"); add("")
|
||||||
|
for _, src in ipairs(dir_sources) do
|
||||||
|
local src_atoms = {}
|
||||||
|
for _, a in ipairs(sa_results.atoms or {}) do
|
||||||
|
if a.source_path == src.path then src_atoms[#src_atoms + 1] = a end
|
||||||
|
end
|
||||||
|
if #src_atoms > 0 then
|
||||||
|
local mn, mx = math.huge, -1
|
||||||
|
for _, a in ipairs(src_atoms) do
|
||||||
|
local p = a.paths or {}
|
||||||
|
if (p.cycles_min or 0) < mn then mn = p.cycles_min or 0 end
|
||||||
|
if (p.cycles_max or 0) > mx then mx = p.cycles_max or 0 end
|
||||||
|
end
|
||||||
|
local path_str
|
||||||
|
if mx > 0 then
|
||||||
|
path_str = string.format(" cycles=%d..%d", mn, mx)
|
||||||
|
else
|
||||||
|
path_str = string.format(" %d cycles", mn)
|
||||||
|
end
|
||||||
|
add(string.format("- `%s` — %d atom%s%s",
|
||||||
|
src.basename, #src_atoms,
|
||||||
|
#src_atoms == 1 and "" or "s", path_str))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
add("")
|
||||||
|
|
||||||
return table.concat(lines, "\n") .. "\n"
|
return table.concat(lines, "\n") .. "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Per-project summary
|
-- REPORT_RENDERERS — data-driven report dispatch (one row per file kind)
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
-- `once = true` means render once at the project level (not per-module).
|
||||||
--- Render the per-project summary (`build/gen/annotation_validation.txt`).
|
-- `basename(dir_basename)` yields the file's basename for that kind.
|
||||||
--- Aggregates totals across all sources; lists per-source error counts if any source has errors.
|
-- `gather(ctx, dir, dir_sources [, all_modules])` returns the rendered string.
|
||||||
--- @param all_results AnnotationResult[]
|
local REPORT_RENDERERS = {
|
||||||
--- @return string
|
{
|
||||||
local function render_project_report(all_results)
|
name = "atom_meta_report",
|
||||||
local lines = {}
|
ext = "md",
|
||||||
local function add(s) lines[#lines + 1] = s end
|
basename = function(dir_basename) return dir_basename .. ".atom_meta_report" end,
|
||||||
|
once = false,
|
||||||
local total_atoms, total_annots, total_macros, total_binds = 0, 0, 0, 0
|
gather = function(ctx, dir, dir_sources)
|
||||||
local total_errors, total_warnings = 0, 0
|
-- Annotations: re-run `annotation.validate()` per source (the existing pattern).
|
||||||
for _, r in ipairs(all_results) do
|
local annot_results = {}
|
||||||
total_atoms = total_atoms + #r.atoms
|
|
||||||
total_annots = total_annots + #r.annots
|
|
||||||
total_macros = total_macros + #r.macros
|
|
||||||
total_binds = total_binds + #r.binds
|
|
||||||
total_errors = total_errors + #r.errors
|
|
||||||
total_warnings = total_warnings + #r.warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
add(RULE_THICK)
|
|
||||||
add("ANNOTATION VALIDATION — project summary")
|
|
||||||
add(RULE_THICK)
|
|
||||||
add("")
|
|
||||||
add(string.format("Atoms: %d", total_atoms))
|
|
||||||
add(string.format("Annotations: %d", total_annots))
|
|
||||||
add(string.format("Macros: %d", total_macros))
|
|
||||||
add(string.format("Binds: %d", total_binds))
|
|
||||||
add("")
|
|
||||||
add(string.format("Errors: %d", total_errors))
|
|
||||||
add(string.format("Warnings: %d", total_warnings))
|
|
||||||
add("")
|
|
||||||
|
|
||||||
if total_errors > 0 then
|
|
||||||
add("Per-source error counts:")
|
|
||||||
for _, r in ipairs(all_results) do
|
|
||||||
if #r.errors > 0 then
|
|
||||||
local src_name = source_basename(r.source)
|
|
||||||
add(string.format(" %s : %d error(s)", src_name, #r.errors))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
add("")
|
|
||||||
end
|
|
||||||
|
|
||||||
return table.concat(lines, "\n") .. "\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
-- Orchestration helpers
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
--- (internal) Re-validate every source in a directory against the canonical corpus projection.
|
|
||||||
--- Calls `annotation.validate()` per source to produce the per-source AnnotationResult (atoms / annots / macros / binds / errors / warnings)
|
|
||||||
--- that the report renderer consumes. Eeach report pass run is reproducible from the corpus.
|
|
||||||
--- Returns the list of module results + the flat list of all results (for the project-wide summary).
|
|
||||||
--- @param ctx PassCtx
|
|
||||||
--- @param dir_sources SourceFile[]
|
|
||||||
--- @return AnnotationResult[], AnnotationResult[]
|
|
||||||
local function lookup_module_results(ctx, dir_sources)
|
|
||||||
local module_results = {}
|
|
||||||
local all_results = {}
|
|
||||||
for _, src in ipairs(dir_sources) do
|
for _, src in ipairs(dir_sources) do
|
||||||
if src.scan then
|
if src.scan then
|
||||||
local result = annotation.validate(ctx, src, nil)
|
local r = annotation.validate(ctx, src, nil)
|
||||||
result.source = src.path -- tag for downstream rendering
|
r.source = src.path
|
||||||
module_results[#module_results + 1] = result
|
annot_results[#annot_results + 1] = r
|
||||||
all_results[#all_results + 1] = result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return module_results, all_results
|
|
||||||
end
|
|
||||||
|
|
||||||
--- (internal) Does this module's results contain anything worth emitting?
|
|
||||||
--- @param module_results AnnotationResult[]
|
|
||||||
--- @return boolean
|
|
||||||
local function module_has_content(module_results)
|
|
||||||
for _, r in ipairs(module_results) do
|
|
||||||
if #r.atoms > 0 or #r.annots > 0 or #r.binds > 0
|
|
||||||
or #r.macros > 0 or #r.errors > 0 or #r.warnings > 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--- (internal) Log a debug message if `_G[DEBUG_FLAG]` is truthy.
|
|
||||||
--- @param fmt string
|
|
||||||
local function debug_log(fmt, ...)
|
|
||||||
if _G[DEBUG_FLAG] then
|
|
||||||
io.stderr:write(string.format("[%s] " .. fmt, PASS_NAME, ...))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Static-analysis: read stashed projection (no re-validate).
|
||||||
|
local dir_basename = dir:match("([^/\\]+)$") or dir
|
||||||
|
local sa_results = (ctx.shared.corpus.static_analysis_results or {})[dir_basename] or {}
|
||||||
|
return render_module_meta_report(dir, dir_sources, annot_results, sa_results)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "atoms",
|
||||||
|
ext = "md",
|
||||||
|
basename = function(dir_basename) return dir_basename .. ".atoms" end,
|
||||||
|
once = false,
|
||||||
|
gather = function(ctx, dir, dir_sources)
|
||||||
|
return render_module_atoms_md(dir, dir_sources,
|
||||||
|
ctx.shared.corpus.word_counts or {})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "summary",
|
||||||
|
ext = "md",
|
||||||
|
basename = function(_dir_basename) return "atom_meta_report.summary" end,
|
||||||
|
once = true,
|
||||||
|
gather = function(_ctx, _dir, _dir_sources, all_modules)
|
||||||
|
return render_project_summary(all_modules)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- M — module exports
|
-- M — public pass surface
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- Run the report pass.
|
--- Run the report pass. Emits 1 `atom_meta_report.summary.md` per build + 2 `atom_meta_report.md` + 2 `atoms.md` files per module (duffle + gte_hello).
|
||||||
--- Renders one `<dir_basename>.annotations.txt` per source-directory that has content, plus the project-wide `annotation_validation.txt` summary.
|
--- Reads `corpus.static_analysis_results` (added in Phase 1) to populate per-module findings without re-running validate().
|
||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @return PassResult
|
--- @return PassResult
|
||||||
function M.run(ctx)
|
function M.run(ctx)
|
||||||
local outputs = {}
|
local outputs = {}
|
||||||
local errors = {}
|
|
||||||
local warnings = {}
|
|
||||||
|
|
||||||
-- Module grouping comes from `corpus.sources_by_dir` (the canonical projection).
|
|
||||||
-- Iterate it directly; no private cache, no per-pass stash.
|
|
||||||
local corpus = ctx.shared and ctx.shared.corpus
|
local corpus = ctx.shared and ctx.shared.corpus
|
||||||
local by_dir = (corpus and corpus.sources_by_dir) or {}
|
local by_dir = (corpus and corpus.sources_by_dir) or {}
|
||||||
|
|
||||||
duffle.ensure_dir(ctx.out_root)
|
-- `out_path_root`: when the conventional `out_root` is `build/gen` (any spelling — relative, absolute, separator variants).
|
||||||
|
-- Write the md files to `build/` (parent of `gen/`) instead of nested under `gen/`.
|
||||||
|
-- Mirrors the `gdb_tape_atoms_runtime.gdb` relocation.
|
||||||
|
local function ends_with_gen(p)
|
||||||
|
return type(p) == "string" and (p:match("[/\\]gen[/\\]?$") ~= nil
|
||||||
|
or p == "build/gen" or p == "build\\gen")
|
||||||
|
end
|
||||||
|
local out_root_effective = ends_with_gen(ctx.out_root)
|
||||||
|
and ctx.out_root:gsub("[/\\]gen[/\\]?$", "")
|
||||||
|
or ctx.out_root
|
||||||
|
|
||||||
|
duffle.ensure_dir(out_root_effective)
|
||||||
|
|
||||||
|
-- Aggregator for the project-wide `once = true` summary renderer.
|
||||||
|
local all_modules = {}
|
||||||
|
|
||||||
local all_results_for_summary = {}
|
|
||||||
for dir, dir_sources in pairs(by_dir) do
|
for dir, dir_sources in pairs(by_dir) do
|
||||||
local dir_basename = dir:match("([^/\\]+)$") or dir
|
local dir_basename = dir:match("([^/\\]+)$") or dir
|
||||||
debug_log("dir=%s basename=%s sources=%d\n", dir, dir_basename, #dir_sources)
|
|
||||||
|
|
||||||
if #dir_sources > 0 then
|
-- Per-renderer dispatch for the per-module renderers (once = false).
|
||||||
local module_results, all_results = lookup_module_results(ctx, dir_sources)
|
for _, renderer in ipairs(REPORT_RENDERERS) do
|
||||||
for _, r in ipairs(all_results) do
|
if not renderer.once then
|
||||||
all_results_for_summary[#all_results_for_summary + 1] = r
|
local body = renderer.gather(ctx, dir, dir_sources)
|
||||||
end
|
local out_path = out_root_effective .. "/" .. renderer.basename(dir_basename) .. "." .. renderer.ext
|
||||||
|
duffle.write_file(out_path, body)
|
||||||
if module_has_content(module_results) then
|
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
||||||
local out_path = ctx.out_root .. "/" .. dir_basename .. ".annotations.txt"
|
|
||||||
duffle.write_file(out_path, render_module_report(dir, dir_sources, module_results))
|
|
||||||
outputs[#outputs + 1] = { annotations_txt = out_path }
|
|
||||||
else
|
|
||||||
debug_log(" -> no content; skipping\n")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #all_results_for_summary > 0 then
|
-- For the summary, compute per-module totals once (re-validating annotations per source — same pattern as the meta_report renderer).
|
||||||
local summary_path = ctx.out_root .. "/annotation_validation.txt"
|
local annot_results = {}
|
||||||
duffle.write_file(summary_path, render_project_report(all_results_for_summary))
|
for _, src in ipairs(dir_sources) do
|
||||||
outputs[#outputs + 1] = { summary_txt = summary_path }
|
if src.scan then
|
||||||
|
local r = annotation.validate(ctx, src, nil)
|
||||||
|
r.source = src.path
|
||||||
|
annot_results[#annot_results + 1] = r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local n_annot, n_binds, n_macros = 0, 0, 0
|
||||||
|
for _, r in ipairs(annot_results) do
|
||||||
|
n_annot = n_annot + #r.annots
|
||||||
|
n_binds = n_binds + #r.binds
|
||||||
|
n_macros = n_macros + #r.macros
|
||||||
|
end
|
||||||
|
local sa_results = (corpus.static_analysis_results or {})[dir_basename] or {}
|
||||||
|
all_modules[#all_modules + 1] = {
|
||||||
|
module = dir_basename,
|
||||||
|
atoms = #(sa_results.atoms or {}),
|
||||||
|
annots = n_annot,
|
||||||
|
binds = n_binds,
|
||||||
|
macros = n_macros,
|
||||||
|
findings = #(sa_results.findings or {}),
|
||||||
|
errors = #(sa_results.errors or {}),
|
||||||
|
warnings = #(sa_results.warnings or {}),
|
||||||
|
info = #(sa_results.info or {}),
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return { outputs = outputs, errors = errors, warnings = warnings }
|
-- Project-wide renderer (once = true): write the summary file.
|
||||||
|
for _, renderer in ipairs(REPORT_RENDERERS) do
|
||||||
|
if renderer.once then
|
||||||
|
local body = renderer.gather(ctx, nil, nil, all_modules)
|
||||||
|
local out_path = out_root_effective .. "/" .. renderer.basename("") .. "." .. renderer.ext
|
||||||
|
duffle.write_file(out_path, body)
|
||||||
|
outputs[#outputs + 1] = { kind = renderer.name, path = out_path }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return { outputs = outputs, errors = {}, warnings = {} }
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
+113
-270
@@ -39,17 +39,19 @@
|
|||||||
--- The report header includes `Info: N` alongside Findings / Errors / Warnings, and a dedicated
|
--- 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.
|
--- `── 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:
|
--- The orchestrator (`ps1_meta.lua`) wires this module in via the PASSES table:
|
||||||
--- `["static-analysis"] = {
|
--- `["static-analysis"] = {
|
||||||
--- module = "passes.static_analysis",
|
--- module = "passes.static_analysis",
|
||||||
--- kind = "diagnostic",
|
--- kind = "diagnostic",
|
||||||
--- deps = {"word-counts", "components"},
|
--- deps = {"word-counts", "components"},
|
||||||
--- out = { { kind = "report", path_template = "<out_root>/<basename>.static_analysis.txt" } }
|
|
||||||
--- }
|
--- }
|
||||||
--- `kind = "diagnostic"` keeps every finding visible in the report; the orchestrator does not exit non-zero on static-analysis errors.
|
--- `kind = "diagnostic"` keeps every finding visible in the projection; the orchestrator does not exit non-zero on static-analysis errors.
|
||||||
--- Annotation and header-output validation remain build-stopping.
|
--- Annotation and header-output validation remain build-stopping.
|
||||||
---
|
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex, Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
@@ -148,6 +150,38 @@ local OUTPUT_EXTENSION = ".static_analysis.txt"
|
|||||||
--- @field findings Finding[] -- findings for this atom
|
--- @field findings Finding[] -- findings for this atom
|
||||||
--- @field total_cycles integer -- sum of token cycle costs
|
--- @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
|
-- classify_tokens — per-token classification
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -551,11 +585,12 @@ local function append_cu2_finding(atom, event, forward, transition,
|
|||||||
local event_ident = event.encoder or event.ident or "?"
|
local event_ident = event.encoder or event.ident or "?"
|
||||||
local policy = duffle.CU2_TRANSITION_POLICY or {}
|
local policy = duffle.CU2_TRANSITION_POLICY or {}
|
||||||
local evidence = policy.evidence or {}
|
local evidence = policy.evidence or {}
|
||||||
|
local event_line = line_for_word_event(event)
|
||||||
atom.paths.hazards[#atom.paths.hazards + 1] = {
|
atom.paths.hazards[#atom.paths.hazards + 1] = {
|
||||||
check = "transfer_hazards",
|
check = "transfer_hazards",
|
||||||
kind = kind,
|
kind = kind,
|
||||||
atom = atom.name,
|
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 "",
|
source = event.def_path or event.source or "",
|
||||||
relation_id = "mtc0_cu2_visibility",
|
relation_id = "mtc0_cu2_visibility",
|
||||||
semantic = "MTC0",
|
semantic = "MTC0",
|
||||||
@@ -619,10 +654,11 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
|||||||
|
|
||||||
local gap = ev_word - transition.producer_word - 1
|
local gap = ev_word - transition.producer_word - 1
|
||||||
local target = transition.target_state
|
local target = transition.target_state
|
||||||
|
local event_line = line_for_word_event(event)
|
||||||
if target == "unknown" then
|
if target == "unknown" then
|
||||||
append_cu2_finding(atom, event, forward, transition, gap, "info", "unknown",
|
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)"
|
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
|
, gap, transition.required
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -635,7 +671,7 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
|||||||
local verb = target == "enabled" and "enable" or "disable"
|
local verb = target == "enabled" and "enable" or "disable"
|
||||||
append_cu2_finding(atom, event, forward, transition, gap, "warning", "conservative",
|
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)"
|
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
|
, verb, gap, transition.required
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -653,7 +689,7 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
|||||||
string.format(
|
string.format(
|
||||||
"%s at line %d: COP2 unavailable after SR.CU2 was disabled"
|
"%s at line %d: COP2 unavailable after SR.CU2 was disabled"
|
||||||
.. " (gap=%d, required=%d)",
|
.. " (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))
|
gap, transition.required))
|
||||||
forward.cu2_state = "disabled"
|
forward.cu2_state = "disabled"
|
||||||
end
|
end
|
||||||
@@ -708,7 +744,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
|
|
||||||
for _, ev in ipairs(events) do
|
for _, ev in ipairs(events) do
|
||||||
local ev_ident = ev.encoder or ev.ident or "?"
|
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_source = ev.def_path or ev.source or ""
|
||||||
local ev_args = ev.args or {}
|
local ev_args = ev.args or {}
|
||||||
-- `word_events` use `i` as the 0-based word index across the entire expansion.
|
-- `word_events` use `i` as the 0-based word index across the entire expansion.
|
||||||
@@ -867,7 +903,7 @@ local function analyze_hardware_relations(atom)
|
|||||||
|
|
||||||
-- ── 4. Update semantic role state and stage post-command latch relations. ──
|
-- ── 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`.
|
-- 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 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`).
|
-- The relation kind is intentionally separate from the preceding MTC2 → command relation (`MTC2` / `CTC2` / `LWC2`).
|
||||||
@@ -980,7 +1016,7 @@ local function check_gte_input_latch(atom, _pipe_ctx, findings)
|
|||||||
end
|
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`.
|
-- 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.
|
-- The forward walker records `forward_state.post_command_roles[<register>]` after each command.
|
||||||
@@ -988,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.
|
-- 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`.
|
-- 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.
|
-- 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
|
local forward = atom.paths and atom.paths.forward_state
|
||||||
if not forward or not forward.post_command_roles then return end
|
if not forward or not forward.post_command_roles then return end
|
||||||
local events = atom.paths.word_events or {}
|
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`.
|
-- 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).
|
-- 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
|
for _, ev in ipairs(events) do
|
||||||
@@ -1047,11 +1056,12 @@ local function check_gte_result_position(atom, _pipe_ctx, findings)
|
|||||||
-- This is a semantic mismatch.
|
-- This is a semantic mismatch.
|
||||||
if reg ~= latest_screen_xy_entry.command_register
|
if reg ~= latest_screen_xy_entry.command_register
|
||||||
and (reg == "C2_SXY0" or reg == "C2_SXY1") then
|
and (reg == "C2_SXY0" or reg == "C2_SXY1") then
|
||||||
|
local ev_line = line_for_word_event(ev)
|
||||||
findings[#findings + 1] = {
|
findings[#findings + 1] = {
|
||||||
check = "gte_result_position",
|
check = "gte_role_mismatch",
|
||||||
kind = "warning",
|
kind = "warning",
|
||||||
atom = atom.name,
|
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 "",
|
source = ev.def_path or ev.source or "",
|
||||||
relation_id = "result_role_mismatch",
|
relation_id = "result_role_mismatch",
|
||||||
semantic = "result_position",
|
semantic = "result_position",
|
||||||
@@ -1062,7 +1072,7 @@ local function check_gte_result_position(atom, _pipe_ctx, findings)
|
|||||||
producer_word = latest_screen_xy_entry.producer_word,
|
producer_word = latest_screen_xy_entry.producer_word,
|
||||||
producer_line = latest_screen_xy_entry.producer_line,
|
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)"
|
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
|
, reg, latest_screen_xy_entry.command
|
||||||
, latest_screen_xy_entry.role
|
, latest_screen_xy_entry.role
|
||||||
, reg),
|
, reg),
|
||||||
@@ -1084,6 +1094,13 @@ end
|
|||||||
-- Branch/jump delay-slot NOPs belong to `control_transfer_delay_slot_use`, so this check leaves them unclassified.
|
-- 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.
|
-- 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.
|
-- The first `transfer_hazards` reader comment above records the projection contract.
|
||||||
-- ─────────────────────────────────────────────────────────────────────────
|
-- ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1091,6 +1108,9 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
local forward = atom.paths and atom.paths.forward_state
|
local forward = atom.paths and atom.paths.forward_state
|
||||||
local events = atom.paths.word_events or {}
|
local events = atom.paths.word_events or {}
|
||||||
if not events or #events == 0 then return end
|
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 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.
|
-- The replay is observation-only (no staging); the only output is one finding per non-BD-slot nop with its classification.
|
||||||
@@ -1100,20 +1120,16 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
local ev_ident = ev.encoder or ""
|
local ev_ident = ev.encoder or ""
|
||||||
local ev_args = ev.args or {}
|
local ev_args = ev.args or {}
|
||||||
local ev_word = ev.i or 0
|
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.
|
-- Classify the nop BEFORE its event is applied to the pending state.
|
||||||
if ev_ident == "nop" and prev_ev ~= nil then
|
if ev_ident == "nop" and prev_ev ~= nil then
|
||||||
-- Skip BD-slot nops: they are exclusively owned by control_transfer_delay_slot_use.
|
-- Skip BD-slot nops: they are exclusively owned by control_transfer_delay_slot_use.
|
||||||
|
-- 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 prev_ident = prev_ev.encoder or ""
|
||||||
local prev_args = prev_ev.args or {}
|
|
||||||
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||||
local is_bd_slot = false
|
local is_bd_slot = bd_policies[prev_ident] ~= nil
|
||||||
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
|
|
||||||
if not is_bd_slot then
|
if not is_bd_slot then
|
||||||
-- Find a pending modeled relation that this nop would retire.
|
-- Find a pending modeled relation that this nop would retire.
|
||||||
local retired = nil
|
local retired = nil
|
||||||
@@ -1157,7 +1173,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
check = "hazard_nop_use",
|
check = "hazard_nop_use",
|
||||||
kind = "info",
|
kind = "info",
|
||||||
atom = atom.name,
|
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 "",
|
source = ev.def_path or ev.source or "",
|
||||||
nop_classification = "modeled-required",
|
nop_classification = "modeled-required",
|
||||||
nop_word_index = ev_word,
|
nop_word_index = ev_word,
|
||||||
@@ -1165,7 +1181,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
producer_destination = retired.destination,
|
producer_destination = retired.destination,
|
||||||
consumer_token = would_be_consumer or "<would-be-consumer>",
|
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)"
|
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
|
else
|
||||||
@@ -1173,16 +1189,16 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
local slot_kind = "plain"
|
local slot_kind = "plain"
|
||||||
findings[#findings + 1] = {
|
findings[#findings + 1] = {
|
||||||
check = "hazard_nop_use",
|
check = "hazard_nop_use",
|
||||||
kind = "warning",
|
kind = "info",
|
||||||
atom = atom.name,
|
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 "",
|
source = ev.def_path or ev.source or "",
|
||||||
nop_classification = "modeled-redundant",
|
nop_classification = "modeled-redundant",
|
||||||
nop_word_index = ev_word,
|
nop_word_index = ev_word,
|
||||||
retired_relation = nil,
|
retired_relation = nil,
|
||||||
slot_kind = slot_kind,
|
slot_kind = slot_kind,
|
||||||
msg = string.format("%s at line %d: nop at word %d is modeled-redundant (no pending modeled relation)"
|
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
|
end
|
||||||
@@ -1254,6 +1270,9 @@ end
|
|||||||
-- Suppress the finding when `policy.suppress_arg1[first_arg]` is non-nil.
|
-- 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.
|
-- 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
|
-- `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.
|
-- 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.
|
-- `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.
|
||||||
@@ -1262,6 +1281,9 @@ end
|
|||||||
local function check_control_transfer_delay_slot_use(atom, pipe_ctx, findings)
|
local function check_control_transfer_delay_slot_use(atom, pipe_ctx, findings)
|
||||||
local events = atom.paths.word_events or {}
|
local events = atom.paths.word_events or {}
|
||||||
if not events or #events == 0 then return end
|
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 {}
|
local policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||||
for event_idx, event in ipairs(events) do
|
for event_idx, event in ipairs(events) do
|
||||||
-- Canonical word_events use `encoder` as the leading identifier of the emitting token).
|
-- Canonical word_events use `encoder` as the leading identifier of the emitting token).
|
||||||
@@ -1276,9 +1298,9 @@ local function check_control_transfer_delay_slot_use(atom, pipe_ctx, findings)
|
|||||||
local slot = events[event_idx + 1]
|
local slot = events[event_idx + 1]
|
||||||
local slot_ident = slot and (slot.encoder or slot.ident) or "<missing>"
|
local slot_ident = slot and (slot.encoder or slot.ident) or "<missing>"
|
||||||
if slot == nil or (slot.encoder or slot.ident) == "nop" then
|
if slot == nil or (slot.encoder or slot.ident) == "nop" then
|
||||||
-- Each word event carries `body_line` as the physical source line.
|
-- Prefer `call_line` (the line of the `mac_X(...)` call site in the atom body) so the rendered
|
||||||
-- Use `body_line`, then `def_line`, then 0.
|
-- finding points at the user's source, not at the vendored component body.
|
||||||
local ev_line = event.body_line or event.line or event.def_line or 0
|
local ev_line = line_for_word_event(event)
|
||||||
findings[#findings + 1] = {
|
findings[#findings + 1] = {
|
||||||
atom = atom.name,
|
atom = atom.name,
|
||||||
line = ev_line,
|
line = ev_line,
|
||||||
@@ -1303,8 +1325,17 @@ end
|
|||||||
--- Empty bodies are not currently flagged — runtime infrastructure atoms like
|
--- Empty bodies are not currently flagged — runtime infrastructure atoms like
|
||||||
--- `MipsAtom_(yield) { mac_yield() }` and `MipsAtom_(tape_exit) { jump_reg(rret_addr), nop }`
|
--- `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.
|
--- 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.
|
--- Uses the standard `(atom, pipe_ctx, findings)` signature; `pipe_ctx` is unused.
|
||||||
local function check_mac_yield_uniformity(atom, pipe_ctx, findings)
|
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:
|
-- Per-kind semantics:
|
||||||
-- MipsAtom_ (baked atom): exactly 1 mac_yield at the end of the body. Control transfer is the atom's job.
|
-- 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.
|
-- MipsAtomComp_ (bare static-array component): ZERO mac_yield.
|
||||||
@@ -1935,7 +1966,7 @@ end
|
|||||||
local CHECK_RULES = {
|
local CHECK_RULES = {
|
||||||
{ name = "transfer_hazards", per_atom = check_transfer_hazards },
|
{ name = "transfer_hazards", per_atom = check_transfer_hazards },
|
||||||
{ name = "gte_input_latch", per_atom = check_gte_input_latch },
|
{ 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 = "hazard_nop_use", per_atom = check_hazard_nop_use },
|
||||||
{ name = "control_transfer_delay_slot_use",per_atom = check_control_transfer_delay_slot_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 },
|
{ name = "mac_yield_uniformity", per_atom = check_mac_yield_uniformity },
|
||||||
@@ -2179,203 +2210,6 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
-- Per-directory output: build/gen/<dir_basename>.static_analysis.txt
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
--- Per-directory emit. Aggregates atoms + findings across every source in `dir_sources`
|
|
||||||
--- and writes a single report to `<out_root>/<dir_basename>.static_analysis.txt`.
|
|
||||||
--- Called only when at least one atom was found (the caller in M.run handles the skip).
|
|
||||||
---
|
|
||||||
--- `info` is finding-level info only (kind == "info" findings); the scanned/cycles summary rows
|
|
||||||
--- live in `summaries` and are rendered as trailing summary lines after `Module findings:`.
|
|
||||||
local function emit_module_static_analysis_txt(ctx, dir, dir_sources, atoms, findings, errors, warnings, info, summaries)
|
|
||||||
-- Module basename = last component of `dir` ("code/duffle" -> "duffle").
|
|
||||||
local dir_basename = dir:match("([^/\\]+)$") or dir
|
|
||||||
local out_path = ctx.out_root .. "/" .. dir_basename .. ".static_analysis.txt"
|
|
||||||
duffle.ensure_dir(ctx.out_root)
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
local function add(s) lines[#lines + 1] = s end
|
|
||||||
|
|
||||||
add("========================================================")
|
|
||||||
add("STATIC ANALYSIS PASS -- module " .. dir_basename)
|
|
||||||
add("========================================================")
|
|
||||||
add(string.format("Sources: %d", #dir_sources))
|
|
||||||
for _, s in ipairs(dir_sources) do
|
|
||||||
add(" " .. s.path)
|
|
||||||
end
|
|
||||||
add("")
|
|
||||||
|
|
||||||
-- Tally atoms by kind for the header summary
|
|
||||||
local n_atoms, n_bare, n_proc = 0, 0, 0
|
|
||||||
for _, a in ipairs(atoms) do
|
|
||||||
n_atoms = n_atoms + 1
|
|
||||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
|
||||||
elseif a.kind == "comp_proc" then n_proc = n_proc + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local header_atoms = string.format("Atoms: %d", n_atoms)
|
|
||||||
if n_bare > 0 or n_proc > 0 then
|
|
||||||
header_atoms = header_atoms .. string.format(" (atoms: %d, comp_bare: %d, comp_proc: %d)",
|
|
||||||
n_atoms - n_bare - n_proc, n_bare, n_proc)
|
|
||||||
end
|
|
||||||
-- Header carries the per-severity counts; info is its own column, not a warning.
|
|
||||||
-- (`Info: N` is the byte-asserted field that the focused test matches; do not collapse it into Warnings.)
|
|
||||||
add(string.format("%s Findings: %d Errors: %d Warnings: %d Info: %d",
|
|
||||||
header_atoms, #findings, #errors, #warnings, #info))
|
|
||||||
add("")
|
|
||||||
|
|
||||||
-- Group findings by atom (with source prefix when multi-source module)
|
|
||||||
local multi_source = #dir_sources > 1
|
|
||||||
local by_atom = {}
|
|
||||||
for _, f in ipairs(findings) do
|
|
||||||
by_atom[f.atom] = by_atom[f.atom] or {}
|
|
||||||
by_atom[f.atom][#by_atom[f.atom] + 1] = f
|
|
||||||
end
|
|
||||||
|
|
||||||
if next(by_atom) == nil then
|
|
||||||
add(" (no findings -- every atom passed all checks)")
|
|
||||||
else
|
|
||||||
add("── Findings by atom ─────────────────────────────────────")
|
|
||||||
for _, a in ipairs(atoms) do
|
|
||||||
local fs = by_atom[a.name]
|
|
||||||
if fs then
|
|
||||||
local label = a.name
|
|
||||||
if multi_source and a.source_path then
|
|
||||||
label = string.format("%s (%s)", a.name, a.source_path:match("([^/\\]+)$") or a.source_path)
|
|
||||||
end
|
|
||||||
add(string.format(" %s line %d", label, a.line))
|
|
||||||
for _, f in ipairs(fs) do
|
|
||||||
add(string.format(" [%s] %s", f.check, f.msg))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Errors ──────────────────────────────────────────────")
|
|
||||||
if #errors == 0 then add(" (none)") end
|
|
||||||
for _, e in ipairs(errors) do
|
|
||||||
add(string.format(" X line %d %s", e.line, e.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Warnings ────────────────────────────────────────────")
|
|
||||||
if #warnings == 0 then add(" (none)") end
|
|
||||||
for _, w in ipairs(warnings) do
|
|
||||||
add(string.format(" ! line %d %s", w.line, w.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Finding-level Info section.
|
|
||||||
-- Rendered between Warnings and the per-atom cycle table so the next `── ` line after `── Info` is the per-atom cycle counts section;
|
|
||||||
-- the trailing scan/cycle summary rows (rendered after Module findings) stay outside this section.
|
|
||||||
add("")
|
|
||||||
add("── Info ────────────────────────────────────────────────")
|
|
||||||
if #info == 0 then add(" (none)") end
|
|
||||||
for _, i_ in ipairs(info) do
|
|
||||||
add(string.format(" i line %d %s", i_.line, i_.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Per-atom cycle counts (path-aware). For each atom:
|
|
||||||
-- min = shortest path through the body (earliest exit)
|
|
||||||
-- max = longest path through the body (full fall-through)
|
|
||||||
-- br = number of branch instructions
|
|
||||||
-- paths = number of distinct paths reached
|
|
||||||
-- Both min and max are best-case (no stalls); BD-slot nops are absorbed into branch costs (MIPS semantics).
|
|
||||||
add("")
|
|
||||||
add("── Per-atom cycle counts (path-aware, best case, no stalls) ─")
|
|
||||||
if #atoms == 0 then
|
|
||||||
add(" (no atoms)")
|
|
||||||
else
|
|
||||||
-- Sort atoms by max cycles descending for quick scanning.
|
|
||||||
local sorted = {}
|
|
||||||
for _, a in ipairs(atoms) do sorted[#sorted + 1] = a end
|
|
||||||
table.sort(sorted, function(x, y) return ((x.paths or {}).cycles_max or 0) > ((y.paths or {}).cycles_max or 0) end)
|
|
||||||
for _, a in ipairs(sorted) do
|
|
||||||
local p = a.paths or {}
|
|
||||||
local br_count = p.branches or 0
|
|
||||||
local path_count = p.paths or 0
|
|
||||||
local loops_tag = p.has_loops and " [loop!]" or ""
|
|
||||||
local unknown_tag = ""
|
|
||||||
if p.unknown_macros and #p.unknown_macros > 0 then
|
|
||||||
unknown_tag = string.format(" [unknown: %s]",
|
|
||||||
table.concat(p.unknown_macros, ", "))
|
|
||||||
end
|
|
||||||
local name_label = a.name
|
|
||||||
if multi_source and a.source_path then
|
|
||||||
name_label = string.format("%s (%s)", a.name, a.source_path:match("([^/\\]+)$") or a.source_path)
|
|
||||||
end
|
|
||||||
if br_count > 0 then
|
|
||||||
add(string.format(" %-44s min=%4d max=%4d br=%d paths=%d (line %d)%s%s",
|
|
||||||
name_label, p.cycles_min or 0, p.cycles_max or 0, br_count, path_count,
|
|
||||||
a.line, loops_tag, unknown_tag))
|
|
||||||
else
|
|
||||||
add(string.format(" %-44s %4d cycles (line %d, no branches)%s%s",
|
|
||||||
name_label, p.cycles_min or 0, a.line, loops_tag, unknown_tag))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Per-source scan summary ──────────────────────────────")
|
|
||||||
-- One line per source that contributed atoms.
|
|
||||||
-- The line includes the source basename + per-source atom count + (if path-aware cycle data is present) the min..max cycle range.
|
|
||||||
-- Sources with 0 atoms are skipped (they're just header files that declared no MipsAtom_ — they're already listed in the module's "Sources:" section above).
|
|
||||||
for _, src in ipairs(dir_sources) do
|
|
||||||
local src_atoms = {}
|
|
||||||
for _, a in ipairs(atoms) do
|
|
||||||
if a.source_path == src.path then
|
|
||||||
src_atoms[#src_atoms + 1] = a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #src_atoms == 0 then
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
local atom_count = #src_atoms
|
|
||||||
local mn, mx = math.huge, -1
|
|
||||||
for _, a in ipairs(src_atoms) do
|
|
||||||
local p = a.paths or {}
|
|
||||||
if (p.cycles_min or 0) < mn then mn = p.cycles_min or 0 end
|
|
||||||
if (p.cycles_max or 0) > mx then mx = p.cycles_max or 0 end
|
|
||||||
end
|
|
||||||
local path_str
|
|
||||||
if mx > 0 then
|
|
||||||
path_str = string.format(" cycles=%d..%d", mn, mx)
|
|
||||||
else
|
|
||||||
path_str = string.format(" %d cycles", mn)
|
|
||||||
end
|
|
||||||
add(string.format(" %-30s %d atom%s%s",
|
|
||||||
src.basename, atom_count,
|
|
||||||
atom_count == 1 and "" or "s",
|
|
||||||
path_str))
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Module-level findings summary (across all sources).
|
|
||||||
-- Info has its own count; it remains separate from warnings.
|
|
||||||
local total_errs = #errors
|
|
||||||
local total_warns = #warnings
|
|
||||||
local total_infos = #info
|
|
||||||
add("")
|
|
||||||
add(string.format("Module findings: %d error(s), %d warning(s), %d info", total_errs, total_warns, total_infos))
|
|
||||||
|
|
||||||
-- Per-source "scanned:" / "cycles:" summary lines (each line includes the source basename for traceability).
|
|
||||||
-- These are kept SEPARATE from the finding-level Info section above so the report's Info section is signal-only
|
|
||||||
-- (true findings), not a mix of findings + rollups.
|
|
||||||
-- The downstream test (`test_control_transfer_delay_slot.lua`)
|
|
||||||
-- asserts that the Info section contains NEITHER `scanned:` NOR `cycles:` lines.
|
|
||||||
if summaries and #summaries > 0 then
|
|
||||||
add("")
|
|
||||||
for _, s in ipairs(summaries) do
|
|
||||||
add(string.format(" %s", s.msg))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
duffle.write_file(out_path, table.concat(lines, "\n") .. "\n")
|
|
||||||
return out_path
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- M.run — orchestrator entry
|
-- M.run — orchestrator entry
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -2431,21 +2265,30 @@ function M.run(ctx)
|
|||||||
for _, s in ipairs(result.summaries or {}) do dir_summaries[#dir_summaries + 1] = s end
|
for _, s in ipairs(result.summaries or {}) do dir_summaries[#dir_summaries + 1] = s end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Skip directories with zero atoms. A directory with only headers / no MipsAtom_ is "nothing to report".
|
-- Stash per-module results on the corpus for `report.lua` to consume.
|
||||||
if #all_atoms == 0 then
|
-- Avoids re-running validate() in the report pass + avoids rebuilding corpus_pipe_ctx.
|
||||||
-- Still aggregate errors/warnings/info so orchestrator sees them, but don't write a file.
|
-- Pattern matches `corpus.atoms_by_name` / `corpus.word_counts` / `corpus.components`
|
||||||
|
-- (one writer: `static_analysis.lua`; one reader: `report.lua`).
|
||||||
|
-- Module basename = last component of `dir` ("code/duffle" -> "duffle").
|
||||||
|
local dir_basename = dir:match("([^/\\]+)$") or dir
|
||||||
|
corpus.static_analysis_results = corpus.static_analysis_results or {}
|
||||||
|
corpus.static_analysis_results[dir_basename] = {
|
||||||
|
atoms = all_atoms,
|
||||||
|
findings = all_findings,
|
||||||
|
errors = dir_errors,
|
||||||
|
warnings = dir_warnings,
|
||||||
|
info = dir_info,
|
||||||
|
summaries = dir_summaries,
|
||||||
|
sources = dir_sources,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Aggregate per-dir errors/warnings/info into the orchestrator totals.
|
||||||
|
-- Hoisted out of any per-dir file-emit so `report.lua` can drop the on-disk file emitter without losing the cross-module rollup.
|
||||||
for _, e in ipairs(dir_errors) do errors [#errors + 1] = e end
|
for _, e in ipairs(dir_errors) do errors [#errors + 1] = e end
|
||||||
for _, w in ipairs(dir_warnings) do warnings[#warnings + 1] = w end
|
for _, w in ipairs(dir_warnings) do warnings[#warnings + 1] = w end
|
||||||
for _, i_ in ipairs(dir_info) do info [#info + 1] = i_ end
|
for _, i_ in ipairs(dir_info) do info [#info + 1] = i_ end
|
||||||
else
|
-- (No per-dir emit: per-module findings are stashed on `corpus.static_analysis_results` above.
|
||||||
local out_path = emit_module_static_analysis_txt(ctx, dir, dir_sources, all_atoms, all_findings, dir_errors, dir_warnings, dir_info, dir_summaries)
|
-- `report.lua` reads that projection to render `<module>.atom_meta_report.md` without re-running validate().)
|
||||||
if out_path then
|
|
||||||
table.insert(outputs, { static_analysis_txt = out_path })
|
|
||||||
end
|
|
||||||
for _, e in ipairs(dir_errors) do errors [#errors + 1] = e end
|
|
||||||
for _, w in ipairs(dir_warnings) do warnings[#warnings + 1] = w end
|
|
||||||
for _, i_ in ipairs(dir_info) do info[#info + 1] = i_ end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Result exposes at least {outputs, errors, warnings, info}.
|
-- Result exposes at least {outputs, errors, warnings, info}.
|
||||||
|
|||||||
@@ -2,17 +2,14 @@
|
|||||||
---
|
---
|
||||||
--- Dispatches to pass modules under `scripts/passes/`, resolving dependencies topologically (Kahn's algorithm + cycle detection).
|
--- Dispatches to pass modules under `scripts/passes/`, resolving dependencies topologically (Kahn's algorithm + cycle detection).
|
||||||
---
|
---
|
||||||
--- **Architecture**:
|
--- Architecture:
|
||||||
--- - **PASSES table** — declarative dep graph (data, not code).
|
--- - PASSES table: Declarative dep graph (data, not code).
|
||||||
--- - **FLAG_HANDLERS table** — maps CLI flags to handlers.
|
--- - FLAG_HANDLERS table: Maps CLI flags to handlers.
|
||||||
--- - **parse_args** → **build_ctx** (resolves unity/direct includes or exact sources; no semantic scanning) → **topo_sort** → **dispatch_passes**.
|
--- - parse_args → build_ctx (resolves unity/direct includes or exact sources) → topo_sort → dispatch_passes.
|
||||||
--- - The first pass in the dep graph is `scan-source` (see `passes/scan_source.lua`).
|
--- - The first pass in the dep graph is `scan-source` (see `passes/scan_source.lua`).
|
||||||
--- It calls `duffle.scan_source` once per source to produce the fat `SourceScan` payload, which is attached to each `src.scan`.
|
--- It calls `duffle.scan_source` once per source to produce the fat `SourceScan` payload, which is attached to each `src.scan`.
|
||||||
--- Every other pass that reads source structure depends on `scan-source` and consumes `src.scan` as a read-only.
|
--- Every other pass that reads source structure depends on `scan-source` and consumes `src.scan` as a read-only.
|
||||||
---
|
---
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
|
||||||
--- Lua 5.3 compatible.
|
|
||||||
---
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -160,7 +157,7 @@ local PASSES = {
|
|||||||
report = {
|
report = {
|
||||||
module = "passes.report",
|
module = "passes.report",
|
||||||
kind = "report",
|
kind = "report",
|
||||||
deps = {"annotation", "static-analysis"},
|
deps = {"annotation", "static-analysis", "atoms-source-map"}, -- +atoms-source-map (consolidated-report-files refactor, 2026-07-26)
|
||||||
groups = { "pre-link" },
|
groups = { "pre-link" },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -206,7 +203,8 @@ end
|
|||||||
|
|
||||||
-- Pass-kind taxonomy: Which kinds stop the build on errors?
|
-- Pass-kind taxonomy: Which kinds stop the build on errors?
|
||||||
--
|
--
|
||||||
-- Report severity is independent from process exit policy. A "diagnostic" pass still writes every `error`/`warning` finding into its report file,
|
-- Report severity is independent from process exit policy.
|
||||||
|
-- A "diagnostic" pass still writes every `error`/`warning` finding into its report file,
|
||||||
-- but `report_validation_errors` returns early for non-stopping kinds, so nothing is printed to stderr and the orchestrator does not exit non-zero.
|
-- but `report_validation_errors` returns early for non-stopping kinds, so nothing is printed to stderr and the orchestrator does not exit non-zero.
|
||||||
-- Adding a new pass kind requires listing it here explicitly; an unknown kind must not silently fall back to "true".
|
-- Adding a new pass kind requires listing it here explicitly; an unknown kind must not silently fall back to "true".
|
||||||
local PASS_KIND_STOP_ON_ERROR = {
|
local PASS_KIND_STOP_ON_ERROR = {
|
||||||
|
|||||||
Reference in New Issue
Block a user