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