p1_gen.py (8140B)
1 #!/usr/bin/env python3 2 """Generate P1 v2 DEFINE tables. 3 4 This is a fresh generator for docs/P1v2.md. The ISA surface is described by 5 plain namedtuple rows, and each backend registers a simple row-type -> encoder 6 mapping. The emitted immediate/offset domains are still curated tables rather 7 than the full theoretical spec space, so extending coverage is a one-line data 8 edit instead of an architecture rewrite. 9 10 Usage: 11 python3 p1/p1_gen.py [--arch ARCH] [build-root] 12 python3 p1/p1_gen.py --check [--arch ARCH] [build-root] 13 python3 p1/p1_gen.py --list-archs 14 """ 15 16 import os 17 import sys 18 from itertools import product 19 20 from common import ( 21 AddI, 22 Banner, 23 BranchReg, 24 CondB, 25 CondBZ, 26 Enter, 27 La, 28 LaBr, 29 LdArg, 30 Li, 31 Literal, 32 LogI, 33 Mem, 34 Mov, 35 Nullary, 36 Rrr, 37 ShiftI, 38 get_arch, 39 registered_arches, 40 word_hex, 41 ) 42 43 import aarch64 # noqa: F401 - imported for arch registration side effects 44 45 46 P1_GPRS = ('a0', 'a1', 'a2', 'a3', 't0', 't1', 't2', 's0', 's1', 's2', 's3') 47 P1_BASES = P1_GPRS + ('sp',) 48 49 RRR_OPS = ('ADD', 'SUB', 'AND', 'OR', 'XOR', 'SHL', 'SHR', 'SAR', 'MUL', 'DIV', 'REM') 50 LOGI_OPS = ('ANDI', 'ORI') 51 SHIFT_OPS = ('SHLI', 'SHRI', 'SARI') 52 MEM_OPS = ('LD', 'ST', 'LB', 'SB') 53 CONDB_OPS = ('BEQ', 'BNE', 'BLT', 'BLTU') 54 CONDBZ_OPS = ('BEQZ', 'BNEZ', 'BLTZ') 55 56 ADDI_IMMS = ( 57 -2048, -1024, -256, -128, -64, -48, -32, -24, -16, -12, -8, -7, -6, 58 -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 15, 16, 24, 32, 40, 59 48, 63, 64, 127, 128, 255, 256, 512, 1024, 2047, 60 ) 61 62 LOGI_IMMS = ( 63 -1, 0, 1, 2, 3, 4, 6, 7, 8, 15, 16, 31, 32, 63, 64, 127, 255, 511, 1023, 64 2047, 65 ) 66 67 SHIFT_IMMS = tuple(range(64)) 68 69 MEM_OFFS = ( 70 -256, -128, -64, -48, -32, -24, -16, -8, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 71 15, 16, 24, 32, 40, 48, 56, 64, 128, 255, 72 ) 73 74 LDARG_SLOTS = tuple(range(32)) 75 ENTER_SIZES = tuple(range(0, 129)) 76 77 78 HEADER = """## p1_{arch}.M1 — GENERATED by p1/p1_gen.py. Do not edit by hand. 79 ## 80 ## This table targets the P1 v2 ISA described in docs/P1v2.md. 81 ## Row shapes are shared; per-arch lowering lives in p1/<arch>.py. 82 """ 83 84 85 def imm_suffix(imm): 86 return f'NEG{-imm}' if imm < 0 else str(imm) 87 88 89 def rows(arch): 90 out = [] 91 92 out.append(Banner('Materialization')) 93 for rd in P1_GPRS: 94 out.append(Li(name=f'LI_{rd.upper()}', rd=rd)) 95 for rd in P1_GPRS: 96 out.append(La(name=f'LA_{rd.upper()}', rd=rd)) 97 out.append(LaBr(name='LA_BR')) 98 99 out.append(Banner('Moves')) 100 for rd, rs in product(P1_GPRS, P1_GPRS): 101 out.append(Mov(name=f'MOV_{rd.upper()}_{rs.upper()}', rd=rd, rs=rs)) 102 for rd in P1_GPRS: 103 out.append(Mov(name=f'MOV_{rd.upper()}_SP', rd=rd, rs='sp')) 104 105 out.append(Banner('Register Arithmetic')) 106 for op, rd, ra, rb in product(RRR_OPS, P1_GPRS, P1_GPRS, P1_GPRS): 107 out.append(Rrr(name=f'{op}_{rd.upper()}_{ra.upper()}_{rb.upper()}', 108 op=op, rd=rd, ra=ra, rb=rb)) 109 110 out.append(Banner('Immediate Arithmetic')) 111 for rd, ra, imm in product(P1_GPRS, P1_GPRS, ADDI_IMMS): 112 out.append(AddI(name=f'ADDI_{rd.upper()}_{ra.upper()}_{imm_suffix(imm)}', 113 rd=rd, ra=ra, imm=imm)) 114 for op, rd, ra, imm in product(LOGI_OPS, P1_GPRS, P1_GPRS, LOGI_IMMS): 115 out.append(LogI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm_suffix(imm)}', 116 op=op, rd=rd, ra=ra, imm=imm)) 117 for op, rd, ra, imm in product(SHIFT_OPS, P1_GPRS, P1_GPRS, SHIFT_IMMS): 118 out.append(ShiftI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm}', 119 op=op, rd=rd, ra=ra, imm=imm)) 120 121 out.append(Banner('Memory')) 122 for op, rt, rn, off in product(MEM_OPS, P1_GPRS, P1_BASES, MEM_OFFS): 123 out.append(Mem(name=f'{op}_{rt.upper()}_{rn.upper()}_{imm_suffix(off)}', 124 op=op, rt=rt, rn=rn, off=off)) 125 126 out.append(Banner('ABI Access')) 127 for rd, slot in product(P1_GPRS, LDARG_SLOTS): 128 out.append(LdArg(name=f'LDARG_{rd.upper()}_{slot}', rd=rd, slot=slot)) 129 130 out.append(Banner('Branches')) 131 out.append(Nullary(name='B', kind='B')) 132 for rs in P1_GPRS: 133 out.append(BranchReg(name=f'BR_{rs.upper()}', kind='BR', rs=rs)) 134 for op, ra, rb in product(CONDB_OPS, P1_GPRS, P1_GPRS): 135 out.append(CondB(name=f'{op}_{ra.upper()}_{rb.upper()}', op=op, ra=ra, rb=rb)) 136 for op, ra in product(CONDBZ_OPS, P1_GPRS): 137 out.append(CondBZ(name=f'{op}_{ra.upper()}', op=op, ra=ra)) 138 139 out.append(Banner('Calls And Returns')) 140 out.append(Nullary(name='CALL', kind='CALL')) 141 out.append(Nullary(name='RET', kind='RET')) 142 out.append(Nullary(name='ERET', kind='ERET')) 143 out.append(Nullary(name='TAIL', kind='TAIL')) 144 for rs in P1_GPRS: 145 out.append(BranchReg(name=f'CALLR_{rs.upper()}', kind='CALLR', rs=rs)) 146 for rs in P1_GPRS: 147 out.append(BranchReg(name=f'TAILR_{rs.upper()}', kind='TAILR', rs=rs)) 148 149 out.append(Banner('Frame Management')) 150 for size in ENTER_SIZES: 151 out.append(Enter(name=f'ENTER_{size}', size=size)) 152 153 out.append(Banner('System')) 154 out.append(Nullary(name='SYSCALL', kind='SYSCALL')) 155 for name, number in sorted(arch.syscall_numbers.items()): 156 out.append(Literal(name=name, hex_by_arch={arch.name: word_hex(arch.word_bytes, number)})) 157 158 return out 159 160 161 def lower_name(name): 162 low = name.lower() 163 head, sep, rest = low.partition('_') 164 if not sep: 165 return low 166 if '_' not in rest: 167 return low 168 return f'{head}_{rest.replace("_", ",")}' 169 170 171 def encode_row(arch, row): 172 if isinstance(row, Literal): 173 return row.hex_by_arch[arch.name] 174 encoder = arch.encoders[type(row)] 175 return encoder(arch, row) 176 177 178 def emit(arch_name): 179 arch = get_arch(arch_name) 180 out = [HEADER.format(arch=arch.name).rstrip(), ''] 181 seen = set() 182 for row in rows(arch): 183 if isinstance(row, Banner): 184 out.append('') 185 out.append(f'## ---- {row.text}') 186 continue 187 name = lower_name(row.name) 188 if name in seen: 189 raise RuntimeError(f'duplicate DEFINE: {name}') 190 seen.add(name) 191 out.append(f'DEFINE {name} {encode_row(arch, row)}') 192 out.append('') 193 out.append('## ---- Program Entry') 194 out.append('## Backend-owned :_start stub per docs/P1.md §Program Entry.') 195 out.append('## Calls p1_main under the one-word direct-result convention') 196 out.append("## (a0=argc, a1=argv) and sys_exits its return value.") 197 out.extend(arch.start_stub()) 198 out.append('') 199 return '\n'.join(out) 200 201 202 def parse_args(argv): 203 check = False 204 archs = [] 205 positional = [] 206 i = 0 207 while i < len(argv): 208 arg = argv[i] 209 if arg == '--check': 210 check = True 211 elif arg == '--list-archs': 212 print('\n'.join(registered_arches())) 213 sys.exit(0) 214 elif arg == '--arch': 215 i += 1 216 if i >= len(argv): 217 raise SystemExit('--arch requires a value') 218 archs.append(argv[i]) 219 else: 220 positional.append(arg) 221 i += 1 222 build_root = positional[0] if positional else os.path.join('build', 'p1v2') 223 if not archs: 224 archs = list(registered_arches()) 225 return check, archs, build_root 226 227 228 def main(argv=None): 229 check, archs, build_root = parse_args(argv or sys.argv[1:]) 230 had_diff = False 231 232 for arch_name in archs: 233 arch = get_arch(arch_name) 234 dest_dir = os.path.join(build_root, arch.name) 235 path = os.path.join(dest_dir, f'p1_{arch.name}.M1') 236 content = emit(arch.name) 237 if check: 238 try: 239 with open(path) as f: 240 existing = f.read() 241 except FileNotFoundError: 242 existing = '' 243 if existing != content: 244 sys.stderr.write(f'DIFF: {path}\n') 245 had_diff = True 246 continue 247 os.makedirs(dest_dir, exist_ok=True) 248 with open(path, 'w') as f: 249 f.write(content) 250 print(f'wrote {path} ({len(content)} bytes)') 251 252 if check and had_diff: 253 sys.exit(1) 254 255 256 if __name__ == '__main__': 257 main()