boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

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()