"""Generate the cases for the tier 2 optimizer. Reads the instruction definitions from bytecodes.c and optimizer_bytecodes.c Writes the cases to optimizer_cases.c.h, which is #included in Python/optimizer_analysis.c. """ import argparse from analyzer import ( Analysis, Instruction, Uop, analyze_files, StackItem, analysis_error, ) from generators_common import ( DEFAULT_INPUT, ROOT, write_header, Emitter, TokenIterator, ) from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token from stack import Local, Stack, StackError, Storage DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_posix() def validate_uop(override: Uop, uop: Uop) -> None: # To do pass def type_name(var: StackItem) -> str: if var.is_array(): return f"_Py_UopsSymbol **" if var.type: return var.type return f"_Py_UopsSymbol *" def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: variables = {"unused"} if not skip_inputs: for var in reversed(uop.stack.inputs): if var.used and var.name not in variables: variables.add(var.name) if var.condition: out.emit(f"{type_name(var)}{var.name} = NULL;\n") else: out.emit(f"{type_name(var)}{var.name};\n") for var in uop.stack.outputs: if var.peek: continue if var.name not in variables: variables.add(var.name) if var.condition: out.emit(f"{type_name(var)}{var.name} = NULL;\n") else: out.emit(f"{type_name(var)}{var.name};\n") def decref_inputs( out: CWriter, tkn: Token, tkn_iter: TokenIterator, uop: Uop, stack: Stack, inst: Instruction | None, ) -> None: next(tkn_iter) next(tkn_iter) next(tkn_iter) out.emit_at("", tkn) def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None: for var in reversed(uop.stack.inputs): stack.pop(var) top_offset = stack.top_offset.copy() for var in uop.stack.outputs: if var.is_array() and not var.peek and not var.name == "unused": c_offset = top_offset.to_c() out.emit(f"{var.name} = &stack_pointer[{c_offset}];\n") top_offset.push(var) for var in uop.stack.outputs: local = Local.undefined(var) stack.push(local) if var.name != "unused" and not var.peek: local.defined = True if var.is_array(): if var.size == "1": out.emit(f"{var.name}[0] = sym_new_not_null(ctx);\n") else: out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") out.emit(f"{var.name}[_i] = sym_new_not_null(ctx);\n") out.emit("}\n") elif var.name == "null": out.emit(f"{var.name} = sym_new_null(ctx);\n") else: out.emit(f"{var.name} = sym_new_not_null(ctx);\n") class OptimizerEmitter(Emitter): def emit_save(self, storage: Storage) -> None: storage.flush(self.out) def emit_reload(self, storage: Storage) -> None: pass def write_uop( override: Uop | None, uop: Uop, out: CWriter, stack: Stack, debug: bool, skip_inputs: bool, ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop try: out.start_line() if override: code_list, storage = Storage.for_uop(stack, prototype, extract_bits=False) for code in code_list: out.emit(code) if debug: args = [] for input in prototype.stack.inputs: if not input.peek or override: args.append(input.name) out.emit(f'DEBUG_PRINTF({", ".join(args)});\n') if override: for cache in uop.caches: if cache.name != "unused": if cache.size == 4: type = cast = "PyObject *" else: type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n") if override: emitter = OptimizerEmitter(out) # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.defined = False storage = emitter.emit_tokens(override, storage, None) out.start_line() storage.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False) else: emit_default(out, uop, stack) out.start_line() stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False) except StackError as ex: raise analysis_error(ex.args[0], prototype.body[0]) # from None SKIPS = ("_EXTENDED_ARG",) def generate_abstract_interpreter( filenames: list[str], abstract: Analysis, base: Analysis, outfile: TextIO, debug: bool, ) -> None: write_header(__file__, filenames, outfile) out = CWriter(outfile, 2, False) out.emit("\n") base_uop_names = set([uop.name for uop in base.uops.values()]) for abstract_uop_name in abstract.uops: assert ( abstract_uop_name in base_uop_names ), f"All abstract uops should override base uops, but {abstract_uop_name} is not." for uop in base.uops.values(): override: Uop | None = None if uop.name in abstract.uops: override = abstract.uops[uop.name] validate_uop(override, uop) if uop.properties.tier == 1: continue if uop.replicates: continue if uop.is_super(): continue if not uop.is_viable(): out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n") continue out.emit(f"case {uop.name}: {{\n") if override: declare_variables(override, out, skip_inputs=False) else: declare_variables(uop, out, skip_inputs=True) stack = Stack() write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) out.start_line() out.emit("break;\n") out.emit("}") out.emit("\n\n") def generate_tier2_abstract_from_files( filenames: list[str], outfilename: str, debug: bool = False ) -> None: assert len(filenames) == 2, "Need a base file and an abstract cases file." base = analyze_files([filenames[0]]) abstract = analyze_files([filenames[1]]) with open(outfilename, "w") as outfile: generate_abstract_interpreter(filenames, abstract, base, outfile, debug) arg_parser = argparse.ArgumentParser( description="Generate the code for the tier 2 interpreter.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) arg_parser.add_argument( "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT ) arg_parser.add_argument("input", nargs="*", help="Abstract interpreter definition file") arg_parser.add_argument( "base", nargs="*", help="The base instruction definition file(s)" ) arg_parser.add_argument("-d", "--debug", help="Insert debug calls", action="store_true") if __name__ == "__main__": args = arg_parser.parse_args() if not args.input: args.base.append(DEFAULT_INPUT) args.input.append(DEFAULT_ABSTRACT_INPUT) else: args.base.append(args.input[-1]) args.input.pop() abstract = analyze_files(args.input) base = analyze_files(args.base) with open(args.output, "w") as outfile: generate_abstract_interpreter(args.input, abstract, base, outfile, args.debug)