p1_gen.py (8961B)
1 #!/usr/bin/env python3 2 """Generate P1 DEFINE tables. 3 4 This is a fresh generator for docs/P1.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/gen/p1_gen.py --arch ARCH --out PATH 12 python3 p1/gen/p1_gen.py --check --arch ARCH --out PATH 13 python3 p1/gen/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 word_hex, 39 ) 40 41 import aarch64 42 import amd64 43 import riscv32 44 import riscv64 45 46 ARCHES = { 47 a.name: a 48 for a in (aarch64.ARCH, amd64.ARCH, riscv32.ARCH, riscv64.ARCH) 49 } 50 51 52 P1_GPRS = ('a0', 'a1', 'a2', 'a3', 't0', 't1', 't2', 's0', 's1', 's2', 's3') 53 P1_BASES = P1_GPRS + ('sp',) 54 55 RRR_OPS = ('ADD', 'SUB', 'AND', 'OR', 'XOR', 'SHL', 'SHR', 'SAR', 'MUL', 'DIV', 'REM') 56 LOGI_OPS = ('ANDI', 'ORI') 57 SHIFT_OPS = ('SHLI', 'SHRI', 'SARI') 58 MEM_OPS = ('LD', 'ST', 'LB', 'SB') 59 CONDB_OPS = ('BEQ', 'BNE', 'BLT', 'BLTU') 60 CONDBZ_OPS = ('BEQZ', 'BNEZ', 'BLTZ') 61 62 ADDI_IMMS = ( 63 -2048, -1024, -256, -128, -64, -48, -32, -24, -16, -12, -8, -7, -6, 64 -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 15, 16, 24, 32, 40, 65 48, 63, 64, 127, 128, 255, 256, 512, 1024, 2047, 66 ) 67 68 LOGI_IMMS = ( 69 -1, 0, 1, 2, 3, 4, 6, 7, 8, 15, 16, 31, 32, 63, 64, 127, 255, 511, 1023, 70 2047, 71 ) 72 73 MEM_OFFS = ( 74 -256, -128, -64, -48, -32, -24, -16, -8, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 75 15, 16, 24, 32, 40, 48, 56, 64, 128, 255, 76 ) 77 78 LDARG_SLOTS = tuple(range(32)) 79 ENTER_SIZES = tuple(range(0, 129)) 80 81 82 HEADER = """## {arch}.M1 — GENERATED by p1/gen/p1_gen.py. Do not edit by hand. 83 ## 84 ## This table targets the P1 ISA described in docs/P1.md. 85 ## Row shapes are shared; per-arch lowering lives in p1/gen/<arch>.py. 86 """ 87 88 89 def imm_suffix(imm): 90 return f'NEG{-imm}' if imm < 0 else str(imm) 91 92 93 def rows(arch): 94 out = [] 95 96 out.append(Banner('Materialization')) 97 # Stage0 .P1 sources use these target facts for native pointer-array 98 # indexing while retaining their deliberately padded 8-byte records. 99 literal_bytes = 8 if arch.name == 'riscv32' else arch.word_bytes 100 out.append(Literal(name='p1wordbytes', hex_by_arch={ 101 arch.name: word_hex(literal_bytes, arch.word_bytes), 102 })) 103 out.append(Literal(name='p1wordbits', hex_by_arch={ 104 arch.name: word_hex(literal_bytes, arch.word_bytes * 8), 105 })) 106 out.append(Literal(name='p1wordshift', hex_by_arch={ 107 arch.name: word_hex(literal_bytes, 2 if arch.word_bytes == 4 else 3), 108 })) 109 for rd in P1_GPRS: 110 out.append(Li(name=f'LI_{rd.upper()}', rd=rd)) 111 for rd in P1_GPRS: 112 out.append(La(name=f'LA_{rd.upper()}', rd=rd)) 113 out.append(LaBr(name='LA_BR')) 114 115 out.append(Banner('Moves')) 116 for rd, rs in product(P1_GPRS, P1_GPRS): 117 out.append(Mov(name=f'MOV_{rd.upper()}_{rs.upper()}', rd=rd, rs=rs)) 118 for rd in P1_GPRS: 119 out.append(Mov(name=f'MOV_{rd.upper()}_SP', rd=rd, rs='sp')) 120 121 out.append(Banner('Register Arithmetic')) 122 for op, rd, ra, rb in product(RRR_OPS, P1_GPRS, P1_GPRS, P1_GPRS): 123 out.append(Rrr(name=f'{op}_{rd.upper()}_{ra.upper()}_{rb.upper()}', 124 op=op, rd=rd, ra=ra, rb=rb)) 125 126 out.append(Banner('Immediate Arithmetic')) 127 for rd, ra, imm in product(P1_GPRS, P1_GPRS, ADDI_IMMS): 128 out.append(AddI(name=f'ADDI_{rd.upper()}_{ra.upper()}_{imm_suffix(imm)}', 129 rd=rd, ra=ra, imm=imm)) 130 for op, rd, ra, imm in product(LOGI_OPS, P1_GPRS, P1_GPRS, LOGI_IMMS): 131 out.append(LogI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm_suffix(imm)}', 132 op=op, rd=rd, ra=ra, imm=imm)) 133 shift_imms = tuple(range(arch.word_bytes * 8)) 134 for op, rd, ra, imm in product(SHIFT_OPS, P1_GPRS, P1_GPRS, shift_imms): 135 out.append(ShiftI(name=f'{op}_{rd.upper()}_{ra.upper()}_{imm}', 136 op=op, rd=rd, ra=ra, imm=imm)) 137 138 out.append(Banner('Memory')) 139 for op, rt, rn, off in product(MEM_OPS, P1_GPRS, P1_BASES, MEM_OFFS): 140 out.append(Mem(name=f'{op}_{rt.upper()}_{rn.upper()}_{imm_suffix(off)}', 141 op=op, rt=rt, rn=rn, off=off)) 142 143 out.append(Banner('ABI Access')) 144 for rd, slot in product(P1_GPRS, LDARG_SLOTS): 145 out.append(LdArg(name=f'LDARG_{rd.upper()}_{slot}', rd=rd, slot=slot)) 146 147 out.append(Banner('Branches')) 148 out.append(Nullary(name='B', kind='B')) 149 for rs in P1_GPRS: 150 out.append(BranchReg(name=f'BR_{rs.upper()}', kind='BR', rs=rs)) 151 for op, ra, rb in product(CONDB_OPS, P1_GPRS, P1_GPRS): 152 out.append(CondB(name=f'{op}_{ra.upper()}_{rb.upper()}', op=op, ra=ra, rb=rb)) 153 for op, ra in product(CONDBZ_OPS, P1_GPRS): 154 out.append(CondBZ(name=f'{op}_{ra.upper()}', op=op, ra=ra)) 155 156 out.append(Banner('Calls And Returns')) 157 out.append(Nullary(name='CALL', kind='CALL')) 158 out.append(Nullary(name='RET', kind='RET')) 159 out.append(Nullary(name='ERET', kind='ERET')) 160 out.append(Nullary(name='TAIL', kind='TAIL')) 161 for rs in P1_GPRS: 162 out.append(BranchReg(name=f'CALLR_{rs.upper()}', kind='CALLR', rs=rs)) 163 for rs in P1_GPRS: 164 out.append(BranchReg(name=f'TAILR_{rs.upper()}', kind='TAILR', rs=rs)) 165 166 out.append(Banner('Frame Management')) 167 for size in ENTER_SIZES: 168 out.append(Enter(name=f'ENTER_{size}', size=size)) 169 170 out.append(Banner('System')) 171 out.append(Nullary(name='SYSCALL', kind='SYSCALL')) 172 for name, number in sorted(arch.syscall_numbers.items()): 173 # The two stage0 .P1 consumers retain their original padded 8-byte 174 # immediate spelling on RV32; its generated LI skips that padding. 175 # M1pp-authored P1-32 programs use the canonical four-byte form. 176 out.append(Literal(name=name, hex_by_arch={arch.name: word_hex(literal_bytes, number)})) 177 178 return out 179 180 181 def lower_name(name): 182 low = name.lower() 183 head, sep, rest = low.partition('_') 184 if not sep: 185 return low 186 if '_' not in rest: 187 return low 188 return f'{head}_{rest.replace("_", ",")}' 189 190 191 def encode_row(arch, row): 192 if isinstance(row, Literal): 193 return row.hex_by_arch[arch.name] 194 encoder = arch.encoders[type(row)] 195 return encoder(arch, row) 196 197 198 def emit(arch_name): 199 arch = ARCHES[arch_name] 200 out = [HEADER.format(arch=arch.name).rstrip(), ''] 201 seen = set() 202 for row in rows(arch): 203 if isinstance(row, Banner): 204 out.append('') 205 out.append(f'## ---- {row.text}') 206 continue 207 name = lower_name(row.name) 208 if name in seen: 209 raise RuntimeError(f'duplicate DEFINE: {name}') 210 seen.add(name) 211 out.append(f'DEFINE {name} {encode_row(arch, row)}') 212 out.append('') 213 out.append('## ---- Program Entry') 214 out.append('## Backend-owned :_start stub per docs/P1.md §Program Entry.') 215 out.append('## Calls p1_main under the one-word direct-result convention') 216 out.append("## (a0=argc, a1=argv) and sys_exits its return value.") 217 out.extend(arch.start_stub()) 218 out.append('') 219 return '\n'.join(out) 220 221 222 def parse_args(argv): 223 check = False 224 arch = None 225 out = None 226 i = 0 227 while i < len(argv): 228 a = argv[i] 229 if a == '--check': 230 check = True 231 elif a == '--list-archs': 232 print('\n'.join(sorted(ARCHES))) 233 sys.exit(0) 234 elif a == '--arch': 235 i += 1 236 if i >= len(argv): 237 raise SystemExit('--arch requires a value') 238 arch = argv[i] 239 elif a == '--out': 240 i += 1 241 if i >= len(argv): 242 raise SystemExit('--out requires a value') 243 out = argv[i] 244 else: 245 raise SystemExit(f'unexpected argument: {a}') 246 i += 1 247 if arch is None: 248 raise SystemExit('--arch is required') 249 if out is None: 250 raise SystemExit('--out is required') 251 if arch not in ARCHES: 252 raise SystemExit(f'unknown arch: {arch}') 253 return check, arch, out 254 255 256 def main(argv=None): 257 check, arch_name, path = parse_args(argv or sys.argv[1:]) 258 content = emit(arch_name) 259 if check: 260 try: 261 with open(path) as f: 262 existing = f.read() 263 except FileNotFoundError: 264 existing = '' 265 if existing != content: 266 sys.stderr.write(f'DIFF: {path}\n') 267 sys.exit(1) 268 return 269 os.makedirs(os.path.dirname(path) or '.', exist_ok=True) 270 with open(path, 'w') as f: 271 f.write(content) 272 print(f'wrote {path} ({len(content)} bytes)') 273 274 275 if __name__ == '__main__': 276 main()