phase-36: T5 (header step) — the GTE consolidation: tools/gte_consolidate.py (signatures by the build's own maspsx→as tail: bytes + operand counts + clobbers; one canonical text per signature under Sony's names, Sony's clobbers canonical even when every definition carries the steer; lever variants <name>_m tried as canonical first; direct statements → canonical calls; --sweep), include/gte_inline.h (50 macros for 9,102 definitions) included from common.h — R22 218/218; the census's gte-lever class + per-TU definition count + strict gate; the cycle's MODE=gte; dictionary + SETUP rows; kit corpus; log entry

This commit is contained in:
Drew T
2026-09-09 05:28:50 -06:00
parent 917c68afd9
commit 0bd784c64e
21 changed files with 13318 additions and 48 deletions
+6
View File
@@ -489,6 +489,12 @@ unsloth_compiled_cache/
/.run/P36/delever/includers_cache.json
!/.run/P36/delever/*.jsonl
!/.run/P36/delever/*.txt
!/.run/P36/gte/
/.run/P36/gte/*
!/.run/P36/gte/*.json
!/.run/P36/gte/*.log
!/.run/P36/gte/*.txt
/.run/P36/gte/inventory.json
!/.run/P36/permuter/
/.run/P36/permuter/*
!/.run/P36/permuter/*.log
+476
View File
@@ -0,0 +1,476 @@
clean: removed build/, expected/, and the regenerated splat tree (asm/, assets/, undefined_*_auto.txt).
disc-extract: up to date — extracted/retail/ matches the committed manifest (sha1 e5c6019249dec3c71bb2cf40658a78439dddb037)
splat 0.41.0 (powered by spimdisasm 1.41.0)
0%| | 0/1096 [00:00<?, ?it/s]
Loading symbols (symbols.us): 0%| | 0/1096 [00:00<?, ?it/s]
Loading symbols (symbols.us): 100%|██████████| 1096/1096 [00:00<00:00, 227696.13it/s]
0%| | 0/47 [00:00<?, ?it/s]
Loading symbols (symbols.us.ram): 0%| | 0/47 [00:00<?, ?it/s]
Loading symbols (symbols.us.ram): 100%|██████████| 47/47 [00:00<00:00, 633865.88it/s]
0%| | 0/2 [00:00<?, ?it/s]
Scanning header: 0%| | 0/2 [00:00<?, ?it/s]
Scanning main: 0%| | 0/2 [00:00<?, ?it/s]
Rodata segment '800' may belong to the text segment '800'
Based on the usage from the function LzssDecodeSector to the symbol jtbl_80072A38
The rodata segment '800' has jumptables that are not aligned properly file-wise, indicating one or more likely file split.
File split suggestions for this segment will follow in config yaml format:
- [0x6324C, .rodata]
- [0x63388, .rodata]
- [0x633FC, .rodata]
Rodata segment '800_b' may belong to the text segment '800_b'
Based on the usage from the function func_8002B0B4 to the symbol jtbl_80072E44
Rodata segment '800_b_2' may belong to the text segment '800_b_2'
Based on the usage from the function func_8002DC68 to the symbol jtbl_80072F3C
The rodata segment '800_b_2' has jumptables that are not aligned properly file-wise, indicating one or more likely file split.
File split suggestions for this segment will follow in config yaml format:
- [0x63920, .rodata]
Rodata segment '800_c' may belong to the text segment '800_c'
Based on the usage from the function func_80035270 to the symbol jtbl_800732A0
Scanning main: 100%|██████████| 2/2 [00:01<00:00, 1.37it/s]
Scanning main: 100%|██████████| 2/2 [00:01<00:00, 1.37it/s]
0%| | 0/2 [00:00<?, ?it/s]
Splitting header: 0%| | 0/2 [00:00<?, ?it/s]
Splitting main: 0%| | 0/2 [00:00<?, ?it/s]
Splitting main: 100%|██████████| 2/2 [00:01<00:00, 1.47it/s]
Splitting main: 100%|██████████| 2/2 [00:01<00:00, 1.47it/s]
0%| | 0/2 [00:00<?, ?it/s]
Linker script header: 0%| | 0/2 [00:00<?, ?it/s]
Linker script main: 0%| | 0/2 [00:00<?, ?it/s]
Linker script main: 100%|██████████| 2/2 [00:00<00:00, 90.06it/s]
Split 340 KB (82.28%) in defined segments
header: 2 KB (0.50%) 1 split, 0 cached
c: 338 KB (81.78%) 85 split, 0 cached
unknown: 0 B (0.00%) from unknown bin files
ld_interleave --order: .main island = 8 pieces [53198.data.o, 800.o, 63470.data.o, 800_b.o, 800_b_2.o, 63940.data.o, 800_c.o, 63C4C.data.o]; pre=0 text=85 empties=166 bss=85
[ OK ] ov_SC05_000
[ OK ] resident
[ OK ] ov_SC01_000
[ OK ] ov_SC04_000
[ OK ] ov_SC06_000
[ OK ] ov_SC01_006
[ OK ] ov_SC01_005
[ OK ] ov_SC01_077
[ OK ] ov_SC02_003
[ OK ] ov_SC04_018
[ OK ] ov_SC04_019
[ OK ] ov_SC02_000
[ OK ] ov_SC03_002
[ OK ] ov_SC01_001
[ OK ] ov_SC03_006
[ OK ] ov_SC01_004
[ OK ] ov_SC03_001
[ OK ] ov_SC01_008
[ OK ] ov_SC01_074
[ OK ] ov_SC07_000
[ OK ] ov_SC02_004
[ OK ] ov_SC02_015
[ OK ] ov_SC02_021
[ OK ] ov_SC01_080
[ OK ] ov_SC01_084
[ OK ] ov_SC01_009
[ OK ] ov_SC02_016
[ OK ] ov_SC02_031
[ OK ] ov_SC02_005
[ OK ] ov_SC02_026
[ OK ] ov_SC02_039
[ OK ] ov_SC02_027
[ OK ] ov_SC03_003
[ OK ] ov_SC02_028
[ OK ] ov_SC02_035
[ OK ] ov_SC02_011
[ OK ] ov_SC02_041
[ OK ] ov_SC02_017
[ OK ] ov_SC03_012
[ OK ] ov_SC03_010
[ OK ] ov_SC03_011
[ OK ] ov_SC03_013
[ OK ] ov_SC03_023
[ OK ] ov_SC03_007
[ OK ] ov_SC03_024
[ OK ] ov_SC03_089
[ OK ] ov_SC03_014
[ OK ] ov_SC03_029
[ OK ] ov_SC03_015
[ OK ] ov_SC03_092
[ OK ] ov_SC03_031
[ OK ] ov_SC03_090
[ OK ] ov_SC03_028
[ OK ] ov_SC03_095
[ OK ] ov_SC03_096
[ OK ] ov_SC03_030
[ OK ] ov_SC03_093
[ OK ] ov_SC03_094
[ OK ] ov_SC03_091
[ OK ] ov_SC03_097
[ OK ] ov_SC03_101
[ OK ] ov_SC03_098
[ OK ] ov_SC03_108
[ OK ] ov_SC03_109
[ OK ] ov_SC03_110
[ OK ] ov_SC03_102
[ OK ] ov_SC03_100
[ OK ] ov_SC03_115
[ OK ] ov_SC03_105
[ OK ] ov_SC03_113
[ OK ] ov_SC03_112
[ OK ] ov_SC03_099
[ OK ] ov_SC03_104
[ OK ] ov_SC03_103
[ OK ] ov_SC03_114
[ OK ] ov_SC03_111
[ OK ] ov_SC03_116
[ OK ] ov_SC03_126
[ OK ] ov_SC03_121
[ OK ] ov_SC03_117
[ OK ] ov_SC04_003
[ OK ] ov_SC04_008
[ OK ] ov_SC03_125
[ OK ] ov_SC03_118
[ OK ] ov_SC04_010
[ OK ] ov_SC04_006
[ OK ] ov_SC04_002
[ OK ] ov_SC03_119
[ OK ] ov_SC04_007
[ OK ] ov_SC04_004
[ OK ] ov_SC04_005
[ OK ] ov_SC03_124
[ OK ] ov_SC04_009
[ OK ] ov_SC04_012
[ OK ] ov_SC04_021
[ OK ] ov_SC04_016
[ OK ] ov_SC05_006
[ OK ] ov_SC04_015
[ OK ] ov_SC05_002
[ OK ] ov_SC05_009
[ OK ] ov_SC05_007
[ OK ] ov_SC05_001
[ OK ] ov_SC05_008
[ OK ] ov_SC04_011
[ OK ] ov_SC05_003
[ OK ] ov_SC04_020
[ OK ] ov_SC05_005
[ OK ] ov_SC05_004
[ OK ] ov_SC05_011
[ OK ] ov_SC05_010
[ OK ] ov_SC05_019
[ OK ] ov_SC06_008
[ OK ] ov_SC06_015
[ OK ] ov_SC06_013
[ OK ] ov_SC06_010
[ OK ] ov_SC06_014
[ OK ] ov_SC06_016
[ OK ] ov_SC06_011
[ OK ] ov_SC05_017
[ OK ] ov_SC05_018
[ OK ] ov_SC06_018
[ OK ] ov_SC06_006
[ OK ] ov_SC06_027
[ OK ] ov_SC06_020
[ OK ] ov_SC06_024
[ OK ] ov_SC06_025
[ OK ] ov_SC06_022
[ OK ] ov_SC07_008
[ OK ] md_MAIN_013
[ OK ] md_MAIN_014
[ OK ] ov_SC07_001
[ OK ] md_MAIN_015
[ OK ] ov_SC07_002
[ OK ] ov_SC06_030
[ OK ] md_MAIN_016
[ OK ] md_MAIN_017
[ OK ] md_MAIN_018
[ OK ] ov_SC07_009
[ OK ] md_MAIN_020
[ OK ] md_MAIN_019
[ OK ] md_MAIN_021
[ OK ] md_MAIN_022
[ OK ] md_MAIN_023
[ OK ] md_MAIN_024
[ OK ] md_MAIN_025
[ OK ] md_MAIN_026
[ OK ] md_MAIN_027
[ OK ] md_MAIN_028
[ OK ] md_MAIN_029
[ OK ] md_MAIN_030
[ OK ] ov_SC06_033
[ OK ] ov_SC06_032
[ OK ] md_MAIN_031
[ OK ] md_MAIN_033
[ OK ] md_MAIN_035
[ OK ] md_MAIN_034
[ OK ] md_MAIN_032
[ OK ] md_MAIN_036
[ OK ] md_MAIN_037
[ OK ] ov_SC07_011
[ OK ] ov_SC06_029
[ OK ] md_MAIN_039
[ OK ] md_MAIN_038
[ OK ] md_MAIN_042
[ OK ] md_MAIN_043
[ OK ] md_MAIN_040
[ OK ] md_MAIN_044
[ OK ] md_MAIN_041
[ OK ] md_MAIN_045
[ OK ] md_MAIN_047
[ OK ] ov_SC07_010
[ OK ] md_MAIN_046
[ OK ] md_MAIN_008
[ OK ] md_MAIN_001
[ OK ] md_MAIN_011
[ OK ] ov_MAIN_012
[ OK ] md_SC03_078
[ OK ] md_SC03_073
[ OK ] md_SC03_074
[ OK ] md_SC03_075
[ OK ] md_SC03_077
[ OK ] ov_SC03_107
[ OK ] md_SC03_076
[ OK ] md_SC03_079
[ OK ] md_SC03_132
[ OK ] md_SC03_133
[ OK ] md_SC03_137
[ OK ] md_SC03_134
[ OK ] md_SC03_136
[ OK ] ov_SC07_007
[ OK ] md_SC03_135
[ OK ] md_SC04_025
[ OK ] md_SC03_138
[ OK ] md_MAIN_003
[ OK ] md_SC04_024
[ OK ] md_SC04_026
[ OK ] md_SC04_029
[ OK ] md_SC04_027
[ OK ] md_SC04_028
[ OK ] ov_SC07_006
[ OK ] md_SC05_028
[ OK ] md_SC05_024
[ OK ] md_SC04_030
[ OK ] md_SC05_023
[ OK ] md_SC05_027
[ OK ] md_SC05_025
[ OK ] md_MAIN_007
[ OK ] md_SC05_029
[ OK ] md_SC03_053
[ OK ] md_MAIN_009
[ OK ] md_SC05_026
[ OK ] md_SC03_056
[ OK ] md_SC03_054
[ OK ] ov_SC02_037
[ OK ] md_SC02_009
[ OK ] md_SC07_003
[ OK ] md_SC07_004
extract-all: 217 extracted, 0 failed of 217 (+ main, serial)
[ OK ] resident
[ OK ] ov_SC05_000
[ OK ] ov_SC01_005
[ OK ] ov_SC01_006
[ OK ] ov_SC03_002
[ OK ] ov_SC04_000
[ OK ] ov_SC01_001
[ OK ] ov_SC01_000
[ OK ] ov_SC03_006
[ OK ] ov_SC04_018
[ OK ] ov_SC04_019
[ OK ] ov_SC06_000
[ OK ] ov_SC03_001
[ OK ] ov_SC01_077
[ OK ] ov_SC02_000
[ OK ] ov_SC02_003
[ OK ] ov_SC07_000
[ OK ] ov_SC01_004
[ OK ] ov_SC02_004
[ OK ] ov_SC01_008
[ OK ] ov_SC01_080
[ OK ] ov_SC01_074
[ OK ] ov_SC01_009
[ OK ] ov_SC01_084
[ OK ] ov_SC02_016
[ OK ] ov_SC02_015
[ OK ] ov_SC02_011
[ OK ] ov_SC02_021
[ OK ] ov_SC02_026
[ OK ] ov_SC02_005
[ OK ] ov_SC02_017
[ OK ] ov_SC02_039
[ OK ] ov_SC02_031
[ OK ] ov_SC02_028
[ OK ] ov_SC02_027
[ OK ] ov_SC02_041
[ OK ] ov_SC03_003
[ OK ] ov_SC02_035
[ OK ] ov_SC03_010
[ OK ] ov_SC03_012
[ OK ] ov_SC03_011
[ OK ] ov_SC03_013
[ OK ] ov_SC03_007
[ OK ] ov_SC03_023
[ OK ] ov_SC03_014
[ OK ] ov_SC03_015
[ OK ] ov_SC03_031
[ OK ] ov_SC03_030
[ OK ] ov_SC03_024
[ OK ] ov_SC03_029
[ OK ] ov_SC03_089
[ OK ] ov_SC03_028
[ OK ] ov_SC03_092
[ OK ] ov_SC03_095
[ OK ] ov_SC03_090
[ OK ] ov_SC03_091
[ OK ] ov_SC03_093
[ OK ] ov_SC03_096
[ OK ] ov_SC03_094
[ OK ] main
[ OK ] ov_SC03_097
[ OK ] ov_SC03_098
[ OK ] ov_SC03_100
[ OK ] ov_SC03_099
[ OK ] ov_SC03_101
[ OK ] ov_SC03_102
[ OK ] ov_SC03_108
[ OK ] ov_SC03_103
[ OK ] ov_SC03_109
[ OK ] ov_SC03_113
[ OK ] ov_SC03_110
[ OK ] ov_SC03_111
[ OK ] ov_SC03_104
[ OK ] ov_SC03_114
[ OK ] ov_SC03_112
[ OK ] ov_SC03_105
[ OK ] ov_SC03_115
[ OK ] ov_SC03_116
[ OK ] ov_SC03_117
[ OK ] ov_SC03_126
[ OK ] ov_SC03_121
[ OK ] ov_SC03_125
[ OK ] ov_SC04_003
[ OK ] ov_SC03_118
[ OK ] ov_SC04_008
[ OK ] ov_SC04_004
[ OK ] ov_SC04_002
[ OK ] ov_SC03_119
[ OK ] ov_SC03_124
[ OK ] ov_SC04_006
[ OK ] ov_SC04_005
[ OK ] ov_SC04_009
[ OK ] ov_SC04_007
[ OK ] ov_SC04_010
[ OK ] ov_SC04_012
[ OK ] ov_SC04_016
[ OK ] ov_SC04_021
[ OK ] ov_SC05_006
[ OK ] ov_SC04_015
[ OK ] ov_SC04_011
[ OK ] ov_SC05_002
[ OK ] ov_SC05_003
[ OK ] ov_SC04_020
[ OK ] ov_SC05_004
[ OK ] ov_SC05_007
[ OK ] ov_SC05_001
[ OK ] ov_SC05_005
[ OK ] ov_SC05_009
[ OK ] ov_SC05_008
[ OK ] ov_SC05_010
[ OK ] ov_SC05_011
[ OK ] ov_SC05_019
[ OK ] ov_SC06_010
[ OK ] ov_SC05_018
[ OK ] ov_SC06_011
[ OK ] ov_SC06_008
[ OK ] ov_SC06_013
[ OK ] ov_SC06_015
[ OK ] ov_SC06_006
[ OK ] ov_SC06_014
[ OK ] ov_SC06_016
[ OK ] ov_SC05_017
[ OK ] ov_SC06_020
[ OK ] ov_SC06_018
[ OK ] ov_SC06_022
[ OK ] ov_SC06_024
[ OK ] ov_SC06_025
[ OK ] ov_SC06_027
[ OK ] md_MAIN_013
[ OK ] md_MAIN_014
[ OK ] ov_SC07_009
[ OK ] ov_SC07_008
[ OK ] ov_SC07_001
[ OK ] md_MAIN_015
[ OK ] ov_SC06_030
[ OK ] md_MAIN_017
[ OK ] md_MAIN_018
[ OK ] md_MAIN_016
[ OK ] md_MAIN_019
[ OK ] md_MAIN_021
[ OK ] md_MAIN_023
[ OK ] md_MAIN_020
[ OK ] md_MAIN_022
[ OK ] md_MAIN_024
[ OK ] md_MAIN_026
[ OK ] md_MAIN_025
[ OK ] ov_SC07_002
[ OK ] md_MAIN_027
[ OK ] md_MAIN_028
[ OK ] ov_SC06_029
[ OK ] md_MAIN_029
[ OK ] md_MAIN_031
[ OK ] md_MAIN_030
[ OK ] md_MAIN_033
[ OK ] ov_SC06_032
[ OK ] md_MAIN_032
[ OK ] md_MAIN_035
[ OK ] md_MAIN_034
[ OK ] md_MAIN_037
[ OK ] md_MAIN_038
[ OK ] ov_SC07_006
[ OK ] md_MAIN_040
[ OK ] md_MAIN_036
[ OK ] ov_SC07_007
[ OK ] md_MAIN_039
[ OK ] md_MAIN_041
[ OK ] ov_SC07_010
[ OK ] md_MAIN_042
[ OK ] md_MAIN_043
[ OK ] md_MAIN_047
[ OK ] md_MAIN_046
[ OK ] md_MAIN_001
[ OK ] md_MAIN_045
[ OK ] md_MAIN_008
[ OK ] md_MAIN_044
[ OK ] md_MAIN_011
[ OK ] md_SC03_073
[ OK ] md_SC03_075
[ OK ] md_SC03_074
[ OK ] md_SC03_079
[ OK ] md_SC03_078
[ OK ] md_SC03_132
[ OK ] ov_MAIN_012
[ OK ] md_SC03_077
[ OK ] md_SC03_076
[ OK ] md_SC07_003
[ OK ] md_SC03_134
[ OK ] md_SC03_133
[ OK ] ov_SC07_011
[ OK ] md_SC03_137
[ OK ] md_SC04_024
[ OK ] md_SC03_136
[ OK ] md_SC04_025
[ OK ] ov_SC06_033
[ OK ] md_SC03_138
[ OK ] md_SC04_026
[ OK ] md_SC03_135
[ OK ] md_SC05_023
[ OK ] md_MAIN_003
[ OK ] md_SC04_029
[ OK ] md_SC04_030
File diff suppressed because it is too large Load Diff
+36 -12
View File
@@ -24,13 +24,13 @@
"gte_stsv": 145,
"gte_stsxy": 361,
"gte_stsxy3": 191,
"gte_stsxy3_f3": 61,
"gte_stsxy3c": 94,
"gte_stsz3": 123,
"gte_stsz4": 126,
"gte_stszotz": 113,
"lhu": 70,
"lw": 80,
"lwc2": 435,
"lhu": 66,
"lwc2": 432,
"mfc2": 61,
"swc2": 449
},
@@ -56,7 +56,9 @@
"asm-body/direct": 13,
"barrier/direct": 4576,
"barrier/via-macro": 2,
"gte/direct": 1140,
"gte-lever/direct": 49,
"gte-unsigned/direct": 3,
"gte/direct": 1088,
"gte/via-macro": 5751,
"instruction/direct": 1690,
"instruction/via-macro": 22,
@@ -89,7 +91,9 @@
"kinds": {
"asm-body": 13,
"barrier": 4578,
"gte": 6891,
"gte": 6839,
"gte-lever": 49,
"gte-unsigned": 3,
"instruction": 1712,
"keepalive": 2000,
"launder": 4676,
@@ -232,18 +236,27 @@
}
},
"coverage_ok": true,
"elapsed_s": 39.0,
"elapsed_s": 50.8,
"generated": "2026-09-09",
"head": "f087282a3",
"gte_levers": {
"direct": 49,
"marked": 0,
"sites": 49,
"unmarked": 49,
"unsigned": 3,
"via_macro": 0,
"what": "GTE ops whose clobbers exceed the canonical macro's (a scheduling steer): levers for T7, outside the headline number"
},
"head": "917c68afd",
"headers": 3181,
"levers_AB": {
"asm": 12979,
"bodies": 12501,
"distinct_bodies": 1728,
"asm": 13028,
"bodies": 12518,
"distinct_bodies": 1745,
"marked": 33625,
"pins": 20646,
"sites": 33625,
"unmarked": 0,
"sites": 33674,
"unmarked": 49,
"what": "register pins + asm statements excluding GTE ops: the classes the phase drives to 0"
},
"macro_definitions": {
@@ -388,6 +401,17 @@
"count": 0,
"sample": []
},
"per_tu_asm_macro_definitions": {
"by_kind": {
"barrier": 1,
"gte": 9102,
"instruction": 9,
"launder": 428
},
"gte_variants": 0,
"total": 9540,
"what": "asm-bearing #define blocks outside the GTE header: 0 at the close (T5 deletes the canonical duplicates, T7 the variants)"
},
"pins": {
"bare_name": 25,
"init": 2776,
+8 -6
View File
@@ -6,24 +6,26 @@ lever_census: 218 binaries · 4,121 TUs + 3,181 headers · coverage OK · unclas
coverage attribute raw 76 = live 76 + macro-block 0 + comment/dead 0
class sites in-bodies file-scope bodies distinct TUs marked unmarked kinds
A pins 20646 20646 0 10329 1330 2580 20646 0 {'pin': 20646}
B asm 22552 22539 13 9758 1054 2289 13155 9397 {'gte': 6891, 'barrier': 4578, 'launder': 4676, 'instruction': 1712, 'keepalive': 2000, 'asm-body': 13, 'verbatim-body': 2682}
B asm 22552 22539 13 9758 1054 2289 13155 9397 {'gte': 6839, 'barrier': 4578, 'gte-lever': 49, 'gte-unsigned': 3, 'launder': 4676, 'instruction': 1712, 'keepalive': 2000, 'asm-body': 13, 'verbatim-body': 2682}
C volatile 1722 1570 152 628 97 612 14 1708 {'decl-body': 58, 'decl-file': 152, 'cast': 1510, 'param': 2}
D register 50 50 0 47 47 6 0 50 {'register': 50}
E asm-label 7428 1005 6423 737 117 2080 0 7428 {'asm-label': 7428}
F builtin 445 445 0 428 27 302 0 445 {'builtin': 445}
G attribute 76 1 75 1 1 40 0 76 {'attribute': 76}
UNION A–D: 15,955 bodies · 2,023 distinct (addresses normalized) · 394 multi-copy classes holding 14,326 bodies · by kind {'ov': 15258, 'resident': 12, 'main': 180, 'md': 182, 'shared': 323}
THE PHASE'S NUMBER (pins + asm statements, GTE excluded): 33,625 sites in 12,501 bodies (1,728 distinct) · marked !FAKE 33,625 · UNMARKED 0
UNION A–D: 15,955 bodies · 2,023 distinct (addresses normalized) · 394 multi-copy classes holding 14,326 bodies · by kind {'ov': 15258, 'shared': 323, 'main': 180, 'md': 182, 'resident': 12}
THE PHASE'S NUMBER (pins + asm statements, GTE excluded): 33,674 sites in 12,518 bodies (1,745 distinct) · marked !FAKE 33,625 · UNMARKED 49
orphan !FAKE markers (no pin/asm site on the line nor below): 0
GTE levers (clobbers beyond the canonical macro's): 49 sites (0 via a variant macro, 49 direct) · marked 0 · UNMARKED 49 · unsigned GTE statements 3
per-TU asm macro definitions outside the GTE header: 9,540 {'gte': 9102, 'launder': 428, 'instruction': 9, 'barrier': 1} (GTE variants 0)
pins: 20,646 · $0 1,321 · $sp 13 · with initializer 2,776 · volatile-qualified 134 · bare-name 25 · spellings {'__asm__': 20544, 'asm': 102}
whole-body asm routines in C shells, manifest PERMANENT (hand asm, NOT levers): 22 routines · 2,682 sites (2,660 private copies + 22 shared headers); asm-bodies NOT permanent (levers): 13 site(s) ['func_8001E378:DECOMPILE-NOW', 'func_80020F34:DECOMPILE-NOW', 'func_800249F0:DECOMPILE-NOW', 'func_80025CBC:DECOMPILE-NOW', 'func_80026514:UNCERTAIN', 'func_800268D0:UNCERTAIN', 'func_80027058:DECOMPILE-NOW', 'func_80027200:DECOMPILE-NOW', 'func_800CBA44:DECOMPILE-NOW', 'func_8017D810:DECOMPILE-NOW', 'func_8017E26C:UNCERTAIN', 'func_80184440:DECOMPILE-NOW', 'func_801A3BCC:DECOMPILE-NOW']
asm kinds: {'asm-body/direct': 13, 'barrier/direct': 4576, 'barrier/via-macro': 2, 'gte/direct': 1140, 'gte/via-macro': 5751, 'instruction/direct': 1690, 'instruction/via-macro': 22, 'keepalive/direct': 2000, 'launder/direct': 4236, 'launder/via-macro': 440, 'verbatim-body/direct': 2682}
asm kinds: {'asm-body/direct': 13, 'barrier/direct': 4576, 'barrier/via-macro': 2, 'gte/direct': 1088, 'gte/via-macro': 5751, 'gte-lever/direct': 49, 'gte-unsigned/direct': 3, 'instruction/direct': 1690, 'instruction/via-macro': 22, 'keepalive/direct': 2000, 'launder/direct': 4236, 'launder/via-macro': 440, 'verbatim-body/direct': 2682}
instruction mnemonics: {'addu': 966, 'move': 406, 'la': 142, 'lh': 134, 'addiu': 22, 'RTP_SND': 22, '.section': 7, 'lui': 4, 'and': 2, 'mult': 1, 'mfhi': 1, 'sll': 1, 'li': 1, 'lw': 1, 'nop': 1, 'srl': 1}
gte mnemonics: {'gte_ldv0': 653, 'swc2': 449, 'lwc2': 435, 'gte_stsxy': 361, 'gte_rtps': 356, 'gte_stflg': 319, 'gte_rtpt': 289, 'gte_stsxy3': 191, 'gte_ldrgb': 162, 'gte_ldv3c': 159, 'gte_rt': 155, 'gte_SetRotMatrix': 150, 'gte_SetTransMatrix': 145, 'gte_stsv': 145, 'gte_stlvnl': 143, 'gte_ldv3': 140, 'gte_ldIR0z': 133, 'gte_ldIRGB': 133, 'gte_dpcl': 133, 'gte_stORGB': 133, 'gte_mvmva0': 131, 'gte_stsz4': 126, 'gte_stsz3': 123, 'gte_stszotz': 113, 'gte_nclip': 96, 'gte_stopz': 96, 'gte_stsxy3c': 94, 'lw': 80, 'lhu': 70, 'mfc2': 61}
gte mnemonics: {'gte_ldv0': 653, 'swc2': 449, 'lwc2': 432, 'gte_stsxy': 361, 'gte_rtps': 356, 'gte_stflg': 319, 'gte_rtpt': 289, 'gte_stsxy3': 191, 'gte_ldrgb': 162, 'gte_ldv3c': 159, 'gte_rt': 155, 'gte_SetRotMatrix': 150, 'gte_SetTransMatrix': 145, 'gte_stsv': 145, 'gte_stlvnl': 143, 'gte_ldv3': 140, 'gte_ldIR0z': 133, 'gte_ldIRGB': 133, 'gte_dpcl': 133, 'gte_stORGB': 133, 'gte_mvmva0': 131, 'gte_stsz4': 126, 'gte_stsz3': 123, 'gte_stszotz': 113, 'gte_nclip': 96, 'gte_stopz': 96, 'gte_stsxy3c': 94, 'lhu': 66, 'mfc2': 61, 'gte_stsxy3_f3': 61}
asm-bearing macro definitions: 9,540 (338 names, 32 with >1 text) kinds {'gte': 9102, 'launder': 428, 'instruction': 9, 'barrier': 1}
controls (R39):
src/800.c func_800226C0 pins got 14 expected 45 N-A
src/shared/ov/func_80178004.h pins got 8 expected 26 N-A
ov_SC03_006 func_80184034 bare-name pins got 0 expected 3 N-A
engine_prelude.h asm sites (a macro definition only) got 0 expected 0 OK
elapsed 39.0 s
elapsed 50.8 s
File diff suppressed because it is too large Load Diff
+142
View File
@@ -0,0 +1,142 @@
{
"lw $12, 0( $4 );lw $13, 4( $4 );ctc2 $12, $0;ctc2 $13, $1;lw $12, 8( $4 );lw $13, 12( $4 );lw $14, 16( $4 );ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce48",
"lhu $12, 0( $4 );lhu $13, 6( $4 );lhu $14, 12( $4 );mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11": "00008c9406008d940c008e9400488c4800508d4800588e48",
"nop;nop;mvmva 1, 0, 3, 3, 0": "000000000000000012e0494a",
"mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0( $4 );sh $13, 6( $4 );sh $14, 12( $4 )": "00480c4800500d4800580e4800008ca406008da40c008ea4",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce48",
"lhu $12, 0($4);lhu $13, 6($4);lhu $14, 12($4);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11": "00008c9406008d940c008e9400488c4800508d4800588e48",
"nop; nop; mvmva 1, 0, 3, 3, 0": "000000000000000012e0494a",
"mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($4);sh $13, 6($4);sh $14, 12($4)": "00480c4800500d4800580e4800008ca406008da40c008ea4",
"lwc2 $0, 0( $4 );lwc2 $1, 4( $4 )": "000080c8040081c8",
"lwc2 $0, 0( $4 );lwc2 $1, 4( $4 );lwc2 $2, 0( $5 );lwc2 $3, 4( $5 );lwc2 $4, 0( $6 );lwc2 $5, 4( $6 )": "000080c8040081c80000a2c80400a3c80000c4c80400c5c8",
"nop;nop;rtps": "00000000000000000100184a",
"nop;nop;rtpt": "00000000000000003000284a",
"nop;nop;avsz4": "00000000000000002e00684b",
"swc2 $14, 0( $4 )": "00008ee8",
"swc2 $12, 0( $4 );swc2 $13, 0( $5 );swc2 $14, 0( $6 )": "00008ce80000ade80000cee8",
"swc2 $7, 0( $4 )": "000087e8",
"cfc2 $12, $31;nop;sw $12, 0( $4 )": "00f84c480000000000008cac",
"nop;nop;mvmva 1, 0, 0, 0, 0": "00000000000000001200484a",
"swc2 $25, 0( $4 );swc2 $26, 4( $4 );swc2 $27, 8( $4 )": "000099e804009ae808009be8",
"lwc2 $6, 0( $4 )": "000086c8",
"nop;nop;nccs": "00000000000000001b04084b",
"swc2 $22, 0( $4 )": "000096e8",
"lw $12, 20( $4 );lw $13, 24( $4 );ctc2 $12, $5;lw $14, 28( $4 );ctc2 $13, $6;ctc2 $14, $7": "14008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"lwc2 $0, 0( $4 );lwc2 $1, 4( $4 );lwc2 $2, 0( $5 );lwc2 $3, 4( $5 );lwc2 $4, 0( $5 );lwc2 $5, 4( $5 )": "000080c8040081c80000a2c80400a3c80000a4c80400a5c8",
"swc2 $12, 0( $4 )": "00008ce8",
"swc2 $13, 0( $4 )": "00008de8",
"swc2 $19, 0( $4 )": "000093e8",
"mfc2 $12, $19;nop;sra $12, $12, 2;sw $12, 0( $4 )": "00980c480000000083600c0000008cac",
"lwc2 $0, 0( $5 );lwc2 $1, 4( $5 );nop;nop;mvmva 1, 0, 0, 3, 0;swc2 $25, 0( $4 );swc2 $26, 4( $4 );swc2 $27, 8( $4 )": "0000a0c80400a1c800000000000000001260484a000099e804009ae808009be8",
"nop;nop;mvmva 1, 0, 0, 3, 0": "00000000000000001260484a",
"lhu $13, 4( $4 );lhu $12, 0( $4 );sll $13, $13, 16;or $12, $12, $13;mtc2 $12, $0;lwc2 $1, 8( $4 )": "04008d9400008c94006c0d0025608d0100008c48080081c8",
"lb $13, 1( $4 );lb $12, 0( $4 );sll $13, $13, 16;sll $12, $12, 16;srl $12, $12, 16;or $12, $12, $13;lb $13, 2( $4 );mtc2 $12, $0;mtc2 $13, $1": "01008d8000008c80006c0d0000640c0002640c0025608d0102008d8000008c4800088d48",
"lwc2 $0, 0($4)\nlwc2 $1, 4($4)\n": "000080c8040081c8",
"nop\nnop\nmvmva 1, 0, 0, 0, 0\n": "00000000000000001200484a",
"swc2 $25, 0($4)\nswc2 $26, 4($4)\nswc2 $27, 8($4)\n": "000099e804009ae808009be8",
"cfc2 $12, $31\nnop\nsw $12, 0($4)\n": "00f84c480000000000008cac",
"nop;nop;nclip": "00000000000000000600404b",
"swc2 $24, 0( $4 )": "000098e8",
"lw $12, 20($4);lw $13, 24($4);ctc2 $12, $5;lw $14, 28($4);ctc2 $13, $6;ctc2 $14, $7": "14008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"swc2 $12, 8( $4 );swc2 $13, 12( $4 );swc2 $14, 16( $4 )": "08008ce80c008de810008ee8",
"nop;nop;avsz3": "00000000000000002d00584b",
"cfc2 $12, $31;nop;sw $12, 0( $4 )": "00f84c480000000000008cac",
"swc2 $12, 8( $4 );swc2 $13, 16( $4 );swc2 $14, 24( $4 )": "08008ce810008de818008ee8",
"swc2 $12, 8( $4 );swc2 $13, 16( $4 );swc2 $14, 24( $4 )": "08008ce810008de818008ee8",
"nop;nop;ncct": "00000000000000003f04184b",
"swc2 $17, 0( $4 );swc2 $18, 0( $5 );swc2 $19, 0( $6 )": "000091e80000b2e80000d3e8",
"swc2 $20, 4( $4 );swc2 $21, 12( $4 );swc2 $22, 20( $4 )": "040094e80c0095e8140096e8",
"lwc2 $0, 0($4);lwc2 $1, 4($4)": "000080c8040081c8",
"lwc2 $0, 0($4);lwc2 $1, 4($4);lwc2 $2, 0($5);lwc2 $3, 4($5);lwc2 $4, 0($6);lwc2 $5, 4($6)": "000080c8040081c80000a2c80400a3c80000c4c80400c5c8",
"lwc2 $6, 0($4)": "000086c8",
"nop;nop;cop2 0x0280030": "00000000000000003000284a",
"nop;nop;cop2 0x0180001": "00000000000000000100184a",
"nop;nop;cop2 0x1400006": "00000000000000000600404b",
"nop;nop;cop2 0x168002e": "00000000000000002e00684b",
"nop;nop;cop2 0x118043f": "00000000000000003f04184b",
"nop;nop;cop2 0x108041b": "00000000000000001b04084b",
"cfc2 $12, $31;nop;sw $12, 0($4)": "00f84c480000000000008cac",
"swc2 $24, 0($4)": "000098e8",
"swc2 $7, 0($4)": "000087e8",
"swc2 $14, 0($4)": "00008ee8",
"swc2 $22, 0($4)": "000096e8",
"swc2 $12, 8($4);swc2 $13, 16($4);swc2 $14, 24($4)": "08008ce810008de818008ee8",
"swc2 $20, 4($4);swc2 $21, 12($4);swc2 $22, 20($4)": "040094e80c0095e8140096e8",
"swc2 $16, 0($4);swc2 $17, 0($5);swc2 $18, 0($6);swc2 $19, 0($7)": "000090e80000b1e80000d2e80000f3e8",
"nop;nop;.word 0x4A280030": "00000000000000003000284a",
"nop;nop;.word 0x4B400006": "00000000000000000600404b",
"nop;nop;.word 0x4B58002D": "00000000000000002d00584b",
"nop;nop;.word 0x4B08041B": "00000000000000001b04084b",
"swc2 $16, 0( $4 );swc2 $17, 0( $5 );swc2 $18, 0( $6 );swc2 $19, 0( $7 )": "000090e80000b1e80000d2e80000f3e8",
"swc2 $20, 0( $4 );swc2 $21, 0( $5 );swc2 $22, 0( $6 )": "000094e80000b5e80000d6e8",
"swc2 $12, 8( $4 );swc2 $13, 20( $4 );swc2 $14, 32( $4 )": "08008ce814008de820008ee8",
"nop;nop;.word 0x4a180001": "00000000000000000100184a",
"nop;nop;.word 0x4a280030": "00000000000000003000284a",
"nop;nop;.word 0x4b400006": "00000000000000000600404b",
"nop;nop;.word 0x4b68002e": "00000000000000002e00684b",
"mfc2 $12, $9\nmfc2 $13, $10\nmfc2 $14, $11\nsh $12, 0($4)\nsh $13, 2($4)\nsh $14, 4($4)\n": "00480c4800500d4800580e4800008ca402008da404008ea4",
"lw $12, 0($4)\nlw $13, 4($4)\nctc2 $12, $0\nctc2 $13, $1\nlw $12, 8($4)\nlw $13, 12($4)\nlw $14, 16($4)\nctc2 $12, $2\nctc2 $13, $3\nctc2 $14, $4\n": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce48",
"nop\nnop\nmvmva 1, 0, 0, 3, 0\n": "00000000000000001260484a",
"lwc2 $0, 0($4)\nlwc2 $1, 4($4)": "000080c8040081c8",
"nop\nnop\nrtps": "00000000000000000100184a",
"lw $12, 20($4)\nlw $13, 24($4)\nctc2 $12, $5\nlw $14, 28($4)\nctc2 $13, $6\nctc2 $14, $7\n": "14008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"lhu $12, 0($4)\nlhu $13, 6($4)\nlhu $14, 12($4)\nmtc2 $12, $9\nmtc2 $13, $10\nmtc2 $14, $11\nnop\nnop\nmvmva 1, 0, 3, 3, 0\nmfc2 $12, $9\nmfc2 $13, $10\nmfc2 $14, $11\nsh $12, 0($4)\nsh $13, 6($4)\nsh $14, 12($4)\n": "00008c9406008d940c008e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800008ca406008da40c008ea4",
"lhu $13, 4($4)\nlhu $12, 0($4)\nsll $13, $13, 16\nor $12, $12, $13\nmtc2 $12, $0\nlwc2 $1, 8($4)\nnop\nnop\nmvmva 1, 0, 0, 0, 0\nswc2 $25, 0($4)\nswc2 $26, 4($4)\nswc2 $27, 8($4)\n": "04008d9400008c94006c0d0025608d0100008c48080081c800000000000000001200484a000099e804009ae808009be8",
"lwc2 $0, 0($4)\nlwc2 $1, 4($4)\nlwc2 $2, 0($5)\nlwc2 $3, 4($5)\nlwc2 $4, 0($6)\nlwc2 $5, 4($6)\n": "000080c8040081c80000a2c80400a3c80000c4c80400c5c8",
"nop\nnop\nrtpt": "00000000000000003000284a",
"nop\nnop\navsz4": "00000000000000002e00684b",
"swc2 $12, 0($4)\nswc2 $13, 0($5)\nswc2 $14, 0($6)": "00008ce80000ade80000cee8",
"cfc2 $12, $31\nnop\nsw $12, 0($4)": "00f84c480000000000008cac",
"mtc2 $0, $8": "00408048",
"lwc2 $28, 0($4)": "00009cc8",
"nop\n\nnop\n\ndpcl": "00000000000000002900684a",
"swc2 $29, 0($4)": "00009de8",
"lwc2 $0, 0( $4 );lwc2 $1, 4( $4 );lwc2 $2, 8( $4 );lwc2 $3, 12( $4 );lwc2 $4, 16( $4 );lwc2 $5, 20( $4 )": "000080c8040081c8080082c80c0083c8100084c8140085c8",
"swc2 $12, 0( $4 );swc2 $13, 4( $4 );swc2 $14, 8( $4 )": "00008ce804008de808008ee8",
"swc2 $12, 0( $4 );swc2 $13, 0( $5 )": "00008ce80000ade8",
"nop;nop;.word 0x4A480012": "00000000000000001200484a",
"lw $12, 0( $4 );lw $13, 4( $4 );ctc2 $12, $0;ctc2 $13, $1;lw $12, 8( $4 );lw $13, 12( $4 );lw $14, 16( $4 );ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;lw $12, 20( $4 );lw $13, 24( $4 );ctc2 $12, $5;lw $14, 28( $4 );ctc2 $13, $6;ctc2 $14, $7": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce4814008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"lhu $12, 0( $4 );lhu $13, 6( $4 );lhu $14, 12( $4 );mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0( $4 );sh $13, 6( $4 );sh $14, 12( $4 )": "00008c9406008d940c008e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800008ca406008da40c008ea4",
"lhu $13, 4( $4 );lhu $12, 0( $4 );sll $13, $13, 16;or $12, $12, $13;mtc2 $12, $0;lwc2 $1, 8( $4 );nop;nop;mvmva 1, 0, 0, 0, 0;swc2 $25, 0( $4 );swc2 $26, 4( $4 );swc2 $27, 8( $4 )": "04008d9400008c94006c0d0025608d0100008c48080081c800000000000000001200484a000099e804009ae808009be8",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;addiu $2, $sp, 16;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($4);sh $13, 6($4);sh $14, 12($4);addiu $2, $sp, 18;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $4, 2;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 20;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $4, 4;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2)": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce481000a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800008ca406008da40c008ea41200a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a0200822400480c4800500d4800580e4800004ca406004da40c004ea41400a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a0400822400480c4800500d4800580e4800004ca406004da40c004ea4",
"lwc2 $9, 0($4)\n\nlwc2 $10, 4($4)\n\nlwc2 $11, 8($4)": "000089c804008ac808008bc8",
"nop\n\nnop\n\nsqr 0": "00000000000000002804a04a",
"swc2 $25, 0($4)\n\nswc2 $26, 4($4)\n\nswc2 $27, 8($4)": "000099e804009ae808009be8",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;lhu $12, 0($5);lhu $13, 6($5);lhu $14, 12($5);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($4);sh $13, 6($4);sh $14, 12($4);addiu $2, $sp, 50;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 18;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 52;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 20;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2)": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce480000ac940600ad940c00ae9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800008ca406008da40c008ea43200a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1200a22700480c4800500d4800580e4800004ca406004da40c004ea43400a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1400a22700480c4800500d4800580e4800004ca406004da40c004ea4",
"lw $12, 0($5);lw $13, 4($5);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($5);lw $13, 12($5);lw $14, 16($5);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;lhu $12, 0($4);lhu $13, 6($4);lhu $14, 12($4);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($5);sh $13, 6($5);sh $14, 12($5);addiu $2, $sp, 50;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 18;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 52;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 20;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2)": "0000ac8c0400ad8c0000cc480008cd480800ac8c0c00ad8c1000ae8c0010cc480018cd480020ce4800008c9406008d940c008e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e480000aca40600ada40c00aea43200a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1200a22700480c4800500d4800580e4800004ca406004da40c004ea43400a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1400a22700480c4800500d4800580e4800004ca406004da40c004ea4",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce48",
"lhu $12, 0($4);lhu $13, 6($4);lhu $14, 12($4);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;": "00008c9406008d940c008e9400488c4800508d4800588e48",
"mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($4);sh $13, 6($4);sh $14, 12($4);": "00480c4800500d4800580e4800008ca406008da40c008ea4",
".set\nnoreorder\nlw $12, 0($4)\nlw $13, 4($4)\nctc2 $12, $0\nctc2 $13, $1\nlw $12, 8($4)\nlw $13, 12($4)\nlw $14, 16($4)\nctc2 $12, $2\nctc2 $13, $3\nctc2 $14, $4\nlwc2 $0, 0($5)\nlwc2 $1, 4($5)\nnop\nnop\nmvmva 1, 0, 0, 3, 0\nswc2 $25, 0($6)\nswc2 $26, 4($6)\nswc2 $27, 8($6)\n.set\nreorder\n": "ERROR: {standard input}:36: Error: unrecognized opcode `reorder'",
"swc2 $8, 0( $4 )": "000088e8",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;addiu $2, $sp, 0x30;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($4);sh $13, 6($4);sh $14, 12($4);addiu $2, $sp, 0x32;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 0x12;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 0x34;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $sp, 0x14;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce483000a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800008ca406008da40c008ea43200a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1200a22700480c4800500d4800580e4800004ca406004da40c004ea43400a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a1400a22700480c4800500d4800580e4800004ca406004da40c004ea4",
"lw $12, 0($4);lw $13, 4($4);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($4);lw $13, 12($4);lw $14, 16($4);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;lw $12, 20($4);lw $13, 24($4);ctc2 $12, $5;lw $14, 28($4);ctc2 $13, $6;ctc2 $14, $7": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce4814008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"lhu $12, 0($4)\nlhu $13, 6($4)\nlhu $14, 12($4)\nmtc2 $12, $9\nmtc2 $13, $10\nmtc2 $14, $11\nnop\nnop\nmvmva 1, 0, 3, 3, 0\n": "00008c9406008d940c008e9400488c4800508d4800588e48000000000000000012e0494a",
"mfc2 $12, $9\nmfc2 $13, $10\nmfc2 $14, $11\nsh $12, 0($4)\nsh $13, 6($4)\nsh $14, 12($4)\n": "00480c4800500d4800580e4800008ca406008da40c008ea4",
"lhu $13, 4($4)\nlhu $12, 0($4)\nsll $13, $13, 16\nor $12, $12, $13\nmtc2 $12, $0\nlwc2 $1, 8($4)\nnop\nnop\nmvmva 1, 0, 0, 0, 0\n": "04008d9400008c94006c0d0025608d0100008c48080081c800000000000000001200484a",
"swc2 $8, 0($4)": "000088e8",
"mfc2 $12, $19;nop;sra $12, $12, 2;sw $12, 0($4)": "00980c480000000083600c0000008cac",
"lhu $12, 0( $4 );lhu $13, 6( $4 );lhu $14, 12( $4 );mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0": "00008c9406008d940c008e9400488c4800508d4800588e48000000000000000012e0494a",
"lwc2 $9, 0($4)\nlwc2 $10, 4($4)\nlwc2 $11, 8($4)\nnop\nnop\nsqr 0\n": "000089c804008ac808008bc800000000000000002804a04a",
"lwc2 $0, 0($4)\nlwc2 $1, 4($4)\nnop\nnop\nmvmva 1, 0, 0, 0, 0\n": "000080c8040081c800000000000000001200484a",
"lwc2 $0, 0($4)\nlwc2 $1, 4($4)\nnop\nnop\nmvmva 1, 0, 0, 3, 0\n": "000080c8040081c800000000000000001260484a",
"lw $12, 0($4)\nlw $13, 4($4)\nctc2 $12, $0\nctc2 $13, $1\nlw $12, 8($4)\nlw $13, 12($4)\nlw $14, 16($4)\nctc2 $12, $2\nctc2 $13, $3\nctc2 $14, $4\nlw $12, 20($4)\nlw $13, 24($4)\nctc2 $12, $5\nlw $14, 28($4)\nctc2 $13, $6\nctc2 $14, $7\n": "00008c8c04008d8c0000cc480008cd4808008c8c0c008d8c10008e8c0010cc480018cd480020ce4814008c8c18008d8c0028cc481c008e8c0030cd480038ce48",
"lwc2 $0, 0($4)\n\nlwc2 $1, 4($4)": "000080c8040081c8",
"nop\n\nnop\n\nrtps": "00000000000000000100184a",
"cfc2 $12, $31\n\nnop\n\nsw $12, 0($4)": "00f84c480000000000008cac",
"lw $12, 0($2);lw $13, 4($2);ctc2 $12, $0;ctc2 $13, $1;lw $12, 8($2);lw $13, 12($2);lw $14, 16($2);ctc2 $12, $2;ctc2 $13, $3;ctc2 $14, $4;addiu $2, $sp, 0x10;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 0x12;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $2, 0x2;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2);addiu $2, $sp, 0x14;lhu $12, 0($2);lhu $13, 6($2);lhu $14, 12($2);mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $11;nop;nop;mvmva 1, 0, 3, 3, 0;addiu $2, $2, 4;mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0($2);sh $13, 6($2);sh $14, 12($2)": "00004c8c04004d8c0000cc480008cd4808004c8c0c004d8c10004e8c0010cc480018cd480020ce481000a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a00480c4800500d4800580e4800004ca406004da40c004ea41200a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a0200422400480c4800500d4800580e4800004ca406004da40c004ea41400a22700004c9406004d940c004e9400488c4800508d4800588e48000000000000000012e0494a0400422400480c4800500d4800580e4800004ca406004da40c004ea4",
"lui $2,%%hi(D_800AF648);addiu $2,$2,%%lo(D_800AF648);lw $12,0($2);lw $13,4($2);ctc2 $12,$0;ctc2 $13,$1;lw $12,8($2);lw $13,12($2);lw $14,16($2);ctc2 $12,$2;ctc2 $13,$3;ctc2 $14,$4;lw $12,20($2);lw $13,24($2);ctc2 $12,$5;lw $14,28($2);ctc2 $13,$6;ctc2 $14,$7": "ERROR: {standard input}:10: Error: operand 3 must be constant `addiu $2,$2,%%lo(D_800AF648)'",
"lwc2 $0,0($4);lwc2 $1,4($4);nop;nop;rtps;swc2 $14,0($4)": "000080c8040081c800000000000000000100184a00008ee8",
"cfc2 $12,$31;nop;sw $12,0($4)": "00f84c480000000000008cac",
"lwc2 $9, 0($sp)\nlwc2 $10, 4($sp)\nlwc2 $11, 8($sp)\nnop\nnop\nsqr 0\n": "0000a9cb0400aacb0800abcb00000000000000002804a04a",
"swc2 $25, 0($sp)\nswc2 $26, 4($sp)\nswc2 $27, 8($sp)\n": "0000b9eb0400baeb0800bbeb",
"lw $2, 0(%3);lw $3, 4(%3);ctc2 $2, $0;ctc2 $3, $1;lw $2, 8(%3);lw $3, 12(%3);lw $4, 16(%3);ctc2 $2, $2;ctc2 $3, $3;ctc2 $4, $4;lw $2, 20(%3);lw $3, 24(%3);ctc2 $2, $5;lw $4, 28(%3);ctc2 $3, $6;ctc2 $4, $7": "ERROR: {standard input}:10: Error: invalid operands `lw $4,28(%3)'",
"lwc2 $0, 0($4);lwc2 $1, 4($4);nop;nop;rtps": "000080c8040081c800000000000000000100184a",
"cfc2 $2, $31; nop; sw $2, 0($4)": "00f8424800000000000082ac",
"lwc2 $9, 0( $4 );lwc2 $10, 4( $4 );lwc2 $11, 8( $4 );nop;nop;sqr 0;swc2 $25, 0( $4 );swc2 $26, 4( $4 );swc2 $27, 8( $4 )": "000089c804008ac808008bc800000000000000002804a04a000099e804009ae808009be8",
"nop\nnop\nmvmva 1, 0, 0, 3, 0": "00000000000000001260484a",
"mfc2 $12, $9\nmfc2 $13, $10\nmfc2 $14, $11\nsh $12, 0($4)\nsh $13, 2($4)\nsh $14, 4($4)": "00480c4800500d4800580e4800008ca402008da404008ea4",
"swc2 $19, 0($4)": "000093e8",
"mfc2 $12, $19\nnop\nsra $12, $12, 2\nsw $12, 0($4)\n": "00980c480000000083600c0000008cac",
"lwc2 $9, 0($4)\nlwc2 $10, 4($4)\nlwc2 $11, 8($4)\nnop\nnop\nsqr 1\n": "000089c804008ac808008bc800000000000000002804a84a"
}
+1
View File
@@ -71,6 +71,7 @@ tools/dedup_integrate.py P6 ADAPT validate a code-share registry byte-honestly,
tools/dedup_propagate.py P6 ADAPT lift one matched body into a shared macro and instantiate it everywhere Lifts one matched body into a shared macro and instantiates it at every duplicate site repo shared-header/src layout SUPERSEDED tools/share_body.py
tools/delever.py P10 ADAPT take register pins, asm statements, volatile and register levers out of matched C, byte-gated per site The Phase-36 de-lever engine and campaign tool: positional rewrites per lever class (pin -> plain declaration, barrier/keep-alive deleted, a launder deleted or turned into the assignment it is, a hand-placed instruction -> its C, the zero-register variable -> 0, a macro-carried site deleted/valued/refused by its macro's shape, volatile/register dropped); per body replay -> rung A strip-all -> rung B greedy through delever_oracle; the file is the write unit and its final compile through every recipe the proof; --plan/--apply batches (TUs parallel, exemplar files first; headers serial, includers parallel), the ledger keyed by tu+fn+addr with the body's nhash before/after, !FAKE markers on class A/B survivors, --redraw for bodies refused by an older tool, --restore from inflight.json, --scrub for orphan markers, --apply-body for T6/T7, --selftest with a stub oracle, --probe (T2) the census site records, the oracle LIVE
tools/delever_oracle.py P10 ADAPT judge one translation-unit edit by the bytes of its object in under a second The Phase-36 fast byte oracle: every object's exact build command captured once via make -n -W (the Makefile's own recipe, pad stage and -O0 overrides included), a candidate compiled in place with -o/-MF redirected to scratch and compared with the fleet run's object; --calibrate proves 100 % equality untouched, twin == primary and a positive control before any verdict is trusted the Makefile's object rules, the twin rule LIVE
tools/gte_consolidate.py P10 ADAPT consolidate the GTE coprocessor inline-asm macros under Sony's names in one header and sweep dead lever macros The Phase-36 T5 tool: every asm-bearing macro definition and direct GTE statement SIGNED by the build's own maspsx→as tail (template bytes with operands bound to fixed registers, operand counts, clobbers); one canonical text per signature named by PsyQ's inline_c.h convention (Sony's file supplies names and clobber lists, never opcode words) written to include/gte_inline.h; per file the canonical duplicates deleted, private names renamed, a variant with extra clobbers tried as canonical and kept as a marked <name>_m lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two included); every file judged through every recipe; --sweep deletes dead asm-bearing macros and drops a compound macro's inner asm when byte-neutral Sony's inline_c.h path, the census sites, the oracle LIVE
tools/demacroize.py P6 ADAPT escape a shared-header declaration conflict locally by unmacroizing one body Per-binary local escape from a shared-header declaration conflict by unmacroizing one body repo shared-header layout FROZEN tools/share_body.py
tools/diff_autopsy.sh P7 ADAPT find which words diverge when the whole-binary gate says different but not where Reproduces exactly what the gate saw, then decodes the diverging words and restores the tree repo src/build paths STILL-NEEDED find WHICH words diverge when the whole-binary gate says DIFF but not where
tools/diff_regions.py P7 ADAPT classify where a remapped sibling's compiled bytes diverge from its target Classifies where a remapped member's compiled bytes diverge from its target repo build paths STILL-NEEDED classify where a mechanically remapped sibling's compiled bytes diverge from its target
1 path phase portability need what adapts status successor_or_product
71 tools/dedup_propagate.py P6 ADAPT lift one matched body into a shared macro and instantiate it everywhere Lifts one matched body into a shared macro and instantiates it at every duplicate site repo shared-header/src layout SUPERSEDED tools/share_body.py
72 tools/delever.py P10 ADAPT take register pins, asm statements, volatile and register levers out of matched C, byte-gated per site The Phase-36 de-lever engine and campaign tool: positional rewrites per lever class (pin -> plain declaration, barrier/keep-alive deleted, a launder deleted or turned into the assignment it is, a hand-placed instruction -> its C, the zero-register variable -> 0, a macro-carried site deleted/valued/refused by its macro's shape, volatile/register dropped); per body replay -> rung A strip-all -> rung B greedy through delever_oracle; the file is the write unit and its final compile through every recipe the proof; --plan/--apply batches (TUs parallel, exemplar files first; headers serial, includers parallel), the ledger keyed by tu+fn+addr with the body's nhash before/after, !FAKE markers on class A/B survivors, --redraw for bodies refused by an older tool, --restore from inflight.json, --scrub for orphan markers, --apply-body for T6/T7, --selftest with a stub oracle, --probe (T2) the census site records, the oracle LIVE
73 tools/delever_oracle.py P10 ADAPT judge one translation-unit edit by the bytes of its object in under a second The Phase-36 fast byte oracle: every object's exact build command captured once via make -n -W (the Makefile's own recipe, pad stage and -O0 overrides included), a candidate compiled in place with -o/-MF redirected to scratch and compared with the fleet run's object; --calibrate proves 100 % equality untouched, twin == primary and a positive control before any verdict is trusted the Makefile's object rules, the twin rule LIVE
74 tools/gte_consolidate.py P10 ADAPT consolidate the GTE coprocessor inline-asm macros under Sony's names in one header and sweep dead lever macros The Phase-36 T5 tool: every asm-bearing macro definition and direct GTE statement SIGNED by the build's own maspsx→as tail (template bytes with operands bound to fixed registers, operand counts, clobbers); one canonical text per signature named by PsyQ's inline_c.h convention (Sony's file supplies names and clobber lists, never opcode words) written to include/gte_inline.h; per file the canonical duplicates deleted, private names renamed, a variant with extra clobbers tried as canonical and kept as a marked <name>_m lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two included); every file judged through every recipe; --sweep deletes dead asm-bearing macros and drops a compound macro's inner asm when byte-neutral Sony's inline_c.h path, the census sites, the oracle LIVE
75 tools/demacroize.py P6 ADAPT escape a shared-header declaration conflict locally by unmacroizing one body Per-binary local escape from a shared-header declaration conflict by unmacroizing one body repo shared-header layout FROZEN tools/share_body.py
76 tools/diff_autopsy.sh P7 ADAPT find which words diverge when the whole-binary gate says different but not where Reproduces exactly what the gate saw, then decodes the diverging words and restores the tree repo src/build paths STILL-NEEDED find WHICH words diverge when the whole-binary gate says DIFF but not where
77 tools/diff_regions.py P7 ADAPT classify where a remapped sibling's compiled bytes diverge from its target Classifies where a remapped member's compiled bytes diverge from its target repo build paths STILL-NEEDED classify where a mechanically remapped sibling's compiled bytes diverge from its target
+3 -2
View File
@@ -8,8 +8,8 @@
> its platform SDK need the marked adaptation. The last table lists the tools that are project-only in code (their *shape* is a task;
> their code does not transfer). *TODO(platform): the MIPS and PlayStation SDK hard-codes are the ones another platform replaces first.*
>
> **Coverage:** 299 tool files in scope (submodules, vendored and downloaded code excluded), of which 299 live rows
> below; per phase: P1 2 · P2 26 · P3 17 · P4 9 · P5 26 · P6 51 · P7 20 · P8 86 · P9 27 · P10 19 · PROJECT-ONLY 16. Superseded tools appear only as pointers to their successor (30 pointer rows); one-offs are omitted. Table rows in all: 329 (the installer checks its copy against this figure).
> **Coverage:** 300 tool files in scope (submodules, vendored and downloaded code excluded), of which 300 live rows
> below; per phase: P1 2 · P2 26 · P3 17 · P4 9 · P5 26 · P6 51 · P7 20 · P8 86 · P9 27 · P10 20 · PROJECT-ONLY 16. Superseded tools appear only as pointers to their successor (30 pointer rows); one-offs are omitted. Table rows in all: 330 (the installer checks its copy against this figure).
## P1 — extraction + manifest
@@ -327,6 +327,7 @@
| `cast_call_sites.py` | add per-site function-pointer casts so a draft can call a differently typed callee | Adds per-site function-pointer casts so a draft can call a differently typed callee | repo src layout |
| `ghidra_scripts/ApplySymbols.java` | apply curated names and signatures inside the analysis tool and save | The in-tool half of that mirror: apply curated names and signatures, save on exit | none |
| `lever_census.py` | census every compiler-forcing construct in the C and gate the levers-off phase | The Phase-36 census of lever sites (register pins, asm statements by kind, volatile levers, bare register, asm-label aliases, builtins, attributes; the whole-body hand-asm routines and the GTE ops set apart) derived from the shared-body scanner with a per-token coverage assertion against the raw text, four known-true controls, the !FAKE marker split, --check (0 unmarked pins/asm, 0 orphan markers) and --strict (0 pins, 0 asm) gates, --sites for the delever ledger, a fixture self-test | repo paths, the shared-body scanner |
| `gte_consolidate.py` | consolidate the GTE coprocessor inline-asm macros under Sony's names in one header and sweep dead lever macros | The Phase-36 T5 tool: every asm-bearing macro definition and direct GTE statement SIGNED by the build's own maspsx→as tail (template bytes with operands bound to fixed registers, operand counts, clobbers); one canonical text per signature named by PsyQ's inline_c.h convention (Sony's file supplies names and clobber lists, never opcode words) written to include/gte_inline.h; per file the canonical duplicates deleted, private names renamed, a variant with extra clobbers tried as canonical and kept as a marked <name>_m lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two included); every file judged through every recipe; --sweep deletes dead asm-bearing macros and drops a compound macro's inner asm when byte-neutral | Sony's inline_c.h path, the census sites, the oracle |
| `macro_to_header.py` | convert a shared-body macro header into per-function plain-C headers included at each site | The Phase-35 converter: every DEFINE_ macro body becomes a plain-C header under src/shared/<space>/ included at its site, the legacy name-parameterized headers converted, the registry text-edited, the macro header deleted; --plan / --apply / --finalize / --verify | the macro form is this project's; a kit-born project shares headers from its first bank |
| `asm_verbatim.py` | emit a function's target assembly as an inline-assembly body | Emits the file-scope inline-assembly body form from a disassembly file | repo asm layout |
| `build_engine_types.py` | extract inline-defined types and typedefs from a source file into a shared header | Extracts inline-defined named types and typedefs from a source file into a shared header | repo shared-header path |
@@ -9,7 +9,7 @@
# and src/ + the ledger/batch record/log + the census + the phase log are committed (R42).
# Stops on the first red (a non-zero tool exit, a final N/M with N != M, a fleet run that is not 218/218) — the batch's files are then
# still in place: inspect, then `tools/delever.py --restore` (never git checkout, R102). Stops cleanly when nothing is drawable.
# Usage: [LABEL_PREFIX=t3_] [TASK=T4] [REDRAW="REFUSED NOTHING-USABLE"] tools/delever_cycle.sh START END [BATCH=300] [MODE=tus|headers] [ONLY="alias1 alias2 …"]
# Usage: [LABEL_PREFIX=t3_] [TASK=T4] [REDRAW="REFUSED NOTHING-USABLE"] tools/delever_cycle.sh START END [BATCH=300] [MODE=tus|headers|gte] [ONLY="alias1 alias2 …"]
# RUN IT DETACHED from the Claude Code harness (its low-memory guard kills a long BACKGROUND task — batch tus3 died mid-apply, S98):
# setsid nohup bash -c 'TASK=T4 tools/delever_cycle.sh 3 12 300 tus; echo "cycle exit=$?"' > .run/P36/delever/cycle_<x>.log 2>&1 &
# and watch the log (a tiny waiter: `until grep -q 'cycle exit=' <log>; do sleep 60; done`). A killed batch: `tools/delever.py --restore`.
@@ -20,7 +20,12 @@ set -o pipefail
cd "$(dirname "$0")/.." || exit 2
START=$1; END=$2; BATCH=${3:-300}; MODE=${4:-tus}; ONLY=${5:-}
[ -n "$START" ] && [ -n "$END" ] || { echo "usage: $0 START END [BATCH] [tus|headers] [ONLY]"; exit 2; }
case "$MODE" in tus) HFLAG="";; headers) HFLAG="--headers";; *) echo "MODE must be tus or headers"; exit 2;; esac
case "$MODE" in
tus) HFLAG=""; TOOL="tools/delever.py --apply"; VPAT='delever: batch .*';;
headers) HFLAG="--headers"; TOOL="tools/delever.py --apply"; VPAT='delever: batch .*';;
gte) HFLAG=""; TOOL="tools/gte_consolidate.py --apply"; VPAT='gte_consolidate: batch .*';; # T5: the GTE consolidation per file
*) echo "MODE must be tus, headers or gte"; exit 2;;
esac
ONLYFLAG=""; [ -n "$ONLY" ] && ONLYFLAG="--only $ONLY"
REDRAWFLAG=""; [ -n "${REDRAW:-}" ] && REDRAWFLAG="--redraw $REDRAW" # e.g. REDRAW="REFUSED NOTHING-USABLE" after a tool fix
FLEET=$(ls config/check.*.sha | wc -l)
@@ -41,11 +46,11 @@ for k in $(seq "$START" "$END"); do
[ -z "$(git status --short --porcelain src | grep '^??')" ] || { echo "cycle: untracked files under src/ (a dotfile probe?) — sweep them first"; exit 3; }
$PY tools/delever_oracle.py --status >/dev/null 2>&1 || $PY tools/delever_oracle.py --calibrate ov_SC04_011 ov_SC03_015 ov_SC03_014 main -j 16 > .run/P36/delever/calibrate_${label}.log 2>&1 \
|| { echo "cycle: calibration FAILED"; tail -5 .run/P36/delever/calibrate_${label}.log; exit 1; }
{ /usr/bin/time -f "$label wall=%e s" $PY tools/delever.py --apply --batch "$BATCH" --label "$label" $HFLAG $ONLYFLAG $REDRAWFLAG -j 12; echo "exit=$?"; } > "$log" 2>&1
{ /usr/bin/time -f "$label wall=%e s" $PY $TOOL --batch "$BATCH" --label "$label" $HFLAG $ONLYFLAG $REDRAWFLAG -j 12; echo "exit=$?"; } > "$log" 2>&1
fi
grep -q '^exit=0$' "$log" || { echo "cycle: batch $label — delever exited non-zero"; tail -4 "$log"; exit 1; }
if grep -q 'nothing to do (no drawable file)' "$log"; then echo "cycle: nothing drawable at $label — done"; exit 0; fi
vline=$(grep -oE 'delever: batch .*' "$log" | tail -1)
if grep -qE 'nothing to do' "$log"; then echo "cycle: nothing drawable at $label — done"; exit 0; fi
vline=$(grep -oE "$VPAT" "$log" | tail -1)
[ -n "$vline" ] || { echo "cycle: batch $label — no verify line in $log (R32: an empty line is not a pass)"; tail -3 "$log"; exit 1; }
final=$(echo "$vline" | grep -oE 'final [0-9]+/[0-9]+' | grep -oE '[0-9]+/[0-9]+')
[ -n "$final" ] && [ "${final%/*}" = "${final#*/}" ] || { echo "cycle: batch $label — $vline"; exit 1; }
@@ -0,0 +1,922 @@
#!/usr/bin/env python3
"""gte_consolidate.py — the GTE coprocessor asm consolidated under Sony's names, and the dead lever-macro sweep (Phase 36 T5).
tools/gte_consolidate.py --inventory # every asm-bearing macro definition + every direct GTE statement, SIGNED by the
# build's own maspsx→as tail; the canonical table → .run/P36/gte/{inventory,canonical}.json
tools/gte_consolidate.py --header # write include/gte_inline.h (one canonical macro per signature) + the include in common.h
tools/gte_consolidate.py --apply --batch N --label L [--only …] [-j 12]
# per file: canonical-duplicate definitions deleted, private/variant names renamed, direct
# statements rewritten into canonical calls; every file judged through every recipe
tools/gte_consolidate.py --sweep --label L [--only …] # dead asm-bearing macros deleted; a compound macro's inner asm dropped when byte-neutral
tools/gte_consolidate.py --status | --selftest
A SIGNATURE is what the assembler makes of a template, not how it is spelled: the template with its operands bound to fixed registers
(%0.. → $4 $5 $6 $7 for inputs, $2 $3 for outputs) is assembled through the SAME tail as the build (maspsx --aspsx-version=2.56 →
mipsel-linux-gnu-as with include/macro.inc + include/gte_macros.inc, where `rtps`, `mvmva`, `sqr 0` are GAS macros), and the signature
is (bytes, #outputs, #inputs). `nop;nop;rtps`, `nop;nop;.word 0x4a180001` and `nop;nop;cop2 0x0180001` are one signature. Sony's
converted inline_c.h is the source of NAMES and of operand/clobber lists only — its `.word` opcode spellings assemble to nothing
useful (`gte_rtps` there is `.word 0x0000007f`).
THE CANONICAL TABLE: definitions grouped by signature; the canonical CLOBBER set of a signature is Sony's for that name (a store macro
legitimately clobbers "memory"), else the majority's; the canonical NAME is the most-used `gte_*` name in the group (Sony's convention),
else a name derived from gte_macros.inc, else `gte_seq_<hash>`; the canonical TEXT is the majority definition text (byte-proven in the
fleet — the header ships nothing that was not already in the tree). A definition with the canonical signature but EXTRA clobbers (the
`"memory"` a drafter added to a load or a compute op to force the schedule) is a LEVER VARIANT: it stays per TU, renamed `<name>_m`, and
its uses carry `// !FAKE:` markers for T7. A direct statement is rewritten into `NAME(args)` (or `NAME1(args); NAME2();` for the
concatenation of two canonical macros) when bytes and clobbers agree; with extra clobbers it is a lever variant too (marked); a sequence
that matches nothing is counted and left. Every file's final text is judged through every recipe of the file (tools/delever_oracle.py);
the clean fleet run gates every batch (tools/delever_cycle.sh MODE=gte).
"""
import argparse
import collections
import hashlib
import json
import os
import pathlib
import re
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor
REPO = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "tools"))
import delever as dl # noqa: E402
import delever_oracle as oracle # noqa: E402
import lever_census as lc # noqa: E402
import share_census as sc # noqa: E402
RUN = REPO / ".run" / "P36" / "gte"
SIG_CACHE = RUN / "sig_cache.json"
INVENTORY = RUN / "inventory.json"
CANON = RUN / "canonical.json"
LEDGER = REPO / ".run" / "P36" / "delever" / "ledger.jsonl"
HEADER = "include/gte_inline.h"
COMMON = "include/common.h"
SONY = REPO / "tools/psyq/conv47/psyq-4_7-converted/include/inline_c.h"
MASPSX = ".venv/bin/python tools/maspsx/maspsx.py --aspsx-version=2.56 --expand-div"
AS = "mipsel-linux-gnu-as -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0"
FAKE = dl.FAKE
DEFINE_LINE = re.compile(r"^[ \t]*#[ \t]*define[ \t]+([A-Za-z_]\w*)(\([^)]*\))?[ \t]*(.*)$")
_LOCK = dl._LOCK
class Refuse(Exception):
pass
# ----------------------------------------------------------------------------------------------------------------------
# the signature engine
# ----------------------------------------------------------------------------------------------------------------------
def parse_asm(inner):
"""(template, outs, ins, clobbers) — delever's operand parser plus the clobber section."""
tmpl, outs, ins = dl.parse_asm_operands(inner)
s = inner.strip()
i, n = 0, len(s)
while i < n:
if s[i] in " \t\r\n":
i += 1
continue
mm = lc.STRLIT.match(s, i)
if not mm:
break
i = mm.end()
rest = s[i:].strip()
secs = lc._split_sections(rest[1:]) if rest.startswith(":") else []
clob = [m.group(1) for m in re.finditer(r'"([^"]*)"', secs[2])] if len(secs) > 2 else []
return tmpl, outs, ins, sorted(set(clob))
def bind_regs(tmpl, nout, nin):
regs = ["$2", "$3"][:nout] + ["$4", "$5", "$6", "$7"][:nin]
def sub(m):
k = int(m.group(1))
return regs[k] if k < len(regs) else m.group(0)
t = re.sub(r"%(\d+)", sub, tmpl)
t = re.sub(r"\\n|\\t", "\n", t)
return t
_sig_cache = None
def _load_cache():
global _sig_cache
if _sig_cache is None:
_sig_cache = json.loads(SIG_CACHE.read_text()) if SIG_CACHE.exists() else {}
return _sig_cache
def _save_cache():
RUN.mkdir(parents=True, exist_ok=True)
tmp = SIG_CACHE.with_suffix(".tmp")
tmp.write_text(json.dumps(_sig_cache, indent=0))
os.replace(tmp, SIG_CACHE)
def asm_bytes(bound_template):
"""hex of the .text bytes the build's tail makes of the template (cached by text); 'ERROR: …' when it does not assemble."""
cache = _load_cache()
key = bound_template
if key in cache:
return cache[key]
d = RUN / "probe"
d.mkdir(parents=True, exist_ok=True)
tag = hashlib.sha1(key.encode()).hexdigest()[:10]
s_path, o_path, b_path = d / f"{tag}.s", d / f"{tag}.o", d / f"{tag}.bin"
s_path.write_text("\t.include \"include/macro.inc\"\n\t.include \"gte_macros.inc\"\n\t.set\tnoreorder\n\t.set\tnoat\n\t.text\n\t.align\t2\n"
"\t.globl\tprobe\n\t.ent\tprobe\nprobe:\n" + bound_template + "\n\t.end\tprobe\n")
cmd = f"set -o pipefail; cat {s_path} | {MASPSX} | {AS} -o {o_path} && mipsel-linux-gnu-objcopy -O binary -j .text {o_path} {b_path}"
r = subprocess.run(["bash", "-c", cmd], cwd=REPO, capture_output=True, text=True)
if r.returncode != 0 or not b_path.exists():
res = "ERROR: " + (r.stderr.strip().splitlines() or ["?"])[-1][:120]
else:
res = b_path.read_bytes().hex()
for p in (s_path, o_path, b_path):
try:
p.unlink()
except OSError:
pass
cache[key] = res
return res
def signature(inner):
"""dict(bytes, nout, nin, clob, tmpl, ok) for the text between an asm statement's parens."""
tmpl, outs, ins, clob = parse_asm(inner)
bound = bind_regs(tmpl, len(outs), len(ins))
b = asm_bytes(bound)
return dict(bytes=b, nout=len(outs), nin=len(ins), clob=clob, tmpl=tmpl, ok=not b.startswith("ERROR"),
ins=[e for _, e in ins], outs=[e for _, e in outs])
def set_clobbers(body, clob):
"""the macro body with the asm statement's clobber section replaced by `clob` (removed when empty)."""
m_asm = lc.ASM_KW.search(body)
o = body.find("(", m_asm.end())
c = lc._paren_span(body, o)
inner = body[o + 1:c]
# the sections: template, then up to three ':'-separated parts outside string literals
s = inner
i, n = 0, len(s)
while i < n:
if s[i] in " \t\r\n":
i += 1
continue
mm = lc.STRLIT.match(s, i)
if not mm:
break
i = mm.end()
head, rest = s[:i], s[i:]
secs = lc._split_sections(rest.strip()[1:]) if rest.strip().startswith(":") else []
while len(secs) < 3:
secs.append("")
secs[2] = ", ".join(f'"{x}"' for x in clob) if clob else ""
while secs and secs[-1].strip() == "":
secs.pop()
new_inner = head.rstrip() + ("".join(" : " + x.strip() for x in secs) if secs else "") + " "
return body[:o + 1] + new_inner + body[c:]
def sig_key(s):
return f"{s['bytes']}|{s['nout']}|{s['nin']}"
# ----------------------------------------------------------------------------------------------------------------------
# the inventory
# ----------------------------------------------------------------------------------------------------------------------
def asm_inner(body_masked):
"""the text between the parens of the FIRST asm statement in a macro body (None if none / more than one)."""
ms = list(lc.ASM_KW.finditer(body_masked))
if len(ms) != 1:
return None
o = body_masked.find("(", ms[0].end())
c = lc._paren_span(body_masked, o) if o >= 0 else -1
return body_masked[o + 1:c] if (o >= 0 and c > 0) else None
def define_params(raw, l0):
m = DEFINE_LINE.match(raw.split("\n")[l0 - 1])
return (m.group(2) or "") if m else ""
def sony_table():
"""name -> (nout, nin, clobbers) from Sony's inline_c.h — the parts of it that are plain C text."""
out = {}
if not SONY.exists():
return out
raw = SONY.read_text(errors="surrogateescape")
for (l0, l1, name, body) in lc.define_blocks(raw):
inner = asm_inner(sc.mask_text(body) if "/*" in body else body)
if inner is None:
continue
try:
_, outs, ins, clob = parse_asm(inner)
except Exception: # noqa: BLE001
continue
out[name] = (len(outs), len(ins), clob)
return out
def inc_names():
"""bytes -> name for gte_macros.inc's single-op macros (assembled as `nop;nop;<macro>`), for signatures without a gte_* name."""
raw = (REPO / "include/gte_macros.inc").read_text(errors="surrogateescape")
out = {}
for m in re.finditer(r"^\.macro\s+(\w+)\s*$", raw, re.M):
b = asm_bytes(f"nop\nnop\n{m.group(1)}")
if not b.startswith("ERROR"):
out[f"{b}|0|0"] = m.group(1)
return out
def files_under_src():
return [p for p in sorted((REPO / "src").rglob("*")) if p.suffix in (".c", ".h") and not p.name.startswith(".")]
def inventory(jobs=12, quiet=False):
"""definitions: [{tu, l0, l1, name, params, body, kind, sig}], direct: [{tu, line, col, inner, sig}], uses: Counter(name)."""
sites = dl.load_sites()
uses = collections.Counter(s["via"] for s in sites if s.get("via") and s["kind"] == "gte")
direct_sites = [s for s in sites if s["kind"] == "gte" and not s.get("via")]
defs, direct = [], []
for p in files_under_src():
raw = p.read_text(errors="surrogateescape")
if "#define" not in raw and "asm" not in raw:
continue
rel = p.relative_to(REPO).as_posix()
for (l0, l1, name, body) in lc.define_blocks(raw):
kind = lc.macro_kind(body)
if not kind:
continue
d = dict(tu=rel, l0=l0, l1=l1, name=name, params=define_params(raw, l0), body=body, kind=kind, sig=None)
if kind == "gte":
inner = asm_inner(sc.mask_text(body) if ("/*" in body or "//" in body) else body)
if inner is not None:
try:
d["sig"] = signature(inner)
except Exception as ex: # noqa: BLE001
d["sig"] = dict(bytes=f"ERROR: {ex}"[:100], ok=False, nout=0, nin=0, clob=[], tmpl="", ins=[], outs=[])
defs.append(d)
by_tu = collections.defaultdict(list)
for s in direct_sites:
by_tu[s["tu"]].append(s)
for tu, ss in by_tu.items():
raw = (REPO / tu).read_text(errors="surrogateescape")
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
for s in ss:
pos = ls[s["line"] - 1] + s["col"] - 1
o = m.find("(", pos)
c = lc._paren_span(m, o) if o >= 0 else -1
if o < 0 or c < 0:
continue
inner = m[o + 1:c]
try:
sg = signature(inner)
except Exception as ex: # noqa: BLE001
sg = dict(bytes=f"ERROR: {ex}"[:100], ok=False, nout=0, nin=0, clob=[], tmpl="", ins=[], outs=[])
direct.append(dict(tu=tu, line=s["line"], col=s["col"], fn=s.get("fn"), inner=inner, sig=sg, marked=s.get("marked", False)))
_save_cache()
inv = dict(generated=time.strftime("%Y-%m-%d %H:%M"), definitions=defs, direct=direct, uses=dict(uses))
RUN.mkdir(parents=True, exist_ok=True)
INVENTORY.write_text(json.dumps(inv))
if not quiet:
gte = [d for d in defs if d["kind"] == "gte"]
print(f"gte_consolidate --inventory: {len(defs)} asm-bearing macro definitions ({len(gte)} GTE, {sum(1 for d in gte if d['sig'] and d['sig']['ok'])} signed), "
f"{len(direct)} direct GTE statements ({sum(1 for d in direct if d['sig']['ok'])} signed), {sum(uses.values())} macro-carried GTE uses")
return inv
# ----------------------------------------------------------------------------------------------------------------------
# the canonical table
# ----------------------------------------------------------------------------------------------------------------------
def canonical_table(inv, quiet=False):
sony = sony_table()
incn = inc_names()
groups = collections.defaultdict(list)
for d in inv["definitions"]:
if d["kind"] == "gte" and d["sig"] and d["sig"]["ok"]:
groups[sig_key(d["sig"])].append(d)
table, variants, unsigned = {}, {}, [d for d in inv["definitions"] if d["kind"] == "gte" and not (d["sig"] and d["sig"]["ok"])]
names_taken = set()
for key, ds in sorted(groups.items(), key=lambda kv: -len(kv[1])):
names = collections.Counter(d["name"] for d in ds)
# candidates: the names with their address / variant suffixes stripped (`gte_stsxy01_80181E04` is Sony's gte_stsxy01;
# `LDV0_DA34` is a private copy of gte_ldv0), Sony's spelling preferred, then the most-used gte_* spelling
stripped = collections.Counter()
for n, cnt in names.items():
base = re.sub(r"(_[0-9A-Fa-f]{3,8}|X|_m|_A|_B)$", "", n)
base = re.sub(r"_v[0-9a-f]{4}$", "", base)
stripped[base] += cnt
nout0, nin0 = ds[0]["sig"]["nout"], ds[0]["sig"]["nin"]
# a Sony name is taken only when the operand counts agree with Sony's macro (a private compound that borrowed the name is not it)
sony_named = [n for n, _ in stripped.most_common() if n in sony and (sony[n][0], sony[n][1]) == (nout0, nin0)]
gte_names = [n for n, _ in stripped.most_common() if n.startswith("gte_")]
name = (sony_named or gte_names or [None])[0]
if name is None:
base = incn.get(key)
priv = stripped.most_common(1)[0][0]
name = f"gte_{base}" if base else f"gte_{re.sub(r'[^A-Za-z0-9]', '', priv).lower()}"
if name in sony and not sony_named:
name = f"{name}_alt"
while name in names_taken:
name = f"{name}_alt"
names_taken.add(name)
clob_sets = collections.Counter(tuple(d["sig"]["clob"]) for d in ds)
if name in sony:
canon_clob = tuple(sony[name][2])
else:
canon_clob = clob_sets.most_common(1)[0][0]
canon_defs = [d for d in ds if tuple(d["sig"]["clob"]) == canon_clob]
synthesized = False
if canon_defs:
texts = collections.Counter((d["params"], re.sub(r"[ \t]+\n", "\n", d["body"]).strip()) for d in canon_defs)
(params, body), _ = texts.most_common(1)[0]
else:
# nobody spells it with Sony's clobbers (every definition carries the steer): the canonical text is the majority body
# with its clobber list set to Sony's — the steer must not become canonical by being universal
texts = collections.Counter((d["params"], re.sub(r"[ \t]+\n", "\n", d["body"]).strip()) for d in ds)
(params, body), _ = texts.most_common(1)[0]
body = set_clobbers(body, list(canon_clob))
synthesized = True
table[key] = dict(name=name, params=params, body=body, clob=list(canon_clob), defs=len(canon_defs), files=len({d["tu"] for d in canon_defs}),
synthesized=synthesized,
names=dict(names), sony=(name in sony), nin=ds[0]["sig"]["nin"], nout=ds[0]["sig"]["nout"], tmpl=ds[0]["sig"]["tmpl"])
for cs, cnt in clob_sets.items():
if cs != canon_clob:
extra = sorted(set(cs) - set(canon_clob))
fewer = sorted(set(canon_clob) - set(cs))
vname = f"{name}_m" if extra == ["memory"] and not fewer else f"{name}_v{hashlib.sha1('|'.join(cs).encode()).hexdigest()[:4]}"
variants[f"{key}|{'|'.join(cs)}"] = dict(name=vname, of=name, key=key, clob=list(cs), extra=extra, fewer=fewer, defs=cnt,
files=len({d["tu"] for d in ds if tuple(d["sig"]["clob"]) == cs}))
out = dict(generated=time.strftime("%Y-%m-%d %H:%M"), canonical=table, variants=variants, unsigned=[dict(tu=d["tu"], name=d["name"], l0=d["l0"]) for d in unsigned])
CANON.write_text(json.dumps(out, indent=1))
if not quiet:
print(f"gte_consolidate: canonical table — {len(table)} signatures ({sum(1 for t in table.values() if t['sony'])} with Sony's name), "
f"{sum(t['defs'] for t in table.values())} canonical definitions; {len(variants)} lever variants holding {sum(v['defs'] for v in variants.values())} definitions; "
f"{len(unsigned)} unsigned definitions")
for v in sorted(variants.values(), key=lambda v: -v["defs"])[:12]:
print(f" variant {v['name']:24s} of {v['of']:18s} defs {v['defs']:4d} files {v['files']:4d} extra {v['extra']} fewer {v['fewer']}")
for u in unsigned[:8]:
print(f" unsigned {u['tu']}:{u['l0']} {u['name']}")
return out
def render_header(canon):
L = ["/* include/gte_inline.h — the GTE coprocessor macros under Sony's names, ONE definition per signature (Phase 36 T5).",
" *",
" * Every macro here is the majority spelling the tree already carried in its per-TU definitions (byte-proven in the fleet),",
" * named by PsyQ's inline_c.h convention; the signature of each (the bytes the build's maspsx → as tail makes of the template,",
" * its operand counts, its clobbers) was computed by tools/gte_consolidate.py, which also deleted the per-TU duplicates.",
" * A definition that differs from the one here only by an extra clobber (a scheduling steer) lives on in its TU as <name>_m",
" * and is a marked lever, not a second spelling. Generated: do not edit by hand — regenerate with `tools/gte_consolidate.py --header`.",
" */", "#ifndef GTE_INLINE_H", "#define GTE_INLINE_H", ""]
for key, t in sorted(canon["canonical"].items(), key=lambda kv: kv[1]["name"]):
body_lines = t["body"].split("\n")
L.append(f"/* {t['tmpl'][:90]}{' …' if len(t['tmpl']) > 90 else ''} ({t['defs']} definitions in {t['files']} files{'' if t['sony'] else '; not a PsyQ name'}) */")
if len(body_lines) == 1:
L.append(f"#define {t['name']}{t['params']} {body_lines[0]}")
else:
L.append(f"#define {t['name']}{t['params']} \\")
for i, bl in enumerate(body_lines):
L.append(f" {bl.strip()}" + (" \\" if i < len(body_lines) - 1 else ""))
L.append("")
L += ["#endif /* GTE_INLINE_H */", ""]
return "\n".join(L)
def write_header(canon):
RUN.mkdir(parents=True, exist_ok=True)
(REPO / HEADER).write_text(render_header(canon))
c = REPO / COMMON
t = c.read_text()
if '#include "gte_inline.h"' not in t:
t = t.replace("#endif /* COMMON_H */", '#include "gte_inline.h" /* Phase 36 T5: the GTE coprocessor macros, one definition each */\n#endif /* COMMON_H */', 1)
c.write_text(t)
print(f"gte_consolidate --header: {HEADER} written ({len(canon['canonical'])} macros); {COMMON} includes it")
# ----------------------------------------------------------------------------------------------------------------------
# --apply: per file
# ----------------------------------------------------------------------------------------------------------------------
def plan_files(inv, canon, only=None):
"""tu -> dict(defs=[definitions to delete/rename], direct=[direct statements], renames={old: new})"""
by_key = canon["canonical"]
var_by = {v["key"] + "|" + "|".join(v["clob"]): v for v in canon["variants"].values()}
plan = collections.defaultdict(lambda: dict(defs=[], direct=[], renames={}))
for d in inv["definitions"]:
if d["kind"] != "gte" or not (d["sig"] and d["sig"]["ok"]):
continue
key = sig_key(d["sig"])
t = by_key.get(key)
if not t:
continue
vkey = key + "|" + "|".join(d["sig"]["clob"])
if tuple(d["sig"]["clob"]) == tuple(t["clob"]):
plan[d["tu"]]["defs"].append(dict(d, action="delete", canonical=t["name"]))
if d["name"] != t["name"]:
plan[d["tu"]]["renames"][d["name"]] = t["name"]
elif vkey in var_by:
v = var_by[vkey]
plan[d["tu"]]["defs"].append(dict(d, action="rename", canonical=t["name"], variant=v["name"], extra=v["extra"], fewer=v["fewer"]))
if d["name"] != v["name"]:
plan[d["tu"]]["renames"][d["name"]] = v["name"]
for s in inv["direct"]:
plan[s["tu"]]["direct"].append(s)
files = {tu: p for tu, p in plan.items() if p["defs"] or p["direct"]}
if only:
files = {tu: p for tu, p in files.items() if any(o == tu or tu.endswith("/" + o) or ("/" + o + "/") in tu for o in only)}
return files
def direct_rewrite(s, canon):
"""('call', text, name) | ('lever', variant_name, extra) | ('none', why, None) for a direct GTE statement."""
sg = s["sig"]
if not sg["ok"]:
return "none", "unsigned", None
key = sig_key(sg)
t = canon["canonical"].get(key)
if t and sg["nout"] == 0:
if tuple(sg["clob"]) == tuple(t["clob"]):
return "call", f"{t['name']}({', '.join(sg['ins'])});", t["name"]
extra = sorted(set(sg["clob"]) - set(t["clob"]))
fewer = sorted(set(t["clob"]) - set(sg["clob"]))
return "lever", (f"{t['name']}_m" if extra == ["memory"] and not fewer else f"{t['name']}_v"), extra or fewer
# a concatenation of two canonical macros (a load then an op): bytes = b1 + b2, inputs = the first's, clobbers = the union
b = sg["bytes"]
for k1, t1 in canon["canonical"].items():
b1 = k1.split("|")[0]
if not b.startswith(b1) or len(b1) == 0 or len(b1) >= len(b):
continue
k2 = f"{b[len(b1):]}|0|0"
t2 = canon["canonical"].get(k2)
if not t2 or t1["nout"] or t1["nin"] != sg["nin"]:
continue
union = sorted(set(t1["clob"]) | set(t2["clob"]))
if union == list(sg["clob"]):
return "call", f"{t1['name']}({', '.join(sg['ins'])}); {t2['name']}();", f"{t1['name']}+{t2['name']}"
extra = sorted(set(sg["clob"]) - set(union))
return "lever", f"{t1['name']}+{t2['name']}_m" if extra == ["memory"] else f"{t1['name']}+{t2['name']}_v", extra or sorted(set(union) - set(sg["clob"]))
return "none", "no canonical signature", None
def file_edits(tu, raw, p, canon, label):
"""[(start, end, repl)] + the record (per definition / use / direct statement) for one file's consolidation."""
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
edits, rec = [], dict(deleted=0, renamed_defs=0, use_renames=0, direct_calls=0, direct_levers=0, direct_none=0, marked=0, defs=[], direct=[])
# definitions: delete or rename (the #define line's name token); a renamed variant gets a comment line above
for d in sorted(p["defs"], key=lambda d: d["l0"]):
start, end = ls[d["l0"] - 1], ls[d["l1"]] if d["l1"] < len(ls) else len(raw)
if d["action"] == "delete":
edits.append((start, end, ""))
rec["deleted"] += 1
rec["defs"].append(dict(name=d["name"], l0=d["l0"], action="deleted", canonical=d["canonical"]))
else:
line = raw[start:ls[d["l0"]] - 1]
k = line.find(d["name"])
if k < 0:
raise Refuse(f"{tu}:{d['l0']}: the #define line does not carry `{d['name']}`")
note = f"/* {FAKE[3:]} gte variant `{d['variant']}` — {', '.join(d['extra']) or 'fewer clobbers'} beyond Sony's `{d['canonical']}` (a scheduling steer; P36 T5) */\n"
edits.append((start, start, note))
edits.append((start + k, start + k + len(d["name"]), d["variant"]))
rec["renamed_defs"] += 1
rec["defs"].append(dict(name=d["name"], l0=d["l0"], action="renamed", variant=d["variant"], canonical=d["canonical"]))
# uses: rename tokens (outside the #define lines being edited); mark the uses of a lever variant
def_lines = {ln for d in p["defs"] for ln in range(d["l0"], d["l1"] + 1)}
lever_names = {d["name"] for d in p["defs"] if d["action"] == "rename"} | {d["variant"] for d in p["defs"] if d["action"] == "rename"}
for old, new in p["renames"].items():
for mm in re.finditer(r"\b%s\b(?=\s*\()" % re.escape(old), m):
ln = m.count("\n", 0, mm.start()) + 1
if ln in def_lines:
continue
edits.append((mm.start(), mm.end(), new))
rec["use_renames"] += 1
marked_lines = set()
for name in lever_names:
for mm in re.finditer(r"\b%s\b(?=\s*\()" % re.escape(name), m):
ln = m.count("\n", 0, mm.start()) + 1
if ln in def_lines or ln in marked_lines:
continue
le = m.find("\n", mm.start())
le = len(raw) if le < 0 else le
if FAKE in raw[ls[ln - 1]:le]:
continue
edits.append((le, le, f" {FAKE} gte via {name if name.endswith(('_m',)) or '_v' in name else p['renames'].get(name, name)} — a clobber Sony's macro lacks (P36 T5 {label})"))
marked_lines.add(ln)
rec["marked"] += 1
# direct statements
for s in p["direct"]:
pos = ls[s["line"] - 1] + s["col"] - 1
e = dl.stmt_end(m, pos)
if e < 0 or not dl.ASM_HEAD.match(m, pos):
rec["direct"].append(dict(line=s["line"], action="refused", why="statement not found at the census position"))
continue
how, text, name = direct_rewrite(s, canon)
if how == "call":
edits.append((pos, e, text))
rec["direct_calls"] += 1
rec["direct"].append(dict(line=s["line"], action="call", name=name))
elif how == "lever":
le = m.find("\n", e)
le = len(raw) if le < 0 else le
if FAKE not in raw[ls[s["line"] - 1]:le] and s["line"] not in marked_lines:
edits.append((le, le, f" {FAKE} gte direct — clobbers {name} ({', '.join(text if isinstance(text, list) else [str(text)])}) beyond Sony's (P36 T5 {label})".replace("clobbers " + str(text) + " (", "as " + str(text) + " (")))
marked_lines.add(s["line"])
rec["marked"] += 1
rec["direct_levers"] += 1
rec["direct"].append(dict(line=s["line"], action="lever", variant=text, delta=name))
else:
rec["direct_none"] += 1
rec["direct"].append(dict(line=s["line"], action="none", why=text))
return edits, rec
def apply_batch(a):
clean, dirty = dl.src_clean()
if not clean:
sys.exit(f"gte_consolidate --apply: src/ is dirty — commit or tools/delever.py --restore first:\n{dirty[:400]}")
ok, why = oracle.calibration_current()
if not ok:
sys.exit(f"gte_consolidate --apply: calibration not current ({why})")
if not (REPO / HEADER).exists() or '#include "gte_inline.h"' not in (REPO / COMMON).read_text():
sys.exit("gte_consolidate --apply: the header is not in place (--header first, then the fleet run and its commit)")
dl.ensure_census(a.jobs)
inv = inventory(a.jobs, quiet=True)
canon = canonical_table(inv, quiet=True)
files = plan_files(inv, canon, a.only)
order = sorted(files, key=lambda tu: (-len(files[tu]["defs"]) - len(files[tu]["direct"]), tu))
ledger_rows = [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()] if LEDGER.exists() else []
done = {r["tu"] for r in ledger_rows if r.get("rung") == "gte" and r.get("verdict") in ("CONSOLIDATED", "UNCHANGED")}
order = [tu for tu in order if tu not in done or a.rejudge][:a.batch]
print(f"gte_consolidate --apply {a.label}: {len(order)} files of {len(files)} with work ({len(done)} done) — "
f"{sum(len(files[t]['defs']) for t in order)} definitions, {sum(len(files[t]['direct']) for t in order)} direct statements", flush=True)
if not order:
print("gte_consolidate --apply: nothing to do")
return 0
recipes = oracle.load_recipes()["recipes"]
by_src = oracle.recipes_by_src(recipes)
inc = dl.includers()
def recipes_for(tu):
return [r for t in inc.get(tu, []) for r in by_src.get(t, [])] if tu.endswith(".h") else by_src.get(tu, [])
dl.INFLIGHT.write_text(json.dumps(dict(label=a.label, files={tu: (REPO / tu).read_text(errors="surrogateescape") for tu in order})))
log_p = RUN / f"apply_{a.label}.log"
log_f = open(log_p, "a")
def log(msg):
line = f"{time.strftime('%H:%M:%S')} {msg}"
with _LOCK:
print(line, flush=True)
log_f.write(line + "\n")
log_f.flush()
pool = ThreadPoolExecutor(max_workers=a.jobs)
results = []
def work(tu):
path = REPO / tu
st = path.stat()
raw = path.read_text(errors="surrogateescape")
recs = recipes_for(tu)
row = dict(ts=time.strftime("%Y-%m-%d %H:%M:%S"), label=a.label, rung="gte", calib=dict(head=oracle.head(), stamp=oracle.config_stamp()),
tu=tu, fn=None, addr=None, aliases=None, header=tu.endswith(".h"), sites=[], compiles=0, seconds=0.0)
if not recs:
row["verdict"] = "NO-RECIPE"
return row
plan_tu = files[tu]
# the variant trial: a definition whose only difference from the canonical one is a clobber is tried AS canonical (deleted, its
# uses renamed to the canonical name), one at a time on the untouched text; IDENTICAL -> the clobber was no lever here
variants = [d for d in plan_tu["defs"] if d["action"] == "rename"]
freed = 0
for d in variants:
trial = dict(defs=[dict(d, action="delete")], direct=[], renames=({d["name"]: d["canonical"]} if d["name"] != d["canonical"] else {}))
try:
ed, _ = file_edits(tu, raw, trial, canon, a.label)
cand = dl.apply_edits(raw, ed)
if cand == raw:
continue
try:
v, dt, err = oracle.judge_all(recs, cand, tag="gtev", write_path=(tu if tu.endswith(".h") else None))
finally:
dl.restore_file(path, raw, st)
row["compiles"] += len(recs)
row["seconds"] += dt
if v == "IDENTICAL":
d["action"], d["trial"] = "delete", "IDENTICAL"
if d["name"] != d["canonical"]:
plan_tu["renames"][d["name"]] = d["canonical"]
plan_tu["renames"].pop(d["name"], None) if d["name"] == d["canonical"] else None
freed += 1
else:
d["trial"] = v
except (Refuse, dl.Refuse) as ex:
d["trial"] = f"REFUSED {ex}"
# a variant that stays a variant keeps its rename to <name>_m
for d in variants:
if d["action"] == "rename" and d["name"] != d["variant"]:
plan_tu["renames"][d["name"]] = d["variant"]
try:
edits, rec = file_edits(tu, raw, plan_tu, canon, a.label)
except Refuse as ex:
row.update(verdict="REFUSED", why=str(ex))
log(f" {tu}: REFUSED {ex}")
return row
rec["variants_freed"] = freed
rec["variants_kept"] = sum(1 for d in variants if d["action"] == "rename")
row["record"] = rec
cands = []
try:
cands.append(("all", dl.apply_edits(raw, edits)))
no_direct = [e for e in edits if not any(e[0] == dl.line_starts(raw)[s["line"] - 1] + s["col"] - 1 for s in files[tu]["direct"])]
if len(no_direct) != len(edits):
cands.append(("defs-only", dl.apply_edits(raw, no_direct)))
except dl.Refuse as ex:
row.update(verdict="REFUSED", why=f"edits: {ex}")
log(f" {tu}: REFUSED edits {ex}")
return row
verdict = "UNCHANGED"
for tag, cand in cands:
if cand == raw:
continue
try:
if tu.endswith(".h") and len(recs) > 1:
path.write_text(cand, errors="surrogateescape")
vs = list(pool.map(lambda r: oracle.judge(r, None, tag="gte"), recs))
bad = [v for v in vs if v[0] != "IDENTICAL"]
v, dt, err = (bad[0][0], sum(x[1] for x in vs), bad[0][2]) if bad else ("IDENTICAL", sum(x[1] for x in vs), "")
else:
v, dt, err = oracle.judge_all(recs, cand, tag="gte", write_path=(tu if tu.endswith(".h") else None))
finally:
dl.restore_file(path, raw, st)
row["compiles"] += len(recs)
row["seconds"] += dt
if v == "IDENTICAL":
path.write_text(cand, errors="surrogateescape")
verdict = "CONSOLIDATED" if tag == "all" else "CONSOLIDATED-DEFS-ONLY"
if tag != "all":
rec["direct_calls_refused"] = rec.pop("direct_calls", 0)
log(f" {tu}: the direct-statement rewrites DIFFER — kept the definition edits only")
break
verdict = f"DIFFERS-{tag}" if v == "DIFFERS" else f"{v}-{tag}"
log(f" {tu}: {tag} {v} {err[:120]}")
row["verdict"] = verdict if verdict.startswith(("CONSOLIDATED", "UNCHANGED")) else "REFUSED"
if not verdict.startswith(("CONSOLIDATED", "UNCHANGED")):
row["why"] = verdict
log(f" {tu}: {row['verdict']} — deleted {rec['deleted']} defs, renamed {rec['renamed_defs']}, use renames {rec['use_renames']}, "
f"direct calls {rec.get('direct_calls', 0)}, direct levers {rec['direct_levers']}, unmatched {rec['direct_none']}, marked {rec['marked']}")
return row
tus = [tu for tu in order if not tu.endswith(".h")]
hdrs = [tu for tu in order if tu.endswith(".h")]
with ThreadPoolExecutor(max_workers=a.jobs) as ex:
results = list(ex.map(work, tus))
for h in hdrs:
results.append(work(h))
pool.shutdown(wait=True)
dl.ledger_append(results)
agg = collections.Counter()
for r in results:
agg[r["verdict"]] += 1
for k in ("deleted", "renamed_defs", "use_renames", "direct_calls", "direct_levers", "direct_none", "marked", "variants_freed", "variants_kept"):
agg[k] += r.get("record", {}).get(k, 0)
agg["compiles"] += r["compiles"]
line = (f"gte_consolidate: batch {a.label} — {len(order)} files: {agg['CONSOLIDATED']} consolidated, {agg['CONSOLIDATED-DEFS-ONLY']} defs-only, "
f"{agg['UNCHANGED']} unchanged, {agg['REFUSED']} refused, {agg['NO-RECIPE']} no-recipe · definitions deleted {agg['deleted']} / renamed as lever variants "
f"{agg['renamed_defs']} (variant trials: {agg['variants_freed']} freed, {agg['variants_kept']} kept) · use renames {agg['use_renames']} · direct statements → calls {agg['direct_calls']} / lever {agg['direct_levers']} / "
f"unmatched {agg['direct_none']} · markers {agg['marked']} · compiles {agg['compiles']} · final {len(order) - agg['REFUSED'] - agg['NO-RECIPE']}/{len(order)} identical")
log(line)
(RUN / f"batch_{a.label}.json").write_text(json.dumps(dict(label=a.label, files=order, totals=dict(agg), rows=results), indent=1))
return 0 if agg["REFUSED"] == 0 else 1
# ----------------------------------------------------------------------------------------------------------------------
# --sweep: dead asm-bearing macros; a compound macro's inner asm dropped when byte-neutral
# ----------------------------------------------------------------------------------------------------------------------
def sweep(a):
clean, dirty = dl.src_clean()
if not clean:
sys.exit(f"gte_consolidate --sweep: src/ is dirty:\n{dirty[:400]}")
ok, why = oracle.calibration_current()
if not ok:
sys.exit(f"gte_consolidate --sweep: calibration not current ({why})")
dl.ensure_census(a.jobs)
sites = dl.load_sites()
uses_by_file = collections.defaultdict(collections.Counter)
for s in sites:
if s.get("via"):
uses_by_file[s["tu"]][s["via"]] += 1
inc = dl.includers()
# a macro used by an included shared header counts as used by every includer
hdr_uses = {h: uses_by_file.get(h, collections.Counter()) for h in inc}
recipes = oracle.load_recipes()["recipes"]
by_src = oracle.recipes_by_src(recipes)
rows, n_dead, n_inner, n_files = [], 0, 0, 0
files = files_under_src()
if a.only:
files = [p for p in files if any(o in p.as_posix() for o in a.only)]
for p in files:
rel = p.relative_to(REPO).as_posix()
raw = p.read_text(errors="surrogateescape")
if "#define" not in raw:
continue
blocks = [(l0, l1, name, body) for (l0, l1, name, body) in lc.define_blocks(raw) if lc.macro_kind(body) and lc.macro_kind(body) != "gte"]
if not blocks:
continue
used = collections.Counter(uses_by_file.get(rel, {}))
for h, hu in hdr_uses.items():
if rel in inc.get(h, []):
used.update(hu)
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
edits, dead, inner = [], [], []
for (l0, l1, name, body) in blocks:
start, end = ls[l0 - 1], ls[l1] if l1 < len(ls) else len(raw)
if used.get(name, 0) == 0 and not re.search(r"\b%s\b" % re.escape(name), m[:start] + m[end:]):
edits.append((start, end, ""))
dead.append(name)
continue
# a compound macro: drop the asm statement line(s) inside the definition (the launder/barrier steer), keep the code
lines = raw[start:end].split("\n")
keep, dropped = [], 0
for ln in lines:
if lc.ASM_KW.search(ln) and ("__asm__" in ln or "asm" in ln) and ('"' in ln):
dropped += 1
continue
keep.append(ln)
if dropped and len(keep) > 1:
new = "\n".join(keep)
if not new.endswith("\n") and raw[start:end].endswith("\n"):
new += "\n"
edits.append((start, end, new))
inner.append(name)
if not edits:
continue
recs = [r for t in inc.get(rel, []) for r in by_src.get(t, [])] if rel.endswith(".h") else by_src.get(rel, [])
if not recs:
print(f" {rel}: no recipe — skipped")
continue
st = p.stat()
try:
cand = dl.apply_edits(raw, edits)
except dl.Refuse as ex:
print(f" {rel}: REFUSED {ex}")
continue
verdict_all = None
for tag, cand_t in (("all", cand), ("dead-only", dl.apply_edits(raw, [e for e in edits if e[2] == ""]) if inner and dead else None)):
if cand_t is None or cand_t == raw:
continue
try:
v, dt, err = oracle.judge_all(recs, cand_t, tag="sweep", write_path=(rel if rel.endswith(".h") else None))
finally:
dl.restore_file(p, raw, st)
if v == "IDENTICAL":
p.write_text(cand_t, errors="surrogateescape")
verdict_all = tag
break
print(f" {rel}: {tag} {v} {err[:100]}")
n_files += verdict_all is not None
if verdict_all == "all":
n_dead += len(dead)
n_inner += len(inner)
elif verdict_all == "dead-only":
n_dead += len(dead)
inner_kept = inner
inner = []
rows.append(dict(ts=time.strftime("%Y-%m-%d %H:%M:%S"), label=a.label, rung="sweep", calib=dict(head=oracle.head(), stamp=oracle.config_stamp()),
tu=rel, fn=None, addr=None, aliases=None, header=rel.endswith(".h"), verdict=("SWEPT-" + verdict_all if verdict_all else "REFUSED"),
dead=dead, inner_dropped=(inner if verdict_all else []), sites=[], compiles=len(recs)))
print(f" {rel}: {rows[-1]['verdict']} — dead {dead} inner-asm dropped {inner if verdict_all else []}")
dl.ledger_append(rows)
print(f"gte_consolidate --sweep {a.label}: {n_dead} dead asm-bearing macro definitions deleted, {n_inner} compound macros' inner asm dropped, "
f"{n_files} files written, {sum(1 for r in rows if r['verdict'] == 'REFUSED')} refused")
return 0
def status():
rows = [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()] if LEDGER.exists() else []
g = [r for r in rows if r.get("rung") in ("gte", "sweep")]
print(f"gte_consolidate --status: {len(g)} rows — {dict(collections.Counter(r['verdict'] for r in g))}")
return 0
# ----------------------------------------------------------------------------------------------------------------------
def selftest():
ok = True
def fail(msg):
nonlocal ok
ok = False
print("selftest FAIL:", msg)
# 1. the signature engine: three spellings of rtps are one signature; a template with an operand binds to $4
s1 = signature('"nop;nop;rtps"')
s2 = signature('"nop;" "nop;" ".word 0x4a180001"')
s3 = signature('"nop;nop;cop2 0x0180001"')
if not (s1["ok"] and s1["bytes"] == s2["bytes"] == s3["bytes"]):
fail(f"rtps spellings: {s1['bytes'][:40]} {s2['bytes'][:40]} {s3['bytes'][:40]}")
s4 = signature('"lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 )')
s5 = signature('"lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory"')
if not (s4["ok"] and s4["bytes"] == s5["bytes"] and s4["nin"] == 1 and s4["clob"] == [] and s5["clob"] == ["memory"]):
fail(f"ldv0 spellings/clobbers: {s4} {s5}")
if signature('"nop;nop;rtpt"')["bytes"] == s1["bytes"]:
fail("rtps and rtpt must differ")
# 2. the canonical table on a synthetic inventory: canonical / variant / private name / concatenated direct statement
inv = dict(definitions=[
dict(tu="a.c", l0=1, l1=2, name="gte_ldv0", params="( r0 )", body='__asm__ volatile ( "lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 ) )', kind="gte", sig=s4),
dict(tu="b.c", l0=1, l1=2, name="gte_ldv0", params="(r0)", body='__asm__ __volatile__( "lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory")', kind="gte", sig=s5),
dict(tu="c.c", l0=1, l1=2, name="LDV0_DA34", params="( r0 )", body='__asm__ volatile ("lwc2 $0, 0( %0 );lwc2 $1, 4( %0 )" : : "r"( r0 ))', kind="gte", sig=s4),
dict(tu="a.c", l0=3, l1=3, name="gte_rtps", params="()", body='__asm__ volatile ("nop;nop;rtps")', kind="gte", sig=s1),
], direct=[], uses={})
canon = canonical_table(inv, quiet=True)
t = canon["canonical"].get(sig_key(s4))
if not t or t["name"] != "gte_ldv0" or t["clob"] != [] or t["defs"] != 2:
fail(f"canonical ldv0: {t}")
v = [x for x in canon["variants"].values() if x["of"] == "gte_ldv0"]
if len(v) != 1 or v[0]["name"] != "gte_ldv0_m" or v[0]["extra"] != ["memory"]:
fail(f"ldv0 memory variant: {v}")
# 3. direct statements: a canonical call, a lever, a concatenation
d_call = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(p)'))
d_lever = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(p) : "memory"'))
d_cat = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0);" "nop;nop;rtps" : : "r"(p)'))
if direct_rewrite(d_call, canon) != ("call", "gte_ldv0(p);", "gte_ldv0"):
fail(f"direct call: {direct_rewrite(d_call, canon)}")
if direct_rewrite(d_lever, canon)[0] != "lever" or direct_rewrite(d_lever, canon)[1] != "gte_ldv0_m":
fail(f"direct lever: {direct_rewrite(d_lever, canon)}")
r = direct_rewrite(d_cat, canon)
if r != ("call", "gte_ldv0(p); gte_rtps();", "gte_ldv0+gte_rtps"):
fail(f"direct concatenation: {r}")
# 4. the per-file edits on a fixture: the canonical def deleted, the private use renamed, the variant renamed + marked, a direct call
fx = ('#define gte_ldv0( r0 ) __asm__ volatile ( "lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 ) )\n'
'#define LDV0_DA34( r0 ) __asm__ volatile ("lwc2 $0, 0( %0 );lwc2 $1, 4( %0 )" : : "r"( r0 ))\n'
'#define gte_ldv0m( r0 ) __asm__ __volatile__( "lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory")\n'
'void f(s32 *v) {\n LDV0_DA34(v);\n gte_ldv0m(v);\n __asm__ volatile("lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(v));\n}\n')
defs = []
for (l0, l1, name, body) in lc.define_blocks(fx):
sg = signature(asm_inner(body))
defs.append(dict(tu="fx.c", l0=l0, l1=l1, name=name, params=define_params(fx, l0), body=body, kind="gte", sig=sg))
fm, fls = dl.same_len_mask(fx), dl.line_starts(fx)
fpos = fls[6] + 4
fo = fm.find("(", fpos)
finner = fm[fo + 1:lc._paren_span(fm, fo)]
inv2 = dict(definitions=defs, direct=[dict(tu="fx.c", line=7, col=5, fn="f", inner=finner, sig=signature(finner))], uses={})
canon2 = canonical_table(inv2, quiet=True)
plan = plan_files(inv2, canon2)
edits, rec = file_edits("fx.c", fx, plan["fx.c"], canon2, "self")
out = dl.apply_edits(fx, edits)
if "#define gte_ldv0(" in out or "#define LDV0_DA34" in out:
fail("canonical/private definitions must be deleted")
if "#define gte_ldv0_m( r0 )" not in out or "gte variant `gte_ldv0_m`" not in out:
fail(f"the variant definition renamed + noted:\n{out}")
if " gte_ldv0(v);\n" not in out:
fail(f"the private use renamed to the canonical name:\n{out}")
if "gte_ldv0_m(v); // !FAKE: gte via gte_ldv0_m" not in out:
fail(f"the lever variant's use marked:\n{out}")
if " gte_ldv0(v);\n}" not in out:
fail(f"the direct statement rewritten into the canonical call:\n{out}")
if rec["deleted"] != 2 or rec["renamed_defs"] != 1 or rec["use_renames"] != 2 or rec["direct_calls"] != 1 or rec["marked"] != 1:
fail(f"record: {rec}")
# 5. the header renders every canonical macro once, with its parameters
h = render_header(canon2)
if h.count("#define gte_ldv0(") != 1 or "#define gte_ldv0_m" in h:
fail("header: one canonical definition, no variant")
print(f"gte_consolidate --selftest: {'OK' if ok else 'FAIL'} — signatures rtps={s1['bytes'][16:24]} ldv0={s4['bytes'][:16]}; table {len(canon2['canonical'])} canonical / {len(canon2['variants'])} variant; fixture edits {len(edits)}")
return ok
def main():
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("--inventory", action="store_true")
ap.add_argument("--header", action="store_true")
ap.add_argument("--apply", action="store_true")
ap.add_argument("--sweep", action="store_true")
ap.add_argument("--status", action="store_true")
ap.add_argument("--selftest", action="store_true")
ap.add_argument("--batch", type=int, default=400)
ap.add_argument("--label", default=None)
ap.add_argument("--only", nargs="*", default=None)
ap.add_argument("--rejudge", action="store_true")
ap.add_argument("-j", "--jobs", type=int, default=12)
a = ap.parse_args()
RUN.mkdir(parents=True, exist_ok=True)
if a.selftest:
sys.exit(0 if selftest() else 1)
if a.status:
sys.exit(status())
if a.inventory:
dl.ensure_census(a.jobs)
inv = inventory(a.jobs)
canonical_table(inv)
return
if a.header:
inv = json.loads(INVENTORY.read_text()) if INVENTORY.exists() else inventory(a.jobs, quiet=True)
canon = canonical_table(inv, quiet=True)
write_header(canon)
return
if a.apply:
if not a.label:
sys.exit("--label is required")
sys.exit(apply_batch(a))
if a.sweep:
if not a.label:
sys.exit("--label is required")
sys.exit(sweep(a))
ap.print_help()
if __name__ == "__main__":
main()
@@ -69,7 +69,50 @@ MIPS_REG_NAMES = {"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1",
GTE_MNEMONICS = {"lwc2", "swc2", "mtc2", "mfc2", "ctc2", "cfc2", "cop2", "rtps", "rtpt", "nclip", "ncds", "nccs", "ncdt", "ncct",
"ncs", "nct", "cdp", "cc", "dpcs", "dpct", "dpcl", "intpl", "sqr", "op", "gpf", "gpl", "avsz3", "avsz4", "mvmva"}
FAKE_MARK = "!FAKE:"
NON_LEVER_KINDS = {"gte", "verbatim-body"} # Sony's coprocessor idiom; a manifest-listed hand-asm routine — censused, never a lever
NON_LEVER_KINDS = {"gte", "verbatim-body", "gte-unsigned"} # Sony's coprocessor idiom; a manifest-listed hand-asm routine — censused, never a lever
GTE_LEVER_KIND = "gte-lever" # a GTE op whose clobbers exceed its canonical's (a `_m`/`_v` variant macro, or a direct statement): a steer
GTE_VARIANT_NAME = re.compile(r"(_m|_v[0-9a-f]{4})$")
_gte_tables = None
def _gte_lookup():
"""(sig_cache, canonical-by-bytes) from tools/gte_consolidate.py's files, read once; ({}, {}) when T5 has not run."""
global _gte_tables
if _gte_tables is None:
cache, canon = {}, {}
try:
import json as _json
base = REPO / ".run" / "P36" / "gte"
if (base / "sig_cache.json").exists():
cache = _json.loads((base / "sig_cache.json").read_text())
if (base / "canonical.json").exists():
for key, tbl in _json.loads((base / "canonical.json").read_text())["canonical"].items():
canon[key] = dict(name=tbl["name"], clob=sorted(tbl["clob"]))
except Exception: # noqa: BLE001 — a torn file reads as "no table"
cache, canon = {}, {}
_gte_tables = (cache, canon)
return _gte_tables
def classify_direct_gte(inner):
"""'gte' (Sony's idiom or no canonical to compare), 'gte-lever' (clobbers beyond the canonical's), 'gte-unsigned' (a text the
consolidation tool has not signed yet — run `tools/gte_consolidate.py --inventory`)."""
cache, canon = _gte_lookup()
if not canon:
return "gte"
try:
import gte_consolidate as gc
tmpl, outs, ins, clob = gc.parse_asm(inner)
bound = gc.bind_regs(tmpl, len(outs), len(ins))
except Exception: # noqa: BLE001
return "gte-unsigned"
b = cache.get(bound)
if not b or b.startswith("ERROR"):
return "gte-unsigned"
c = canon.get(f"{b}|{len(outs)}|{len(ins)}")
if not c:
return "gte"
return GTE_LEVER_KIND if sorted(clob) != c["clob"] else "gte"
# an `asm-body` site (a whole routine as one asm statement inside a C shell) becomes `verbatim-body` only when the manifest lists the
# routine as PERMANENT-VERBATIM; an unlisted or DECOMPILE-* one stays a lever and the guard (verbatim_check) names it
NORM_SYM = re.compile(r"\b(?:func|D)_80[0-9A-Fa-f]{6}\b")
@@ -479,6 +522,8 @@ def walk_file(raw, rel, is_header):
continue
role(pos, "asm-stmt")
d = span_of_line.get(ln)
if kind == "gte" and d:
kind = classify_direct_gte(masked[o + 1:c])
if d and is_whole_body_asm(masked, line_starts[d["line"] - 1], line_starts[d["end"]] - 1, pos, c):
kind = "asm-body" # a whole routine as one asm statement inside a C shell (any template: GTE ones too); the manifest decides its fate
add_site("B", kind, pos, masked[pos:c + 1], detail=detail, via="")
@@ -501,7 +546,10 @@ def walk_file(raw, rel, is_header):
continue
name = m.group(1)
c = _paren_span(masked, m.end() - 1)
add_site("B", asm_macro_names[name], m.start(), masked[m.start():c + 1] if c > 0 else name, detail=name, via=name)
k = asm_macro_names[name]
if k == "gte" and GTE_VARIANT_NAME.search(name):
k = GTE_LEVER_KIND
add_site("B", k, m.start(), masked[m.start():c + 1] if c > 0 else name, detail=name, via=name)
# ---- 4. volatile tokens
for m in VOLATILE_KW.finditer(masked):
@@ -814,6 +862,17 @@ def run_census(jobs, use_cache=True, out_dir=OUT_DIR_DEFAULT, want_sites=False,
what="whole routines written in assembly inside a C shell (the §265 lane's in-function form): the manifest's "
"PERMANENT-VERBATIM rows are hand asm and not levers; an asm-body the manifest does not list as permanent "
"stays a lever (unlisted_routines names them with their disposition)"),
gte_levers=dict(sites=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND),
marked=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and s["marked"]),
unmarked=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and not s["marked"]),
via_macro=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and s.get("via")),
direct=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and not s.get("via")),
unsigned=sum(1 for s in sites if s["kind"] == "gte-unsigned"),
what="GTE ops whose clobbers exceed the canonical macro's (a scheduling steer): levers for T7, outside the headline number"),
per_tu_asm_macro_definitions=dict(total=sum(1 for m in mdefs_all if m["tu"] != gte_header),
by_kind=dict(collections.Counter(m["kind"] for m in mdefs_all if m["tu"] != gte_header)),
gte_variants=sum(1 for m in mdefs_all if m["tu"] != gte_header and m["kind"] == "gte" and GTE_VARIANT_NAME.search(m["name"])),
what="asm-bearing #define blocks outside the GTE header: 0 at the close (T5 deletes the canonical duplicates, T7 the variants)"),
macro_definitions=dict(total=len(mdefs_all), names=len(macro_names), kinds=dict(macro_kinds),
names_with_multiple_texts=sum(1 for n, t in macro_texts.items() if len(t) > 1),
top=[(n, c, len(macro_texts[n])) for n, c in macro_names.most_common(25)]),
@@ -888,6 +947,12 @@ def render(s):
L.append(f" THE PHASE'S NUMBER (pins + asm statements, GTE excluded): {ab['sites']:,} sites in {ab['bodies']:,} bodies "
f"({ab['distinct_bodies']:,} distinct) · marked !FAKE {ab['marked']:,} · UNMARKED {ab['unmarked']:,}")
L.append(f" orphan !FAKE markers (no pin/asm site on the line nor below): {s.get('orphan_markers', {}).get('count', 0)}")
g = s.get("gte_levers", {})
if g:
L.append(f" GTE levers (clobbers beyond the canonical macro's): {g['sites']:,} sites ({g['via_macro']:,} via a variant macro, {g['direct']:,} direct) · "
f"marked {g['marked']:,} · UNMARKED {g['unmarked']:,} · unsigned GTE statements {g['unsigned']:,}")
pt = s.get("per_tu_asm_macro_definitions", {})
L.append(f" per-TU asm macro definitions outside the GTE header: {pt.get('total', 0):,} {pt.get('by_kind', {})} (GTE variants {pt.get('gte_variants', 0):,})")
p = s["pins"]
L.append(f" pins: {p['sites']:,} · $0 {p['zero']:,} · $sp {p['sp']} · with initializer {p['init']:,} · volatile-qualified "
f"{p['volatile_qualified']} · bare-name {p['bare_name']} · spellings {p['spelling']}")
@@ -1032,9 +1097,12 @@ def main():
ab = [x for x in sites if x["cls"] in "AB" and x["kind"] not in NON_LEVER_KINDS and x["tu"] != a.gte_header]
un = [x for x in ab if not x["marked"]]
if a.strict:
print(f"lever_census --check --strict: pins {s['pins']['sites']}, asm {sum(1 for x in ab if x['cls'] == 'B')}, "
f"volatile-needed {s['classes']['C']['sites']}, register-needed {s['classes']['D']['sites']} — {'OK' if not ab else 'FAIL'}")
if ab:
pt = s.get("per_tu_asm_macro_definitions", {}).get("total", 0)
gl = s.get("gte_levers", {}).get("sites", 0)
print(f"lever_census --check --strict: pins {s['pins']['sites']}, asm {sum(1 for x in ab if x['cls'] == 'B' and x['kind'] != GTE_LEVER_KIND)}, "
f"gte-levers {gl}, per-TU asm macro definitions {pt}, volatile-needed {s['classes']['C']['sites']}, register-needed {s['classes']['D']['sites']} — "
f"{'OK' if not ab and not pt else 'FAIL'}")
if ab or pt:
for x in ab[:10]:
print(f" {x['tu']}:{x['line']} {x['fn']} {x['cls']}/{x['kind']} {x['detail']}")
rc = 1
+3 -2
View File
@@ -8,8 +8,8 @@
> its platform SDK need the marked adaptation. The last table lists the tools that are project-only in code (their *shape* is a task;
> their code does not transfer). *TODO(platform): the MIPS and PlayStation SDK hard-codes are the ones another platform replaces first.*
>
> **Coverage:** 299 tool files in scope (submodules, vendored and downloaded code excluded), of which 299 live rows
> below; per phase: P1 2 · P2 26 · P3 17 · P4 9 · P5 26 · P6 51 · P7 20 · P8 86 · P9 27 · P10 19 · PROJECT-ONLY 16. Superseded tools appear only as pointers to their successor (30 pointer rows); one-offs are omitted. Table rows in all: 329 (the installer checks its copy against this figure).
> **Coverage:** 300 tool files in scope (submodules, vendored and downloaded code excluded), of which 300 live rows
> below; per phase: P1 2 · P2 26 · P3 17 · P4 9 · P5 26 · P6 51 · P7 20 · P8 86 · P9 27 · P10 20 · PROJECT-ONLY 16. Superseded tools appear only as pointers to their successor (30 pointer rows); one-offs are omitted. Table rows in all: 330 (the installer checks its copy against this figure).
## P1 — extraction + manifest
@@ -327,6 +327,7 @@
| `cast_call_sites.py` | add per-site function-pointer casts so a draft can call a differently typed callee | Adds per-site function-pointer casts so a draft can call a differently typed callee | repo src layout |
| `ghidra_scripts/ApplySymbols.java` | apply curated names and signatures inside the analysis tool and save | The in-tool half of that mirror: apply curated names and signatures, save on exit | none |
| `lever_census.py` | census every compiler-forcing construct in the C and gate the levers-off phase | The Phase-36 census of lever sites (register pins, asm statements by kind, volatile levers, bare register, asm-label aliases, builtins, attributes; the whole-body hand-asm routines and the GTE ops set apart) derived from the shared-body scanner with a per-token coverage assertion against the raw text, four known-true controls, the !FAKE marker split, --check (0 unmarked pins/asm, 0 orphan markers) and --strict (0 pins, 0 asm) gates, --sites for the delever ledger, a fixture self-test | repo paths, the shared-body scanner |
| `gte_consolidate.py` | consolidate the GTE coprocessor inline-asm macros under Sony's names in one header and sweep dead lever macros | The Phase-36 T5 tool: every asm-bearing macro definition and direct GTE statement SIGNED by the build's own maspsx→as tail (template bytes with operands bound to fixed registers, operand counts, clobbers); one canonical text per signature named by PsyQ's inline_c.h convention (Sony's file supplies names and clobber lists, never opcode words) written to include/gte_inline.h; per file the canonical duplicates deleted, private names renamed, a variant with extra clobbers tried as canonical and kept as a marked <name>_m lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two included); every file judged through every recipe; --sweep deletes dead asm-bearing macros and drops a compound macro's inner asm when byte-neutral | Sony's inline_c.h path, the census sites, the oracle |
| `macro_to_header.py` | convert a shared-body macro header into per-function plain-C headers included at each site | The Phase-35 converter: every DEFINE_ macro body becomes a plain-C header under src/shared/<space>/ included at its site, the legacy name-parameterized headers converted, the registry text-edited, the macro header deleted; --plan / --apply / --finalize / --verify | the macro form is this project's; a kit-born project shares headers from its first bank |
| `asm_verbatim.py` | emit a function's target assembly as an inline-assembly body | Emits the file-scope inline-assembly body form from a disassembly file | repo asm layout |
| `build_engine_types.py` | extract inline-defined types and typedefs from a source file into a shared header | Extracts inline-defined named types and typedefs from a source file into a shared header | repo shared-header path |
+1
View File
@@ -785,6 +785,7 @@ Every script under `tools/` (plus the two report make-targets), grouped by purpo
| | `tools/lever_census.py` | **(P36 T1)** The census of every compiler-forcing construct ("lever") in the fleet's C — register pins, asm statements by kind (barrier / launder / keepalive / instruction / GTE / verbatim-body), volatile levers, bare `register`, plus the deferred asm-label aliases, builtins and attributes — derived from `share_census`'s scanner with a per-token coverage assertion against the raw text, four known-true controls, the `// !FAKE:` marker split, `--check` (0 unmarked pins/asm AND 0 orphan markers — a `// !FAKE:` line with no pin/asm site on it nor on the line below) and `--check --strict` (0 pins, 0 asm outside the GTE header) gates, the JSON's `head` + stat-based `src_stamp` (delever refuses a census that does not describe the tree) and a per-file walk cache keyed on the walker's own hash (a tool change invalidates it, R35), `--sites` (every site to `.run/P36/census/lever_sites.jsonl`, the delever ledger's input), `--selftest`. Evidence: `.run/P36/census/lever_census.{json,txt}`; `progress.py` publishes the `levers` block and a README sentence from it. |
| | `tools/delever_oracle.py` | **(P36 T2)** The fast byte oracle for a single-translation-unit edit: `--recipes` captures every object's exact build command through `make -n -W <src> <obj> BINARY=<alias>` (the Makefile's own pipeline — the jump-table pad stage, the per-object `-O0` overrides, the twin rule — into `.run/P36/delever/recipes.json`, regenerable in ~15 s); a candidate is compiled IN PLACE with `-o`/`-MF` redirected to scratch and its bytes compared with `build/`'s object from the fleet run (build/ is never written); `--calibrate <aliases>` proves 100 % equality untouched + twin == primary + a positive control (a nop injected at the end of a real body → DIFFERS) and writes `calibration.json` (HEAD, config stamp, per-object seconds); `--status`. Measured at T2: main 0.08–0.77 s per object, overlays ~0.14 s. |
| | `tools/delever.py` | **(P36 T2/T3)** The de-lever engine AND the campaign tool. Rewrites per lever class on the raw text at the census's positions (a token mismatch REFUSES, never guesses): pin → plain declaration (type/qualifiers/initializer kept; the `$0` zero-register variable's uses → 0, refused if it is ever assigned), barrier / keep-alive → deleted, launder → deleted when it launders a value into itself, **an ASSIGNMENT `out = in;` when its output and input differ** (deleting those made cc1 2.7.2 abort — the T2 probe counted them NEEDED), a hand-placed instruction → its C (addu-$zero/move/la/lh/lw/addiu/sll/srl/and/andi/lui/li and the lui+addiu / lui+ori pairs), a macro-carried site → deleted for a pure launder statement macro (`SHB`), its value `((T)(p))` for a launder statement-expression, REFUSED for a compound macro (XFER/DRAW/RTP_SND: the lever is in the `#define`, T5), volatile / register → dropped. Per body: replay (a ledger exemplar with the same normalized text: 1 compile) → rung A strip-all → rung B greedy, through `delever_oracle` (verdicts IDENTICAL / DIFFERS / COMPILE-ERROR / COMPILE-CRASH). **The file is the write unit and its final compile the proof**: every body's accepted edits + the file-scope volatile edits + the `// !FAKE:` markers spliced once, compiled through every recipe of the file (a twin's object; every includer of a header, in parallel) — IDENTICAL or the file-scope edits are dropped, or the file is restored and REFUSED (COMBINATION-FAILED). `--plan` / `--apply --batch N --label L [--headers] [--only …] [-j 12]` (TUs in parallel, exemplar files before their copies; headers serial; the tree must be clean, the calibration current, the census's src stamp the tree's — it reruns the census itself), `--redraw REFUSED NOTHING-USABLE` (draw again the bodies whose latest ledger verdict is one of these — after a tool fix), `--restore` (from `inflight.json`, the only restore — R102; it also drops the in-flight batch's ledger rows into an ignored `ledger.jsonl.killed_<label>`, so a killed batch leaves no trace and its bodies are drawn again), `--status`, `--scrub [--only …]` (remove the census's ORPHAN markers — a `// !FAKE:` whose site is gone — each file judged through every recipe; a removed or rewritten site consumes its own trailing marker, so orphans arise only from older tool versions), `--apply-body TU FN FILE --label L [--rung E]` (T6/T7: a reshaped body judged on the bytes, refused if a class A/B lever remains), `--selftest` (the fixture through the census's own parser: every rewrite, the ladder against a stub oracle with a crashing site, the markers, a replay and a disagreeing replay, the ledger index, the oracle's crash forms), `--probe` (T2). The ledger `.run/P36/delever/ledger.jsonl`: one row per (tu, fn, addr, aliases) per judgement with every site's verdict REMOVED / REWRITTEN / NEEDED(oracle word) / REFUSED(why) / DEFERRED, the rung, the calibration id, the body's nhash BEFORE and AFTER — "done" = the current nhash is some row's after-hash; a body with a row's before-hash replays it. Class C/D survivors are never marked (decision 3); class A/B survivors carry `// !FAKE: <kind> <detail> — <verdict> (P36 <rung> <label>)` at the end of their line. Evidence: `apply_<label>.log`, `batch_<label>.json`. Per-batch setup: the per-file include lists are cached in the ignored `includers_cache.json` on (mtime, size) — 29 s → 1.3 s, proven equal to the uncached map. |
| | `tools/gte_consolidate.py` | **(P36 T5)** The GTE consolidation. `--inventory`: every asm-bearing `#define` and every direct GTE statement signed by the build's own tail (`maspsx --aspsx-version=2.56` → `mipsel-linux-gnu-as` with `include/macro.inc` + `gte_macros.inc`, where `rtps`/`mvmva`/`sqr 0` are GAS macros): signature = (bytes with %k bound to $4–$7 / $2–$3, #outputs, #inputs); `nop;nop;rtps`, `.word 0x4a180001` and `cop2 0x0180001` are one signature. The canonical table (`.run/P36/gte/canonical.json`): one text per signature (the majority body — byte-proven in the fleet), Sony's name when the operand counts agree with `inline_c.h` (Sony's converted header is unreliable for opcode words: `gte_rtps` there is `.word 0x0000007f`), Sony's clobber set canonical even when every definition carries the steer (the body is synthesized with Sony's clobbers); a definition with the canonical bytes and OTHER clobbers is a lever variant `<name>_m` / `<name>_v<hash>`. `--header` writes `include/gte_inline.h` (50 macros at T5) and the include in `common.h`. `--apply --batch N --label L`: per file, canonical duplicates deleted, private names renamed to the canonical, each variant TRIED as canonical (the clobber dropped, judged) and kept as a marked `_m` lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two canonical macros included; a direct statement with extra clobbers is marked as a lever; an unmatched sequence is counted); the file judged through every recipe, the definition edits alone retried when the direct rewrites differ. `--sweep`: dead asm-bearing macros (0 uses in the TU and its included headers) deleted; a compound macro's inner asm dropped when byte-neutral. `tools/delever_cycle.sh … gte` runs the apply batches. The census classes a variant's uses and a direct statement with clobbers beyond the canonical's as `gte-lever` (marked, outside the headline number, 0 at the close) and counts per-TU asm macro definitions. |
| | `tools/delever_cycle.sh` | **(P36 T3)** The unattended batch cycle: `[LABEL_PREFIX=…] [TASK=T4] [REDRAW="REFUSED NOTHING-USABLE"] tools/delever_cycle.sh START END [BATCH=300] [tus|headers] [ONLY="alias …"]` — per batch: the tree must be clean (a dotfile under `src/` refuses), the oracle recalibrated when stale (2–3 s; every commit stales it), `delever --apply` (exit code + its `final X/X identical` — an empty verify line is a failure, R32), the CLEAN fleet run as the outer gate (`218 passed, 0 failed of 218`, R22 — it also refreshes `build/`, the oracle's baselines), the census rerun on the new tree, the phase-log entry + the 🛑 headline (R101), one commit of `src/` + the ledger/batch record/logs + the census + the log (R42). Stops on the first red with the batch's files in place (inspect, then `delever --restore`); stops cleanly when nothing is drawable. Size a call so a batch stays under ~10 min. |
| | `tools/kit_coverage.py` | **(P33.5 task 14.5)** The kit's DISTILLATION coverage: derives the rule population (every `- **R<n>` of DIGEST §3, asserted contiguous) and the hindsight population (every `## ` heading of `docs/accelerators.md` at numbered-item granularity — 58 at S92) and asserts each is cited by a `provenance:` line of the registry seed / the kernels OR dispositioned in `config/kit_coverage_map.tsv` (`G<id>` / `DK-<n>` / `FOLDED:G<id>` / `ENV` / `PA` / `SEED:<file>` / `KIT:<path>` / `RECORD` / `COOKBOOK` / `NOT-PORTABLE`; unknown ids refused, R43); counts with denominators (R41); rc 1 on any gap. In `tools-health` after `tool_census --check`. Its first run found 26 uncited rules and 21 uncited entries → three new kernels (DK-66–DK-68) and 41 authored dispositions. |
| | `decomp-architect/` (the day-one decomp kit) | **(P33.5 tasks 9–14)** The package a new matching-decomp project installs as Phase 0.5 on ProjectArchitect 2.0: `README.md` (the three steps), `intake.decomp.md` (ProjectArchitect's twelve items pre-answered + the phase ladder + the six readability inversions), `SETUP.md` (the installer, Step 0 contract … Step 10 verify + hard stop; `answers: <path>` for unattended runs), `decomp-architect.md` (the methodology), `templates/` (the firewall pack — `gitignore.decomp`, `firewall.txt`, `audit_public.template.py`, the planted fixture, `no-rom.template.yml` —, the READMEs, `pa-overlays.md`, `registry-E.decomp.md` G1–G67, the skeletons, `PLACEHOLDERS.md`, `layout-contract.md`), `corpus/decomp-kernels.md` (DK-1 … DK-80), the three dictionaries `corpus/tools/<phase>/` + `corpus/cookbook/` + `corpus/record/` (generated by `make kit-corpus`), `memory-seed/` (18), `tools/MANIFEST.md` (generated). **How it is checked:** `tools/kit_lint.py` (de-specialisation, placeholders, syntax, the gitignore-template diff) + `tools/tool_census.py --check` (the corpora equal their sources) + `tools/gitignore_template_check.py`, all in `tools-health`; the dry-run harness under `.run/P33.5/kit-dryrun/` (`answers.md`, `expected-manifest.txt`, `judge.py`, the install logs and verdicts of runs 1–5 — a kit change is re-verified by a resume on the last throwaway `repo/`, a fresh full run only when SETUP's steps change; the judge compares the real tree's dirty PATH SETS before/after). Wiki page: `docs/wiki/Start-a-new-decomp-project.md`. Split into its own repository after the flip. |
+7 -6
View File
@@ -5,7 +5,7 @@ freshness). The derived columns come from the tree on every run; the authored on
dictionary, whose coverage is asserted both ways. Read it by NEED: find the phrase that matches what you are trying to do, then the tool,
then what proved it. The same data generates the day-one kit's manifest and its verbatim tool corpus.*
**Coverage:** 299 tool files in scope (submodules, vendored and downloaded code excluded; `find` and `git ls-files` agree) + 38 retired under `tools/sunset/`. Classes: LIVE 240 (a runtime consumer), REFERENCED 29 (a SETUP row only), ORPHAN 30 (neither) — of 299. Portability: PORTABLE 23, ADAPT 259, PROJECT-ONLY 17.
**Coverage:** 300 tool files in scope (submodules, vendored and downloaded code excluded; `find` and `git ls-files` agree) + 38 retired under `tools/sunset/`. Classes: LIVE 241 (a runtime consumer), REFERENCED 29 (a SETUP row only), ORPHAN 30 (neither) — of 300. Portability: PORTABLE 23, ADAPT 260, PROJECT-ONLY 17.
## P1 — extraction + manifest
@@ -322,22 +322,23 @@ then what proved it. The same data generates the day-one kit's manifest and its
|---|---|---|---|---|---|
| add per-site function-pointer casts so a draft can call a differently typed callee | `cast_call_sites.py` | Adds per-site function-pointer casts so a draft can call a differently typed callee | canon_sig_reconcile.py, cast_self_callers.py, family_sweep.py, gate_stage.py (+4) | repo src layout | LIVE |
| apply curated names and signatures inside the analysis tool and save | `ghidra_scripts/ApplySymbols.java` | The in-tool half of that mirror: apply curated names and signatures, save on exit | ghidra_apply_symbols.sh, ghidra_export_annotations.sh, ghidra_rebuild.sh, ExportAnnotations.java (+1) | none | LIVE |
| census every compiler-forcing construct in the C and gate the levers-off phase | `lever_census.py` | The Phase-36 census of lever sites (register pins, asm statements by kind, volatile levers, bare register, asm-label aliases, builtins, attributes; the whole-body hand-asm routines and the GTE ops set apart) derived from the shared-body scanner with a per-token coverage assertion against the raw text, four known-true controls, the !FAKE marker split, --check (0 unmarked pins/asm, 0 orphan markers) and --strict (0 pins, 0 asm) gates, --sites for the delever ledger, a fixture self-test | delever.py, delever_cycle.sh, progress.py, verbatim_check.py | repo paths, the shared-body scanner | LIVE |
| census every compiler-forcing construct in the C and gate the levers-off phase | `lever_census.py` | The Phase-36 census of lever sites (register pins, asm statements by kind, volatile levers, bare register, asm-label aliases, builtins, attributes; the whole-body hand-asm routines and the GTE ops set apart) derived from the shared-body scanner with a per-token coverage assertion against the raw text, four known-true controls, the !FAKE marker split, --check (0 unmarked pins/asm, 0 orphan markers) and --strict (0 pins, 0 asm) gates, --sites for the delever ledger, a fixture self-test | delever.py, delever_cycle.sh, gte_consolidate.py, progress.py (+1) | repo paths, the shared-body scanner | LIVE |
| consolidate the GTE coprocessor inline-asm macros under Sony's names in one header and sweep dead lever macros | `gte_consolidate.py` | The Phase-36 T5 tool: every asm-bearing macro definition and direct GTE statement SIGNED by the build's own maspsx→as tail (template bytes with operands bound to fixed registers, operand counts, clobbers); one canonical text per signature named by PsyQ's inline_c.h convention (Sony's file supplies names and clobber lists, never opcode words) written to include/gte_inline.h; per file the canonical duplicates deleted, private names renamed, a variant with extra clobbers tried as canonical and kept as a marked <name>_m lever only when the object differs, direct statements rewritten into canonical calls (a concatenation of two included); every file judged through every recipe; --sweep deletes dead asm-bearing macros and drops a compound macro's inner asm when byte-neutral | delever_cycle.sh, lever_census.py | Sony's inline_c.h path, the census sites, the oracle | LIVE |
| convert a shared-body macro header into per-function plain-C headers included at each site | `macro_to_header.py` | The Phase-35 converter: every DEFINE_ macro body becomes a plain-C header under src/shared/<space>/ included at its site, the legacy name-parameterized headers converted, the registry text-edited, the macro header deleted; --plan / --apply / --finalize / --verify | share_body.py, tool_census.py | the macro form is this project's; a kit-born project shares headers from its first bank | LIVE |
| emit a function's target assembly as an inline-assembly body | `asm_verbatim.py` | Emits the file-scope inline-assembly body form from a disassembly file | docs/wave-playbook.md, draft_prechecks.py, gate_main.py, recover_route.py | repo asm layout | LIVE |
| extract inline-defined types and typedefs from a source file into a shared header | `build_engine_types.py` | Extracts inline-defined named types and typedefs from a source file into a shared header | inject_capped_externs.py, lift_types.py, uniquify_type.py | repo shared-header path | LIVE |
| flag address-named references whose address now has a curated name | `lint_symbol_refs.py` | Flags address-named references in committed sources whose address now has a curated name | .github/workflows/no-rom.yml, Makefile | repo symbol/src paths | LIVE |
| give each conflicting camp of a same-named type its own name | `uniquify_type.py` | Gives each conflicting camp of a same-named type its own name so every camp becomes liftable | — | repo src layout | REFERENCED |
| guard that every inline-assembly body still reproduces its target bytes | `verbatim_check.py` | Regression guard that every inline-assembly body still reproduces its target bytes | .github/workflows/no-rom.yml, lever_census.py, progress.py, verbatim_target_s.py (+1) | repo src layout | LIVE |
| judge one translation-unit edit by the bytes of its object in under a second | `delever_oracle.py` | The Phase-36 fast byte oracle: every object's exact build command captured once via make -n -W (the Makefile's own recipe, pad stage and -O0 overrides included), a candidate compiled in place with -o/-MF redirected to scratch and compared with the fleet run's object; --calibrate proves 100 % equality untouched, twin == primary and a positive control before any verdict is trusted | delever.py, delever_cycle.sh | the Makefile's object rules, the twin rule | LIVE |
| measure duplicate function bodies across the fleet and assert one source per unique function | `share_census.py` | The census of byte-identical function classes across every binary with their source forms and verdicts, plus the S1 invariant check with its exception ledger and a fixture self-test | Makefile, audit_binaries.py, dedup_integrate.py, delever.py (+8) | repo paths, the registry and signature schemas | LIVE |
| judge one translation-unit edit by the bytes of its object in under a second | `delever_oracle.py` | The Phase-36 fast byte oracle: every object's exact build command captured once via make -n -W (the Makefile's own recipe, pad stage and -O0 overrides included), a candidate compiled in place with -o/-MF redirected to scratch and compared with the fleet run's object; --calibrate proves 100 % equality untouched, twin == primary and a positive control before any verdict is trusted | delever.py, delever_cycle.sh, gte_consolidate.py | the Makefile's object rules, the twin rule | LIVE |
| measure duplicate function bodies across the fleet and assert one source per unique function | `share_census.py` | The census of byte-identical function classes across every binary with their source forms and verdicts, plus the S1 invariant check with its exception ledger and a fixture self-test | Makefile, audit_binaries.py, dedup_integrate.py, delever.py (+9) | repo paths, the registry and signature schemas | LIVE |
| mirror the curated symbol file into the analysis database with a real save | `ghidra_apply_symbols.sh` | Mirrors the curated symbol file into the analysis program headlessly, with a real save | — | repo symbol path, project name | REFERENCED |
| refuse, from one place, the command line of a tool the project has frozen | `frozen.py` | The one refusal a FROZEN tool prints from its main(): the tool, the successor and why; imports never exit (libraries stay usable) | aprop_autodraft.py, blocker_probe.py, canon_sig_reconcile.py, conform_decls.py (+10) | nothing | LIVE |
| regenerate a splitter-format target disassembly for a function no longer stubbed | `verbatim_target_s.py` | Regenerates a splitter-format target disassembly for a function that is no longer a stub; --gas emits the assemblable gas-syntax form ($-registers, .L labels, noreorder) that a web diff service accepts as a pasted target | decompme_replica.sh | repo build/asm layout | LIVE |
| run the de-lever batch cycle unattended: batch, verify by exit code, clean fleet gate, census, log, commit | `delever_cycle.sh` | The Phase-36 T3/T4 driver: per batch a clean-tree check, the oracle recalibrated when stale, delever --apply read by exit code and its final X/X line, the clean fleet run as the outer gate (218/218), the census rerun, the phase-log entry and the checkpoint headline, one commit per batch; stops on the first red with the files in place for --restore | delever.py | the batch size, the aliases, the fleet count | LIVE |
| run the de-lever batch cycle unattended: batch, verify by exit code, clean fleet gate, census, log, commit | `delever_cycle.sh` | The Phase-36 T3/T4 driver: per batch a clean-tree check, the oracle recalibrated when stale, delever --apply read by exit code and its final X/X line, the clean fleet run as the outer gate (218/218), the census rerun, the phase-log entry and the checkpoint headline, one commit per batch; stops on the first red with the files in place for --restore | delever.py, gte_consolidate.py | the batch size, the aliases, the fleet count | LIVE |
| run the share-body batch cycle unattended: batch, verify by exit code, log, commit, periodic clean fleet check | `share_body_cycle.sh` | The Phase-35 T5 driver for bucket new: per batch share_body --apply --bucket new --batches 1 --label new<k>, the gated N/N line read, the phase log entry appended, the bank committed the moment it is green, the clean fleet run every N batches — stops on the first red | — | repo paths, the phase-log format, the fleet count from config/check.*.sha | ORPHAN |
| share one byte-identical function class across its binaries through an include-at-site header, gated per binary | `share_body.py` | The Phase-35 successor of dedup_propagate + dedup_extend for the include-at-site form: exemplar by majority text, the header written once, every private copy replaced by the include at its position, per-binary byte gate with object comparison, bisect on red, the registry appended or extended by text, the exception ledger on refusal; --plan / --apply --bucket extend|new | aprop_autodraft.py, audit_binaries.py, auto_driver.py, blocker_probe.py (+18) | repo paths, the registry and signature schemas | LIVE |
| take register pins, asm statements, volatile and register levers out of matched C, byte-gated per site | `delever.py` | The Phase-36 de-lever engine and campaign tool: positional rewrites per lever class (pin -> plain declaration, barrier/keep-alive deleted, a launder deleted or turned into the assignment it is, a hand-placed instruction -> its C, the zero-register variable -> 0, a macro-carried site deleted/valued/refused by its macro's shape, volatile/register dropped); per body replay -> rung A strip-all -> rung B greedy through delever_oracle; the file is the write unit and its final compile through every recipe the proof; --plan/--apply batches (TUs parallel, exemplar files first; headers serial, includers parallel), the ledger keyed by tu+fn+addr with the body's nhash before/after, !FAKE markers on class A/B survivors, --redraw for bodies refused by an older tool, --restore from inflight.json, --scrub for orphan markers, --apply-body for T6/T7, --selftest with a stub oracle, --probe (T2) | delever_cycle.sh, lever_census.py | the census site records, the oracle | LIVE |
| take register pins, asm statements, volatile and register levers out of matched C, byte-gated per site | `delever.py` | The Phase-36 de-lever engine and campaign tool: positional rewrites per lever class (pin -> plain declaration, barrier/keep-alive deleted, a launder deleted or turned into the assignment it is, a hand-placed instruction -> its C, the zero-register variable -> 0, a macro-carried site deleted/valued/refused by its macro's shape, volatile/register dropped); per body replay -> rung A strip-all -> rung B greedy through delever_oracle; the file is the write unit and its final compile through every recipe the proof; --plan/--apply batches (TUs parallel, exemplar files first; headers serial, includers parallel), the ledger keyed by tu+fn+addr with the body's nhash before/after, !FAKE markers on class A/B survivors, --redraw for bodies refused by an older tool, --restore from inflight.json, --scrub for orphan markers, --apply-body for T6/T7, --selftest with a stub oracle, --probe (T2) | delever_cycle.sh, gte_consolidate.py, lever_census.py | the census site records, the oracle | LIVE |
| turn an inline-assembly body back into a stub the toolchain can reach | `verbatim_to_stub.py` | Turns an inline-assembly body back into an include-assembly stub | — | repo src/asm layout | REFERENCED |
## PROJECT-ONLY — project-only in code (the shape is a task; the code does not transfer)
+1
View File
@@ -41,4 +41,5 @@ typedef s64 M2C_UNK64;
#define NULL ((void *)0) /* m2c emits NULL for null pointers; byte-neutral (== 0) */
#endif
#include "gte_inline.h" /* Phase 36 T5: the GTE coprocessor macros, one definition each */
#endif /* COMMON_H */
+450
View File
@@ -0,0 +1,450 @@
/* include/gte_inline.h — the GTE coprocessor macros under Sony's names, ONE definition per signature (Phase 36 T5).
*
* Every macro here is the majority spelling the tree already carried in its per-TU definitions (byte-proven in the fleet),
* named by PsyQ's inline_c.h convention; the signature of each (the bytes the build's maspsx → as tail makes of the template,
* its operand counts, its clobbers) was computed by tools/gte_consolidate.py, which also deleted the per-TU duplicates.
* A definition that differs from the one here only by an extra clobber (a scheduling steer) lives on in its TU as <name>_m
* and is a marked lever, not a second spelling. Generated: do not edit by hand — regenerate with `tools/gte_consolidate.py --header`.
*/
#ifndef GTE_INLINE_H
#define GTE_INLINE_H
/* lw $12, 0( %0 );lw $13, 4( %0 );ctc2 $12, $0;ctc2 $13, $1;lw $12, 8( %0 );lw $13, 12( %0 ) … (1016 definitions in 244 files) */
#define gte_SetRotMatrix(r0) \
__asm__ volatile ( \
"lw $12, 0( %0 );" \
"lw $13, 4( %0 );" \
"ctc2 $12, $0;" \
"ctc2 $13, $1;" \
"lw $12, 8( %0 );" \
"lw $13, 12( %0 );" \
"lw $14, 16( %0 );" \
"ctc2 $12, $2;" \
"ctc2 $13, $3;" \
"ctc2 $14, $4" \
: \
: "r"( r0 ) \
: "$12", "$13", "$14" )
/* lw $12, 0( %0 );lw $13, 4( %0 );ctc2 $12, $0;ctc2 $13, $1;lw $12, 8( %0 );lw $13, 12( %0 ) … (1 definitions in 1 files; not a PsyQ name) */
#define gte_SetRotTransMatrix(r0) \
__asm__ volatile ( \
"lw $12, 0( %0 );" \
"lw $13, 4( %0 );" \
"ctc2 $12, $0;" \
"ctc2 $13, $1;" \
"lw $12, 8( %0 );" \
"lw $13, 12( %0 );" \
"lw $14, 16( %0 );" \
"ctc2 $12, $2;" \
"ctc2 $13, $3;" \
"ctc2 $14, $4;" \
"lw $12, 20( %0 );" \
"lw $13, 24( %0 );" \
"ctc2 $12, $5;" \
"lw $14, 28( %0 );" \
"ctc2 $13, $6;" \
"ctc2 $14, $7" \
: \
: "r"( r0 ) \
: "$12", "$13", "$14" )
/* lw $12, 20( %0 );lw $13, 24( %0 );ctc2 $12, $5;lw $14, 28( %0 );ctc2 $13, $6;ctc2 $14, $7 (1010 definitions in 243 files) */
#define gte_SetTransMatrix(r0) \
__asm__ volatile ( \
"lw $12, 20( %0 );" \
"lw $13, 24( %0 );" \
"ctc2 $12, $5;" \
"lw $14, 28( %0 );" \
"ctc2 $13, $6;" \
"ctc2 $14, $7" \
: \
: "r"( r0 ) \
: "$12", "$13", "$14" )
/* nop;nop;avsz3 (28 definitions in 21 files) */
#define gte_avsz3() __asm__ volatile ("nop;nop;avsz3")
/* nop;nop;avsz4 (57 definitions in 34 files) */
#define gte_avsz4() __asm__ volatile ("nop;nop;avsz4")
/* nop\n\tnop\n\tdpcl (136 definitions in 136 files) */
#define gte_dpcl() __asm__ __volatile__("nop\n\tnop\n\tdpcl")
/* mtc2 $0, $8 (136 definitions in 136 files; not a PsyQ name) */
#define gte_ldIR0z() __asm__ __volatile__("mtc2 $0, $8")
/* lwc2 $28, 0(%0) (136 definitions in 136 files; not a PsyQ name) */
#define gte_ldIRGB(p) __asm__ __volatile__("lwc2 $28, 0(%0)" :: "r"(p) : "memory")
/* lhu $12, 0( %0 );lhu $13, 6( %0 );lhu $14, 12( %0 );mtc2 $12, $9;mtc2 $13, $10;mtc2 $14, $ … (12 definitions in 6 files) */
#define gte_ldclmv(r0) \
__asm__ volatile ( \
"lhu $12, 0( %0 );" \
"lhu $13, 6( %0 );" \
"lhu $14, 12( %0 );" \
"mtc2 $12, $9;" \
"mtc2 $13, $10;" \
"mtc2 $14, $11" \
: \
: "r"( r0 ) \
: "$12", "$13", "$14" )
/* lhu $13, 4( %0 );lhu $12, 0( %0 );sll $13, $13, 16;or $12, $12, $13;mtc2 $12, $0;lwc2 $1, … (145 definitions in 143 files) */
#define gte_ldlv0(r0) \
__asm__ volatile ( \
"lhu $13, 4( %0 );" \
"lhu $12, 0( %0 );" \
"sll $13, $13, 16;" \
"or $12, $12, $13;" \
"mtc2 $12, $0;" \
"lwc2 $1, 8( %0 )" \
: \
: "r"( r0 ) \
: "$12", "$13" )
/* lb $13, 1( %0 );lb $12, 0( %0 );sll $13, $13, 16;sll $12, $12, 16;srl $12, $12, 16;or $12, … (1 definitions in 1 files; not a PsyQ name) */
#define gte_ldlvl_alt(r0) \
__asm__ volatile ( \
"lb $13, 1( %0 );" \
"lb $12, 0( %0 );" \
"sll $13, $13, 16;" \
"sll $12, $12, 16;" \
"srl $12, $12, 16;" \
"or $12, $12, $13;" \
"lb $13, 2( %0 );" \
"mtc2 $12, $0;" \
"mtc2 $13, $1" \
: \
: "r"( r0 ) \
: "$12", "$13" )
/* lwc2 $6, 0( %0 ) (19 definitions in 2 files) */
#define gte_ldrgb(r0) \
__asm__ volatile ( \
"lwc2 $6, 0( %0 )" \
: \
: "r"( r0 ) )
/* lwc2 $0, 0( %0 );lwc2 $1, 4( %0 ) (690 definitions in 307 files) */
#define gte_ldv0(r0) \
__asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 )" \
: \
: "r"( r0 ) )
/* lwc2 $0, 0( %0 );lwc2 $1, 4( %0 );lwc2 $2, 0( %1 );lwc2 $3, 4( %1 );lwc2 $4, 0( %2 );lwc2 … (227 definitions in 160 files) */
#define gte_ldv3(r0, r1, r2) \
__asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 0( %1 );" \
"lwc2 $3, 4( %1 );" \
"lwc2 $4, 0( %2 );" \
"lwc2 $5, 4( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) )
/* lwc2 $0, 0( %0 );lwc2 $1, 4( %0 );lwc2 $2, 0( %1 );lwc2 $3, 4( %1 );lwc2 $4, 0( %1 );lwc2 … (1 definitions in 1 files; not a PsyQ name) */
#define gte_ldv3_alt(r0, r1) \
__asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 0( %1 );" \
"lwc2 $3, 4( %1 );" \
"lwc2 $4, 0( %1 );" \
"lwc2 $5, 4( %1 )" \
: \
: "r"( r0 ), "r"( r1 ) )
/* lwc2 $0, 0( %0 );lwc2 $1, 4( %0 );lwc2 $2, 8( %0 );lwc2 $3, 12( %0 );lwc2 $4, 16( %0 );lwc … (163 definitions in 140 files) */
#define gte_ldv3c(r0) \
__asm__ volatile ( \
"lwc2 $0, 0( %0 );" \
"lwc2 $1, 4( %0 );" \
"lwc2 $2, 8( %0 );" \
"lwc2 $3, 12( %0 );" \
"lwc2 $4, 16( %0 );" \
"lwc2 $5, 20( %0 )" \
: \
: "r"( r0 ) )
/* lhu $12, 0(%0)\nlhu $13, 6(%0)\nlhu $14, 12(%0)\nmtc2 $12, $9\nmtc2 $13, $10\nmtc2 $14, $1 … (2 definitions in 2 files; not a PsyQ name) */
#define gte_mulcol(r0) \
__asm__ __volatile__ ( \
"lhu $12, 0(%0)\n" "lhu $13, 6(%0)\n" "lhu $14, 12(%0)\n" \
"mtc2 $12, $9\n" "mtc2 $13, $10\n" "mtc2 $14, $11\n" \
"nop\n" "nop\n" "mvmva 1, 0, 3, 3, 0\n" \
"mfc2 $12, $9\n" "mfc2 $13, $10\n" "mfc2 $14, $11\n" \
"sh $12, 0(%0)\n" "sh $13, 6(%0)\n" "sh $14, 12(%0)\n" \
: : "r"(r0) : "$12", "$13", "$14", "memory")
/* lhu $13, 4(%0)\nlhu $12, 0(%0)\nsll $13, $13, 16\nor $12, $12, $13\nmtc2 $12, $0\nlwc2 $1, … (2 definitions in 2 files; not a PsyQ name) */
#define gte_multrans(r0) \
__asm__ __volatile__ ( \
"lhu $13, 4(%0)\n" "lhu $12, 0(%0)\n" \
"sll $13, $13, 16\n" "or $12, $12, $13\n" \
"mtc2 $12, $0\n" "lwc2 $1, 8(%0)\n" \
"nop\n" "nop\n" "mvmva 1, 0, 0, 0, 0\n" \
"swc2 $25, 0(%0)\n" "swc2 $26, 4(%0)\n" "swc2 $27, 8(%0)\n" \
: : "r"(r0) : "$12", "$13", "memory")
/* nop;nop;nccs (15 definitions in 2 files) */
#define gte_nccs() __asm__ volatile ("nop;nop;nccs")
/* nop;nop;ncct (10 definitions in 2 files) */
#define gte_ncct() __asm__ volatile ("nop;nop;ncct")
/* nop;nop;nclip (186 definitions in 142 files) */
#define gte_nclip() __asm__ volatile ("nop;nop;nclip")
/* lwc2 $0, 0( %1 );lwc2 $1, 4( %1 );nop;nop;mvmva 1, 0, 0, 3, 0;swc2 $25, 0( %0 );swc2 $26, … (1 definitions in 1 files; not a PsyQ name) */
#define gte_rt_alt(r1, r0) \
__asm__ volatile ( \
"lwc2 $0, 0( %1 );" \
"lwc2 $1, 4( %1 );" \
"nop;" \
"nop;" \
"mvmva 1, 0, 0, 3, 0;" \
"swc2 $25, 0( %0 );" \
"swc2 $26, 4( %0 );" \
"swc2 $27, 8( %0 )" \
: \
: "r"( r1 ), "r"( r0 ) \
: "memory" )
/* nop;nop;mvmva 1, 0, 3, 3, 0 (9 definitions in 6 files) */
#define gte_rtir() __asm__ volatile ("nop;nop;mvmva 1, 0, 3, 3, 0")
/* nop;nop;rtps (681 definitions in 299 files) */
#define gte_rtps() __asm__ volatile ("nop;nop;rtps")
/* nop;nop;rtpt (249 definitions in 163 files) */
#define gte_rtpt() __asm__ volatile ("nop;nop;rtpt")
/* nop;nop;mvmva 1, 0, 0, 3, 0 (0 definitions in 0 files) */
#define gte_rtv0() \
__asm__ __volatile__( \
"nop\n" \
"nop\n" \
"mvmva 1, 0, 0, 3, 0\n" )
/* nop;nop;mvmva 1, 0, 0, 0, 0 (11 definitions in 10 files) */
#define gte_rtv0tr() __asm__ volatile ("nop;nop;mvmva 1, 0, 0, 0, 0")
/* swc2 $29, 0(%0) (136 definitions in 136 files; not a PsyQ name) */
#define gte_stORGB(p) __asm__ __volatile__("swc2 $29, 0(%0)" :: "r"(p) : "memory")
/* mfc2 $12, $9;mfc2 $13, $10;mfc2 $14, $11;sh $12, 0( %0 );sh $13, 6( %0 );sh $14, 12( %0 ) (11 definitions in 6 files) */
#define gte_stclmv(r0) \
__asm__ volatile ( \
"mfc2 $12, $9;" \
"mfc2 $13, $10;" \
"mfc2 $14, $11;" \
"sh $12, 0( %0 );" \
"sh $13, 6( %0 );" \
"sh $14, 12( %0 )" \
: \
: "r"( r0 ) \
: "$12", "$13", "$14", "memory" )
/* cfc2 $12, $31;nop;sw $12, 0( %0 ) (697 definitions in 309 files) */
#define gte_stflg(r0) \
__asm__ volatile ( \
"cfc2 $12, $31;" \
"nop;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
/* swc2 $25, 0( %0 );swc2 $26, 4( %0 );swc2 $27, 8( %0 ) (291 definitions in 283 files) */
#define gte_stlvnl(r0) \
__asm__ volatile ( \
"swc2 $25, 0( %0 );" \
"swc2 $26, 4( %0 );" \
"swc2 $27, 8( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $24, 0( %0 ) (186 definitions in 142 files) */
#define gte_stopz(r0) \
__asm__ volatile ( \
"swc2 $24, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $7, 0( %0 ) (51 definitions in 19 files) */
#define gte_stotz(r0) \
__asm__ volatile ( \
"swc2 $7, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $22, 0( %0 ) (15 definitions in 2 files) */
#define gte_strgb(r0) \
__asm__ volatile ( \
"swc2 $22, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $20, 0( %0 );swc2 $21, 0( %1 );swc2 $22, 0( %2 ) (4 definitions in 2 files) */
#define gte_strgb3(r0, r1, r2) \
__asm__ volatile ( \
"swc2 $20, 0( %0 );" \
"swc2 $21, 0( %1 );" \
"swc2 $22, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
/* swc2 $20, 4( %0 );swc2 $21, 12( %0 );swc2 $22, 20( %0 ) (6 definitions in 2 files) */
#define gte_strgb3_g3(r0) \
__asm__ volatile ( \
"swc2 $20, 4( %0 );" \
"swc2 $21, 12( %0 );" \
"swc2 $22, 20( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* mfc2 $12, $9\nmfc2 $13, $10\nmfc2 $14, $11\nsh $12, 0(%0)\nsh $13, 2(%0)\nsh $14, 4(%0)\n (33 definitions in 21 files) */
#define gte_stsv(r0) \
__asm__ __volatile__( \
"mfc2 $12, $9\n" \
"mfc2 $13, $10\n" \
"mfc2 $14, $11\n" \
"sh $12, 0(%0)\n" \
"sh $13, 2(%0)\n" \
"sh $14, 4(%0)\n" \
: : "r"(r0) : "$12", "$13", "$14", "memory")
/* swc2 $14, 0( %0 ) (679 definitions in 299 files) */
#define gte_stsxy(r0) \
__asm__ volatile ( \
"swc2 $14, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 0( %0 ) (6 definitions in 5 files) */
#define gte_stsxy0(r0) \
__asm__ volatile ( \
"swc2 $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 0( %0 );swc2 $13, 0( %1 ) (1 definitions in 1 files) */
#define gte_stsxy01(r0, r1) \
__asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 0( %1 )" \
: \
: "r"( r0 ), "r"( r1 ) \
: "memory" )
/* swc2 $13, 0( %0 ) (6 definitions in 5 files) */
#define gte_stsxy1(r0) \
__asm__ volatile ( \
"swc2 $13, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 0( %0 );swc2 $13, 0( %1 );swc2 $14, 0( %2 ) (219 definitions in 159 files) */
#define gte_stsxy3(r0, r1, r2) \
__asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 0( %1 );" \
"swc2 $14, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
/* swc2 $12, 8( %0 );swc2 $13, 12( %0 );swc2 $14, 16( %0 ) (250 definitions in 133 files) */
#define gte_stsxy3_f3(r0) \
__asm__ volatile ( \
"swc2 $12, 8( %0 );" \
"swc2 $13, 12( %0 );" \
"swc2 $14, 16( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 8( %0 );swc2 $13, 16( %0 );swc2 $14, 24( %0 ) (155 definitions in 124 files) */
#define gte_stsxy3_ft3(r0) \
__asm__ volatile ( \
"swc2 $12, 8( %0 );" \
"swc2 $13, 16( %0 );" \
"swc2 $14, 24( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 8( %0 );swc2 $13, 20( %0 );swc2 $14, 32( %0 ) (3 definitions in 2 files) */
#define gte_stsxy3_gt4(r0) \
__asm__ volatile ( \
"swc2 $12, 8( %0 );" \
"swc2 $13, 20( %0 );" \
"swc2 $14, 32( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $12, 0( %0 );swc2 $13, 4( %0 );swc2 $14, 8( %0 ) (163 definitions in 141 files) */
#define gte_stsxy3c(r0) \
__asm__ volatile ( \
"swc2 $12, 0( %0 );" \
"swc2 $13, 4( %0 );" \
"swc2 $14, 8( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $19, 0( %0 ) (15 definitions in 12 files) */
#define gte_stsz(r0) \
__asm__ volatile ( \
"swc2 $19, 0( %0 )" \
: \
: "r"( r0 ) \
: "memory" )
/* swc2 $17, 0( %0 );swc2 $18, 0( %1 );swc2 $19, 0( %2 ) (166 definitions in 142 files) */
#define gte_stsz3(r0, r1, r2) \
__asm__ volatile ( \
"swc2 $17, 0( %0 );" \
"swc2 $18, 0( %1 );" \
"swc2 $19, 0( %2 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
: "memory" )
/* swc2 $16, 0(%0);swc2 $17, 0(%1);swc2 $18, 0(%2);swc2 $19, 0(%3) (156 definitions in 140 files) */
#define gte_stsz4(r0, r1, r2, r3) \
__asm__ volatile ( \
"swc2 $16, 0( %0 );" \
"swc2 $17, 0( %1 );" \
"swc2 $18, 0( %2 );" \
"swc2 $19, 0( %3 )" \
: \
: "r"( r0 ), "r"( r1 ), "r"( r2 ), "r"( r3 ) \
: "memory" )
/* mfc2 $12, $19;nop;sra $12, $12, 2;sw $12, 0( %0 ) (209 definitions in 146 files) */
#define gte_stszotz(r0) \
__asm__ volatile ( \
"mfc2 $12, $19;" \
"nop;" \
"sra $12, $12, 2;" \
"sw $12, 0( %0 )" \
: \
: "r"( r0 ) \
: "$12", "memory" )
#endif /* GTE_INLINE_H */
+28
View File
@@ -412,6 +412,34 @@ accumulate here as the phase produces them.**
T1 hashes; the cycle's FINISH mode; the file-scope pseudo-body now holds file-scope asm statements. **T4 ☑.** Rules check (P6) done
after T4 (re-read complete).
- **S98 — T5 opened: the GTE consolidation — design, the signature engine, the header step (gated).** Reconnaissance (R37): 327 GTE macro
names (215 `gte_*`, 112 address-suffixed private copies), 9,102 definitions, 1,140 direct GTE statements, 5,751 macro-carried uses;
`mipsel-linux-gnu-as` and the maspsx tail both reject `rtps`/`mvmva` — the mnemonics are GAS macros in `include/gte_macros.inc` reached
through `include_asm.h`; Sony's converted `inline_c.h` is unreliable for opcode words (`gte_rtps` = `.word 0x0000007f`, which assembles to a
bare 0x7f) and serves for names, operand counts and clobber lists only; 3,269 macro uses and 968 direct statements carry a `"memory"`
clobber, of which Sony's own STORE macros account for most — the steers are the clobbers on loads and compute ops. **Design (X1):** a
SIGNATURE = (the bytes the build's own maspsx→as tail makes of the template with %k bound to $4–$7/$2–$3, #outputs, #inputs) — `nop;nop;rtps`,
`.word 0x4a180001` and `cop2 0x0180001` are one; one canonical text per signature (the majority body, byte-proven in the fleet — the header
ships nothing new), Sony's name when the operand counts agree, Sony's clobber set canonical EVEN when every definition carries the steer
(the body synthesized: `gte_rtv0`'s six definitions all added `"memory"`); a definition with the canonical bytes and other clobbers is a
lever variant `<name>_m` / `_v<hash>` — the apply TRIES it as canonical first and keeps it only when the object differs; direct statements
rewritten into canonical calls when bytes and clobbers agree (a concatenation of two canonical macros included), marked as levers when
their clobbers exceed the canonical's, counted when they match nothing. **Built:** `tools/gte_consolidate.py` (`--inventory`, `--header`,
`--apply`, `--sweep`, `--status`, `--selftest`: `OK — signatures rtps=0100184a ldv0=000080c8040081c8; table 1 canonical / 1 variant; fixture
edits 8`), the cycle's `MODE=gte`, the census's `gte-lever` class (a variant's uses; a direct statement whose clobbers exceed its canonical's,
by a read-only lookup of the tool's signature cache) + `per_tu_asm_macro_definitions` + the strict gate on both (selftest OK), the dictionary
+ SETUP rows, kit corpus. **The inventory:** `9540 asm-bearing macro definitions (9102 GTE, 9101 signed), 1140 direct GTE statements (1137
signed)` → **`canonical table — 50 signatures (41 with Sony's name), 8402 canonical definitions; 10 lever variants holding 699 definitions;
1 unsigned`** (variants: `gte_rtv0tr_m` 307 defs / 293 files, `gte_ldv0_m` 179 / 163, `gte_ldrgb_m` 137, `gte_SetRotMatrix_m` 30,
`gte_SetTransMatrix_m` 25, `gte_rtv0_m` 6, `gte_rtir_m` 3, `gte_rt_m` 1; three store macros MISSING Sony's `"memory"`: `gte_stsz4_vda39` 10,
`gte_stsxy_vda39` 1, `gte_stclmv_v41be` 1; `gte_ldlvl_v02f2` with extra register clobbers 1; the unsigned one is a two-statement macro in
`md_MAIN_019`); direct statements: 713 → canonical calls, 226 lever, 201 matching no canonical signature. **The header step:**
`include/gte_inline.h` (50 macros; 9 project names beside Sony's 41: `gte_ldIR0z`, `gte_ldIRGB`, `gte_stORGB`, `gte_SetRotTransMatrix`,
`gte_mulcol`, `gte_multrans`, `gte_ldlvl_alt`, `gte_ldv3_alt`, `gte_rt_alt`), included from `include/common.h` → every TU sees it, the local
definitions still win as later definitions → **R22 `check-all: 218 passed, 0 failed of 218` (`wall=112.72 s`,
`.run/P36/baseline/r22_t5_header.log`)**. Census after the header step (before any apply): GTE levers (clobbers beyond the canonical macro's): 49 sites (0 via a variant macro, 49 direct) · marked 0 · UNMARKED 49 · unsigned GTE statements 3 · per-TU asm macro definitions outside the GTE header: 9,540 {'gte': 9102, 'launder': 428, 'instruction': 9, 'barrier': 1} (GTE variants 0). Next: the apply batches
(`tools/delever_cycle.sh 1 4 400 gte`, detached), then `--sweep`, then the T5 verify line.
## 🛑 SESSION CHECKPOINT — S98 (2026-09-09): T0 ☑ T1 ☑ T1b ☑ T2 ☑ T3 ☑ T4 ☑ — all committed (T4 = 13 batch commits `a605e1dba`…`f087282a3` + the tool commits; this close); NEXT = T5 the GTE consolidation + the dead lever-macro sweep | the number: 33,625 sites in 12,501 bodies (1,728 distinct) · marked 33,625 · UNMARKED 0 · orphans 0 — `lever_census --check` OK
### 0. How to use this block
+10 -5
View File
@@ -9,7 +9,7 @@
# and src/ + the ledger/batch record/log + the census + the phase log are committed (R42).
# Stops on the first red (a non-zero tool exit, a final N/M with N != M, a fleet run that is not 218/218) — the batch's files are then
# still in place: inspect, then `tools/delever.py --restore` (never git checkout, R102). Stops cleanly when nothing is drawable.
# Usage: [LABEL_PREFIX=t3_] [TASK=T4] [REDRAW="REFUSED NOTHING-USABLE"] tools/delever_cycle.sh START END [BATCH=300] [MODE=tus|headers] [ONLY="alias1 alias2 …"]
# Usage: [LABEL_PREFIX=t3_] [TASK=T4] [REDRAW="REFUSED NOTHING-USABLE"] tools/delever_cycle.sh START END [BATCH=300] [MODE=tus|headers|gte] [ONLY="alias1 alias2 …"]
# RUN IT DETACHED from the Claude Code harness (its low-memory guard kills a long BACKGROUND task — batch tus3 died mid-apply, S98):
# setsid nohup bash -c 'TASK=T4 tools/delever_cycle.sh 3 12 300 tus; echo "cycle exit=$?"' > .run/P36/delever/cycle_<x>.log 2>&1 &
# and watch the log (a tiny waiter: `until grep -q 'cycle exit=' <log>; do sleep 60; done`). A killed batch: `tools/delever.py --restore`.
@@ -20,7 +20,12 @@ set -o pipefail
cd "$(dirname "$0")/.." || exit 2
START=$1; END=$2; BATCH=${3:-300}; MODE=${4:-tus}; ONLY=${5:-}
[ -n "$START" ] && [ -n "$END" ] || { echo "usage: $0 START END [BATCH] [tus|headers] [ONLY]"; exit 2; }
case "$MODE" in tus) HFLAG="";; headers) HFLAG="--headers";; *) echo "MODE must be tus or headers"; exit 2;; esac
case "$MODE" in
tus) HFLAG=""; TOOL="tools/delever.py --apply"; VPAT='delever: batch .*';;
headers) HFLAG="--headers"; TOOL="tools/delever.py --apply"; VPAT='delever: batch .*';;
gte) HFLAG=""; TOOL="tools/gte_consolidate.py --apply"; VPAT='gte_consolidate: batch .*';; # T5: the GTE consolidation per file
*) echo "MODE must be tus, headers or gte"; exit 2;;
esac
ONLYFLAG=""; [ -n "$ONLY" ] && ONLYFLAG="--only $ONLY"
REDRAWFLAG=""; [ -n "${REDRAW:-}" ] && REDRAWFLAG="--redraw $REDRAW" # e.g. REDRAW="REFUSED NOTHING-USABLE" after a tool fix
FLEET=$(ls config/check.*.sha | wc -l)
@@ -41,11 +46,11 @@ for k in $(seq "$START" "$END"); do
[ -z "$(git status --short --porcelain src | grep '^??')" ] || { echo "cycle: untracked files under src/ (a dotfile probe?) — sweep them first"; exit 3; }
$PY tools/delever_oracle.py --status >/dev/null 2>&1 || $PY tools/delever_oracle.py --calibrate ov_SC04_011 ov_SC03_015 ov_SC03_014 main -j 16 > .run/P36/delever/calibrate_${label}.log 2>&1 \
|| { echo "cycle: calibration FAILED"; tail -5 .run/P36/delever/calibrate_${label}.log; exit 1; }
{ /usr/bin/time -f "$label wall=%e s" $PY tools/delever.py --apply --batch "$BATCH" --label "$label" $HFLAG $ONLYFLAG $REDRAWFLAG -j 12; echo "exit=$?"; } > "$log" 2>&1
{ /usr/bin/time -f "$label wall=%e s" $PY $TOOL --batch "$BATCH" --label "$label" $HFLAG $ONLYFLAG $REDRAWFLAG -j 12; echo "exit=$?"; } > "$log" 2>&1
fi
grep -q '^exit=0$' "$log" || { echo "cycle: batch $label — delever exited non-zero"; tail -4 "$log"; exit 1; }
if grep -q 'nothing to do (no drawable file)' "$log"; then echo "cycle: nothing drawable at $label — done"; exit 0; fi
vline=$(grep -oE 'delever: batch .*' "$log" | tail -1)
if grep -qE 'nothing to do' "$log"; then echo "cycle: nothing drawable at $label — done"; exit 0; fi
vline=$(grep -oE "$VPAT" "$log" | tail -1)
[ -n "$vline" ] || { echo "cycle: batch $label — no verify line in $log (R32: an empty line is not a pass)"; tail -3 "$log"; exit 1; }
final=$(echo "$vline" | grep -oE 'final [0-9]+/[0-9]+' | grep -oE '[0-9]+/[0-9]+')
[ -n "$final" ] && [ "${final%/*}" = "${final#*/}" ] || { echo "cycle: batch $label — $vline"; exit 1; }
+922
View File
@@ -0,0 +1,922 @@
#!/usr/bin/env python3
"""gte_consolidate.py — the GTE coprocessor asm consolidated under Sony's names, and the dead lever-macro sweep (Phase 36 T5).
tools/gte_consolidate.py --inventory # every asm-bearing macro definition + every direct GTE statement, SIGNED by the
# build's own maspsx→as tail; the canonical table → .run/P36/gte/{inventory,canonical}.json
tools/gte_consolidate.py --header # write include/gte_inline.h (one canonical macro per signature) + the include in common.h
tools/gte_consolidate.py --apply --batch N --label L [--only …] [-j 12]
# per file: canonical-duplicate definitions deleted, private/variant names renamed, direct
# statements rewritten into canonical calls; every file judged through every recipe
tools/gte_consolidate.py --sweep --label L [--only …] # dead asm-bearing macros deleted; a compound macro's inner asm dropped when byte-neutral
tools/gte_consolidate.py --status | --selftest
A SIGNATURE is what the assembler makes of a template, not how it is spelled: the template with its operands bound to fixed registers
(%0.. → $4 $5 $6 $7 for inputs, $2 $3 for outputs) is assembled through the SAME tail as the build (maspsx --aspsx-version=2.56 →
mipsel-linux-gnu-as with include/macro.inc + include/gte_macros.inc, where `rtps`, `mvmva`, `sqr 0` are GAS macros), and the signature
is (bytes, #outputs, #inputs). `nop;nop;rtps`, `nop;nop;.word 0x4a180001` and `nop;nop;cop2 0x0180001` are one signature. Sony's
converted inline_c.h is the source of NAMES and of operand/clobber lists only — its `.word` opcode spellings assemble to nothing
useful (`gte_rtps` there is `.word 0x0000007f`).
THE CANONICAL TABLE: definitions grouped by signature; the canonical CLOBBER set of a signature is Sony's for that name (a store macro
legitimately clobbers "memory"), else the majority's; the canonical NAME is the most-used `gte_*` name in the group (Sony's convention),
else a name derived from gte_macros.inc, else `gte_seq_<hash>`; the canonical TEXT is the majority definition text (byte-proven in the
fleet — the header ships nothing that was not already in the tree). A definition with the canonical signature but EXTRA clobbers (the
`"memory"` a drafter added to a load or a compute op to force the schedule) is a LEVER VARIANT: it stays per TU, renamed `<name>_m`, and
its uses carry `// !FAKE:` markers for T7. A direct statement is rewritten into `NAME(args)` (or `NAME1(args); NAME2();` for the
concatenation of two canonical macros) when bytes and clobbers agree; with extra clobbers it is a lever variant too (marked); a sequence
that matches nothing is counted and left. Every file's final text is judged through every recipe of the file (tools/delever_oracle.py);
the clean fleet run gates every batch (tools/delever_cycle.sh MODE=gte).
"""
import argparse
import collections
import hashlib
import json
import os
import pathlib
import re
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor
REPO = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "tools"))
import delever as dl # noqa: E402
import delever_oracle as oracle # noqa: E402
import lever_census as lc # noqa: E402
import share_census as sc # noqa: E402
RUN = REPO / ".run" / "P36" / "gte"
SIG_CACHE = RUN / "sig_cache.json"
INVENTORY = RUN / "inventory.json"
CANON = RUN / "canonical.json"
LEDGER = REPO / ".run" / "P36" / "delever" / "ledger.jsonl"
HEADER = "include/gte_inline.h"
COMMON = "include/common.h"
SONY = REPO / "tools/psyq/conv47/psyq-4_7-converted/include/inline_c.h"
MASPSX = ".venv/bin/python tools/maspsx/maspsx.py --aspsx-version=2.56 --expand-div"
AS = "mipsel-linux-gnu-as -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0"
FAKE = dl.FAKE
DEFINE_LINE = re.compile(r"^[ \t]*#[ \t]*define[ \t]+([A-Za-z_]\w*)(\([^)]*\))?[ \t]*(.*)$")
_LOCK = dl._LOCK
class Refuse(Exception):
pass
# ----------------------------------------------------------------------------------------------------------------------
# the signature engine
# ----------------------------------------------------------------------------------------------------------------------
def parse_asm(inner):
"""(template, outs, ins, clobbers) — delever's operand parser plus the clobber section."""
tmpl, outs, ins = dl.parse_asm_operands(inner)
s = inner.strip()
i, n = 0, len(s)
while i < n:
if s[i] in " \t\r\n":
i += 1
continue
mm = lc.STRLIT.match(s, i)
if not mm:
break
i = mm.end()
rest = s[i:].strip()
secs = lc._split_sections(rest[1:]) if rest.startswith(":") else []
clob = [m.group(1) for m in re.finditer(r'"([^"]*)"', secs[2])] if len(secs) > 2 else []
return tmpl, outs, ins, sorted(set(clob))
def bind_regs(tmpl, nout, nin):
regs = ["$2", "$3"][:nout] + ["$4", "$5", "$6", "$7"][:nin]
def sub(m):
k = int(m.group(1))
return regs[k] if k < len(regs) else m.group(0)
t = re.sub(r"%(\d+)", sub, tmpl)
t = re.sub(r"\\n|\\t", "\n", t)
return t
_sig_cache = None
def _load_cache():
global _sig_cache
if _sig_cache is None:
_sig_cache = json.loads(SIG_CACHE.read_text()) if SIG_CACHE.exists() else {}
return _sig_cache
def _save_cache():
RUN.mkdir(parents=True, exist_ok=True)
tmp = SIG_CACHE.with_suffix(".tmp")
tmp.write_text(json.dumps(_sig_cache, indent=0))
os.replace(tmp, SIG_CACHE)
def asm_bytes(bound_template):
"""hex of the .text bytes the build's tail makes of the template (cached by text); 'ERROR: …' when it does not assemble."""
cache = _load_cache()
key = bound_template
if key in cache:
return cache[key]
d = RUN / "probe"
d.mkdir(parents=True, exist_ok=True)
tag = hashlib.sha1(key.encode()).hexdigest()[:10]
s_path, o_path, b_path = d / f"{tag}.s", d / f"{tag}.o", d / f"{tag}.bin"
s_path.write_text("\t.include \"include/macro.inc\"\n\t.include \"gte_macros.inc\"\n\t.set\tnoreorder\n\t.set\tnoat\n\t.text\n\t.align\t2\n"
"\t.globl\tprobe\n\t.ent\tprobe\nprobe:\n" + bound_template + "\n\t.end\tprobe\n")
cmd = f"set -o pipefail; cat {s_path} | {MASPSX} | {AS} -o {o_path} && mipsel-linux-gnu-objcopy -O binary -j .text {o_path} {b_path}"
r = subprocess.run(["bash", "-c", cmd], cwd=REPO, capture_output=True, text=True)
if r.returncode != 0 or not b_path.exists():
res = "ERROR: " + (r.stderr.strip().splitlines() or ["?"])[-1][:120]
else:
res = b_path.read_bytes().hex()
for p in (s_path, o_path, b_path):
try:
p.unlink()
except OSError:
pass
cache[key] = res
return res
def signature(inner):
"""dict(bytes, nout, nin, clob, tmpl, ok) for the text between an asm statement's parens."""
tmpl, outs, ins, clob = parse_asm(inner)
bound = bind_regs(tmpl, len(outs), len(ins))
b = asm_bytes(bound)
return dict(bytes=b, nout=len(outs), nin=len(ins), clob=clob, tmpl=tmpl, ok=not b.startswith("ERROR"),
ins=[e for _, e in ins], outs=[e for _, e in outs])
def set_clobbers(body, clob):
"""the macro body with the asm statement's clobber section replaced by `clob` (removed when empty)."""
m_asm = lc.ASM_KW.search(body)
o = body.find("(", m_asm.end())
c = lc._paren_span(body, o)
inner = body[o + 1:c]
# the sections: template, then up to three ':'-separated parts outside string literals
s = inner
i, n = 0, len(s)
while i < n:
if s[i] in " \t\r\n":
i += 1
continue
mm = lc.STRLIT.match(s, i)
if not mm:
break
i = mm.end()
head, rest = s[:i], s[i:]
secs = lc._split_sections(rest.strip()[1:]) if rest.strip().startswith(":") else []
while len(secs) < 3:
secs.append("")
secs[2] = ", ".join(f'"{x}"' for x in clob) if clob else ""
while secs and secs[-1].strip() == "":
secs.pop()
new_inner = head.rstrip() + ("".join(" : " + x.strip() for x in secs) if secs else "") + " "
return body[:o + 1] + new_inner + body[c:]
def sig_key(s):
return f"{s['bytes']}|{s['nout']}|{s['nin']}"
# ----------------------------------------------------------------------------------------------------------------------
# the inventory
# ----------------------------------------------------------------------------------------------------------------------
def asm_inner(body_masked):
"""the text between the parens of the FIRST asm statement in a macro body (None if none / more than one)."""
ms = list(lc.ASM_KW.finditer(body_masked))
if len(ms) != 1:
return None
o = body_masked.find("(", ms[0].end())
c = lc._paren_span(body_masked, o) if o >= 0 else -1
return body_masked[o + 1:c] if (o >= 0 and c > 0) else None
def define_params(raw, l0):
m = DEFINE_LINE.match(raw.split("\n")[l0 - 1])
return (m.group(2) or "") if m else ""
def sony_table():
"""name -> (nout, nin, clobbers) from Sony's inline_c.h — the parts of it that are plain C text."""
out = {}
if not SONY.exists():
return out
raw = SONY.read_text(errors="surrogateescape")
for (l0, l1, name, body) in lc.define_blocks(raw):
inner = asm_inner(sc.mask_text(body) if "/*" in body else body)
if inner is None:
continue
try:
_, outs, ins, clob = parse_asm(inner)
except Exception: # noqa: BLE001
continue
out[name] = (len(outs), len(ins), clob)
return out
def inc_names():
"""bytes -> name for gte_macros.inc's single-op macros (assembled as `nop;nop;<macro>`), for signatures without a gte_* name."""
raw = (REPO / "include/gte_macros.inc").read_text(errors="surrogateescape")
out = {}
for m in re.finditer(r"^\.macro\s+(\w+)\s*$", raw, re.M):
b = asm_bytes(f"nop\nnop\n{m.group(1)}")
if not b.startswith("ERROR"):
out[f"{b}|0|0"] = m.group(1)
return out
def files_under_src():
return [p for p in sorted((REPO / "src").rglob("*")) if p.suffix in (".c", ".h") and not p.name.startswith(".")]
def inventory(jobs=12, quiet=False):
"""definitions: [{tu, l0, l1, name, params, body, kind, sig}], direct: [{tu, line, col, inner, sig}], uses: Counter(name)."""
sites = dl.load_sites()
uses = collections.Counter(s["via"] for s in sites if s.get("via") and s["kind"] == "gte")
direct_sites = [s for s in sites if s["kind"] == "gte" and not s.get("via")]
defs, direct = [], []
for p in files_under_src():
raw = p.read_text(errors="surrogateescape")
if "#define" not in raw and "asm" not in raw:
continue
rel = p.relative_to(REPO).as_posix()
for (l0, l1, name, body) in lc.define_blocks(raw):
kind = lc.macro_kind(body)
if not kind:
continue
d = dict(tu=rel, l0=l0, l1=l1, name=name, params=define_params(raw, l0), body=body, kind=kind, sig=None)
if kind == "gte":
inner = asm_inner(sc.mask_text(body) if ("/*" in body or "//" in body) else body)
if inner is not None:
try:
d["sig"] = signature(inner)
except Exception as ex: # noqa: BLE001
d["sig"] = dict(bytes=f"ERROR: {ex}"[:100], ok=False, nout=0, nin=0, clob=[], tmpl="", ins=[], outs=[])
defs.append(d)
by_tu = collections.defaultdict(list)
for s in direct_sites:
by_tu[s["tu"]].append(s)
for tu, ss in by_tu.items():
raw = (REPO / tu).read_text(errors="surrogateescape")
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
for s in ss:
pos = ls[s["line"] - 1] + s["col"] - 1
o = m.find("(", pos)
c = lc._paren_span(m, o) if o >= 0 else -1
if o < 0 or c < 0:
continue
inner = m[o + 1:c]
try:
sg = signature(inner)
except Exception as ex: # noqa: BLE001
sg = dict(bytes=f"ERROR: {ex}"[:100], ok=False, nout=0, nin=0, clob=[], tmpl="", ins=[], outs=[])
direct.append(dict(tu=tu, line=s["line"], col=s["col"], fn=s.get("fn"), inner=inner, sig=sg, marked=s.get("marked", False)))
_save_cache()
inv = dict(generated=time.strftime("%Y-%m-%d %H:%M"), definitions=defs, direct=direct, uses=dict(uses))
RUN.mkdir(parents=True, exist_ok=True)
INVENTORY.write_text(json.dumps(inv))
if not quiet:
gte = [d for d in defs if d["kind"] == "gte"]
print(f"gte_consolidate --inventory: {len(defs)} asm-bearing macro definitions ({len(gte)} GTE, {sum(1 for d in gte if d['sig'] and d['sig']['ok'])} signed), "
f"{len(direct)} direct GTE statements ({sum(1 for d in direct if d['sig']['ok'])} signed), {sum(uses.values())} macro-carried GTE uses")
return inv
# ----------------------------------------------------------------------------------------------------------------------
# the canonical table
# ----------------------------------------------------------------------------------------------------------------------
def canonical_table(inv, quiet=False):
sony = sony_table()
incn = inc_names()
groups = collections.defaultdict(list)
for d in inv["definitions"]:
if d["kind"] == "gte" and d["sig"] and d["sig"]["ok"]:
groups[sig_key(d["sig"])].append(d)
table, variants, unsigned = {}, {}, [d for d in inv["definitions"] if d["kind"] == "gte" and not (d["sig"] and d["sig"]["ok"])]
names_taken = set()
for key, ds in sorted(groups.items(), key=lambda kv: -len(kv[1])):
names = collections.Counter(d["name"] for d in ds)
# candidates: the names with their address / variant suffixes stripped (`gte_stsxy01_80181E04` is Sony's gte_stsxy01;
# `LDV0_DA34` is a private copy of gte_ldv0), Sony's spelling preferred, then the most-used gte_* spelling
stripped = collections.Counter()
for n, cnt in names.items():
base = re.sub(r"(_[0-9A-Fa-f]{3,8}|X|_m|_A|_B)$", "", n)
base = re.sub(r"_v[0-9a-f]{4}$", "", base)
stripped[base] += cnt
nout0, nin0 = ds[0]["sig"]["nout"], ds[0]["sig"]["nin"]
# a Sony name is taken only when the operand counts agree with Sony's macro (a private compound that borrowed the name is not it)
sony_named = [n for n, _ in stripped.most_common() if n in sony and (sony[n][0], sony[n][1]) == (nout0, nin0)]
gte_names = [n for n, _ in stripped.most_common() if n.startswith("gte_")]
name = (sony_named or gte_names or [None])[0]
if name is None:
base = incn.get(key)
priv = stripped.most_common(1)[0][0]
name = f"gte_{base}" if base else f"gte_{re.sub(r'[^A-Za-z0-9]', '', priv).lower()}"
if name in sony and not sony_named:
name = f"{name}_alt"
while name in names_taken:
name = f"{name}_alt"
names_taken.add(name)
clob_sets = collections.Counter(tuple(d["sig"]["clob"]) for d in ds)
if name in sony:
canon_clob = tuple(sony[name][2])
else:
canon_clob = clob_sets.most_common(1)[0][0]
canon_defs = [d for d in ds if tuple(d["sig"]["clob"]) == canon_clob]
synthesized = False
if canon_defs:
texts = collections.Counter((d["params"], re.sub(r"[ \t]+\n", "\n", d["body"]).strip()) for d in canon_defs)
(params, body), _ = texts.most_common(1)[0]
else:
# nobody spells it with Sony's clobbers (every definition carries the steer): the canonical text is the majority body
# with its clobber list set to Sony's — the steer must not become canonical by being universal
texts = collections.Counter((d["params"], re.sub(r"[ \t]+\n", "\n", d["body"]).strip()) for d in ds)
(params, body), _ = texts.most_common(1)[0]
body = set_clobbers(body, list(canon_clob))
synthesized = True
table[key] = dict(name=name, params=params, body=body, clob=list(canon_clob), defs=len(canon_defs), files=len({d["tu"] for d in canon_defs}),
synthesized=synthesized,
names=dict(names), sony=(name in sony), nin=ds[0]["sig"]["nin"], nout=ds[0]["sig"]["nout"], tmpl=ds[0]["sig"]["tmpl"])
for cs, cnt in clob_sets.items():
if cs != canon_clob:
extra = sorted(set(cs) - set(canon_clob))
fewer = sorted(set(canon_clob) - set(cs))
vname = f"{name}_m" if extra == ["memory"] and not fewer else f"{name}_v{hashlib.sha1('|'.join(cs).encode()).hexdigest()[:4]}"
variants[f"{key}|{'|'.join(cs)}"] = dict(name=vname, of=name, key=key, clob=list(cs), extra=extra, fewer=fewer, defs=cnt,
files=len({d["tu"] for d in ds if tuple(d["sig"]["clob"]) == cs}))
out = dict(generated=time.strftime("%Y-%m-%d %H:%M"), canonical=table, variants=variants, unsigned=[dict(tu=d["tu"], name=d["name"], l0=d["l0"]) for d in unsigned])
CANON.write_text(json.dumps(out, indent=1))
if not quiet:
print(f"gte_consolidate: canonical table — {len(table)} signatures ({sum(1 for t in table.values() if t['sony'])} with Sony's name), "
f"{sum(t['defs'] for t in table.values())} canonical definitions; {len(variants)} lever variants holding {sum(v['defs'] for v in variants.values())} definitions; "
f"{len(unsigned)} unsigned definitions")
for v in sorted(variants.values(), key=lambda v: -v["defs"])[:12]:
print(f" variant {v['name']:24s} of {v['of']:18s} defs {v['defs']:4d} files {v['files']:4d} extra {v['extra']} fewer {v['fewer']}")
for u in unsigned[:8]:
print(f" unsigned {u['tu']}:{u['l0']} {u['name']}")
return out
def render_header(canon):
L = ["/* include/gte_inline.h — the GTE coprocessor macros under Sony's names, ONE definition per signature (Phase 36 T5).",
" *",
" * Every macro here is the majority spelling the tree already carried in its per-TU definitions (byte-proven in the fleet),",
" * named by PsyQ's inline_c.h convention; the signature of each (the bytes the build's maspsx → as tail makes of the template,",
" * its operand counts, its clobbers) was computed by tools/gte_consolidate.py, which also deleted the per-TU duplicates.",
" * A definition that differs from the one here only by an extra clobber (a scheduling steer) lives on in its TU as <name>_m",
" * and is a marked lever, not a second spelling. Generated: do not edit by hand — regenerate with `tools/gte_consolidate.py --header`.",
" */", "#ifndef GTE_INLINE_H", "#define GTE_INLINE_H", ""]
for key, t in sorted(canon["canonical"].items(), key=lambda kv: kv[1]["name"]):
body_lines = t["body"].split("\n")
L.append(f"/* {t['tmpl'][:90]}{' …' if len(t['tmpl']) > 90 else ''} ({t['defs']} definitions in {t['files']} files{'' if t['sony'] else '; not a PsyQ name'}) */")
if len(body_lines) == 1:
L.append(f"#define {t['name']}{t['params']} {body_lines[0]}")
else:
L.append(f"#define {t['name']}{t['params']} \\")
for i, bl in enumerate(body_lines):
L.append(f" {bl.strip()}" + (" \\" if i < len(body_lines) - 1 else ""))
L.append("")
L += ["#endif /* GTE_INLINE_H */", ""]
return "\n".join(L)
def write_header(canon):
RUN.mkdir(parents=True, exist_ok=True)
(REPO / HEADER).write_text(render_header(canon))
c = REPO / COMMON
t = c.read_text()
if '#include "gte_inline.h"' not in t:
t = t.replace("#endif /* COMMON_H */", '#include "gte_inline.h" /* Phase 36 T5: the GTE coprocessor macros, one definition each */\n#endif /* COMMON_H */', 1)
c.write_text(t)
print(f"gte_consolidate --header: {HEADER} written ({len(canon['canonical'])} macros); {COMMON} includes it")
# ----------------------------------------------------------------------------------------------------------------------
# --apply: per file
# ----------------------------------------------------------------------------------------------------------------------
def plan_files(inv, canon, only=None):
"""tu -> dict(defs=[definitions to delete/rename], direct=[direct statements], renames={old: new})"""
by_key = canon["canonical"]
var_by = {v["key"] + "|" + "|".join(v["clob"]): v for v in canon["variants"].values()}
plan = collections.defaultdict(lambda: dict(defs=[], direct=[], renames={}))
for d in inv["definitions"]:
if d["kind"] != "gte" or not (d["sig"] and d["sig"]["ok"]):
continue
key = sig_key(d["sig"])
t = by_key.get(key)
if not t:
continue
vkey = key + "|" + "|".join(d["sig"]["clob"])
if tuple(d["sig"]["clob"]) == tuple(t["clob"]):
plan[d["tu"]]["defs"].append(dict(d, action="delete", canonical=t["name"]))
if d["name"] != t["name"]:
plan[d["tu"]]["renames"][d["name"]] = t["name"]
elif vkey in var_by:
v = var_by[vkey]
plan[d["tu"]]["defs"].append(dict(d, action="rename", canonical=t["name"], variant=v["name"], extra=v["extra"], fewer=v["fewer"]))
if d["name"] != v["name"]:
plan[d["tu"]]["renames"][d["name"]] = v["name"]
for s in inv["direct"]:
plan[s["tu"]]["direct"].append(s)
files = {tu: p for tu, p in plan.items() if p["defs"] or p["direct"]}
if only:
files = {tu: p for tu, p in files.items() if any(o == tu or tu.endswith("/" + o) or ("/" + o + "/") in tu for o in only)}
return files
def direct_rewrite(s, canon):
"""('call', text, name) | ('lever', variant_name, extra) | ('none', why, None) for a direct GTE statement."""
sg = s["sig"]
if not sg["ok"]:
return "none", "unsigned", None
key = sig_key(sg)
t = canon["canonical"].get(key)
if t and sg["nout"] == 0:
if tuple(sg["clob"]) == tuple(t["clob"]):
return "call", f"{t['name']}({', '.join(sg['ins'])});", t["name"]
extra = sorted(set(sg["clob"]) - set(t["clob"]))
fewer = sorted(set(t["clob"]) - set(sg["clob"]))
return "lever", (f"{t['name']}_m" if extra == ["memory"] and not fewer else f"{t['name']}_v"), extra or fewer
# a concatenation of two canonical macros (a load then an op): bytes = b1 + b2, inputs = the first's, clobbers = the union
b = sg["bytes"]
for k1, t1 in canon["canonical"].items():
b1 = k1.split("|")[0]
if not b.startswith(b1) or len(b1) == 0 or len(b1) >= len(b):
continue
k2 = f"{b[len(b1):]}|0|0"
t2 = canon["canonical"].get(k2)
if not t2 or t1["nout"] or t1["nin"] != sg["nin"]:
continue
union = sorted(set(t1["clob"]) | set(t2["clob"]))
if union == list(sg["clob"]):
return "call", f"{t1['name']}({', '.join(sg['ins'])}); {t2['name']}();", f"{t1['name']}+{t2['name']}"
extra = sorted(set(sg["clob"]) - set(union))
return "lever", f"{t1['name']}+{t2['name']}_m" if extra == ["memory"] else f"{t1['name']}+{t2['name']}_v", extra or sorted(set(union) - set(sg["clob"]))
return "none", "no canonical signature", None
def file_edits(tu, raw, p, canon, label):
"""[(start, end, repl)] + the record (per definition / use / direct statement) for one file's consolidation."""
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
edits, rec = [], dict(deleted=0, renamed_defs=0, use_renames=0, direct_calls=0, direct_levers=0, direct_none=0, marked=0, defs=[], direct=[])
# definitions: delete or rename (the #define line's name token); a renamed variant gets a comment line above
for d in sorted(p["defs"], key=lambda d: d["l0"]):
start, end = ls[d["l0"] - 1], ls[d["l1"]] if d["l1"] < len(ls) else len(raw)
if d["action"] == "delete":
edits.append((start, end, ""))
rec["deleted"] += 1
rec["defs"].append(dict(name=d["name"], l0=d["l0"], action="deleted", canonical=d["canonical"]))
else:
line = raw[start:ls[d["l0"]] - 1]
k = line.find(d["name"])
if k < 0:
raise Refuse(f"{tu}:{d['l0']}: the #define line does not carry `{d['name']}`")
note = f"/* {FAKE[3:]} gte variant `{d['variant']}` — {', '.join(d['extra']) or 'fewer clobbers'} beyond Sony's `{d['canonical']}` (a scheduling steer; P36 T5) */\n"
edits.append((start, start, note))
edits.append((start + k, start + k + len(d["name"]), d["variant"]))
rec["renamed_defs"] += 1
rec["defs"].append(dict(name=d["name"], l0=d["l0"], action="renamed", variant=d["variant"], canonical=d["canonical"]))
# uses: rename tokens (outside the #define lines being edited); mark the uses of a lever variant
def_lines = {ln for d in p["defs"] for ln in range(d["l0"], d["l1"] + 1)}
lever_names = {d["name"] for d in p["defs"] if d["action"] == "rename"} | {d["variant"] for d in p["defs"] if d["action"] == "rename"}
for old, new in p["renames"].items():
for mm in re.finditer(r"\b%s\b(?=\s*\()" % re.escape(old), m):
ln = m.count("\n", 0, mm.start()) + 1
if ln in def_lines:
continue
edits.append((mm.start(), mm.end(), new))
rec["use_renames"] += 1
marked_lines = set()
for name in lever_names:
for mm in re.finditer(r"\b%s\b(?=\s*\()" % re.escape(name), m):
ln = m.count("\n", 0, mm.start()) + 1
if ln in def_lines or ln in marked_lines:
continue
le = m.find("\n", mm.start())
le = len(raw) if le < 0 else le
if FAKE in raw[ls[ln - 1]:le]:
continue
edits.append((le, le, f" {FAKE} gte via {name if name.endswith(('_m',)) or '_v' in name else p['renames'].get(name, name)} — a clobber Sony's macro lacks (P36 T5 {label})"))
marked_lines.add(ln)
rec["marked"] += 1
# direct statements
for s in p["direct"]:
pos = ls[s["line"] - 1] + s["col"] - 1
e = dl.stmt_end(m, pos)
if e < 0 or not dl.ASM_HEAD.match(m, pos):
rec["direct"].append(dict(line=s["line"], action="refused", why="statement not found at the census position"))
continue
how, text, name = direct_rewrite(s, canon)
if how == "call":
edits.append((pos, e, text))
rec["direct_calls"] += 1
rec["direct"].append(dict(line=s["line"], action="call", name=name))
elif how == "lever":
le = m.find("\n", e)
le = len(raw) if le < 0 else le
if FAKE not in raw[ls[s["line"] - 1]:le] and s["line"] not in marked_lines:
edits.append((le, le, f" {FAKE} gte direct — clobbers {name} ({', '.join(text if isinstance(text, list) else [str(text)])}) beyond Sony's (P36 T5 {label})".replace("clobbers " + str(text) + " (", "as " + str(text) + " (")))
marked_lines.add(s["line"])
rec["marked"] += 1
rec["direct_levers"] += 1
rec["direct"].append(dict(line=s["line"], action="lever", variant=text, delta=name))
else:
rec["direct_none"] += 1
rec["direct"].append(dict(line=s["line"], action="none", why=text))
return edits, rec
def apply_batch(a):
clean, dirty = dl.src_clean()
if not clean:
sys.exit(f"gte_consolidate --apply: src/ is dirty — commit or tools/delever.py --restore first:\n{dirty[:400]}")
ok, why = oracle.calibration_current()
if not ok:
sys.exit(f"gte_consolidate --apply: calibration not current ({why})")
if not (REPO / HEADER).exists() or '#include "gte_inline.h"' not in (REPO / COMMON).read_text():
sys.exit("gte_consolidate --apply: the header is not in place (--header first, then the fleet run and its commit)")
dl.ensure_census(a.jobs)
inv = inventory(a.jobs, quiet=True)
canon = canonical_table(inv, quiet=True)
files = plan_files(inv, canon, a.only)
order = sorted(files, key=lambda tu: (-len(files[tu]["defs"]) - len(files[tu]["direct"]), tu))
ledger_rows = [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()] if LEDGER.exists() else []
done = {r["tu"] for r in ledger_rows if r.get("rung") == "gte" and r.get("verdict") in ("CONSOLIDATED", "UNCHANGED")}
order = [tu for tu in order if tu not in done or a.rejudge][:a.batch]
print(f"gte_consolidate --apply {a.label}: {len(order)} files of {len(files)} with work ({len(done)} done) — "
f"{sum(len(files[t]['defs']) for t in order)} definitions, {sum(len(files[t]['direct']) for t in order)} direct statements", flush=True)
if not order:
print("gte_consolidate --apply: nothing to do")
return 0
recipes = oracle.load_recipes()["recipes"]
by_src = oracle.recipes_by_src(recipes)
inc = dl.includers()
def recipes_for(tu):
return [r for t in inc.get(tu, []) for r in by_src.get(t, [])] if tu.endswith(".h") else by_src.get(tu, [])
dl.INFLIGHT.write_text(json.dumps(dict(label=a.label, files={tu: (REPO / tu).read_text(errors="surrogateescape") for tu in order})))
log_p = RUN / f"apply_{a.label}.log"
log_f = open(log_p, "a")
def log(msg):
line = f"{time.strftime('%H:%M:%S')} {msg}"
with _LOCK:
print(line, flush=True)
log_f.write(line + "\n")
log_f.flush()
pool = ThreadPoolExecutor(max_workers=a.jobs)
results = []
def work(tu):
path = REPO / tu
st = path.stat()
raw = path.read_text(errors="surrogateescape")
recs = recipes_for(tu)
row = dict(ts=time.strftime("%Y-%m-%d %H:%M:%S"), label=a.label, rung="gte", calib=dict(head=oracle.head(), stamp=oracle.config_stamp()),
tu=tu, fn=None, addr=None, aliases=None, header=tu.endswith(".h"), sites=[], compiles=0, seconds=0.0)
if not recs:
row["verdict"] = "NO-RECIPE"
return row
plan_tu = files[tu]
# the variant trial: a definition whose only difference from the canonical one is a clobber is tried AS canonical (deleted, its
# uses renamed to the canonical name), one at a time on the untouched text; IDENTICAL -> the clobber was no lever here
variants = [d for d in plan_tu["defs"] if d["action"] == "rename"]
freed = 0
for d in variants:
trial = dict(defs=[dict(d, action="delete")], direct=[], renames=({d["name"]: d["canonical"]} if d["name"] != d["canonical"] else {}))
try:
ed, _ = file_edits(tu, raw, trial, canon, a.label)
cand = dl.apply_edits(raw, ed)
if cand == raw:
continue
try:
v, dt, err = oracle.judge_all(recs, cand, tag="gtev", write_path=(tu if tu.endswith(".h") else None))
finally:
dl.restore_file(path, raw, st)
row["compiles"] += len(recs)
row["seconds"] += dt
if v == "IDENTICAL":
d["action"], d["trial"] = "delete", "IDENTICAL"
if d["name"] != d["canonical"]:
plan_tu["renames"][d["name"]] = d["canonical"]
plan_tu["renames"].pop(d["name"], None) if d["name"] == d["canonical"] else None
freed += 1
else:
d["trial"] = v
except (Refuse, dl.Refuse) as ex:
d["trial"] = f"REFUSED {ex}"
# a variant that stays a variant keeps its rename to <name>_m
for d in variants:
if d["action"] == "rename" and d["name"] != d["variant"]:
plan_tu["renames"][d["name"]] = d["variant"]
try:
edits, rec = file_edits(tu, raw, plan_tu, canon, a.label)
except Refuse as ex:
row.update(verdict="REFUSED", why=str(ex))
log(f" {tu}: REFUSED {ex}")
return row
rec["variants_freed"] = freed
rec["variants_kept"] = sum(1 for d in variants if d["action"] == "rename")
row["record"] = rec
cands = []
try:
cands.append(("all", dl.apply_edits(raw, edits)))
no_direct = [e for e in edits if not any(e[0] == dl.line_starts(raw)[s["line"] - 1] + s["col"] - 1 for s in files[tu]["direct"])]
if len(no_direct) != len(edits):
cands.append(("defs-only", dl.apply_edits(raw, no_direct)))
except dl.Refuse as ex:
row.update(verdict="REFUSED", why=f"edits: {ex}")
log(f" {tu}: REFUSED edits {ex}")
return row
verdict = "UNCHANGED"
for tag, cand in cands:
if cand == raw:
continue
try:
if tu.endswith(".h") and len(recs) > 1:
path.write_text(cand, errors="surrogateescape")
vs = list(pool.map(lambda r: oracle.judge(r, None, tag="gte"), recs))
bad = [v for v in vs if v[0] != "IDENTICAL"]
v, dt, err = (bad[0][0], sum(x[1] for x in vs), bad[0][2]) if bad else ("IDENTICAL", sum(x[1] for x in vs), "")
else:
v, dt, err = oracle.judge_all(recs, cand, tag="gte", write_path=(tu if tu.endswith(".h") else None))
finally:
dl.restore_file(path, raw, st)
row["compiles"] += len(recs)
row["seconds"] += dt
if v == "IDENTICAL":
path.write_text(cand, errors="surrogateescape")
verdict = "CONSOLIDATED" if tag == "all" else "CONSOLIDATED-DEFS-ONLY"
if tag != "all":
rec["direct_calls_refused"] = rec.pop("direct_calls", 0)
log(f" {tu}: the direct-statement rewrites DIFFER — kept the definition edits only")
break
verdict = f"DIFFERS-{tag}" if v == "DIFFERS" else f"{v}-{tag}"
log(f" {tu}: {tag} {v} {err[:120]}")
row["verdict"] = verdict if verdict.startswith(("CONSOLIDATED", "UNCHANGED")) else "REFUSED"
if not verdict.startswith(("CONSOLIDATED", "UNCHANGED")):
row["why"] = verdict
log(f" {tu}: {row['verdict']} — deleted {rec['deleted']} defs, renamed {rec['renamed_defs']}, use renames {rec['use_renames']}, "
f"direct calls {rec.get('direct_calls', 0)}, direct levers {rec['direct_levers']}, unmatched {rec['direct_none']}, marked {rec['marked']}")
return row
tus = [tu for tu in order if not tu.endswith(".h")]
hdrs = [tu for tu in order if tu.endswith(".h")]
with ThreadPoolExecutor(max_workers=a.jobs) as ex:
results = list(ex.map(work, tus))
for h in hdrs:
results.append(work(h))
pool.shutdown(wait=True)
dl.ledger_append(results)
agg = collections.Counter()
for r in results:
agg[r["verdict"]] += 1
for k in ("deleted", "renamed_defs", "use_renames", "direct_calls", "direct_levers", "direct_none", "marked", "variants_freed", "variants_kept"):
agg[k] += r.get("record", {}).get(k, 0)
agg["compiles"] += r["compiles"]
line = (f"gte_consolidate: batch {a.label} — {len(order)} files: {agg['CONSOLIDATED']} consolidated, {agg['CONSOLIDATED-DEFS-ONLY']} defs-only, "
f"{agg['UNCHANGED']} unchanged, {agg['REFUSED']} refused, {agg['NO-RECIPE']} no-recipe · definitions deleted {agg['deleted']} / renamed as lever variants "
f"{agg['renamed_defs']} (variant trials: {agg['variants_freed']} freed, {agg['variants_kept']} kept) · use renames {agg['use_renames']} · direct statements → calls {agg['direct_calls']} / lever {agg['direct_levers']} / "
f"unmatched {agg['direct_none']} · markers {agg['marked']} · compiles {agg['compiles']} · final {len(order) - agg['REFUSED'] - agg['NO-RECIPE']}/{len(order)} identical")
log(line)
(RUN / f"batch_{a.label}.json").write_text(json.dumps(dict(label=a.label, files=order, totals=dict(agg), rows=results), indent=1))
return 0 if agg["REFUSED"] == 0 else 1
# ----------------------------------------------------------------------------------------------------------------------
# --sweep: dead asm-bearing macros; a compound macro's inner asm dropped when byte-neutral
# ----------------------------------------------------------------------------------------------------------------------
def sweep(a):
clean, dirty = dl.src_clean()
if not clean:
sys.exit(f"gte_consolidate --sweep: src/ is dirty:\n{dirty[:400]}")
ok, why = oracle.calibration_current()
if not ok:
sys.exit(f"gte_consolidate --sweep: calibration not current ({why})")
dl.ensure_census(a.jobs)
sites = dl.load_sites()
uses_by_file = collections.defaultdict(collections.Counter)
for s in sites:
if s.get("via"):
uses_by_file[s["tu"]][s["via"]] += 1
inc = dl.includers()
# a macro used by an included shared header counts as used by every includer
hdr_uses = {h: uses_by_file.get(h, collections.Counter()) for h in inc}
recipes = oracle.load_recipes()["recipes"]
by_src = oracle.recipes_by_src(recipes)
rows, n_dead, n_inner, n_files = [], 0, 0, 0
files = files_under_src()
if a.only:
files = [p for p in files if any(o in p.as_posix() for o in a.only)]
for p in files:
rel = p.relative_to(REPO).as_posix()
raw = p.read_text(errors="surrogateescape")
if "#define" not in raw:
continue
blocks = [(l0, l1, name, body) for (l0, l1, name, body) in lc.define_blocks(raw) if lc.macro_kind(body) and lc.macro_kind(body) != "gte"]
if not blocks:
continue
used = collections.Counter(uses_by_file.get(rel, {}))
for h, hu in hdr_uses.items():
if rel in inc.get(h, []):
used.update(hu)
m = dl.same_len_mask(raw)
ls = dl.line_starts(raw)
edits, dead, inner = [], [], []
for (l0, l1, name, body) in blocks:
start, end = ls[l0 - 1], ls[l1] if l1 < len(ls) else len(raw)
if used.get(name, 0) == 0 and not re.search(r"\b%s\b" % re.escape(name), m[:start] + m[end:]):
edits.append((start, end, ""))
dead.append(name)
continue
# a compound macro: drop the asm statement line(s) inside the definition (the launder/barrier steer), keep the code
lines = raw[start:end].split("\n")
keep, dropped = [], 0
for ln in lines:
if lc.ASM_KW.search(ln) and ("__asm__" in ln or "asm" in ln) and ('"' in ln):
dropped += 1
continue
keep.append(ln)
if dropped and len(keep) > 1:
new = "\n".join(keep)
if not new.endswith("\n") and raw[start:end].endswith("\n"):
new += "\n"
edits.append((start, end, new))
inner.append(name)
if not edits:
continue
recs = [r for t in inc.get(rel, []) for r in by_src.get(t, [])] if rel.endswith(".h") else by_src.get(rel, [])
if not recs:
print(f" {rel}: no recipe — skipped")
continue
st = p.stat()
try:
cand = dl.apply_edits(raw, edits)
except dl.Refuse as ex:
print(f" {rel}: REFUSED {ex}")
continue
verdict_all = None
for tag, cand_t in (("all", cand), ("dead-only", dl.apply_edits(raw, [e for e in edits if e[2] == ""]) if inner and dead else None)):
if cand_t is None or cand_t == raw:
continue
try:
v, dt, err = oracle.judge_all(recs, cand_t, tag="sweep", write_path=(rel if rel.endswith(".h") else None))
finally:
dl.restore_file(p, raw, st)
if v == "IDENTICAL":
p.write_text(cand_t, errors="surrogateescape")
verdict_all = tag
break
print(f" {rel}: {tag} {v} {err[:100]}")
n_files += verdict_all is not None
if verdict_all == "all":
n_dead += len(dead)
n_inner += len(inner)
elif verdict_all == "dead-only":
n_dead += len(dead)
inner_kept = inner
inner = []
rows.append(dict(ts=time.strftime("%Y-%m-%d %H:%M:%S"), label=a.label, rung="sweep", calib=dict(head=oracle.head(), stamp=oracle.config_stamp()),
tu=rel, fn=None, addr=None, aliases=None, header=rel.endswith(".h"), verdict=("SWEPT-" + verdict_all if verdict_all else "REFUSED"),
dead=dead, inner_dropped=(inner if verdict_all else []), sites=[], compiles=len(recs)))
print(f" {rel}: {rows[-1]['verdict']} — dead {dead} inner-asm dropped {inner if verdict_all else []}")
dl.ledger_append(rows)
print(f"gte_consolidate --sweep {a.label}: {n_dead} dead asm-bearing macro definitions deleted, {n_inner} compound macros' inner asm dropped, "
f"{n_files} files written, {sum(1 for r in rows if r['verdict'] == 'REFUSED')} refused")
return 0
def status():
rows = [json.loads(l) for l in LEDGER.read_text().splitlines() if l.strip()] if LEDGER.exists() else []
g = [r for r in rows if r.get("rung") in ("gte", "sweep")]
print(f"gte_consolidate --status: {len(g)} rows — {dict(collections.Counter(r['verdict'] for r in g))}")
return 0
# ----------------------------------------------------------------------------------------------------------------------
def selftest():
ok = True
def fail(msg):
nonlocal ok
ok = False
print("selftest FAIL:", msg)
# 1. the signature engine: three spellings of rtps are one signature; a template with an operand binds to $4
s1 = signature('"nop;nop;rtps"')
s2 = signature('"nop;" "nop;" ".word 0x4a180001"')
s3 = signature('"nop;nop;cop2 0x0180001"')
if not (s1["ok"] and s1["bytes"] == s2["bytes"] == s3["bytes"]):
fail(f"rtps spellings: {s1['bytes'][:40]} {s2['bytes'][:40]} {s3['bytes'][:40]}")
s4 = signature('"lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 )')
s5 = signature('"lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory"')
if not (s4["ok"] and s4["bytes"] == s5["bytes"] and s4["nin"] == 1 and s4["clob"] == [] and s5["clob"] == ["memory"]):
fail(f"ldv0 spellings/clobbers: {s4} {s5}")
if signature('"nop;nop;rtpt"')["bytes"] == s1["bytes"]:
fail("rtps and rtpt must differ")
# 2. the canonical table on a synthetic inventory: canonical / variant / private name / concatenated direct statement
inv = dict(definitions=[
dict(tu="a.c", l0=1, l1=2, name="gte_ldv0", params="( r0 )", body='__asm__ volatile ( "lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 ) )', kind="gte", sig=s4),
dict(tu="b.c", l0=1, l1=2, name="gte_ldv0", params="(r0)", body='__asm__ __volatile__( "lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory")', kind="gte", sig=s5),
dict(tu="c.c", l0=1, l1=2, name="LDV0_DA34", params="( r0 )", body='__asm__ volatile ("lwc2 $0, 0( %0 );lwc2 $1, 4( %0 )" : : "r"( r0 ))', kind="gte", sig=s4),
dict(tu="a.c", l0=3, l1=3, name="gte_rtps", params="()", body='__asm__ volatile ("nop;nop;rtps")', kind="gte", sig=s1),
], direct=[], uses={})
canon = canonical_table(inv, quiet=True)
t = canon["canonical"].get(sig_key(s4))
if not t or t["name"] != "gte_ldv0" or t["clob"] != [] or t["defs"] != 2:
fail(f"canonical ldv0: {t}")
v = [x for x in canon["variants"].values() if x["of"] == "gte_ldv0"]
if len(v) != 1 or v[0]["name"] != "gte_ldv0_m" or v[0]["extra"] != ["memory"]:
fail(f"ldv0 memory variant: {v}")
# 3. direct statements: a canonical call, a lever, a concatenation
d_call = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(p)'))
d_lever = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(p) : "memory"'))
d_cat = dict(sig=signature('"lwc2 $0, 0(%0);" "lwc2 $1, 4(%0);" "nop;nop;rtps" : : "r"(p)'))
if direct_rewrite(d_call, canon) != ("call", "gte_ldv0(p);", "gte_ldv0"):
fail(f"direct call: {direct_rewrite(d_call, canon)}")
if direct_rewrite(d_lever, canon)[0] != "lever" or direct_rewrite(d_lever, canon)[1] != "gte_ldv0_m":
fail(f"direct lever: {direct_rewrite(d_lever, canon)}")
r = direct_rewrite(d_cat, canon)
if r != ("call", "gte_ldv0(p); gte_rtps();", "gte_ldv0+gte_rtps"):
fail(f"direct concatenation: {r}")
# 4. the per-file edits on a fixture: the canonical def deleted, the private use renamed, the variant renamed + marked, a direct call
fx = ('#define gte_ldv0( r0 ) __asm__ volatile ( "lwc2 $0, 0( %0 );" "lwc2 $1, 4( %0 )" : : "r"( r0 ) )\n'
'#define LDV0_DA34( r0 ) __asm__ volatile ("lwc2 $0, 0( %0 );lwc2 $1, 4( %0 )" : : "r"( r0 ))\n'
'#define gte_ldv0m( r0 ) __asm__ __volatile__( "lwc2 $0, 0(%0)\\n" "lwc2 $1, 4(%0)\\n" : : "r"(r0) : "memory")\n'
'void f(s32 *v) {\n LDV0_DA34(v);\n gte_ldv0m(v);\n __asm__ volatile("lwc2 $0, 0(%0);" "lwc2 $1, 4(%0)" : : "r"(v));\n}\n')
defs = []
for (l0, l1, name, body) in lc.define_blocks(fx):
sg = signature(asm_inner(body))
defs.append(dict(tu="fx.c", l0=l0, l1=l1, name=name, params=define_params(fx, l0), body=body, kind="gte", sig=sg))
fm, fls = dl.same_len_mask(fx), dl.line_starts(fx)
fpos = fls[6] + 4
fo = fm.find("(", fpos)
finner = fm[fo + 1:lc._paren_span(fm, fo)]
inv2 = dict(definitions=defs, direct=[dict(tu="fx.c", line=7, col=5, fn="f", inner=finner, sig=signature(finner))], uses={})
canon2 = canonical_table(inv2, quiet=True)
plan = plan_files(inv2, canon2)
edits, rec = file_edits("fx.c", fx, plan["fx.c"], canon2, "self")
out = dl.apply_edits(fx, edits)
if "#define gte_ldv0(" in out or "#define LDV0_DA34" in out:
fail("canonical/private definitions must be deleted")
if "#define gte_ldv0_m( r0 )" not in out or "gte variant `gte_ldv0_m`" not in out:
fail(f"the variant definition renamed + noted:\n{out}")
if " gte_ldv0(v);\n" not in out:
fail(f"the private use renamed to the canonical name:\n{out}")
if "gte_ldv0_m(v); // !FAKE: gte via gte_ldv0_m" not in out:
fail(f"the lever variant's use marked:\n{out}")
if " gte_ldv0(v);\n}" not in out:
fail(f"the direct statement rewritten into the canonical call:\n{out}")
if rec["deleted"] != 2 or rec["renamed_defs"] != 1 or rec["use_renames"] != 2 or rec["direct_calls"] != 1 or rec["marked"] != 1:
fail(f"record: {rec}")
# 5. the header renders every canonical macro once, with its parameters
h = render_header(canon2)
if h.count("#define gte_ldv0(") != 1 or "#define gte_ldv0_m" in h:
fail("header: one canonical definition, no variant")
print(f"gte_consolidate --selftest: {'OK' if ok else 'FAIL'} — signatures rtps={s1['bytes'][16:24]} ldv0={s4['bytes'][:16]}; table {len(canon2['canonical'])} canonical / {len(canon2['variants'])} variant; fixture edits {len(edits)}")
return ok
def main():
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("--inventory", action="store_true")
ap.add_argument("--header", action="store_true")
ap.add_argument("--apply", action="store_true")
ap.add_argument("--sweep", action="store_true")
ap.add_argument("--status", action="store_true")
ap.add_argument("--selftest", action="store_true")
ap.add_argument("--batch", type=int, default=400)
ap.add_argument("--label", default=None)
ap.add_argument("--only", nargs="*", default=None)
ap.add_argument("--rejudge", action="store_true")
ap.add_argument("-j", "--jobs", type=int, default=12)
a = ap.parse_args()
RUN.mkdir(parents=True, exist_ok=True)
if a.selftest:
sys.exit(0 if selftest() else 1)
if a.status:
sys.exit(status())
if a.inventory:
dl.ensure_census(a.jobs)
inv = inventory(a.jobs)
canonical_table(inv)
return
if a.header:
inv = json.loads(INVENTORY.read_text()) if INVENTORY.exists() else inventory(a.jobs, quiet=True)
canon = canonical_table(inv, quiet=True)
write_header(canon)
return
if a.apply:
if not a.label:
sys.exit("--label is required")
sys.exit(apply_batch(a))
if a.sweep:
if not a.label:
sys.exit("--label is required")
sys.exit(sweep(a))
ap.print_help()
if __name__ == "__main__":
main()
+73 -5
View File
@@ -69,7 +69,50 @@ MIPS_REG_NAMES = {"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1",
GTE_MNEMONICS = {"lwc2", "swc2", "mtc2", "mfc2", "ctc2", "cfc2", "cop2", "rtps", "rtpt", "nclip", "ncds", "nccs", "ncdt", "ncct",
"ncs", "nct", "cdp", "cc", "dpcs", "dpct", "dpcl", "intpl", "sqr", "op", "gpf", "gpl", "avsz3", "avsz4", "mvmva"}
FAKE_MARK = "!FAKE:"
NON_LEVER_KINDS = {"gte", "verbatim-body"} # Sony's coprocessor idiom; a manifest-listed hand-asm routine — censused, never a lever
NON_LEVER_KINDS = {"gte", "verbatim-body", "gte-unsigned"} # Sony's coprocessor idiom; a manifest-listed hand-asm routine — censused, never a lever
GTE_LEVER_KIND = "gte-lever" # a GTE op whose clobbers exceed its canonical's (a `_m`/`_v` variant macro, or a direct statement): a steer
GTE_VARIANT_NAME = re.compile(r"(_m|_v[0-9a-f]{4})$")
_gte_tables = None
def _gte_lookup():
"""(sig_cache, canonical-by-bytes) from tools/gte_consolidate.py's files, read once; ({}, {}) when T5 has not run."""
global _gte_tables
if _gte_tables is None:
cache, canon = {}, {}
try:
import json as _json
base = REPO / ".run" / "P36" / "gte"
if (base / "sig_cache.json").exists():
cache = _json.loads((base / "sig_cache.json").read_text())
if (base / "canonical.json").exists():
for key, tbl in _json.loads((base / "canonical.json").read_text())["canonical"].items():
canon[key] = dict(name=tbl["name"], clob=sorted(tbl["clob"]))
except Exception: # noqa: BLE001 — a torn file reads as "no table"
cache, canon = {}, {}
_gte_tables = (cache, canon)
return _gte_tables
def classify_direct_gte(inner):
"""'gte' (Sony's idiom or no canonical to compare), 'gte-lever' (clobbers beyond the canonical's), 'gte-unsigned' (a text the
consolidation tool has not signed yet — run `tools/gte_consolidate.py --inventory`)."""
cache, canon = _gte_lookup()
if not canon:
return "gte"
try:
import gte_consolidate as gc
tmpl, outs, ins, clob = gc.parse_asm(inner)
bound = gc.bind_regs(tmpl, len(outs), len(ins))
except Exception: # noqa: BLE001
return "gte-unsigned"
b = cache.get(bound)
if not b or b.startswith("ERROR"):
return "gte-unsigned"
c = canon.get(f"{b}|{len(outs)}|{len(ins)}")
if not c:
return "gte"
return GTE_LEVER_KIND if sorted(clob) != c["clob"] else "gte"
# an `asm-body` site (a whole routine as one asm statement inside a C shell) becomes `verbatim-body` only when the manifest lists the
# routine as PERMANENT-VERBATIM; an unlisted or DECOMPILE-* one stays a lever and the guard (verbatim_check) names it
NORM_SYM = re.compile(r"\b(?:func|D)_80[0-9A-Fa-f]{6}\b")
@@ -479,6 +522,8 @@ def walk_file(raw, rel, is_header):
continue
role(pos, "asm-stmt")
d = span_of_line.get(ln)
if kind == "gte" and d:
kind = classify_direct_gte(masked[o + 1:c])
if d and is_whole_body_asm(masked, line_starts[d["line"] - 1], line_starts[d["end"]] - 1, pos, c):
kind = "asm-body" # a whole routine as one asm statement inside a C shell (any template: GTE ones too); the manifest decides its fate
add_site("B", kind, pos, masked[pos:c + 1], detail=detail, via="")
@@ -501,7 +546,10 @@ def walk_file(raw, rel, is_header):
continue
name = m.group(1)
c = _paren_span(masked, m.end() - 1)
add_site("B", asm_macro_names[name], m.start(), masked[m.start():c + 1] if c > 0 else name, detail=name, via=name)
k = asm_macro_names[name]
if k == "gte" and GTE_VARIANT_NAME.search(name):
k = GTE_LEVER_KIND
add_site("B", k, m.start(), masked[m.start():c + 1] if c > 0 else name, detail=name, via=name)
# ---- 4. volatile tokens
for m in VOLATILE_KW.finditer(masked):
@@ -814,6 +862,17 @@ def run_census(jobs, use_cache=True, out_dir=OUT_DIR_DEFAULT, want_sites=False,
what="whole routines written in assembly inside a C shell (the §265 lane's in-function form): the manifest's "
"PERMANENT-VERBATIM rows are hand asm and not levers; an asm-body the manifest does not list as permanent "
"stays a lever (unlisted_routines names them with their disposition)"),
gte_levers=dict(sites=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND),
marked=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and s["marked"]),
unmarked=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and not s["marked"]),
via_macro=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and s.get("via")),
direct=sum(1 for s in sites if s["kind"] == GTE_LEVER_KIND and not s.get("via")),
unsigned=sum(1 for s in sites if s["kind"] == "gte-unsigned"),
what="GTE ops whose clobbers exceed the canonical macro's (a scheduling steer): levers for T7, outside the headline number"),
per_tu_asm_macro_definitions=dict(total=sum(1 for m in mdefs_all if m["tu"] != gte_header),
by_kind=dict(collections.Counter(m["kind"] for m in mdefs_all if m["tu"] != gte_header)),
gte_variants=sum(1 for m in mdefs_all if m["tu"] != gte_header and m["kind"] == "gte" and GTE_VARIANT_NAME.search(m["name"])),
what="asm-bearing #define blocks outside the GTE header: 0 at the close (T5 deletes the canonical duplicates, T7 the variants)"),
macro_definitions=dict(total=len(mdefs_all), names=len(macro_names), kinds=dict(macro_kinds),
names_with_multiple_texts=sum(1 for n, t in macro_texts.items() if len(t) > 1),
top=[(n, c, len(macro_texts[n])) for n, c in macro_names.most_common(25)]),
@@ -888,6 +947,12 @@ def render(s):
L.append(f" THE PHASE'S NUMBER (pins + asm statements, GTE excluded): {ab['sites']:,} sites in {ab['bodies']:,} bodies "
f"({ab['distinct_bodies']:,} distinct) · marked !FAKE {ab['marked']:,} · UNMARKED {ab['unmarked']:,}")
L.append(f" orphan !FAKE markers (no pin/asm site on the line nor below): {s.get('orphan_markers', {}).get('count', 0)}")
g = s.get("gte_levers", {})
if g:
L.append(f" GTE levers (clobbers beyond the canonical macro's): {g['sites']:,} sites ({g['via_macro']:,} via a variant macro, {g['direct']:,} direct) · "
f"marked {g['marked']:,} · UNMARKED {g['unmarked']:,} · unsigned GTE statements {g['unsigned']:,}")
pt = s.get("per_tu_asm_macro_definitions", {})
L.append(f" per-TU asm macro definitions outside the GTE header: {pt.get('total', 0):,} {pt.get('by_kind', {})} (GTE variants {pt.get('gte_variants', 0):,})")
p = s["pins"]
L.append(f" pins: {p['sites']:,} · $0 {p['zero']:,} · $sp {p['sp']} · with initializer {p['init']:,} · volatile-qualified "
f"{p['volatile_qualified']} · bare-name {p['bare_name']} · spellings {p['spelling']}")
@@ -1032,9 +1097,12 @@ def main():
ab = [x for x in sites if x["cls"] in "AB" and x["kind"] not in NON_LEVER_KINDS and x["tu"] != a.gte_header]
un = [x for x in ab if not x["marked"]]
if a.strict:
print(f"lever_census --check --strict: pins {s['pins']['sites']}, asm {sum(1 for x in ab if x['cls'] == 'B')}, "
f"volatile-needed {s['classes']['C']['sites']}, register-needed {s['classes']['D']['sites']} — {'OK' if not ab else 'FAIL'}")
if ab:
pt = s.get("per_tu_asm_macro_definitions", {}).get("total", 0)
gl = s.get("gte_levers", {}).get("sites", 0)
print(f"lever_census --check --strict: pins {s['pins']['sites']}, asm {sum(1 for x in ab if x['cls'] == 'B' and x['kind'] != GTE_LEVER_KIND)}, "
f"gte-levers {gl}, per-TU asm macro definitions {pt}, volatile-needed {s['classes']['C']['sites']}, register-needed {s['classes']['D']['sites']} — "
f"{'OK' if not ab and not pt else 'FAIL'}")
if ab or pt:
for x in ab[:10]:
print(f" {x['tu']}:{x['line']} {x['fn']} {x['cls']}/{x['kind']} {x['detail']}")
rc = 1