4 Commits
23 changed files with 1222 additions and 552 deletions
+43
View File
@@ -0,0 +1,43 @@
# Package and install the local VS Code Insiders extensions under .vscode/.
# Usage:
# .\install_extensions.ps1
# .\install_extensions.ps1 -SkipPackage
param([switch] $SkipPackage)
$path_vscode = $PSScriptRoot
$code_insiders = "C:\apps\Microsoft VS Code Insiders\bin\code-insiders.cmd"
if (-not (test-path -literalpath $code_insiders)) {
$found = get-command code-insiders -erroraction silentlycontinue
if ($found) { $code_insiders = $found.source }
}
if (-not (test-path -literalpath $code_insiders)) { throw "code-insiders not found. Install VS Code Insiders or add it to PATH." }
$extensions = @(
(join-path $path_vscode "tape-atom-syntax"),
(join-path $path_vscode "cozy-and-windy")
)
foreach ($extension in $extensions) {
$package_json = join-path $extension "package.json"
if (-not (test-path -literalpath $package_json)) { throw "missing $package_json" }
$manifest = get-content -literalpath $package_json -raw | convertfrom-json
$vsix = join-path $extension ("{0}-{1}.vsix" -f $manifest.name, $manifest.version)
if (-not $SkipPackage) {
if (-not $manifest.scripts.package) { throw "$package_json has no scripts.package" }
write-host "packaging $($manifest.displayName) ($($manifest.name)@$($manifest.version))"
& npm --prefix $extension run package
if ($LASTEXITCODE -ne 0) { throw "npm run package failed for $extension" }
}
if (-not (test-path -literalpath $vsix)) { throw "missing $vsix" }
write-host "installing $vsix"
& $code_insiders --install-extension $vsix --force
if ($LASTEXITCODE -ne 0) { throw "install failed for $vsix" }
}
write-host "done. reload the Insiders window (Developer: Reload Window)."
+1
View File
@@ -148,6 +148,7 @@ typedef void Proc_(VoidFn) (void);
#define null C_(U4, 0) #define null C_(U4, 0)
#define nullptr C_(void*, 0) #define nullptr C_(void*, 0)
#define O_(type, field) C_(U4, & C_(type*,0)->field) #define O_(type, field) C_(U4, & C_(type*,0)->field)
#define OA_(type, aexpr) C_(U4, & C_(type*,0) aexpr)
#define OT_(field) O_(typeof_ptr(& field), field)) #define OT_(field) O_(typeof_ptr(& field), field))
#define S_(data) C_(U4, sizeof(data)) #define S_(data) C_(U4, sizeof(data))
+57 -28
View File
@@ -17,7 +17,7 @@
// source: C:\projects\Pikuma\ps1\code\duffle\bios.h // source: C:\projects\Pikuma\ps1\code\duffle\bios.h
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.h // source: C:\projects\Pikuma\ps1\code\duffle\psyq.h
// source: C:\projects\Pikuma\ps1\code\duffle\pad.c // source: C:\projects\Pikuma\ps1\code\duffle\pad.c
// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\math.atom.h
// source: C:\projects\Pikuma\ps1\code\duffle\mips.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\gte.atom.c
// source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c
@@ -71,19 +71,42 @@ WORD_COUNT(mac_load_v2s2, 2)
WORD_COUNT(mac_store_v2s2, 2) WORD_COUNT(mac_store_v2s2, 2)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_load_v3s4(rs_x, rs_y, rs_z, r_base, offset) \ #define mac_load_word_v3(tx, ty, tz, base, offset) \
load_word( rs_x, r_base, offset + O_(V3_S4,x)) \ load_word(tx, base, offset + OA_(U4,[0])) \
, load_word( rs_y, r_base, offset + O_(V3_S4,y)) \ , load_word(ty, base, offset + OA_(U4,[1])) \
, load_word( rs_z, r_base, offset + O_(V3_S4,z)) , load_word(tz, base, offset + OA_(U4,[2]))
WORD_COUNT(mac_load_word_v3, 3)
#define mac_load_v3s4(transfer, base, offset) \
mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset)
WORD_COUNT(mac_load_v3s4, 3) WORD_COUNT(mac_load_v3s4, 3)
#define mac_load_p3s4(transfer, base, offset) \
mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset)
WORD_COUNT(mac_load_p3s4, 3)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_store_v3s4(rt_x, rt_y, rt_z, base, offset) \ #define mac_store_word_v3(tx, ty, tz, base, offset) \
store_word(rt_x, base, offset + O_(V3_S4,x)) \ store_word(tx, base, offset + OA_(U4,[0])) \
, store_word(rt_y, base, offset + O_(V3_S4,y)) \ , store_word(ty, base, offset + OA_(U4,[1])) \
, store_word(rt_z, base, offset + O_(V3_S4,z)) , store_word(tz, base, offset + OA_(U4,[2]))
WORD_COUNT(mac_store_word_v3, 3)
#define mac_store_v3s4(transfer, base, offset) \
mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset)
WORD_COUNT(mac_store_v3s4, 3) WORD_COUNT(mac_store_v3s4, 3)
#define mac_store_p3s4(transfer, base, offset) \
mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset)
WORD_COUNT(mac_store_p3s4, 3)
/* atom_dbg_skip */
#define mac_add_si_v3s4(rt_x, rt_y, rt_z, base, offset) \
add_si(rt_x, base, O_(V3_S4,x)) \
, add_si(rt_y, base, O_(V3_S4,y)) \
, add_si(rt_z, base, O_(V3_S4,z))
WORD_COUNT(mac_add_si_v3s4, 3)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_sub_v3s4(rds_x, rds_y, rds_z, rt_x, rt_y, rt_z) \ #define mac_sub_v3s4(rds_x, rds_y, rds_z, rt_x, rt_y, rt_z) \
sub_s(rds_x, rds_x, rt_x) \ sub_s(rds_x, rds_x, rt_x) \
@@ -105,6 +128,24 @@ WORD_COUNT(mac_store_rects2, 4)
, or_i_self( dst, u4_lo(imm)) , or_i_self( dst, u4_lo(imm))
WORD_COUNT(mac_load_word_imm, 2) WORD_COUNT(mac_load_word_imm, 2)
#define mac_shift_aright_v3_self(dt_x, dt_y, dt_z, shift_amount) \
shift_aright(dt_x, dt_x, shift_amount) \
, shift_aright(dt_y, dt_y, shift_amount) \
, shift_aright(dt_z, dt_z, shift_amount)
WORD_COUNT(mac_shift_aright_v3_self, 3)
#define mac_shift_aright_var_v3(rd_v0, rd_v1, rd_v2, rs_v0, rs_v1, rs_v2, r_shift) \
shift_aright_var(rd_v0, rs_v0, r_shift) \
, shift_aright_var(rd_v1, rs_v1, r_shift) \
, shift_aright_var(rd_v2, rs_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3, 3)
#define mac_shift_aright_var_v3_self(rds_v0, rds_v1, rds_v2, r_shift) \
shift_aright_var(rds_v0, rds_v0, r_shift) \
, shift_aright_var(rds_v1, rds_v1, r_shift) \
, shift_aright_var(rds_v2, rds_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3_self, 3)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_load_tri_indices(r_face_cusor, r_i0, r_i1, r_i2) \ #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_i0, r_face_cusor, 0 * S_(S2)) \
@@ -125,19 +166,19 @@ WORD_COUNT(mac_gte_store_f3, 3)
, add_u_self(R_AT, r_vert_base) \ , add_u_self(R_AT, r_vert_base) \
, load_word(R_V0, R_AT, O_(V3_S2,x)) \ , load_word(R_V0, R_AT, O_(V3_S2,x)) \
, load_word(R_V1, R_AT, O_(V3_S2,z)) \ , load_word(R_V1, R_AT, O_(V3_S2,z)) \
, gte_mv_to_data_r(R_V0, C2_VXY0) \ , LdSlot_ gte_mv_to_data_r(R_V0, C2_VXY0) \
, gte_mv_to_data_r(R_V1, C2_VZ0) \ , gte_mv_to_data_r(R_V1, C2_VZ0) \
, shift_lleft(R_AT, r_v1, v3s2_byteoff) \ , shift_lleft(R_AT, r_v1, v3s2_byteoff) \
, add_u_self(R_AT, r_vert_base) \ , add_u_self(R_AT, r_vert_base) \
, load_word(R_V0, R_AT, O_(V3_S2,x)) \ , load_word(R_V0, R_AT, O_(V3_S2,x)) \
, load_word(R_V1, R_AT, O_(V3_S2,z)) \ , load_word(R_V1, R_AT, O_(V3_S2,z)) \
, gte_mv_to_data_r(R_V0, C2_VXY1) \ , LdSlot_ gte_mv_to_data_r(R_V0, C2_VXY1) \
, gte_mv_to_data_r(R_V1, C2_VZ1) \ , gte_mv_to_data_r(R_V1, C2_VZ1) \
, shift_lleft(R_AT, r_v2, v3s2_byteoff) \ , shift_lleft(R_AT, r_v2, v3s2_byteoff) \
, add_u_self(R_AT, r_vert_base) \ , add_u_self(R_AT, r_vert_base) \
, load_word(R_V0, R_AT, O_(V3_S2,x)) \ , load_word(R_V0, R_AT, O_(V3_S2,x)) \
, load_word(R_V1, R_AT, O_(V3_S2,z)) \ , load_word(R_V1, R_AT, O_(V3_S2,z)) \
, gte_mv_to_data_r(R_V0, C2_VXY2) \ , LdSlot_ gte_mv_to_data_r(R_V0, C2_VXY2) \
, gte_mv_to_data_r(R_V1, C2_VZ2) , gte_mv_to_data_r(R_V1, C2_VZ2)
WORD_COUNT(mac_gte_load_tri_verts, 18) WORD_COUNT(mac_gte_load_tri_verts, 18)
@@ -162,11 +203,11 @@ WORD_COUNT(mac_gte_store_g4_p3, 1)
WORD_COUNT(mac_gte_sqr_v3, 8) WORD_COUNT(mac_gte_sqr_v3, 8)
/* atom_dbg_skip */ /* atom_dbg_skip */
#define mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, nop_slot) \ #define mac_gte_sqr_v3s4(r_sx, r_sy, r_sz, delay_slot) \
gte_mv_to_data_r(r_sx, C2_IR1) \ gte_mv_to_data_r(r_sx, C2_IR1) \
, gte_mv_to_data_r(r_sy, C2_IR2) \ , gte_mv_to_data_r(r_sy, C2_IR2) \
, gte_mv_to_data_r(r_sz, C2_IR3) \ , gte_mv_to_data_r(r_sz, C2_IR3) \
, nop_slot \ , delay_slot \
, gte_cmdw_sqr , gte_cmdw_sqr
WORD_COUNT(mac_gte_sqr_v3s4, 5) WORD_COUNT(mac_gte_sqr_v3s4, 5)
@@ -204,25 +245,13 @@ WORD_COUNT(mac_trans_mt3s3s4, 6)
, shift_aright(r_mag_sq, r_mag_sq, 1) , shift_aright(r_mag_sq, r_mag_sq, 1)
WORD_COUNT(mac_lzcr_round_even_half_shift, 5) WORD_COUNT(mac_lzcr_round_even_half_shift, 5)
#define mac_shift_aright_var_v3(rd_v0, rd_v1, rd_v2, rs_v0, rs_v1, rs_v2, r_shift) \
shift_aright_var(rd_v0, rs_v0, r_shift) \
, shift_aright_var(rd_v1, rs_v1, r_shift) \
, shift_aright_var(rd_v2, rs_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3, 3)
#define mac_shift_aright_var_v3_self(rds_v0, rds_v1, rds_v2, r_shift) \
shift_aright_var(rds_v0, rds_v0, r_shift) \
, shift_aright_var(rds_v1, rds_v1, r_shift) \
, shift_aright_var(rds_v2, rds_v2, r_shift)
WORD_COUNT(mac_shift_aright_var_v3_self, 3)
#define mac_gte_general_purpose_interopolation(to_ir0, to_ir1, to_ir2, to_ir3, fr_mac1, fr_mac2, fr_mac3, nop_slot1, nop_slot2) \ #define mac_gte_general_purpose_interopolation(to_ir0, to_ir1, to_ir2, to_ir3, fr_mac1, fr_mac2, fr_mac3, nop_slot1, nop_slot2) \
gte_mv_to_data_r(to_ir0, C2_IR0) \ gte_mv_to_data_r(to_ir0, C2_IR0) \
, gte_mv_to_data_r(to_ir1, C2_IR1) /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ \ , gte_mv_to_data_r(to_ir1, C2_IR1) /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ \
, gte_mv_to_data_r(to_ir2, C2_IR2) \ , gte_mv_to_data_r(to_ir2, C2_IR2) \
, gte_mv_to_data_r(to_ir3, C2_IR3) /* IR3 = src.z (reloaded) */ \ , gte_mv_to_data_r(to_ir3, C2_IR3) /* IR3 = src.z (reloaded) */ \
, LdSlot_ nop_slot1 \ , DmaSlot_ nop_slot1 \
, LdSlot_ nop_slot2 \ , DmaSlot_ nop_slot2 \
, gte_cmdw_gpf \ , gte_cmdw_gpf \
, gte_mv_from_data_r(fr_mac1, C2_MAC1) \ , gte_mv_from_data_r(fr_mac1, C2_MAC1) \
, gte_mv_from_data_r(fr_mac2, C2_MAC2) \ , gte_mv_from_data_r(fr_mac2, C2_MAC2) \
+9 -1
View File
@@ -14,7 +14,7 @@
// source: C:\projects\Pikuma\ps1\code\duffle\bios.h // source: C:\projects\Pikuma\ps1\code\duffle\bios.h
// source: C:\projects\Pikuma\ps1\code\duffle\psyq.h // source: C:\projects\Pikuma\ps1\code\duffle\psyq.h
// source: C:\projects\Pikuma\ps1\code\duffle\pad.c // source: C:\projects\Pikuma\ps1\code\duffle\pad.c
// source: C:\projects\Pikuma\ps1\code\duffle\math.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\math.atom.h
// source: C:\projects\Pikuma\ps1\code\duffle\mips.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\gte.atom.c
// source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c // source: C:\projects\Pikuma\ps1\code\duffle\gp.atom.c
@@ -25,6 +25,14 @@
#pragma region duffle #pragma region duffle
// --- atom: example_atom (10 words) ---
#define _atom_offset_example_atom_proc_skip 2
enum {
atom_offset_example_atom_proc_skip = _atom_offset_example_atom_proc_skip,
};
// --- atom: normalize_v3s4 (47 words) --- // --- atom: normalize_v3s4 (47 words) ---
#define _atom_offset_aligned_done_srav_path 3 #define _atom_offset_aligned_done_srav_path 3
+2 -1
View File
@@ -44,7 +44,8 @@ atom_dbg_skip MipsAtomComp_Proc_(ab, {
}) })
/* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. */ /* Words: 11; Correctly inserts a primitive into the Ordering Table linked list. */
I_ Slice_MipsCode ac_insert_ot_tag(AtomBuilder_R ab, U4 r_ot_base, U4 r_prim_cursor, U4 poly_size) MipsAtomComp_Proc_(ab, { // TODO(Ed): Expose R_T1 as a r_t0, r_V0 as r_t2
I_ Slice_MipsCode ac_insert_ot_tag(AtomBuilder_R ab, Reg r_ot_base, Reg r_prim_cursor, U2 poly_size) MipsAtomComp_Proc_(ab, {
shift_lleft( R_T1, R_T1, S_(U4)/2), // T1 = otz * S_(U4) (otz arg is implicit R_T1) 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] 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_word( R_AT, R_T1, O_(PolyTag,code)), // AT = old_ot_head
+3 -6
View File
@@ -418,14 +418,11 @@ typedef Struct_(PolyTag) {
}; };
}; };
/* DSL cast convention: every cast uses `C_()`, every pointer qualifier is `R_` (restrict) or `V_` (volatile).
* No raw C-style casts. RHS values are assumed to be `U4` — caller passes a `U4` directly. */
#define set_len(tag,v) (C_(PolyTag_R,tag)->len = u4_(v)) #define set_len(tag,v) (C_(PolyTag_R,tag)->len = u4_(v))
#define set_addr(tag,v) (C_(PolyTag_R,tag)->addr = u4_(v)) #define set_addr(tag,v) (C_(PolyTag_R,tag)->addr = u4_(v))
/* `set_code` is no longer in the new PolyTag design — the code byte lives in the primitive body /* `set_code` is no longer in the new PolyTag design
* (e.g. `((Poly_F3*)(p))->code`), not in the tag. * (e.g. `((Poly_F3*)(p))->code`), not in the tag.
* Use the typed primitive structs (Poly_F3, Poly_G4, etc.) and the `set_poly_*` setters, * Use the typed primitive structs (Poly_F3, Poly_G4, etc.) and the `set_poly_*` setters, which set both the tag's length and the code. */
* which set both the tag's length and the code. */
#define get_len(tag) C_(U4,C_(PolyTag_R,tag)->len) #define get_len(tag) C_(U4,C_(PolyTag_R,tag)->len)
#define get_addr(tag) C_(U4,C_(PolyTag_R,tag)->addr) #define get_addr(tag) C_(U4,C_(PolyTag_R,tag)->addr)
@@ -572,7 +569,7 @@ enum {
/* Default TPage value libpsyx's SetDefDrawEnv writes (matches the `li v1, 10; sh v1, 20(v0)` sequence at C11_only.elf:0x8001273C). */ /* Default TPage value libpsyx's SetDefDrawEnv writes (matches the `li v1, 10; sh v1, 20(v0)` sequence at C11_only.elf:0x8001273C). */
gp0_tpage_default = 10, gp0_tpage_default = 10,
/* TPage semi-transparency mode payload values (NOT bit positions). */ /* TPage semi-transparency mode payload values. */
gp0_tpage_semi_trans_none = 0x0, gp0_tpage_semi_trans_none = 0x0,
gp0_tpage_semi_trans_alpha = 0x1, gp0_tpage_semi_trans_alpha = 0x1,
gp0_tpage_semi_trans_add = 0x2, gp0_tpage_semi_trans_add = 0x2,
+19 -41
View File
@@ -28,9 +28,9 @@ FI_ Slice_MipsCode ac_gte_store_f3(AtomBuilder_R ab, U4 r_primitive_cursor) atom
/* Words: 18; Translates indices to vertex addresses and pushes them to GTE */ /* Words: 18; Translates indices to vertex addresses and pushes them to GTE */
I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ab, { I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v0, U4 r_v1, U4 r_v2) atom_dbg_skip MipsAtomComp_Proc_(ab, {
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_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)), LdSlot_ 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_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)), LdSlot_ 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), 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)), LdSlot_ 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 /* Words: 3; Stores the 3 transformed (V2_S2 screen) vertices of the
@@ -38,7 +38,7 @@ I_ Slice_MipsCode ac_gte_load_tri_verts(AtomBuilder_R ab, U4 r_vert_base, U4 r_v
* PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen). * PIPELINE: post-RTPT, pre-RTPS (SXY0=v0.screen, SXY1=v1.screen, SXY2=v2.screen).
* MUST be called BEFORE V3-RTPS, otherwise SXY0/1/2 get overwritten with v3 * 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). */ * (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(AtomBuilder_R ab, U4 r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ab, { FI_ Slice_MipsCode ac_gte_store_g4_p012(AtomBuilder_R ab, Reg r_primitive_cursor) atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_sw(C2_SXY0, r_primitive_cursor, O_(Poly_G4,p0)), 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_SXY1, r_primitive_cursor, O_(Poly_G4,p1)),
gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2)), gte_sw(C2_SXY2, r_primitive_cursor, O_(Poly_G4,p2)),
@@ -65,12 +65,12 @@ FI_ Slice_MipsCode ac_gte_sqr_v3(AtomBuilder_R ab, U4 r_sx, U4 r_sy, U4 r_sz, U4
* The SQR command always squares IR1/IR2/IR3 — those C2 registers are fixed. * The SQR command always squares IR1/IR2/IR3 — those C2 registers are fixed.
* The GPRs holding the source vector are caller-determined. * The GPRs holding the source vector are caller-determined.
* Words: 5 (3 mtc2 + 1 nop hazard + 1 cmd). */ * Words: 5 (3 mtc2 + 1 nop hazard + 1 cmd). */
FI_ Slice_MipsCode ac_gte_sqr_v3s4(AtomBuilder_R ab, Reg r_sx, Reg r_sy, Reg r_sz, MipsCode nop_slot) FI_ Slice_MipsCode ac_gte_sqr_v3s4(AtomBuilder_R ab, Reg r_sx, Reg r_sy, Reg r_sz, MipsCode delay_slot)
atom_dbg_skip MipsAtomComp_Proc_(ab, { atom_dbg_skip MipsAtomComp_Proc_(ab, {
gte_mv_to_data_r(r_sx, C2_IR1), gte_mv_to_data_r(r_sx, C2_IR1),
gte_mv_to_data_r(r_sy, C2_IR2), gte_mv_to_data_r(r_sy, C2_IR2),
gte_mv_to_data_r(r_sz, C2_IR3), gte_mv_to_data_r(r_sz, C2_IR3),
nop_slot, gte_cmdw_sqr, delay_slot, gte_cmdw_sqr,
}) })
/* ─── STAGE 4 of normalize: mtc2 IR0..3 + GPF + mfc2 MAC + srav finalize ─── /* ─── STAGE 4 of normalize: mtc2 IR0..3 + GPF + mfc2 MAC + srav finalize ───
@@ -132,8 +132,7 @@ FI_ Slice_MipsCode ac_trans_mt3s3s4(AtomBuilder_R ab
FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab, FI_ Slice_MipsCode ac_lzcr_round_even_half_shift(AtomBuilder_R ab,
U4 r_shift, U4 r_shift,
U4 r_mag_sq, U4 r_mag_sq,
U4 r_mag_sq_copy U4 r_mag_sq_copy)
)
atom_dbg_skip MipsAtomComp_Proc_(ab, { atom_dbg_skip MipsAtomComp_Proc_(ab, {
and_i(r_shift, r_shift, gte_lzcr_even_mask), and_i(r_shift, r_shift, gte_lzcr_even_mask),
or_u(r_mag_sq_copy, r_mag_sq, 0), or_u(r_mag_sq_copy, r_mag_sq, 0),
@@ -142,25 +141,6 @@ atom_dbg_skip MipsAtomComp_Proc_(ab, {
shift_aright(r_mag_sq, r_mag_sq, 1), shift_aright(r_mag_sq, r_mag_sq, 1),
}) })
FI_ Slice_MipsCode ac_shift_aright_var_v3(AtomBuilder_R ab
, Reg rd_v0, Reg rd_v1, Reg rd_v2
, Reg rs_v0, Reg rs_v1, Reg rs_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rd_v0, rs_v0, r_shift),
shift_aright_var(rd_v1, rs_v1, r_shift),
shift_aright_var(rd_v2, rs_v2, r_shift),
})
FI_ Slice_MipsCode ac_shift_aright_var_v3_self(AtomBuilder_R ab
, Reg rds_v0, Reg rds_v1, Reg rds_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rds_v0, rds_v0, r_shift),
shift_aright_var(rds_v1, rds_v1, r_shift),
shift_aright_var(rds_v2, rds_v2, r_shift),
})
FI_ Slice_MipsCode ac_gte_general_purpose_interopolation(AtomBuilder_R ab FI_ Slice_MipsCode ac_gte_general_purpose_interopolation(AtomBuilder_R ab
, Reg to_ir0, Reg to_ir1, Reg to_ir2, Reg to_ir3 , Reg to_ir0, Reg to_ir1, Reg to_ir2, Reg to_ir3
, Reg fr_mac1, Reg fr_mac2, Reg fr_mac3 , Reg fr_mac1, Reg fr_mac2, Reg fr_mac3
@@ -170,17 +150,16 @@ MipsAtomComp_Proc_(ab, {
gte_mv_to_data_r(to_ir1, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */ gte_mv_to_data_r(to_ir1, C2_IR1), /* IR1 = src.x (preserved in r_tmp — r_mac2_scratch was clobbered to MAC2 in stage 1.5) */
gte_mv_to_data_r(to_ir2, C2_IR2), gte_mv_to_data_r(to_ir2, C2_IR2),
gte_mv_to_data_r(to_ir3, C2_IR3), /* IR3 = src.z (reloaded) */ gte_mv_to_data_r(to_ir3, C2_IR3), /* IR3 = src.z (reloaded) */
LdSlot_ nop_slot1, DmaSlot_ nop_slot1,
LdSlot_ nop_slot2, DmaSlot_ nop_slot2,
gte_cmdw_gpf, gte_cmdw_gpf,
gte_mv_from_data_r(fr_mac1, C2_MAC1), gte_mv_from_data_r(fr_mac1, C2_MAC1),
gte_mv_from_data_r(fr_mac2, C2_MAC2), gte_mv_from_data_r(fr_mac2, C2_MAC2),
gte_mv_from_data_r(fr_mac3, C2_MAC3), gte_mv_from_data_r(fr_mac3, C2_MAC3),
}) })
FI_ Slice_MipsCode gte_mv_from_data_r_mac123(AtomBuilder_R ab FI_ Slice_MipsCode ac_gte_mv_from_data_r_mac123(AtomBuilder_R ab
, Reg fr_mac1, Reg fr_mac2, Reg fr_mac3 , Reg fr_mac1, Reg fr_mac2, Reg fr_mac3)
)
MipsAtomComp_Proc_(ab, { MipsAtomComp_Proc_(ab, {
gte_mv_from_data_r(fr_mac1, C2_MAC1), gte_mv_from_data_r(fr_mac1, C2_MAC1),
gte_mv_from_data_r(fr_mac2, C2_MAC2), gte_mv_from_data_r(fr_mac2, C2_MAC2),
@@ -257,9 +236,8 @@ internal S2 const gte_normalize_sqr_tbl[192] align_(2) = {
0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804, 0x0820, 0x081c, 0x0818, 0x0814, 0x0810, 0x080c, 0x0808, 0x0804,
}; };
#define RegUse_(proc_name) (tmpl(RegUse,proc_name))
typedef Struct_(RegUse_normalize_v3s4_proc) { typedef Struct_(RegUse_normalize_v3s4_proc) {
Reg scratch; // Scratch base carrier. Reg const scratch; // Scratch base carrier.
Reg src_ptr; Reg src_ptr;
Reg dst_ptr; Reg dst_ptr;
Reg recip_est; // |v|² sum + shift-input + sqrtbl[index] Reg recip_est; // |v|² sum + shift-input + sqrtbl[index]
@@ -267,7 +245,7 @@ typedef Struct_(RegUse_normalize_v3s4_proc) {
Reg src_x; Reg src_x;
union { Reg mac1_scratch; } t3; union { Reg mac1_scratch; } t3;
union { Reg mac2_scratch; } t4; union { Reg mac2_scratch; } t4;
union { Reg shift_count, btarget, lookup_addr, src_z; } t5; union { Reg btarget, shift_count, lookup_addr, src_z; } t5;
}; };
/* ─── Full normalize (all 4 stages inline) ─── /* ─── Full normalize (all 4 stages inline) ───
* Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav). * Generic 4-stage GTE normalize (SQR → sum+LZCR → align+sqrtbl → GPF+srav).
@@ -306,7 +284,7 @@ MipsAtom_Proc_(aa, {
/* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp. /* Load src.x/y/z from r_src_ptr (caller-determined address) into r_tmp/r_recip_est/r_branch_tmp.
* r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */ * r.rt1_src_x holds src.x throughout stages 1-2 — r_mac2_scratch is clobbered to MAC2 in stage 1.5 (line below). */
mac_load_v3s4(r.src_x, r.recip_est, r.t5.lookup_addr, r.src_ptr, 0), mac_load_word_v3(r.src_x, r.recip_est, r.t5.src_z, r.src_ptr, 0),
/* Stage 1: mtc2 src → IR1/2/3, SQR fires. */ /* Stage 1: mtc2 src → IR1/2/3, SQR fires. */
LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.t5.src_z, LdSlot_ nop), LdSlot_ mac_gte_sqr_v3s4(r.src_x, r.recip_est, r.t5.src_z, LdSlot_ nop),
@@ -315,8 +293,8 @@ MipsAtom_Proc_(aa, {
mac_gte_mv_from_data_r_mac123(r.t3.mac1_scratch, r.t4.mac2_scratch, r.norm), LdSlot_ nop, mac_gte_mv_from_data_r_mac123(r.t3.mac1_scratch, r.t4.mac2_scratch, r.norm), LdSlot_ nop,
add_u_self( r.norm, r.t3.mac1_scratch), add_u_self( r.norm, r.t3.mac1_scratch),
add_u_self( r.norm, r.t4.mac2_scratch), add_u_self( r.norm, r.t4.mac2_scratch),
gte_mv_to_data_r( r.norm, C2_LZCS), LdSlot_ nop2, gte_mv_to_data_r( r.norm, C2_LZCS), DmaSlot_ nop2,
gte_mv_from_data_r(r.shift, C2_LZCR), LdSlot_ nop, gte_mv_from_data_r(r.shift, C2_LZCR), DmaSlot_ nop,
/* Stage 3: round LZCR to even, compute half-shift, align |v|² to bit 24. /* Stage 3: round LZCR to even, compute half-shift, align |v|² to bit 24.
* r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2. * r_norm holds |v|² sum; r_shift holds the LZCR count from mfc2.
@@ -350,13 +328,13 @@ MipsAtom_Proc_(aa, {
r.recip_est, r.recip_est,
r.t5.src_z, /* IR3 = src.z (reloaded) */ r.t5.src_z, /* IR3 = src.z (reloaded) */
r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.t4.mac2_scratch, r.recip_est, r.t5.src_z,
LdSlot_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here. DmaSlot_ add_si(r.dst_ptr, r.scratch, dst_offset), // pre-laoding destination to register here.
LdSlot_ nop DmaSlot_ nop
), ),
/* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */ /* sra by r_shift = (31-LZCR)/2 (saved before sqrtbl lookup) */
mac_shift_aright_var_v3_self(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.shift), mac_shift_aright_var_v3_self(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.shift),
/* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */ /* Store result.x/y/z to r_dst_ptr (caller-determined dst address). */
mac_store_v3s4(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.dst_ptr, 0), mac_store_word_v3(r.t4.mac2_scratch, r.recip_est, r.t5.src_z, r.dst_ptr, 0),
mac_yield() mac_yield()
}) })
+17 -23
View File
@@ -33,7 +33,7 @@
* gte.h — Geometry Transformation Engine (COP2) for the PS1 * gte.h — Geometry Transformation Engine (COP2) for the PS1
* ============================================================================ * ============================================================================
* *
* Hand-rolled DSL for emitting GTE/MIPS instruction words as raw `.word` constants from C. * Hand-rolled DSL for emitting GTE/MIPS instruction words from C.
* No GCC inline-assembly string syntax in the code body. * No GCC inline-assembly string syntax in the code body.
* *
* STYLE NOTES * STYLE NOTES
@@ -191,27 +191,22 @@ enum {
}; };
/* --- GTE Control Register Aliases (Pitfall 1) --- /* --- GTE Control Register Aliases (Pitfall 1) ---
* Three pairs of aliases map to the SAME C2 control-register slot on real silicon: * Three pairs of aliases map to the C2 control-register slot:
* C2[24] = gte_cr_RBK (background R) | gte_cr_OFX (screen offset X) * C2[24] = gte_cr_RBK (background R) | gte_cr_OFX (screen offset X)
* C2[25] = gte_cr_GBK (background G) | gte_cr_OFY (screen offset Y) * C2[25] = gte_cr_GBK (background G) | gte_cr_OFY (screen offset Y)
* C2[26] = gte_cr_BBK (background B) | gte_cr_H (projection plane distance H) * C2[26] = gte_cr_BBK (background B) | gte_cr_H (projection plane distance H)
* Cross-alias writes inside one atom body, or across the wave-context boundary, * Cross-alias writes inside one atom body, or across the wave-context boundary, silently clobber each other.
* silently clobber each other. The metaprogram's check_gte_cr_alias_writes * The metaprogram's check_gte_cr_alias_writes (CHECK_RULES row) warns about each pair per source.
* (CHECK_RULES row) warns about each pair per source. See * See psx-spx docs/gte_reference.md §"Control-register alias table" for the silicon rationale and the libgte outer-product convention.
* docs/gte_reference.md §"Control-register alias table" for the silicon
* rationale and the libgte outer-product convention.
*/ */
/* --- RT-matrix packed-slot convention (Pitfall 4) --- /* --- RT-matrix packed-slot convention (Pitfall 4) ---
* The silicon packs two 16-bit RT elements per 32-bit C2 slot: * The silicon packs two 16-bit RT elements per 32-bit C2 slot:
* C2[2] = (RT22 << 16) | RT13 (gte_cr_RT13 writes the low half, gte_cr_RT22 writes the high half) * C2[2] = (RT22 << 16) | RT13 (gte_cr_RT13 writes the low half, gte_cr_RT22 writes the high half)
* C2[4] = (RT33 << 16) | RT22 (gte_cr_RT22 writes the low half — clobbers prior RT22 value if RT13 was also written) * C2[4] = (RT33 << 16) | RT22 (gte_cr_RT22 writes the low half — clobbers prior RT22 value if RT13 was also written)
* OP and MVMVA read D1/D2/D3 from these packed slots. The libgte outer-product * OP and MVMVA read D1/D2/D3 from these packed slots.
* convention (see ac_apply_matrix_lv at gte.atom.c:108-122) writes C2[2] then * The libgte outer-product convention (see ac_apply_matrix_lv at gte.atom.c:108-122) writes C2[2] then C2[4] in sequence;
* C2[4] in sequence; the SECOND write's low half is RT22, not RT13. An agent * the SECOND write's low half is RT22, not RT13.
* who writes gte_cr_RT13 then gte_cr_RT22 to the SAME source GPR clobbers the
* RT13 value. See docs/gte_reference.md §"RT-matrix packed-slot convention"
* for the canonical write pattern.
*/ */
/* --- GTE Control Register Indices (for ctc2/cfc2) --- /* --- GTE Control Register Indices (for ctc2/cfc2) ---
@@ -300,8 +295,7 @@ enum { _C2_TX_SUBS_ = 0
// #define gte_mv_from_data_r(rt, rd) enc_gte_tx(cop_mf, (rt), (rd)) /* Move GTE Control Register (rd) to GPR (rt) */ // #define gte_mv_from_data_r(rt, rd) enc_gte_tx(cop_mf, (rt), (rd)) /* Move GTE Control Register (rd) to GPR (rt) */
/* GTE Data vs Control Register Transfers /* GTE Data vs Control Register Transfers
* * Each macro emits a single instruction for one of MFC2/CFC2/MTC2/CTC2.
* Each macro emits a single .word constant for one of MFC2/CFC2/MTC2/CTC2.
* *
* `rd` is the C2 register index in the file the sub-opcode names: * `rd` is the C2 register index in the file the sub-opcode names:
* gte_mv_from_data_r / gte_mv_to_data_r → C2 data register file * gte_mv_from_data_r / gte_mv_to_data_r → C2 data register file
@@ -316,14 +310,14 @@ enum { _C2_TX_SUBS_ = 0
#define gte_mv_from_ctrl_r(rt, rd) enc_gte_tx(sub_cfc2, (rt), (rd)) /* Copy From ctrl reg */ #define gte_mv_from_ctrl_r(rt, rd) enc_gte_tx(sub_cfc2, (rt), (rd)) /* Copy From ctrl reg */
#define gte_mv_to_data_r(rt, rd) enc_gte_tx(sub_mtc2, (rt), (rd)) /* Move To data reg */ #define gte_mv_to_data_r(rt, rd) enc_gte_tx(sub_mtc2, (rt), (rd)) /* Move To data reg */
#define gte_mv_to_ctrl_r(rt, rd) enc_gte_tx(sub_ctc2, (rt), (rd)) /* Copy To ctrl reg */ #define gte_mv_to_ctrl_r(rt, rd) enc_gte_tx(sub_ctc2, (rt), (rd)) /* Copy To ctrl reg */
#define DmaSlot_ // Annotate an instruction as filling a CPU <-> GTE DMA delay slot/s
/* COP2 Data Load (lwc2): `lwc2 rt, off(rs)` /* COP2 Data Load (lwc2): `lwc2 rt, off(rs)`
* Layout: [op_lwc2:6][rs:5][rt:5][imm:16] * Layout: [op_lwc2:6][rs:5][rt:5][imm:16]
* - rs: GPR base address * - rs: GPR base address
* - rt: COP2 data register index (0..31) * - rt: COP2 data register index (0..31)
* - imm: signed 16-bit offset * - imm: signed 16-bit offset
* NOTE: When `rs` is a runtime register, the encoding cannot be pre-baked * NOTE: When `rs` is a runtime register, the encoding cannot be pre-baked into a .word — use the string-style `gte_load_v0` macro below instead. */
* into a .word — use the string-style `gte_load_v0` macro below instead. */
#define enc_gte_lw(rt, base, off) enc_i(op_lwc2, (base), (rt), (off)) #define enc_gte_lw(rt, base, off) enc_i(op_lwc2, (base), (rt), (off))
/* Store Word */ /* Store Word */
#define enc_gte_sw(rt, base, off) enc_i(op_swc2, (base), (rt), (off)) #define enc_gte_sw(rt, base, off) enc_i(op_swc2, (base), (rt), (off))
@@ -332,8 +326,7 @@ enum { _C2_TX_SUBS_ = 0
* `swc2` is redundant when we're already inside the `gte_` namespace. * `swc2` is redundant when we're already inside the `gte_` namespace.
* gte_lw rt, base, off → lwc2 rt, off(base) * gte_lw rt, base, off → lwc2 rt, off(base)
* gte_sw rt, base, off → swc2 rt, off(base) * gte_sw rt, base, off → swc2 rt, off(base)
* For the typical user-facing vector-level load (xy + z as two instructions), * For the typical user-facing vector-level load (xy + z as two instructions), use the higher-level `gte_load_vN` macros below. */
* use the higher-level `gte_load_vN` macros below. */
#define gte_lw(rt, base, off) enc_gte_lw(rt, base, off) #define gte_lw(rt, base, off) enc_gte_lw(rt, base, off)
#define gte_sw(rt, base, off) enc_gte_sw(rt, base, off) #define gte_sw(rt, base, off) enc_gte_sw(rt, base, off)
@@ -408,10 +401,11 @@ enum { _C2_TX_SUBS_ = 0
#define gte_cmdw_rtpt (gte_cmd_base | enc_gte_cmd(gte_cmd_rtpt ) | gte_cmdw_psyq_compat) #define gte_cmdw_rtpt (gte_cmd_base | enc_gte_cmd(gte_cmd_rtpt ) | gte_cmdw_psyq_compat)
#define gte_cmdw_nclip (gte_cmd_base | enc_gte_cmd(gte_cmd_nclip)) #define gte_cmdw_nclip (gte_cmd_base | enc_gte_cmd(gte_cmd_nclip))
#define gte_cmdw_op (gte_cmd_base | enc_gte_cmd(gte_cmd_op )) #define gte_cmdw_op (gte_cmd_base | enc_gte_cmd(gte_cmd_op ))
#define gte_cmdw_outer_product gte_cmdw_op /* "outer product" -- NOCASH/Sdk terminology */ #define gte_cmdw_outer_product gte_cmdw_op /* "outer product" -- PSY-Q terminology */
#define gte_cmdw_wedge gte_cmdw_op /* "wedge product" -- geometric-algebra terminology. #define gte_cmdw_wedge gte_cmdw_op /* "wedge product" -- geometric-algebra terminology. */
* RGA(Lengyel): the GTE OP is a 3D signed-16-bit D x IR cross, not a generic RGA exterior product. #define gte_cmdw_cross gte_cmdw_op /* "cross product" -- geometric-algebra terminology.
* The wedge alias is the 3D complement interpretation of the same 3 scalars (MAC1..MAC3). */ * RGA(Lengyel): The GTE OP is a 3D signed-16-bit D x IR cross, not a generic RGA exterior product.
* The wedge alias is a 3D complement interpretation of the same 3 scalars (MAC1..MAC3). */
#define gte_cmdw_mvmva (gte_cmd_base | enc_gte_cmd(gte_cmd_mvmva)) #define gte_cmdw_mvmva (gte_cmd_base | enc_gte_cmd(gte_cmd_mvmva))
/* MVMVA with sf=0 (no shift, full-integer), cv=3 (no translation), v=3 (IR vector input). /* MVMVA with sf=0 (no shift, full-integer), cv=3 (no translation), v=3 (IR vector input).
+27 -2
View File
@@ -101,11 +101,12 @@ enum {
}; };
typedef U2 Reg; // Register parameter used with atom or atom component procedures typedef U2 Reg; // Register parameter used with atom or atom component procedures
#define Reg_(type) tmpl(Reg,type) // Just a way to template register allocations of C-struct types.
typedef U4 const MipsCode; // Underlying type to mips asm words. typedef U4 const MipsCode; // Underlying type to mips asm words.
typedef Slice_(MipsCode); typedef Slice_(MipsCode);
typedef U4 const MipsAtom; typedef U4 const MipsAtom; // Underlying type to a mips atom defnition
typedef Slice_(MipsAtom); typedef Slice_(MipsAtom);
// Sometimes a user will define a bundle of atoms that represent a procedure of work as: // Sometimes a user will define a bundle of atoms that represent a procedure of work as:
// MipsAtom* <identifier>[...]; // MipsAtom* <identifier>[...];
@@ -143,6 +144,8 @@ typedef Slice_(MipsAtom);
// Inline-only callers (the generated `mac_<name>` aliases) skip the `ab` arg via metaprogram filtering; escape callers (ac_<name> invoked as a function) pass a long-lived builder. // Inline-only callers (the generated `mac_<name>` aliases) skip the `ab` arg via metaprogram filtering; escape callers (ac_<name> invoked as a function) pass a long-lived builder.
#define MipsAtomComp_Proc_(ab, ...) { MipsCode atom_comp_code[] align_(4) = __VA_ARGS__; atombuilder_push(ab, slice_from_array(MipsCode, atom_comp_code)); } #define MipsAtomComp_Proc_(ab, ...) { MipsCode atom_comp_code[] align_(4) = __VA_ARGS__; atombuilder_push(ab, slice_from_array(MipsCode, atom_comp_code)); }
#define MipsAtomComp_ProcMap_(ab, base_command) atom_dbg_skip MipsAtomComp_Proc_(ab, {base_command })
/* Line-table anchor: gcc only adds a file to the .debug_line file table when the contains line-numbered content. /* Line-table anchor: gcc only adds a file to the .debug_line file table when the contains line-numbered content.
Files containing only atoms and atom components. Files containing only atoms and atom components.
Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms. Place `ATOM_FILE_LINE_MARKER();` once at file scope in any `.atom.c` that defines atoms.
@@ -242,6 +245,7 @@ atom_dbg_skip MipsAtomComp_(ac_yield_tail) {
add_ui_self(R_TapePtr, S_(MipsCode)), add_ui_self(R_TapePtr, S_(MipsCode)),
jump_reg( R_AtomJmp), nop, jump_reg( R_AtomJmp), nop,
}; };
#pragma endregion Macro Atom Components #pragma endregion Macro Atom Components
#pragma region Atom Builder #pragma region Atom Builder
@@ -371,13 +375,34 @@ FI_ void regfile_reset(RegFile_R rf) {
rf->GPR[0] = u4_lo(regfile_abi_mask); rf->GPR[0] = u4_lo(regfile_abi_mask);
rf->GPR[1] = u4_hi(regfile_abi_mask); rf->GPR[1] = u4_hi(regfile_abi_mask);
} }
FI_ void regfile_reset_mask(RegFile_R rf, U4 mask) { FI_ void regfile_reset_to_mask(RegFile_R rf, U4 mask) {
rf->GPR[0] = u4_lo(mask); rf->GPR[0] = u4_lo(mask);
rf->GPR[1] = u4_hi(mask); rf->GPR[1] = u4_hi(mask);
} }
#pragma endregion RegFileArena (Register File Allocator) #pragma endregion RegFileArena (Register File Allocator)
#pragma region Mips Atom Procs #pragma region Mips Atom Procs
/* RegUse structs are a convention to organize register allocations for a mips atom procedure.
Unlike the usual enum-based declarations, they provide a namespaced scope
and have view types via union declarations.
*/
#define RegUse_(proc_name) (tmpl(RegUse,proc_name))
typedef Struct_(RegUse_example_atom_proc) {
Reg const ro_register; // Scratch base carrier.
Reg usual_modifiable;
union { Reg view_1, view_2, view_3; } t1;
};
internal MipsAtom* example_atom_proc(AtomArena_R aa, U2 offset, RegUse_example_atom_proc r)
MipsAtom_Proc_(aa, {
add_si(r.usual_modifiable, r.ro_register, offset),
or_u(r.t1.view_1, r.ro_register, 0),
branch_lt_zero(r.t1.view_1, atom_offset(example_atom_proc, skip)), BdSlot_ nop,
li_s(r.t1.view_2, 100),
atom_label(skip)
add_si(r.t1.view_3, r.usual_modifiable, 10),
mac_yield(),
})
#pragma endregion Mips Atom Procs #pragma endregion Mips Atom Procs
-55
View File
@@ -1,55 +0,0 @@
#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_imm
FI_ Slice_MipsCode ac_load_v2s2(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_half( rs_x, r_base, offset + O_(V3_S2,x)),
load_half( rs_y, r_base, offset + O_(V3_S2,y)),
})
FI_ Slice_MipsCode ac_store_v2s2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_half(rt_x, base, offset + O_(V2_S2,x)),
store_half(rt_y, base, offset + O_(V2_S2,y)),
})
FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 rs_z, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_word( rs_x, r_base, offset + O_(V3_S4,x)),
load_word( rs_y, r_base, offset + O_(V3_S4,y)),
load_word( rs_z, r_base, offset + O_(V3_S4,z)),
})
// TODO(Ed): we could generate these mappings properly..
#define ac_load_p3s4 ac_load_v3s4
#define mac_load_p3s4 mac_load_v3s4
FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_z, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_word(rt_x, base, offset + O_(V3_S4,x)),
store_word(rt_y, base, offset + O_(V3_S4,y)),
store_word(rt_z, base, offset + O_(V3_S4,z)),
})
// TODO(Ed): we could generate these mappings properly..
#define ac_store_p3s4 ac_store_v3s4
#define mac_store_p3s4 mac_store_v3s4
FI_ Slice_MipsCode ac_sub_v3s4(AtomBuilder_R ab, U4 rds_x, U4 rds_y, U4 rds_z, U4 rt_x, U4 rt_y, U4 rt_z) atom_dbg_skip MipsAtomComp_Proc_(ab, {
sub_s(rds_x, rds_x, rt_x),
sub_s(rds_y, rds_y, rt_y),
sub_s(rds_z, rds_z, rt_z),
})
FI_ Slice_MipsCode ac_store_rects2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
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)
+63
View File
@@ -0,0 +1,63 @@
#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);
typedef Struct_(Reg_V3_S4) { Reg x, y, z; }; // Register allocation of a V3_S4
typedef Struct_(Reg_P3_S4) { Reg x, y, z; }; // Register allocation of a P3_S4
#pragma region MACs (Mips Atom Component)
FI_ Slice_MipsCode ac_load_v2s2(AtomBuilder_R ab, U4 rs_x, U4 rs_y, U4 r_base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_half( rs_x, r_base, offset + O_(V3_S2,x)),
load_half( rs_y, r_base, offset + O_(V3_S2,y)),
})
FI_ Slice_MipsCode ac_store_v2s2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_half(rt_x, base, offset + O_(V2_S2,x)),
store_half(rt_y, base, offset + O_(V2_S2,y)),
})
FI_ Slice_MipsCode ac_load_word_v3(AtomBuilder_R ab, Reg tx, Reg ty, Reg tz, Reg base, U2 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
load_word(tx, base, offset + OA_(U4,[0])),
load_word(ty, base, offset + OA_(U4,[1])),
load_word(tz, base, offset + OA_(U4,[2])),
})
FI_ Slice_MipsCode ac_load_v3s4(AtomBuilder_R ab, Reg_(V3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset))
FI_ Slice_MipsCode ac_load_p3s4(AtomBuilder_R ab, Reg_(P3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_load_word_v3(transfer.x, transfer.y, transfer.z, base, offset))
FI_ Slice_MipsCode ac_store_word_v3(AtomBuilder_R ab, U4 tx, U4 ty, U4 tz, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
store_word(tx, base, offset + OA_(U4,[0])),
store_word(ty, base, offset + OA_(U4,[1])),
store_word(tz, base, offset + OA_(U4,[2])),
})
FI_ Slice_MipsCode ac_store_v3s4(AtomBuilder_R ab, Reg_(V3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset))
FI_ Slice_MipsCode ac_store_p3s4(AtomBuilder_R ab, Reg_(P3_S4) transfer, Reg base, U2 offset) MipsAtomComp_ProcMap_(ab, mac_store_word_v3(transfer.x, transfer.y, transfer.z, base, offset))
FI_ Slice_MipsCode ac_add_si_v3s4(AtomBuilder_R ab, Reg rt_x, Reg rt_y, Reg rt_z, Reg base, U2 offset)
atom_dbg_skip MipsAtomComp_Proc_(ab, {
add_si(rt_x, base, O_(V3_S4,x)),
add_si(rt_y, base, O_(V3_S4,y)),
add_si(rt_z, base, O_(V3_S4,z)),
})
FI_ Slice_MipsCode ac_sub_v3s4(AtomBuilder_R ab, U4 rds_x, U4 rds_y, U4 rds_z, U4 rt_x, U4 rt_y, U4 rt_z) atom_dbg_skip MipsAtomComp_Proc_(ab, {
sub_s(rds_x, rds_x, rt_x),
sub_s(rds_y, rds_y, rt_y),
sub_s(rds_z, rds_z, rt_z),
})
FI_ Slice_MipsCode ac_store_rects2(AtomBuilder_R ab, U4 rt_x, U4 rt_y, U4 rt_width, U4 rt_height, U4 base, U4 offset) atom_dbg_skip MipsAtomComp_Proc_(ab, {
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)
+26
View File
@@ -16,6 +16,32 @@ atom_dbg_skip MipsAtomComp_Proc_(ab, {
or_i_self( dst, u4_lo(imm)), or_i_self( dst, u4_lo(imm)),
}) })
FI_ Slice_MipsCode ac_shift_aright_v3_self(AtomBuilder_R ab, Reg dt_x, Reg dt_y, Reg dt_z, U2 shift_amount)
MipsAtomComp_Proc_( ab, {
shift_aright(dt_x, dt_x, shift_amount),
shift_aright(dt_y, dt_y, shift_amount),
shift_aright(dt_z, dt_z, shift_amount),
})
FI_ Slice_MipsCode ac_shift_aright_var_v3(AtomBuilder_R ab
, Reg rd_v0, Reg rd_v1, Reg rd_v2
, Reg rs_v0, Reg rs_v1, Reg rs_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rd_v0, rs_v0, r_shift),
shift_aright_var(rd_v1, rs_v1, r_shift),
shift_aright_var(rd_v2, rs_v2, r_shift),
})
FI_ Slice_MipsCode ac_shift_aright_var_v3_self(AtomBuilder_R ab
, Reg rds_v0, Reg rds_v1, Reg rds_v2
, Reg r_shift)
MipsAtomComp_Proc_(ab, {
shift_aright_var(rds_v0, rds_v0, r_shift),
shift_aright_var(rds_v1, rds_v1, r_shift),
shift_aright_var(rds_v2, rds_v2, r_shift),
})
#pragma endregion MACs (Mips Atom Components) #pragma endregion MACs (Mips Atom Components)
#pragma region Baked Atoms #pragma region Baked Atoms
+1 -1
View File
@@ -26,7 +26,7 @@ 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,
}; };
// --- atom: pad_input_cam (40 words) --- // --- atom: pad_input_cam (42 words) ---
#define _atom_offset_left_x_exit_left_x 3 #define _atom_offset_left_x_exit_left_x 3
#define _atom_offset_right_x_exit_right_x 3 #define _atom_offset_right_x_exit_right_x 3
+65 -72
View File
@@ -10,7 +10,7 @@
# include "duffle/pad.h" # include "duffle/pad.h"
# include "duffle/word_count.metadata.h" # include "duffle/word_count.metadata.h"
# include "duffle/psyq.h" # include "duffle/psyq.h"
# include "duffle/math.atom.c" # include "duffle/math.atom.h"
# include "duffle/mips.atom.c" # include "duffle/mips.atom.c"
# include "duffle/gte.atom.c" # include "duffle/gte.atom.c"
# include "duffle/gp.atom.c" # include "duffle/gp.atom.c"
@@ -128,6 +128,11 @@ typedef Struct_(Binds_ResolveLookAtSub) {
ResolveLookAtScratch* scratchpad; ResolveLookAtScratch* scratchpad;
}; };
typedef Struct_(RegUse_resolve_look_at__input_and_sub_proc) {
Reg scratch;
Reg target; Reg eye; Reg up_in;
Reg t0; Reg t1; Reg t2; Reg t3; Reg t4;
};
/* Atom 0 in the bundle: input_and_sub. Stages C-side inputs into the scratchpad and computes fwd = target - eye. /* Atom 0 in the bundle: input_and_sub. Stages C-side inputs into the scratchpad and computes fwd = target - eye.
* Staging work: * Staging work:
* * Stage eye.x/y/z → scratch (for atom 6's translation column) * * Stage eye.x/y/z → scratch (for atom 6's translation column)
@@ -146,66 +151,61 @@ typedef Struct_(Binds_ResolveLookAtSub) {
* R_V0 : hardcoded (load eye.z / target.z) * R_V0 : hardcoded (load eye.z / target.z)
* Pool cost: 8 GPRs + R_T4 (carrier) + R_AT + R_V0 (hardcoded) = 11 GPRs. * Pool cost: 8 GPRs + R_T4 (carrier) + R_AT + R_V0 (hardcoded) = 11 GPRs.
*/ */
internal MipsAtom* resolve_look_at__input_and_sub_proc(AtomArena_R aa, internal MipsAtom* resolve_look_at__input_and_sub_proc(AtomArena_R aa, RegUse_resolve_look_at__input_and_sub_proc r)
// TODO(Ed): We can resolve scratch at anytime its fixed to a specific address. atom_info(atom_bind(Binds_ResolveLookAtSub)) MipsAtom_Proc_(aa, {
U4 r_scratch load_word(r.target, R_TapePtr, O_(Binds_ResolveLookAtSub,target)),
, U4 r_target_ptr,U4 r_eye_ptr, U4 r_up_in_ptr load_word(r.eye, R_TapePtr, O_(Binds_ResolveLookAtSub,eye)),
, U4 r_tmp0, U4 r_tmp1, U4 r_tmp2, U4 r_tmp3 load_word(r.up_in, R_TapePtr, O_(Binds_ResolveLookAtSub,up_in)),
) MipsAtom_Proc_(aa, { load_word(r.scratch, R_TapePtr, O_(Binds_ResolveLookAtSub,scratchpad)),
load_word(r_target_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,target)),
load_word(r_eye_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,eye)),
load_word(r_up_in_ptr, R_TapePtr, O_(Binds_ResolveLookAtSub,up_in)),
load_word(r_scratch, R_TapePtr, O_(Binds_ResolveLookAtSub,scratchpad)),
add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtSub)), add_ui_self( R_TapePtr, S_(Binds_ResolveLookAtSub)),
// Stage eye.x/y/z into the scratchpad (atom 6 reads these for the translation column).
mac_load_p3s4( r_tmp0, r_tmp1, r_tmp2, r_eye_ptr, 0),
mac_store_p3s4(r_tmp0, r_tmp1, r_tmp2, r_scratch, O_(ResolveLookAtScratch,eye)),
/* Stage up_in.x/y/z into the scratchpad. */ /* Stage up_in.x/y/z into the scratchpad. */
mac_load_p3s4( r_tmp0, r_tmp1, r_tmp2, r_up_in_ptr, 0), mac_load_word_v3( r.t0, r.t1, r.t2, r.up_in, 0),
mac_store_p3s4(r_tmp0, r_tmp1, r_tmp2, r_scratch, O_(ResolveLookAtScratch,up_in)), mac_store_word_v3(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,up_in)),
// Stage eye.x/y/z into the scratchpad (atom 6 reads these for the translation column).
mac_load_word_v3( r.t0, r.t1, r.t2, r.eye, 0),
mac_store_word_v3(r.t0, r.t1, r.t2, r.scratch, O_(ResolveLookAtScratch,eye)),
/* Compute fwd = target - eye. */ /* Compute fwd = target - eye. */
mac_load_p3s4(r_tmp0, r_tmp1, r_tmp2, r_target_ptr, 0), // mac_load_p3s4(t3, R_AT, t4, r.eye, 0),
mac_load_p3s4(r_tmp3, R_AT, R_V0, r_eye_ptr, 0), mac_load_word_v3(r.t3, R_AT, r.t4, r.target, 0),
mac_sub_v3s4( mac_sub_v3s4(
r_tmp0, r_tmp1, r_tmp2, r.t3, R_AT, r.t4,
r_tmp3, R_AT, R_V0), r.t0, r.t1, r.t2),
mac_store_v3s4(r_tmp0, r_tmp1, r_tmp2, r_scratch, O_(ResolveLookAtScratch,fwd)), mac_store_word_v3(r.t3, R_AT, r.t4, r.scratch, O_(ResolveLookAtScratch,fwd)),
mac_yield() mac_yield()
}) })
typedef Struct_(RegUse_resolve_look_at__cross_uz_up_into_right_proc) {
Reg scratch;
Reg a; Reg b; Reg c; /* load a.x/y/z; result out.x/y/z */
Reg d; /* load b.x */
Reg f; /* r_f = &right (out ptr), r_g = &uz, r_h = &up_in */
union { Reg t1, g, target0; };
union { Reg t2, h, target1; };
Reg t0;
};
/* Atom 2: cross uz × up_in → right. */ /* Atom 2: cross uz × up_in → right. */
internal MipsAtom* resolve_look_at__cross_uz_up_in_to_right_proc(AtomArena_R aa, U4 r_scratch internal MipsAtom* resolve_look_at__cross_uz_up_into_right_proc(AtomArena_R aa,
, U4 r_a, U4 r_b, U4 r_c /* load a.x/y/z; result out.x/y/z */ RegUse_resolve_look_at__cross_uz_up_into_right_proc r
, U4 r_d /* load b.x */
, U4 r_f, U4 r_g, U4 r_h /* r_f = &right (out ptr), r_g = &uz, r_h = &up_in */
) MipsAtom_Proc_(aa, { ) MipsAtom_Proc_(aa, {
/* FIX: build packed RT22+RT33 with proper sign extension. */ /* FIX: build packed RT22+RT33 with proper sign extension. */
add_si(r_g, r_scratch, O_(ResolveLookAtScratch,uz)), /* r_g = &uz */ add_si(r.g, r.scratch, O_(ResolveLookAtScratch,uz)), /* r_g = &uz */
add_si(r_h, r_scratch, O_(ResolveLookAtScratch,up_in)), /* r_h = &up_in */ add_si(r.h, r.scratch, O_(ResolveLookAtScratch,up_in)), /* r_h = &up_in */
add_si(r_f, r_scratch, O_(ResolveLookAtScratch,right)), /* r_f = &right (out) */ add_si(r.f, r.scratch, O_(ResolveLookAtScratch,right)), /* r_f = &right (out) */
nop, nop,
/* Load a (uz).x/y/z into r_a/r_b/r_c. */ /* Load a (uz).x/y/z into r_a/r_b/r_c. */
load_word(r_a, r_g, O_(V3_S4,x)), mac_load_word_v3(r.a, r.b, r.c, r.g, 0),
load_word(r_b, r_g, O_(V3_S4,y)),
load_word(r_c, r_g, O_(V3_S4,z)),
nop,
/* Load b (up_in).x/y/z into r_d + R_AT/R_V0 (R_AT/R_V0 are hardcoded scratch). */ /* Load b (up_in).x/y/z into r_d + R_AT/R_V0 (R_AT/R_V0 are hardcoded scratch). */
load_word(r_d, r_h, O_(V3_S4,x)), mac_load_word_v3(r.d, R_AT, r.t0, r.h, 0), LdSlot_ // (taken by gte_mv_from_ctrl_r)
load_word(R_AT, r_h, O_(V3_S4,y)),
load_word(R_V0, r_h, O_(V3_S4,z)),
nop,
/* Save the two RT control-register slots OP will clobber. We reuse
* r_g/r_h (scratch pointers, no longer needed) as the save targets. */
gte_mv_from_ctrl_r(r_g, gte_cr_RT11), /* r_g = C2 r0 (RT11|RT12) */
gte_mv_from_ctrl_r(r_h, gte_cr_RT22), /* r_h = C2 r4 (RT22|RT33) */
/* Save the two RT control-register slots OP will clobber. We reuse r_g/r_h (scratch pointers, no longer needed) as the save targets. */
gte_mv_from_ctrl_r(r.target0, gte_cr_RT11), /* r_g = C2 r0 (RT11|RT12) */
gte_mv_from_ctrl_r(r.target1, gte_cr_RT22), /* r_h = C2 r4 (RT22|RT33) */
/* Load uz.x/uz.y/uz.z into COP2 control registers. /* Load uz.x/uz.y/uz.z into COP2 control registers.
* OP reads D1 = RT11 from $0.low, D2 = RT22 from $2.high, D3 = RT33 from $4.high. * OP reads D1 = RT11 from $0.low, D2 = RT22 from $2.high, D3 = RT33 from $4.high.
* RT22 is in BOTH $2.high AND $4.low (shared bit position). OP reads from $2.high. * RT22 is in BOTH $2.high AND $4.low (shared bit position). OP reads from $2.high.
@@ -215,20 +215,20 @@ internal MipsAtom* resolve_look_at__cross_uz_up_in_to_right_proc(AtomArena_R aa,
* The 2nd ctc2 DOES clobber $4.low (becomes a.z.low, NOT a.y.high), but since OP * The 2nd ctc2 DOES clobber $4.low (becomes a.z.low, NOT a.y.high), but since OP
* reads RT22 from $2.high (which the 2nd ctc2 doesn't touch), D2 is still a.y.high. * reads RT22 from $2.high (which the 2nd ctc2 doesn't touch), D2 is still a.y.high.
* This is libpsyx's OuterProduct12 convention EXACTLY. */ * This is libpsyx's OuterProduct12 convention EXACTLY. */
gte_mv_to_ctrl_r(r_b, gte_cr_RT13), /* $2 = r_b = a.y. RT13=a.y.low, RT22=a.y.high. */ gte_mv_to_ctrl_r(r.b, gte_cr_RT13), /* $2 = r_b = a.y. RT13=a.y.low, RT22=a.y.high. */
gte_mv_to_ctrl_r(r_c, gte_cr_RT22), /* $4 = r_c = a.z. RT22=a.z.low, RT33=a.z.high. */ gte_mv_to_ctrl_r(r.c, gte_cr_RT22), /* $4 = r_c = a.z. RT22=a.z.low, RT33=a.z.high. */
/* Load uz into the RT diagonal. */ /* Load uz into the RT diagonal. */
gte_mv_to_ctrl_r(r_a, gte_cr_RT11), /* D1 = RT11 = uz.x (low 16 of $0, sign-extended by OP). */ gte_mv_to_ctrl_r(r.a, gte_cr_RT11), /* D1 = RT11 = uz.x (low 16 of $0, sign-extended by OP). */
nop2, /* CTC2 retirement (CPU→COP2 2-slot delay) */ DmaSlot_ nop2, /* CTC2 retirement (CPU→COP2 2-slot delay) */
/* Load up_in into IR (the second operand for OP). */ /* Load up_in into IR (the second operand for OP). */
gte_mv_to_data_r(r_d, C2_IR1), /* IR1 = up_in.x */ gte_mv_to_data_r(r.d, C2_IR1), /* IR1 = up_in.x */
gte_mv_to_data_r(R_AT, C2_IR2), /* IR2 = up_in.y */ gte_mv_to_data_r(R_AT, C2_IR2), /* IR2 = up_in.y */
gte_mv_to_data_r(R_V0, C2_IR3), /* IR3 = up_in.z */ gte_mv_to_data_r(r.t0, C2_IR3), /* IR3 = up_in.z */
nop2, /* MTC2 retirement (CPU→COP2 2-slot delay) */ DmaSlot_ nop2, /* MTC2 retirement (CPU→COP2 2-slot delay) */
gte_cmdw_outer_product, /* OP: MAC1/2/3 = uz × up_in gte_cmdw_cross, /* OP: MAC1/2/3 = uz × up_in
* MAC1 = IR3*D2 - IR2*D3 = up_in.z*uz.y.high - up_in.y*uz.z.high * MAC1 = IR3*D2 - IR2*D3 = up_in.z*uz.y.high - up_in.y*uz.z.high
* MAC2 = IR1*D3 - IR3*D1 = up_in.x*uz.z.high - up_in.z*uz.x * MAC2 = IR1*D3 - IR3*D1 = up_in.x*uz.z.high - up_in.z*uz.x
* MAC3 = IR2*D1 - IR1*D2 = up_in.y*uz.x - up_in.x*uz.y.high * MAC3 = IR2*D1 - IR1*D2 = up_in.y*uz.x - up_in.x*uz.y.high
@@ -238,25 +238,18 @@ internal MipsAtom* resolve_look_at__cross_uz_up_in_to_right_proc(AtomArena_R aa,
* MAC3 = (-fp_one)*uz.x - 0 = -fp_one*uz.x */ * MAC3 = (-fp_one)*uz.x - 0 = -fp_one*uz.x */
/* Restore the RT slots we clobbered. */ /* Restore the RT slots we clobbered. */
gte_mv_to_ctrl_r(r_g, gte_cr_RT11), /* restore C2 r0 (RT11|RT12) */ gte_mv_to_ctrl_r(r.target0, gte_cr_RT11), /* restore C2 r0 (RT11|RT12) */
gte_mv_to_ctrl_r(r_h, gte_cr_RT22), /* restore C2 r4 (RT22|RT33) */ gte_mv_to_ctrl_r(r.target1, gte_cr_RT22), /* restore C2 r4 (RT22|RT33) */
/* mfc2 MAC1/2/3 → r_a/r_b/r_c (out.x/y/z). */ /* mfc2 MAC1/2/3 → r_a/r_b/r_c (out.x/y/z). */
gte_mv_from_data_r(r_a, C2_MAC1), mac_gte_mv_from_data_r_mac123(r.a, r.b, r.c),
gte_mv_from_data_r(r_b, C2_MAC2), DmaSlot_ nop, /* MFC2 retirement */
gte_mv_from_data_r(r_c, C2_MAC3),
nop, /* MFC2 retirement */
/* Right-shift MAC by 12 to convert from GTE's S12.20 fixed-point scale back to libpsyx OuterProduct12 convention (S12.0, fp_one=4096=1<<12). /* Right-shift MAC by 12 to convert from GTE's S12.20 fixed-point scale back to libpsyx OuterProduct12 convention (S12.0, fp_one=4096=1<<12).
* Without this, MAC values (~16M for unit-vector cross products) overflow the GTE's 16-bit IR registers when atom 3 normalizes via mtc2. */ * Without this, MAC values (~16M for unit-vector cross products) overflow the GTE's 16-bit IR registers when atom 3 normalizes via mtc2. */
shift_aright(r_a, r_a, 12), mac_shift_aright_v3_self(r.a, r.b, r.c, 12),
shift_aright(r_b, r_b, 12),
shift_aright(r_c, r_c, 12),
/* Store out.x/y/z to r_f (out ptr = scratch+32). */ /* Store out.x/y/z to r_f (out ptr = scratch+32). */
store_word(r_a, r_f, O_(V3_S4,x)), mac_store_word_v3(r.a, r.b, r.c, r.f, 0),
store_word(r_b, r_f, O_(V3_S4,y)),
store_word(r_c, r_f, O_(V3_S4,z)),
mac_yield() mac_yield()
}) })
@@ -774,34 +767,34 @@ internal MipsAtom_(pad_input_cam) atom_info(atom_bind(Binds_PadInputCam)
load_word(R_T1, R_Cam, O_(Camera,pos.x)), // BD-Slot. load_word(R_T1, R_Cam, O_(Camera,pos.x)), // BD-Slot.
// D-pad Left → cam.pos.x -= 50. and_i fulfills BD-slot for load on R_Cam. // D-pad Left → cam.pos.x -= 50. and_i fulfills BD-slot for load on R_Cam.
and_i(R_T3, R_T0, Pad_Left), branch_le_zero(R_T3, atom_offset(left_x, exit_left_x)), mac_yield_load(), LdSlot_ and_i(R_T3, R_T0, Pad_Left), branch_le_zero(R_T3, atom_offset(left_x, exit_left_x)), BdSlot_ mac_yield_load(),
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.x)), add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.x)),
atom_label(exit_left_x) atom_label(exit_left_x)
/* D-pad Right → cam.pos.x += 50. Reuses R_T1 from Left. */ /* D-pad Right → cam.pos.x += 50. Reuses R_T1 from Left. */
and_i(R_T3, R_T0, Pad_Right), branch_le_zero(R_T3, atom_offset(right_x, exit_right_x)), nop, and_i(R_T3, R_T0, Pad_Right), branch_le_zero(R_T3, atom_offset(right_x, exit_right_x)), BdSlot_ nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.x)), add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.x)),
atom_label(exit_right_x) atom_label(exit_right_x)
/* D-pad Up → cam.pos.y -= 50. Load pos.y BEFORE the andi. */ /* D-pad Up → cam.pos.y -= 50. Load pos.y BEFORE the andi. */
load_word(R_T1, R_Cam, O_(Camera,pos.y)), load_word(R_T1, R_Cam, O_(Camera,pos.y)), LdSlot_
and_i(R_T3, R_T0, Pad_Up), branch_le_zero(R_T3, atom_offset(up_y, exit_up_y)), nop, and_i(R_T3, R_T0, Pad_Up), branch_le_zero(R_T3, atom_offset(up_y, exit_up_y)), BdSlot_ nop,
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.y)), add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.y)),
atom_label(exit_up_y) atom_label(exit_up_y)
/* D-pad Down → cam.pos.y += 50. Reuses R_T1 from Up. */ /* D-pad Down → cam.pos.y += 50. Reuses R_T1 from Up. */
and_i(R_T3, R_T0, Pad_Down), branch_le_zero(R_T3, atom_offset(down_y, exit_down_y)), nop, and_i(R_T3, R_T0, Pad_Down), branch_le_zero(R_T3, atom_offset(down_y, exit_down_y)), BdSlot_ nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.y)), add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.y)),
atom_label(exit_down_y) atom_label(exit_down_y)
/* D-pad Cross → cam.pos.z -= 50. Load pos.z BEFORE the andi. */ /* D-pad Cross → cam.pos.z -= 50. Load pos.z BEFORE the andi. */
load_word(R_T1, R_Cam, O_(Camera,pos.z)), load_word(R_T1, R_Cam, O_(Camera,pos.z)), LdSlot_
and_i(R_T3, R_T0, Pad_Cross), branch_le_zero(R_T3, atom_offset(cross_z, exit_cross_z)), nop, and_i(R_T3, R_T0, Pad_Cross), branch_le_zero(R_T3, atom_offset(cross_z, exit_cross_z)), BdSlot_ nop,
add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.z)), add_si(R_T1, R_T1, -50), store_word(R_T1, R_Cam, O_(Camera,pos.z)),
atom_label(exit_cross_z) atom_label(exit_cross_z)
/* D-pad Circle → cam.pos.z += 50. Reuses R_T1 from Cross. */ /* D-pad Circle → cam.pos.z += 50. Reuses R_T1 from Cross. */
and_i(R_T3, R_T0, Pad_Circle), branch_le_zero(R_T3, atom_offset(circle_z, exit_circle_z)), nop, and_i(R_T3, R_T0, Pad_Circle), branch_le_zero(R_T3, atom_offset(circle_z, exit_circle_z)), BdSlot_ nop,
add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.z)), add_si(R_T1, R_T1, 50), store_word(R_T1, R_Cam, O_(Camera,pos.z)),
atom_label(exit_circle_z) atom_label(exit_circle_z)
+38 -30
View File
@@ -32,7 +32,7 @@
#pragma region Duffle TUs #pragma region Duffle TUs
#include "duffle/pad.c" #include "duffle/pad.c"
#include "duffle/math.atom.c" #include "duffle/math.atom.h"
#include "duffle/mips.atom.c" #include "duffle/mips.atom.c"
#include "duffle/gte.atom.c" #include "duffle/gte.atom.c"
#include "duffle/gp.atom.c" #include "duffle/gp.atom.c"
@@ -149,18 +149,22 @@ internal void resolve_look_at_init(void) {
U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch); U4 pin_mask = regfile_abi_mask | (1 << R_ResolveScratch);
RegFile rf = regfile(pin_mask); RegFile rf = regfile(pin_mask);
#define ralloc() regfile_alloc(& rf)
U4 r_target_ptr = regfile_alloc(& rf);
U4 r_eye_ptr = regfile_alloc(& rf);
U4 r_up_in_ptr = regfile_alloc(& rf);
U4 r_tmp0 = regfile_alloc(& rf);
U4 r_tmp1 = regfile_alloc(& rf);
U4 r_tmp2 = regfile_alloc(& rf);
U4 r_tmp3 = regfile_alloc(& rf);
smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab, smem.resolve_look_at_atom_addrs[0] = resolve_look_at__input_and_sub_proc(& ab,
R_ResolveScratch, RegUse_(resolve_look_at__input_and_sub_proc) {
r_target_ptr, r_eye_ptr, r_up_in_ptr, .scratch = R_ResolveScratch,
r_tmp0, r_tmp1, r_tmp2, r_tmp3); .target = ralloc(),
.eye = ralloc(),
.up_in = ralloc(),
.t0 = ralloc(),
.t1 = ralloc(),
.t2 = ralloc(),
.t3 = ralloc(),
.t4 = ralloc(),
}
);
regfile_reset_to_mask(& rf, pin_mask);
/* === ATOM 1: normalize fwd→uz === */ /* === ATOM 1: normalize fwd→uz === */
U2 src_offset = O_(ResolveLookAtScratch, fwd); U2 src_offset = O_(ResolveLookAtScratch, fwd);
@@ -168,28 +172,31 @@ internal void resolve_look_at_init(void) {
smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab, smem.resolve_look_at_atom_addrs[1] = normalize_v3s4_proc(& ab,
src_offset, dst_offset, RegUse_(normalize_v3s4_proc){ src_offset, dst_offset, RegUse_(normalize_v3s4_proc){
.scratch = R_ResolveScratch, .scratch = R_ResolveScratch,
.src_ptr = R_T0, .src_ptr = ralloc(),
.dst_ptr = R_T1, .dst_ptr = ralloc(),
.recip_est = R_T6, .recip_est = ralloc(),
.norm = R_T7, .norm = ralloc(),
.shift = R_V0, .shift = ralloc(),
.src_x = R_T2, .src_x = ralloc(),
.t3 = R_T3, .t3 = ralloc(),
.t4 = R_T5, .t4 = ralloc(),
.t5 = R_V1, .t5 = ralloc(),
}); });
regfile_reset_to_mask(& rf, pin_mask);
/* === ATOM 2: cross uz×up_in→right === */ /* === ATOM 2: cross uz×up_in→right === */
U4 r_a_2 = R_T0; smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_into_right_proc(& ab,
U4 r_b_2 = R_T1; RegUse_(resolve_look_at__cross_uz_up_into_right_proc) {
U4 r_c_2 = R_T2; .scratch = R_ResolveScratch,
U4 r_d_2 = R_T3; .a = ralloc(),
U4 r_f_2 = R_T5; /* out ptr (HARDCODED in body: scratch+32) */ .b = ralloc(),
U4 r_g_2 = R_T6; /* a ptr = scratch+16 */ .c = ralloc(),
U4 r_h_2 = R_T7; /* b ptr = scratch+128 */ .d = ralloc(),
smem.resolve_look_at_atom_addrs[2] = resolve_look_at__cross_uz_up_in_to_right_proc(& ab, .f = ralloc(),
R_ResolveScratch, .t1 = ralloc(),
r_a_2, r_b_2, r_c_2, r_d_2, r_f_2, r_g_2, r_h_2); .t2 = ralloc(),
.t0 = ralloc(),
});
/* === ATOM 3: normalize right→ux === */ /* === ATOM 3: normalize right→ux === */
src_offset = O_(ResolveLookAtScratch, right); src_offset = O_(ResolveLookAtScratch, right);
@@ -283,6 +290,7 @@ internal void resolve_look_at_init(void) {
/* Sanity check: arena didn't overflow. */ /* Sanity check: arena didn't overflow. */
assert(ab.used <= ResolveLookAtArena_Size); assert(ab.used <= ResolveLookAtArena_Size);
#undef ralloc
} }
/* Emit the resolve_look_at bundle into the tape. Called once per frame from update(). /* Emit the resolve_look_at bundle into the tape. Called once per frame from update().
+96 -7
View File
@@ -2236,6 +2236,43 @@ local function _project_emission_inner(root_body_entry, ctx_table)
local invocation_stack = {} -- stack of currently-open invocation records local invocation_stack = {} -- stack of currently-open invocation records
local next_inv_id = 0 local next_inv_id = 0
local reg_use_schema = ctx_table.reg_use_schema
local reg_use_param = ctx_table.reg_use_param
local atom_name = ctx_table.atom_name
local slot_readonly = {}
if reg_use_schema then
for _, slot in ipairs(reg_use_schema.slots or {}) do
slot_readonly[slot.name] = slot.readonly == true
end
end
local function apply_sub(sub_map, operand)
if not (sub_map and type(operand) == "string") then return operand end
if sub_map[operand] then return sub_map[operand] end
local dot = operand:find(".", 1, true)
if dot then
local head = operand:sub(1, dot - 1)
local mapped = sub_map[head]
if type(mapped) == "string" then
return mapped .. operand:sub(dot)
end
end
return operand
end
local function resolve_gpr_key(operand)
if type(operand) ~= "string" then return nil end
if operand:sub(1, 2) == "R_" then return operand end
if not (reg_use_schema and reg_use_param) then return nil end
local prefix = reg_use_param .. "."
if operand:sub(1, #prefix) ~= prefix then return nil end
local member_path = operand:sub(#prefix + 1)
local slot = reg_use_schema.alias_to_slot[member_path]
if not slot then return nil, member_path end
return "reguse:" .. atom_name .. ":" .. slot, nil, slot
end
local function open_invocation_ids_snapshot() local function open_invocation_ids_snapshot()
local ids = {} local ids = {}
for _, inv in ipairs(invocation_stack) do for _, inv in ipairs(invocation_stack) do
@@ -2246,7 +2283,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
local function emit_word(encoder, args, line, word_call_text, local function emit_word(encoder, args, line, word_call_text,
def_source_now, def_line_now, def_source_now, def_line_now,
immediate_call_text, root_call_text_w) immediate_call_text, root_call_text_w, sub_map)
local inv_ids = open_invocation_ids_snapshot() local inv_ids = open_invocation_ids_snapshot()
local outermost = inv_ids[1] or 0 local outermost = inv_ids[1] or 0
-- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text. -- For words emitted at the root atom body, `immediate_call_text` is nil and the walker's `word_call_text` (the word's own token, e.g. "nop") becomes the effective call_text.
@@ -2254,6 +2291,42 @@ local function _project_emission_inner(root_body_entry, ctx_table)
-- The call that triggered the body expansion we're currently walking. -- The call that triggered the body expansion we're currently walking.
local eff_call_text = immediate_call_text or word_call_text local eff_call_text = immediate_call_text or word_call_text
local eff_root_call_text = root_call_text_w local eff_root_call_text = root_call_text_w
local gpr_keys = nil
if reg_use_schema or sub_map then
gpr_keys = {}
for pos, arg in ipairs(args or {}) do
local effective = apply_sub(sub_map, arg)
local key, unresolved, slot = resolve_gpr_key(effective)
gpr_keys[pos] = key
if unresolved then
errors[#errors + 1] = {
kind = "reguse_unresolved",
line = line,
msg = string.format("RegUse operand %q does not resolve in schema %q",
effective, (reg_use_schema and reg_use_schema.name) or "?"),
}
end
if key and slot and slot_readonly[slot] then
local effects = M.INSTRUCTION_GPR_EFFECTS or {}
local row = effects[encoder]
if row and row.writes then
for _, wpos in ipairs(row.writes) do
if wpos == pos then
errors[#errors + 1] = {
kind = "reguse_const_write",
line = line,
msg = string.format("RegUse slot %q is Reg const; %s writes it",
slot, encoder),
}
end
end
end
end
end
end
if not reg_use_schema then
gpr_keys = nil
end
items[#items + 1] = { items[#items + 1] = {
kind = "word", kind = "word",
encoder = encoder, encoder = encoder,
@@ -2265,6 +2338,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
root_call_text = eff_root_call_text, root_call_text = eff_root_call_text,
invocation_ids = inv_ids, invocation_ids = inv_ids,
outermost_invocation_id = outermost, outermost_invocation_id = outermost,
gpr_keys = gpr_keys,
} }
word_events[#word_events + 1] = { word_events[#word_events + 1] = {
i = word_idx, i = word_idx,
@@ -2277,6 +2351,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
invocation_ids = inv_ids, invocation_ids = inv_ids,
outermost_invocation_id = outermost, outermost_invocation_id = outermost,
word_count = 1, word_count = 1,
gpr_keys = gpr_keys,
} }
word_idx = word_idx + 1 word_idx = word_idx + 1
end end
@@ -2520,6 +2595,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
local line_of = body_entry.line_of or M.LineIndex("") local line_of = body_entry.line_of or M.LineIndex("")
local def_source = body_entry.source or "" local def_source = body_entry.source or ""
local def_line = body_entry.declaration or 0 local def_line = body_entry.declaration or 0
local sub_map = body_entry.sub_map
-- Per-token dispatch: each matched branch returns; only the fall-through -- Per-token dispatch: each matched branch returns; only the fall-through
-- "opaque word" emit handles direct encoders + mac_X-without-component. -- "opaque word" emit handles direct encoders + mac_X-without-component.
local function process_token(bt) local function process_token(bt)
@@ -2575,12 +2651,22 @@ local function _project_emission_inner(root_body_entry, ctx_table)
-- Propagate trackers into the recursive walk: -- Propagate trackers into the recursive walk:
-- immediate_call_text = this call's tok (the IMMEDIATE outer call for words emitted in this body) -- immediate_call_text = this call's tok (the IMMEDIATE outer call for words emitted in this body)
-- root_call_text = the OUTERMOST call (immutable across the recursion) -- root_call_text = the OUTERMOST call (immutable across the recursion)
local formal_names = ctx_table.component_index[bare]
and ctx_table.component_index[bare].arg_names
local child_map = nil
if formal_names then
child_map = {}
for i, fname in ipairs(formal_names) do
child_map[fname] = apply_sub(sub_map, args[i])
end
end
walk_body_entry({ walk_body_entry({
body_tokens = comp.body_tokens or {}, body_tokens = comp.body_tokens or {},
body_off = comp.body_off or 0, body_off = comp.body_off or 0,
line_of = comp.line_of, line_of = comp.line_of,
source = comp.source, source = comp.source,
declaration = comp.declaration, declaration = comp.declaration,
sub_map = child_map,
}, },
inv.id, inv.id,
invocation_root_call_text, invocation_root_call_text,
@@ -2618,7 +2704,7 @@ local function _project_emission_inner(root_body_entry, ctx_table)
local n = resolve_count(ident, tok_line) local n = resolve_count(ident, tok_line)
local out_ident = (ident == "nop2") and "nop" or ident local out_ident = (ident == "nop2") and "nop" or ident
for _ = 1, n do for _ = 1, n do
emit_word(out_ident, args, tok_line, tok, def_source, def_line, walk_immediate_call_text, walk_root_call_text) emit_word(out_ident, args, tok_line, tok, def_source, def_line, walk_immediate_call_text, walk_root_call_text, sub_map)
end end
end end
@@ -2683,7 +2769,7 @@ end
--- @param components table -- bare-name → component definition (corpus.components); REQUIRED — consumed at the invocation-construction site to stamp --- @param components table -- bare-name → component definition (corpus.components); REQUIRED — consumed at the invocation-construction site to stamp
--- `invocation.debug_skip`. A missing or non-table `components` raises a fail-loud error rather than silently falling back. --- `invocation.debug_skip`. A missing or non-table `components` raises a fail-loud error rather than silently falling back.
--- @return EmissionProjection --- @return EmissionProjection
function M.project_emission(body_text, component_index, word_counts, components) function M.project_emission(body_text, component_index, word_counts, components, reg_use_ctx)
-- The recursive walk delegates to `_project_emission_inner` so component bodies (which arrive as -- The recursive walk delegates to `_project_emission_inner` so component bodies (which arrive as
-- `{body_tokens, body_off, line_of, source, declaration}` records from `corpus.component_body_index`) -- `{body_tokens, body_off, line_of, source, declaration}` records from `corpus.component_body_index`)
-- re-enter the same walker with the same shared output state. -- re-enter the same walker with the same shared output state.
@@ -2726,6 +2812,10 @@ function M.project_emission(body_text, component_index, word_counts, components)
component_index = component_index or {}, component_index = component_index or {},
word_counts = word_counts or {}, word_counts = word_counts or {},
components = components, components = components,
reg_use_schema = reg_use_ctx and reg_use_ctx.reg_use_schema,
reg_use_param = reg_use_ctx and reg_use_ctx.reg_use_param,
atom_name = reg_use_ctx and reg_use_ctx.atom_name,
schema_name = reg_use_ctx and reg_use_ctx.schema_name,
}) })
end end
@@ -2860,13 +2950,12 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len)
if source:sub(next_pos, next_pos) == "(" then if source:sub(next_pos, next_pos) == "(" then
local inner = M.read_parens(source, next_pos) local inner = M.read_parens(source, next_pos)
if inner then if inner then
-- strip the _proc suffix to get the atom name
local proc_suffix = "_proc" local proc_suffix = "_proc"
local atom_name = ident
if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then if #ident > #proc_suffix and ident:sub(-#proc_suffix) == proc_suffix then
return ident:sub(1, #ident - #proc_suffix), inner atom_name = ident:sub(1, #ident - #proc_suffix)
end end
-- no _proc suffix — return as-is return atom_name, inner, ident
return ident, inner
end end
end end
-- ident not followed by "(" — it's a qualifier; skip it -- ident not followed by "(" — it's a qualifier; skip it
+13 -1
View File
@@ -83,6 +83,7 @@ local function canonical_word_entries(atom)
line = event.call_line or item.line or 0, line = event.call_line or item.line or 0,
text = event.call_text or item.call_text or "", text = event.call_text or item.call_text or "",
body_line = event.body_line or item.body_line or item.line or 0, body_line = event.body_line or item.body_line or item.line or 0,
gpr_keys = event.gpr_keys,
invocation = (event.outermost_invocation_id invocation = (event.outermost_invocation_id
and paths.invocations and paths.invocations
and paths.invocations[event.outermost_invocation_id]) or nil, and paths.invocations[event.outermost_invocation_id]) or nil,
@@ -491,8 +492,19 @@ function M.render_atom_source_map(atom)
local lines = {} local lines = {}
lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total) lines[#lines + 1] = string.format("ATOM %s %d", (atom.raw_name or atom.name), total)
for _, entry in ipairs(entries) do for _, entry in ipairs(entries) do
lines[#lines + 1] = string.format("WORD %d LINE %d TEXT %s", local word_line = string.format("WORD %d LINE %d TEXT %s",
entry.pos, entry.line, entry.text) entry.pos, entry.line, entry.text)
local keys = {}
for pos = 1, 16 do
local k = entry.gpr_keys and entry.gpr_keys[pos]
if type(k) == "string" and k:sub(1, 7) == "reguse:" then
keys[#keys + 1] = k
end
end
if #keys > 0 then
word_line = word_line .. " KEYS " .. table.concat(keys, ",")
end
lines[#lines + 1] = word_line
end end
lines[#lines + 1] = "ENDATOM" lines[#lines + 1] = "ENDATOM"
return table.concat(lines, "\n") .. "\n" return table.concat(lines, "\n") .. "\n"
+13 -12
View File
@@ -159,6 +159,14 @@ local function extract_arg_names(args_str)
return names return names
end end
local function formal_arg_names(args_str)
local names = extract_arg_names(args_str)
if not names then return nil end
if names[1] == "ab" then table.remove(names, 1) end
if #names == 0 then return nil end
return names
end
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
-- Component projection (read from pre-scanned SourceScan) -- Component projection (read from pre-scanned SourceScan)
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
@@ -197,6 +205,7 @@ local function project_components(source, scan)
body_off = a.body_off, body_off = a.body_off,
body_tokens = a.body_tokens, body_tokens = a.body_tokens,
args = args, args = args,
arg_names = formal_arg_names(args),
comment = comment, comment = comment,
kind = a.kind, -- "comp_bare" | "comp_proc"; provenance emitter reads this. kind = a.kind, -- "comp_bare" | "comp_proc"; provenance emitter reads this.
debug_skip = a.debug_skip == true, debug_skip = a.debug_skip == true,
@@ -466,18 +475,9 @@ end
--- @param args_str string|nil --- @param args_str string|nil
--- @return string --- @return string
local function signature_from_args(args_str) local function signature_from_args(args_str)
local arg_names = extract_arg_names(args_str) local names = formal_arg_names(args_str)
if arg_names and #arg_names > 0 then if names then
-- Drop the leading `ab` (atom-builder) first arg if present. return table.concat(names, ", ")
-- Convention: `MipsAtomComp_Proc_` components always declare `ab` as the first function-arg
-- (type `MipsAtomBuilder_R`), mirroring the macro signature in `lottes_tape.h`.
if arg_names[1] == "ab" then
table.remove(arg_names, 1)
end
if #arg_names > 0 then
return table.concat(arg_names, ", ")
end
return "..." -- `ab` was the only arg; fall through to variadic
end end
return "..." return "..."
end end
@@ -710,6 +710,7 @@ local function update_canonical_component_body_index(corpus, src, components, sc
source = src.path, source = src.path,
declaration = c.line, declaration = c.line,
kind = c.kind, kind = c.kind,
arg_names = c.arg_names,
} }
end end
end end
+21 -1
View File
@@ -155,8 +155,28 @@ local function project_atom(atom_record, src, corpus)
local body = atom_record.body or "" local body = atom_record.body or ""
local wc = corpus.word_counts or {} local wc = corpus.word_counts or {}
local cbi = corpus.component_body_index or {} local cbi = corpus.component_body_index or {}
local schema = nil
if atom_record.reg_use_schema_name then
schema = corpus.reg_use_schemas and corpus.reg_use_schemas[atom_record.reg_use_schema_name]
end
-- That construction site stamps `invocation.debug_skip` while appending each record to `proj.invocations`. -- That construction site stamps `invocation.debug_skip` while appending each record to `proj.invocations`.
local proj = duffle.project_emission(body, cbi, wc, corpus.components) local proj = duffle.project_emission(body, cbi, wc, corpus.components, {
reg_use_schema = schema,
reg_use_param = atom_record.reg_use_param_name,
atom_name = atom_record.name,
schema_name = atom_record.reg_use_schema_name,
})
if atom_record.reg_use_schema_name and not schema then
proj.errors[#proj.errors + 1] = {
kind = "reguse_missing_schema",
msg = string.format("RegUse schema %q is missing", atom_record.reg_use_schema_name),
}
end
for _, err in ipairs(corpus.reg_use_errors or {}) do
if err.schema_name == atom_record.reg_use_schema_name then
proj.errors[#proj.errors + 1] = err
end
end
local paths = { local paths = {
tokens = atom_record.body_tokens or {}, tokens = atom_record.body_tokens or {},
line_in_body = duffle.build_body_line_index(body), line_in_body = duffle.build_body_line_index(body),
+383 -227
View File
@@ -4,8 +4,8 @@
--- - `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 canonical `corpus.sources_by_dir` projection groups sources by directory.
--- This pass iterates the dir projection directly and re-validates each source via `annotation.validate()` to get the detailed per-source results. --- This pass builds one ModuleView per directory and walks SECTION_RENDERERS.
-- ════════════════════════════════════════════════════════════════════════════ -- ════════════════════════════════════════════════════════════════════════════
-- Module-scope requires + package.path setup -- Module-scope requires + package.path setup
@@ -20,11 +20,6 @@
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")
-- Load the annotation pass so we can re-validate each source against the canonical corpus projection.
-- The annotation pass exposes `M.validate`, which returns the per-source AnnotationResult (atoms / annots / macros / binds / errors / warnings)
-- that the report pass renders into the per-module `<dir_basename>.annotations.txt` output.
local annotation = dofile(_bootstrap_dir .. "annotation.lua")
-- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `<module>.atoms.md` without re-walking source tokens). -- Load atoms_source_map for the `render_source_map` / `render_provenance` module functions (used by `render_module_atoms_md` to produce `<module>.atoms.md` without re-walking source tokens).
-- The pass itself emits no per-source files anymore; we only consume the two pure renderers here. -- The pass itself emits no per-source files anymore; we only consume the two pure renderers here.
-- Defined BEFORE the renderer functions below so their upvalues resolve to this local (not the global `atoms_source_map`, which is nil). -- Defined BEFORE the renderer functions below so their upvalues resolve to this local (not the global `atoms_source_map`, which is nil).
@@ -225,7 +220,7 @@ local function render_module_atoms_md(dir, dir_sources, wc)
for _, atom in ipairs(atoms_list) do for _, atom in ipairs(atoms_list) do
lines[#lines + 1] = string.format( lines[#lines + 1] = string.format(
"### atom: %s (line %d, %d words)", "### atom: %s (line %d, %d words)",
atom.name, atom.line or 0, #(atom.paths.items or {})) atom.name, atom.line or 0, #((atom.paths or {}).word_events or {}))
lines[#lines + 1] = "" lines[#lines + 1] = ""
lines[#lines + 1] = "**Sourcemap** — per-word call site:" lines[#lines + 1] = "**Sourcemap** — per-word call site:"
lines[#lines + 1] = "```" lines[#lines + 1] = "```"
@@ -246,17 +241,349 @@ local function render_module_atoms_md(dir, dir_sources, wc)
return table.concat(lines, "\n") .. "\n" return table.concat(lines, "\n") .. "\n"
end end
--- Render the consolidated per-module markdown (`build/<module>.atom_meta_report.md`). local function decl_words(atom)
--- Aggregates annotation + static-analysis content across all sources in `dir`. local p = atom.paths or {}
--- Annotations come from re-running `annotation.validate()` per source (the existing pattern); return #(p.word_events or {})
--- static-analysis comes from `corpus.static_analysis_results[dir_basename]` (populated by `static_analysis.lua` — no second corpus_pipe_ctx build). end
--- @param dir string
--- @param dir_sources SourceFile[] local function count_kinds(decls)
--- @param annot_results AnnotationResult[] local n = { atom = 0, atom_proc = 0, comp_bare = 0, comp_proc = 0 }
--- @param sa_results table -- corpus.static_analysis_results[dir_basename] for _, a in ipairs(decls or {}) do
--- @return string if n[a.kind] ~= nil then n[a.kind] = n[a.kind] + 1 end
local function render_module_meta_report(dir, dir_sources, annot_results, sa_results) end
return n
end
local function slot_suffix(key)
if type(key) ~= "string" or key:sub(1, 7) ~= "reguse:" then return nil end
return key:match("([^:]+)$")
end
local function build_module_view(dir, dir_sources, corpus)
local decls = {}
for _, src in ipairs(dir_sources or {}) do
for _, a in ipairs((src.scan and src.scan.atoms) or {}) do
if not a.source_path then a.source_path = src.path end
decls[#decls + 1] = a
end
end
local dir_basename = source_basename(dir) local dir_basename = source_basename(dir)
local sa = (corpus.static_analysis_results or {})[dir_basename] or {}
local schemas = {}
for name, schema in pairs(corpus.reg_use_schemas or {}) do
for _, a in ipairs(decls) do
if a.reg_use_schema_name == name then
schemas[#schemas + 1] = schema
break
end
end
end
return {
dir = dir,
sources = dir_sources or {},
decls = decls,
schemas = schemas,
findings = sa.findings or {},
sa = sa,
corpus = corpus,
}
end
local function render_section_declarations(add, view)
if #view.decls == 0 then add("_(none)_"); add(""); return end
add("| kind | name | source | line | words | min | max | branches | paths |")
add("|------|------|--------|------|-------|-----|-----|----------|-------|")
for _, a in ipairs(view.decls) do
local p = a.paths or {}
add(string.format("| %s | %s | %s | %d | %d | %s | %s | %s | %s |",
a.kind or "?",
a.name or "?",
source_basename(a.source_path or ""),
a.line or 0,
decl_words(a),
tostring(p.cycles_min or ""),
tostring(p.cycles_max or ""),
tostring(p.branches or ""),
tostring(p.paths or "")))
end
add("")
end
local function render_section_components(add, view)
local rows = {}
local index = (view.corpus and view.corpus.component_body_index) or {}
for _, a in ipairs(view.decls) do
if a.kind == "comp_bare" or a.kind == "comp_proc" then
local idx = index[a.name] or {}
local args = idx.arg_names or {}
rows[#rows + 1] = {
name = a.name,
kind = a.kind,
args = table.concat(args, ", "),
words = decl_words(a),
map = a.map_command or "",
}
end
end
if #rows == 0 then add("_(none)_"); add(""); return end
add("| name | kind | arg_names | words | map |")
add("|------|------|-----------|-------|-----|")
for _, r in ipairs(rows) do
add(string.format("| %s | %s | %s | %d | %s |",
r.name, r.kind, r.args ~= "" and r.args or "", r.words, r.map))
end
add("")
end
local function render_section_reguse(add, view)
local wrote = false
for _, schema in ipairs(view.schemas or {}) do
wrote = true
add(string.format("### %s", schema.name or "?"))
for _, slot in ipairs(schema.slots or {}) do
local aliases = table.concat(slot.aliases or { slot.name }, ", ")
local ro = slot.readonly and " readonly" or ""
add(string.format("- slot `%s` aliases %s%s", slot.name, aliases, ro))
end
for _, a in ipairs(view.decls) do
if a.reg_use_schema_name == schema.name then
add(string.format("- bound `%s` param `%s`", a.name, a.reg_use_param_name or "?"))
end
end
add("")
end
local errors = (view.corpus and view.corpus.reg_use_errors) or {}
if #errors > 0 then
wrote = true
add("### parse errors")
for _, err in ipairs(errors) do
add(string.format("- `%s` %s", err.kind or "?", err.schema_name or ""))
end
add("")
end
if not wrote then add("_(none)_"); add("") end
end
local function render_section_annotations(add, view)
local rows = {}
for _, src in ipairs(view.sources) do
for _, info in ipairs((src.scan and src.scan.atom_infos) or {}) do
rows[#rows + 1] = {
source = source_basename(src.path),
line = info.info_line or 0,
name = info.atom_name or "?",
binds = info.binds or "",
reads = (#(info.reads or {}) > 0 and table.concat(info.reads, ",")) or "",
writes = (#(info.writes or {}) > 0 and table.concat(info.writes, ",")) or "",
phase = info.phase or "",
}
end
end
if #rows == 0 then add("_(none)_"); add(""); return end
add("| source | line | name | binds | reads | writes | phase |")
add("|--------|------|------|-------|-------|--------|-------|")
for _, r in ipairs(rows) do
add(string.format("| %s | %d | %s | %s | %s | %s | %s |",
r.source, r.line, r.name, r.binds, r.reads, r.writes, r.phase))
end
add("")
end
local function render_section_binds(add, view)
local wrote = false
for _, src in ipairs(view.sources) do
for _, b in ipairs((src.scan and src.scan.binds) or {}) do
wrote = true
local line = b.line or 0
if src.scan.line_of and type(b.line) == "number" then
line = src.scan.line_of(b.line) or b.line
end
add(string.format("### %s (%s:%s, %s bytes)",
b.name, source_basename(src.path), tostring(line), tostring(b.bytes or "")))
for _, f in ipairs(b.fields or {}) do
add(string.format("- `+%s %s`", tostring(f.offset or "?"), f.name or "?"))
end
add("")
end
end
if not wrote then add("_(none)_"); add("") end
end
local function render_section_phases(add, view)
local corpus = view.corpus or {}
local wrote = false
for phase, entry in pairs(corpus.atom_phases or {}) do
wrote = true
add(string.format("- phase `%s`: %s", phase, table.concat(entry.atoms or {}, ", ")))
end
for name, entry in pairs(corpus.atom_views or {}) do
wrote = true
add(string.format("- view `%s` binds `%s`", name, entry.binds_name or ""))
end
for name, entry in pairs(corpus.atom_ctxs or {}) do
wrote = true
add(string.format("- ctx `%s` rbind `%s`", name, entry.rbind_atom or ""))
end
if not wrote then add("_(none)_") end
add("")
end
local function render_section_aliases(add, view)
local reg = (view.corpus and view.corpus.register_alias_registry) or {}
local names = {}
for name in pairs(reg) do names[#names + 1] = name end
table.sort(names)
if #names == 0 then add("_(none)_"); add(""); return end
add("| alias | type |")
add("|-------|------|")
for _, name in ipairs(names) do
local e = reg[name]
add(string.format("| %s | %s |", name, (e and e.default_type) or ""))
end
add("")
end
local function render_section_autoreg(add, view)
local corpus = view.corpus or {}
local wrote = false
local function dump(label, table_map)
local scopes = {}
for scope in pairs(table_map or {}) do scopes[#scopes + 1] = scope end
table.sort(scopes)
for _, scope in ipairs(scopes) do
wrote = true
local syms = {}
for sym, gpr in pairs(table_map[scope] or {}) do
if type(gpr) == "string" and gpr ~= sym then
syms[#syms + 1] = string.format("%s → %s", sym, gpr)
else
syms[#syms + 1] = tostring(sym)
end
end
table.sort(syms)
add(string.format("- %s `%s`: %s", label, scope, table.concat(syms, ", ")))
end
end
dump("atom", corpus.atom_auto_regs)
dump("phase", corpus.phase_auto_regs)
if not wrote then add("_(none)_") end
add("")
end
local function render_section_collisions(add, view)
local cols = (view.corpus and view.corpus.collisions) or {}
if #cols == 0 then add("_(none)_"); add(""); return end
for _, c in ipairs(cols) do
local first = c.first_site or {}
local other = c.conflicting_site or {}
add(string.format("- `%s` `%s` first %s:%s conflict %s:%s",
c.kind or "?", c.name or "?",
tostring(first.path or "?"), tostring(first.line or "?"),
tostring(other.path or "?"), tostring(other.line or "?")))
end
add("")
end
local function render_section_findings(add, view)
local by_atom = {}
for _, f in ipairs(view.findings or {}) do
local key = f.atom or "?"
by_atom[key] = by_atom[key] or {}
by_atom[key][#by_atom[key] + 1] = f
end
if next(by_atom) == nil then add("_(none)_"); add(""); return end
local seen = {}
local function emit(name, fs)
add("### " .. name)
for _, f in ipairs(fs) do
local msg = f.msg or ""
local slot = slot_suffix(f.gpr_key or f.producer_destination)
if slot and not msg:find("(slot ", 1, true) then
msg = msg .. " (slot " .. slot .. ")"
end
add(string.format("- `[%s/%s] %s`", f.kind or "info", f.check or "?", msg))
end
add("")
end
for _, a in ipairs(view.decls) do
if by_atom[a.name] then
seen[a.name] = true
emit(a.name, by_atom[a.name])
end
end
local leftovers = {}
for name in pairs(by_atom) do
if not seen[name] then leftovers[#leftovers + 1] = name end
end
table.sort(leftovers)
for _, name in ipairs(leftovers) do emit(name, by_atom[name]) end
end
local function render_section_relations(add, view)
local wrote = false
for _, a in ipairs(view.decls) do
local rels = (a.paths and a.paths.relations) or {}
if #rels > 0 then
wrote = true
add("### " .. a.name)
for _, rel in ipairs(rels) do
local dest = rel.destination or rel.producer_destination or ""
local slot = slot_suffix(dest)
local dest_s = tostring(dest)
if slot then dest_s = dest_s .. " (slot " .. slot .. ")" end
add(string.format("- `%s` words %s → %s dest %s",
rel.semantic or "?",
tostring(rel.producer_word or "?"),
tostring(rel.consumer_word or "?"),
dest_s))
end
add("")
end
end
if not wrote then add("_(none)_"); add("") end
end
local function render_section_forward(add, view)
local wrote = false
for _, a in ipairs(view.decls) do
local gpr = a.paths and a.paths.forward_state and a.paths.forward_state.gpr_values
if gpr and next(gpr) ~= nil then
wrote = true
add("### " .. a.name)
local keys = {}
for k in pairs(gpr) do keys[#keys + 1] = k end
table.sort(keys)
for _, k in ipairs(keys) do
local slot = gpr[k]
add(string.format("- `%s` %s", k, (slot and slot.kind) or "unknown"))
end
add("")
end
end
if not wrote then add("_(none)_"); add("") end
end
local SECTION_RENDERERS = {
{ header = "## Declarations", render = render_section_declarations },
{ header = "## Components", render = render_section_components },
{ header = "## RegUse schemas", render = render_section_reguse },
{ header = "## Annotations", render = render_section_annotations },
{ header = "## Binds_* structs", render = render_section_binds },
{ header = "## Phases / views / ctx", render = render_section_phases },
{ header = "## Register aliases", render = render_section_aliases },
{ header = "## Auto-reg", render = render_section_autoreg },
{ header = "## Collisions", render = render_section_collisions },
{ header = "## Findings", render = render_section_findings },
{ header = "## Relations", render = render_section_relations },
{ header = "## Forward GPR", render = render_section_forward },
}
--- Render the consolidated per-module markdown (`build/<module>.atom_meta_report.md`).
--- One ModuleView from the corpus; SECTION_RENDERERS walks it.
--- @param view table
--- @return string
local function render_module_meta_report(view)
local dir_basename = source_basename(view.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.",
@@ -264,199 +591,41 @@ local function render_module_meta_report(dir, dir_sources, annot_results, sa_res
} }
local function add(s) lines[#lines + 1] = s end local function add(s) lines[#lines + 1] = s end
-- Module summary table. local kinds = count_kinds(view.decls)
local n_atoms = 0 local n_annot, n_binds, n_macros = 0, 0, 0
local n_annot = 0 for _, src in ipairs(view.sources) do
local n_binds = 0 n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
local n_macros = 0 n_binds = n_binds + #((src.scan and src.scan.binds) or {})
local n_bare, n_proc = 0, 0 n_macros = n_macros + #((src.scan and src.scan.macros) or {})
for _, r in ipairs(annot_results) do
n_atoms = n_atoms + #r.atoms
n_annot = n_annot + #r.annots
n_binds = n_binds + #r.binds
n_macros = n_macros + #r.macros
end end
for _, a in ipairs(sa_results.atoms or {}) do local n_err, n_warn, n_info = 0, 0, 0
if a.kind == "comp_bare" then n_bare = n_bare + 1 for _, f in ipairs(view.findings or {}) do
elseif a.kind == "comp_proc" then n_proc = n_proc + 1 if f.kind == "error" then n_err = n_err + 1
elseif f.kind == "warning" then n_warn = n_warn + 1
else n_info = n_info + 1
end end
end end
add("## Module summary"); add("") add("## Module summary"); add("")
add("| metric | value |"); add("|--------|-------|") add("| metric | value |"); add("|--------|-------|")
add(string.format("| sources | %d |", #dir_sources)) add(string.format("| sources | %d |", #view.sources))
add(string.format("| atoms | %d (atoms: %d, comp_bare: %d, comp_proc: %d) |", add(string.format("| decls | %d (atom: %d, atom_proc: %d, comp_bare: %d, comp_proc: %d) |",
#(sa_results.atoms or {}), #view.decls, kinds.atom, kinds.atom_proc, kinds.comp_bare, kinds.comp_proc))
#(sa_results.atoms or {}) - n_bare - n_proc, n_bare, n_proc))
add(string.format("| annotations | %d |", n_annot)) add(string.format("| annotations | %d |", n_annot))
add(string.format("| binds structs | %d |", n_binds)) add(string.format("| binds structs | %d |", n_binds))
add(string.format("| macro decls | %d |", n_macros)) add(string.format("| macro decls | %d |", n_macros))
add(string.format("| findings | %d (errors: %d, warnings: %d, info: %d) |", add(string.format("| findings | %d (errors: %d, warnings: %d, info: %d) |",
#(sa_results.findings or {}), #(view.findings or {}), n_err, n_warn, n_info))
#(sa_results.errors or {}),
#(sa_results.warnings or {}),
#(sa_results.info or {})))
add("") add("")
-- Sources
add("## Sources"); add("") add("## Sources"); add("")
for _, s in ipairs(dir_sources) do add("- `" .. s.path .. "`") end for _, s in ipairs(view.sources) do add("- `" .. s.path .. "`") end
add("") add("")
-- Atoms (annotation) for _, row in ipairs(SECTION_RENDERERS) do
add("## Atoms"); add("") add(row.header); add("")
add("| kind | name | source | line |"); add("|------|------|--------|------|") row.render(add, view)
for _, r in ipairs(annot_results) do
local src_name = source_basename(r.source)
for _, a in ipairs(r.atoms) do
add(string.format("| atom | %s | %s | %d |", a.name, src_name, a.line))
end end
end
add("")
-- Annotations
add("## Annotations"); add("")
if #annot_results == 0 then
add("_(none)_")
else
add("| source | line | name | binds | reads | writes |")
add("|--------|------|------|-------|-------|--------|")
for _, r in ipairs(annot_results) do
local src_name = source_basename(r.source)
for _, a in ipairs(r.annots) do
local binds = a.binds or ""
local reads = (#a.reads > 0 and table.concat(a.reads, ",")) or ""
local writes = (#a.writes > 0 and table.concat(a.writes, ",")) or ""
add(string.format("| %s | %d | %s | %s | %s | %s |"
, src_name, a.line, a.name, binds, reads, writes))
end
end
end
add("")
-- Binds_* structs
add("## Binds_* structs"); add("")
if #annot_results == 0 then
add("_(none)_")
else
for _, r in ipairs(annot_results) do
local src_name = source_basename(r.source)
for _, b in ipairs(r.binds) do
add(string.format("### %s (%s:%d, %d bytes)",
b.name, src_name, b.line, b.bytes))
for _, f in ipairs(b.fields) do
add(string.format("- `+%d %s`", f.offset, f.name))
end
add("")
end
end
end
-- Macro decls
add("## Macro word-count declarations"); add("")
if #annot_results == 0 then
add("_(none)_")
else
add("| source | line | macro declaration |")
add("|--------|------|-------------------|")
for _, r in ipairs(annot_results) do
local src_name = source_basename(r.source)
for _, m in ipairs(r.macros) do
add(string.format("| %s | %d | %s |",
src_name, m.line, m.name))
end
end
end
add("")
-- Findings by atom (static-analysis)
add("## Static analysis — findings by atom"); add("")
local by_atom = {}
for _, f in ipairs(sa_results.findings or {}) do
by_atom[f.atom] = by_atom[f.atom] or {}
by_atom[f.atom][#by_atom[f.atom] + 1] = f
end
if next(by_atom) == nil then
add("_(no findings)_")
else
for _, a in ipairs(sa_results.atoms or {}) do
local fs = by_atom[a.name]
if fs then
add(string.format("### %s", a.name))
for _, f in ipairs(fs) do
add(string.format("- `[%s] %s`", f.check, f.msg))
end
add("")
end
end
end
-- Errors / Warnings / Info
local function add_findings(label, entries)
add(string.format("## %s", label))
if #entries == 0 then
add("_(none)_")
else
for _, e in ipairs(entries) do
add(string.format("- line %d %s", e.line, e.msg))
end
end
add("")
end
add_findings("Errors", sa_results.errors or {})
add_findings("Warnings", sa_results.warnings or {})
add_findings("Info", sa_results.info or {})
-- Per-atom cycle counts (path-aware)
add("## Per-atom cycle counts (path-aware, best case, no stalls)"); add("")
add("| atom | source | min | max | branches | paths | notes |")
add("|------|--------|-----|-----|----------|-------|-------|")
local sorted = {}
for _, a in ipairs(sa_results.atoms or {}) do sorted[#sorted + 1] = a end
table.sort(sorted, function(x, y)
return ((x.paths or {}).cycles_max or 0) > ((y.paths or {}).cycles_max or 0)
end)
for _, a in ipairs(sorted) do
local p = a.paths or {}
local src_name = a.source_path and source_basename(a.source_path) or ""
local notes = ""
if p.has_loops then notes = notes .. " [loop!]" end
if p.unknown_macros and #p.unknown_macros > 0 then
notes = notes .. " [unknown: " .. table.concat(p.unknown_macros, ", ") .. "]"
end
add(string.format("| %s | %s | %d | %d | %d | %d | %s |",
a.name, src_name,
p.cycles_min or 0, p.cycles_max or 0,
p.branches or 0, p.paths or 0, notes))
end
add("")
-- Per-source scan summary
add("## Per-source scan summary"); add("")
for _, src in ipairs(dir_sources) do
local src_atoms = {}
for _, a in ipairs(sa_results.atoms or {}) do
if a.source_path == src.path then src_atoms[#src_atoms + 1] = a end
end
if #src_atoms > 0 then
local mn, mx = math.huge, -1
for _, a in ipairs(src_atoms) do
local p = a.paths or {}
if (p.cycles_min or 0) < mn then mn = p.cycles_min or 0 end
if (p.cycles_max or 0) > mx then mx = p.cycles_max or 0 end
end
local path_str
if mx > 0 then
path_str = string.format(" cycles=%d..%d", mn, mx)
else
path_str = string.format(" %d cycles", mn)
end
add(string.format("- `%s` — %d atom%s%s",
src.basename, #src_atoms,
#src_atoms == 1 and "" or "s", path_str))
end
end
add("")
return table.concat(lines, "\n") .. "\n" return table.concat(lines, "\n") .. "\n"
end end
@@ -474,19 +643,8 @@ local REPORT_RENDERERS = {
basename = function(dir_basename) return dir_basename .. ".atom_meta_report" end, basename = function(dir_basename) return dir_basename .. ".atom_meta_report" end,
once = false, once = false,
gather = function(ctx, dir, dir_sources) gather = function(ctx, dir, dir_sources)
-- Annotations: re-run `annotation.validate()` per source (the existing pattern). local corpus = ctx.shared.corpus
local annot_results = {} return render_module_meta_report(build_module_view(dir, dir_sources, corpus))
for _, src in ipairs(dir_sources) do
if src.scan then
local r = annotation.validate(ctx, src, nil)
r.source = src.path
annot_results[#annot_results + 1] = r
end
end
-- Static-analysis: read stashed projection (no re-validate).
local dir_basename = dir:match("([^/\\]+)$") or dir
local sa_results = (ctx.shared.corpus.static_analysis_results or {})[dir_basename] or {}
return render_module_meta_report(dir, dir_sources, annot_results, sa_results)
end, end,
}, },
{ {
@@ -554,32 +712,30 @@ function M.run(ctx)
end end
end end
-- For the summary, compute per-module totals once (re-validating annotations per source — same pattern as the meta_report renderer). local view = build_module_view(dir, dir_sources, corpus)
local annot_results = {}
for _, src in ipairs(dir_sources) do
if src.scan then
local r = annotation.validate(ctx, src, nil)
r.source = src.path
annot_results[#annot_results + 1] = r
end
end
local n_annot, n_binds, n_macros = 0, 0, 0 local n_annot, n_binds, n_macros = 0, 0, 0
for _, r in ipairs(annot_results) do for _, src in ipairs(dir_sources) do
n_annot = n_annot + #r.annots n_annot = n_annot + #((src.scan and src.scan.atom_infos) or {})
n_binds = n_binds + #r.binds n_binds = n_binds + #((src.scan and src.scan.binds) or {})
n_macros = n_macros + #r.macros n_macros = n_macros + #((src.scan and src.scan.macros) or {})
end
local n_err, n_warn, n_info = 0, 0, 0
for _, f in ipairs(view.findings or {}) do
if f.kind == "error" then n_err = n_err + 1
elseif f.kind == "warning" then n_warn = n_warn + 1
else n_info = n_info + 1
end
end end
local sa_results = (corpus.static_analysis_results or {})[dir_basename] or {}
all_modules[#all_modules + 1] = { all_modules[#all_modules + 1] = {
module = dir_basename, module = dir_basename,
atoms = #(sa_results.atoms or {}), atoms = #view.decls,
annots = n_annot, annots = n_annot,
binds = n_binds, binds = n_binds,
macros = n_macros, macros = n_macros,
findings = #(sa_results.findings or {}), findings = #(view.findings or {}),
errors = #(sa_results.errors or {}), errors = n_err,
warnings = #(sa_results.warnings or {}), warnings = n_warn,
info = #(sa_results.info or {}), info = n_info,
} }
end end
+259 -2
View File
@@ -6,6 +6,7 @@
--- MipsAtom_Proc_ (kind = "atom_proc", body inside last {}) --- MipsAtom_Proc_ (kind = "atom_proc", body inside last {})
--- MipsAtomComp_ (kind = "comp_bare") --- MipsAtomComp_ (kind = "comp_bare")
--- MipsAtomComp_Proc_ (kind = "comp_proc", body inside last {}) --- MipsAtomComp_Proc_ (kind = "comp_proc", body inside last {})
--- MipsAtomComp_ProcMap_ (kind = "comp_proc", body is the one command)
--- atom_dbg_skip — bare whole-atom/component debug-step marker; following declaration disambiguates --- atom_dbg_skip — bare whole-atom/component debug-step marker; following declaration disambiguates
--- MipsCode code_<name> (kind = "raw_atom", offsets pass only) --- MipsCode code_<name> (kind = "raw_atom", offsets pass only)
--- typedef Struct_(Binds_X) { fields } --- typedef Struct_(Binds_X) { fields }
@@ -1380,6 +1381,32 @@ local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
return after_paren return after_paren
end end
--- Parse: `MipsAtomComp_ProcMap_(ab, command)` — body is the one command (second arg).
--- Reuses the proc name walk. Kind is `comp_proc`. The C expansion wraps
--- `atom_dbg_skip MipsAtomComp_Proc_(ab, {command })`; source-as-written is the map.
--- @param source string
--- @param pos integer
--- @param ident_end integer
--- @param line_of fun(pos: integer): integer
--- @param out SourceScan
--- @return integer
local function parse_mips_atom_comp_proc_map(source, pos, ident_end, line_of, out)
local inner, after_paren, open_paren = read_parens_after(source, ident_end)
if not inner then return after_paren end
local args = duffle.split_top_level_commas(inner)
if #args < 2 then return after_paren end
local command = duffle.trim(args[2])
if command == "" then return after_paren end
local raw_name = duffle.find_function_decl_for(source, open_paren, SLICE_MIPS_CODE_LEN)
if not raw_name then raw_name = "?" end
local name = strip_ac_prefix(raw_name)
local body_off = open_paren + 1 + (inner:find(command, 1, true) or 1) - 1
register_atom(out, "comp_proc", line_of(pos), name, command, body_off, raw_name, pos, after_paren, source)
local entry = out.atoms[#out.atoms]
entry.map_command = command
return after_paren
end
--- Parse: `MipsAtom_Proc_(<name>, <abuilder>, { <body> })` — body is inside the LAST `{` in args. --- Parse: `MipsAtom_Proc_(<name>, <abuilder>, { <body> })` — body is inside the LAST `{` in args.
--- Per Task 12.10: full support for the runtime-proc atom form. Registers the atom --- Per Task 12.10: full support for the runtime-proc atom form. Registers the atom
--- with kind `"atom_proc"` so offsets.lua / components.lua can emit --- with kind `"atom_proc"` so offsets.lua / components.lua can emit
@@ -1416,13 +1443,49 @@ local function parse_mips_atom_proc(source, pos, ident_end, line_of, out)
-- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which -- (`internal MipsAtom* X_proc(...)`), not from the first macro arg (which
-- is now `aa`). The backward walk finds the function decl before open_paren -- is now `aa`). The backward walk finds the function decl before open_paren
-- and strips the `_proc` suffix. -- and strips the `_proc` suffix.
local raw_name = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN) local raw_name, args_inner, func_ident = duffle.find_atom_proc_decl_for(source, open_paren, MIPS_ATOM_PTR_LEN)
if not raw_name then raw_name = "?" end if not raw_name then raw_name = "?" end
local name = strip_ac_prefix(raw_name) local name = strip_ac_prefix(raw_name)
local reg_use_schema_name = nil
local reg_use_param_name = nil
if args_inner then
local arg_tokens = duffle.split_top_level_commas(args_inner)
for _, tok in ipairs(arg_tokens) do
local trimmed = duffle.trim(tok)
local schema_suffix, param = trimmed:match("RegUse_([%w_]+)%s+([%w_]+)$")
if schema_suffix then
if reg_use_schema_name then
out.reg_use_errors[#out.reg_use_errors + 1] = {
kind = "reguse_multiple_params",
schema_name = "RegUse_" .. schema_suffix,
source_line = line_of(pos),
}
else
reg_use_schema_name = "RegUse_" .. schema_suffix
reg_use_param_name = param
end
end
end
end
-- 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, "atom_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source) register_atom(out, "atom_proc", line_of(pos), name, body, body_off, raw_name, pos, after_paren, source)
local entry = out.atoms[#out.atoms]
entry.reg_use_schema_name = reg_use_schema_name
entry.reg_use_param_name = reg_use_param_name
if reg_use_schema_name and func_ident then
local expected = "RegUse_" .. func_ident
if reg_use_schema_name ~= expected then
out.reg_use_errors[#out.reg_use_errors + 1] = {
kind = "reguse_name_mismatch",
schema_name = reg_use_schema_name,
func_ident = func_ident,
source_line = line_of(pos),
}
end
end
return after_paren return after_paren
end end
@@ -1530,6 +1593,171 @@ local function register_typedef_alias(underlying, name, pos, line_of, out)
} }
end end
local function parse_reg_use_schema_body(body)
local slots = {}
local alias_to_slot = {}
local slot_names = {}
local errors = {}
local function add_alias(path, slot)
if alias_to_slot[path] then
errors[#errors + 1] = { kind = "reguse_duplicate_alias", path = path }
return false
end
alias_to_slot[path] = slot
return true
end
local function add_slot(name, aliases, readonly)
if slot_names[name] then
errors[#errors + 1] = { kind = "reguse_duplicate_slot", name = name }
return nil
end
slot_names[name] = true
local slot = { name = name, aliases = aliases, readonly = readonly == true }
slots[#slots + 1] = slot
return slot
end
local function parse_reg_names(text, pos)
local names = {}
while pos <= #text do
pos = duffle.skip_ws_and_cmt(text, pos)
local name, name_end = duffle.read_ident(text, pos)
if not name then return nil, pos end
names[#names + 1] = name
pos = duffle.skip_ws_and_cmt(text, name_end)
if text:sub(pos, pos) == "," then
pos = pos + 1
else
break
end
end
if text:sub(pos, pos) == ";" then pos = pos + 1 end
return names, pos
end
local pos = 1
while pos <= #body do
pos = duffle.skip_ws_and_cmt(body, pos)
if pos > #body then break end
local first, first_end = duffle.read_ident(body, pos)
if not first then
pos = pos + 1
goto continue
end
local after = duffle.skip_ws_and_cmt(body, first_end)
if first == "const" then
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
return nil, errors
elseif first == "union" then
if body:sub(after, after) ~= "{" then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
local inner, after_braces = duffle.read_braces(body, after)
if not inner then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
local members = {}
local union_readonly = nil
local inner_pos = 1
while inner_pos <= #inner do
inner_pos = duffle.skip_ws_and_cmt(inner, inner_pos)
if inner_pos > #inner then break end
local m_type, m_type_end = duffle.read_ident(inner, inner_pos)
if not m_type then
inner_pos = inner_pos + 1
goto continue_inner
end
if m_type == "const" then
errors[#errors + 1] = { kind = "reguse_const_reg_spelling" }
return nil, errors
end
if m_type ~= "Reg" then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
local m_after = duffle.skip_ws_and_cmt(inner, m_type_end)
local m_readonly = false
local maybe_const, maybe_end = duffle.read_ident(inner, m_after)
if maybe_const == "const" then
m_readonly = true
m_after = duffle.skip_ws_and_cmt(inner, maybe_end)
end
if union_readonly == nil then
union_readonly = m_readonly
elseif union_readonly ~= m_readonly then
errors[#errors + 1] = { kind = "reguse_mixed_const" }
return nil, errors
end
local names, new_inner = parse_reg_names(inner, m_after)
if not names or #names == 0 then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
for _, n in ipairs(names) do members[#members + 1] = n end
inner_pos = new_inner
::continue_inner::
end
if #members == 0 then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
local after_close = duffle.skip_ws_and_cmt(body, after_braces)
local inst_name, inst_end = duffle.read_ident(body, after_close)
local aliases = {}
local slot_name
if inst_name then
slot_name = inst_name
for _, m in ipairs(members) do
local path = inst_name .. "." .. m
if not add_alias(path, slot_name) then return nil, errors end
aliases[#aliases + 1] = path
end
after_close = inst_end
else
slot_name = members[1]
for _, m in ipairs(members) do
if not add_alias(m, slot_name) then return nil, errors end
aliases[#aliases + 1] = m
end
end
if not add_slot(slot_name, aliases, union_readonly) then return nil, errors end
after_close = duffle.skip_ws_and_cmt(body, after_close)
if body:sub(after_close, after_close) == ";" then after_close = after_close + 1 end
pos = after_close
elseif first == "Reg" then
local readonly = false
local maybe_const, maybe_end = duffle.read_ident(body, after)
if maybe_const == "const" then
readonly = true
after = duffle.skip_ws_and_cmt(body, maybe_end)
end
local names, new_pos = parse_reg_names(body, after)
if not names or #names == 0 then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
for _, n in ipairs(names) do
if not add_alias(n, n) then return nil, errors end
if not add_slot(n, { n }, readonly) then return nil, errors end
end
pos = new_pos
else
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
::continue::
end
if #slots == 0 then
errors[#errors + 1] = { kind = "reguse_malformed" }
return nil, errors
end
return { slots = slots, alias_to_slot = alias_to_slot }, errors
end
--- Parse: `typedef` declarations. --- Parse: `typedef` declarations.
--- ---
--- Recognizes four shapes: --- Recognizes four shapes:
@@ -1563,6 +1791,21 @@ local function parse_typedef_binds(source, pos, ident_end, line_of, out)
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_struct_type(body, name, pos, line_of, out) register_struct_type(body, name, pos, line_of, out)
if name:sub(1, 7) == "RegUse_" then
local schema, schema_errors = parse_reg_use_schema_body(body)
if schema then
schema.name = name
schema.source_file = out._source_file
schema.source_line = line_of(pos)
out.reg_use_schemas[name] = schema
end
for _, err in ipairs(schema_errors or {}) do
err.schema_name = name
err.source_file = out._source_file
err.source_line = line_of(pos)
out.reg_use_errors[#out.reg_use_errors + 1] = err
end
end
attach_debug_skip_marker(out, "unrelated") attach_debug_skip_marker(out, "unrelated")
return after_brace return after_brace
@@ -1893,6 +2136,7 @@ local DECL_PARSERS = {
MipsAtom_Proc_ = parse_mips_atom_proc, MipsAtom_Proc_ = parse_mips_atom_proc,
MipsAtomComp_ = parse_mips_atom_comp, MipsAtomComp_ = parse_mips_atom_comp,
MipsAtomComp_Proc_ = parse_mips_atom_comp_proc, MipsAtomComp_Proc_ = parse_mips_atom_comp_proc,
MipsAtomComp_ProcMap_ = parse_mips_atom_comp_proc_map,
-- `atom_dbg_skip` is the only debug-skip parser entry. Every other -- `atom_dbg_skip` is the only debug-skip parser entry. Every other
-- identifier follows the ordinary unrelated-token path; there is no alias. -- identifier follows the ordinary unrelated-token path; there is no alias.
atom_dbg_skip = parse_dbg_skip_marker, atom_dbg_skip = parse_dbg_skip_marker,
@@ -1954,6 +2198,8 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies)
-- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums. -- typedef chain walking (cycle-guarded, depth <= 8), and struct field sums.
-- See `propagate_type_sizes()` below. -- See `propagate_type_sizes()` below.
type_name_registry = {}, type_name_registry = {},
reg_use_schemas = {},
reg_use_errors = {},
-- Shared `R_*_Code -> integer code` registry -- Shared `R_*_Code -> integer code` registry
-- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver). -- (passed in from M.run pass 1; same reference so preprocessor intercept writes are visible to the enum-value resolver).
-- Stripped from `src.scan` before return. -- Stripped from `src.scan` before return.
@@ -2186,13 +2432,15 @@ local function merge_corpus_registries(corpus)
corpus.atom_auto_regs = corpus.atom_auto_regs or {} corpus.atom_auto_regs = corpus.atom_auto_regs or {}
corpus.phase_auto_regs = corpus.phase_auto_regs or {} corpus.phase_auto_regs = corpus.phase_auto_regs or {}
corpus.collisions = corpus.collisions or {} corpus.collisions = corpus.collisions or {}
corpus.reg_use_schemas = corpus.reg_use_schemas or {}
corpus.reg_use_errors = corpus.reg_use_errors or {}
-- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge). -- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge).
-- This is safe because M.run is the only writer to these tables within a single orchestrator invocation. -- This is safe because M.run is the only writer to these tables within a single orchestrator invocation.
for _, key in ipairs({ for _, key in ipairs({
"register_alias_registry", "type_name_registry", "binds_by_name", "register_alias_registry", "type_name_registry", "binds_by_name",
"atoms_by_name", "atom_views", "atom_ctxs", "atom_phases", "atoms_by_name", "atom_views", "atom_ctxs", "atom_phases",
"atom_infos", "collisions", "atom_infos", "collisions", "reg_use_schemas", "reg_use_errors",
}) do }) do
corpus[key] = {} corpus[key] = {}
end end
@@ -2285,6 +2533,15 @@ local function merge_corpus_registries(corpus)
for _, info in ipairs(scan.atom_infos or {}) do for _, info in ipairs(scan.atom_infos or {}) do
corpus.atom_infos[#corpus.atom_infos + 1] = info corpus.atom_infos[#corpus.atom_infos + 1] = info
end end
for name, schema in pairs(scan.reg_use_schemas or {}) do
if corpus.reg_use_schemas[name] == nil then
corpus.reg_use_schemas[name] = schema
end
end
for _, err in ipairs(scan.reg_use_errors or {}) do
corpus.reg_use_errors[#corpus.reg_use_errors + 1] = err
end
end end
end end
end end
+46 -22
View File
@@ -451,6 +451,14 @@ local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
return false return false
end end
local function gpr_identity(event, pos)
local keys = event and event.gpr_keys
if keys and keys[pos] then return keys[pos] end
local arg = event and event.args and event.args[pos]
if type(arg) == "string" and arg:sub(1, 2) == "R_" then return arg end
return nil
end
-- True iff `consumer_event` reads the GPR operand at any position the destination register occupies. -- True iff `consumer_event` reads the GPR operand at any position the destination register occupies.
-- read_pos lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match. -- read_pos lookup consults `duffle.OPERAND_READ_POSITIONS` for the consumer's encoder and walks each `args[pos]` to find an operand-equal match.
local function is_gpr_consumer_of(consumer_event, destination) local function is_gpr_consumer_of(consumer_event, destination)
@@ -458,9 +466,8 @@ local function is_gpr_consumer_of(consumer_event, destination)
local read_pos = duffle.OPERAND_READ_POSITIONS or {} local read_pos = duffle.OPERAND_READ_POSITIONS or {}
local positions = read_pos[consumer_token] local positions = read_pos[consumer_token]
if not positions then return false end if not positions then return false end
local args = consumer_event.args or {}
for _, pos in ipairs(positions) do for _, pos in ipairs(positions) do
if args[pos] == destination then return true end if gpr_identity(consumer_event, pos) == destination then return true end
end end
return false return false
end end
@@ -552,6 +559,11 @@ local function is_gpr_operand(operand)
return type(operand) == "string" and operand:sub(1, 2) == "R_" return type(operand) == "string" and operand:sub(1, 2) == "R_"
end end
local function is_tracked_gpr(operand)
return is_gpr_operand(operand)
or (type(operand) == "string" and operand:sub(1, 7) == "reguse:")
end
local function constant_for_operand(gpr_values, operand) local function constant_for_operand(gpr_values, operand)
if operand == "R_0" then return 0 end if operand == "R_0" then return 0 end
local slot = is_gpr_operand(operand) and gpr_values[operand] or nil local slot = is_gpr_operand(operand) and gpr_values[operand] or nil
@@ -560,13 +572,13 @@ local function constant_for_operand(gpr_values, operand)
end end
local function invalidate_gpr(gpr_values, operand) local function invalidate_gpr(gpr_values, operand)
if is_gpr_operand(operand) and operand ~= "R_0" then if is_tracked_gpr(operand) and operand ~= "R_0" then
gpr_values[operand] = { kind = "unknown" } gpr_values[operand] = { kind = "unknown" }
end end
end end
local function store_gpr_constant(gpr_values, operand, value) local function store_gpr_constant(gpr_values, operand, value)
if not is_gpr_operand(operand) or operand == "R_0" then return end if not is_tracked_gpr(operand) or operand == "R_0" then return end
if value == nil then gpr_values[operand] = { kind = "unknown" } if value == nil then gpr_values[operand] = { kind = "unknown" }
else gpr_values[operand] = { kind = "constant", value = wrap_u4(value) } else gpr_values[operand] = { kind = "constant", value = wrap_u4(value) }
end end
@@ -629,26 +641,31 @@ end
-- Encoders without an explicit effect row conservatively invalidate every R_-prefixed operand. -- Encoders without an explicit effect row conservatively invalidate every R_-prefixed operand.
-- Recognized value rules are evaluated before their destination is invalidated. -- Recognized value rules are evaluated before their destination is invalidated.
-- A failed/unknown evaluation writes `{kind = "unknown"}` instead. -- A failed/unknown evaluation writes `{kind = "unknown"}` instead.
local function apply_gpr_effects(ev_ident, ev_args, forward_state) local function apply_gpr_effects(ev, forward_state)
local ev_ident = ev.encoder or ev.ident
local ev_args = ev.args or {}
local gpr_values = forward_state.gpr_values local gpr_values = forward_state.gpr_values
local effects = duffle.INSTRUCTION_GPR_EFFECTS or {} local effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
local row = effects[ev_ident] local row = effects[ev_ident]
if row == nil then if row == nil then
for _, operand in ipairs(ev_args or {}) do for pos, operand in ipairs(ev_args) do
invalidate_gpr(gpr_values, operand) local key = gpr_identity(ev, pos) or operand
if type(key) == "string" and (key:sub(1, 2) == "R_" or key:sub(1, 7) == "reguse:") then
if key ~= "R_0" then gpr_values[key] = { kind = "unknown" } end
end
end end
return return
end end
local value_rule = (duffle.GPR_VALUE_RULES or {})[ev_ident] local value_rule = (duffle.GPR_VALUE_RULES or {})[ev_ident]
local value = value_rule and evaluate_gpr_value_rule(value_rule, ev_args or {}, gpr_values) or nil local value = value_rule and evaluate_gpr_value_rule(value_rule, ev_args, gpr_values) or nil
for _, position in ipairs(row.writes or {}) do for _, position in ipairs(row.writes or {}) do
local destination = ev_args and ev_args[position] local destination = gpr_identity(ev, position)
if is_gpr_operand(destination) then if destination then
if value_rule and position == value_rule.dest and value ~= nil then if value_rule and position == value_rule.dest and value ~= nil then
store_gpr_constant(gpr_values, destination, value) store_gpr_constant(gpr_values, destination, value)
else else
invalidate_gpr(gpr_values, destination) if destination ~= "R_0" then gpr_values[destination] = { kind = "unknown" } end
end end
end end
end end
@@ -953,7 +970,7 @@ local function analyze_hardware_relations(atom)
end end
-- ── 2. Apply GPR value effects. ── -- ── 2. Apply GPR value effects. ──
apply_gpr_effects(ev_ident, ev_args, forward) apply_gpr_effects(ev, forward)
-- ── 3. Stage producers created by this event. ── -- ── 3. Stage producers created by this event. ──
local rows = rows_by_token[ev_ident] local rows = rows_by_token[ev_ident]
@@ -962,7 +979,7 @@ local function analyze_hardware_relations(atom)
-- `stage = false` rows document a direction but do not create a later command-input producer (SWC2 and ordinary MTC0). -- `stage = false` rows document a direction but do not create a later command-input producer (SWC2 and ordinary MTC0).
if row.stage ~= false then if row.stage ~= false then
local dest_arg = row.writes and row.writes.arg local dest_arg = row.writes and row.writes.arg
local destination = dest_arg and ev_args[dest_arg] or nil local destination = dest_arg and (gpr_identity(ev, dest_arg) or ev_args[dest_arg]) or nil
if destination then if destination then
-- Apply the destination_match filter when present. -- Apply the destination_match filter when present.
if row.destination_match and row.destination_match ~= destination then if row.destination_match and row.destination_match ~= destination then
@@ -1305,8 +1322,14 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
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 dest_pos = prev_writes[1]
local load_dest = prev_writes[1] and prev_args[prev_writes[1]] or "<load-destination>" local load_dest = dest_pos and (gpr_identity(prev_ev, dest_pos) or (prev_ev.args or {})[dest_pos]) or "<load-destination>"
local authored = dest_pos and (prev_ev.args or {})[dest_pos] or load_dest
local shown = authored
if type(load_dest) == "string" and load_dest:sub(1, 7) == "reguse:" then
local slot = load_dest:match("([^:]+)$")
if slot then shown = authored .. " (slot " .. slot .. ")" end
end
findings[#findings + 1] = { findings[#findings + 1] = {
check = "hazard_nop_use", check = "hazard_nop_use",
kind = "info", kind = "info",
@@ -1319,7 +1342,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
producer_destination = load_dest, producer_destination = load_dest,
consumer_token = "<would-be-consumer>", consumer_token = "<would-be-consumer>",
msg = string.format("%s at line %d: nop at word %d is modeled-required (load-delay slot for %s)" msg = string.format("%s at line %d: nop at word %d is modeled-required (load-delay slot for %s)"
, atom.name, ev_line, ev_word, load_dest , atom.name, ev_line, ev_word, shown
), ),
} }
else else
@@ -1352,7 +1375,7 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
for _, row in ipairs(relations_table) do for _, row in ipairs(relations_table) do
if row.token == ev_ident and row.stage ~= false then if row.token == ev_ident and row.stage ~= false then
local dest_arg = row.writes and row.writes.arg local dest_arg = row.writes and row.writes.arg
local destination = dest_arg and ev_args[dest_arg] or nil local destination = dest_arg and (gpr_identity(ev, dest_arg) or ev_args[dest_arg]) or nil
if destination and (not row.destination_match or row.destination_match == destination) then if destination and (not row.destination_match or row.destination_match == destination) then
local required = row.visibility and row.visibility.required local required = row.visibility and row.visibility.required
if required == nil and not (row.visibility and row.visibility.kind == "unknown_consumer") then if required == nil and not (row.visibility and row.visibility.kind == "unknown_consumer") then
@@ -1517,11 +1540,12 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
-- Use `net_reads` to ignore RMW positions (write shadows read within the same instruction). -- Use `net_reads` to ignore RMW positions (write shadows read within the same instruction).
if not is_load then if not is_load then
for _, pos in ipairs(net_reads(event_ident, args)) do for _, pos in ipairs(net_reads(event_ident, args)) do
local reg = args[pos] local reg = gpr_identity(event, pos)
if type(reg) == "string" and reg:sub(1, 2) == "R_" then if reg then
local until_idx = volatile_until[reg] local until_idx = volatile_until[reg]
if until_idx and event_idx <= until_idx then if until_idx and event_idx <= until_idx then
local ev_line = line_for_word_event(event) local ev_line = line_for_word_event(event)
local authored = args[pos] or reg
findings[#findings + 1] = { findings[#findings + 1] = {
atom = atom.name, atom = atom.name,
line = ev_line, line = ev_line,
@@ -1530,7 +1554,7 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
msg = string.format("%s at line %d reads %s at word %d, but a prior load's " msg = string.format("%s at line %d reads %s at word %d, but a prior load's "
.. "delay slot is not over until word %d; insert a `nop` between the " .. "delay slot is not over until word %d; insert a `nop` between the "
.. "load and this instruction.", .. "load and this instruction.",
atom.name, ev_line, reg, event_idx, until_idx), atom.name, ev_line, authored, event_idx, until_idx),
} }
end end
end end
@@ -1541,8 +1565,8 @@ local function check_load_delay_slots(atom, pipe_ctx, findings)
local effect = gpr_effects[event_ident] local effect = gpr_effects[event_ident]
if effect and effect.writes then if effect and effect.writes then
for _, pos in ipairs(effect.writes) do for _, pos in ipairs(effect.writes) do
local reg = args[pos] local reg = gpr_identity(event, pos)
if type(reg) == "string" and reg:sub(1, 2) == "R_" then if reg then
if is_load then if is_load then
-- Load: destination volatile for exactly 1 slot (the delay slot). -- Load: destination volatile for exactly 1 slot (the delay slot).
volatile_until[reg] = event_idx + 1 volatile_until[reg] = event_idx + 1