refactor-m1pp-bss.py (7354B)
1 #!/usr/bin/env python3 2 """One-shot M1pp.P1 BSS refactor. 3 4 Identifies labels in M1pp/M1pp.P1 whose body is one or more ZERO32 lines 5 (the big BSS-style buffers). Moves them out of the file into BSS: 6 7 * Removes the label declarations + their ZERO32 padding. 8 * Adds an OFFSET DEFINE for each buffer (offset from BSS_BASE, the byte 9 past ELF_end). 10 * Adds a per-buffer 8-byte pointer slot in the in-file data area 11 (input_buf_ptr, output_buf_ptr, ...). 12 * Adds initialization code at the top of p1_main that computes each base 13 pointer once and stores it into its slot. 14 * Rewrites every `la_<reg> &<buf>` in code to `la_<reg> &<buf>_ptr; 15 ld_<reg>,<reg>,0` (cached-pointer indirection — same dest reg, no 16 scratch needed). 17 18 Run: python3 scripts/refactor-m1pp-bss.py 19 """ 20 21 import re 22 import sys 23 from pathlib import Path 24 25 SRC = Path('M1pp/M1pp.P1') 26 text = SRC.read_text() 27 lines = text.split('\n') 28 29 # --- Pass 1: identify BSS buffers (label followed by ZERO32 lines). ---------- 30 31 buffers = [] # (name, start_idx, end_idx_exclusive, zero32_count) 32 i = 0 33 while i < len(lines): 34 line = lines[i].strip() 35 m = re.match(r':([A-Za-z_][A-Za-z0-9_]*)$', line) 36 if m: 37 label = m.group(1) 38 # scan forward to find ZERO32 lines (allowing intermediate blank/comment) 39 j = i + 1 40 zero32_count = 0 41 while j < len(lines): 42 stripped = lines[j].strip() 43 if stripped.startswith('ZERO32'): 44 zero32_count += stripped.split().count('ZERO32') 45 j += 1 46 elif stripped == '' or stripped.startswith('#') or stripped.startswith(';'): 47 # Comments/blank — peek further 48 k = j + 1 49 while k < len(lines) and (lines[k].strip() == '' or lines[k].strip().startswith('#')): 50 k += 1 51 if k < len(lines) and lines[k].strip().startswith('ZERO32'): 52 j = k 53 continue 54 break 55 else: 56 break 57 if zero32_count > 0: 58 buffers.append((label, i, j, zero32_count)) 59 i = j 60 continue 61 i += 1 62 63 # --- Pass 2: compute layout. ------------------------------------------------- 64 65 layout = [] 66 offset = 0 67 for name, _, _, count in buffers: 68 size = count * 32 69 layout.append((name, offset, size)) 70 offset += size 71 72 bss_total = offset 73 print(f"Found {len(buffers)} BSS buffers, total {bss_total} bytes ({bss_total/1024:.1f} KB)") 74 for name, off, size in layout: 75 print(f" {name:25s} off=0x{off:08x} size={size:9d}") 76 77 # --- Pass 3: build the rewrite. ---------------------------------------------- 78 79 def le_u64(value: int) -> str: 80 """Encode a non-negative int as 8 bytes little-endian hex.""" 81 assert 0 <= value < (1 << 64) 82 return ''.join(f'{(value >> (8*k)) & 0xff:02x}' for k in range(8)).upper() 83 84 # 3a. Generate OFFSET DEFINEs. 85 offset_defines = ['## --- BSS layout (offsets from ELF_end) -------------------------------------'] 86 for name, off, _ in layout: 87 offset_defines.append(f'DEFINE OFF_{name} {le_u64(off)}') 88 offset_defines.append('') 89 90 # 3b. Generate pointer slots (8 bytes each, ZERO8). 91 ptr_slots = ['## --- BSS pointer slots (set by p1_main; one per BSS buffer) -----------------'] 92 for name, _, _ in layout: 93 ptr_slots.append(f':{name}_ptr') 94 ptr_slots.append('ZERO8') 95 ptr_slots.append('') 96 97 # 3c. Generate init code (computes ELF_end + offset, stores into each slot). 98 init_code = [' # --- init BSS pointer slots from ELF_end ---------------------------------'] 99 init_code.append(' la_t0 &ELF_end') 100 for name, off, _ in layout: 101 if off == 0: 102 init_code.append(f' la_t1 &{name}_ptr') 103 init_code.append(f' st_t0,t1,0 # {name}_ptr = ELF_end') 104 else: 105 init_code.append(f' li_t1 OFF_{name}') 106 init_code.append(f' add_t1,t0,t1') 107 init_code.append(f' la_t2 &{name}_ptr') 108 init_code.append(f' st_t1,t2,0 # {name}_ptr = ELF_end + OFF_{name}') 109 init_code.append(' # --- end BSS init -------------------------------------------------------') 110 init_code.append('') 111 112 # --- Pass 4: rewrite the file. ----------------------------------------------- 113 114 # Build a mask of lines to delete (the BSS buffer blocks). 115 delete_mask = [False] * len(lines) 116 for _, start, end in [(n, s, e) for (n, s, e, _) in buffers]: 117 for k in range(start, end): 118 delete_mask[k] = True 119 120 # Find p1_main and the line right after enter_0 (insertion point for init code). 121 p1_main_idx = None 122 for k, ln in enumerate(lines): 123 if ln.strip() == ':p1_main': 124 p1_main_idx = k 125 break 126 assert p1_main_idx is not None, 'no :p1_main label' 127 # Find first non-blank/non-comment line after enter_0. 128 init_insert_idx = None 129 for k in range(p1_main_idx + 1, len(lines)): 130 if 'enter_0' in lines[k]: 131 init_insert_idx = k + 1 132 break 133 assert init_insert_idx is not None, 'no enter_0 after :p1_main' 134 135 # Find :ELF_end (insertion point for ptr_slots — just before). 136 elf_end_idx = None 137 for k, ln in enumerate(lines): 138 if ln.strip() == ':ELF_end': 139 elf_end_idx = k 140 break 141 assert elf_end_idx is not None 142 143 # Find the constants section to insert OFFSET DEFINEs (after the last sizing 144 # DEFINE, which I'll detect as the last DEFINE EXPR_INVALID). 145 defines_insert_idx = None 146 for k, ln in enumerate(lines): 147 if ln.strip() == 'DEFINE EXPR_INVALID 1200000000000000': 148 defines_insert_idx = k + 1 149 break 150 assert defines_insert_idx is not None 151 152 # Build the new line list. Process in reverse so insert indices stay valid. 153 out = list(lines) 154 155 # Insert ptr_slots just before :ELF_end. 156 out[elf_end_idx:elf_end_idx] = ptr_slots 157 158 # Then we need to delete buffer blocks. But the delete mask refers to the 159 # original `lines` indices; ptr_slots insertion shifted later indices. To 160 # avoid that, do all index-based work on the original `lines`, then assemble 161 # at the end. 162 163 # Restart with a clean approach. 164 out = [] 165 buffer_names = {b[0] for b in buffers} 166 for k, ln in enumerate(lines): 167 if delete_mask[k]: 168 continue 169 out.append(ln) 170 if k == defines_insert_idx - 1: 171 out.extend(offset_defines) 172 if k == init_insert_idx - 1: 173 out.extend(init_code) 174 if k == elf_end_idx - 1: 175 out.extend(ptr_slots) 176 177 # --- Pass 5: rewrite all `la_<reg> &<buf>` references. ----------------------- 178 179 # Pattern: optional indent, "la_<reg> &<buf>", optional comment. 180 # Replace with two lines: la_<reg> &<buf>_ptr ; ld_<reg>,<reg>,0 181 la_re = re.compile(r'^(\s*)la_([a-z][a-z0-9]+) &(' + '|'.join(re.escape(n) for n in buffer_names) + r')\b(.*)$') 182 183 rewritten = [] 184 ref_count = 0 185 for ln in out: 186 m = la_re.match(ln) 187 if m: 188 indent, reg, name, tail = m.group(1), m.group(2), m.group(3), m.group(4) 189 rewritten.append(f'{indent}la_{reg} &{name}_ptr{tail}') 190 rewritten.append(f'{indent}ld_{reg},{reg},0') 191 ref_count += 1 192 else: 193 rewritten.append(ln) 194 195 print(f"Rewrote {ref_count} la_<reg> &<buf> references.") 196 197 # --- Pass 6: write back. ----------------------------------------------------- 198 199 new_text = '\n'.join(rewritten) 200 SRC.write_text(new_text) 201 202 old_lines = len(lines) 203 new_lines = len(rewritten) 204 print(f"M1pp.P1 lines: {old_lines} -> {new_lines} ({new_lines-old_lines:+d})") 205 print(f"M1pp.P1 bytes: {len(text)} -> {len(new_text)} ({len(new_text)-len(text):+d})")