1"""Generate the cases for the tier 2 interpreter. 2Reads the instruction definitions from bytecodes.c. 3Writes the cases to executor_cases.c.h, which is #included in ceval.c. 4""" 5 6import argparse 7import os.path 8import sys 9 10from analyzer import ( 11 Analysis, 12 Instruction, 13 Uop, 14 Part, 15 analyze_files, 16 Skip, 17 StackItem, 18 analysis_error, 19) 20from generators_common import ( 21 DEFAULT_INPUT, 22 ROOT, 23 write_header, 24 emit_tokens, 25 emit_to, 26 REPLACEMENT_FUNCTIONS, 27) 28from cwriter import CWriter 29from typing import TextIO, Iterator 30from lexer import Token 31from stack import StackOffset, Stack, SizeMismatch 32 33DEFAULT_OUTPUT = ROOT / "Python/executor_cases.c.h" 34 35 36def declare_variable( 37 var: StackItem, uop: Uop, variables: set[str], out: CWriter 38) -> None: 39 if var.name in variables: 40 return 41 type = var.type if var.type else "PyObject *" 42 variables.add(var.name) 43 if var.condition: 44 out.emit(f"{type}{var.name} = NULL;\n") 45 if uop.replicates: 46 # Replicas may not use all their conditional variables 47 # So avoid a compiler warning with a fake use 48 out.emit(f"(void){var.name};\n") 49 else: 50 out.emit(f"{type}{var.name};\n") 51 52 53def declare_variables(uop: Uop, out: CWriter) -> None: 54 variables = {"unused"} 55 for var in reversed(uop.stack.inputs): 56 declare_variable(var, uop, variables, out) 57 for var in uop.stack.outputs: 58 declare_variable(var, uop, variables, out) 59 60 61def tier2_replace_error( 62 out: CWriter, 63 tkn: Token, 64 tkn_iter: Iterator[Token], 65 uop: Uop, 66 stack: Stack, 67 inst: Instruction | None, 68) -> None: 69 out.emit_at("if ", tkn) 70 out.emit(next(tkn_iter)) 71 emit_to(out, tkn_iter, "COMMA") 72 label = next(tkn_iter).text 73 next(tkn_iter) # RPAREN 74 next(tkn_iter) # Semi colon 75 out.emit(") JUMP_TO_ERROR();\n") 76 77 78def tier2_replace_error_no_pop( 79 out: CWriter, 80 tkn: Token, 81 tkn_iter: Iterator[Token], 82 uop: Uop, 83 stack: Stack, 84 inst: Instruction | None, 85) -> None: 86 next(tkn_iter) # LPAREN 87 next(tkn_iter) # RPAREN 88 next(tkn_iter) # Semi colon 89 out.emit_at("JUMP_TO_ERROR();", tkn) 90 91def tier2_replace_deopt( 92 out: CWriter, 93 tkn: Token, 94 tkn_iter: Iterator[Token], 95 uop: Uop, 96 unused: Stack, 97 inst: Instruction | None, 98) -> None: 99 out.emit_at("if ", tkn) 100 out.emit(next(tkn_iter)) 101 emit_to(out, tkn_iter, "RPAREN") 102 next(tkn_iter) # Semi colon 103 out.emit(") {\n") 104 out.emit("UOP_STAT_INC(uopcode, miss);\n") 105 out.emit("JUMP_TO_JUMP_TARGET();\n"); 106 out.emit("}\n") 107 108 109def tier2_replace_exit_if( 110 out: CWriter, 111 tkn: Token, 112 tkn_iter: Iterator[Token], 113 uop: Uop, 114 unused: Stack, 115 inst: Instruction | None, 116) -> None: 117 out.emit_at("if ", tkn) 118 out.emit(next(tkn_iter)) 119 emit_to(out, tkn_iter, "RPAREN") 120 next(tkn_iter) # Semi colon 121 out.emit(") {\n") 122 out.emit("UOP_STAT_INC(uopcode, miss);\n") 123 out.emit("JUMP_TO_JUMP_TARGET();\n") 124 out.emit("}\n") 125 126 127def tier2_replace_oparg( 128 out: CWriter, 129 tkn: Token, 130 tkn_iter: Iterator[Token], 131 uop: Uop, 132 unused: Stack, 133 inst: Instruction | None, 134) -> None: 135 if not uop.name.endswith("_0") and not uop.name.endswith("_1"): 136 out.emit(tkn) 137 return 138 amp = next(tkn_iter) 139 if amp.text != "&": 140 out.emit(tkn) 141 out.emit(amp) 142 return 143 one = next(tkn_iter) 144 assert one.text == "1" 145 out.emit_at(uop.name[-1], tkn) 146 147 148TIER2_REPLACEMENT_FUNCTIONS = REPLACEMENT_FUNCTIONS.copy() 149TIER2_REPLACEMENT_FUNCTIONS["ERROR_IF"] = tier2_replace_error 150TIER2_REPLACEMENT_FUNCTIONS["ERROR_NO_POP"] = tier2_replace_error_no_pop 151TIER2_REPLACEMENT_FUNCTIONS["DEOPT_IF"] = tier2_replace_deopt 152TIER2_REPLACEMENT_FUNCTIONS["oparg"] = tier2_replace_oparg 153TIER2_REPLACEMENT_FUNCTIONS["EXIT_IF"] = tier2_replace_exit_if 154 155 156def write_uop(uop: Uop, out: CWriter, stack: Stack) -> None: 157 try: 158 out.start_line() 159 if uop.properties.oparg: 160 out.emit("oparg = CURRENT_OPARG();\n") 161 assert uop.properties.const_oparg < 0 162 elif uop.properties.const_oparg >= 0: 163 out.emit(f"oparg = {uop.properties.const_oparg};\n") 164 out.emit(f"assert(oparg == CURRENT_OPARG());\n") 165 for var in reversed(uop.stack.inputs): 166 out.emit(stack.pop(var)) 167 if not uop.properties.stores_sp: 168 for i, var in enumerate(uop.stack.outputs): 169 out.emit(stack.push(var)) 170 for cache in uop.caches: 171 if cache.name != "unused": 172 if cache.size == 4: 173 type = cast = "PyObject *" 174 else: 175 type = f"uint{cache.size*16}_t " 176 cast = f"uint{cache.size*16}_t" 177 out.emit(f"{type}{cache.name} = ({cast})CURRENT_OPERAND();\n") 178 emit_tokens(out, uop, stack, None, TIER2_REPLACEMENT_FUNCTIONS) 179 if uop.properties.stores_sp: 180 for i, var in enumerate(uop.stack.outputs): 181 out.emit(stack.push(var)) 182 except SizeMismatch as ex: 183 raise analysis_error(ex.args[0], uop.body[0]) 184 185 186SKIPS = ("_EXTENDED_ARG",) 187 188 189def generate_tier2( 190 filenames: list[str], analysis: Analysis, outfile: TextIO, lines: bool 191) -> None: 192 write_header(__file__, filenames, outfile) 193 outfile.write( 194 """ 195#ifdef TIER_ONE 196 #error "This file is for Tier 2 only" 197#endif 198#define TIER_TWO 2 199""" 200 ) 201 out = CWriter(outfile, 2, lines) 202 out.emit("\n") 203 for name, uop in analysis.uops.items(): 204 if uop.properties.tier == 1: 205 continue 206 if uop.properties.oparg_and_1: 207 out.emit(f"/* {uop.name} is split on (oparg & 1) */\n\n") 208 continue 209 if uop.is_super(): 210 continue 211 why_not_viable = uop.why_not_viable() 212 if why_not_viable is not None: 213 out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 because it {why_not_viable} */\n\n") 214 continue 215 out.emit(f"case {uop.name}: {{\n") 216 declare_variables(uop, out) 217 stack = Stack() 218 write_uop(uop, out, stack) 219 out.start_line() 220 if not uop.properties.always_exits: 221 stack.flush(out) 222 if uop.properties.ends_with_eval_breaker: 223 out.emit("CHECK_EVAL_BREAKER();\n") 224 out.emit("break;\n") 225 out.start_line() 226 out.emit("}") 227 out.emit("\n\n") 228 outfile.write("#undef TIER_TWO\n") 229 230 231arg_parser = argparse.ArgumentParser( 232 description="Generate the code for the tier 2 interpreter.", 233 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 234) 235 236arg_parser.add_argument( 237 "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT 238) 239 240arg_parser.add_argument( 241 "-l", "--emit-line-directives", help="Emit #line directives", action="store_true" 242) 243 244arg_parser.add_argument( 245 "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" 246) 247 248if __name__ == "__main__": 249 args = arg_parser.parse_args() 250 if len(args.input) == 0: 251 args.input.append(DEFAULT_INPUT) 252 data = analyze_files(args.input) 253 with open(args.output, "w") as outfile: 254 generate_tier2(args.input, data, outfile, args.emit_line_directives) 255