mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-05 15:18:49 +00:00
Codebase overhaul. Metaprogram proofread (part 2). Starting to get serious.
Need to rewrite the ps1 lua metaprogram sometime soonish. Getting too bloated... need to consolidate code paths. In this push codebase structure is starting to get a bit more realized. Decided todo now to match Pikuma's linking module files vods beginning to reorganize its codebase as well. Atoms & atom components are not in their on *.atom.c files. (Not calling it tape.c as I don't really bake tapes like that outside of the unity c file so far...) The lua metaprogram has had additional features added to it yet again to avoid hardcoding module handling and supporting multiple atom files per-module. Either after the camera or cd-rom section I'll be most likely pausing to fully refactor the metaprogram. Possibly as a full re-write to get the loc minimal.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* atom_dsl.h
|
* dsl.atom.h
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*
|
*
|
||||||
* ATOM DSL: Annotation layer for tape atoms (lottes_tape.h).
|
* ATOM DSL: Annotation layer for tape atoms (lottes_tape.h).
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
|
|
||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
#pragma once
|
#pragma once
|
||||||
// #include <stdint.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
@@ -148,12 +147,12 @@
|
|||||||
* ... body ...
|
* ... body ...
|
||||||
* atom_label(bounds_chk) ← another anchor
|
* atom_label(bounds_chk) ← another anchor
|
||||||
*
|
*
|
||||||
* atom_offset(culling, bounds_chk) ← resolved by gen/.offsets.h
|
* atom_offset(culling, bounds_chk) ← resolved by gen/offsets.h
|
||||||
*
|
*
|
||||||
* The metaprogram generates gen/atom_offsets.h with one #define with the offset value per atom_offset(F, T) call.
|
* The metaprogram generates gen/offsets.h with one #define with the offset value per atom_offset(F, T) call.
|
||||||
* The preprocessor then expands the call to the right immediate value.
|
* The preprocessor then expands the call to the right immediate value.
|
||||||
*
|
*
|
||||||
* If gen/atom_offsets.h is stale (or atom_label(name) is undefined), `atom_offset_F_T` becomes an undefined macro and the C build fails.
|
* If gen/offsets.h is stale (or atom_label(name) is undefined), `atom_offset_F_T` becomes an undefined macro and the C build fails.
|
||||||
* ============================================================================*/
|
* ============================================================================*/
|
||||||
#define atom_offset(F, T) atom_offset_ ## F ## _ ## T
|
#define atom_offset(F, T) atom_offset_ ## F ## _ ## T
|
||||||
// atom_label is a pure annotation for the metaprogram's offset calculations.
|
// atom_label is a pure annotation for the metaprogram's offset calculations.
|
||||||
+5
-2
@@ -43,7 +43,9 @@
|
|||||||
|
|
||||||
#define R_ restrict
|
#define R_ restrict
|
||||||
#define V_ volatile
|
#define V_ volatile
|
||||||
// Fictional, used for intiution.
|
|
||||||
|
#pragma region Fictional //, used for intiution
|
||||||
|
|
||||||
#define EUB_ restrict // Execute Unit Bound: Data is siloed in the ALU Register File. The Load/Store Unit is bypassed. (Route to Execution Unit. Keep in registers)
|
#define EUB_ restrict // Execute Unit Bound: Data is siloed in the ALU Register File. The Load/Store Unit is bypassed. (Route to Execution Unit. Keep in registers)
|
||||||
#define ISO_ restrict // Isolated Provenance: Alternative to Exu_. Guarantees electrical memory isolation,
|
#define ISO_ restrict // Isolated Provenance: Alternative to Exu_. Guarantees electrical memory isolation,
|
||||||
// unlocking the compiler’s ability to safely pack data across multiple parallel SIMD lanes (vectorization).
|
// unlocking the compiler’s ability to safely pack data across multiple parallel SIMD lanes (vectorization).
|
||||||
@@ -67,7 +69,8 @@
|
|||||||
#define latch_load_anchor(ptr) //__atomic_load_n(ptr, ooo_anchor_)
|
#define latch_load_anchor(ptr) //__atomic_load_n(ptr, ooo_anchor_)
|
||||||
#define latch_store_drain(ptr, val) //__atomic_store_n(ptr, val, ooo_drain_)
|
#define latch_store_drain(ptr, val) //__atomic_store_n(ptr, val, ooo_drain_)
|
||||||
#define pulse_xchg_weld(ptr, val) //__atomic_exchange_n(ptr, val, ooo_weld_)
|
#define pulse_xchg_weld(ptr, val) //__atomic_exchange_n(ptr, val, ooo_weld_)
|
||||||
//end of: Fictional.
|
|
||||||
|
#pragma endreigon Fictional
|
||||||
|
|
||||||
|
|
||||||
// R_ (restrict) establishes an "Eigen" or "Proprius" mapping.
|
// R_ (restrict) establishes an "Eigen" or "Proprius" mapping.
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
|
||||||
#pragma once
|
|
||||||
#endif
|
|
||||||
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
|
||||||
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
|
||||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
|
||||||
|
|
||||||
#ifndef WORD_COUNT
|
|
||||||
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* MACRO ATOM Components (Reusable Assembly Components)
|
|
||||||
* These do NOT yield. They are expanded inline inside Tape Atoms.
|
|
||||||
* ---------------------------------------------------------------------------*/
|
|
||||||
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
|
||||||
// - mac_yield() is the safe default for atom-endings: 4 words, BD-slot of jr is mandatory nop.
|
|
||||||
// - mac_yield_load() + mac_yield_tail():
|
|
||||||
// - unconditional branch: mac_yield_load fills the branch's BD-slot (replaces a nop);
|
|
||||||
// - mac_yield_tail runs at the branch target (does NOT re-load R_AtomJmp).
|
|
||||||
#define mac_yield(...) \
|
|
||||||
load_word(R_AtomJmp, R_TapePtr, 0) \
|
|
||||||
, add_ui_self( R_TapePtr, S_(MipsCode)) \
|
|
||||||
, jump_reg( R_AtomJmp) \
|
|
||||||
, nop
|
|
||||||
WORD_COUNT(mac_yield, 4)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_yield_load(...) \
|
|
||||||
load_word(R_AtomJmp, R_TapePtr, 0)
|
|
||||||
WORD_COUNT(mac_yield_load, 1)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_yield_tail(...) \
|
|
||||||
add_ui_self(R_TapePtr, S_(MipsCode)) \
|
|
||||||
, jump_reg( R_AtomJmp) \
|
|
||||||
, nop
|
|
||||||
WORD_COUNT(mac_yield_tail, 3)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* Words: 3; Loads 3 S2 indices from the face array */
|
|
||||||
#define mac_load_tri_indices(...) \
|
|
||||||
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))
|
|
||||||
WORD_COUNT(mac_load_tri_indices, 3)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE */
|
|
||||||
#define mac_gte_load_tri_verts(...) \
|
|
||||||
shift_lleft(R_AT, R_T0, v3s2_byteoff) \
|
|
||||||
, add_u_self(R_AT, R_VertBase) \
|
|
||||||
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
|
||||||
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
|
||||||
, gte_mv_to_data_r(R_V0, C2_VXY0) \
|
|
||||||
, gte_mv_to_data_r(R_V1, C2_VZ0) \
|
|
||||||
, shift_lleft(R_AT, R_T1, v3s2_byteoff) \
|
|
||||||
, add_u_self(R_AT, R_VertBase) \
|
|
||||||
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
|
||||||
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
|
||||||
, gte_mv_to_data_r(R_V0, C2_VXY1) \
|
|
||||||
, gte_mv_to_data_r(R_V1, C2_VZ1) \
|
|
||||||
, shift_lleft(R_AT, R_T2, v3s2_byteoff) \
|
|
||||||
, add_u_self(R_AT, R_VertBase) \
|
|
||||||
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
|
||||||
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
|
||||||
, gte_mv_to_data_r(R_V0, C2_VXY2) \
|
|
||||||
, gte_mv_to_data_r(R_V1, C2_VZ2)
|
|
||||||
WORD_COUNT(mac_gte_load_tri_verts, 18)
|
|
||||||
|
|
||||||
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
|
||||||
* Hardcoded for Poly_F3 (5 words). For Poly_G4, use ac_insert_ot_tag_g4. */
|
|
||||||
#define mac_insert_ot_tag_f3(...) \
|
|
||||||
shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \
|
|
||||||
, add_u_self( R_T1, R_OtBase) /* T1 = & OrderingTable[OTZ] */ \
|
|
||||||
, load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \
|
|
||||||
, load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (5 - 1) << 24 = 4 << 24 */ \
|
|
||||||
, mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \
|
|
||||||
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
|
||||||
, store_word( R_AT, R_PrimCursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \
|
|
||||||
, shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \
|
|
||||||
, shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \
|
|
||||||
, store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */
|
|
||||||
WORD_COUNT(mac_insert_ot_tag_f3, 11)
|
|
||||||
|
|
||||||
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
|
||||||
* Hardcoded for Poly_G4 (9 words). For Poly_F3, use ac_insert_ot_tag_f3. */
|
|
||||||
#define mac_insert_ot_tag_g4(...) \
|
|
||||||
shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \
|
|
||||||
, add_u_self( R_T1, R_OtBase) /* T1 = & OrderingTable[OTZ] */ \
|
|
||||||
, load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \
|
|
||||||
, load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (9 - 1) << 24 = 8 << 24 */ \
|
|
||||||
, mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \
|
|
||||||
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
|
||||||
, store_word( R_AT, R_PrimCursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \
|
|
||||||
, shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \
|
|
||||||
, shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \
|
|
||||||
, store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */
|
|
||||||
WORD_COUNT(mac_insert_ot_tag_g4, 11)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_pack_color_word(off, cmd, r, g, b) \
|
|
||||||
load_upper_i(R_AT, (cmd) << 8 | (b)) \
|
|
||||||
, or_i_self( R_AT, ((g) << 8) | (r)) \
|
|
||||||
, store_word( R_AT, R_PrimCursor, (off))
|
|
||||||
WORD_COUNT(mac_pack_color_word, 3)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_format_f3_color(r, g, b) \
|
|
||||||
mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b)
|
|
||||||
WORD_COUNT(mac_format_f3_color, 3)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3.
|
|
||||||
* PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). */
|
|
||||||
#define mac_gte_store_f3(...) \
|
|
||||||
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_F3,p0)) \
|
|
||||||
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)) \
|
|
||||||
, gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2))
|
|
||||||
WORD_COUNT(mac_gte_store_f3, 3)
|
|
||||||
|
|
||||||
#define mac_format_g4_color(r0, g0, b0, r1, g1, b1, r2, g2, b2, r3, g3, b3) \
|
|
||||||
mac_pack_color_word(O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0) \
|
|
||||||
, mac_pack_color_word(O_(Poly_G4,c1), 0, r1,g1,b1) \
|
|
||||||
, mac_pack_color_word(O_(Poly_G4,c2), 0, r2,g2,b2) \
|
|
||||||
, mac_pack_color_word(O_(Poly_G4,c3), 0, r3,g3,b3)
|
|
||||||
WORD_COUNT(mac_format_g4_color, 12)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
|
|
||||||
* G4 triangle portion to p0/p1/p2.
|
|
||||||
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
|
||||||
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
|
|
||||||
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
|
|
||||||
#define mac_gte_store_g4_p012(...) \
|
|
||||||
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_G4,p0)) \
|
|
||||||
, gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)) \
|
|
||||||
, gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2))
|
|
||||||
WORD_COUNT(mac_gte_store_g4_p012, 3)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
|
||||||
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
|
|
||||||
* SXY0 still holds v0.screen from the earlier RTPT.
|
|
||||||
*/
|
|
||||||
#define mac_gte_store_g4_p3(...) \
|
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p3))
|
|
||||||
WORD_COUNT(mac_gte_store_g4_p3, 1)
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
|
||||||
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#pragma region lottes_tape
|
|
||||||
|
|
||||||
|
|
||||||
#pragma endregion lottes_tape
|
|
||||||
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
#pragma once
|
||||||
|
#endif
|
||||||
|
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||||
|
// Directory: C:\projects\Pikuma\ps1\code\duffle/
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\word_count.metadata.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\dsl.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\memory.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\math.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gcc_asm.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\mips.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gp.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gte.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\pad.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\dsl.atom.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\mips.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gte.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.atom.c
|
||||||
|
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||||
|
|
||||||
|
#ifndef WORD_COUNT
|
||||||
|
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* MACRO ATOM Components (Reusable Assembly Components)
|
||||||
|
* These do NOT yield. They are expanded inline inside Tape Atoms.
|
||||||
|
* ---------------------------------------------------------------------------*/
|
||||||
|
// The 'Yield' sequence for Tape Atoms (mac_yield).
|
||||||
|
// - mac_yield() is the safe default for atom-endings: 4 words, BD-slot of jr is mandatory nop.
|
||||||
|
// - mac_yield_load() + mac_yield_tail():
|
||||||
|
// - unconditional branch: mac_yield_load fills the branch's BD-slot (replaces a nop);
|
||||||
|
// - mac_yield_tail runs at the branch target (does NOT re-load R_AtomJmp).
|
||||||
|
#define mac_yield(...) \
|
||||||
|
load_word(R_AtomJmp, R_TapePtr, 0) \
|
||||||
|
, add_ui_self( R_TapePtr, S_(MipsCode)) \
|
||||||
|
, jump_reg( R_AtomJmp) \
|
||||||
|
, nop
|
||||||
|
WORD_COUNT(mac_yield, 4)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_yield_load(...) \
|
||||||
|
load_word(R_AtomJmp, R_TapePtr, 0)
|
||||||
|
WORD_COUNT(mac_yield_load, 1)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_yield_tail(...) \
|
||||||
|
add_ui_self(R_TapePtr, S_(MipsCode)) \
|
||||||
|
, jump_reg( R_AtomJmp) \
|
||||||
|
, nop
|
||||||
|
WORD_COUNT(mac_yield_tail, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_load_v2s2(rs_x, rs_y, r_base, offset) \
|
||||||
|
load_half( rs_x, r_base, O_(V3_S2,x)) \
|
||||||
|
, load_half( rs_y, r_base, O_(V3_S2,y))
|
||||||
|
WORD_COUNT(mac_load_v2s2, 2)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_store_v2s2(rt_x, rt_y, base, offset) \
|
||||||
|
store_half(rt_x, base, offset + O_(V2_S2,x)) \
|
||||||
|
, store_half(rt_y, base, offset + O_(V2_S2,y))
|
||||||
|
WORD_COUNT(mac_store_v2s2, 2)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_store_rects2(rt_x, rt_y, rt_width, rt_height, base, offset) \
|
||||||
|
store_half(rt_x, base, offset + O_(Rect_S2,x)) \
|
||||||
|
, store_half(rt_y, base, offset + O_(Rect_S2,y)) \
|
||||||
|
, store_half(rt_width, base, offset + O_(Rect_S2,width)) \
|
||||||
|
, store_half(rt_height, base, offset + O_(Rect_S2,height))
|
||||||
|
WORD_COUNT(mac_store_rects2, 4)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_load_tri_indices(r_face_cusor, r_i0, r_i1, r_i2) \
|
||||||
|
load_half_u(r_i0, r_face_cusor, 0 * S_(S2)) \
|
||||||
|
, load_half_u(r_i1, r_face_cusor, 1 * S_(S2)) \
|
||||||
|
, load_half_u(r_i2, r_face_cusor, 2 * S_(S2))
|
||||||
|
WORD_COUNT(mac_load_tri_indices, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_gte_store_f3(r_primitive_cursor) \
|
||||||
|
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_F3,p0)) \
|
||||||
|
, gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_F3,p1)) \
|
||||||
|
, gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_F3,p2))
|
||||||
|
WORD_COUNT(mac_gte_store_f3, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_gte_load_tri_verts(r_vert_base, r_v0, r_v1, r_v2) \
|
||||||
|
shift_lleft(R_AT, r_v0, v3s2_byteoff) \
|
||||||
|
, add_u_self(R_AT, r_vert_base) \
|
||||||
|
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
||||||
|
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
||||||
|
, gte_mv_to_data_r(R_V0, C2_VXY0) \
|
||||||
|
, gte_mv_to_data_r(R_V1, C2_VZ0) \
|
||||||
|
, shift_lleft(R_AT, r_v1, v3s2_byteoff) \
|
||||||
|
, add_u_self(R_AT, r_vert_base) \
|
||||||
|
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
||||||
|
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
||||||
|
, gte_mv_to_data_r(R_V0, C2_VXY1) \
|
||||||
|
, gte_mv_to_data_r(R_V1, C2_VZ1) \
|
||||||
|
, shift_lleft(R_AT, r_v2, v3s2_byteoff) \
|
||||||
|
, add_u_self(R_AT, r_vert_base) \
|
||||||
|
, load_word(R_V0, R_AT, O_(V3_S2,x)) \
|
||||||
|
, load_word(R_V1, R_AT, O_(V3_S2,z)) \
|
||||||
|
, gte_mv_to_data_r(R_V0, C2_VXY2) \
|
||||||
|
, gte_mv_to_data_r(R_V1, C2_VZ2)
|
||||||
|
WORD_COUNT(mac_gte_load_tri_verts, 18)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_gte_store_g4_p012(r_primitive_cursor) \
|
||||||
|
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_G4,p0)) \
|
||||||
|
, gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_G4,p1)) \
|
||||||
|
, gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2))
|
||||||
|
WORD_COUNT(mac_gte_store_g4_p012, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_gte_store_g4_p3(r_primitive_cursor) \
|
||||||
|
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3))
|
||||||
|
WORD_COUNT(mac_gte_store_g4_p3, 1)
|
||||||
|
|
||||||
|
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
|
||||||
|
load_upper_i(reg_transfer, cmd >> 16) \
|
||||||
|
, or_i_self( reg_transfer, cmd & 0xFFFF) \
|
||||||
|
, store_word( reg_transfer, reg_base, port)
|
||||||
|
WORD_COUNT(mac_gcmd_push, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_store_rgb8(rr, rg, rb, base, offset) \
|
||||||
|
store_byte(rr, base, offset + O_(RGB8,r)) \
|
||||||
|
, store_byte(rg, base, offset + O_(RGB8,g)) \
|
||||||
|
, store_byte(rb, base, offset + O_(RGB8,b))
|
||||||
|
WORD_COUNT(mac_store_rgb8, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_pack_color_word(r_base, off, cmd, r, g, b) \
|
||||||
|
load_upper_i(R_AT, (cmd) << 8 | (b)) \
|
||||||
|
, or_i_self( R_AT, ((g) << 8) | (r)) \
|
||||||
|
, store_word( R_AT, r_base, (off))
|
||||||
|
WORD_COUNT(mac_pack_color_word, 3)
|
||||||
|
|
||||||
|
/* atom_dbg_skip */
|
||||||
|
#define mac_format_f3_color(r_base, r, g, b) \
|
||||||
|
mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b)
|
||||||
|
WORD_COUNT(mac_format_f3_color, 3)
|
||||||
|
|
||||||
|
#define mac_format_g4_color(r_prim_cursor, r0, g0, b0, r1, g1, b1, r2, g2, b2, r3, g3, b3) \
|
||||||
|
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0) \
|
||||||
|
, mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c1), 0, r1,g1,b1) \
|
||||||
|
, mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c2), 0, r2,g2,b2) \
|
||||||
|
, mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c3), 0, r3,g3,b3)
|
||||||
|
WORD_COUNT(mac_format_g4_color, 12)
|
||||||
|
|
||||||
|
#define mac_insert_ot_tag_f3(r_ot_base, r_prim_cursor) \
|
||||||
|
shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \
|
||||||
|
, add_u_self( R_T1, r_ot_base) /* T1 = & OrderingTable[OTZ] */ \
|
||||||
|
, load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \
|
||||||
|
, load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (5 - 1) << 24 = 4 << 24 */ \
|
||||||
|
, mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \
|
||||||
|
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
||||||
|
, store_word( R_AT, r_prim_cursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \
|
||||||
|
, shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \
|
||||||
|
, shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \
|
||||||
|
, store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */
|
||||||
|
WORD_COUNT(mac_insert_ot_tag_f3, 11)
|
||||||
|
|
||||||
|
#define mac_insert_ot_tag_g4(r_ot_base, r_prim_cursor) \
|
||||||
|
shift_lleft( R_T1, R_T1, S_(U4)/2) /* T1 = otz * S_(U4) (otz arg is implicit R_T1) */ \
|
||||||
|
, add_u_self( R_T1, r_ot_base) /* T1 = & OrderingTable[OTZ] */ \
|
||||||
|
, load_word( R_AT, R_T1, O_(PolyTag,code)) /* AT = old_ot_head */ \
|
||||||
|
, load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits) /* V0 = (9 - 1) << 24 = 8 << 24 */ \
|
||||||
|
, mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)) /* Strip upper 8 bits (length from prev cell) → keep only low 24 */ \
|
||||||
|
, or_u( R_AT, R_AT, R_V0) /* Merge length */ \
|
||||||
|
, store_word( R_AT, r_prim_cursor, O_(PolyTag,code)) /* prim->tag = packed(prim_length, old_addr) */ \
|
||||||
|
, shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)) /* AT = (prim_length << 24) | old_addr */ \
|
||||||
|
, shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)) \
|
||||||
|
, store_word( R_AT, R_T1, O_(PolyTag,code)) /* OrderingTable[OTZ] = PrimCursor */
|
||||||
|
WORD_COUNT(mac_insert_ot_tag_g4, 11)
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||||
|
// Directory: C:\projects\Pikuma\ps1\code\duffle\
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\word_count.metadata.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\dsl.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\memory.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\math.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gcc_asm.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\mips.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gp.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gte.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\pad.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\dsl.atom.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\mips.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gte.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.atom.c
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#pragma region duffle
|
||||||
|
|
||||||
|
|
||||||
|
#pragma endregion duffle
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
# include "dsl.h"
|
||||||
|
# include "gp.h"
|
||||||
|
# include "lottes_tape.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(gp_atom_c);
|
||||||
|
|
||||||
|
#pragma region MACs (Mips Atom Components)
|
||||||
|
|
||||||
|
FI_ Slice_MipsCode ac_gcmd_push(U4 cmd, U4 reg_transfer, U4 reg_base, U2 port)
|
||||||
|
MipsAtomComp_Proc_(ac_gcmd_push, {
|
||||||
|
load_upper_i(reg_transfer, cmd >> 16),
|
||||||
|
or_i_self( reg_transfer, cmd & 0xFFFF),
|
||||||
|
store_word( reg_transfer, reg_base, port),
|
||||||
|
})
|
||||||
|
|
||||||
|
FI_ Slice_MipsCode ac_store_rgb8(U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rgb8, {
|
||||||
|
store_byte(rr, base, offset + O_(RGB8,r)),
|
||||||
|
store_byte(rg, base, offset + O_(RGB8,g)),
|
||||||
|
store_byte(rb, base, offset + O_(RGB8,b)),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given
|
||||||
|
* byte offset. Internal helper used by the *_format_*_color macros. */
|
||||||
|
FI_ Slice_MipsCode ac_pack_color_word(U4 r_base, U4 off, U4 cmd, U1 r, U1 g, U1 b)
|
||||||
|
atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
|
||||||
|
load_upper_i(R_AT, (cmd) << 8 | (b)),
|
||||||
|
or_i_self( R_AT, ((g) << 8) | (r)),
|
||||||
|
store_word( R_AT, r_base, (off)),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 3; Emits the F3 command+color word (cmd byte | BLUE | GREEN | RED)
|
||||||
|
* Args: _r, _g, _b are 8-bit RGB byte values (not raw 16-bit fields). */
|
||||||
|
FI_ Slice_MipsCode ac_format_f3_color(U4 r_base, U1 r, U1 g, U1 b)
|
||||||
|
atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(r_base, O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
|
||||||
|
|
||||||
|
/* Words: 12; Emits the four (code|color) words of a Poly_G4.
|
||||||
|
* Args: rN,gN,bN are 8-bit RGB byte values for each of the 4 vertices. */
|
||||||
|
FI_ Slice_MipsCode ac_format_g4_color(U4 r_prim_cursor,
|
||||||
|
U1 r0, U1 g0, U1 b0,
|
||||||
|
U1 r1, U1 g1, U1 b1,
|
||||||
|
U1 r2, U1 g2, U1 b2,
|
||||||
|
U1 r3, U1 g3, U1 b3)
|
||||||
|
MipsAtomComp_Proc_(ac_format_g4_color, {
|
||||||
|
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0),
|
||||||
|
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c1), 0, r1,g1,b1),
|
||||||
|
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c2), 0, r2,g2,b2),
|
||||||
|
mac_pack_color_word(r_prim_cursor, O_(Poly_G4,c3), 0, r3,g3,b3),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
||||||
|
* Hardcoded for Poly_F3 (5 words). For Poly_G4, use ac_insert_ot_tag_g4. */
|
||||||
|
I_ Slice_MipsCode ac_insert_ot_tag_f3(U4 r_ot_base, U4 r_prim_cursor) MipsAtomComp_Proc_(ac_insert_ot_tag_f3, {
|
||||||
|
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1)
|
||||||
|
add_u_self( R_T1, r_ot_base), // T1 = & OrderingTable[OTZ]
|
||||||
|
load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
|
||||||
|
load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (5 - 1) << 24 = 4 << 24
|
||||||
|
mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24
|
||||||
|
or_u( R_AT, R_AT, R_V0), // Merge length
|
||||||
|
store_word( R_AT, r_prim_cursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr)
|
||||||
|
shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr
|
||||||
|
shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
|
||||||
|
store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
||||||
|
* Hardcoded for Poly_G4 (9 words). For Poly_F3, use ac_insert_ot_tag_f3. */
|
||||||
|
I_ Slice_MipsCode ac_insert_ot_tag_g4(U4 r_ot_base, U4 r_prim_cursor) MipsAtomComp_Proc_(ac_insert_ot_tag_g4, {
|
||||||
|
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1)
|
||||||
|
add_u_self( R_T1, r_ot_base), // T1 = & OrderingTable[OTZ]
|
||||||
|
load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
|
||||||
|
load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (9 - 1) << 24 = 8 << 24
|
||||||
|
mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24
|
||||||
|
or_u( R_AT, R_AT, R_V0), // Merge length
|
||||||
|
store_word( R_AT, r_prim_cursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr)
|
||||||
|
shift_lleft( R_AT, r_prim_cursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr
|
||||||
|
shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
|
||||||
|
store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor
|
||||||
|
})
|
||||||
|
|
||||||
|
#pragma endregion MACs (Mips Atom Components)
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
# include "gte.h"
|
||||||
|
# include "gp.h"
|
||||||
|
# include "lottes_tape.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(gte_atom_c);
|
||||||
|
|
||||||
|
#pragma region MACs (Mips Atom Components)
|
||||||
|
|
||||||
|
/* Words: 3; Loads 3 S2 indices from the face array */
|
||||||
|
FI_ Slice_MipsCode ac_load_tri_indices(U4 r_face_cusor, U4 r_i0, U4 r_i1, U4 r_i2) atom_dbg_skip MipsAtomComp_Proc_(ac_load_tri_indices, {
|
||||||
|
load_half_u(r_i0, r_face_cusor, 0 * S_(S2)),
|
||||||
|
load_half_u(r_i1, r_face_cusor, 1 * S_(S2)),
|
||||||
|
load_half_u(r_i2, r_face_cusor, 2 * S_(S2)),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* 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). */
|
||||||
|
FI_ Slice_MipsCode ac_gte_store_f3(U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_f3, {
|
||||||
|
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_F3,p0)),
|
||||||
|
gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_F3,p1)),
|
||||||
|
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_F3,p2)),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE */
|
||||||
|
I_ Slice_MipsCode ac_gte_load_tri_verts(U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_load_tri_verts, {
|
||||||
|
shift_lleft(R_AT, r_v0, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
|
||||||
|
shift_lleft(R_AT, r_v1, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
|
||||||
|
shift_lleft(R_AT, r_v2, v3s2_byteoff), add_u_self(R_AT, r_vert_base), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
|
||||||
|
* G4 triangle portion to p0/p1/p2.
|
||||||
|
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
||||||
|
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
|
||||||
|
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
|
||||||
|
FI_ Slice_MipsCode ac_gte_store_g4_p012(U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p012, {
|
||||||
|
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_G4,p0)),
|
||||||
|
gte_sw(C2_SXY1, r_primitive_cursor, O_(Poly_G4,p1)),
|
||||||
|
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2)),
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
||||||
|
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
|
||||||
|
* SXY0 still holds v0.screen from the earlier RTPT.
|
||||||
|
*/
|
||||||
|
FI_ Slice_MipsCode ac_gte_store_g4_p3(U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ac_gte_store_g4_p3, { gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p3)) })
|
||||||
|
|
||||||
|
#pragma endregion MACs (Mips Atom Components)
|
||||||
|
|
||||||
|
#pragma region Bsked Atoms
|
||||||
|
|
||||||
|
typedef Struct_(Binds_SetGteWorld) {
|
||||||
|
M3_S2* transform;
|
||||||
|
};
|
||||||
|
internal MipsAtom_(set_gte_world) atom_info(
|
||||||
|
atom_bind(Binds_SetGteWorld)
|
||||||
|
, atom_reads(R_TapePtr)
|
||||||
|
){
|
||||||
|
/* Pop matrix address from tape into R_T3 ($11) */
|
||||||
|
load_word(R_T3, R_TapePtr, O_(Binds_SetGteWorld,transform)),
|
||||||
|
add_ui_self( R_TapePtr, S_(Binds_SetGteWorld)),
|
||||||
|
/* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */
|
||||||
|
load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4),
|
||||||
|
gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12),
|
||||||
|
load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16),
|
||||||
|
gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22),
|
||||||
|
load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28),
|
||||||
|
gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ),
|
||||||
|
mac_yield()
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma endregion Baked Atoms
|
||||||
+42
-171
@@ -1,13 +1,14 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
# pragma once
|
# pragma once
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
|
||||||
# include "dsl.h"
|
# include "dsl.h"
|
||||||
# include "gcc_asm.h"
|
# include "gcc_asm.h"
|
||||||
# include "mips.h"
|
# include "mips.h"
|
||||||
# include "gte.h"
|
# include "gte.h"
|
||||||
# include "memory.h"
|
# include "memory.h"
|
||||||
# include "atom_dsl.h"
|
# include "dsl.atom.h"
|
||||||
# include "gen/duffle.macs.h"
|
|
||||||
# include "gen/duffle.offsets.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef U4 const MipsCode; // Underlying type to mips asm words.
|
typedef U4 const MipsCode; // Underlying type to mips asm words.
|
||||||
@@ -28,7 +29,19 @@ typedef U4 const MipsAtom; // Underlying type to an array of mips asm words that
|
|||||||
// FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); }
|
// FI_ Slice_MipsCode ac_X(args) { MipsCode ac_X[] align_(4) = { body }; return slice_from_array(MipsCode, ac_X); }
|
||||||
#define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); }
|
#define MipsAtomComp_Proc_(sym, ...) { MipsCode sym [] align_(4) = __VA_ARGS__; return slice_from_array(MipsCode, sym); }
|
||||||
|
|
||||||
// Auto-generated component macros (<module>/gen/<dir>/<dir>.macs.h) are included manually by the unity build.
|
/* Line-table anchor: gcc only adds a file to the .debug_line file table when the
|
||||||
|
file contains line-numbered content. Files containing only:
|
||||||
|
- `MipsAtomComp_` static-array declarations, or
|
||||||
|
- `MipsAtomComp_Proc_` (force-inline) function bodies whose line info gets
|
||||||
|
attributed to the call site at the include point are otherwise omitted from the file table,
|
||||||
|
which breaks the DWARF injection when it tries to resolve atom-component provenance paths.
|
||||||
|
|
||||||
|
Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms.
|
||||||
|
The macro expands to a file-scope `internal U4 const` declaration keeps the file in the line table.
|
||||||
|
The constant is in `.rodata` and unreferenced; the linker may eliminate it.
|
||||||
|
The two-level concat + `__LINE__` suffix makes the identifier unique per call site
|
||||||
|
(the identifier embeds the source line, so duplicates across `#include`d files don't collide). */
|
||||||
|
#define ATOM_FILE_DEBUGGER_LINE_MARKER(file_name) internal U4 const tmpl(atom_file_debugger_line_marker,file_name) = 0
|
||||||
|
|
||||||
/* Register aliases */
|
/* Register aliases */
|
||||||
enum {
|
enum {
|
||||||
@@ -59,15 +72,11 @@ enum {
|
|||||||
R_TScratch6 = R_T6,
|
R_TScratch6 = R_T6,
|
||||||
R_TScratch7 = R_T7,
|
R_TScratch7 = R_T7,
|
||||||
R_TScratch8 = R_T8,
|
R_TScratch8 = R_T8,
|
||||||
R_TScratch10 = R_V0,
|
R_TScratch10 = R_V0, // Tend to be used with gte DMAs
|
||||||
R_TScratch11 = R_V1,
|
R_TScratch11 = R_V1, // Tend to be used with gte DMAs
|
||||||
// Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck.
|
// Note(Ed): We can technically clobber these, but don't unless we hit a bottleneck.
|
||||||
// R_TScratch12 = R_A0,
|
// A 0-2
|
||||||
// R_TScratch13 = R_A1,
|
// S 0-7
|
||||||
// R_TScratch14 = R_A3,
|
|
||||||
// TODO(Ed): Review S0-S7, they are technically avaialble, we just have to snapshot them at the ABI boundary.
|
|
||||||
// TODO(Ed): This is technically a waste of cycles for most work? so maybe only do this for expensive atoms on-demand or atom phases.
|
|
||||||
// TODO(Ed): Sort out the other available registers... (Not sure how much is left avail)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma region Tape Drive
|
#pragma region Tape Drive
|
||||||
@@ -79,7 +88,6 @@ typedef Slice_(MipsAtom); typedef Slice_MipsAtom Tape;
|
|||||||
/* The 'Exit' Atom */
|
/* The 'Exit' Atom */
|
||||||
atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(rret_addr), nop };
|
atom_dbg_skip MipsAtom_(tape_exit) { jump_reg(rret_addr), nop };
|
||||||
|
|
||||||
//TODO(Ed): Do we backup R_S0-7 here? Have it in a heavier tape run as a opt-in? Same with V0-1 and A0-3?
|
|
||||||
/* Generalized Tape Engine Runner */
|
/* Generalized Tape Engine Runner */
|
||||||
FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile(
|
FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile(
|
||||||
asm_words(
|
asm_words(
|
||||||
@@ -97,6 +105,26 @@ FI_ void tape_run(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape
|
|||||||
clb_mem_drain
|
clb_mem_drain
|
||||||
); }
|
); }
|
||||||
|
|
||||||
|
/* Fully Clobbered Tape */
|
||||||
|
FI_ void tape_run_a02_s07(Tape tape) { register U4* tape_ptr rgcc(R_TapePtr) = u4_r(tape.ptr); asm volatile(
|
||||||
|
asm_words(
|
||||||
|
load_word( R_AtomJmp, R_TapePtr, 0) /* Bootstrap the first jump */
|
||||||
|
, add_ui_self(R_TapePtr, S_(MipsAtom)) /* Advance tape */
|
||||||
|
, call_reg( R_AtomJmp) /* jalr $t9 */
|
||||||
|
, nop /* Branch delay slot */
|
||||||
|
)
|
||||||
|
asm_rpins, r_use(tape_ptr)
|
||||||
|
asm_clobber:
|
||||||
|
rlit(R_AT),
|
||||||
|
rlit(R_V0), rlit(R_V1), rlit(R_A0), rlit(R_A1), rlit(R_A2),
|
||||||
|
rlit(R_T0), rlit(R_T1), rlit(R_T2), rlit(R_T3), rlit(R_T4),
|
||||||
|
rlit(R_T5), rlit(R_T6), rlit(R_T7), rlit(R_T8),
|
||||||
|
rlit(R_S0), rlit(R_S1), rlit(R_S2), rlit(R_S3), rlit(R_S4),
|
||||||
|
rlit(R_S5), rlit(R_S6), rlit(R_S7),
|
||||||
|
clb_mem_drain
|
||||||
|
); }
|
||||||
|
|
||||||
|
// Procedural authoring of tapes:
|
||||||
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
|
typedef Relative_(FArena) Struct_(TapeBuilder) { U4 ptr; U4 capacity; U4 used; };
|
||||||
FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; }
|
FI_ void tb_init(TapeBuilder* tb, FArena* arena) { tb->ptr = arena->start; tb->used = 0; }
|
||||||
FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
|
FI_ TapeBuilder tb_make_old( FArena* arena) { return (TapeBuilder){ arena->start, 0 }; }
|
||||||
@@ -113,7 +141,6 @@ FI_ Tape tb_slice(TapeBuilder tb) { return (Tape){ C_(U4
|
|||||||
|
|
||||||
FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_slice(tb[0])); }
|
FI_ void tb_scope_run_end(TapeBuilder* tb) { tb_emit(tb,tape_exit); tape_run(tb_slice(tb[0])); }
|
||||||
#define tb_scope_run(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_scope_run_end(tb))
|
#define tb_scope_run(tb) for(U4 tbs_once=0;tbs_once==0;++tbs_once,tb_scope_run_end(tb))
|
||||||
|
|
||||||
#pragma endregion Tape Drive
|
#pragma endregion Tape Drive
|
||||||
|
|
||||||
#pragma region Macro Mips Atom Components
|
#pragma region Macro Mips Atom Components
|
||||||
@@ -143,118 +170,10 @@ atom_dbg_skip MipsAtomComp_(ac_yield_tail) {
|
|||||||
jump_reg( R_AtomJmp), nop,
|
jump_reg( R_AtomJmp), nop,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
|
||||||
R_PrimCursor = R_T7 atom_reg atom_type(U4*), /* VRAM output cursor (primitive buffer) */
|
|
||||||
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2*), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
|
|
||||||
R_VertBase = R_T5 atom_reg atom_type(V3_S2*), /* Base address of the vertex array */
|
|
||||||
R_OtBase = R_T6 atom_reg atom_type(U4*), /* Base address of the Ordering Table */
|
|
||||||
#define R_PrimCursor_Code R_T7_Code
|
|
||||||
#define R_FaceCursor_Code R_T4_Code
|
|
||||||
#define R_VertBase_Code R_T5_Code
|
|
||||||
#define R_OtBase_Code R_T6_Code
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 3; Loads 3 S2 indices from the face array */
|
|
||||||
atom_dbg_skip MipsAtomComp_(ac_load_tri_indices) {
|
|
||||||
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)),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE */
|
|
||||||
atom_dbg_skip MipsAtomComp_(ac_gte_load_tri_verts) {
|
|
||||||
shift_lleft(R_AT, R_T0, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
|
|
||||||
shift_lleft(R_AT, R_T1, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY1), gte_mv_to_data_r(R_V1, C2_VZ1),
|
|
||||||
shift_lleft(R_AT, R_T2, v3s2_byteoff), add_u_self(R_AT, R_VertBase), load_word(R_V0, R_AT, O_(V3_S2,x)), load_word(R_V1, R_AT, O_(V3_S2,z)), gte_mv_to_data_r(R_V0, C2_VXY2), gte_mv_to_data_r(R_V1, C2_VZ2),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
|
||||||
* Hardcoded for Poly_F3 (5 words). For Poly_G4, use ac_insert_ot_tag_g4. */
|
|
||||||
MipsAtomComp_(ac_insert_ot_tag_f3) {
|
|
||||||
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1)
|
|
||||||
add_u_self( R_T1, R_OtBase), // T1 = & OrderingTable[OTZ]
|
|
||||||
load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
|
|
||||||
load_upper_i(R_V0, (S_(Poly_F3)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (5 - 1) << 24 = 4 << 24
|
|
||||||
mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24
|
|
||||||
or_u( R_AT, R_AT, R_V0), // Merge length
|
|
||||||
store_word( R_AT, R_PrimCursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr)
|
|
||||||
shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr
|
|
||||||
shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
|
|
||||||
store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list.
|
|
||||||
* Hardcoded for Poly_G4 (9 words). For Poly_F3, use ac_insert_ot_tag_f3. */
|
|
||||||
MipsAtomComp_(ac_insert_ot_tag_g4) {
|
|
||||||
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1)
|
|
||||||
add_u_self( R_T1, R_OtBase), // T1 = & OrderingTable[OTZ]
|
|
||||||
load_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
|
|
||||||
load_upper_i(R_V0, (S_(Poly_G4)/S_(U4) - S_(PolyTag)/S_(U4)) << PolyTag_len_bits), // V0 = (9 - 1) << 24 = 8 << 24
|
|
||||||
mask_upper( R_AT, R_AT, S_(PolyTag_len_bits)), // Strip upper 8 bits (length from prev cell) → keep only low 24
|
|
||||||
or_u( R_AT, R_AT, R_V0), // Merge length
|
|
||||||
store_word( R_AT, R_PrimCursor, O_(PolyTag,code)), // prim->tag = packed(prim_length, old_addr)
|
|
||||||
shift_lleft( R_AT, R_PrimCursor, S_(PolyTag_len_bits)), // AT = (prim_length << 24) | old_addr
|
|
||||||
shift_lright(R_AT, R_AT, S_(PolyTag_len_bits)),
|
|
||||||
store_word( R_AT, R_T1, O_(PolyTag,code)), // OrderingTable[OTZ] = PrimCursor
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 3; Emits one (cmd|color) word to R_PrimCursor at the given
|
|
||||||
* byte offset. Internal helper used by the *_format_*_color macros. */
|
|
||||||
FI_ Slice_MipsCode ac_pack_color_word(U4 off, U4 cmd, U1 r, U1 g, U1 b)
|
|
||||||
atom_dbg_skip MipsAtomComp_Proc_(ac_pack_color_word, {
|
|
||||||
load_upper_i(R_AT, (cmd) << 8 | (b)),
|
|
||||||
or_i_self( R_AT, ((g) << 8) | (r)),
|
|
||||||
store_word( R_AT, R_PrimCursor, (off)),
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Words: 3; Emits the F3 command+color word (cmd byte | BLUE | GREEN | RED)
|
|
||||||
* Args: _r, _g, _b are 8-bit RGB byte values (not raw 16-bit fields). */
|
|
||||||
FI_ Slice_MipsCode ac_format_f3_color(U1 r, U1 g, U1 b)
|
|
||||||
atom_dbg_skip MipsAtomComp_Proc_(ac_format_f3_color, { mac_pack_color_word(O_(Poly_F3,color), gp0_cmd_poly_f3, r, g, b) })
|
|
||||||
|
|
||||||
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices to the F3.
|
|
||||||
* PIPELINE: post-RTPT (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). */
|
|
||||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_f3) {
|
|
||||||
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_F3,p0)),
|
|
||||||
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_F3,p1)),
|
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_F3,p2)),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 12; Emits the four (code|color) words of a Poly_G4.
|
|
||||||
* Args: rN,gN,bN are 8-bit RGB byte values for each of the 4 vertices. */
|
|
||||||
FI_ Slice_MipsCode ac_format_g4_color(
|
|
||||||
U1 r0, U1 g0, U1 b0,
|
|
||||||
U1 r1, U1 g1, U1 b1,
|
|
||||||
U1 r2, U1 g2, U1 b2,
|
|
||||||
U1 r3, U1 g3, U1 b3)
|
|
||||||
MipsAtomComp_Proc_(ac_format_g4_color, {
|
|
||||||
mac_pack_color_word(O_(Poly_G4,c0), gp0_cmd_poly_g4, r0,g0,b0),
|
|
||||||
mac_pack_color_word(O_(Poly_G4,c1), 0, r1,g1,b1),
|
|
||||||
mac_pack_color_word(O_(Poly_G4,c2), 0, r2,g2,b2),
|
|
||||||
mac_pack_color_word(O_(Poly_G4,c3), 0, r3,g3,b3),
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
|
|
||||||
* G4 triangle portion to p0/p1/p2.
|
|
||||||
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
|
|
||||||
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3
|
|
||||||
* (RTPS writes only to SXY2, but to keep the three registers aligned with v0/v1/v2 you must store before RTPS). */
|
|
||||||
atom_dbg_skip MipsAtomComp_(ac_gte_store_g4_p012) {
|
|
||||||
gte_sw(C2_SXY0, R_PrimCursor, O_(Poly_G4,p0)),
|
|
||||||
gte_sw(C2_SXY1, R_PrimCursor, O_(Poly_G4,p1)),
|
|
||||||
gte_sw(C2_SXY2, R_PrimCursor, O_(Poly_G4,p2)),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Words: 1; Stores the V3 screen coord to the G4's p3 slot.
|
|
||||||
* PIPELINE: post-RTPS (SXY2 holds v3.screen because RTPS writes its single-vertex result to SXY2;
|
|
||||||
* SXY0 still holds v0.screen from the earlier RTPT.
|
|
||||||
*/
|
|
||||||
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
|
||||||
|
|
||||||
#pragma region Mips Atom Builder
|
#pragma region Mips Atom Builder
|
||||||
// This allows for runtime procedural authoring of mips atoms.
|
// This helps with runtime procedural authoring of mips atoms.
|
||||||
|
|
||||||
typedef Struct_(FMipsAtom512) { U4 data[512]; U4 used; };
|
typedef Struct_(FMipsAtom512) { U4 data[512]; U4 used; };
|
||||||
|
|
||||||
@@ -277,57 +196,9 @@ FI_ void atombuilder_end(MipsAtomBuilder_R ab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define mipsatom_from_builder(ab) (Slice_MipsCode){ab.start, ab.used}
|
#define mipsatom_from_builder(ab) (Slice_MipsCode){ab.start, ab.used}
|
||||||
|
|
||||||
#pragma endregion Mips Atom Builder
|
#pragma endregion Mips Atom Builder
|
||||||
|
|
||||||
#pragma region Baked Mips Atoms
|
#pragma region Baked Mips Atoms
|
||||||
// These atoms are resolved at compile time and are (usually) statically linked readonly data.
|
// These atoms are resolved at compile time and are (usually) statically linked readonly data.
|
||||||
|
|
||||||
enum {
|
|
||||||
bios_flushcache = 0x44,
|
|
||||||
bios_table_addr = 0xA0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Flushes the Instruction Cache (PSX A-function 0x44 via BIOS stub at 0xA0).
|
|
||||||
* Sequence (per MIPS ABI; arguments in arg registers, RA pushed to stack):
|
|
||||||
* 1. sp -= 8; sw $ra, 4($sp) ; save RA
|
|
||||||
* 2. $a0 = bios_flushcache (arg0)
|
|
||||||
* 3. $t0 = bios_table_addr ; t0 = &BIOS A-function table
|
|
||||||
* 4. jalr $t0, $ra ; call BIOS(flushcache)
|
|
||||||
* nop ; branch delay slot
|
|
||||||
* 5. lw $ra, 4($sp); jr $ra ; restore & return
|
|
||||||
* 6. sp += 8
|
|
||||||
*/
|
|
||||||
internal MipsAtom_(mips_flush_icache) {
|
|
||||||
add_ui(rstack_ptr, rstack_ptr, -MipsStackAlignment), // sp -= 8
|
|
||||||
store_word(rret_addr, rstack_ptr, S_(U4)), // sw $ra, 4($sp)
|
|
||||||
add_ui(rret_0, rdiscard, bios_flushcache), // addiu $a0, $0, 0x44
|
|
||||||
add_ui(rtmp_0, rdiscard, bios_table_addr), // addiu $t0, $0, 0xA0
|
|
||||||
jump_link(rtmp_0, rret_addr), nop, // jalr $t0, $ra, BD slot
|
|
||||||
load_word(rret_addr, rstack_ptr, S_(U4)), // lw $ra, 4($sp)
|
|
||||||
jump_reg(rret_addr), // jr $ra
|
|
||||||
add_ui(rstack_ptr, rstack_ptr, MipsStackAlignment), // sp += 8 (BD)
|
|
||||||
mac_yield(),
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef Struct_(Binds_SetGteWorld) {
|
|
||||||
M3_S2* transform;
|
|
||||||
};
|
|
||||||
internal MipsAtom_(set_gte_world) atom_info(
|
|
||||||
atom_bind(Binds_SetGteWorld)
|
|
||||||
, atom_reads(R_TapePtr)
|
|
||||||
){
|
|
||||||
/* Pop matrix address from tape into R_T3 ($11) */
|
|
||||||
load_word(R_T3, R_TapePtr, O_(Binds_SetGteWorld,transform)),
|
|
||||||
add_ui_self( R_TapePtr, S_(Binds_SetGteWorld)),
|
|
||||||
/* Load 3x3 Rotation + 3x1 Translation from R_T3 into GTE CONTROL Regs (ctc2) */
|
|
||||||
load_word(R_T0, R_T3, 0), load_word(R_T1, R_T3, 4),
|
|
||||||
gte_mv_to_ctrl_r(R_T0, gte_cr_RT11), gte_mv_to_ctrl_r(R_T1, gte_cr_RT12),
|
|
||||||
load_word(R_T0, R_T3, 8), load_word(R_T1, R_T3, 12), load_word(R_T2, R_T3, 16),
|
|
||||||
gte_mv_to_ctrl_r(R_T0, gte_cr_RT13), gte_mv_to_ctrl_r(R_T1, gte_cr_RT21), gte_mv_to_ctrl_r(R_T2, gte_cr_RT22),
|
|
||||||
load_word(R_T0, R_T3, 20), load_word(R_T1, R_T3, 24), load_word(R_T2, R_T3, 28),
|
|
||||||
gte_mv_to_ctrl_r(R_T0, gte_cr_TRX), gte_mv_to_ctrl_r(R_T1, gte_cr_TRY), gte_mv_to_ctrl_r(R_T2, gte_cr_TRZ),
|
|
||||||
mac_yield()
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma endregion Baked Mips Atoms
|
#pragma endregion Baked Mips Atoms
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
# include "math.h"
|
||||||
|
# include "lottes_tape.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(math_atom_c);
|
||||||
|
|
||||||
|
#pragma region MACs (Mips Atom Component)
|
||||||
|
|
||||||
|
FI_ Slice_MipsCode ac_load_v2s2(U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_load_v2s2, {
|
||||||
|
load_half( rs_x, r_base, O_(V3_S2,x)),
|
||||||
|
load_half( rs_y, r_base, O_(V3_S2,y)),
|
||||||
|
})
|
||||||
|
|
||||||
|
FI_ Slice_MipsCode ac_store_v2s2(U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v2s2, {
|
||||||
|
store_half(rt_x, base, offset + O_(V2_S2,x)),
|
||||||
|
store_half(rt_y, base, offset + O_(V2_S2,y)),
|
||||||
|
})
|
||||||
|
|
||||||
|
FI_ Slice_MipsCode ac_store_rects2(U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rects2, {
|
||||||
|
store_half(rt_x, base, offset + O_(Rect_S2,x)),
|
||||||
|
store_half(rt_y, base, offset + O_(Rect_S2,y)),
|
||||||
|
store_half(rt_width, base, offset + O_(Rect_S2,width)),
|
||||||
|
store_half(rt_height, base, offset + O_(Rect_S2,height)),
|
||||||
|
})
|
||||||
|
|
||||||
|
#pragma endregion MACs (Mips Atom Component)
|
||||||
+5
-10
@@ -34,10 +34,10 @@ typedef Struct_(V4_S4) { S4 x; S4 y; S4 z; S4 w; };
|
|||||||
typedef Struct_(R2_S2) { V2_S2 p0; V2_S2 p1; };
|
typedef Struct_(R2_S2) { V2_S2 p0; V2_S2 p1; };
|
||||||
typedef Struct_(R2_S4) { V2_S4 p0; V2_S4 p1; };
|
typedef Struct_(R2_S4) { V2_S4 p0; V2_S4 p1; };
|
||||||
|
|
||||||
typedef Struct_(Rect_S2) { S2 x; S2 y; S2 width; S2 height; };
|
typedef Struct_(Rect_S2) { S2 x; S2 y; S2 width; S2 height; };
|
||||||
typedef Struct_(Rect_S4) { S4 x; S4 y; S4 width; S4 height; };
|
typedef Struct_(Rect_S4) { S4 x; S4 y; S4 width; S4 height; };
|
||||||
|
|
||||||
typedef Struct_(M3_S2) { A3x3_S2 m; A3_S4 t; };
|
typedef Struct_(M3_S2) { A3x3_S2 m; A3_S4 t; };
|
||||||
|
|
||||||
typedef Array_(V2_S2, 2);
|
typedef Array_(V2_S2, 2);
|
||||||
typedef Array_(V2_S2, 3);
|
typedef Array_(V2_S2, 3);
|
||||||
@@ -61,10 +61,5 @@ FI_ void add_a3s4_fp(A3_S4_R out_a, A3_S4 b) {
|
|||||||
(out_a[0])[2] += b[2] >> 1;
|
(out_a[0])[2] += b[2] >> 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FI_ void add_v3s4(V3_S4_R out_a, V3_S4 b) {
|
FI_ void add_v3s4 (V3_S4_R out_a, V3_S4 b) { add_a3s4 (pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
|
||||||
add_a3s4(pcast(A3_S4_R, out_a), pcast(A3_S4, b));
|
FI_ void add_v3s4_fp(V3_S4_R out_a, V3_S4 b) { add_a3s4_fp(pcast(A3_S4_R, out_a), pcast(A3_S4, b)); }
|
||||||
}
|
|
||||||
|
|
||||||
FI_ void add_v3s4_fp(V3_S4_R out_a, V3_S4 b) {
|
|
||||||
add_a3s4_fp(pcast(A3_S4_R, out_a), pcast(A3_S4, b));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
# include "lottes_tape.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(mips_atom_c);
|
||||||
|
|
||||||
|
#pragma region Baked Atoms
|
||||||
|
|
||||||
|
enum {
|
||||||
|
bios_flushcache = 0x44,
|
||||||
|
bios_table_addr = 0xA0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Flushes the Instruction Cache (PSX A-function 0x44 via BIOS stub at 0xA0).
|
||||||
|
* Sequence (per MIPS ABI; arguments in arg registers, RA pushed to stack):
|
||||||
|
* 1. sp -= 8; sw $ra, 4($sp) ; save RA
|
||||||
|
* 2. $a0 = bios_flushcache (arg0)
|
||||||
|
* 3. $t0 = bios_table_addr ; t0 = &BIOS A-function table
|
||||||
|
* 4. jalr $t0, $ra ; call BIOS(flushcache)
|
||||||
|
* nop ; branch delay slot
|
||||||
|
* 5. lw $ra, 4($sp); jr $ra ; restore & return
|
||||||
|
* 6. sp += 8
|
||||||
|
*/
|
||||||
|
internal MipsAtom_(mips_flush_icache) {
|
||||||
|
add_ui(rstack_ptr, rstack_ptr, -MipsStackAlignment), // sp -= 8
|
||||||
|
store_word(rret_addr, rstack_ptr, S_(U4)), // sw $ra, 4($sp)
|
||||||
|
add_ui(rret_0, rdiscard, bios_flushcache), // addiu $a0, $0, 0x44
|
||||||
|
add_ui(rtmp_0, rdiscard, bios_table_addr), // addiu $t0, $0, 0xA0
|
||||||
|
jump_link(rtmp_0, rret_addr), nop, // jalr $t0, $ra, BD slot
|
||||||
|
load_word(rret_addr, rstack_ptr, S_(U4)), // lw $ra, 4($sp)
|
||||||
|
jump_reg(rret_addr), // jr $ra
|
||||||
|
add_ui(rstack_ptr, rstack_ptr, MipsStackAlignment), // sp += 8 (BD)
|
||||||
|
mac_yield(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma endregion Baked Atoms
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#ifdef INTELLISSENSE_DIRECTIVES
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(pad_atom_c);
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
# include "psyq.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(pysq_atom_c);
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
# pragma once
|
# pragma once
|
||||||
# include "duffle/dsl.h"
|
# include "dsl.h"
|
||||||
# include "duffle/math.h"
|
# include "math.h"
|
||||||
# include "duffle/gp.h"
|
# include "gp.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; };
|
typedef Struct_(DrawEnv_Packed) { U4 tag; U4 code[15]; };
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// One line per macro that appears in your atom sources.
|
// One line per macro that appears in your atom sources.
|
||||||
//
|
//
|
||||||
// This file is encoding-macros-only.
|
// This file is encoding-macros-only.
|
||||||
// The auto-generated component macros (mac_X) live in duffle/gen/<dir>.macs.h (included separately by the unity build).
|
// The auto-generated component macros (mac_X) live in the source directory's own gen/macs.h (per-directory aggregation; included separately by the unity build).
|
||||||
// The unity build should include THIS file and the .macs.h file in the same TU, with both wrapped
|
// The unity build should include THIS file and the .macs.h file in the same TU, with both wrapped
|
||||||
// (or the include guard order handled) to avoid WORD_COUNT redeclaration.
|
// (or the include guard order handled) to avoid WORD_COUNT redeclaration.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
#include "duffle/gp.h"
|
#include "duffle/gp.h"
|
||||||
#include "duffle/gte.h"
|
#include "duffle/gte.h"
|
||||||
|
|
||||||
# include "duffle/gen/duffle.macs.h"
|
# include "duffle/gen/macs.h"
|
||||||
# include "duffle/gen/duffle.offsets.h"
|
# include "duffle/gen/offsets.h"
|
||||||
#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/hello_gte.offsets.h"
|
# include "gen/offsets.h"
|
||||||
#include "hello_gte.h"
|
#include "hello_gte.h"
|
||||||
|
|
||||||
#include "hello_gte.tape.c"
|
#include "hello_gte.tape.c"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
# include "duffle/gen/duffle.macs.h"
|
# include "duffle/gen/macs.h"
|
||||||
# include "duffle/gen/duffle.offsets.h"
|
# include "duffle/gen/offsets.h"
|
||||||
# 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/hello_gte.offsets.h"
|
# include "gen/offsets.h"
|
||||||
# include "hello_gte.h"
|
# include "hello_gte.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -2,53 +2,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#endif
|
#endif
|
||||||
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
// Directory: C:\projects\Pikuma\ps1\code\hello_joypad/
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.atom.c
|
||||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||||
|
|
||||||
#ifndef WORD_COUNT
|
#ifndef WORD_COUNT
|
||||||
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
#define WORD_COUNT(name, count) enum { words_##name = (count) };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_load_v2s2(rs_x, rs_y, r_base, offset) \
|
|
||||||
load_half( rs_x, r_base, O_(V3_S2,x)) \
|
|
||||||
, load_half( rs_y, r_base, O_(V3_S2,y))
|
|
||||||
WORD_COUNT(mac_load_v2s2, 2)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_store_v2s2(rt_x, rt_y, base, offset) \
|
|
||||||
store_half(rt_x, base, offset + O_(V2_S2,x)) \
|
|
||||||
, store_half(rt_y, base, offset + O_(V2_S2,y))
|
|
||||||
WORD_COUNT(mac_store_v2s2, 2)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_store_rects2(rt_x, rt_y, rt_width, rt_height, base, offset) \
|
|
||||||
store_half(rt_x, base, offset + O_(Rect_S2,x)) \
|
|
||||||
, store_half(rt_y, base, offset + O_(Rect_S2,y)) \
|
|
||||||
, store_half(rt_width, base, offset + O_(Rect_S2,width)) \
|
|
||||||
, store_half(rt_height, base, offset + O_(Rect_S2,height))
|
|
||||||
WORD_COUNT(mac_store_rects2, 4)
|
|
||||||
|
|
||||||
/* atom_dbg_skip */
|
|
||||||
#define mac_store_rgb8(rr, rg, rb, base, offset) \
|
|
||||||
store_byte(rr, base, offset + O_(DrawEnv,initial_bg_color.r)) \
|
|
||||||
, store_byte(rg, base, offset + O_(DrawEnv,initial_bg_color.g)) \
|
|
||||||
, store_byte(rb, base, offset + O_(DrawEnv,initial_bg_color.b))
|
|
||||||
WORD_COUNT(mac_store_rgb8, 3)
|
|
||||||
|
|
||||||
#define mac_gcmd_push(cmd, reg_transfer, reg_base, port) \
|
|
||||||
load_upper_i(reg_transfer, cmd >> 16) \
|
|
||||||
, or_i_self( reg_transfer, cmd & 0xFFFF) \
|
|
||||||
, store_word( reg_transfer, reg_base, port)
|
|
||||||
WORD_COUNT(mac_gcmd_push, 3)
|
|
||||||
|
|
||||||
#define mac_put_disp_env(reg_transfer, reg_base, port) \
|
#define mac_put_disp_env(reg_transfer, reg_base, port) \
|
||||||
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
||||||
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) \
|
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port) \
|
||||||
, mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port) \
|
, mac_gcmd_push(gp0_word_set_mask_bit(), reg_transfer, reg_base, port) \
|
||||||
, mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
, mac_gcmd_push(gp0_word_draw_area_top_left_origin, reg_transfer, reg_base, port) \
|
||||||
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port)
|
, mac_gcmd_push(gp0_word_draw_area_bottom_right_320x240, reg_transfer, reg_base, port)
|
||||||
WORD_COUNT(mac_put_disp_env, 15)
|
WORD_COUNT(mac_put_disp_env, 5)
|
||||||
|
|
||||||
#define mac_put_draw_env(reg_transfer, reg_base, port) \
|
#define mac_put_draw_env(reg_transfer, reg_base, port) \
|
||||||
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port) /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */ \
|
mac_gcmd_push(gp0_dr_env_tag, reg_transfer, reg_base, port) /* tag (length=15 << 24, addr=0) — packet header for the DR_ENV sequence. The GPU needs this to recognize the next 15 words as a DR_ENV packet and trigger the isbg auto-clear. */ \
|
||||||
@@ -67,5 +37,5 @@ WORD_COUNT(mac_put_disp_env, 15)
|
|||||||
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[13..14] Padding (NOP) — completes the 16-word packet. */ \
|
, mac_gcmd_push(gp0_word_set_texture_window(), reg_transfer, reg_base, port) /* code[13..14] Padding (NOP) — completes the 16-word packet. */ \
|
||||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
|
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) \
|
||||||
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port)
|
, mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port)
|
||||||
WORD_COUNT(mac_put_draw_env, 48)
|
WORD_COUNT(mac_put_draw_env, 16)
|
||||||
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
// Directory: C:\projects\Pikuma\ps1\code\hello_joypad\
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.c
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.h
|
||||||
|
// source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.atom.c
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#pragma region hello_joypad.tape
|
#pragma region hello_joypad
|
||||||
|
|
||||||
|
|
||||||
// --- atom: cube_g4_face (76 words) ---
|
// --- atom: cube_g4_face (76 words) ---
|
||||||
@@ -69,5 +72,5 @@ enum {
|
|||||||
atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick,
|
atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick,
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma endregion hello_joypad.tape
|
#pragma endregion hello_joypad
|
||||||
|
|
||||||
@@ -1,51 +1,29 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
# include "duffle/gen/duffle.macs.h"
|
# pragma once
|
||||||
# include "duffle/gen/duffle.offsets.h"
|
# include "duffle/gen/macs.h"
|
||||||
# include "duffle/atom_dsl.h"
|
# include "duffle/gen/offsets.h"
|
||||||
|
# include "duffle/dsl.atom.h"
|
||||||
# include "duffle/lottes_tape.h"
|
# include "duffle/lottes_tape.h"
|
||||||
# include "duffle/mips.h"
|
# include "duffle/mips.h"
|
||||||
# include "duffle/gte.h"
|
# include "duffle/gte.h"
|
||||||
# include "duffle/gp.h"
|
# include "duffle/gp.h"
|
||||||
# include "duffle/pad.h"
|
# include "duffle/pad.h"
|
||||||
# include "duffle/word_count.metadata.h"
|
# include "duffle/word_count.metadata.h"
|
||||||
# include "psyq.h"
|
# include "duffle/psyq.h"
|
||||||
# include "gen/hello_joypad.offsets.h"
|
# include "duffle/math.atom.c"
|
||||||
# include "gen/hello_joypad.macs.h"
|
# include "duffle/mips.atom.c"
|
||||||
|
# include "duffle/gte.atom.c"
|
||||||
|
# include "duffle/gp.atom.c"
|
||||||
|
# include "duffle/psyq.atom.c"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
# include "gen/macs.h"
|
||||||
# include "hello_joypad.h"
|
# include "hello_joypad.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ATOM_FILE_DEBUGGER_LINE_MARKER(hello_joypad_atom_c);
|
||||||
|
|
||||||
#pragma region MACs (Mips Atom components)
|
#pragma region MACs (Mips Atom components)
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_load_v2s2(U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_load_v2s2, {
|
|
||||||
load_half( rs_x, r_base, O_(V3_S2,x)),
|
|
||||||
load_half( rs_y, r_base, O_(V3_S2,y)),
|
|
||||||
})
|
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_store_v2s2(U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_v2s2, {
|
|
||||||
store_half(rt_x, base, offset + O_(V2_S2,x)),
|
|
||||||
store_half(rt_y, base, offset + O_(V2_S2,y)),
|
|
||||||
})
|
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_store_rects2(U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rects2, {
|
|
||||||
store_half(rt_x, base, offset + O_(Rect_S2,x)),
|
|
||||||
store_half(rt_y, base, offset + O_(Rect_S2,y)),
|
|
||||||
store_half(rt_width, base, offset + O_(Rect_S2,width)),
|
|
||||||
store_half(rt_height, base, offset + O_(Rect_S2,height)),
|
|
||||||
})
|
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_store_rgb8(U1 rr, U1 rg, U1 rb, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ac_store_rgb8, {
|
|
||||||
store_byte(rr, base, offset + O_(DrawEnv,initial_bg_color.r)),
|
|
||||||
store_byte(rg, base, offset + O_(DrawEnv,initial_bg_color.g)),
|
|
||||||
store_byte(rb, base, offset + O_(DrawEnv,initial_bg_color.b)),
|
|
||||||
})
|
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_gcmd_push(U4 cmd, U4 reg_transfer, U4 reg_base, U2 port)
|
|
||||||
MipsAtomComp_Proc_(ac_gcmd_push, {
|
|
||||||
load_upper_i(reg_transfer, cmd >> 16),
|
|
||||||
or_i_self( reg_transfer, cmd & 0xFFFF),
|
|
||||||
store_word( reg_transfer, reg_base, port),
|
|
||||||
})
|
|
||||||
|
|
||||||
FI_ Slice_MipsCode ac_put_disp_env(U4 reg_transfer, U4 reg_base, U2 port)
|
FI_ Slice_MipsCode ac_put_disp_env(U4 reg_transfer, U4 reg_base, U2 port)
|
||||||
MipsAtomComp_Proc_(ac_put_disp_env, {
|
MipsAtomComp_Proc_(ac_put_disp_env, {
|
||||||
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
|
// Emits 5 GP0 commands for buffer 0 (display_area = (0,0,320,240)).
|
||||||
@@ -202,6 +180,17 @@ internal MipsAtom_(gp_screen_init) atom_info(atom_phase(screen_init), atom_reads
|
|||||||
mac_yield(),
|
mac_yield(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
R_PrimCursor = R_T7 atom_reg atom_type(U4*), /* VRAM output cursor (primitive buffer) */
|
||||||
|
R_FaceCursor = R_T4 atom_reg atom_type(V4_S2*), /* Cube face-index cursor (V4_S2*); floor context switches to V3_S2* via atom_phase */
|
||||||
|
R_VertBase = R_T5 atom_reg atom_type(V3_S2*), /* Base address of the vertex array */
|
||||||
|
R_OtBase = R_T6 atom_reg atom_type(U4*), /* Base address of the Ordering Table */
|
||||||
|
#define R_PrimCursor_Code R_T7_Code
|
||||||
|
#define R_FaceCursor_Code R_T4_Code
|
||||||
|
#define R_VertBase_Code R_T5_Code
|
||||||
|
#define R_OtBase_Code R_T6_Code
|
||||||
|
};
|
||||||
|
|
||||||
typedef Struct_(Binds_CubeTri) {
|
typedef Struct_(Binds_CubeTri) {
|
||||||
U4 PrimCursor;
|
U4 PrimCursor;
|
||||||
V4_S2* FaceCursor;
|
V4_S2* FaceCursor;
|
||||||
@@ -232,23 +221,23 @@ MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4),
|
|||||||
load_half_u(R_T2, R_FaceCursor, 2 * S_(S2)),
|
load_half_u(R_T2, R_FaceCursor, 2 * S_(S2)),
|
||||||
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_VertBase, R_T0, R_T1, R_T2),
|
||||||
nop2, gte_cmdw_rotate_translate_perspective_triple, // required cpu -> gte delay slot
|
nop2, gte_cmdw_rotate_translate_perspective_triple, // required cpu -> gte delay slot
|
||||||
gte_cmdw_nclip,
|
gte_cmdw_nclip,
|
||||||
|
|
||||||
gte_mv_from_data_r(R_T0, C2_MAC0), nop,
|
gte_mv_from_data_r(R_T0, C2_MAC0), nop,
|
||||||
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)),
|
branch_le_zero(R_T0, atom_offset(cull, cube_g4_face_exit)),
|
||||||
/* BD-slot: write the prim tag (R_0=0; overwrites the legacy tag word in the prim_buffer).
|
/* BD-slot: write the prim tag (R_0=0; overwrites the legacy tag word in the prim_buffer).
|
||||||
* If branch IS taken (face culled), the body is skipped and this 0-tag is stranded — harmless
|
* If branch IS taken (face culled), the body is skipped and this 0-tag is stranded —
|
||||||
* because the OT entry that points to this prim is created later, only on the body path. */
|
* harmless because the OT entry that points to this prim is created later, only on the body path. */
|
||||||
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),
|
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)),
|
load_word(R_V0, R_AT, O_(V3_S2, x)), load_word(R_V1, R_AT, O_(V3_S2, z)),
|
||||||
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
|
gte_mv_to_data_r(R_V0, C2_VXY0), gte_mv_to_data_r(R_V1, C2_VZ0),
|
||||||
|
|
||||||
mac_gte_store_g4_p012(),
|
mac_gte_store_g4_p012(R_PrimCursor),
|
||||||
gte_cmdw_rotate_translate_perspective_single,
|
gte_cmdw_rotate_translate_perspective_single,
|
||||||
mac_gte_store_g4_p3(),
|
mac_gte_store_g4_p3(R_PrimCursor),
|
||||||
|
|
||||||
gte_cmdw_avg_sort_z4,
|
gte_cmdw_avg_sort_z4,
|
||||||
gte_mv_from_data_r(R_T1, C2_OTZ),
|
gte_mv_from_data_r(R_T1, C2_OTZ),
|
||||||
@@ -256,8 +245,8 @@ MipsAtom_(cube_g4_face) atom_info(atom_phase(cube_g4),
|
|||||||
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, cube_g4_face_exit)), nop,
|
branch_equal(R_AT, R_0, atom_offset(bounds_chk, cube_g4_face_exit)), nop,
|
||||||
mac_insert_ot_tag_g4(),
|
mac_insert_ot_tag_g4(R_OtBase, R_PrimCursor),
|
||||||
mac_format_g4_color(
|
mac_format_g4_color(R_PrimCursor,
|
||||||
/* 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,
|
||||||
@@ -297,8 +286,8 @@ 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_FaceCursor)
|
, atom_writes(R_PrimCursor, R_FaceCursor)
|
||||||
) {
|
) {
|
||||||
mac_load_tri_indices( R_T0, R_T1, R_T2),
|
mac_load_tri_indices( R_FaceCursor, R_T0, R_T1, R_T2),
|
||||||
mac_gte_load_tri_verts(R_T0, R_T1, R_T2),
|
mac_gte_load_tri_verts(R_VertBase, R_T0, R_T1, R_T2),
|
||||||
nop2, gte_cmdw_rotate_translate_perspective_triple, // 2 nops retire the final cpu -> gte writes before RTPT
|
nop2, gte_cmdw_rotate_translate_perspective_triple, // 2 nops retire the final cpu -> gte writes before RTPT
|
||||||
gte_cmdw_nclip,
|
gte_cmdw_nclip,
|
||||||
|
|
||||||
@@ -306,7 +295,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_gte_store_f3(),
|
mac_gte_store_f3(R_PrimCursor),
|
||||||
|
|
||||||
/* Calculate Depth */
|
/* Calculate Depth */
|
||||||
gte_avg_sort_z3,
|
gte_avg_sort_z3,
|
||||||
@@ -315,8 +304,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,
|
||||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
mac_format_f3_color(R_PrimCursor, 0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||||
mac_insert_ot_tag_f3(), /* Insert into Ordering Table Linked List */
|
mac_insert_ot_tag_f3(R_OtBase, R_PrimCursor), /* 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.
|
||||||
// end: branch(bounds_chk)
|
// end: branch(bounds_chk)
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
|
#pragma region Vendors
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
// #include "libgpu.h"
|
// #include "libgpu.h"
|
||||||
// #include "libetc.h"
|
// #include "libetc.h"
|
||||||
// #include "libgte.h"
|
// #include "libgte.h"
|
||||||
|
#pragma endregion Vendors
|
||||||
|
|
||||||
|
#pragma region Duffle Headers
|
||||||
|
# include "duffle/gen/macs.h"
|
||||||
|
# include "duffle/gen/offsets.h"
|
||||||
|
|
||||||
|
#include "duffle/word_count.metadata.h"
|
||||||
|
|
||||||
#include "duffle/dsl.h"
|
#include "duffle/dsl.h"
|
||||||
#include "duffle/memory.h"
|
#include "duffle/memory.h"
|
||||||
@@ -15,96 +23,30 @@
|
|||||||
#include "duffle/gte.h"
|
#include "duffle/gte.h"
|
||||||
#include "duffle/pad.h"
|
#include "duffle/pad.h"
|
||||||
|
|
||||||
# include "duffle/gen/duffle.macs.h"
|
#include "duffle/dsl.atom.h"
|
||||||
# include "duffle/gen/duffle.offsets.h"
|
|
||||||
#include "duffle/atom_dsl.h"
|
|
||||||
#include "duffle/lottes_tape.h"
|
#include "duffle/lottes_tape.h"
|
||||||
#include "duffle/word_count.metadata.h"
|
|
||||||
|
|
||||||
#include "psyq.h"
|
#include "duffle/psyq.h"
|
||||||
|
#pragma endregion Duffle Headers
|
||||||
|
|
||||||
|
#pragma region Duffle TUs
|
||||||
|
#include "duffle/math.atom.c"
|
||||||
|
#include "duffle/mips.atom.c"
|
||||||
|
#include "duffle/gte.atom.c"
|
||||||
|
#include "duffle/gp.atom.c"
|
||||||
|
#include "duffle/psyq.atom.c"
|
||||||
|
#pragma endregion Duffle TUs
|
||||||
|
|
||||||
|
#pragma region Joypade Headers
|
||||||
|
# include "gen/macs.h"
|
||||||
|
# include "gen/offsets.h"
|
||||||
|
|
||||||
# include "gen/hello_joypad.macs.h"
|
|
||||||
# include "gen/hello_joypad.offsets.h"
|
|
||||||
#include "hello_joypad.h"
|
#include "hello_joypad.h"
|
||||||
|
#pragma region Joypad Headers
|
||||||
|
|
||||||
#include "psyq.c"
|
#pragma region Hello Joypad TUs
|
||||||
#include "hello_joypad.tape.c"
|
#include "hello_joypad.atom.c"
|
||||||
|
#pragma endregion Hello Joypad TUs
|
||||||
|
|
||||||
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 {
|
enum {
|
||||||
Scratchpad_Len = 1024,
|
Scratchpad_Len = 1024,
|
||||||
@@ -212,50 +154,6 @@ NI_ void pad_bios_init_start(PadBiosRaw* raw0, PadBiosRaw* raw1)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
GCC_OPTIMIZATION_DISABLE
|
GCC_OPTIMIZATION_DISABLE
|
||||||
void update(PrimitiveArena* pa, U4* ordering_buf)
|
void update(PrimitiveArena* pa, U4* ordering_buf)
|
||||||
{
|
{
|
||||||
@@ -484,6 +382,19 @@ GCC_OPTIMIZATION_ENABLE
|
|||||||
void render(void) {
|
void render(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
GCC_OPTIMIZATION_DISABLE
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
smem = (SMemory){0};
|
smem = (SMemory){0};
|
||||||
@@ -527,3 +438,4 @@ int main(void)
|
|||||||
};
|
};
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
GCC_OPTIMIZATION_ENABLE
|
||||||
|
|||||||
@@ -26,3 +26,77 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#define v3s4_fp_one() v3s4(fp_one, fp_one, fp_one)
|
#define v3s4_fp_one() v3s4(fp_one, fp_one, fp_one)
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
|
||||||
# include "psyq.h"
|
|
||||||
#endif
|
|
||||||
@@ -6,10 +6,9 @@
|
|||||||
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations,
|
--- Reads the pre-scanned SourceScan payload from `duffle.scan_source` for `MipsAtomComp_(ac_X)` and `MipsAtomComp_Proc_(ac_X, { body })` declarations,
|
||||||
--- then resolves the function-args string from the preceding `FI_ Slice_MipsCode ac_X(...)` declaration via a backward walk.
|
--- then resolves the function-args string from the preceding `FI_ Slice_MipsCode ac_X(...)` declaration via a backward walk.
|
||||||
---
|
---
|
||||||
--- Emits one `<dir_basename>.macs.h` per source with `#define mac_X(sig) \` macros plus `WORD_COUNT(mac_X, N)` entries for downstream offset computation.
|
--- Emits one `gen/macs.h` per *immediate source directory* with `#define mac_X(sig) \` macros plus `WORD_COUNT(mac_X, N)` entries for downstream offset computation.
|
||||||
---
|
--- All sources inside the same directory contribute to the same file (per-directory aggregation).
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
--- The directory itself is the namespace, so the filename does not repeat the module name.
|
||||||
--- Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
@@ -41,43 +40,44 @@ local MAC_PREFIX_LEN = 4
|
|||||||
local BYTE_NEWLINE = 10
|
local BYTE_NEWLINE = 10
|
||||||
local BYTE_SLASH = 47
|
local BYTE_SLASH = 47
|
||||||
|
|
||||||
-- Source dir basename used as the output `.macs.h` filename.
|
-- Output gen subdirectory + filename (per-directory aggregation; the directory name is the namespace).
|
||||||
local GEN_SUBDIR = "gen"
|
local GEN_SUBDIR = "gen"
|
||||||
|
local MACS_FILENAME = "macs.h"
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Type declarations
|
-- Type declarations
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- @class SourceFile
|
--- @class SourceFile
|
||||||
--- @field path string -- absolute path to the source file
|
--- @field path string -- Absolute path to the source file
|
||||||
--- @field text string -- the full source text
|
--- @field text string -- Full source text
|
||||||
--- @field dir string -- the directory containing the source
|
--- @field dir string -- Directory containing the source
|
||||||
--- @field basename string -- filename without extension
|
--- @field basename string -- Filename without extension
|
||||||
--- @field scan table -- pre-scanned SourceScan payload (from duffle.scan_source)
|
--- @field scan table -- Pre-scanned SourceScan payload (from duffle.scan_source)
|
||||||
|
|
||||||
--- @class PassCtx
|
--- @class PassCtx
|
||||||
--- @field sources SourceFile[] -- all source files in the build
|
--- @field sources SourceFile[] -- All source files in the build
|
||||||
--- @field metadata_path string -- path to word_count.metadata.h
|
--- @field metadata_path string -- Path to word_count.metadata.h
|
||||||
--- @field shared table -- cross-pass shared state
|
--- @field shared table -- Cross-pass shared state
|
||||||
--- @field out_root string -- output root (e.g. "build/gen")
|
--- @field out_root string -- Output root (e.g. "build/gen")
|
||||||
--- @field project_root string -- project root (e.g. "code/")
|
--- @field project_root string -- Project root (e.g. "code/")
|
||||||
--- @field upstream table<string, table> -- per-pass upstream outputs
|
--- @field upstream table<string, table> -- Per-pass upstream outputs
|
||||||
--- @field flags table -- CLI flags
|
--- @field flags table -- CLI flags
|
||||||
--- @field verbose boolean -- log diagnostic info
|
--- @field verbose boolean -- Log diagnostic info
|
||||||
|
|
||||||
--- @class PassResult
|
--- @class PassResult
|
||||||
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
||||||
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
||||||
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
||||||
|
|
||||||
--- @class Component
|
--- @class Component
|
||||||
--- @field name string -- atom name (without `ac_` prefix)
|
--- @field name string -- Atom name (without `ac_` prefix)
|
||||||
--- @field body string -- brace-delimited body (without the braces)
|
--- @field body string -- Brace-delimited body (without the braces)
|
||||||
--- @field args string|nil -- function-args string (function form only)
|
--- @field args string|nil -- Function-args string (function form only)
|
||||||
--- @field line integer -- source line of the declaration
|
--- @field line integer -- Source line of the declaration
|
||||||
--- @field comment string|nil -- scanner-owned `declaration_comment`; the components pass reads it from the scanner record
|
--- @field comment string|nil -- Scanner-owned `declaration_comment`; the components pass reads it from the scanner record
|
||||||
--- @field kind string -- "comp_bare" | "comp_proc"
|
--- @field kind string -- "comp_bare" | "comp_proc"
|
||||||
--- @field debug_skip boolean -- mirror of `a.debug_skip` (scanner-owned); true iff a bare `atom_dbg_skip` marker immediately preceded the declaration
|
--- @field debug_skip boolean -- Mirror of `a.debug_skip` (scanner-owned); true iff a bare `atom_dbg_skip` marker immediately preceded the declaration
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Local helpers (file I/O + path normalization)
|
-- Local helpers (file I/O + path normalization)
|
||||||
@@ -231,7 +231,6 @@ end
|
|||||||
--
|
--
|
||||||
-- Skips `//` sequences that are inside string or character literals
|
-- Skips `//` sequences that are inside string or character literals
|
||||||
-- (a rough heuristic — sufficient for component bodies which don't have those constructs).
|
-- (a rough heuristic — sufficient for component bodies which don't have those constructs).
|
||||||
--
|
|
||||||
--- @param s string
|
--- @param s string
|
||||||
--- @return string
|
--- @return string
|
||||||
local function convert_line_comments_to_block(s)
|
local function convert_line_comments_to_block(s)
|
||||||
@@ -300,7 +299,7 @@ local function word_count_rec(name, comp_by_name, wc, cache)
|
|||||||
local trimmed = t.tok
|
local trimmed = t.tok
|
||||||
if trimmed ~= "" then
|
if trimmed ~= "" then
|
||||||
local lookup = strip_mac_prefix(duffle.read_ident(trimmed, 1))
|
local lookup = strip_mac_prefix(duffle.read_ident(trimmed, 1))
|
||||||
if lookup and comp_by_name[lookup] then
|
if lookup and comp_by_name[lookup] then
|
||||||
-- It's a `mac_X(...)` call. Recurse.
|
-- It's a `mac_X(...)` call. Recurse.
|
||||||
n = n + word_count_rec(lookup, comp_by_name, wc, cache)
|
n = n + word_count_rec(lookup, comp_by_name, wc, cache)
|
||||||
elseif lookup and wc and wc[lookup] then
|
elseif lookup and wc and wc[lookup] then
|
||||||
@@ -350,7 +349,6 @@ end
|
|||||||
|
|
||||||
--- (internal) Recursive cycle-cost derivation. Sum `latency[ident]` per emitted instruction in the component body,
|
--- (internal) Recursive cycle-cost derivation. Sum `latency[ident]` per emitted instruction in the component body,
|
||||||
--- recursing through nested `mac_*` calls (so `mac_format_g4_color`'s cost = 4 × `mac_pack_color_word`'s cost).
|
--- recursing through nested `mac_*` calls (so `mac_format_g4_color`'s cost = 4 × `mac_pack_color_word`'s cost).
|
||||||
---
|
|
||||||
--- Special rule: `mac_yield`'s cost = 0 (per `lottes_tape.h:125-130` "the runtime cost lands in the next atom's prologue").
|
--- Special rule: `mac_yield`'s cost = 0 (per `lottes_tape.h:125-130` "the runtime cost lands in the next atom's prologue").
|
||||||
--- @param name string -- component bare name (e.g. "yield", "pack_color_word")
|
--- @param name string -- component bare name (e.g. "yield", "pack_color_word")
|
||||||
--- @param comp_by_name table<string, Component>
|
--- @param comp_by_name table<string, Component>
|
||||||
@@ -394,7 +392,6 @@ end
|
|||||||
--- (internal) Recursive GP0 prim-buffer contribution. Count `store_word` / `store_half` / `store_byte`
|
--- (internal) Recursive GP0 prim-buffer contribution. Count `store_word` / `store_half` / `store_byte`
|
||||||
--- calls in the component body that target `R_PrimCursor` (these are the
|
--- calls in the component body that target `R_PrimCursor` (these are the
|
||||||
--- RAM-side prim-buffer words the macro contributes), recursing through nested `mac_*` calls.
|
--- RAM-side prim-buffer words the macro contributes), recursing through nested `mac_*` calls.
|
||||||
---
|
|
||||||
--- Only `R_PrimCursor`-targeting stores count. Stores targeting other registers (e.g. `R_OtBase`, heap pointers) are not prim-buffer contributions.
|
--- Only `R_PrimCursor`-targeting stores count. Stores targeting other registers (e.g. `R_OtBase`, heap pointers) are not prim-buffer contributions.
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param comp_by_name table<string, Component>
|
--- @param comp_by_name table<string, Component>
|
||||||
@@ -410,9 +407,9 @@ local function gp0_contrib_rec(name, comp_by_name, cache)
|
|||||||
local tokens = cc.body_tokens
|
local tokens = cc.body_tokens
|
||||||
for _, t in ipairs(tokens) do
|
for _, t in ipairs(tokens) do
|
||||||
local trimmed = t.tok
|
local trimmed = t.tok
|
||||||
if trimmed ~= "" then
|
if trimmed ~= "" then
|
||||||
local ident = duffle.read_ident(trimmed, 1)
|
local ident = duffle.read_ident(trimmed, 1)
|
||||||
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
||||||
-- Nested `mac_X(...)` call: recurse.
|
-- Nested `mac_X(...)` call: recurse.
|
||||||
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
||||||
n = n + gp0_contrib_rec(nested, comp_by_name, cache)
|
n = n + gp0_contrib_rec(nested, comp_by_name, cache)
|
||||||
@@ -460,8 +457,8 @@ end
|
|||||||
--- @param s string
|
--- @param s string
|
||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function split_comment_lines(s)
|
local function split_comment_lines(s)
|
||||||
local out = {}
|
local out = {}
|
||||||
local pos = 1
|
local pos = 1
|
||||||
local s_len = #s
|
local s_len = #s
|
||||||
while pos <= s_len do
|
while pos <= s_len do
|
||||||
local nl = s:find("\n", pos, true)
|
local nl = s:find("\n", pos, true)
|
||||||
@@ -536,9 +533,9 @@ local function build_component_lines(c, counts)
|
|||||||
|
|
||||||
local tokens = duffle.split_top_level_commas(c.body)
|
local tokens = duffle.split_top_level_commas(c.body)
|
||||||
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end
|
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end
|
||||||
local sig = signature_from_args(c.args)
|
local sig = signature_from_args(c.args)
|
||||||
-- Direct lookup against the per-source precomputed `counts` table (built once by count_all_components).
|
-- Direct lookup against the per-source precomputed `counts` table (built once by count_all_components).
|
||||||
local n = counts[c.name]
|
local n = counts[c.name]
|
||||||
|
|
||||||
if n > 0 then
|
if n > 0 then
|
||||||
emit_macro_body(lines, c, sig, tokens)
|
emit_macro_body(lines, c, sig, tokens)
|
||||||
@@ -557,9 +554,15 @@ end
|
|||||||
|
|
||||||
--- Build the boilerplate header lines (the `#ifdef INTELLISENSE_DIRECTIVES` block,
|
--- Build the boilerplate header lines (the `#ifdef INTELLISENSE_DIRECTIVES` block,
|
||||||
--- the `// Auto-generated` comment, the `// Source:` line, and the self-contained `WORD_COUNT` macro definition).
|
--- the `// Auto-generated` comment, the `// Source:` line, and the self-contained `WORD_COUNT` macro definition).
|
||||||
--- @param src SourceFile
|
--- @param dir string -- the absolute source directory
|
||||||
|
--- @param sources SourceFile[] -- sources contributing to this directory (for the header comment)
|
||||||
--- @return string[]
|
--- @return string[]
|
||||||
local function header_boilerplate(src)
|
local function header_boilerplate(dir, sources)
|
||||||
|
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" }
|
||||||
|
for _, src in ipairs(sources) do
|
||||||
|
source_lines[#source_lines + 1] = "// source: " .. duffle.to_absolute_path(src.path)
|
||||||
|
end
|
||||||
|
local source_blob = table.concat(source_lines, "\n")
|
||||||
return {
|
return {
|
||||||
-- #pragma once wrapped in #ifdef INTELLISENSE_DIRECTIVES, matching the convention in lottes_tape.h.
|
-- #pragma once wrapped in #ifdef INTELLISENSE_DIRECTIVES, matching the convention in lottes_tape.h.
|
||||||
-- The build does manual unity includes (the user controls include order), so the pragma is only active for IDE/tooling.
|
-- The build does manual unity includes (the user controls include order), so the pragma is only active for IDE/tooling.
|
||||||
@@ -567,7 +570,7 @@ local function header_boilerplate(src)
|
|||||||
"#pragma once",
|
"#pragma once",
|
||||||
"#endif",
|
"#endif",
|
||||||
"// Auto-generated by ps1_meta.lua — DO NOT EDIT",
|
"// Auto-generated by ps1_meta.lua — DO NOT EDIT",
|
||||||
"// Source: " .. duffle.to_absolute_path(src.path),
|
source_blob,
|
||||||
"// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)",
|
"// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)",
|
||||||
"",
|
"",
|
||||||
-- Self-contained: define WORD_COUNT if not already defined.
|
-- Self-contained: define WORD_COUNT if not already defined.
|
||||||
@@ -580,30 +583,30 @@ local function header_boilerplate(src)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Compute the output path for one source's `.macs.h` file.
|
--- Compute the per-directory output path for `.macs.h`.
|
||||||
--- The pre-rework convention uses the *directory* basename (not the source file basename)
|
--- e.g. any source in `code/duffle/` produces `code/duffle/gen/macs.h` regardless of source filename.
|
||||||
--- e.g. `code/duffle/lottes_tape.h` produces `code/duffle/gen/duffle.macs.h`.
|
--- The directory name is the namespace; the filename does not repeat it.
|
||||||
--- This matches what the C codebase #includes.
|
--- @param dir string -- the absolute source directory
|
||||||
--- @param src SourceFile
|
|
||||||
--- @return string -- the output directory
|
--- @return string -- the output directory
|
||||||
--- @return string -- the full output path
|
--- @return string -- the full output path
|
||||||
local function compute_macs_h_path(src)
|
local function compute_macs_h_path(dir)
|
||||||
local out_dir = src.dir .. "/" .. GEN_SUBDIR
|
local out_dir = dir .. "/" .. GEN_SUBDIR
|
||||||
local out_path = out_dir .. "/" .. duffle.basename_no_ext(src.dir) .. ".macs.h"
|
local out_path = out_dir .. "/" .. MACS_FILENAME
|
||||||
return out_dir, out_path
|
return out_dir, out_path
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Emit a per-source `.macs.h` header with the `mac_X` macros + `WORD_COUNT` entries.
|
--- Emit a per-directory `.macs.h` header with the aggregated `mac_X` macros + `WORD_COUNT` entries.
|
||||||
--- Writes in BINARY mode so LF line endings are preserved (the git blob is LF; Windows text-mode would emit CRLF and break the byte-identical diff).
|
--- Writes in BINARY mode so LF line endings are preserved (the git blob is LF; Windows text-mode would emit CRLF and break the byte-identical diff).
|
||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @param src SourceFile
|
--- @param dir string -- the absolute source directory
|
||||||
--- @param components Component[]
|
--- @param sources SourceFile[] -- sources contributing to this directory (for the header comment)
|
||||||
|
--- @param components Component[] -- aggregated components from all sources in this directory
|
||||||
--- @param counts table<string, integer> -- precomputed word counts (from count_all_components)
|
--- @param counts table<string, integer> -- precomputed word counts (from count_all_components)
|
||||||
--- @return string|nil -- path to the written file (nil if no components)
|
--- @return string|nil -- path to the written file (nil if no components)
|
||||||
local function emit_component_macros_h(ctx, src, components, counts)
|
local function emit_component_macros_h(ctx, dir, sources, components, counts)
|
||||||
if #components == 0 then return nil end
|
if #components == 0 then return nil end
|
||||||
local out_dir, out_path = compute_macs_h_path(src)
|
local out_dir, out_path = compute_macs_h_path(dir)
|
||||||
local lines = header_boilerplate(src)
|
local lines = header_boilerplate(dir, sources)
|
||||||
|
|
||||||
for _, c in ipairs(components) do
|
for _, c in ipairs(components) do
|
||||||
for _, l in ipairs(build_component_lines(c, counts)) do
|
for _, l in ipairs(build_component_lines(c, counts)) do
|
||||||
@@ -675,7 +678,7 @@ local function update_canonical_components(corpus, src, components, metadata)
|
|||||||
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
||||||
local existing = corpus.components[c.name]
|
local existing = corpus.components[c.name]
|
||||||
if existing.path ~= rel_path or existing.line ~= c.line then
|
if existing.path ~= rel_path or existing.line ~= c.line then
|
||||||
local kind = c.kind or "comp_bare"
|
local kind = c.kind or "comp_bare"
|
||||||
local first_kind = existing.kind or "comp_bare"
|
local first_kind = existing.kind or "comp_bare"
|
||||||
corpus.collisions[#corpus.collisions + 1] = {
|
corpus.collisions[#corpus.collisions + 1] = {
|
||||||
kind = "component",
|
kind = "component",
|
||||||
@@ -740,24 +743,39 @@ function M.run(ctx)
|
|||||||
-- * `corpus.component_body_index[name]` — body / line_of / source index
|
-- * `corpus.component_body_index[name]` — body / line_of / source index
|
||||||
-- The pass writes to the corpus only; consumers read from the corpus directly.
|
-- The pass writes to the corpus only; consumers read from the corpus directly.
|
||||||
|
|
||||||
for _, src in ipairs(corpus.source_order) do
|
-- Per-directory aggregation: every source in the same directory contributes to one `gen/macs.h`.
|
||||||
-- project_components reads from src.scan + does backward lookups on src.text
|
-- The directory itself is the namespace. `corpus.sources_by_dir` preserves source-order within each bucket (matches `corpus.source_order`).
|
||||||
local components = project_components(src.text, src.scan)
|
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order)
|
||||||
if #components > 0 then
|
for dir, sources in pairs(sources_by_dir) do
|
||||||
-- Compute all component word counts once per source.
|
-- Aggregate components from every source in this directory.
|
||||||
-- Use `corpus.word_counts` so the recursive lookup sees both authored-metadata entries
|
-- `project_components` returns nil for sources with no `MipsAtomComp_` declarations; we skip those.
|
||||||
-- (loaded by word_count_eval.run) AND same-source component entries (populated earlier in this loop by `update_canonical_word_counts`).
|
local aggregated_components = {}
|
||||||
local counts = count_all_components(components, corpus.word_counts)
|
local metadata_per_source = {}
|
||||||
-- Derive cycle_cost + gp0_contrib from the original `MipsAtomComp_` body tokens
|
for _, src in ipairs(sources) do
|
||||||
-- (NOT from the generated `mac_*` variants — those are written to disk above).
|
local per_source = project_components(src.text, src.scan) or {}
|
||||||
local metadata = compute_components_metadata(components, duffle.INSTRUCTION_LATENCY)
|
for _, c in ipairs(per_source) do
|
||||||
local macs_path = emit_component_macros_h(ctx, src, components, counts)
|
aggregated_components[#aggregated_components + 1] = c
|
||||||
|
end
|
||||||
|
if #per_source > 0 then
|
||||||
|
metadata_per_source[src] = compute_components_metadata(per_source, duffle.INSTRUCTION_LATENCY)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #aggregated_components > 0 then
|
||||||
|
-- Compute word counts across the aggregated set. `corpus.word_counts` carries the
|
||||||
|
-- same-source + prior-directory entries so the recursive lookup sees both.
|
||||||
|
local counts = count_all_components(aggregated_components, corpus.word_counts)
|
||||||
|
local macs_path = emit_component_macros_h(ctx, dir, sources, aggregated_components, counts)
|
||||||
if macs_path then
|
if macs_path then
|
||||||
outputs[#outputs + 1] = { macs_h = macs_path }
|
outputs[#outputs + 1] = { macs_h = macs_path }
|
||||||
-- Populate the projections AFTER disk emission (so the byte-identical `.macs.h` contract is preserved before any current-count mutation).
|
-- Populate the projections AFTER disk emission (byte-identical `.macs.h` contract).
|
||||||
update_canonical_word_counts(corpus, components, counts)
|
update_canonical_word_counts(corpus, aggregated_components, counts)
|
||||||
update_canonical_components(corpus, src, components, metadata)
|
for _, src in ipairs(sources) do
|
||||||
update_canonical_component_body_index(corpus, src, components, src.scan)
|
local per_source = project_components(src.text, src.scan) or {}
|
||||||
|
if #per_source > 0 then
|
||||||
|
update_canonical_components(corpus, src, per_source, metadata_per_source[src])
|
||||||
|
update_canonical_component_body_index(corpus, src, per_source, src.scan)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ local DW_RLE_start_length = DWARF5_RNGLISTS.start_length
|
|||||||
|
|
||||||
-- File-index lookup for the existing main line unit (Unit 2).
|
-- File-index lookup for the existing main line unit (Unit 2).
|
||||||
-- Populated at pass start by `init_file_index_lookup(elf_path)` from the runtime ELF (see `elf_dwarf.read_line_unit_file_table`).
|
-- Populated at pass start by `init_file_index_lookup(elf_path)` from the runtime ELF (see `elf_dwarf.read_line_unit_file_table`).
|
||||||
local _file_index_by_basename = nil -- [basename] = 1-based line-table file index
|
local _file_index_by_basename = nil -- [basename] = 1-based line-table file index
|
||||||
local _file_path_by_index = nil -- [1-based index] = full source path (diagnostics / future consumers)
|
local _file_path_by_index = nil -- [1-based index] = full source path (diagnostics / future consumers)
|
||||||
local _default_atom_source_index = nil -- any valid index used in opaque-row fallbacks
|
local _default_atom_source_index = nil -- any valid index used in opaque-row fallbacks
|
||||||
|
|
||||||
-- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31
|
-- RR_<R_Name> debug-visible variables come from the merged register_alias_registry filtered to aliases whose code is a valid MIPS GPR 0..31
|
||||||
@@ -107,8 +107,8 @@ local ABBREV_TYPED_VIEW_POINTER = 0x6E -- 110: DW_TAG_pointer_type no children
|
|||||||
-- DWARF5 §7.7.3 loclist opcodes.
|
-- DWARF5 §7.7.3 loclist opcodes.
|
||||||
local DW_LLE_end_of_list = 0x00
|
local DW_LLE_end_of_list = 0x00
|
||||||
local DW_LLE_start_length = 0x08
|
local DW_LLE_start_length = 0x08
|
||||||
local DW_OP_reg0 = 0x50 -- base reg op; regN = 0x50 + N
|
local DW_OP_reg0 = 0x50 -- base reg op; regN = 0x50 + N
|
||||||
local DW_OP_breg0 = 0x70 -- base breg op; bregN = 0x70 + N (SLEB offset)
|
local DW_OP_breg0 = 0x70 -- base breg op; bregN = 0x70 + N (SLEB offset)
|
||||||
local DW_OP_piece = 0x93
|
local DW_OP_piece = 0x93
|
||||||
local MIPS_LOAD_DELAY_BYTES = 0x08 -- 1 load word + 1 BD-slot word
|
local MIPS_LOAD_DELAY_BYTES = 0x08 -- 1 load word + 1 BD-slot word
|
||||||
|
|
||||||
@@ -184,15 +184,17 @@ end
|
|||||||
--- Resolve an absolute provenance path to the line-unit file index used by the emitting line program.
|
--- Resolve an absolute provenance path to the line-unit file index used by the emitting line program.
|
||||||
--- Normalizes mixed `/` and `\` separators to a basename and looks it up against the runtime-computed file table populated by `init_file_index_lookup`.
|
--- Normalizes mixed `/` and `\` separators to a basename and looks it up against the runtime-computed file table populated by `init_file_index_lookup`.
|
||||||
---
|
---
|
||||||
--- Fails loudly on an unknown provenance basename: adding a new component source file will produce a clear error message naming the missing basename and listing the .debug_line file table contents,
|
--- Returns 0 (the DWARF `set_file(0)` "no file change" sentinel) when the basename is not in the file table.
|
||||||
--- so the user can either confirm the gcc include order, the unity-root, or the `.debug_line` file table contents.
|
--- This is a normal occurrence: the compiler only adds a file to the `.debug_line` file table when the file has line-numbered content (i.e., code).
|
||||||
--- Silent fallback would mask the new-file case by misattributing component rows to an arbitrary source file.
|
--- Files containing only static-array data (e.g. `MipsAtomComp_` declarations in `gp.atom.c`, `psyq.atom.c`, `pad.atom.c` — the OT-tag inserts, etc.) produce no line numbers,
|
||||||
|
--- so gcc omits them from the file table.
|
||||||
|
--- The DWARF emitter then keeps the previous line-program file state instead of pointing at a file that has no entries to walk.
|
||||||
|
--- A stderr warning is emitted per-miss so the user can audit which files the compiler dropped.
|
||||||
--- @param path string -- absolute provenance path (mixed slashes accepted)
|
--- @param path string -- absolute provenance path (mixed slashes accepted)
|
||||||
--- @return integer -- 1-based line-unit file index
|
--- @return integer -- 1-based line-unit file index, or 0 on miss (DWARF no-change sentinel)
|
||||||
local function resolve_provenance_file_index(path)
|
local function resolve_provenance_file_index(path)
|
||||||
if _file_index_by_basename == nil then
|
if _file_index_by_basename == nil then
|
||||||
error("[dwarf_injection] resolve_provenance_file_index called before init_file_index_lookup. "
|
error("[dwarf_injection] resolve_provenance_file_index called before init_file_index_lookup. Is M.run being entered correctly (with --elf)?")
|
||||||
.. "Is M.run being entered correctly (with --elf)?")
|
|
||||||
end
|
end
|
||||||
if path == nil or path == "" then
|
if path == nil or path == "" then
|
||||||
error("[dwarf_injection] resolve_provenance_file_index: empty path")
|
error("[dwarf_injection] resolve_provenance_file_index: empty path")
|
||||||
@@ -201,19 +203,17 @@ local function resolve_provenance_file_index(path)
|
|||||||
local normalized = path:gsub("\\", "/")
|
local normalized = path:gsub("\\", "/")
|
||||||
-- Take the last path component (the basename).
|
-- Take the last path component (the basename).
|
||||||
local basename = normalized:match("([^/]+)$") or normalized
|
local basename = normalized:match("([^/]+)$") or normalized
|
||||||
local idx = _file_index_by_basename[basename]
|
local idx = _file_index_by_basename[basename]
|
||||||
if idx ~= nil then return idx end
|
if idx ~= nil then return idx end
|
||||||
-- Last-resort exact-path match (handles paths that don't reduce to a known basename).
|
-- Last-resort exact-path match (handles paths that don't reduce to a known basename).
|
||||||
for i, p in pairs(_file_path_by_index) do
|
for i, p in pairs(_file_path_by_index) do
|
||||||
if p and p:gsub("\\", "/") == normalized then return i end
|
if p and p:gsub("\\", "/") == normalized then return i end
|
||||||
end
|
end
|
||||||
-- Build an error message listing the known basenames for fast diagnostics.
|
-- File is in the corpus but gcc omitted it from the .debug_line file table (data-only content).
|
||||||
local known = {}
|
-- Return 0 = DWARF `set_file(0)` no-change sentinel so the line program keeps its prior file state.
|
||||||
for k in pairs(_file_index_by_basename) do known[#known + 1] = k end
|
io.stderr:write(string.format("[dwarf_injection] line-table miss: '%s' (basename '%s') not in .debug_line file table; "
|
||||||
table.sort(known)
|
.. "falling back to set_file(0)\n", path, basename))
|
||||||
error(string.format("[dwarf_injection] resolve_provenance_file_index: unknown provenance basename '%s' (from '%s'). "
|
return 0
|
||||||
.. "Known basenames in the .debug_line file table (%d): %s"
|
|
||||||
, basename, path, #known, table.concat(known, ", ")))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local DW_FORM_addr = 0x01
|
local DW_FORM_addr = 0x01
|
||||||
@@ -228,7 +228,6 @@ local DW_FORM_sec_offset = 0x17 -- 4-byte section-relative offset (into .d
|
|||||||
|
|
||||||
-- DW_OP_reg0 + DW_OP_piece are declared above (lines 114-116) alongside the other DWARF5 §7.7.3 loclist opcodes.
|
-- DW_OP_reg0 + DW_OP_piece are declared above (lines 114-116) alongside the other DWARF5 §7.7.3 loclist opcodes.
|
||||||
|
|
||||||
|
|
||||||
local DW_ATE_unsigned = 0x07 -- DWARF5 §7.8.1: DW_ATE_unsigned (used for U4 base type)
|
local DW_ATE_unsigned = 0x07 -- DWARF5 §7.8.1: DW_ATE_unsigned (used for U4 base type)
|
||||||
|
|
||||||
-- (DW_LANG_Mips_Assembler = 0x8001 was used in the, but we want this CU to look like a C TU so VSCode's Variables pane treats it as code.)
|
-- (DW_LANG_Mips_Assembler = 0x8001 was used in the, but we want this CU to look like a C TU so VSCode's Variables pane treats it as code.)
|
||||||
@@ -448,9 +447,9 @@ end
|
|||||||
--- Statement-state rules:
|
--- Statement-state rules:
|
||||||
--- * A marked whole atom emits one opaque is_stmt=false range row and no nested component rows; its subprogram symbol/range remains available.
|
--- * A marked whole atom emits one opaque is_stmt=false range row and no nested component rows; its subprogram symbol/range remains available.
|
||||||
--- * Per-row policy at every other PC:
|
--- * Per-row policy at every other PC:
|
||||||
--- - Call-site row of any invocation's first word: is_stmt = true (unconditional; `want_call = true`).
|
--- - Call-site row of any invocation's first word: is_stmt = true (unconditional; `want_call = true`).
|
||||||
--- - Body row of any invocation (first or subsequent): is_stmt = not inv.debug_skip (`want_body = not inv.debug_skip`).
|
--- - Body row of any invocation (first or subsequent): is_stmt = not inv.debug_skip (`want_body = not inv.debug_skip`).
|
||||||
--- - RAW word (no containing invocation): is_stmt = true (unconditional).
|
--- - RAW word (no containing invocation): is_stmt = true (unconditional).
|
||||||
--- * The previous per-word `marked_idx` ancestor walk and the GDB 12 zero-instruction-prologue duplicate row at atom entry are DELETED; the new
|
--- * The previous per-word `marked_idx` ancestor walk and the GDB 12 zero-instruction-prologue duplicate row at atom entry are DELETED; the new
|
||||||
--- first-word emission IS the entry statement.
|
--- first-word emission IS the entry statement.
|
||||||
--- * Whole-atom suppression wins over component markers; no nested inversion.
|
--- * Whole-atom suppression wins over component markers; no nested inversion.
|
||||||
@@ -612,10 +611,8 @@ local function build_atom_sequence(atom)
|
|||||||
-- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call).
|
-- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call).
|
||||||
for ai, anc in ipairs(entry_1_ancestry) do
|
for ai, anc in ipairs(entry_1_ancestry) do
|
||||||
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
|
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
|
||||||
assert(anc.body_lines[1] ~= nil
|
assert(anc.body_lines[1] ~= nil, "dwarf_injection: body_lines[1] missing on first-word entry for inv=" .. tostring(anc.component_name))
|
||||||
, "dwarf_injection: body_lines[1] missing on first-word entry for inv=" .. tostring(anc.component_name))
|
assert(anc.call_path and anc.call_path ~= "", "dwarf_injection: inv.call_path is missing on invocation " .. tostring(anc.component_name) .. "; emitter did not run emission-model.")
|
||||||
assert(anc.call_path and anc.call_path ~= ""
|
|
||||||
, "dwarf_injection: inv.call_path is missing on invocation " .. tostring(anc.component_name) .. "; emitter did not run emission-model.")
|
|
||||||
emit_row(resolve_provenance_file_index(anc.call_path), anc.call_line, true)
|
emit_row(resolve_provenance_file_index(anc.call_path), anc.call_line, true)
|
||||||
local is_outermost = (ai == 1)
|
local is_outermost = (ai == 1)
|
||||||
if not (is_outermost and anc.debug_skip) then
|
if not (is_outermost and anc.debug_skip) then
|
||||||
@@ -640,8 +637,7 @@ local function build_atom_sequence(atom)
|
|||||||
-- all OTHER ancestors emit body_lines[1] with is_stmt = not debug_skip.
|
-- all OTHER ancestors emit body_lines[1] with is_stmt = not debug_skip.
|
||||||
--
|
--
|
||||||
-- This re-emits the outer ancestor's call-site + body rows at the inner's first word PC
|
-- This re-emits the outer ancestor's call-site + body rows at the inner's first word PC
|
||||||
-- for debugger context: source-level stepping now shows the outer body line
|
-- for debugger context: source-level stepping now shows the outer body line (not the inner body line) when stepping into the inner. PROBLEM B fix.
|
||||||
-- (not the inner body line) when stepping into the inner. PROBLEM B fix.
|
|
||||||
-- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content line in the parent's source),
|
-- The body_lines[1] row references body_first_line_of[anc.id] (= the body's first content line in the parent's source),
|
||||||
-- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call:
|
-- NOT anc.body_lines[1] (= the line of the first WORD, which is wrong when the outer's body starts with a nested call:
|
||||||
-- gdb 12.1 picks the displayed line as the LAST row at the same PC in byte-stream order,
|
-- gdb 12.1 picks the displayed line as the LAST row at the same PC in byte-stream order,
|
||||||
@@ -649,12 +645,9 @@ local function build_atom_sequence(atom)
|
|||||||
local ancestry = ancestry_idx[idx]
|
local ancestry = ancestry_idx[idx]
|
||||||
for ai, anc in ipairs(ancestry) do
|
for ai, anc in ipairs(ancestry) do
|
||||||
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
|
assert(anc.body_lines, "missing body_lines: emitter did not run emission-model")
|
||||||
assert(anc.body_lines[1] ~= nil
|
assert(anc.body_lines[1] ~= nil, string.format("missing body_lines[1] for inv=%s start_pos=%d len=%d", anc.component_name, anc.start_pos, #(anc.body_lines or {})))
|
||||||
, string.format("missing body_lines[1] for inv=%s start_pos=%d len=%d",
|
assert(anc.call_path and anc.call_path ~= "", "dwarf_injection: inv.call_path is missing on invocation " .. tostring(anc.component_name) .. "; emitter did not run emission-model.")
|
||||||
anc.component_name, anc.start_pos, #(anc.body_lines or {})))
|
emit_row(resolve_provenance_file_index(anc.call_path), anc.call_line, true)
|
||||||
assert(anc.call_path and anc.call_path ~= ""
|
|
||||||
, "dwarf_injection: inv.call_path is missing on invocation " .. tostring(anc.component_name) .. "; emitter did not run emission-model.")
|
|
||||||
emit_row(resolve_provenance_file_index(anc.call_path), anc.call_line, true)
|
|
||||||
local is_outermost = (ai == 1)
|
local is_outermost = (ai == 1)
|
||||||
if not (is_outermost and anc.debug_skip) then
|
if not (is_outermost and anc.debug_skip) then
|
||||||
emit_row(resolve_provenance_file_index(anc.def_path), body_first_line_of[anc.id] or anc.body_lines[1], not anc.debug_skip)
|
emit_row(resolve_provenance_file_index(anc.def_path), body_first_line_of[anc.id] or anc.body_lines[1], not anc.debug_skip)
|
||||||
@@ -669,10 +662,8 @@ local function build_atom_sequence(atom)
|
|||||||
-- Marked invocations emit non-statement body rows at every body word; unmarked invocations emit statement body rows.
|
-- Marked invocations emit non-statement body rows at every body word; unmarked invocations emit statement body rows.
|
||||||
assert(inv.body_lines, "missing body_lines: emitter did not run emission-model")
|
assert(inv.body_lines, "missing body_lines: emitter did not run emission-model")
|
||||||
local words_into = idx - inv.start_pos
|
local words_into = idx - inv.start_pos
|
||||||
assert(inv.body_lines[words_into] ~= nil
|
assert(inv.body_lines[words_into] ~= nil, string.format("missing body_lines[%d] for inv=%s start_pos=%d len=%d idx=%d", words_into, inv.component_name, inv.start_pos, #(inv.body_lines or {}), idx))
|
||||||
, string.format("missing body_lines[%d] for inv=%s start_pos=%d len=%d idx=%d",
|
emit_row(resolve_provenance_file_index(inv.def_path), inv.body_lines[words_into], not inv.debug_skip)
|
||||||
words_into, inv.component_name, inv.start_pos, #(inv.body_lines or {}), idx))
|
|
||||||
emit_row(resolve_provenance_file_index(inv.def_path), inv.body_lines[words_into], not inv.debug_skip)
|
|
||||||
else
|
else
|
||||||
-- RAW word: single call-site row, always a statement target (the word itself is unmarked).
|
-- RAW word: single call-site row, always a statement target (the word itself is unmarked).
|
||||||
emit_row(call_file_idx, entry.line, true)
|
emit_row(call_file_idx, entry.line, true)
|
||||||
@@ -712,8 +703,8 @@ end
|
|||||||
--- `{comp_name, call_file, call_line, comp_file, comp_line, start_pos, end_pos, body_lines, debug_skip}`. `body_lines[k]`
|
--- `{comp_name, call_file, call_line, comp_file, comp_line, start_pos, end_pos, body_lines, debug_skip}`. `body_lines[k]`
|
||||||
--- is the k-th word's source line within the component body.
|
--- is the k-th word's source line within the component body.
|
||||||
---
|
---
|
||||||
--- @param corpus table -- the corpus from `ctx.shared.corpus`
|
--- @param corpus table -- the corpus from `ctx.shared.corpus`
|
||||||
--- @param addrs table -- ELF symbols keyed by atom name from `elf_dwarf.read_nm`
|
--- @param addrs table -- ELF symbols keyed by atom name from `elf_dwarf.read_nm`
|
||||||
--- @return table[] -- list of {name, addr, size_bytes, words, entries, invocations, debug_skip?}
|
--- @return table[] -- list of {name, addr, size_bytes, words, entries, invocations, debug_skip?}
|
||||||
local function build_atom_table(corpus, addrs)
|
local function build_atom_table(corpus, addrs)
|
||||||
-- Cross-ref: keep only atoms present in BOTH the nm symbol table AND `corpus.atoms_by_name`. Output is sorted by ascending addr.
|
-- Cross-ref: keep only atoms present in BOTH the nm symbol table AND `corpus.atoms_by_name`. Output is sorted by ascending addr.
|
||||||
@@ -730,8 +721,8 @@ local function build_atom_table(corpus, addrs)
|
|||||||
local word_events = paths.word_events or {}
|
local word_events = paths.word_events or {}
|
||||||
local invocations_proj = paths.invocations or {}
|
local invocations_proj = paths.invocations or {}
|
||||||
-- Build the dense entries list from `word_events`.
|
-- Build the dense entries list from `word_events`.
|
||||||
-- `word_events[i].i` = the 0-based `.word` position
|
-- `word_events[i].i` = the 0-based `.word` position
|
||||||
-- `call_line` = the root atom's physical source line for that word (stamped by emission_model)
|
-- `call_line` = the root atom's physical source line for that word (stamped by emission_model)
|
||||||
local entries = {}
|
local entries = {}
|
||||||
for idx, ev in ipairs(word_events) do
|
for idx, ev in ipairs(word_events) do
|
||||||
entries[#entries + 1] = {
|
entries[#entries + 1] = {
|
||||||
@@ -773,9 +764,8 @@ local function build_atom_table(corpus, addrs)
|
|||||||
|
|
||||||
local out = {}
|
local out = {}
|
||||||
-- Walk every source's atom list (which preserves source order + per-source src_path).
|
-- Walk every source's atom list (which preserves source order + per-source src_path).
|
||||||
-- Cross-ref with the nm symbol table; atoms absent from `addrs` are skipped (an atom
|
-- Cross-ref with the nm symbol table; atoms absent from `addrs` are skipped
|
||||||
-- declared in source but not emitted as a symbol is a metaprogram or atom-info bug, not
|
-- (an atom declared in source but not emitted as a symbol is a metaprogram or atom-info bug, not a source-correlation bug — emit_no_emit would catch it upstream).
|
||||||
-- a source-correlation bug — emit_no_emit would catch it upstream).
|
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do
|
||||||
local src_path = src.path or ""
|
local src_path = src.path or ""
|
||||||
for _, atom_rec in ipairs(((src.scan or {}).atoms) or {}) do
|
for _, atom_rec in ipairs(((src.scan or {}).atoms) or {}) do
|
||||||
@@ -843,8 +833,7 @@ end
|
|||||||
--- (no `atom_reg` opt-in) are silently skipped — the resulting rbind record will be incomplete and the atom will fail to bind a usable piece chain.
|
--- (no `atom_reg` opt-in) are silently skipped — the resulting rbind record will be incomplete and the atom will fail to bind a usable piece chain.
|
||||||
--- This is intentional: silently falling back to a hardcoded GPR would mask the missing opt-in.
|
--- This is intentional: silently falling back to a hardcoded GPR would mask the missing opt-in.
|
||||||
---
|
---
|
||||||
--- Pre-tokenized: `body_tokens` is the scan-source pass's pre-split list of top-level
|
--- Pre-tokenized: `body_tokens` is the scan-source pass's pre-split list of top-level statements (each entry is a single `load_*` call or other statement).
|
||||||
--- statements (each entry is a single `load_*` call or other statement).
|
|
||||||
--- @param body_tokens table[] -- the atom's pre-tokenized body statements (from atom.body_tokens)
|
--- @param body_tokens table[] -- the atom's pre-tokenized body statements (from atom.body_tokens)
|
||||||
--- @param binds_name string -- expected Binds_X name (skip pairs with mismatching binds)
|
--- @param binds_name string -- expected Binds_X name (skip pairs with mismatching binds)
|
||||||
--- @param registries table -- merged registries from collect_per_source_registries
|
--- @param registries table -- merged registries from collect_per_source_registries
|
||||||
@@ -856,7 +845,7 @@ local function parse_body_load_pairs(body_tokens, binds_name, registries)
|
|||||||
-- The captured ident is `kind`; `inner` holds the parens body for arg parsing.
|
-- The captured ident is `kind`; `inner` holds the parens body for arg parsing.
|
||||||
local load_pattern = "^(load_word|load_half|load_half_u|load_byte|load_byte_u|gte_lw|gte_lwc2)%s*%((.*)%)$"
|
local load_pattern = "^(load_word|load_half|load_half_u|load_byte|load_byte_u|gte_lw|gte_lwc2)%s*%((.*)%)$"
|
||||||
for _, t in ipairs(body_tokens or {}) do
|
for _, t in ipairs(body_tokens or {}) do
|
||||||
local tok = duffle.trim(t.tok or "")
|
local tok = duffle.trim(t.tok or "")
|
||||||
local kind, inner = tok:match(load_pattern)
|
local kind, inner = tok:match(load_pattern)
|
||||||
if kind then
|
if kind then
|
||||||
local args = duffle.split_top_level_commas(inner)
|
local args = duffle.split_top_level_commas(inner)
|
||||||
@@ -882,9 +871,7 @@ local function parse_body_load_pairs(body_tokens, binds_name, registries)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Collect every rbind atom + the matching Binds_X struct + (reg, field) pairs.
|
--- Collect every rbind atom + the matching Binds_X struct + (reg, field) pairs.
|
||||||
---
|
|
||||||
--- Inputs come from the dep-closed `scan-source` pass (the per-source `src.scan` payload is preserved on each `corpus.source_order` entry).
|
--- Inputs come from the dep-closed `scan-source` pass (the per-source `src.scan` payload is preserved on each `corpus.source_order` entry).
|
||||||
---
|
|
||||||
--- Returns:
|
--- Returns:
|
||||||
--- rbind_atoms = {[atom_name] = {binds, fields, regs, byte_size, info_line}}
|
--- rbind_atoms = {[atom_name] = {binds, fields, regs, byte_size, info_line}}
|
||||||
--- rbind_structs = {[binds_name] = {byte_size, fields, atom_names}}
|
--- rbind_structs = {[binds_name] = {byte_size, fields, atom_names}}
|
||||||
@@ -929,7 +916,7 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
local body_tokens_by_atom = {}
|
local body_tokens_by_atom = {}
|
||||||
for _, src in ipairs((corpus and corpus.source_order) or {}) do
|
for _, src in ipairs((corpus and corpus.source_order) or {}) do
|
||||||
local scan = src.scan
|
local scan = src.scan
|
||||||
if scan then
|
if scan then
|
||||||
for _, atom in ipairs(scan.atoms or {}) do
|
for _, atom in ipairs(scan.atoms or {}) do
|
||||||
body_tokens_by_atom[atom.name] = atom.body_tokens
|
body_tokens_by_atom[atom.name] = atom.body_tokens
|
||||||
end
|
end
|
||||||
@@ -948,8 +935,8 @@ local function parse_rbind_atoms(corpus, atom_table, registries)
|
|||||||
|
|
||||||
for atom_name, ai in pairs(ai_by_atom) do
|
for atom_name, ai in pairs(ai_by_atom) do
|
||||||
if ai.binds then
|
if ai.binds then
|
||||||
local struct = rbind_structs[ai.binds]
|
local struct = rbind_structs[ai.binds]
|
||||||
local body_toks = body_tokens_by_atom[atom_name]
|
local body_toks = body_tokens_by_atom[atom_name]
|
||||||
if struct and body_toks then
|
if struct and body_toks then
|
||||||
local pairs = parse_body_load_pairs(body_toks, ai.binds, registries)
|
local pairs = parse_body_load_pairs(body_toks, ai.binds, registries)
|
||||||
if #pairs > 0 then
|
if #pairs > 0 then
|
||||||
@@ -984,7 +971,8 @@ end
|
|||||||
--- (the final unit, referenced by the main CU's DW_AT_stmt_list).
|
--- (the final unit, referenced by the main CU's DW_AT_stmt_list).
|
||||||
---
|
---
|
||||||
--- This builder extends the main compilation unit.
|
--- This builder extends the main compilation unit.
|
||||||
--- A detached synthetic line unit has no DW_AT_stmt_list referencing it, so gdb ignored it (a previous experiment); byte 13 is the first special opcode, not the extended-opcode marker.
|
--- A detached synthetic line unit has no DW_AT_stmt_list referencing it, so gdb ignored it (a previous experiment);
|
||||||
|
--- byte 13 is the first special opcode, not the extended-opcode marker.
|
||||||
--- The existing final unit already contains hello_gte_tape.c as file index 11 and ends with a valid end_sequence.
|
--- The existing final unit already contains hello_gte_tape.c as file index 11 and ends with a valid end_sequence.
|
||||||
--- We preserve its bytes, append independent atom sequences, and increase only that unit's DWARF32 unit_length.
|
--- We preserve its bytes, append independent atom sequences, and increase only that unit's DWARF32 unit_length.
|
||||||
--- @param existing string -- existing section bytes, byte-for-byte
|
--- @param existing string -- existing section bytes, byte-for-byte
|
||||||
@@ -995,9 +983,7 @@ local function build_dwarf_line_section(existing, atom_table)
|
|||||||
|
|
||||||
-- Build the sequences.
|
-- Build the sequences.
|
||||||
local sequences = {}
|
local sequences = {}
|
||||||
for _, atom in ipairs(atom_table) do
|
for _, atom in ipairs(atom_table) do sequences[#sequences + 1] = build_atom_sequence(atom) end
|
||||||
sequences[#sequences + 1] = build_atom_sequence(atom)
|
|
||||||
end
|
|
||||||
local appended = table.concat(sequences)
|
local appended = table.concat(sequences)
|
||||||
|
|
||||||
-- Walk DWARF32 line units and retain the final unit's bounds.
|
-- Walk DWARF32 line units and retain the final unit's bounds.
|
||||||
@@ -1005,7 +991,7 @@ local function build_dwarf_line_section(existing, atom_table)
|
|||||||
local unit_pos, last_pos, last_length, last_end = 0, nil, nil, nil
|
local unit_pos, last_pos, last_length, last_end = 0, nil, nil, nil
|
||||||
while unit_pos < #existing do
|
while unit_pos < #existing do
|
||||||
if unit_pos + 4 > #existing then return existing end
|
if unit_pos + 4 > #existing then return existing end
|
||||||
local unit_length = elf_dwarf.read_u32_le(existing, unit_pos)
|
local unit_length = elf_dwarf.read_u32_le(existing, unit_pos)
|
||||||
if unit_length == elf_dwarf.ELF32.dw_dwarf32_terminator then return existing end
|
if unit_length == elf_dwarf.ELF32.dw_dwarf32_terminator then return existing end
|
||||||
local unit_end_excl = unit_pos + 4 + unit_length
|
local unit_end_excl = unit_pos + 4 + unit_length
|
||||||
if unit_end_excl > #existing then return existing end
|
if unit_end_excl > #existing then return existing end
|
||||||
@@ -1531,13 +1517,12 @@ end
|
|||||||
--- DW_AT_location = piece-chain (DW_FORM_exprloc)
|
--- DW_AT_location = piece-chain (DW_FORM_exprloc)
|
||||||
--- DW_AT_type = ref4 → structure_type DIE
|
--- DW_AT_type = ref4 → structure_type DIE
|
||||||
---
|
---
|
||||||
--- This function does NOT emit the final 0 byte (root terminator). build_debug_info_section splices bytes ahead of the root terminator
|
--- This function does NOT emit the final 0 byte (root terminator).
|
||||||
--- and preserves existing DIE bytes exactly.
|
--- build_debug_info_section splices bytes ahead of the root terminator and preserves existing DIE bytes exactly.
|
||||||
---
|
---
|
||||||
--- ref4 basis: DW_FORM_ref4 is CU-relative (offset from the first byte of the CU header).
|
--- ref4 basis: DW_FORM_ref4 is CU-relative (offset from the first byte of the CU header).
|
||||||
--- Our inserted DIEs live in the main CU, so every ref4 = (target section offset) - main_cu_offset.
|
--- Our inserted DIEs live in the main CU, so every ref4 = (target section offset) - main_cu_offset.
|
||||||
--- Per-die section offsets are tracked via the running `next_offset` cursor (= section offset of the NEXT byte to emit).
|
--- Per-die section offsets are tracked via the running `next_offset` cursor (= section offset of the NEXT byte to emit).
|
||||||
---
|
|
||||||
--- @param main_cu_offset integer -- 0-based section offset of the main CU's unit_length field
|
--- @param main_cu_offset integer -- 0-based section offset of the main CU's unit_length field
|
||||||
--- @param main_cu_end_excl integer -- 0-based section offset of the first byte AFTER the main CU
|
--- @param main_cu_end_excl integer -- 0-based section offset of the first byte AFTER the main CU
|
||||||
--- @param atom_table table[] -- atoms (with atom.rbind set if rbind; atom.invocations set if mac_X(...) calls)
|
--- @param atom_table table[] -- atoms (with atom.rbind set if rbind; atom.invocations set if mac_X(...) calls)
|
||||||
@@ -1657,6 +1642,8 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
--
|
--
|
||||||
-- The table is small + explicit — the prototype principle treats the typed-view struct layout as data, not derived state.
|
-- The table is small + explicit — the prototype principle treats the typed-view struct layout as data, not derived state.
|
||||||
local STRUCT_MEMBER_TABLE = {
|
local STRUCT_MEMBER_TABLE = {
|
||||||
|
-- TODO(Ed): This hardcoding is brittle...
|
||||||
|
-- TODO(Ed): Better to just have a table for the fundamental types in duffle/dsl.h, we can derive the rest via typedef parsing...
|
||||||
-- 2-element signed short vector (rare; placeholder for future use).
|
-- 2-element signed short vector (rare; placeholder for future use).
|
||||||
V2_S2 = { byte_size = 4, members = {
|
V2_S2 = { byte_size = 4, members = {
|
||||||
{ name = "x", offset = 0, byte_size = 2 },
|
{ name = "x", offset = 0, byte_size = 2 },
|
||||||
@@ -1909,12 +1896,13 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
local atom_view = (registries.atom_views or {})[atom.name]
|
local atom_view = (registries.atom_views or {})[atom.name]
|
||||||
-- Build the atom-name lookup table once (cheap; O(atom_table)) so step (b) and step (d) can resolve rbind_atom names.
|
-- Build the atom-name lookup table once (cheap; O(atom_table)) so step (b) and step (d) can resolve rbind_atom names.
|
||||||
|
-- TODO(Ed): Bad assignment?
|
||||||
local atom_by_name = atom_by_name or (function() local m = {}; for _, a in ipairs(atom_table) do if a.name then m[a.name] = a end end; return m end)()
|
local atom_by_name = atom_by_name or (function() local m = {}; for _, a in ipairs(atom_table) do if a.name then m[a.name] = a end end; return m end)()
|
||||||
-- step (b) inputs: this atom's `atom_ctx(<rbind_atom>)` (resolved from the registries' atom_ctxs)
|
-- step (b) inputs: this atom's `atom_ctx(<rbind_atom>)` (resolved from the registries' atom_ctxs)
|
||||||
local this_ctx = registries.atom_ctxs and registries.atom_ctxs[atom.name]
|
local this_ctx = registries.atom_ctxs and registries.atom_ctxs[atom.name]
|
||||||
if this_ctx and this_ctx.rbind_atom then
|
if this_ctx and this_ctx.rbind_atom then
|
||||||
local rbind = atom_by_name_global[this_ctx.rbind_atom]
|
local rbind = atom_by_name_global[this_ctx.rbind_atom]
|
||||||
if rbind and rbind.rbind and rbind.rbind.fields then
|
if rbind and rbind.rbind and rbind.rbind.fields then
|
||||||
atom_view_ctx_fields = {}
|
atom_view_ctx_fields = {}
|
||||||
for _, f in ipairs(rbind.rbind.fields) do atom_view_ctx_fields[f.name] = f end
|
for _, f in ipairs(rbind.rbind.fields) do atom_view_ctx_fields[f.name] = f end
|
||||||
if rbind.rbind.regs then
|
if rbind.rbind.regs then
|
||||||
@@ -1934,11 +1922,11 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
end
|
end
|
||||||
if my_phase_label then
|
if my_phase_label then
|
||||||
local group = (registries.atom_phases or {})[my_phase_label]
|
local group = (registries.atom_phases or {})[my_phase_label]
|
||||||
if group and group.atoms then
|
if group and group.atoms then
|
||||||
for _, group_atom_name in ipairs(group.atoms) do
|
for _, group_atom_name in ipairs(group.atoms) do
|
||||||
if group_atom_name ~= atom.name then
|
if group_atom_name ~= atom.name then
|
||||||
local cand = atom_by_name_global[group_atom_name]
|
local cand = atom_by_name_global[group_atom_name]
|
||||||
if cand and cand.rbind and cand.rbind.fields then
|
if cand and cand.rbind and cand.rbind.fields then
|
||||||
atom_view_phase_fields = {}
|
atom_view_phase_fields = {}
|
||||||
for _, f in ipairs(cand.rbind.fields) do atom_view_phase_fields[f.name] = f end
|
for _, f in ipairs(cand.rbind.fields) do atom_view_phase_fields[f.name] = f end
|
||||||
if cand.rbind.regs then
|
if cand.rbind.regs then
|
||||||
@@ -1959,7 +1947,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- (a) per-atom callsite atom_type(R_X, <T>): most specific; user explicit override for THIS atom only.
|
-- (a) per-atom callsite atom_type(R_X, <T>): most specific; user explicit override for THIS atom only.
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local override = atom_view and atom_view.reg_type_overrides and atom_view.reg_type_overrides[r_name]
|
local override = atom_view and atom_view.reg_type_overrides and atom_view.reg_type_overrides[r_name]
|
||||||
if override and override.pointer_depth and override.pointer_depth > 0 then
|
if override and override.pointer_depth and override.pointer_depth > 0 then
|
||||||
return type_chain_offsets[override.type_name .. "|" .. override.pointer_depth]
|
return type_chain_offsets[override.type_name .. "|" .. override.pointer_depth]
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -1967,7 +1955,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local ctx_field_name = reg_to_field_ctx and reg_to_field_ctx[alias_code]
|
local ctx_field_name = reg_to_field_ctx and reg_to_field_ctx[alias_code]
|
||||||
local ctx_f = ctx_field_name and atom_view_ctx_fields and atom_view_ctx_fields[ctx_field_name]
|
local ctx_f = ctx_field_name and atom_view_ctx_fields and atom_view_ctx_fields[ctx_field_name]
|
||||||
if ctx_f and ctx_f.pointer_depth and ctx_f.pointer_depth > 0 then
|
if ctx_f and ctx_f.pointer_depth and ctx_f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[ctx_f.type_name .. "|" .. ctx_f.pointer_depth]
|
return type_chain_offsets[ctx_f.type_name .. "|" .. ctx_f.pointer_depth]
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -1975,7 +1963,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local field_name = reg_to_field[alias_code]
|
local field_name = reg_to_field[alias_code]
|
||||||
local f = field_name and field_type_by_name[field_name]
|
local f = field_name and field_type_by_name[field_name]
|
||||||
if f and f.pointer_depth and f.pointer_depth > 0 then
|
if f and f.pointer_depth and f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[f.type_name .. "|" .. f.pointer_depth]
|
return type_chain_offsets[f.type_name .. "|" .. f.pointer_depth]
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -1983,12 +1971,13 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
local phase_field_name = reg_to_field_phase and reg_to_field_phase[alias_code]
|
local phase_field_name = reg_to_field_phase and reg_to_field_phase[alias_code]
|
||||||
local phase_f = phase_field_name and atom_view_phase_fields and atom_view_phase_fields[phase_field_name]
|
local phase_f = phase_field_name and atom_view_phase_fields and atom_view_phase_fields[phase_field_name]
|
||||||
if phase_f and phase_f.pointer_depth and phase_f.pointer_depth > 0 then
|
if phase_f and phase_f.pointer_depth and phase_f.pointer_depth > 0 then
|
||||||
return type_chain_offsets[phase_f.type_name .. "|" .. phase_f.pointer_depth]
|
return type_chain_offsets[phase_f.type_name .. "|" .. phase_f.pointer_depth]
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
-- (e) enum-site atom_type(<T>) default on the registry entry.
|
-- (e) enum-site atom_type(<T>) default on the registry entry.
|
||||||
function(r_name, alias_code)
|
function(r_name, alias_code)
|
||||||
|
-- TODO(Ed): Bad definition?
|
||||||
if alias and alias.default_type and alias.default_depth and alias.default_depth > 0 then
|
if alias and alias.default_type and alias.default_depth and alias.default_depth > 0 then
|
||||||
return type_chain_offsets[alias.default_type .. "|" .. alias.default_depth]
|
return type_chain_offsets[alias.default_type .. "|" .. alias.default_depth]
|
||||||
end
|
end
|
||||||
@@ -1997,8 +1986,8 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
|
|
||||||
-- Iterate `by_alias` in sorted order; Lua's pairs() is non-deterministic, so sorting ensures byte-identical DWARF output across builds.
|
-- Iterate `by_alias` in sorted order; Lua's pairs() is non-deterministic, so sorting ensures byte-identical DWARF output across builds.
|
||||||
for _, r_name in ipairs(by_alias_order) do
|
for _, r_name in ipairs(by_alias_order) do
|
||||||
local alias = by_alias[r_name]
|
local alias = by_alias[r_name]
|
||||||
local rr_name = "RR_" .. strip_r_prefix(r_name)
|
local rr_name = "RR_" .. strip_r_prefix(r_name)
|
||||||
local alias_code = alias.code
|
local alias_code = alias.code
|
||||||
emit(uleb128(ABBREV_VARIABLE))
|
emit(uleb128(ABBREV_VARIABLE))
|
||||||
emit(rr_name .. "\0") -- DW_FORM_string (DW_AT_name)
|
emit(rr_name .. "\0") -- DW_FORM_string (DW_AT_name)
|
||||||
@@ -2020,7 +2009,7 @@ local function build_inserted_children(main_cu_offset, main_cu_end_excl, atom_ta
|
|||||||
-- Two PC ranges cover every field: [atom.addr, last_load+8) describes each field as tape memory (DW_OP_bregN + offset) piece,
|
-- Two PC ranges cover every field: [atom.addr, last_load+8) describes each field as tape memory (DW_OP_bregN + offset) piece,
|
||||||
-- and [last_load+8, atom.end) describes each field as a GPR (DW_OP_regN) piece.
|
-- and [last_load+8, atom.end) describes each field as a GPR (DW_OP_regN) piece.
|
||||||
if atom.rbind then
|
if atom.rbind then
|
||||||
local binds_name = atom.rbind.binds
|
local binds_name = atom.rbind.binds
|
||||||
local loclists_offset = loclists_offsets[atom.name] or 0
|
local loclists_offset = loclists_offsets[atom.name] or 0
|
||||||
emit(uleb128(ABBREV_BIND_VAR_LOCLIST))
|
emit(uleb128(ABBREV_BIND_VAR_LOCLIST))
|
||||||
emit("bind_args\0") -- DW_FORM_string (DW_AT_name)
|
emit("bind_args\0") -- DW_FORM_string (DW_AT_name)
|
||||||
@@ -2073,7 +2062,7 @@ end
|
|||||||
---
|
---
|
||||||
--- Fails safely by returning existing sections unchanged if the table walker can't find the table terminator (malformed input).
|
--- Fails safely by returning existing sections unchanged if the table walker can't find the table terminator (malformed input).
|
||||||
---
|
---
|
||||||
--- @param existing string -- existing .debug_abbrev bytes, byte-for-byte
|
--- @param existing string -- existing .debug_abbrev bytes, byte-for-byte
|
||||||
--- @param main_abbrev_offset integer -- 0-based offset into `existing` of the main CU's abbrev table
|
--- @param main_abbrev_offset integer -- 0-based offset into `existing` of the main CU's abbrev table
|
||||||
--- @return string, integer -- (new_abbrev_bytes, offset_where_duplicate_table_starts = #existing)
|
--- @return string, integer -- (new_abbrev_bytes, offset_where_duplicate_table_starts = #existing)
|
||||||
local function build_debug_abbrev_section(existing, main_abbrev_offset)
|
local function build_debug_abbrev_section(existing, main_abbrev_offset)
|
||||||
@@ -2091,7 +2080,7 @@ local function build_debug_abbrev_section(existing, main_abbrev_offset)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Build the new .debug_str: existing strings + new strings appended.
|
--- Build the new .debug_str: existing strings + new strings appended.
|
||||||
--- @param existing string -- existing .debug_str bytes, byte-for-byte
|
--- @param existing string -- existing .debug_str bytes, byte-for-byte
|
||||||
--- @param atom_table table[]
|
--- @param atom_table table[]
|
||||||
--- @param registries table -- merged registries from collect_per_source_registries
|
--- @param registries table -- merged registries from collect_per_source_registries
|
||||||
--- @return string -- existing bytes plus the deterministic appended strings
|
--- @return string -- existing bytes plus the deterministic appended strings
|
||||||
@@ -2101,7 +2090,6 @@ local function build_debug_str_section(existing, atom_table, registries)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Build the new .debug_info: SPLICE inserted DIEs into the MAIN CU as children.
|
--- Build the new .debug_info: SPLICE inserted DIEs into the MAIN CU as children.
|
||||||
---
|
|
||||||
--- This implementation:
|
--- This implementation:
|
||||||
--- 1. Builds the inserted-children bytes (base_type, struct_types, subprograms with their RR_* + bind_args children) via build_inserted_children.
|
--- 1. Builds the inserted-children bytes (base_type, struct_types, subprograms with their RR_* + bind_args children) via build_inserted_children.
|
||||||
--- 2. Patches the main CU's `unit_length` field to account for the inserted bytes.
|
--- 2. Patches the main CU's `unit_length` field to account for the inserted bytes.
|
||||||
@@ -2111,14 +2099,14 @@ end
|
|||||||
---
|
---
|
||||||
--- The crt CU (everything before main_cu_start) is preserved.
|
--- The crt CU (everything before main_cu_start) is preserved.
|
||||||
|
|
||||||
--- @param existing string -- existing .debug_info section bytes
|
--- @param existing string -- existing .debug_info section bytes
|
||||||
--- @param main_cu_start integer -- 0-based offset of the main CU's unit_length field
|
--- @param main_cu_start integer -- 0-based offset of the main CU's unit_length field
|
||||||
--- @param main_cu_end_excl integer -- 0-based offset of the first byte AFTER the main CU
|
--- @param main_cu_end_excl integer -- 0-based offset of the first byte AFTER the main CU
|
||||||
--- @param new_abbrev_offset integer -- 0-based offset into the new .debug_abbrev of the duplicate main table
|
--- @param new_abbrev_offset integer -- 0-based offset into the new .debug_abbrev of the duplicate main table
|
||||||
--- @param atom_table table[]
|
--- @param atom_table table[]
|
||||||
--- @param rbind_structs table -- {[binds_name] = {bytes, fields, atom_names}}
|
--- @param rbind_structs table -- {[binds_name] = {bytes, fields, atom_names}}
|
||||||
--- @param loclists_offsets table -- {[atom_name] = section-relative offset}
|
--- @param loclists_offsets table -- {[atom_name] = section-relative offset}
|
||||||
--- @param registries table -- merged registries from collect_per_source_registries
|
--- @param registries table -- merged registries from collect_per_source_registries
|
||||||
--- @return string -- the rebuilt .debug_info bytes
|
--- @return string -- the rebuilt .debug_info bytes
|
||||||
local function build_debug_info_section(existing, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
local function build_debug_info_section(existing, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
||||||
-- 1) Build the inserted children bytes (just before the main CU's root terminator).
|
-- 1) Build the inserted children bytes (just before the main CU's root terminator).
|
||||||
@@ -2176,13 +2164,13 @@ local SECTION_WRITERS = {
|
|||||||
-- Write a list of `{name, data}` section records to disk via SECTION_WRITERS.
|
-- Write a list of `{name, data}` section records to disk via SECTION_WRITERS.
|
||||||
-- @param results table[] -- list of `{name=, data=}` records to write
|
-- @param results table[] -- list of `{name=, data=}` records to write
|
||||||
-- @param ctx PassCtx
|
-- @param ctx PassCtx
|
||||||
-- @param basename string -- output file basename (e.g. "hello_gte")
|
-- @param basename string -- output file basename (e.g. "hello_gte")
|
||||||
-- @return table -- list of {name_bin = path} entries to append to M.run's outputs
|
-- @return table -- list of {name_bin = path} entries to append to M.run's outputs
|
||||||
local function write_sections(results, ctx, basename)
|
local function write_sections(results, ctx, basename)
|
||||||
local outputs = {}
|
local outputs = {}
|
||||||
for _, r in ipairs(results) do
|
for _, r in ipairs(results) do
|
||||||
local path = SECTION_WRITERS[r.name](ctx.out_root, basename)
|
local path = SECTION_WRITERS[r.name](ctx.out_root, basename)
|
||||||
local f = io.open(path, "wb")
|
local f = io.open(path, "wb")
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", path))
|
io.stderr:write(string.format("[dwarf_injection] failed to open %s for write\n", path))
|
||||||
else
|
else
|
||||||
@@ -2209,7 +2197,7 @@ function M.run(ctx)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Guard: --elf is required.
|
-- Guard: --elf is required.
|
||||||
local elf_path = ctx.flags and ctx.flags.elf_path
|
local elf_path = ctx.flags and ctx.flags.elf_path
|
||||||
if not elf_path or elf_path == "" then
|
if not elf_path or elf_path == "" then
|
||||||
io.stderr:write("[dwarf_injection] --elf flag missing\n")
|
io.stderr:write("[dwarf_injection] --elf flag missing\n")
|
||||||
return { outputs = {}, errors = {}, warnings = {} }
|
return { outputs = {}, errors = {}, warnings = {} }
|
||||||
@@ -2220,7 +2208,8 @@ function M.run(ctx)
|
|||||||
|
|
||||||
-- Read the existing DWARF sections directly (no subprocess; io.open + manual ELF32 section-header walk).
|
-- Read the existing DWARF sections directly (no subprocess; io.open + manual ELF32 section-header walk).
|
||||||
-- We need all 8 sections: .debug_line / .debug_aranges / .debug_rnglists get extended (additional rows appended to the existing unit),
|
-- We need all 8 sections: .debug_line / .debug_aranges / .debug_rnglists get extended (additional rows appended to the existing unit),
|
||||||
-- and .debug_info / .debug_abbrev / .debug_str / .debug_loc / .debug_loclists get spliced (the main CU's unit_length is patched; no new compile unit is appended; .debug_loc/.debug_loclists may not exist in the source ELF so we add-section them on splice).
|
-- and .debug_info / .debug_abbrev / .debug_str / .debug_loc / .debug_loclists get spliced
|
||||||
|
-- (the main CU's unit_length is patched; no new compile unit is appended; .debug_loc/.debug_loclists may not exist in the source ELF so we add-section them on splice).
|
||||||
-- The per-section dispatch is inlined in the writers loop below.
|
-- The per-section dispatch is inlined in the writers loop below.
|
||||||
local existing_sections = elf_dwarf.read_elf_sections(elf_path, {
|
local existing_sections = elf_dwarf.read_elf_sections(elf_path, {
|
||||||
".debug_line", ".debug_aranges", ".debug_rnglists",
|
".debug_line", ".debug_aranges", ".debug_rnglists",
|
||||||
@@ -2235,12 +2224,11 @@ function M.run(ctx)
|
|||||||
init_file_index_lookup(elf_path)
|
init_file_index_lookup(elf_path)
|
||||||
-- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation).
|
-- Skip state lives in `corpus.atoms_by_name[*].debug_skip` (whole-atom) and `atom.paths.invocations[*].debug_skip` (per-invocation).
|
||||||
-- `corpus` is the sole canonical source projection.
|
-- `corpus` is the sole canonical source projection.
|
||||||
local corpus = (ctx.shared and ctx.shared.corpus) or {}
|
local corpus = (ctx.shared and ctx.shared.corpus) or {}
|
||||||
local registries = collect_per_source_registries(corpus)
|
local registries = collect_per_source_registries(corpus)
|
||||||
-- Read nm symbols (the ONLY disk-side input to the atom table) and join
|
-- Read nm symbols (the ONLY disk-side input to the atom table) and join them against `corpus.atoms_by_name` + `atom.paths` for word rows + invocation ancestry.
|
||||||
-- them against `corpus.atoms_by_name` + `atom.paths` for word rows + invocation ancestry.
|
|
||||||
-- Disk source-map/provenance text is not consulted (those are diagnostic artifacts; semantic inputs are in memory).
|
-- Disk source-map/provenance text is not consulted (those are diagnostic artifacts; semantic inputs are in memory).
|
||||||
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path)
|
local addrs = elf_dwarf.read_nm(ctx.flags.elf_path)
|
||||||
local atom_table = build_atom_table(corpus, addrs)
|
local atom_table = build_atom_table(corpus, addrs)
|
||||||
|
|
||||||
-- Detect rbind atoms + index Binds_* struct fields from the corpus.
|
-- Detect rbind atoms + index Binds_* struct fields from the corpus.
|
||||||
@@ -2264,7 +2252,7 @@ function M.run(ctx)
|
|||||||
duffle.ensure_dir(ctx.out_root)
|
duffle.ensure_dir(ctx.out_root)
|
||||||
|
|
||||||
-- Step 0: layout validation. Bail out safely if the .debug_info layout doesn't match what we expect (crt CU + DWARF5 main CU + final 0 byte).
|
-- Step 0: layout validation. Bail out safely if the .debug_info layout doesn't match what we expect (crt CU + DWARF5 main CU + final 0 byte).
|
||||||
-- A layout mismatch means the gcc emission changed; the safest response is to leave existing sections unchanged and emit no synthetic data, so the build's debug-info step never silently produces broken DWARF.
|
-- A layout mismatch means the gcc emission changed; the safest response is to leave existing sections unchanged and emit no synthetic data, so the build's debug-info step never silently produces broken DWARF.
|
||||||
local existing_info = existing_sections[".debug_info"] or ""
|
local existing_info = existing_sections[".debug_info"] or ""
|
||||||
local existing_abbrev = existing_sections[".debug_abbrev"] or ""
|
local existing_abbrev = existing_sections[".debug_abbrev"] or ""
|
||||||
local main_cu_start, main_cu_end_excl, main_abbrev_offset = find_main_cu_layout(existing_info)
|
local main_cu_start, main_cu_end_excl, main_abbrev_offset = find_main_cu_layout(existing_info)
|
||||||
@@ -2299,7 +2287,7 @@ function M.run(ctx)
|
|||||||
local new_info = build_debug_info_section(existing_info, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
local new_info = build_debug_info_section(existing_info, main_cu_start, main_cu_end_excl, new_abbrev_offset, atom_table, rbind_structs, loclists_offsets, registries)
|
||||||
|
|
||||||
-- Step 2b: rebuild .debug_str now that we know which RR_<R_Name> entries get emitted.
|
-- Step 2b: rebuild .debug_str now that we know which RR_<R_Name> entries get emitted.
|
||||||
-- This aligns with build_debug_info_section's by_alias loop.
|
-- This aligns with build_debug_info_section's by_alias loop.
|
||||||
local new_str = build_debug_str_section(existing_sections[".debug_str"] or "", atom_table, registries)
|
local new_str = build_debug_str_section(existing_sections[".debug_str"] or "", atom_table, registries)
|
||||||
|
|
||||||
-- Step 3-5: independent sections.
|
-- Step 3-5: independent sections.
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
|||||||
-- Convert the recursive walk's body-relative line numbers into physical source lines once.
|
-- Convert the recursive walk's body-relative line numbers into physical source lines once.
|
||||||
-- The walker builds `line_of` from `body_text` and stamps body-relative line numbers (1..N) into `item.line` and `invocation.call_line`.
|
-- The walker builds `line_of` from `body_text` and stamps body-relative line numbers (1..N) into `item.line` and `invocation.call_line`.
|
||||||
-- This function converts those values to physical source lines at the close site with the forwarded source `line_of` closure.
|
-- This function converts those values to physical source lines at the close site with the forwarded source `line_of` closure.
|
||||||
--
|
|
||||||
-- `call_line` discipline:
|
-- `call_line` discipline:
|
||||||
-- * ROOT invocations (`inv.parent_id == 0`) receive body-relative `call_line` values directly from `M.LineIndex(body_text)` in the walker.
|
-- * ROOT invocations (`inv.parent_id == 0`) receive body-relative `call_line` values directly from `M.LineIndex(body_text)` in the walker.
|
||||||
-- The source `line_of` closure supplies physical lines at the close site, so this function converts each root value exactly once.
|
-- The source `line_of` closure supplies physical lines at the close site, so this function converts each root value exactly once.
|
||||||
@@ -59,10 +58,9 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- `root_body_line` is the physical source line of the ATOM HEADER byte containing the opening `{`; that byte is one byte BEFORE `atom_record.body_off`.
|
-- `root_body_line` is the physical source line of the ATOM HEADER byte containing the opening `{`; that byte is one byte BEFORE `atom_record.body_off`.
|
||||||
-- The walker assigns line 2 to the body's first content line because line 1 is the trailing `\n` after `{`. Body-text line k therefore maps to `root_body_line + (k - 1)`.
|
-- The walker assigns line 2 to the body's first content line because line 1 is the trailing `\n` after `{`. Body-text line k therefore maps to `root_body_line + (k - 1)`.
|
||||||
-- `body_off - 1` points at the opening `{`, whose line index identifies the header line. `body_off` points after `{` and would shift every word row forward by one line.
|
-- `body_off - 1` points at the opening `{`, whose line index identifies the header line. `body_off` points after `{` and would shift every word row forward by one line.
|
||||||
local root_body_line = root_line_of(atom_record.body_off - 1)
|
local root_body_line = root_line_of(atom_record.body_off - 1) or atom_record.line or 0
|
||||||
or atom_record.line or 0
|
|
||||||
local component_index = corpus.component_body_index or {}
|
local component_index = corpus.component_body_index or {}
|
||||||
local word_items = {}
|
local word_items = {}
|
||||||
|
|
||||||
for _, item in ipairs(projection.items) do
|
for _, item in ipairs(projection.items) do
|
||||||
if item.kind == "word" then word_items[#word_items + 1] = item end
|
if item.kind == "word" then word_items[#word_items + 1] = item end
|
||||||
@@ -102,7 +100,7 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Normalize `inv.call_line` to a physical source line.
|
-- Normalize `inv.call_line` to a physical source line.
|
||||||
-- * ROOT invocations (`parent_id == 0`) carry body-relative `call_line` values from `M.LineIndex(body_text)`; convert them once with `root_body_line`.
|
-- * ROOT invocations (`parent_id == 0`) carry body-relative `call_line` values from `M.LineIndex(body_text)`; convert them once with `root_body_line`.
|
||||||
-- * INNER invocations (`parent_id ~= 0`) carry physical `call_line` values from the component's `line_of`; retain them unchanged.
|
-- * INNER invocations (`parent_id ~= 0`) carry physical `call_line` values from the component's `line_of`; retain them unchanged.
|
||||||
for _, inv in ipairs(projection.invocations) do
|
for _, inv in ipairs(projection.invocations) do
|
||||||
if inv.parent_id == 0 then
|
if inv.parent_id == 0 then
|
||||||
@@ -114,14 +112,14 @@ local function stamp_root_provenance(projection, atom_record, src, corpus)
|
|||||||
-- `atoms_source_map` and `dwarf_injection` read `inv.body_lines[k]` directly from the invocation record created here.
|
-- `atoms_source_map` and `dwarf_injection` read `inv.body_lines[k]` directly from the invocation record created here.
|
||||||
-- Component words already carry physical `item.line` values from the walker's COMPONENT line index, so `body_line_for` returns them unchanged.
|
-- Component words already carry physical `item.line` values from the walker's COMPONENT line index, so `body_line_for` returns them unchanged.
|
||||||
for _, inv in ipairs(projection.invocations) do
|
for _, inv in ipairs(projection.invocations) do
|
||||||
local sw = inv.start_word
|
local sw = inv.start_word
|
||||||
local ew = inv.end_word
|
local ew = inv.end_word
|
||||||
local bls = {}
|
local bls = {}
|
||||||
for i = sw, ew do
|
for i = sw, ew do
|
||||||
local it = projection.items and projection.items[i]
|
local it = projection.items and projection.items[i]
|
||||||
if it and it.kind == "word" then
|
if it and it.kind == "word" then
|
||||||
local fake_event = { invocation_ids = { inv.id } }
|
local fake_event = { invocation_ids = { inv.id } }
|
||||||
bls[#bls + 1] = body_line_for(fake_event, it) or 0
|
bls[#bls + 1] = body_line_for(fake_event, it) or 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
inv.body_lines = bls
|
inv.body_lines = bls
|
||||||
|
|||||||
+61
-52
@@ -3,12 +3,11 @@
|
|||||||
--- Reads the pre-scanned SourceScan payload (produced once upstream by `duffle.scan_source`)
|
--- Reads the pre-scanned SourceScan payload (produced once upstream by `duffle.scan_source`)
|
||||||
--- for `MipsAtom_(name)` and `MipsCode code_<name>` declarations, computes the word offset
|
--- for `MipsAtom_(name)` and `MipsCode code_<name>` declarations, computes the word offset
|
||||||
--- from each `atom_offset(F, T)` marker to its target `atom_label(T)` declaration, and emits
|
--- from each `atom_offset(F, T)` marker to its target `atom_label(T)` declaration, and emits
|
||||||
--- `<dir_basename>.offsets.h` with one `#define _atom_offset_F_T = N` per branch.
|
--- `gen/offsets.h` with one `#define _atom_offset_F_T = N` per branch.
|
||||||
|
--- Per-directory aggregation: every source in the same directory contributes to the same `gen/offsets.h`.
|
||||||
|
--- The directory itself is the namespace; the filename does not repeat the module name.
|
||||||
---
|
---
|
||||||
--- The offset is `target_word - branch_word - 1` (the standard MIPS branch-immediate encoding: branch_offset = relative_pc_in_words - 1).
|
--- The offset is `target_word - branch_word - 1` (the standard MIPS branch-immediate encoding: branch_offset = relative_pc_in_words - 1).
|
||||||
---
|
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
|
||||||
--- Lua 5.3 compatible.
|
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
@@ -16,12 +15,11 @@
|
|||||||
|
|
||||||
-- Bootstrap: same as entry scripts. See `ps1_meta.lua` for the rationale.
|
-- Bootstrap: same as entry scripts. 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 "./"
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Constants
|
-- Constants
|
||||||
@@ -39,35 +37,35 @@ local OFFSET_MACRO_COL = 44
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- @class SourceFile
|
--- @class SourceFile
|
||||||
--- @field path string -- absolute path to the source file
|
--- @field path string -- Absolute path to the source file
|
||||||
--- @field text string -- the full source text
|
--- @field text string -- Full source text
|
||||||
--- @field dir string -- the directory containing the source
|
--- @field dir string -- Directory containing the source
|
||||||
--- @field basename string -- filename without extension
|
--- @field basename string -- Filename without extension
|
||||||
--- @field scan table -- pre-scanned SourceScan payload (from duffle.scan_source)
|
--- @field scan table -- Pre-scanned SourceScan payload (from duffle.scan_source)
|
||||||
|
|
||||||
--- @class PassCtx
|
--- @class PassCtx
|
||||||
--- @field shared table -- cross-pass shared state
|
--- @field shared table -- Cross-pass shared state
|
||||||
--- @field shared.corpus table -- canonical corpus projection
|
--- @field shared.corpus table -- Corpus projection
|
||||||
--- @field shared.word_counts table
|
--- @field shared.word_counts table
|
||||||
--- @field out_root string -- output root (e.g. "build/gen")
|
--- @field out_root string -- Output root (e.g. "build/gen")
|
||||||
|
|
||||||
--- @class PassResult
|
--- @class PassResult
|
||||||
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
||||||
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
||||||
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
||||||
|
|
||||||
--- @class BranchOffset
|
--- @class BranchOffset
|
||||||
--- @field tag string -- the marker tag (e.g. "F" in `atom_offset(F, T)`)
|
--- @field tag string -- Marker tag (e.g. "F" in `atom_offset(F, T)`)
|
||||||
--- @field target string -- the target label name (e.g. "T" in `atom_offset(F, T)`)
|
--- @field target string -- Target label name (e.g. "T" in `atom_offset(F, T)`)
|
||||||
--- @field branch_word integer -- branch word position within the atom body
|
--- @field branch_word integer -- Branch word position within the atom body
|
||||||
--- @field offset integer -- computed per consuming instruction (see `compute_offsets`)
|
--- @field offset integer -- Computed per consuming instruction (see `compute_offsets`)
|
||||||
--- @field consuming_encoder string|nil -- the instruction consuming the offset (e.g. "branch_le_zero", "jump", "call_addr")
|
--- @field consuming_encoder string|nil -- Instruction consuming the offset (e.g. "branch_le_zero", "jump", "call_addr")
|
||||||
--- @field consuming_arg_pos integer|nil -- 1-based arg position within the consuming instruction's arg list
|
--- @field consuming_arg_pos integer|nil -- 1-based arg position within the consuming instruction's arg list
|
||||||
|
|
||||||
--- @class AtomData
|
--- @class AtomData
|
||||||
--- @field name string -- atom name
|
--- @field name string -- Atom name
|
||||||
--- @field total_words integer -- total word count of the atom body
|
--- @field total_words integer -- Total word count of the atom body
|
||||||
--- @field offsets BranchOffset[] -- per-branch offset list
|
--- @field offsets BranchOffset[] -- Per-branch offset list
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Canonical marker projection
|
-- Canonical marker projection
|
||||||
@@ -99,7 +97,7 @@ local function project_markers(markers)
|
|||||||
local state = { labels = {}, branches = {} }
|
local state = { labels = {}, branches = {} }
|
||||||
for _, marker in ipairs(markers or {}) do
|
for _, marker in ipairs(markers or {}) do
|
||||||
local project = MARKER_PROJECTORS[marker.kind]
|
local project = MARKER_PROJECTORS[marker.kind]
|
||||||
if project then project(state, marker) end
|
if project then project(state, marker) end
|
||||||
end
|
end
|
||||||
return state.labels, state.branches
|
return state.labels, state.branches
|
||||||
end
|
end
|
||||||
@@ -109,7 +107,6 @@ end
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- Compute branch offsets per consuming instruction.
|
--- Compute branch offsets per consuming instruction.
|
||||||
---
|
|
||||||
--- Disposition table:
|
--- Disposition table:
|
||||||
--- `branch_*` -> relative offset: `target_word - branch_word - 1` (MIPS branch-immediate encoding).
|
--- `branch_*` -> relative offset: `target_word - branch_word - 1` (MIPS branch-immediate encoding).
|
||||||
--- `jump` / `call_addr` -> same value as `branch_*` (a relative word offset).
|
--- `jump` / `call_addr` -> same value as `branch_*` (a relative word offset).
|
||||||
@@ -121,7 +118,7 @@ end
|
|||||||
---
|
---
|
||||||
--- Top-level `atom_offset(F, T)` markers (where the marker is the entire token — `consuming_encoder` == nil) default to `branch_*` behavior (relative offset).
|
--- Top-level `atom_offset(F, T)` markers (where the marker is the entire token — `consuming_encoder` == nil) default to `branch_*` behavior (relative offset).
|
||||||
--- This preserves backward compatibility for any top-level marker that may exist outside a control-transfer instruction.
|
--- This preserves backward compatibility for any top-level marker that may exist outside a control-transfer instruction.
|
||||||
--- @param labels table<string, integer>
|
--- @param labels table<string, integer>
|
||||||
--- @param branches table[]
|
--- @param branches table[]
|
||||||
--- @return BranchOffset[]
|
--- @return BranchOffset[]
|
||||||
local function compute_offsets(labels, branches)
|
local function compute_offsets(labels, branches)
|
||||||
@@ -195,44 +192,48 @@ local function emit_atom_offsets(add, atom)
|
|||||||
add("")
|
add("")
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Generate the per-source .offsets.h header.
|
--- Generate the per-directory .offsets.h header.
|
||||||
--- @param source_path string
|
--- @param dir string -- the absolute source directory
|
||||||
--- @param atoms_data AtomData[]
|
--- @param sources table[] -- sources contributing to this directory (for the header comment)
|
||||||
|
--- @param atoms_data AtomData[]
|
||||||
--- @return string
|
--- @return string
|
||||||
local function generate_header(source_path, atoms_data)
|
local function generate_header(dir, sources, atoms_data)
|
||||||
local basename = duffle.basename_no_ext(source_path)
|
local dir_basename = duffle.basename_no_ext(dir)
|
||||||
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
local function add(s) lines[#lines + 1] = s end
|
local function add(s) lines[#lines + 1] = s end
|
||||||
|
|
||||||
add("// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT")
|
add("// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT")
|
||||||
add("// Source: " .. source_path)
|
add("// Directory: " .. dir:gsub("/", "\\") .. "\\")
|
||||||
|
for _, src in ipairs(sources) do
|
||||||
|
add("// source: " .. src.path:gsub("/", "\\"))
|
||||||
|
end
|
||||||
add("#pragma once")
|
add("#pragma once")
|
||||||
add("")
|
add("")
|
||||||
add("#pragma region " .. basename)
|
add("#pragma region " .. dir_basename)
|
||||||
add("")
|
add("")
|
||||||
add("")
|
add("")
|
||||||
for _, atom in ipairs(atoms_data) do
|
for _, atom in ipairs(atoms_data) do
|
||||||
emit_atom_offsets(add, atom)
|
emit_atom_offsets(add, atom)
|
||||||
end
|
end
|
||||||
add("#pragma endregion " .. basename)
|
add("#pragma endregion " .. dir_basename)
|
||||||
add("")
|
add("")
|
||||||
return table.concat(lines, "\n") .. "\n"
|
return table.concat(lines, "\n") .. "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
--- (internal) Process one source: render offsets from canonical atom paths.
|
--- (internal) Aggregate atoms from every source in one directory, render the per-directory `offsets.h`.
|
||||||
--- Returns the offsets_h path if a header was written, or nil.
|
--- Returns the offsets_h path if a header was written, or nil.
|
||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @param src SourceFile
|
--- @param dir string -- the absolute source directory
|
||||||
|
--- @param sources SourceFile[] -- sources in this directory
|
||||||
--- @return string|nil -- the offsets_h path
|
--- @return string|nil -- the offsets_h path
|
||||||
local function process_source(ctx, src)
|
local function process_directory(ctx, dir, sources)
|
||||||
local atoms_data = {}
|
local atoms_data = {}
|
||||||
local scan = src.scan or {}
|
|
||||||
|
|
||||||
local function append_atom(atom)
|
local function append_atom(atom)
|
||||||
local paths = atom and atom.paths
|
local paths = atom and atom.paths
|
||||||
if not paths then return end
|
if not paths then return end
|
||||||
local labels, branches = project_markers(paths.markers)
|
local labels, branches = project_markers(paths.markers)
|
||||||
atoms_data[#atoms_data + 1] = {
|
atoms_data[#atoms_data + 1] = {
|
||||||
@@ -242,19 +243,22 @@ local function process_source(ctx, src)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, atom in ipairs(scan.atoms or {}) do append_atom(atom) end
|
for _, src in ipairs(sources) do
|
||||||
for _, atom in ipairs(scan.raw_atoms or {}) do append_atom(atom) end
|
local scan = src.scan or {}
|
||||||
|
for _, atom in ipairs(scan.atoms or {}) do append_atom(atom) end
|
||||||
|
for _, atom in ipairs(scan.raw_atoms or {}) do append_atom(atom) end
|
||||||
|
end
|
||||||
if #atoms_data == 0 then return nil end
|
if #atoms_data == 0 then return nil end
|
||||||
|
|
||||||
local out_path = src.dir .. "/gen/" .. duffle.basename_no_ext(src.dir) .. ".offsets.h"
|
local out_path = dir .. "/gen/offsets.h"
|
||||||
duffle.ensure_dir(duffle.dirname(out_path))
|
duffle.ensure_dir(duffle.dirname(out_path))
|
||||||
duffle.write_file(out_path, generate_header(src.path:gsub("/", "\\"), atoms_data))
|
duffle.write_file(out_path, generate_header(dir, sources, atoms_data))
|
||||||
return out_path
|
return out_path
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Run the offsets pass.
|
--- Run the offsets pass.
|
||||||
--- For each canonical source, emits a per-module `<dir_basename>.offsets.h`
|
--- For each canonical source-directory, emits a per-directory `gen/offsets.h`
|
||||||
--- containing constants for every marker recorded in atom.paths.
|
--- containing constants for every marker recorded in atom.paths across every source in that directory.
|
||||||
--- @param ctx PassCtx
|
--- @param ctx PassCtx
|
||||||
--- @return PassResult
|
--- @return PassResult
|
||||||
function M.run(ctx)
|
function M.run(ctx)
|
||||||
@@ -263,12 +267,17 @@ function M.run(ctx)
|
|||||||
local warnings = {}
|
local warnings = {}
|
||||||
|
|
||||||
local corpus = ctx.shared and ctx.shared.corpus
|
local corpus = ctx.shared and ctx.shared.corpus
|
||||||
if type(corpus) ~= "table" or type(corpus.source_order) ~= "table" then
|
if type(corpus) ~= "table" then
|
||||||
error("offsets.run requires ctx.shared.corpus.source_order (canonical corpus).", 0)
|
error("offsets.run requires ctx.shared.corpus", 0)
|
||||||
|
end
|
||||||
|
if type(corpus.source_order) ~= "table" then
|
||||||
|
error("offsets.run requires ctx.shared.corpus.source_order.", 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, src in ipairs(corpus.source_order) do
|
-- Per-directory aggregation: every source in the same directory contributes to one `gen/offsets.h`.
|
||||||
local out_path = process_source(ctx, src)
|
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order)
|
||||||
|
for dir, sources in pairs(sources_by_dir) do
|
||||||
|
local out_path = process_directory(ctx, dir, sources)
|
||||||
if out_path then
|
if out_path then
|
||||||
outputs[#outputs + 1] = { offsets_h = out_path }
|
outputs[#outputs + 1] = { offsets_h = out_path }
|
||||||
end
|
end
|
||||||
|
|||||||
+77
-76
@@ -1,22 +1,21 @@
|
|||||||
--- passes/report.lua — Per-MODULE annotation report renderer +
|
--- passes/report.lua — Per-MODULE annotation report renderer + project-wide summary writer.
|
||||||
--- project-wide summary writer.
|
|
||||||
---
|
---
|
||||||
--- Two output files per build:
|
--- Two output files per build:
|
||||||
--- - `build/gen/<dir_basename>.annotations.txt` — one per source-directory containing atoms; aggregates across all sources in the directory.
|
--- - `build/gen/<dir_basename>.annotations.txt` — one per source-directory containing atoms; aggregates across all sources in the directory.
|
||||||
--- - `build/gen/annotation_validation.txt` — the project summary.
|
--- - `build/gen/annotation_validation.txt` — the project summary.
|
||||||
---
|
---
|
||||||
--- 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 dir projection directly and re-validates each source via `annotation.validate()` to get the detailed per-source results.
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Module-scope requires + package.path setup
|
-- Module-scope requires + package.path setup
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
-- 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 both standalone and when require'd from the orchestrator.
|
-- Uses `debug.getinfo` to find this file's own directory, so it works 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 "./"
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||||
@@ -37,7 +36,7 @@ local atoms_source_map = dofile(_bootstrap_dir .. "atoms_source_map.lua")
|
|||||||
|
|
||||||
-- Section separators used in the rendered text reports.
|
-- Section separators used in the rendered text reports.
|
||||||
-- The thin rules are hand-tuned to align with the per-section content width; do not change without also checking the section renderers below.
|
-- The thin rules are hand-tuned to align with the per-section content width; do not change without also checking the section renderers below.
|
||||||
local RULE_THICK = "========================================================"
|
local RULE_THICK = "========================================================"
|
||||||
local SECTION_HEADER_ATOMS = "── Atoms ────────────────────────────────────────────────"
|
local SECTION_HEADER_ATOMS = "── Atoms ────────────────────────────────────────────────"
|
||||||
local SECTION_HEADER_ANNOTS = "── Annotations ──────────────────────────────────────────"
|
local SECTION_HEADER_ANNOTS = "── Annotations ──────────────────────────────────────────"
|
||||||
local SECTION_HEADER_BINDS = "── Binds_* structs ──────────────────────────────────────"
|
local SECTION_HEADER_BINDS = "── Binds_* structs ──────────────────────────────────────"
|
||||||
@@ -59,83 +58,83 @@ local PASS_NAME = "report"
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- @class SourceFile
|
--- @class SourceFile
|
||||||
--- @field path string -- absolute path to the source file
|
--- @field path string -- Absolute path to the source file
|
||||||
--- @field text string -- the full source text
|
--- @field text string -- Full source text
|
||||||
--- @field dir string -- the directory containing the source
|
--- @field dir string -- Directory containing the source
|
||||||
--- @field basename string -- filename without extension
|
--- @field basename string -- Filename without extension
|
||||||
|
|
||||||
--- @class PassCtx
|
--- @class PassCtx
|
||||||
--- @field sources SourceFile[] -- all source files in the build
|
--- @field sources SourceFile[] -- All source files in the build
|
||||||
--- @field metadata_path string -- path to word_count.metadata.h
|
--- @field metadata_path string -- Path to word_count.metadata.h
|
||||||
--- @field shared table -- cross-pass shared state
|
--- @field shared table -- Cross-pass shared state
|
||||||
--- @field out_root string -- output root (e.g. "build/gen")
|
--- @field out_root string -- Output root (e.g. "build/gen")
|
||||||
--- @field project_root string -- project root (e.g. "code/")
|
--- @field project_root string -- Project root (e.g. "code/")
|
||||||
--- @field upstream table<string, table> -- per-pass upstream outputs
|
--- @field upstream table<string, table> -- Per-pass upstream outputs
|
||||||
--- @field flags table -- CLI flags + per-pass stash
|
--- @field flags table -- CLI flags + per-pass stash
|
||||||
--- @field verbose boolean -- if true, log diagnostic info
|
--- @field verbose boolean -- If true, log diagnostic info
|
||||||
|
|
||||||
--- @class PassResult
|
--- @class PassResult
|
||||||
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
--- @field outputs table[] -- {kind=, path=} entries describing emit files
|
||||||
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
--- @field errors table[] -- {line=, msg=} entries; build-stops
|
||||||
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
--- @field warnings table[] -- {line=, msg=} entries; build-succeeds
|
||||||
|
|
||||||
-- Shapes produced by `passes/annotation.lua`'s `M.validate()`.
|
-- Shapes produced by `passes/annotation.lua`'s `M.validate()`.
|
||||||
|
|
||||||
--- @class AtomEntry
|
--- @class AtomEntry
|
||||||
--- @field name string -- atom name (e.g. "cube_g4_face")
|
--- @field name string -- Atom name (e.g. "cube_g4_face")
|
||||||
--- @field line integer -- source line of the atom declaration
|
--- @field line integer -- Source line of the atom declaration
|
||||||
|
|
||||||
--- @class AnnotEntry
|
--- @class AnnotEntry
|
||||||
--- @field line integer -- source line
|
--- @field line integer -- Source line
|
||||||
--- @field macro string -- the macro name (e.g. "atom_reads")
|
--- @field macro string -- Macro name (e.g. "atom_reads")
|
||||||
--- @field name string -- the atom name (if a `name(...)` was given)
|
--- @field name string -- Atom name (if a `name(...)` was given)
|
||||||
--- @field kind string -- "atom_info" | "atom_bind" | ...
|
--- @field kind string -- "atom_info" | "atom_bind" | ...
|
||||||
--- @field binds string|nil -- Binds_X name if any
|
--- @field binds string|nil -- Binds_X name if any
|
||||||
--- @field reads string[] -- R_* names (read targets)
|
--- @field reads string[] -- R_* names (read targets)
|
||||||
--- @field writes string[] -- R_* names (write targets)
|
--- @field writes string[] -- R_* names (write targets)
|
||||||
--- @field error string|nil -- error message if annotation was malformed
|
--- @field error string|nil -- Error message if annotation was malformed
|
||||||
|
|
||||||
--- @class BindsField
|
--- @class BindsField
|
||||||
--- @field name string -- field name
|
--- @field name string -- Field name
|
||||||
--- @field offset integer -- byte offset within the Binds_X struct
|
--- @field offset integer -- Byte offset within the Binds_X struct
|
||||||
|
|
||||||
--- @class BindsStruct
|
--- @class BindsStruct
|
||||||
--- @field name string -- struct name (e.g. "Binds_Floor")
|
--- @field name string -- Struct name (e.g. "Binds_Floor")
|
||||||
--- @field line integer -- source line of the typedef
|
--- @field line integer -- Source line of the typedef
|
||||||
--- @field bytes integer -- total byte size
|
--- @field bytes integer -- Total byte size
|
||||||
--- @field fields BindsField[] -- the field list
|
--- @field fields BindsField[] -- The field list
|
||||||
|
|
||||||
--- @class MacroEntry
|
--- @class MacroEntry
|
||||||
--- @field name string -- macro name (e.g. "WORD_COUNT(my_macro, 4)")
|
--- @field name string -- Macro name (e.g. "WORD_COUNT(my_macro, 4)")
|
||||||
--- @field line integer -- source line
|
--- @field line integer -- Source line
|
||||||
--- @field words integer -- declared word count
|
--- @field words integer -- Declared word count
|
||||||
|
|
||||||
--- @class Finding
|
--- @class Finding
|
||||||
--- @field line integer -- source line
|
--- @field line integer -- Source line
|
||||||
--- @field msg string -- finding message
|
--- @field msg string -- Finding message
|
||||||
|
|
||||||
--- @class AnnotationResult
|
--- @class AnnotationResult
|
||||||
--- @field source string -- set by this pass; original source path
|
--- @field source string -- Set by this pass; original source path
|
||||||
--- @field atoms AtomEntry[] -- atom declarations in this source
|
--- @field atoms AtomEntry[] -- Atom declarations in this source
|
||||||
--- @field annots AnnotEntry[] -- annotation entries
|
--- @field annots AnnotEntry[] -- Annotation entries
|
||||||
--- @field macros MacroEntry[] -- macro word-count declarations
|
--- @field macros MacroEntry[] -- Macro word-count declarations
|
||||||
--- @field binds BindsStruct[] -- Binds_* struct declarations
|
--- @field binds BindsStruct[] -- Binds_* struct declarations
|
||||||
--- @field errors Finding[] -- errors from validation
|
--- @field errors Finding[] -- Errors from validation
|
||||||
--- @field warnings Finding[] -- warnings from validation
|
--- @field warnings Finding[] -- Warnings from validation
|
||||||
--- @field info table -- info summary (not rendered here)
|
--- @field info table -- Info summary (not rendered here)
|
||||||
|
|
||||||
--- @class ModuleEntry
|
--- @class ModuleEntry
|
||||||
--- @field dir string -- absolute directory path
|
--- @field dir string -- Absolute directory path
|
||||||
--- @field dir_basename string -- basename (e.g. "duffle", "gte_hello")
|
--- @field dir_basename string -- Basename (e.g. "duffle", "gte_hello")
|
||||||
--- @field atoms_count integer -- pre-counted atoms for filtering
|
--- @field atoms_count integer -- Pre-counted atoms for filtering
|
||||||
|
|
||||||
--- @class ModuleReport
|
--- @class ModuleReport
|
||||||
--- @field dir string -- module directory
|
--- @field dir string -- Module directory
|
||||||
--- @field sources SourceFile[] -- sources in this module
|
--- @field sources SourceFile[] -- Sources in this module
|
||||||
--- @field results AnnotationResult[] -- per-source validate() results
|
--- @field results AnnotationResult[] -- Per-source validate() results
|
||||||
|
|
||||||
--- @class ProjectReport
|
--- @class ProjectReport
|
||||||
--- @field results AnnotationResult[] -- all per-source results
|
--- @field results AnnotationResult[] -- All per-source results
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Per-MODULE annotation report (aggregated across all sources in a dir)
|
-- Per-MODULE annotation report (aggregated across all sources in a dir)
|
||||||
@@ -153,9 +152,16 @@ end
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- Render the thin project-wide summary (`build/atom_meta_report.summary.md`).
|
--- Render the thin project-wide summary (`build/atom_meta_report.summary.md`).
|
||||||
--- @param all_results { module:string, atoms:integer, annots:integer, binds:integer,
|
--- @param all_results {
|
||||||
--- macros:integer, findings:integer, errors:integer,
|
--- module:string,
|
||||||
--- warnings:integer, info:integer }[]
|
--- atoms:integer,
|
||||||
|
--- annots:integer,
|
||||||
|
--- binds:integer,
|
||||||
|
--- macros:integer,
|
||||||
|
--- findings:integer,
|
||||||
|
--- errors:integer,
|
||||||
|
--- warnings:integer,
|
||||||
|
--- info:integer }[]
|
||||||
--- @return string
|
--- @return string
|
||||||
local function render_project_summary(all_results)
|
local function render_project_summary(all_results)
|
||||||
local lines = {
|
local lines = {
|
||||||
@@ -165,13 +171,10 @@ local function render_project_summary(all_results)
|
|||||||
"| module | atoms | annots | binds | macros | findings | errors | warnings | info |",
|
"| module | atoms | annots | binds | macros | findings | errors | warnings | info |",
|
||||||
"|--------|-------|--------|-------|--------|----------|--------|----------|------|",
|
"|--------|-------|--------|-------|--------|----------|--------|----------|------|",
|
||||||
}
|
}
|
||||||
local totals = { atoms = 0, annots = 0, binds = 0, macros = 0,
|
local totals = { atoms = 0, annots = 0, binds = 0, macros = 0, findings = 0, errors = 0, warnings = 0, info = 0 }
|
||||||
findings = 0, errors = 0, warnings = 0, info = 0 }
|
|
||||||
for _, e in ipairs(all_results) do
|
for _, e in ipairs(all_results) do
|
||||||
lines[#lines + 1] = string.format(
|
lines[#lines + 1] = string.format("| %s | %d | %d | %d | %d | %d | %d | %d | %d |"
|
||||||
"| %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)
|
||||||
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.atoms = totals.atoms + e.atoms
|
||||||
totals.annots = totals.annots + e.annots
|
totals.annots = totals.annots + e.annots
|
||||||
totals.binds = totals.binds + e.binds
|
totals.binds = totals.binds + e.binds
|
||||||
@@ -181,23 +184,21 @@ local function render_project_summary(all_results)
|
|||||||
totals.warnings = totals.warnings + e.warnings
|
totals.warnings = totals.warnings + e.warnings
|
||||||
totals.info = totals.info + e.info
|
totals.info = totals.info + e.info
|
||||||
end
|
end
|
||||||
lines[#lines + 1] = string.format(
|
lines[#lines + 1] = string.format("| **TOTAL** | %d | %d | %d | %d | %d | %d | %d | %d |"
|
||||||
"| **TOTAL** | %d | %d | %d | %d | %d | %d | %d | %d |",
|
, totals.atoms, totals.annots, totals.binds, totals.macros, totals.findings, totals.errors, totals.warnings, totals.info)
|
||||||
totals.atoms, totals.annots, totals.binds, totals.macros,
|
|
||||||
totals.findings, totals.errors, totals.warnings, totals.info)
|
|
||||||
return table.concat(lines, "\n") .. "\n"
|
return table.concat(lines, "\n") .. "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Render the per-module verbose source-map markdown (`build/<module>.atoms.md`).
|
--- Render the per-module verbose source-map markdown (`build/<module>.atoms.md`).
|
||||||
--- Per-source sub-section, per-atom stanza with sourcemap + provenance rows.
|
--- Per-source sub-section, per-atom stanza with sourcemap + provenance rows.
|
||||||
--- Pulls sourcemap + provenance from `atoms_source_map` (no second source walk).
|
--- Pulls sourcemap + provenance from `atoms_source_map` (no second source walk).
|
||||||
--- @param dir string
|
--- @param dir string
|
||||||
--- @param dir_sources SourceFile[]
|
--- @param dir_sources SourceFile[]
|
||||||
--- @param wc table<string, integer>
|
--- @param wc table<string, integer>
|
||||||
--- @return string
|
--- @return string
|
||||||
local function render_module_atoms_md(dir, dir_sources, wc)
|
local function render_module_atoms_md(dir, dir_sources, wc)
|
||||||
local dir_basename = source_basename(dir)
|
local dir_basename = source_basename(dir)
|
||||||
local lines = {
|
local lines = {
|
||||||
"# " .. dir_basename .. " — atoms (verbose source map)",
|
"# " .. dir_basename .. " — atoms (verbose source map)",
|
||||||
"> Per-word call-site + provenance. Auto-generated.",
|
"> Per-word call-site + provenance. Auto-generated.",
|
||||||
"",
|
"",
|
||||||
@@ -249,14 +250,14 @@ end
|
|||||||
--- Aggregates annotation + static-analysis content across all sources in `dir`.
|
--- Aggregates annotation + static-analysis content across all sources in `dir`.
|
||||||
--- Annotations come from re-running `annotation.validate()` per source (the existing pattern);
|
--- Annotations come from re-running `annotation.validate()` per source (the existing pattern);
|
||||||
--- static-analysis comes from `corpus.static_analysis_results[dir_basename]` (populated by `static_analysis.lua` — no second corpus_pipe_ctx build).
|
--- 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 string
|
||||||
--- @param dir_sources SourceFile[]
|
--- @param dir_sources SourceFile[]
|
||||||
--- @param annot_results AnnotationResult[]
|
--- @param annot_results AnnotationResult[]
|
||||||
--- @param sa_results table -- corpus.static_analysis_results[dir_basename]
|
--- @param sa_results table -- corpus.static_analysis_results[dir_basename]
|
||||||
--- @return string
|
--- @return string
|
||||||
local function render_module_meta_report(dir, dir_sources, annot_results, sa_results)
|
local function render_module_meta_report(dir, dir_sources, annot_results, sa_results)
|
||||||
local dir_basename = source_basename(dir)
|
local dir_basename = source_basename(dir)
|
||||||
local lines = {
|
local lines = {
|
||||||
"# " .. dir_basename .. " — atom meta report",
|
"# " .. dir_basename .. " — atom meta report",
|
||||||
"> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.",
|
"> Auto-generated by ps1_meta.lua (passes/report.lua). Do not edit.",
|
||||||
"",
|
"",
|
||||||
@@ -326,8 +327,8 @@ local function render_module_meta_report(dir, dir_sources, annot_results, sa_res
|
|||||||
local binds = a.binds or "—"
|
local binds = a.binds or "—"
|
||||||
local reads = (#a.reads > 0 and table.concat(a.reads, ",")) or "—"
|
local reads = (#a.reads > 0 and table.concat(a.reads, ",")) or "—"
|
||||||
local writes = (#a.writes > 0 and table.concat(a.writes, ",")) or "—"
|
local writes = (#a.writes > 0 and table.concat(a.writes, ",")) or "—"
|
||||||
add(string.format("| %s | %d | %s | %s | %s | %s |",
|
add(string.format("| %s | %d | %s | %s | %s | %s |"
|
||||||
src_name, a.line, a.name, binds, reads, writes))
|
, src_name, a.line, a.name, binds, reads, writes))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -418,7 +419,7 @@ local function render_module_meta_report(dir, dir_sources, annot_results, sa_res
|
|||||||
for _, a in ipairs(sorted) do
|
for _, a in ipairs(sorted) do
|
||||||
local p = a.paths or {}
|
local p = a.paths or {}
|
||||||
local src_name = a.source_path and source_basename(a.source_path) or ""
|
local src_name = a.source_path and source_basename(a.source_path) or ""
|
||||||
local notes = ""
|
local notes = ""
|
||||||
if p.has_loops then notes = notes .. " [loop!]" end
|
if p.has_loops then notes = notes .. " [loop!]" end
|
||||||
if p.unknown_macros and #p.unknown_macros > 0 then
|
if p.unknown_macros and #p.unknown_macros > 0 then
|
||||||
notes = notes .. " [unknown: " .. table.concat(p.unknown_macros, ", ") .. "]"
|
notes = notes .. " [unknown: " .. table.concat(p.unknown_macros, ", ") .. "]"
|
||||||
@@ -426,7 +427,7 @@ local function render_module_meta_report(dir, dir_sources, annot_results, sa_res
|
|||||||
add(string.format("| %s | %s | %d | %d | %d | %d | %s |",
|
add(string.format("| %s | %s | %d | %d | %d | %d | %s |",
|
||||||
a.name, src_name,
|
a.name, src_name,
|
||||||
p.cycles_min or 0, p.cycles_max or 0,
|
p.cycles_min or 0, p.cycles_max or 0,
|
||||||
p.branches or 0, p.paths or 0, notes))
|
p.branches or 0, p.paths or 0, notes))
|
||||||
end
|
end
|
||||||
add("")
|
add("")
|
||||||
|
|
||||||
|
|||||||
+149
-154
@@ -2,7 +2,6 @@
|
|||||||
---
|
---
|
||||||
--- Single source-walk pass that produces the fat `SourceScan` payload consumed by all downstream passes. Walks each corpus source record once,
|
--- Single source-walk pass that produces the fat `SourceScan` payload consumed by all downstream passes. Walks each corpus source record once,
|
||||||
--- extracting every construct type the metaprograms need:
|
--- extracting every construct type the metaprograms need:
|
||||||
---
|
|
||||||
--- MipsAtom_ (kind = "atom", with optional atom_info inner)
|
--- MipsAtom_ (kind = "atom", with optional atom_info inner)
|
||||||
--- MipsAtomComp_ (kind = "comp_bare")
|
--- MipsAtomComp_ (kind = "comp_bare")
|
||||||
--- MipsAtomComp_Proc_ (kind = "comp_proc", body inside last {})
|
--- MipsAtomComp_Proc_ (kind = "comp_proc", body inside last {})
|
||||||
@@ -14,8 +13,6 @@
|
|||||||
--- The result is attached to each `src.scan` so downstream passes can read from `src.scan.atoms` / `src.scan.binds` / etc. without re-walking the source.
|
--- The result is attached to each `src.scan` so downstream passes can read from `src.scan.atoms` / `src.scan.binds` / etc. without re-walking the source.
|
||||||
--- This is the first pass in the dep graph (no deps).
|
--- This is the first pass in the dep graph (no deps).
|
||||||
--- Every other pass that reads source structure depends on this one — see `ps1_meta.lua :: PASSES`.
|
--- Every other pass that reads source structure depends on this one — see `ps1_meta.lua :: PASSES`.
|
||||||
---
|
|
||||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex, Lua 5.3 compatible
|
|
||||||
|
|
||||||
-- Bootstrap: same as entry scripts. See `ps1_meta.lua` for the rationale.
|
-- Bootstrap: same as entry scripts. 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).
|
||||||
@@ -23,7 +20,7 @@
|
|||||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when required).
|
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when required).
|
||||||
-- 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 "./"
|
||||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||||
|
|
||||||
-- Forward declarations for helpers used by earlier parsers (parse_enum_body_fields needs parse_enum_int_literal;
|
-- Forward declarations for helpers used by earlier parsers (parse_enum_body_fields needs parse_enum_int_literal;
|
||||||
-- parse_typedef_binds needs duffle.find_byte).
|
-- parse_typedef_binds needs duffle.find_byte).
|
||||||
@@ -50,12 +47,12 @@ local parse_enum_int_literal
|
|||||||
--- @field line_of fun(pos: integer): integer -- shared LineIndex closure
|
--- @field line_of fun(pos: integer): integer -- shared LineIndex closure
|
||||||
|
|
||||||
--- @class DebugSkipMarker
|
--- @class DebugSkipMarker
|
||||||
--- @field marker_kind string -- exact marker ident read from source. Only "atom_dbg_skip" (bare) is positive; any other ident reaches the unrelated fallback and is never associated with a declaration.
|
--- @field marker_kind string -- Exact marker ident read from source. Only "atom_dbg_skip" (bare) is positive; any other ident reaches the unrelated fallback and is never associated with a declaration.
|
||||||
--- @field marker_line integer -- line of the marker ident start
|
--- @field marker_line integer -- Line of the marker ident start
|
||||||
--- @field marker_pos integer -- byte position of the marker ident start (the comment walker anchors here)
|
--- @field marker_pos integer -- Byte position of the marker ident start (the comment walker anchors here)
|
||||||
--- @field is_bare boolean -- true iff marker_kind == "atom_dbg_skip" AND has_parens == false (the only positive form)
|
--- @field is_bare boolean -- true iff marker_kind == "atom_dbg_skip" AND has_parens == false (the only positive form)
|
||||||
--- @field has_parens boolean -- true iff a `(...)` follows the marker ident (diagnostic-only)
|
--- @field has_parens boolean -- true iff a `(...)` follows the marker ident (diagnostic-only)
|
||||||
--- @field args string|nil -- trimmed args inside the `(...)` (nil when has_parens is false)
|
--- @field args string|nil -- Trimmed args inside the `(...)` (nil when has_parens is false)
|
||||||
--- @field pending boolean -- true while awaiting the following declaration
|
--- @field pending boolean -- true while awaiting the following declaration
|
||||||
--- @field superseded_by_marker_line integer|nil -- set when a newer marker bumped this one out of the pending slot
|
--- @field superseded_by_marker_line integer|nil -- set when a newer marker bumped this one out of the pending slot
|
||||||
--- @field target_kind string|nil -- "atom" | "comp_bare" | "comp_proc" | "unrelated" once observed (nil if no declaration ever followed)
|
--- @field target_kind string|nil -- "atom" | "comp_bare" | "comp_proc" | "unrelated" once observed (nil if no declaration ever followed)
|
||||||
@@ -71,15 +68,15 @@ local parse_enum_int_literal
|
|||||||
--- @field reg string -- "R_T0"
|
--- @field reg string -- "R_T0"
|
||||||
--- @field type_name string
|
--- @field type_name string
|
||||||
--- @field pointer_depth integer
|
--- @field pointer_depth integer
|
||||||
--- @field source_line integer -- line of the call site (callsite or enum-site)
|
--- @field source_line integer -- Line of the call site (callsite or enum-site)
|
||||||
|
|
||||||
--- @class AtomCtxEntry
|
--- @class AtomCtxEntry
|
||||||
--- @field rbind_atom string -- the rbind atom ident that this consumer should propagate types from
|
--- @field rbind_atom string -- The rbind atom ident that this consumer should propagate types from
|
||||||
--- @field info_line integer
|
--- @field info_line integer
|
||||||
--- @field source string -- absolute path of the source file
|
--- @field source string -- Absolute path of the source file
|
||||||
|
|
||||||
--- @class AtomPhaseGroup
|
--- @class AtomPhaseGroup
|
||||||
--- @field atoms string[] -- atom names tagged with this phase label (source-order)
|
--- @field atoms string[] -- Atom names tagged with this phase label (source-order)
|
||||||
|
|
||||||
--- @class AtomViewEntry
|
--- @class AtomViewEntry
|
||||||
--- @field atom_name string -- e.g. "red_cube_g4_face"
|
--- @field atom_name string -- e.g. "red_cube_g4_face"
|
||||||
@@ -88,11 +85,11 @@ local parse_enum_int_literal
|
|||||||
--- @field info_line integer -- line of the atom_info call
|
--- @field info_line integer -- line of the atom_info call
|
||||||
|
|
||||||
--- @class SourceFile
|
--- @class SourceFile
|
||||||
--- @field path string -- absolute path to the source file
|
--- @field path string -- Absolute path to the source file
|
||||||
--- @field text string -- the full source text
|
--- @field text string -- Full source text
|
||||||
--- @field dir string -- the directory containing the source
|
--- @field dir string -- Directory containing the source
|
||||||
--- @field basename string -- filename without extension
|
--- @field basename string -- Filename without extension
|
||||||
--- @field scan table -- pre-scanned SourceScan payload (set by this pass)
|
--- @field scan table -- Pre-scanned SourceScan payload (set by this pass)
|
||||||
|
|
||||||
--- @class PassCtx
|
--- @class PassCtx
|
||||||
--- @field sources SourceFile[]
|
--- @field sources SourceFile[]
|
||||||
@@ -111,15 +108,15 @@ local parse_enum_int_literal
|
|||||||
|
|
||||||
--- @class AtomEntry
|
--- @class AtomEntry
|
||||||
--- @field line integer
|
--- @field line integer
|
||||||
--- @field name string -- atom name (for components: without ac_ prefix)
|
--- @field name string -- Atom name (for components: without ac_ prefix)
|
||||||
--- @field body string -- brace-delimited body (without the braces)
|
--- @field body string -- Brace-delimited body (without the braces)
|
||||||
--- @field body_off integer -- char offset of body[1] in source
|
--- @field body_off integer -- Char offset of body[1] in source
|
||||||
--- @field kind string -- "atom" | "comp_bare" | "comp_proc" | "raw_atom"
|
--- @field kind string -- "atom" | "comp_bare" | "comp_proc" | "raw_atom"
|
||||||
--- @field raw_name string -- un-stripped name (for components: with ac_ prefix)
|
--- @field raw_name string -- Un-stripped name (for components: with ac_ prefix)
|
||||||
--- @field ident_pos integer -- position of the MipsAtom_/MipsAtomComp_ ident start
|
--- @field ident_pos integer -- Position of the MipsAtom_/MipsAtomComp_ ident start
|
||||||
--- @field after_paren integer -- position past the closing paren
|
--- @field after_paren integer -- Position past the closing paren
|
||||||
--- @field debug_skip boolean -- true when an `atom_dbg_skip` bare marker immediately precedes this declaration (sole-owner stamp; see push_debug_skip_marker)
|
--- @field debug_skip boolean -- true when an `atom_dbg_skip` bare marker immediately precedes this declaration (sole-owner stamp; see push_debug_skip_marker)
|
||||||
--- @field declaration_comment string|nil -- populated by the scanner (backward walk past the marker, captures contiguous `/* */` or `//` block)
|
--- @field declaration_comment string|nil -- Populated by the scanner (backward walk past the marker, captures contiguous `/* */` or `//` block)
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Local helpers (shared by per-form parsers)
|
-- Local helpers (shared by per-form parsers)
|
||||||
@@ -139,10 +136,10 @@ local QUALIFIER_KEYWORDS = {
|
|||||||
local AC_PREFIX = "ac_"
|
local AC_PREFIX = "ac_"
|
||||||
local AC_PREFIX_LEN = 3
|
local AC_PREFIX_LEN = 3
|
||||||
|
|
||||||
-- Strip the "ac_" prefix from a component name.
|
--- Strip the "ac_" prefix from a component name.
|
||||||
-- Returns the input unchanged if it doesn't start with the prefix.
|
--- Returns the input unchanged if it doesn't start with the prefix.
|
||||||
-- @param raw_name string
|
--- @param raw_name string
|
||||||
-- @return string
|
--- @return string
|
||||||
local function strip_ac_prefix(raw_name)
|
local function strip_ac_prefix(raw_name)
|
||||||
if #raw_name > AC_PREFIX_LEN and raw_name:sub(1, AC_PREFIX_LEN) == AC_PREFIX then
|
if #raw_name > AC_PREFIX_LEN and raw_name:sub(1, AC_PREFIX_LEN) == AC_PREFIX then
|
||||||
return raw_name:sub(AC_PREFIX_LEN + 1)
|
return raw_name:sub(AC_PREFIX_LEN + 1)
|
||||||
@@ -156,7 +153,7 @@ end
|
|||||||
local function push_debug_skip_marker(out, marker)
|
local function push_debug_skip_marker(out, marker)
|
||||||
local markers = out.debug_skip_markers
|
local markers = out.debug_skip_markers
|
||||||
local prior = markers[#markers]
|
local prior = markers[#markers]
|
||||||
if prior and prior.pending then
|
if prior and prior.pending then
|
||||||
prior.pending = false
|
prior.pending = false
|
||||||
prior.superseded_by_marker_line = marker.marker_line
|
prior.superseded_by_marker_line = marker.marker_line
|
||||||
end
|
end
|
||||||
@@ -178,25 +175,25 @@ end
|
|||||||
-- Returns (body, after_brace, body_off) on success, or (nil, fallback_pos) on no brace.
|
-- Returns (body, after_brace, body_off) on success, or (nil, fallback_pos) on no brace.
|
||||||
-- `fallback_pos` defaults to `after_paren + 1` (the common "advance by 1" case).
|
-- `fallback_pos` defaults to `after_paren + 1` (the common "advance by 1" case).
|
||||||
local function find_body_braces(source, after_paren, fallback)
|
local function find_body_braces(source, after_paren, fallback)
|
||||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||||
if not brace then return nil, fallback or (after_paren + 1) end
|
if not brace then return nil, fallback or (after_paren + 1) end
|
||||||
local body, after_brace = duffle.read_braces(source, brace)
|
local body, after_brace = duffle.read_braces(source, brace)
|
||||||
return body, after_brace, brace + 1
|
return body, after_brace, brace + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Walk backward from `start_pos` capturing contiguous `/* */` block(s) and
|
--- Walk backward from `start_pos` capturing contiguous `/* */` block(s) and
|
||||||
-- `//` line(s) that immediately precede it. The caller (preceding_declaration_comment)
|
--- `//` line(s) that immediately precede it. The caller (preceding_declaration_comment)
|
||||||
-- supplies `start_pos` so the walker does not need to detect marker shape or prelude layout.
|
--- supplies `start_pos` so the walker does not need to detect marker shape or prelude layout.
|
||||||
-- The scanner already knows the marker_pos + decl ident_pos and threads that knowledge forward.
|
--- The scanner already knows the marker_pos + decl ident_pos and threads that knowledge forward.
|
||||||
--
|
---
|
||||||
-- The walker captures:
|
--- The walker captures:
|
||||||
-- - Block comment close `*/` followed by walking back to `/*`.
|
--- - Block comment close `*/` followed by walking back to `/*`.
|
||||||
-- - `//` line comments (the line containing the current non-ws position starts with `//`).
|
--- - `//` line comments (the line containing the current non-ws position starts with `//`).
|
||||||
-- It stops at the first non-ws char that does not begin a comment block or line.
|
--- It stops at the first non-ws char that does not begin a comment block or line.
|
||||||
-- Empty string if no comment is adjacent.
|
--- Empty string if no comment is adjacent.
|
||||||
-- @param source string
|
--- @param source string
|
||||||
-- @param start_pos integer -- exclusive upper bound for the captured block
|
--- @param start_pos integer -- exclusive upper bound for the captured block
|
||||||
-- @return string
|
--- @return string
|
||||||
local function preceding_comment_walk_backward(source, start_pos)
|
local function preceding_comment_walk_backward(source, start_pos)
|
||||||
local pieces = {}
|
local pieces = {}
|
||||||
local scan_pos = start_pos
|
local scan_pos = start_pos
|
||||||
@@ -249,13 +246,13 @@ local function preceding_comment_walk_backward(source, start_pos)
|
|||||||
return table.concat(pieces, "\n")
|
return table.concat(pieces, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve the start position for the declaration-comment walk.
|
--- Resolve the start position for the declaration-comment walk.
|
||||||
-- When a debug-skip marker is pending, the walker must start from the position immediately before the marker ident
|
--- When a debug-skip marker is pending, the walker must start from the position immediately before the marker ident
|
||||||
-- (so it walks backward past the marker text and any `FI_ MipsAtom ac_X(args)` proc-prelude layout — neither of which is visible if we start from the declaration ident_pos).
|
--- (so it walks backward past the marker text and any `FI_ MipsAtom ac_X(args)` proc-prelude layout — neither of which is visible if we start from the declaration ident_pos).
|
||||||
-- When no marker is pending, the walker starts from the declaration ident_pos directly.
|
--- When no marker is pending, the walker starts from the declaration ident_pos directly.
|
||||||
-- @param pending_marker DebugSkipMarker|nil
|
--- @param pending_marker DebugSkipMarker|nil
|
||||||
-- @param ident_pos integer -- declaration ident position
|
--- @param ident_pos integer -- declaration ident position
|
||||||
-- @return integer
|
--- @return integer
|
||||||
local function comment_walk_start(pending_marker, ident_pos)
|
local function comment_walk_start(pending_marker, ident_pos)
|
||||||
if pending_marker then
|
if pending_marker then
|
||||||
return pending_marker.marker_pos - 1
|
return pending_marker.marker_pos - 1
|
||||||
@@ -263,16 +260,16 @@ local function comment_walk_start(pending_marker, ident_pos)
|
|||||||
return ident_pos - 1
|
return ident_pos - 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Attach the pending marker to the next declaration.
|
--- Attach the pending marker to the next declaration.
|
||||||
-- The declaration form disambiguates whole atoms from components; the resolved `debug_skip` is stamped directly on the declaration record
|
--- The declaration form disambiguates whole atoms from components; the resolved `debug_skip` is stamped directly on the declaration record
|
||||||
-- (sole-owner discipline; see push_debug_skip_marker).
|
--- (sole-owner discipline; see push_debug_skip_marker).
|
||||||
--
|
---
|
||||||
-- A marker is POSITIVE (stamps `debug_skip = true` on the declaration) iff:
|
--- A marker is POSITIVE (stamps `debug_skip = true` on the declaration) iff:
|
||||||
-- marker_kind == "atom_dbg_skip" AND is_bare == true
|
--- marker_kind == "atom_dbg_skip" AND is_bare == true
|
||||||
-- Any other spelling or shape (parenthesized form, legacy name) is recorded as a raw marker for annotation validation but never stamps `debug_skip`.
|
--- Any other spelling or shape (parenthesized form, legacy name) is recorded as a raw marker for annotation validation but never stamps `debug_skip`.
|
||||||
-- @param out SourceScan
|
--- @param out SourceScan
|
||||||
-- @param target_kind string|nil -- "atom" | "comp_bare" | "comp_proc" | "unrelated" once observed
|
--- @param target_kind string|nil -- "atom" | "comp_bare" | "comp_proc" | "unrelated" once observed
|
||||||
-- @return boolean|nil -- true iff the marker is the positive bare form
|
--- @return boolean|nil -- true iff the marker is the positive bare form
|
||||||
local function attach_debug_skip_marker(out, target_kind)
|
local function attach_debug_skip_marker(out, target_kind)
|
||||||
local markers = out.debug_skip_markers
|
local markers = out.debug_skip_markers
|
||||||
local marker = markers[#markers]
|
local marker = markers[#markers]
|
||||||
@@ -390,13 +387,13 @@ local function walk_body_fields(body, build_field)
|
|||||||
while body_pos <= body_len do
|
while body_pos <= body_len do
|
||||||
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
body_pos = duffle.skip_ws_and_cmt(body, body_pos)
|
||||||
if body_pos > body_len then break end
|
if body_pos > body_len then break end
|
||||||
local first, first_end = duffle.read_ident(body, body_pos)
|
local first, first_end = duffle.read_ident(body, body_pos)
|
||||||
if not first then
|
if not first then
|
||||||
body_pos = body_pos + 1
|
body_pos = body_pos + 1
|
||||||
else
|
else
|
||||||
local after_first = duffle.skip_ws_and_cmt(body, first_end)
|
local after_first = duffle.skip_ws_and_cmt(body, first_end)
|
||||||
local result, new_pos = build_field(first, first_end, after_first)
|
local result, new_pos = build_field(first, first_end, after_first)
|
||||||
if result then fields[#fields + 1] = result end
|
if result then fields[#fields + 1] = result end
|
||||||
body_pos = new_pos or first_end
|
body_pos = new_pos or first_end
|
||||||
-- Skip a single trailing `,` or `;`.
|
-- Skip a single trailing `,` or `;`.
|
||||||
if body_pos <= body_len and (body:sub(body_pos, body_pos) == "," or body:sub(body_pos, body_pos) == ";") then
|
if body_pos <= body_len and (body:sub(body_pos, body_pos) == "," or body:sub(body_pos, body_pos) == ";") then
|
||||||
@@ -442,7 +439,7 @@ local function parse_enum_body_fields(body)
|
|||||||
local value
|
local value
|
||||||
local new_pos
|
local new_pos
|
||||||
if body:sub(after_name, after_name) == "=" then
|
if body:sub(after_name, after_name) == "=" then
|
||||||
local val_pos = duffle.skip_ws_and_cmt(body, after_name + 1)
|
local val_pos = duffle.skip_ws_and_cmt(body, after_name + 1)
|
||||||
local v, end_pos = parse_enum_int_literal(body, val_pos)
|
local v, end_pos = parse_enum_int_literal(body, val_pos)
|
||||||
if v ~= nil then
|
if v ~= nil then
|
||||||
value = v
|
value = v
|
||||||
@@ -461,16 +458,16 @@ end
|
|||||||
-- Returns a positive integer byte_size when the chain bottoms out at a builtin, or nil if the chain is broken, exceeds TYPE_CHAIN_MAX_DEPTH, or contains a cycle.
|
-- Returns a positive integer byte_size when the chain bottoms out at a builtin, or nil if the chain is broken, exceeds TYPE_CHAIN_MAX_DEPTH, or contains a cycle.
|
||||||
local function resolve_typedef_byte_size(type_name, type_name_registry, visited, depth)
|
local function resolve_typedef_byte_size(type_name, type_name_registry, visited, depth)
|
||||||
if depth > TYPE_CHAIN_MAX_DEPTH then return nil end
|
if depth > TYPE_CHAIN_MAX_DEPTH then return nil end
|
||||||
if visited[type_name] then return nil end
|
if visited[type_name] then return nil end
|
||||||
visited[type_name] = true
|
visited[type_name] = true
|
||||||
|
|
||||||
-- Check the builtin primitive map FIRST.
|
-- Check the builtin primitive map FIRST.
|
||||||
-- This handles undeclared builtin idents (e.g. `__UINT32_TYPE__` appears as underlying_type in `typedef __UINT32_TYPE__ TSet_(V4_S2);`
|
-- This handles undeclared builtin idents (e.g. `__UINT32_TYPE__` appears as underlying_type in `typedef __UINT32_TYPE__ TSet_(V4_S2);`
|
||||||
-- even though the fixture never declares `__UINT32_TYPE__` itself).
|
-- even though the fixture never declares `__UINT32_TYPE__` itself).
|
||||||
local builtin = BUILTIN_BYTE_SIZES[type_name]
|
local builtin = BUILTIN_BYTE_SIZES[type_name]
|
||||||
if builtin ~= nil then return builtin end
|
if builtin ~= nil then return builtin end
|
||||||
|
|
||||||
local entry = type_name_registry[type_name]
|
local entry = type_name_registry[type_name]
|
||||||
if not entry then return nil end
|
if not entry then return nil end
|
||||||
|
|
||||||
-- Confident: this entry was already resolved by the propagation pass (e.g., a builtin or a struct whose fields are all resolved).
|
-- Confident: this entry was already resolved by the propagation pass (e.g., a builtin or a struct whose fields are all resolved).
|
||||||
@@ -519,9 +516,9 @@ local function propagate_type_sizes(out)
|
|||||||
for name, entry in pairs(reg) do
|
for name, entry in pairs(reg) do
|
||||||
if entry.byte_size == nil then
|
if entry.byte_size == nil then
|
||||||
local resolved = resolve_typedef_byte_size(name, reg, {}, 1)
|
local resolved = resolve_typedef_byte_size(name, reg, {}, 1)
|
||||||
if resolved ~= nil then
|
if resolved ~= nil then
|
||||||
entry.byte_size = resolved
|
entry.byte_size = resolved
|
||||||
any_change = true
|
any_change = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -828,7 +825,7 @@ end
|
|||||||
--- Parse a decimal/negative-decimal/hex integer literal starting at byte position `start`.
|
--- Parse a decimal/negative-decimal/hex integer literal starting at byte position `start`.
|
||||||
--- Returns (value, end_pos) on success, or (nil, start) on failure / no match.
|
--- Returns (value, end_pos) on success, or (nil, start) on failure / no match.
|
||||||
--- Accepts: 12, -1, 0, 0x10, 0X1F, -0x10.
|
--- Accepts: 12, -1, 0, 0x10, 0X1F, -0x10.
|
||||||
--- @param text string
|
--- @param text string
|
||||||
--- @param start integer
|
--- @param start integer
|
||||||
--- @return integer|nil, integer
|
--- @return integer|nil, integer
|
||||||
--- Implementation note: this is a plain assignment (not `local function`)
|
--- Implementation note: this is a plain assignment (not `local function`)
|
||||||
@@ -952,9 +949,9 @@ end
|
|||||||
--- Always saves the raw RHS text into `code_macro_bodies` (for cross-source fallback during chain resolution),
|
--- Always saves the raw RHS text into `code_macro_bodies` (for cross-source fallback during chain resolution),
|
||||||
--- then (if resolvable) stores the resolved integer code into `code_macros` keyed by the macro name.
|
--- then (if resolvable) stores the resolved integer code into `code_macros` keyed by the macro name.
|
||||||
--- `directive_start` points at the `#` byte. The function is silent on non-matching directives, the caller skips the line in any case.
|
--- `directive_start` points at the `#` byte. The function is silent on non-matching directives, the caller skips the line in any case.
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param directive_start integer -- byte position of `#`
|
--- @param directive_start integer -- byte position of `#`
|
||||||
--- @param code_macros table -- out._code_macros / ctx.shared._code_macros
|
--- @param code_macros table -- out._code_macros / ctx.shared._code_macros
|
||||||
--- @param code_macro_bodies table -- out._code_macro_bodies / ctx.shared._code_macro_bodies
|
--- @param code_macro_bodies table -- out._code_macro_bodies / ctx.shared._code_macro_bodies
|
||||||
local function try_extract_code_macro(source, directive_start, code_macros, code_macro_bodies)
|
local function try_extract_code_macro(source, directive_start, code_macros, code_macro_bodies)
|
||||||
local rest = duffle.skip_ws_and_cmt(source, directive_start + 1)
|
local rest = duffle.skip_ws_and_cmt(source, directive_start + 1)
|
||||||
@@ -985,8 +982,8 @@ end
|
|||||||
--- Populates `code_macros` with resolved integer codes AND `code_macro_bodies` with raw RHS text
|
--- Populates `code_macros` with resolved integer codes AND `code_macro_bodies` with raw RHS text
|
||||||
--- (used by the chain walker as cross-source fallback during pass 1b in `M.run`); ignores everything else.
|
--- (used by the chain walker as cross-source fallback during pass 1b in `M.run`); ignores everything else.
|
||||||
--- Used by `M.run` pass 1a to build the cross-source `_code_macros` + `_code_macro_bodies` registries before pass 1b resolves chains.
|
--- Used by `M.run` pass 1a to build the cross-source `_code_macros` + `_code_macro_bodies` registries before pass 1b resolves chains.
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param code_macros table
|
--- @param code_macros table
|
||||||
--- @param code_macro_bodies table
|
--- @param code_macro_bodies table
|
||||||
local function scan_source_pre_pass(source, code_macros, code_macro_bodies)
|
local function scan_source_pre_pass(source, code_macros, code_macro_bodies)
|
||||||
local pos = 1
|
local pos = 1
|
||||||
@@ -1043,7 +1040,7 @@ local function parse_enum_atom_type_default(body, pos)
|
|||||||
if pos > #body then return nil, 0, pos end
|
if pos > #body then return nil, 0, pos end
|
||||||
-- Bare `atom_type` word with word-bounding on both sides.
|
-- Bare `atom_type` word with word-bounding on both sides.
|
||||||
local ident, ident_end = duffle.read_ident(body, pos)
|
local ident, ident_end = duffle.read_ident(body, pos)
|
||||||
if ident ~= "atom_type" then return nil, 0, pos end
|
if ident ~= "atom_type" then return nil, 0, pos end
|
||||||
if pos > 1 then
|
if pos > 1 then
|
||||||
local prev = body:byte(pos - 1)
|
local prev = body:byte(pos - 1)
|
||||||
if duffle.is_alnum_byte(prev) then return nil, 0, pos end
|
if duffle.is_alnum_byte(prev) then return nil, 0, pos end
|
||||||
@@ -1054,19 +1051,19 @@ local function parse_enum_atom_type_default(body, pos)
|
|||||||
end
|
end
|
||||||
-- Expect `( ... )` immediately after.
|
-- Expect `( ... )` immediately after.
|
||||||
local open_pos = duffle.skip_ws_and_cmt(body, ident_end)
|
local open_pos = duffle.skip_ws_and_cmt(body, ident_end)
|
||||||
if open_pos > #body or body:sub(open_pos, open_pos) ~= "(" then return nil, 0, pos end
|
if open_pos > #body or body:sub(open_pos, open_pos) ~= "(" then return nil, 0, pos end
|
||||||
local inner, after_close = duffle.read_parens(body, open_pos)
|
local inner, after_close = duffle.read_parens(body, open_pos)
|
||||||
-- Reject any trailing tokens past the close paren other than comma / close-brace (next enum entry / end of enum).
|
-- Reject any trailing tokens past the close paren other than comma / close-brace (next enum entry / end of enum).
|
||||||
local residue = duffle.skip_ws_and_cmt(body, after_close)
|
local residue = duffle.skip_ws_and_cmt(body, after_close)
|
||||||
if residue <= #body then
|
if residue <= #body then
|
||||||
local rbyte = body:byte(residue)
|
local rbyte = body:byte(residue)
|
||||||
if rbyte ~= BYTE_COMMA and rbyte ~= BYTE_CLOSE_BRACE then return nil, 0, pos end
|
if rbyte ~= BYTE_COMMA and rbyte ~= BYTE_CLOSE_BRACE then return nil, 0, pos end
|
||||||
end
|
end
|
||||||
-- Parse the type chain inside the parens (e.g. `V4_S2*` -> ("V4_S2", 1)).
|
-- Parse the type chain inside the parens (e.g. `V4_S2*` -> ("V4_S2", 1)).
|
||||||
local type_name, depth, after_chain = parse_type_chain(inner, 1)
|
local type_name, depth, after_chain = parse_type_chain(inner, 1)
|
||||||
if not type_name then return nil, 0, pos end
|
if not type_name then return nil, 0, pos end
|
||||||
local end_check = duffle.skip_ws_and_cmt(inner, after_chain)
|
local end_check = duffle.skip_ws_and_cmt(inner, after_chain)
|
||||||
if end_check <= #inner then return nil, 0, pos end
|
if end_check <= #inner then return nil, 0, pos end
|
||||||
return type_name, depth, duffle.skip_ws_and_cmt(body, after_close)
|
return type_name, depth, duffle.skip_ws_and_cmt(body, after_close)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1098,11 +1095,11 @@ end
|
|||||||
---
|
---
|
||||||
--- Diagnostic-only path: a following `(...)` is recorded as an invalid parenthesized-form marker so the annotation rule can emit a precise "parenthesized form" diagnostic.
|
--- Diagnostic-only path: a following `(...)` is recorded as an invalid parenthesized-form marker so the annotation rule can emit a precise "parenthesized form" diagnostic.
|
||||||
--- The parenthesized form stays diagnostic; the bare form alone carries the runtime stamp.
|
--- The parenthesized form stays diagnostic; the bare form alone carries the runtime stamp.
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer -- source cursor position to resume from
|
--- @return integer -- source cursor position to resume from
|
||||||
local function parse_dbg_skip_marker(source, pos, ident_end, line_of, out)
|
local function parse_dbg_skip_marker(source, pos, ident_end, line_of, out)
|
||||||
local marker_kind = source:sub(pos, ident_end - 1)
|
local marker_kind = source:sub(pos, ident_end - 1)
|
||||||
@@ -1134,15 +1131,15 @@ end
|
|||||||
-- Parse `atom_dbg_reg_default(R_X, <type>...)`;
|
-- Parse `atom_dbg_reg_default(R_X, <type>...)`;
|
||||||
-- the second argument may be a `Type` or `Type*`/`Type**` chain. Records in `out.types[R_X]`.
|
-- the second argument may be a `Type` or `Type*`/`Type**` chain. Records in `out.types[R_X]`.
|
||||||
local function parse_atom_dbg_reg_default(source, pos, ident_end, line_of, out)
|
local function parse_atom_dbg_reg_default(source, pos, ident_end, line_of, out)
|
||||||
local inner, after_paren = read_parens_after(source, ident_end)
|
local inner, after_paren = read_parens_after(source, ident_end)
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
local args = duffle.split_top_level_commas(inner)
|
local args = duffle.split_top_level_commas(inner)
|
||||||
if #args < 1 then
|
if #args < 1 then
|
||||||
-- Annotation pass surfaces this; we still consume the marker.
|
-- Annotation pass surfaces this; we still consume the marker.
|
||||||
return after_paren
|
return after_paren
|
||||||
end
|
end
|
||||||
local reg_name = duffle.trim(args[1])
|
local reg_name = duffle.trim(args[1])
|
||||||
local type_part = args[2] or "void"
|
local type_part = args[2] or "void"
|
||||||
local type_name, depth = parse_type_chain(type_part, 1)
|
local type_name, depth = parse_type_chain(type_part, 1)
|
||||||
if not type_name then type_name, depth = duffle.trim(type_part), 0 end
|
if not type_name then type_name, depth = duffle.trim(type_part), 0 end
|
||||||
out.types[reg_name] = {
|
out.types[reg_name] = {
|
||||||
@@ -1161,23 +1158,23 @@ local function parse_atom_dbg_reg_default(source, pos, ident_end, line_of, out)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Parse: `MipsAtom_(<name>) [atom_info(<binds>, <reads>, <writes>)] { <body> }`
|
--- Parse: `MipsAtom_(<name>) [atom_info(<binds>, <reads>, <writes>)] { <body> }`
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_mips_atom(source, pos, ident_end, line_of, out)
|
local function parse_mips_atom(source, pos, ident_end, line_of, out)
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
local raw_name = duffle.read_ident(inner, 1)
|
local raw_name = duffle.read_ident(inner, 1)
|
||||||
|
|
||||||
-- Lookahead for atom_info(...) between `)` and `{`. Captures sub-calls; updates brace search start.
|
-- Lookahead for atom_info(...) between `)` and `{`. Captures sub-calls; updates brace search start.
|
||||||
local brace_search_pos = after_paren
|
local brace_search_pos = after_paren
|
||||||
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
||||||
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
||||||
if look_ident == "atom_info" then
|
if look_ident == "atom_info" then
|
||||||
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
||||||
if source:sub(info_open, info_open) == "(" then
|
if source:sub(info_open, info_open) == "(" then
|
||||||
local info_inner, info_after = duffle.read_parens(source, info_open)
|
local info_inner, info_after = duffle.read_parens(source, info_open)
|
||||||
@@ -1221,7 +1218,7 @@ local function parse_mips_atom(source, pos, ident_end, line_of, out)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local body, after_brace, body_off = find_body_braces(source, brace_search_pos, open_paren + 1)
|
local body, after_brace, body_off = find_body_braces(source, brace_search_pos, open_paren + 1)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
if raw_name and raw_name ~= "" then
|
if raw_name and raw_name ~= "" then
|
||||||
register_atom(out, "atom", line_of(pos), raw_name, body, body_off, raw_name, pos, after_paren, source)
|
register_atom(out, "atom", line_of(pos), raw_name, body, body_off, raw_name, pos, after_paren, source)
|
||||||
@@ -1231,20 +1228,20 @@ local function parse_mips_atom(source, pos, ident_end, line_of, out)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Parse: `MipsAtomComp_(<name>) { <body> }`
|
--- Parse: `MipsAtomComp_(<name>) { <body> }`
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_mips_atom_comp(source, pos, ident_end, line_of, out)
|
local function parse_mips_atom_comp(source, pos, ident_end, line_of, out)
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
local raw_name = duffle.read_ident(inner, 1)
|
local raw_name = duffle.read_ident(inner, 1)
|
||||||
if not raw_name then return open_paren + 1 end
|
if not raw_name then return open_paren + 1 end
|
||||||
|
|
||||||
local body, after_brace, body_off = find_body_braces(source, after_paren, open_paren + 1)
|
local body, after_brace, body_off = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
local name = strip_ac_prefix(raw_name)
|
local name = strip_ac_prefix(raw_name)
|
||||||
register_atom(out, "comp_bare", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
register_atom(out, "comp_bare", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
||||||
@@ -1253,14 +1250,14 @@ local function parse_mips_atom_comp(source, pos, ident_end, line_of, out)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Parse: `MipsAtomComp_Proc_(<name>, { <body> })` — body is inside the LAST `{` in args.
|
--- Parse: `MipsAtomComp_Proc_(<name>, { <body> })` — body is inside the LAST `{` in args.
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
|
local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
|
||||||
if not inner then return after_paren end
|
if not inner then return after_paren end
|
||||||
|
|
||||||
-- Find the LAST `{` in inner (the body brace, not any potential embedded braces in expressions).
|
-- Find the LAST `{` in inner (the body brace, not any potential embedded braces in expressions).
|
||||||
@@ -1277,21 +1274,20 @@ local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
|
|||||||
if close_pos > #inner + 1 then return after_paren end
|
if close_pos > #inner + 1 then return after_paren end
|
||||||
|
|
||||||
local raw_name = inner:match("^%s*([%w_]+)") or "?"
|
local raw_name = inner:match("^%s*([%w_]+)") or "?"
|
||||||
local name = strip_ac_prefix(raw_name)
|
local name = strip_ac_prefix(raw_name)
|
||||||
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
|
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
|
||||||
local body_off = open_paren + 2 + last_brace_pos
|
local body_off = open_paren + 2 + last_brace_pos
|
||||||
|
|
||||||
register_atom(out, "comp_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
register_atom(out, "comp_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
|
||||||
|
|
||||||
return after_paren
|
return after_paren
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Parse: `MipsCode code_<name> { <body> }` (raw atom form — offsets pass only).
|
--- Parse: `MipsCode code_<name> { <body> }` (raw atom form — offsets pass only).
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_mips_code(source, pos, ident_end, line_of, out)
|
local function parse_mips_code(source, pos, ident_end, line_of, out)
|
||||||
local next_pos = duffle.skip_ws_and_cmt(source, ident_end)
|
local next_pos = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
@@ -1300,8 +1296,8 @@ local function parse_mips_code(source, pos, ident_end, line_of, out)
|
|||||||
return ident_end
|
return ident_end
|
||||||
end
|
end
|
||||||
|
|
||||||
local atom_name = next_ident:sub(6)
|
local atom_name = next_ident:sub(6)
|
||||||
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end)
|
local body, after_brace, body_off = find_body_braces(source, next_after, ident_end)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
register_raw_atom(out, line_of(pos), atom_name, body, body_off, atom_name, pos)
|
register_raw_atom(out, line_of(pos), atom_name, body, body_off, atom_name, pos)
|
||||||
|
|
||||||
@@ -1320,13 +1316,13 @@ end
|
|||||||
--- pointer_depth = 0
|
--- pointer_depth = 0
|
||||||
--- } -- byte_size + per-field offset/byte_size set by the propagation pass.
|
--- } -- byte_size + per-field offset/byte_size set by the propagation pass.
|
||||||
--- Also populates `out.binds[]` IFF `name:sub(1, 6) == "Binds_"`.
|
--- Also populates `out.binds[]` IFF `name:sub(1, 6) == "Binds_"`.
|
||||||
--- @param body string
|
--- @param body string
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
local function register_struct_type(body, name, pos, line_of, out)
|
local function register_struct_type(body, name, pos, line_of, out)
|
||||||
local fields = parse_struct_body_fields(body)
|
local fields = parse_struct_body_fields(body)
|
||||||
local source_pos = line_of(pos)
|
local source_pos = line_of(pos)
|
||||||
out.type_name_registry[name] = {
|
out.type_name_registry[name] = {
|
||||||
name = name,
|
name = name,
|
||||||
@@ -1352,11 +1348,11 @@ end
|
|||||||
--- Register an Enum_ entry in type_name_registry.
|
--- Register an Enum_ entry in type_name_registry.
|
||||||
--- Local helper for parse_typedef_binds. Captures the underlying type (1st arg of `Enum_(<underlying>, <name>)`) and the body fields.
|
--- Local helper for parse_typedef_binds. Captures the underlying type (1st arg of `Enum_(<underlying>, <name>)`) and the body fields.
|
||||||
--- @param underlying string
|
--- @param underlying string
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param body string
|
--- @param body string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
local function register_enum_type(underlying, name, body, pos, line_of, out)
|
local function register_enum_type(underlying, name, body, pos, line_of, out)
|
||||||
local fields = parse_enum_body_fields(body)
|
local fields = parse_enum_body_fields(body)
|
||||||
out.type_name_registry[name] = {
|
out.type_name_registry[name] = {
|
||||||
@@ -1375,18 +1371,18 @@ end
|
|||||||
--- Captures the underlying type ident (LHS of `typedef <type> <alias>;`) and exposes it through the registry.
|
--- Captures the underlying type ident (LHS of `typedef <type> <alias>;`) and exposes it through the registry.
|
||||||
--- The propagation pass follows the underlying_type chain to resolve byte_size.
|
--- The propagation pass follows the underlying_type chain to resolve byte_size.
|
||||||
--- @param underlying string
|
--- @param underlying string
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
local function register_typedef_alias(underlying, name, pos, line_of, out)
|
local function register_typedef_alias(underlying, name, pos, line_of, out)
|
||||||
out.type_name_registry[name] = {
|
out.type_name_registry[name] = {
|
||||||
name = name,
|
name = name,
|
||||||
kind = "typedef",
|
kind = "typedef",
|
||||||
underlying_type = underlying,
|
underlying_type = underlying,
|
||||||
source_line = line_of(pos),
|
source_line = line_of(pos),
|
||||||
source_file = out._source_file,
|
source_file = out._source_file,
|
||||||
pointer_depth = 0,
|
pointer_depth = 0,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1396,29 +1392,29 @@ end
|
|||||||
--- 1. `typedef Struct_(<name>) { <body> } <alias>;` adds to type_name_registry (kind="struct").
|
--- 1. `typedef Struct_(<name>) { <body> } <alias>;` adds to type_name_registry (kind="struct").
|
||||||
--- Binds_* aliases also land in out.binds[].
|
--- Binds_* aliases also land in out.binds[].
|
||||||
--- 2. `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
--- 2. `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
||||||
--- adds to type_name_registry (kind="enum").
|
--- Adds to type_name_registry (kind="enum").
|
||||||
--- 3. `typedef <type> <alias>;` simple typedef alias.
|
--- 3. `typedef <type> <alias>;` simple typedef alias.
|
||||||
--- Adds to type_name_registry (kind="typedef").
|
--- Adds to type_name_registry (kind="typedef").
|
||||||
--- 4. `typedef <type> TSet_(<name>);` duffle TSet_ convention.
|
--- 4. `typedef <type> TSet_(<name>);` duffle TSet_ convention.
|
||||||
--- Strips TSet_ wrapper; adds to type_name_registry (kind="typedef") with underlying_type=<type>.
|
--- Strips TSet_ wrapper; adds to type_name_registry (kind="typedef") with underlying_type=<type>.
|
||||||
---
|
---
|
||||||
--- All four shapes also attach an "unrelated" debug-skip marker (the existing behavior — typedef declarations don't carry atom_dbg_skip).
|
--- All four shapes also attach an "unrelated" debug-skip marker (the existing behavior — typedef declarations don't carry atom_dbg_skip).
|
||||||
--- @param source string
|
--- @param source string
|
||||||
--- @param pos integer
|
--- @param pos integer
|
||||||
--- @param ident_end integer
|
--- @param ident_end integer
|
||||||
--- @param line_of fun(pos: integer): integer
|
--- @param line_of fun(pos: integer): integer
|
||||||
--- @param out SourceScan
|
--- @param out SourceScan
|
||||||
--- @return integer
|
--- @return integer
|
||||||
local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
||||||
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
||||||
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
||||||
if not id2 then return ident_end end
|
if not id2 then return ident_end end
|
||||||
|
|
||||||
-- ── Shape 1: `typedef Struct_(<name>) { <body> } <alias>;` ────────────
|
-- ── Shape 1: `typedef Struct_(<name>) { <body> } <alias>;` ────────────
|
||||||
if id2 == "Struct_" then
|
if id2 == "Struct_" then
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
if not inner then return id2_end end
|
if not inner then return id2_end end
|
||||||
local name = duffle.trim(inner)
|
local name = duffle.trim(inner)
|
||||||
|
|
||||||
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
@@ -1428,7 +1424,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
|
|
||||||
-- ── Shape 2: `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
-- ── Shape 2: `typedef Enum_(<underlying>, <name>) { <body> } <alias>;`
|
||||||
elseif id2 == "Enum_" then
|
elseif id2 == "Enum_" then
|
||||||
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
if not inner then return id2_end end
|
if not inner then return id2_end end
|
||||||
-- Split `inner` on the first top-level comma into (<underlying>, <name>).
|
-- Split `inner` on the first top-level comma into (<underlying>, <name>).
|
||||||
local args = duffle.split_top_level_commas(inner)
|
local args = duffle.split_top_level_commas(inner)
|
||||||
@@ -1436,7 +1432,7 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
local underlying = duffle.trim(args[1])
|
local underlying = duffle.trim(args[1])
|
||||||
local name = duffle.trim(args[2])
|
local name = duffle.trim(args[2])
|
||||||
|
|
||||||
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
local body, after_brace = find_body_braces(source, after_paren, open_paren + 1)
|
||||||
if not body then return after_brace end
|
if not body then return after_brace end
|
||||||
register_enum_type(underlying, name, body, pos, line_of, out)
|
register_enum_type(underlying, name, body, pos, line_of, out)
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
@@ -1469,11 +1465,10 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
|
|
||||||
-- Shape 4 (TSet_ at id2 position): no preceding underlying span.
|
-- Shape 4 (TSet_ at id2 position): no preceding underlying span.
|
||||||
if id2 == "TSet_" then
|
if id2 == "TSet_" then
|
||||||
local inner, after_paren = read_parens_after(source, id2_end, id2_end)
|
local inner, after_paren = read_parens_after(source, id2_end, id2_end)
|
||||||
if not inner then return id2_end end
|
if not inner then return id2_end end
|
||||||
local tset_name = duffle.trim(inner)
|
local tset_name = duffle.trim(inner)
|
||||||
-- Empty underlying span is acceptable; the TSet_ wrapper itself
|
-- Empty underlying span is acceptable; the TSet_ wrapper itself encodes the alias identity (per the duffle TSet_ convention).
|
||||||
-- encodes the alias identity (per the duffle TSet_ convention).
|
|
||||||
register_typedef_alias("", tset_name, pos, line_of, out)
|
register_typedef_alias("", tset_name, pos, line_of, out)
|
||||||
attach_debug_skip_marker(out, "unrelated")
|
attach_debug_skip_marker(out, "unrelated")
|
||||||
return after_paren
|
return after_paren
|
||||||
@@ -1491,13 +1486,13 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
|||||||
while scan < semi_pos do
|
while scan < semi_pos do
|
||||||
scan = duffle.skip_ws_and_cmt(source, scan)
|
scan = duffle.skip_ws_and_cmt(source, scan)
|
||||||
if scan >= semi_pos then break end
|
if scan >= semi_pos then break end
|
||||||
local id, id_end = duffle.read_ident(source, scan)
|
local id, id_end = duffle.read_ident(source, scan)
|
||||||
if not id then
|
if not id then
|
||||||
scan = scan + 1
|
scan = scan + 1
|
||||||
elseif id == "TSet_" then
|
elseif id == "TSet_" then
|
||||||
-- Shape 4 (TSet_ at non-id2 position): grab the parenthesized argument.
|
-- Shape 4 (TSet_ at non-id2 position): grab the parenthesized argument.
|
||||||
local inner, after_paren = read_parens_after(source, id_end, id_end)
|
local inner, after_paren = read_parens_after(source, id_end, id_end)
|
||||||
if inner then
|
if inner then
|
||||||
tset_arg = duffle.trim(inner)
|
tset_arg = duffle.trim(inner)
|
||||||
tset_arg_end = after_paren
|
tset_arg_end = after_paren
|
||||||
tset_pos = scan
|
tset_pos = scan
|
||||||
@@ -1957,7 +1952,7 @@ local function merge_named_with_sites(registry, name, new_entry, site, collision
|
|||||||
registry[name].sites = { site }
|
registry[name].sites = { site }
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local existing = registry[name]
|
local existing = registry[name]
|
||||||
local new_shape = shape_fn(new_entry)
|
local new_shape = shape_fn(new_entry)
|
||||||
local old_shape = shape_fn(existing)
|
local old_shape = shape_fn(existing)
|
||||||
if new_shape == old_shape and new_shape ~= "" then
|
if new_shape == old_shape and new_shape ~= "" then
|
||||||
|
|||||||
+117
-123
@@ -1,20 +1,17 @@
|
|||||||
--- passes/static_analysis.lua — Per-atom static-analysis checks.
|
--- passes/static_analysis.lua — Per-atom static-analysis checks.
|
||||||
---
|
--- Ownership: `ctx.shared.corpus` canonical merged registry; per-source fallback synthesis is rejected.
|
||||||
--- Ownership: `ctx.shared.corpus` is the canonical merged registry; per-source fallback synthesis is rejected.
|
|
||||||
--- `atom.paths` supplies the emitted and analysis projections consumed by this pass.
|
--- `atom.paths` supplies the emitted and analysis projections consumed by this pass.
|
||||||
---
|
---
|
||||||
--- Per-atom rules:
|
--- Per-atom rules:
|
||||||
--- 1. transfer_hazards: A single forward walker (`analyze_hardware_relations`) reads `atom.paths.word_events` once per atom.
|
--- 1. transfer_hazards: A single forward walker (`analyze_hardware_relations`) reads `atom.paths.word_events` once per atom.
|
||||||
--- For each emitted word event it (a) inspects pending CPU/COP0/COP2/GTE relations against the event as CONSUMER
|
--- For each emitted word event it (a) inspects pending CPU / COP0 / COP2 / GTE relations against the event as CONSUMER
|
||||||
--- (recording a hazard on `atom.paths.hazards` when the producer→consumer gap is below the required retire-slot count),
|
--- (recording a hazard on `atom.paths.hazards` when the producer→consumer gap is below the required retire-slot count),
|
||||||
--- (b) applies the event's GPR value effects (`duffle.INSTRUCTION_GPR_EFFECTS`) to `atom.paths.forward_state.gpr_values`,
|
--- (b) Applies the event's GPR value effects (`duffle.INSTRUCTION_GPR_EFFECTS`) to `atom.paths.forward_state.gpr_values`,
|
||||||
--- applies bounded constant propagation, and stages matching relation rows as PRODUCERS (with `destination_match` filters, e.g. for the IRGB fan-out).
|
--- applies bounded constant propagation, and stages matching relation rows as PRODUCERS (with `destination_match` filters, e.g. for the IRGB fan-out).
|
||||||
--- The `transfer_hazards` CHECK_RULES reader projects `atom.paths.hazards` into per-atom findings.
|
--- The `transfer_hazards` CHECK_RULES reader projects `atom.paths.hazards` into per-atom findings.
|
||||||
--- The reader does NOT re-walk source; this is the per-check purity contract.
|
--- The reader does NOT re-walk source. The walker runs once per atom before the per-atom dispatch; the reader runs inside the same dispatch.
|
||||||
--- The walker runs once per atom before the per-atom dispatch; the reader runs inside the same dispatch.
|
|
||||||
--- 2. control_transfer_delay_slot_use: For every emitted branch/jump/call encoder in `duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES`
|
--- 2. control_transfer_delay_slot_use: For every emitted branch/jump/call encoder in `duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES`
|
||||||
--- (the six `branch_*` encoders plus `jump` / `jump_reg` / `jump_link` / `call_reg` / `call_addr`),
|
--- (the six `branch_*` encoders plus `jump` / `jump_reg` / `jump_link` / `call_reg` / `call_addr`), inspect the next emitted event in `atom.paths.word_events`.
|
||||||
--- inspect the next emitted event in `atom.paths.word_events`.
|
|
||||||
--- Emit an `info`-severity finding when the successor is `nop` or absent (the next emitted word IS the hardware delay slot).
|
--- Emit an `info`-severity finding when the successor is `nop` or absent (the next emitted word IS the hardware delay slot).
|
||||||
--- `jump_reg(R_AtomJmp)` is suppressed by policy (the fixed `mac_yield()` handshake).
|
--- `jump_reg(R_AtomJmp)` is suppressed by policy (the fixed `mac_yield()` handshake).
|
||||||
--- `nop2` needs no special case: emission-model emits two `nop` events for it, so the first expansion is the hardware delay slot.
|
--- `nop2` needs no special case: emission-model emits two `nop` events for it, so the first expansion is the hardware delay slot.
|
||||||
@@ -101,10 +98,10 @@ local OUTPUT_EXTENSION = ".static_analysis.txt"
|
|||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- @class SourceFile
|
--- @class SourceFile
|
||||||
--- @field path string -- absolute path to the source file
|
--- @field path string -- Absolute path to the source file
|
||||||
--- @field text string -- the full source text
|
--- @field text string -- Full source text
|
||||||
--- @field dir string -- the directory containing the source
|
--- @field dir string -- Directory containing the source
|
||||||
--- @field basename string -- filename without extension
|
--- @field basename string -- Filename without extension
|
||||||
|
|
||||||
--- @class PassCtx
|
--- @class PassCtx
|
||||||
--- @field sources SourceFile[]
|
--- @field sources SourceFile[]
|
||||||
@@ -121,43 +118,42 @@ local OUTPUT_EXTENSION = ".static_analysis.txt"
|
|||||||
--- @field outputs table[]
|
--- @field outputs table[]
|
||||||
--- @field errors table[]
|
--- @field errors table[]
|
||||||
--- @field warnings table[]
|
--- @field warnings table[]
|
||||||
--- @field info table[] -- finding-level info (kind == "info"); distinct from per-source scanned/cycles summary rows
|
--- @field info table[] -- Finding-level info (kind == "info"); distinct from per-source scanned/cycles summary rows
|
||||||
|
|
||||||
--- @alias AtomName string -- lower_snake_case atom nameMacroName string -- lower_snake_case macro identifier
|
--- @alias AtomName string -- lower_snake_case atom nameMacroName string -- lower_snake_case macro identifier
|
||||||
--- @alias CheckName string -- "transfer_hazards" | "control_transfer_delay_slot_use" | "mac_yield_uniformity" | "yield_load_tail_pairing" | "abi_handoff" | "gpu_portstore_shape" | "per_atom_cycle_budget" | "enum_alias_membership" | "atom_type_consistency" | "binds_no_substruct_deref"
|
--- @alias CheckName string -- "transfer_hazards" | "control_transfer_delay_slot_use" | "mac_yield_uniformity" | "yield_load_tail_pairing" | "abi_handoff" | "gpu_portstore_shape" | "per_atom_cycle_budget" | "enum_alias_membership" | "atom_type_consistency" | "binds_no_substruct_deref"
|
||||||
|
|
||||||
--- @class AtomBody
|
--- @class AtomBody
|
||||||
--- @field line integer -- source line of the atom declaration
|
--- @field line integer -- Source line of the atom declaration
|
||||||
--- @field name AtomName -- atom name (e.g. "cube_g4_face")
|
--- @field name AtomName -- Atom name (e.g. "cube_g4_face")
|
||||||
--- @field body string -- the brace-delimited body (without the braces)
|
--- @field body string -- Brace-delimited body (without the braces)
|
||||||
--- @field body_off integer -- char offset of body[1] in source
|
--- @field body_off integer -- char offset of body[1] in source
|
||||||
--- @field kind string -- "atom" | "comp_bare" | "comp_proc"
|
--- @field kind string -- "atom" | "comp_bare" | "comp_proc"
|
||||||
|
|
||||||
--- @class Token
|
--- @class Token
|
||||||
--- @field tok string -- the raw token text (trimmed)
|
--- @field tok string -- Raw token text (trimmed)
|
||||||
--- @field line integer -- source line of the token's start
|
--- @field line integer -- Source line of the token's start
|
||||||
--- @field ident string|nil -- the leading ident of the token (if any)
|
--- @field ident string|nil -- Leading ident of the token (if any)
|
||||||
--- @field kind string -- "n_words" | "mac_yield" | "gte_cmdw" | "mac_format" | "mac_gte_store" | "mac_insert_ot_tag" | "atom_label" | "atom_offset" | "other"
|
--- @field kind string -- "n_words" | "mac_yield" | "gte_cmdw" | "mac_format" | "mac_gte_store" | "mac_insert_ot_tag" | "atom_label" | "atom_offset" | "other"
|
||||||
|
|
||||||
--- @class Finding
|
--- @class Finding
|
||||||
--- @field line integer -- source line of the finding
|
--- @field line integer -- Source line of the finding
|
||||||
--- @field atom AtomName -- the atom this finding is for (or "")
|
--- @field atom AtomName -- Atom this finding is for (or "")
|
||||||
--- @field check CheckName -- the check identifier
|
--- @field check CheckName -- Check identifier
|
||||||
--- @field kind string -- "error" | "warning" | "info"
|
--- @field kind string -- "error" | "warning" | "info"
|
||||||
--- @field msg string -- the finding message
|
--- @field msg string -- Finding message
|
||||||
|
|
||||||
--- @class AtomAnalysis
|
--- @class AtomAnalysis
|
||||||
--- @field atom AtomBody
|
--- @field atom AtomBody
|
||||||
--- @field tokens Token[] -- the tokens in the atom body, annotated
|
--- @field tokens Token[] -- Tokens in the atom body, annotated
|
||||||
--- @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
|
-- Per-word-event helpers
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
-- Pick the source-line field that best represents "where in the user's source file is this word?".
|
-- 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:
|
-- `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)
|
-- * `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(...)`)
|
-- * `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(...)`)
|
||||||
@@ -169,8 +165,7 @@ local OUTPUT_EXTENSION = ".static_analysis.txt"
|
|||||||
-- The user editing their atom body expects the line to point at THEIR source — i.e. the line where `mac_yield()`
|
-- 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`.
|
-- 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,
|
-- For direct words in the atom body (no invocation wrapping them), `call_line == body_line` already, so `call_line` works for both cases.
|
||||||
-- so `call_line` works for both cases.
|
|
||||||
local function line_for_word_event(ev)
|
local function line_for_word_event(ev)
|
||||||
if ev == nil then return 0 end
|
if ev == nil then return 0 end
|
||||||
return ev.call_line or ev.body_line or ev.line or ev.def_line or 0
|
return ev.call_line or ev.body_line or ev.line or ev.def_line or 0
|
||||||
@@ -193,46 +188,44 @@ end
|
|||||||
--
|
--
|
||||||
-- The classification is stored on `atom.paths.tok_class` as an array indexed by token index (1..#tokens).
|
-- The classification is stored on `atom.paths.tok_class` as an array indexed by token index (1..#tokens).
|
||||||
-- Each entry has:
|
-- Each entry has:
|
||||||
-- ident — the leading identifier (e.g. "load_word", "gte_cmdw_rtpt", "nop", "mac_yield")
|
-- ident — the leading identifier (e.g. "load_word", "gte_cmdw_rtpt", "nop", "mac_yield")
|
||||||
-- nop_words — 0 / 1 / 2 (for "nop" / "nop2" / anything else)
|
-- nop_words — 0 / 1 / 2 (for "nop" / "nop2" / anything else)
|
||||||
-- nop_prefix — consecutive nop words ending just BEFORE this token (forward-pass pre-compute;
|
-- nop_prefix — consecutive nop words ending just BEFORE this token (forward-pass pre-compute; makes preceding-nop lookup O(N))
|
||||||
-- makes preceding-nop lookup O(N))
|
-- is_yield — true if this token is `mac_yield` or `mac_yield(...)`
|
||||||
-- is_yield — true if this token is `mac_yield` or `mac_yield(...)`
|
-- is_atom_label — true if this token is `atom_label(name)`; label_name has the name
|
||||||
-- is_atom_label — true if this token is `atom_label(name)`; label_name has the name
|
-- is_branch — true if this token is `branch_*(...)` OR an unconditional-jump-with-offset (`jump(off)` / `call_addr(off)`); branch_label has the target label or false
|
||||||
-- is_branch — true if this token is `branch_*(...)` OR an unconditional-jump-with-offset (`jump(off)` / `call_addr(off)`); branch_label has the target label or false
|
|
||||||
-- is_unconditional_jump — true if this token is `jump` or `call_addr` (BD slot + single successor — taken only; no fall-through).
|
-- is_unconditional_jump — true if this token is `jump` or `call_addr` (BD slot + single successor — taken only; no fall-through).
|
||||||
-- Mutually exclusive with the conditional-branch semantics; combined with `is_branch` above.
|
-- Mutually exclusive with the conditional-branch semantics; combined with `is_branch` above.
|
||||||
-- is_terminal_jump — true if this token is `jump_reg` / `call_reg` / `jump_link` (transfers control OUT of the current atom; the `mac_yield()` handshake ends in `jump_reg(R_AtomJmp), nop`).
|
-- is_terminal_jump — true if this token is `jump_reg` / `call_reg` / `jump_link` (transfers control OUT of the current atom; the `mac_yield()` handshake ends in `jump_reg(R_AtomJmp), nop`).
|
||||||
-- No offset field — `atom_offset` is invalid here. Terminates the current path in the CFG.
|
-- No offset field — `atom_offset` is invalid here. Terminates the current path in the CFG.
|
||||||
-- is_load — true if this token starts with any of: load_word, load_half, load_half_u, load_byte,
|
-- is_load — true if this token starts with any of: load_word, load_half, load_half_u, load_byte,
|
||||||
-- load_byte_u, gte_lw, gte_lwc2. These all have MIPS load-delay semantics (the
|
-- load_byte_u, gte_lw, gte_lwc2. These all have MIPS load-delay semantics (the destination register is volatile for 1 word after the load).
|
||||||
-- destination register is volatile for 1 word after the load).
|
-- is_store_word — true if this token starts with `store_word(`
|
||||||
-- is_store_word — true if this token starts with `store_word(`
|
|
||||||
--
|
--
|
||||||
-- Checks that need the leading ident use `tok_class.ident` instead of re-matching the token string.
|
-- Checks that need the leading ident use `tok_class.ident` instead of re-matching the token string.
|
||||||
-- Checks that need "how many nops before token i" use `tok_class.nop_prefix` instead of walking backwards.
|
-- Checks that need "how many nops before token i" use `tok_class.nop_prefix` instead of walking backwards.
|
||||||
|
|
||||||
--- @class TokClass
|
--- @class TokClass
|
||||||
--- @field ident string -- leading identifier
|
--- @field ident string -- lLading identifier
|
||||||
--- @field nop_words integer -- 0/1/2
|
--- @field nop_words integer -- 0 / 1 / 2
|
||||||
--- @field nop_prefix integer -- consecutive nop words before this token
|
--- @field nop_prefix integer -- Consecutive nop words before this token
|
||||||
--- @field is_yield boolean
|
--- @field is_yield boolean
|
||||||
--- @field is_atom_label boolean
|
--- @field is_atom_label boolean
|
||||||
--- @field label_name string|nil -- for atom_label(name)
|
--- @field label_name string|nil -- For atom_label(name)
|
||||||
--- @field is_branch boolean -- conditional branch OR unconditional-jump-with-offset
|
--- @field is_branch boolean -- Conditional branch OR unconditional-jump-with-offset
|
||||||
--- @field is_unconditional_jump boolean -- `jump` / `call_addr` only
|
--- @field is_unconditional_jump boolean -- `jump` / `call_addr` only
|
||||||
--- @field is_terminal_jump boolean -- `jump_reg` / `call_reg` / `jump_link` only
|
--- @field is_terminal_jump boolean -- `jump_reg` / `call_reg` / `jump_link` only
|
||||||
--- @field branch_label string|false|nil -- for branch_*(..., atom_offset(F, label)) OR jump/call_addr
|
--- @field branch_label string|false|nil -- For branch_*(..., atom_offset(F, label)) OR jump/call_addr
|
||||||
--- @field is_load boolean -- load_word | load_half | load_half_u | load_byte | load_byte_u | gte_lw | gte_lwc2
|
--- @field is_load boolean -- load_word | load_half | load_half_u | load_byte | load_byte_u | gte_lw | gte_lwc2
|
||||||
--- @field is_store_word boolean
|
--- @field is_store_word boolean
|
||||||
--- @field mac_format_shape string|nil -- "f3" / "g4" etc. for mac_format_X_color; nil otherwise
|
--- @field mac_format_shape string|nil -- "f3" / "g4" etc. for mac_format_X_color; nil otherwise
|
||||||
--- @field is_gte_store boolean -- ident matches `mac_gte_store_<shape>`
|
--- @field is_gte_store boolean -- Ident matches `mac_gte_store_<shape>`
|
||||||
--- @field is_ot_tag boolean -- ident matches `mac_insert_ot_tag_<shape>`
|
--- @field is_ot_tag boolean -- Ident matches `mac_insert_ot_tag_<shape>`
|
||||||
--- @field writes_r_prim_cursor boolean -- store_word targeting R_PrimCursor
|
--- @field writes_r_prim_cursor boolean -- store_word targeting R_PrimCursor
|
||||||
--- @field reads_r_tape_ptr boolean -- any token referencing R_TapePtr
|
--- @field reads_r_tape_ptr boolean -- Any token referencing R_TapePtr
|
||||||
--- @field o_arg1 string|nil -- first arg of O_(<a>, <b>) captures; nil for non-O_ tokens
|
--- @field o_arg1 string|nil -- First arg of O_(<a>, <b>) captures; nil for non-O_ tokens
|
||||||
--- @field o_arg2 string|nil -- second arg of O_(<a>, <b>) captures
|
--- @field o_arg2 string|nil -- Second arg of O_(<a>, <b>) captures
|
||||||
--- @field s_arg1 string|nil -- arg of S_(<a>) captures; nil for non-S_ tokens
|
--- @field s_arg1 string|nil -- Arg of S_(<a>) captures; nil for non-S_ tokens
|
||||||
|
|
||||||
-- The set of MIPS instruction idents that have a load-delay slot.
|
-- The set of MIPS instruction idents that have a load-delay slot.
|
||||||
-- Per MIPS I R3000A: `lw`, `lh`, `lhu`, `lb`, `lbu`, `lwc2` (gte_lw).
|
-- Per MIPS I R3000A: `lw`, `lh`, `lhu`, `lb`, `lbu`, `lwc2` (gte_lw).
|
||||||
@@ -343,7 +336,7 @@ local function classify_tokens(tokens)
|
|||||||
is_atom_label = is_atom_label,
|
is_atom_label = is_atom_label,
|
||||||
label_name = label_name,
|
label_name = label_name,
|
||||||
is_branch = is_branch,
|
is_branch = is_branch,
|
||||||
is_unconditional_jump = is_unconditional_jump,
|
is_unconditional_jump = is_unconditional_jump,
|
||||||
is_terminal_jump = is_terminal_jump,
|
is_terminal_jump = is_terminal_jump,
|
||||||
branch_label = branch_label,
|
branch_label = branch_label,
|
||||||
is_load = is_load,
|
is_load = is_load,
|
||||||
@@ -401,15 +394,13 @@ end
|
|||||||
-- therefore counts ONLY words strictly between the producer and the consumer.
|
-- therefore counts ONLY words strictly between the producer and the consumer.
|
||||||
-- ─────────────────────────────────────────────────────────────────────────
|
-- ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
-- True iff `consumer_event` is a GTE command (gte_cmdw_* or one of the human-readable aliases
|
-- True iff `consumer_event` is a GTE command (gte_cmdw_* or one of the human-readable aliases mapped in `duffle.GTE_COMMAND_ALIASES`).
|
||||||
-- mapped in `duffle.GTE_COMMAND_ALIASES`). Used by the LWC2 retirement-regime dispatch in the
|
-- Used by the LWC2 retirement-regime dispatch in the forward walker: a GTE-command consumer can read the LWC2 result in the very next slot
|
||||||
-- forward walker: a GTE-command consumer can read the LWC2 result in the very next slot (the GTE
|
-- (the GTE pipeline latches the LWC2 data); any other consumer must observe the standard MIPS load delay (gap >= 1).
|
||||||
-- pipeline latches the LWC2 data); any other consumer must observe the standard MIPS load delay
|
|
||||||
-- (gap >= 1).
|
|
||||||
local function is_gte_command(consumer_event)
|
local function is_gte_command(consumer_event)
|
||||||
local tok = consumer_event.encoder or consumer_event.ident or ""
|
local tok = consumer_event.encoder or consumer_event.ident or ""
|
||||||
if tok:sub(1, 9) == "gte_cmdw_" then return true end
|
if tok:sub(1, 9) == "gte_cmdw_" then return true end
|
||||||
local aliases = duffle.GTE_COMMAND_ALIASES or {}
|
local aliases = duffle.GTE_COMMAND_ALIASES or {}
|
||||||
return aliases[tok] ~= nil
|
return aliases[tok] ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -422,7 +413,7 @@ local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
|
|||||||
-- not used by MTC2/CTC2 today because the "consumer" is a GTE command and its reads are not operand positions.)
|
-- not used by MTC2/CTC2 today because the "consumer" is a GTE command and its reads are not operand positions.)
|
||||||
local args = consumer_event.args or {}
|
local args = consumer_event.args or {}
|
||||||
for _, pos in ipairs(args) do
|
for _, pos in ipairs(args) do
|
||||||
if pos == destination then return true end
|
if pos == destination then return true end
|
||||||
end
|
end
|
||||||
-- Match via the command's input set: the consumer encoder resolves to a `gte_cmdw_*`
|
-- Match via the command's input set: the consumer encoder resolves to a `gte_cmdw_*`
|
||||||
-- short form whose `duffle.GTE_COMMAND_INPUTS` entry includes the destination (or a fan-out target).
|
-- short form whose `duffle.GTE_COMMAND_INPUTS` entry includes the destination (or a fan-out target).
|
||||||
@@ -718,8 +709,8 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
|||||||
local transition = forward.cu2_transition
|
local transition = forward.cu2_transition
|
||||||
if not transition then return end
|
if not transition then return end
|
||||||
|
|
||||||
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)
|
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",
|
||||||
@@ -752,8 +743,7 @@ local function consume_cu2_transition(atom, event, ev_word, forward)
|
|||||||
else
|
else
|
||||||
append_cu2_finding(atom, event, forward, transition, gap,
|
append_cu2_finding(atom, event, forward, transition, gap,
|
||||||
"error", "exact",
|
"error", "exact",
|
||||||
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_line,
|
atom.name, event_line,
|
||||||
gap, transition.required))
|
gap, transition.required))
|
||||||
@@ -831,8 +821,8 @@ local function analyze_hardware_relations(atom)
|
|||||||
local is_match = false
|
local is_match = false
|
||||||
if semantic == "MTC2" or semantic == "CTC2" or semantic == "LWC2_to_GTE" or semantic == "LWC2_to_other" then
|
if semantic == "MTC2" or semantic == "CTC2" or semantic == "LWC2_to_GTE" or semantic == "LWC2_to_other" then
|
||||||
-- Consumer is a GTE command whose input set contains the producer's COP2 destination (or a fan-out target).
|
-- Consumer is a GTE command whose input set contains the producer's COP2 destination (or a fan-out target).
|
||||||
-- LWC2_to_GTE — GTE-command consumer: gap = 0 OK (the pipeline latches the LWC2 result).
|
-- LWC2_to_GTE — GTE-command consumer: gap = 0 OK (the pipeline latches the LWC2 result).
|
||||||
-- LWC2_to_other — non-GTE consumer: standard load delay applies.
|
-- LWC2_to_other — non-GTE consumer: standard load delay applies.
|
||||||
if relation.id == "lwc2_to_gte_command" then
|
if relation.id == "lwc2_to_gte_command" then
|
||||||
is_match = is_gte_command(ev) and is_cop2_consumer_of(ev, prod.destination, relation)
|
is_match = is_gte_command(ev) and is_cop2_consumer_of(ev, prod.destination, relation)
|
||||||
elseif relation.id == "lwc2_to_other_consumer" then
|
elseif relation.id == "lwc2_to_other_consumer" then
|
||||||
@@ -1266,21 +1256,26 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
else
|
else
|
||||||
-- Track the slot_kind so the BD-separation case can assert the mac_yield handshake is still suppressed.
|
-- Track the slot_kind so the BD-separation case can assert the mac_yield handshake is still suppressed.
|
||||||
local slot_kind = "plain"
|
local slot_kind = "plain"
|
||||||
-- MIPS load-delay slot: a `load_*` wrote a register in the previous slot, and the result
|
-- MIPS load-delay slot: a `load_*` wrote a register in the previous slot, and the result is unavailable for 1 cycle.
|
||||||
-- is unavailable for 1 cycle. This `nop` is structurally required; classifying it as
|
-- This `nop` is structurally required; classifying it as `modeled-required` is the correct signal
|
||||||
-- `modeled-required` is the correct signal (removing it would make the following
|
-- (removing it would make the following instruction read the OLD value of the loaded register, a load-use hazard).
|
||||||
-- instruction read the OLD value of the loaded register, a load-use hazard). The
|
-- The `load_delay_violations` check (Concern 3) catches the actual read-side error; here we suppress the `modeled-redundant` misclassification.
|
||||||
-- `load_delay_violations` check (Concern 3) catches the actual read-side error; here
|
|
||||||
-- we suppress the `modeled-redundant` misclassification.
|
|
||||||
-- The set of load instructions mirrors the LOAD_INSTRUCTION_IDENTS in `check_load_delay_slots`.
|
-- The set of load instructions mirrors the LOAD_INSTRUCTION_IDENTS in `check_load_delay_slots`.
|
||||||
local load_idents = { load_word = true, load_half = true, load_half_u = true,
|
local load_idents = {
|
||||||
load_byte = true, load_byte_u = true, gte_lw = true, gte_lwc2 = true }
|
load_word = true,
|
||||||
|
load_half = true,
|
||||||
|
load_half_u = true,
|
||||||
|
load_byte = true,
|
||||||
|
load_byte_u = true,
|
||||||
|
gte_lw = true,
|
||||||
|
gte_lwc2 = true
|
||||||
|
}
|
||||||
local is_load_delay = load_idents[prev_ident] == true
|
local is_load_delay = load_idents[prev_ident] == true
|
||||||
if is_load_delay then
|
if is_load_delay then
|
||||||
-- Determine the destination register from the load's `writes` field.
|
-- Determine the destination register from the load's `writes` field.
|
||||||
local prev_writes = gpr_effects[prev_ident] and gpr_effects[prev_ident].writes or {}
|
local prev_writes = gpr_effects[prev_ident] and gpr_effects[prev_ident].writes or {}
|
||||||
local prev_args = prev_ev.args or {}
|
local prev_args = prev_ev.args or {}
|
||||||
local load_dest = prev_writes[1] and prev_args[prev_writes[1]] or "<load-destination>"
|
local load_dest = prev_writes[1] and prev_args[prev_writes[1]] or "<load-destination>"
|
||||||
findings[#findings + 1] = {
|
findings[#findings + 1] = {
|
||||||
check = "hazard_nop_use",
|
check = "hazard_nop_use",
|
||||||
kind = "info",
|
kind = "info",
|
||||||
@@ -1447,7 +1442,7 @@ end
|
|||||||
local function check_load_delay_slots(atom, pipe_ctx, findings)
|
local function check_load_delay_slots(atom, pipe_ctx, findings)
|
||||||
if atom.kind ~= "atom" then return end
|
if atom.kind ~= "atom" then return end
|
||||||
local events = atom.paths.word_events or {}
|
local events = atom.paths.word_events or {}
|
||||||
if #events == 0 then return end
|
if #events == 0 then return end
|
||||||
if is_runtime_helper(atom) then return end
|
if is_runtime_helper(atom) then return end
|
||||||
|
|
||||||
local gpr_effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
|
local gpr_effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
|
||||||
@@ -2349,20 +2344,20 @@ end
|
|||||||
-- This is the plex pattern: the iteration is in ONE place (validate), the variation is in DATA (this table).
|
-- This is the plex pattern: the iteration is in ONE place (validate), the variation is in DATA (this table).
|
||||||
|
|
||||||
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_role_mismatch", per_atom = check_gte_role_mismatch },
|
{ 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 = "load_delay_violation", per_atom = check_load_delay_slots },
|
{ name = "load_delay_violation", per_atom = check_load_delay_slots },
|
||||||
{ name = "mac_yield_uniformity", per_atom = check_mac_yield_uniformity },
|
{ name = "mac_yield_uniformity", per_atom = check_mac_yield_uniformity },
|
||||||
{ name = "yield_load_tail_pairing", per_atom = check_yield_load_tail_pairing },
|
{ name = "yield_load_tail_pairing", per_atom = check_yield_load_tail_pairing },
|
||||||
{ name = "abi_handoff", per_atom = check_abi_handoff },
|
{ name = "abi_handoff", per_atom = check_abi_handoff },
|
||||||
{ name = "gpu_portstore_shape", per_atom = check_gpu_portstore_shape },
|
{ name = "gpu_portstore_shape", per_atom = check_gpu_portstore_shape },
|
||||||
{ name = "per_atom_cycle_budget", per_atom = check_per_atom_cycle_budget },
|
{ name = "per_atom_cycle_budget", per_atom = check_per_atom_cycle_budget },
|
||||||
{ name = "enum_alias_membership", per_source = check_enum_alias_membership },
|
{ name = "enum_alias_membership", per_source = check_enum_alias_membership },
|
||||||
{ name = "atom_type_consistency", per_source = check_atom_type_consistency },
|
{ name = "atom_type_consistency", per_source = check_atom_type_consistency },
|
||||||
{ name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref },
|
{ name = "binds_no_substruct_deref", per_source = check_binds_no_substruct_deref },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -2521,28 +2516,27 @@ local function validate(ctx, src, corpus_pipe_ctx)
|
|||||||
-- Hazard readers (transfer_hazards) populate `f.check`, `f.relation_id`, `f.semantic`, `f.direction`, `f.producer_destination`, `f.gap`, `f.required`, `f.evidence_confidence`, etc.;
|
-- Hazard readers (transfer_hazards) populate `f.check`, `f.relation_id`, `f.semantic`, `f.direction`, `f.producer_destination`, `f.gap`, `f.required`, `f.evidence_confidence`, etc.;
|
||||||
-- Copying them through keeps the per-severity bucket schema compatible with the renderer while making the diagnostic payload queryable.
|
-- Copying them through keeps the per-severity bucket schema compatible with the renderer while making the diagnostic payload queryable.
|
||||||
local payload = {
|
local payload = {
|
||||||
line = f.line,
|
line = f.line,
|
||||||
msg = f.msg,
|
msg = f.msg,
|
||||||
check = f.check,
|
check = f.check,
|
||||||
atom = f.atom,
|
atom = f.atom,
|
||||||
source = f.source,
|
source = f.source,
|
||||||
relation_id = f.relation_id,
|
relation_id = f.relation_id,
|
||||||
semantic = f.semantic,
|
semantic = f.semantic,
|
||||||
direction = f.direction,
|
direction = f.direction,
|
||||||
producer_destination = f.producer_destination,
|
producer_destination = f.producer_destination,
|
||||||
producer_word = f.producer_word,
|
producer_word = f.producer_word,
|
||||||
producer_line = f.producer_line,
|
producer_line = f.producer_line,
|
||||||
producer_source = f.producer_source,
|
producer_source = f.producer_source,
|
||||||
consumer_word = f.consumer_word,
|
consumer_word = f.consumer_word,
|
||||||
consumer_token = f.consumer_token,
|
consumer_token = f.consumer_token,
|
||||||
gap = f.gap,
|
gap = f.gap,
|
||||||
required = f.required,
|
required = f.required,
|
||||||
evidence_confidence = f.evidence_confidence,
|
evidence_confidence = f.evidence_confidence,
|
||||||
evidence_source = f.evidence_source,
|
evidence_source = f.evidence_source,
|
||||||
}
|
}
|
||||||
-- Preserve relation fields such as target_state and status_register,
|
-- Preserve relation fields such as target_state and status_register status_value,
|
||||||
-- status_value, and future policy metadata) without making the binner
|
-- and future policy metadata) without making the binner another semantic walker.
|
||||||
-- another semantic walker.
|
|
||||||
for key, value in pairs(f) do
|
for key, value in pairs(f) do
|
||||||
if payload[key] == nil then payload[key] = value end
|
if payload[key] == nil then payload[key] = value end
|
||||||
end
|
end
|
||||||
@@ -2624,7 +2618,7 @@ function M.run(ctx)
|
|||||||
-- Build the corpus-wide pipe_ctx ONCE per pass run.
|
-- Build the corpus-wide pipe_ctx ONCE per pass run.
|
||||||
-- The pipe_ctx is shared across every validate() invocation in this M.run so cross-source visibility is constant.
|
-- The pipe_ctx is shared across every validate() invocation in this M.run so cross-source visibility is constant.
|
||||||
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx)
|
local corpus_pipe_ctx = build_corpus_pipe_ctx(ctx)
|
||||||
local corpus = ctx.shared.corpus
|
local corpus = ctx.shared.corpus
|
||||||
|
|
||||||
-- Aggregate per-DIRECTORY (per-module).
|
-- Aggregate per-DIRECTORY (per-module).
|
||||||
-- One static_analysis.txt per source-directory, emitted only if the directory contains at least one atom.
|
-- One static_analysis.txt per source-directory, emitted only if the directory contains at least one atom.
|
||||||
@@ -2676,8 +2670,8 @@ function M.run(ctx)
|
|||||||
|
|
||||||
-- Aggregate per-dir errors/warnings/info into the orchestrator totals.
|
-- 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.
|
-- 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
|
||||||
-- (No per-dir emit: per-module findings are stashed on `corpus.static_analysis_results` above.
|
-- (No per-dir emit: per-module findings are stashed on `corpus.static_analysis_results` above.
|
||||||
-- `report.lua` reads that projection to render `<module>.atom_meta_report.md` without re-running validate().)
|
-- `report.lua` reads that projection to render `<module>.atom_meta_report.md` without re-running validate().)
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ end
|
|||||||
--- Load the authored `word_count.metadata.h` into `ctx.shared.corpus.word_counts`.
|
--- Load the authored `word_count.metadata.h` into `ctx.shared.corpus.word_counts`.
|
||||||
--- Generated `.macs.h` files are OUTPUT artifacts and are NOT scanned as inputs.
|
--- Generated `.macs.h` files are OUTPUT artifacts and are NOT scanned as inputs.
|
||||||
--- Current component counts are computed and inserted by `passes/components.lua`
|
--- Current component counts are computed and inserted by `passes/components.lua`
|
||||||
--- after the components pass iterates `corpus.source_order` and writes each source's `<dir_basename>.macs.h` file.
|
--- after the components pass iterates `corpus.source_order` and writes each source-directory's `gen/macs.h` file.
|
||||||
---
|
---
|
||||||
--- Contract:
|
--- Contract:
|
||||||
--- * `ctx.shared.corpus` MUST exist (canonical corpus ownership).
|
--- * `ctx.shared.corpus` MUST exist (canonical corpus ownership).
|
||||||
|
|||||||
@@ -269,9 +269,9 @@ PASS_FLAGS:
|
|||||||
Or pick any subset:
|
Or pick any subset:
|
||||||
--scan-source Scan sources into the fat SourceScan payload
|
--scan-source Scan sources into the fat SourceScan payload
|
||||||
--word-counts Load metadata.h + scan for existing .macs.h
|
--word-counts Load metadata.h + scan for existing .macs.h
|
||||||
--components Generate <module>/gen/<basename>.macs.h
|
--components Generate <srcdir>/gen/macs.h (per-directory aggregation)
|
||||||
--validate Run atom annotation DSL validation
|
--validate Run atom annotation DSL validation
|
||||||
--offsets Generate <module>/gen/<basename>.offsets.h
|
--offsets Generate <srcdir>/gen/offsets.h (per-directory aggregation)
|
||||||
--atoms-source-map Generate <basename>.atoms.sourcemap.txt per source
|
--atoms-source-map Generate <basename>.atoms.sourcemap.txt per source
|
||||||
--dwarf-injection [opt-in] Select the post-link dwarf-injection pass + set the opt-in flag. Requires --elf.
|
--dwarf-injection [opt-in] Select the post-link dwarf-injection pass + set the opt-in flag. Requires --elf.
|
||||||
--static-analysis Static analysis: GTE pipeline-fill, mac_yield, ABI handoff, cycle budget
|
--static-analysis Static analysis: GTE pipeline-fill, mac_yield, ABI handoff, cycle budget
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ if (-not $msbuild_exe) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$path_pcsx_sln = join-path $path_pcsx_redux 'vsprojects\pcsx-redux.sln'
|
$path_pcsx_sln = join-path $path_pcsx_redux 'vsprojects\pcsx-redux.sln'
|
||||||
& $msbuild_exe $path_pcsx_sln /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v143 /m /v:minimal
|
& $msbuild_exe $path_pcsx_sln /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v143 /m /v:minimal
|
||||||
|
|
||||||
# Locate luajit via scoop. `luajit.exe` is on PATH via scoop's shim;
|
# Locate luajit via scoop. `luajit.exe` is on PATH via scoop's shim;
|
||||||
# we use `scoop prefix` to find the install root for the include dir (needed to compile lpeg against luajit's headers).
|
# we use `scoop prefix` to find the install root for the include dir (needed to compile lpeg against luajit's headers).
|
||||||
@@ -70,8 +70,8 @@ if (-not $luajit_prefix -or -not (Test-Path (Join-Path $luajit_prefix 'bin/luaji
|
|||||||
# Discover the luajit include dir by globbing `include/luajit-*`.
|
# Discover the luajit include dir by globbing `include/luajit-*`.
|
||||||
# This avoids hardcoding a specific version (e.g. `luajit-2.1`).
|
# This avoids hardcoding a specific version (e.g. `luajit-2.1`).
|
||||||
$luajit_include_root = Join-Path $luajit_prefix 'include'
|
$luajit_include_root = Join-Path $luajit_prefix 'include'
|
||||||
$lua_inc_dir = Get-ChildItem -Path $luajit_include_root -Directory -Filter 'luajit-*' -ErrorAction SilentlyContinue |
|
$lua_inc_dir = Get-ChildItem -Path $luajit_include_root -Directory -Filter 'luajit-*' -ErrorAction SilentlyContinue |
|
||||||
Select-Object -First 1 -ExpandProperty FullName
|
Select-Object -First 1 -ExpandProperty FullName
|
||||||
if (-not $lua_inc_dir) {
|
if (-not $lua_inc_dir) {
|
||||||
write-error "No 'luajit-*' include dir found under '$luajit_include_root'. The scoop luajit install may be broken."
|
write-error "No 'luajit-*' include dir found under '$luajit_include_root'. The scoop luajit install may be broken."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -90,7 +90,7 @@ $lpeg_compile_args = @(
|
|||||||
'-o', 'lpeg.dll'
|
'-o', 'lpeg.dll'
|
||||||
) + $lpeg_sources + @('-lluajit-5.1')
|
) + $lpeg_sources + @('-lluajit-5.1')
|
||||||
push-location $path_lpeg
|
push-location $path_lpeg
|
||||||
& gcc @lpeg_compile_args
|
& gcc @lpeg_compile_args
|
||||||
pop-location
|
pop-location
|
||||||
|
|
||||||
# ════════════════════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -101,8 +101,8 @@ pop-location
|
|||||||
|
|
||||||
$path_lfs = join-path $path_toolchain 'lfs'
|
$path_lfs = join-path $path_toolchain 'lfs'
|
||||||
verify-path $path_lfs
|
verify-path $path_lfs
|
||||||
$lfs_src = join-path $path_pcsx_redux 'third_party\luafilesystem\src\lfs.c'
|
$lfs_src = join-path $path_pcsx_redux 'third_party\luafilesystem\src\lfs.c'
|
||||||
$lfs_dll = join-path $path_lfs 'lfs.dll'
|
$lfs_dll = join-path $path_lfs 'lfs.dll'
|
||||||
$lfs_dll_import = join-path $luajit_lib_dir 'libluajit-5.1.dll.a'
|
$lfs_dll_import = join-path $luajit_lib_dir 'libluajit-5.1.dll.a'
|
||||||
& gcc -O2 -shared "-I$lua_inc_dir" -o $lfs_dll $lfs_src $lfs_dll_import
|
& gcc -O2 -shared "-I$lua_inc_dir" -o $lfs_dll $lfs_src $lfs_dll_import
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user