| """Generate the main interpreter switch.""" |
| |
| # Write the cases to generated_cases.c.h, which is #included in ceval.c. |
| |
| # TODO: Reuse C generation framework from deepfreeze.py? |
| |
| import argparse |
| import io |
| import os |
| import re |
| import sys |
| |
| import parser |
| from parser import InstDef # TODO: Use parser.InstDef |
| |
| arg_parser = argparse.ArgumentParser() |
| arg_parser.add_argument("-i", "--input", type=str, default="Python/bytecodes.c") |
| arg_parser.add_argument("-o", "--output", type=str, default="Python/generated_cases.c.h") |
| arg_parser.add_argument("-c", "--compare", action="store_true") |
| arg_parser.add_argument("-q", "--quiet", action="store_true") |
| |
| |
| def eopen(filename: str, mode: str = "r"): |
| if filename == "-": |
| if "r" in mode: |
| return sys.stdin |
| else: |
| return sys.stdout |
| return open(filename, mode) |
| |
| |
| def parse_cases( |
| src: str, filename: str|None = None |
| ) -> tuple[list[InstDef], list[parser.Super], list[parser.Family]]: |
| psr = parser.Parser(src, filename=filename) |
| instrs: list[InstDef] = [] |
| supers: list[parser.Super] = [] |
| families: list[parser.Family] = [] |
| while not psr.eof(): |
| if inst := psr.inst_def(): |
| assert inst.block |
| instrs.append(inst) |
| elif sup := psr.super_def(): |
| supers.append(sup) |
| elif fam := psr.family_def(): |
| families.append(fam) |
| else: |
| raise psr.make_syntax_error(f"Unexpected token") |
| return instrs, supers, families |
| |
| |
| def always_exits(block: parser.Block) -> bool: |
| text = block.text |
| lines = text.splitlines() |
| while lines and not lines[-1].strip(): |
| lines.pop() |
| if not lines or lines[-1].strip() != "}": |
| return False |
| lines.pop() |
| if not lines: |
| return False |
| line = lines.pop().rstrip() |
| # Indent must match exactly (TODO: Do something better) |
| if line[:12] != " "*12: |
| return False |
| line = line[12:] |
| return line.startswith(("goto ", "return ", "DISPATCH", "GO_TO_", "Py_UNREACHABLE()")) |
| |
| |
| def write_cases(f: io.TextIOBase, instrs: list[InstDef], supers: list[parser.Super]): |
| predictions = set() |
| for inst in instrs: |
| for target in re.findall(r"(?:PREDICT|GO_TO_INSTRUCTION)\((\w+)\)", inst.block.text): |
| predictions.add(target) |
| indent = " " |
| f.write(f"// This file is generated by {os.path.relpath(__file__)}\n") |
| f.write("// Do not edit!\n") |
| instr_index: dict[str, InstDef] = {} |
| for instr in instrs: |
| assert isinstance(instr, InstDef) |
| instr_index[instr.name] = instr |
| f.write(f"\n{indent}TARGET({instr.name}) {{\n") |
| if instr.name in predictions: |
| f.write(f"{indent} PREDICTED({instr.name});\n") |
| # input = ", ".join(instr.inputs) |
| # output = ", ".join(instr.outputs) |
| # f.write(f"{indent} // {input} -- {output}\n") |
| assert instr.block |
| blocklines = instr.block.text.splitlines(True) |
| # Remove blank lines from ends |
| while blocklines and not blocklines[0].strip(): |
| blocklines.pop(0) |
| while blocklines and not blocklines[-1].strip(): |
| blocklines.pop() |
| # Remove leading '{' and trailing '}' |
| assert blocklines and blocklines[0].strip() == "{" |
| assert blocklines and blocklines[-1].strip() == "}" |
| blocklines.pop() |
| blocklines.pop(0) |
| # Remove trailing blank lines |
| while blocklines and not blocklines[-1].strip(): |
| blocklines.pop() |
| # Write the body |
| for line in blocklines: |
| f.write(line) |
| assert instr.block |
| if not always_exits(instr.block): |
| f.write(f"{indent} DISPATCH();\n") |
| # Write trailing '}' |
| f.write(f"{indent}}}\n") |
| |
| for sup in supers: |
| assert isinstance(sup, parser.Super) |
| components = [instr_index[name] for name in sup.ops] |
| f.write(f"\n{indent}TARGET({sup.name}) {{\n") |
| for i, instr in enumerate(components): |
| if i > 0: |
| f.write(f"{indent} NEXTOPARG();\n") |
| f.write(f"{indent} next_instr++;\n") |
| text = instr.block.to_text(-4) |
| textlines = text.splitlines(True) |
| textlines = [line for line in textlines if not line.strip().startswith("PREDICTED(")] |
| text = "".join(textlines) |
| f.write(f"{indent} {text.strip()}\n") |
| f.write(f"{indent} DISPATCH();\n") |
| f.write(f"{indent}}}\n") |
| |
| |
| def main(): |
| args = arg_parser.parse_args() |
| with eopen(args.input) as f: |
| srclines = f.read().splitlines() |
| begin = srclines.index("// BEGIN BYTECODES //") |
| end = srclines.index("// END BYTECODES //") |
| src = "\n".join(srclines[begin+1 : end]) |
| instrs, supers, families = parse_cases(src, filename=args.input) |
| ninstrs = nsupers = nfamilies = 0 |
| if not args.quiet: |
| ninstrs = len(instrs) |
| nsupers = len(supers) |
| nfamilies = len(families) |
| print( |
| f"Read {ninstrs} instructions, {nsupers} supers, " |
| f"and {nfamilies} families from {args.input}", |
| file=sys.stderr, |
| ) |
| with eopen(args.output, "w") as f: |
| write_cases(f, instrs, supers) |
| if not args.quiet: |
| print( |
| f"Wrote {ninstrs + nsupers} instructions to {args.output}", |
| file=sys.stderr, |
| ) |
| |
| |
| if __name__ == "__main__": |
| main() |