1"""Generate the cases for the tier 2 optimizer. 2Reads the instruction definitions from bytecodes.c and optimizer_bytecodes.c 3Writes the cases to optimizer_cases.c.h, which is #included in Python/optimizer_analysis.c. 4""" 5 6import argparse 7 8from analyzer import ( 9 Analysis, 10 Instruction, 11 Uop, 12 analyze_files, 13 StackItem, 14 analysis_error, 15) 16from generators_common import ( 17 DEFAULT_INPUT, 18 ROOT, 19 write_header, 20 emit_tokens, 21 emit_to, 22 replace_sync_sp, 23) 24from cwriter import CWriter 25from typing import TextIO, Iterator 26from lexer import Token 27from stack import Stack, SizeMismatch, UNUSED 28 29DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" 30DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_posix() 31 32 33def validate_uop(override: Uop, uop: Uop) -> None: 34 # To do 35 pass 36 37 38def type_name(var: StackItem) -> str: 39 if var.is_array(): 40 return f"_Py_UopsSymbol **" 41 if var.type: 42 return var.type 43 return f"_Py_UopsSymbol *" 44 45 46def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: 47 variables = {"unused"} 48 if not skip_inputs: 49 for var in reversed(uop.stack.inputs): 50 if var.name not in variables: 51 variables.add(var.name) 52 if var.condition: 53 out.emit(f"{type_name(var)}{var.name} = NULL;\n") 54 else: 55 out.emit(f"{type_name(var)}{var.name};\n") 56 for var in uop.stack.outputs: 57 if var.peek: 58 continue 59 if var.name not in variables: 60 variables.add(var.name) 61 if var.condition: 62 out.emit(f"{type_name(var)}{var.name} = NULL;\n") 63 else: 64 out.emit(f"{type_name(var)}{var.name};\n") 65 66 67def decref_inputs( 68 out: CWriter, 69 tkn: Token, 70 tkn_iter: Iterator[Token], 71 uop: Uop, 72 stack: Stack, 73 inst: Instruction | None, 74) -> None: 75 next(tkn_iter) 76 next(tkn_iter) 77 next(tkn_iter) 78 out.emit_at("", tkn) 79 80 81def emit_default(out: CWriter, uop: Uop) -> None: 82 for i, var in enumerate(uop.stack.outputs): 83 if var.name != "unused" and not var.peek: 84 if var.is_array(): 85 out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") 86 out.emit(f"{var.name}[_i] = sym_new_not_null(ctx);\n") 87 out.emit(f"if ({var.name}[_i] == NULL) goto out_of_space;\n") 88 out.emit("}\n") 89 elif var.name == "null": 90 out.emit(f"{var.name} = sym_new_null(ctx);\n") 91 out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") 92 else: 93 out.emit(f"{var.name} = sym_new_not_null(ctx);\n") 94 out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") 95 96 97def write_uop( 98 override: Uop | None, 99 uop: Uop, 100 out: CWriter, 101 stack: Stack, 102 debug: bool, 103 skip_inputs: bool, 104) -> None: 105 try: 106 prototype = override if override else uop 107 is_override = override is not None 108 out.start_line() 109 for var in reversed(prototype.stack.inputs): 110 res = stack.pop(var) 111 if not skip_inputs: 112 out.emit(res) 113 if not prototype.properties.stores_sp: 114 for i, var in enumerate(prototype.stack.outputs): 115 res = stack.push(var) 116 if not var.peek or is_override: 117 out.emit(res) 118 if debug: 119 args = [] 120 for var in prototype.stack.inputs: 121 if not var.peek or is_override: 122 args.append(var.name) 123 out.emit(f'DEBUG_PRINTF({", ".join(args)});\n') 124 if override: 125 for cache in uop.caches: 126 if cache.name != "unused": 127 if cache.size == 4: 128 type = cast = "PyObject *" 129 else: 130 type = f"uint{cache.size*16}_t " 131 cast = f"uint{cache.size*16}_t" 132 out.emit(f"{type}{cache.name} = ({cast})this_instr->operand;\n") 133 if override: 134 replacement_funcs = { 135 "DECREF_INPUTS": decref_inputs, 136 "SYNC_SP": replace_sync_sp, 137 } 138 emit_tokens(out, override, stack, None, replacement_funcs) 139 else: 140 emit_default(out, uop) 141 142 if prototype.properties.stores_sp: 143 for i, var in enumerate(prototype.stack.outputs): 144 if not var.peek or is_override: 145 out.emit(stack.push(var)) 146 out.start_line() 147 stack.flush(out, cast_type="_Py_UopsSymbol *") 148 except SizeMismatch as ex: 149 raise analysis_error(ex.args[0], uop.body[0]) 150 151 152SKIPS = ("_EXTENDED_ARG",) 153 154 155def generate_abstract_interpreter( 156 filenames: list[str], 157 abstract: Analysis, 158 base: Analysis, 159 outfile: TextIO, 160 debug: bool, 161) -> None: 162 write_header(__file__, filenames, outfile) 163 out = CWriter(outfile, 2, False) 164 out.emit("\n") 165 base_uop_names = set([uop.name for uop in base.uops.values()]) 166 for abstract_uop_name in abstract.uops: 167 assert abstract_uop_name in base_uop_names,\ 168 f"All abstract uops should override base uops, but {abstract_uop_name} is not." 169 170 for uop in base.uops.values(): 171 override: Uop | None = None 172 if uop.name in abstract.uops: 173 override = abstract.uops[uop.name] 174 validate_uop(override, uop) 175 if uop.properties.tier == 1: 176 continue 177 if uop.replicates: 178 continue 179 if uop.is_super(): 180 continue 181 if not uop.is_viable(): 182 out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n") 183 continue 184 out.emit(f"case {uop.name}: {{\n") 185 if override: 186 declare_variables(override, out, skip_inputs=False) 187 else: 188 declare_variables(uop, out, skip_inputs=True) 189 stack = Stack() 190 write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) 191 out.start_line() 192 out.emit("break;\n") 193 out.emit("}") 194 out.emit("\n\n") 195 196 197def generate_tier2_abstract_from_files( 198 filenames: list[str], outfilename: str, debug: bool=False 199) -> None: 200 assert len(filenames) == 2, "Need a base file and an abstract cases file." 201 base = analyze_files([filenames[0]]) 202 abstract = analyze_files([filenames[1]]) 203 with open(outfilename, "w") as outfile: 204 generate_abstract_interpreter(filenames, abstract, base, outfile, debug) 205 206 207arg_parser = argparse.ArgumentParser( 208 description="Generate the code for the tier 2 interpreter.", 209 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 210) 211 212arg_parser.add_argument( 213 "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT 214) 215 216 217arg_parser.add_argument("input", nargs='*', help="Abstract interpreter definition file") 218 219arg_parser.add_argument( 220 "base", nargs="*", help="The base instruction definition file(s)" 221) 222 223arg_parser.add_argument("-d", "--debug", help="Insert debug calls", action="store_true") 224 225if __name__ == "__main__": 226 args = arg_parser.parse_args() 227 if not args.input: 228 args.base.append(DEFAULT_INPUT) 229 args.input.append(DEFAULT_ABSTRACT_INPUT) 230 else: 231 args.base.append(args.input[-1]) 232 args.input.pop() 233 abstract = analyze_files(args.input) 234 base = analyze_files(args.base) 235 with open(args.output, "w") as outfile: 236 generate_abstract_interpreter(args.input, abstract, base, outfile, args.debug) 237