1from pathlib import Path 2from typing import TextIO 3 4from analyzer import ( 5 Instruction, 6 Uop, 7 analyze_files, 8 Properties, 9 Skip, 10) 11from cwriter import CWriter 12from typing import Callable, Mapping, TextIO, Iterator 13from lexer import Token 14from stack import StackOffset, Stack 15 16 17ROOT = Path(__file__).parent.parent.parent 18DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute().as_posix() 19 20 21def root_relative_path(filename: str) -> str: 22 try: 23 return Path(filename).absolute().relative_to(ROOT).as_posix() 24 except ValueError: 25 # Not relative to root, just return original path. 26 return filename 27 28 29def write_header( 30 generator: str, sources: list[str], outfile: TextIO, comment: str = "//" 31) -> None: 32 outfile.write( 33 f"""{comment} This file is generated by {root_relative_path(generator)} 34{comment} from: 35{comment} {", ".join(root_relative_path(src) for src in sources)} 36{comment} Do not edit! 37""" 38 ) 39 40 41def emit_to(out: CWriter, tkn_iter: Iterator[Token], end: str) -> None: 42 parens = 0 43 for tkn in tkn_iter: 44 if tkn.kind == end and parens == 0: 45 return 46 if tkn.kind == "LPAREN": 47 parens += 1 48 if tkn.kind == "RPAREN": 49 parens -= 1 50 out.emit(tkn) 51 52 53def replace_deopt( 54 out: CWriter, 55 tkn: Token, 56 tkn_iter: Iterator[Token], 57 uop: Uop, 58 unused: Stack, 59 inst: Instruction | None, 60) -> None: 61 out.emit_at("DEOPT_IF", tkn) 62 out.emit(next(tkn_iter)) 63 emit_to(out, tkn_iter, "RPAREN") 64 next(tkn_iter) # Semi colon 65 out.emit(", ") 66 assert inst is not None 67 assert inst.family is not None 68 out.emit(inst.family.name) 69 out.emit(");\n") 70 71 72def replace_error( 73 out: CWriter, 74 tkn: Token, 75 tkn_iter: Iterator[Token], 76 uop: Uop, 77 stack: Stack, 78 inst: Instruction | None, 79) -> None: 80 out.emit_at("if ", tkn) 81 out.emit(next(tkn_iter)) 82 emit_to(out, tkn_iter, "COMMA") 83 label = next(tkn_iter).text 84 next(tkn_iter) # RPAREN 85 next(tkn_iter) # Semi colon 86 out.emit(") ") 87 c_offset = stack.peek_offset() 88 try: 89 offset = -int(c_offset) 90 close = ";\n" 91 except ValueError: 92 offset = None 93 out.emit(f"{{ stack_pointer += {c_offset}; ") 94 close = "; }\n" 95 out.emit("goto ") 96 if offset: 97 out.emit(f"pop_{offset}_") 98 out.emit(label) 99 out.emit(close) 100 101 102def replace_error_no_pop( 103 out: CWriter, 104 tkn: Token, 105 tkn_iter: Iterator[Token], 106 uop: Uop, 107 stack: Stack, 108 inst: Instruction | None, 109) -> None: 110 next(tkn_iter) # LPAREN 111 next(tkn_iter) # RPAREN 112 next(tkn_iter) # Semi colon 113 out.emit_at("goto error;", tkn) 114 115 116def replace_decrefs( 117 out: CWriter, 118 tkn: Token, 119 tkn_iter: Iterator[Token], 120 uop: Uop, 121 stack: Stack, 122 inst: Instruction | None, 123) -> None: 124 next(tkn_iter) 125 next(tkn_iter) 126 next(tkn_iter) 127 out.emit_at("", tkn) 128 for var in uop.stack.inputs: 129 if var.name == "unused" or var.name == "null" or var.peek: 130 continue 131 if var.size != "1": 132 out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") 133 out.emit(f"Py_DECREF({var.name}[_i]);\n") 134 out.emit("}\n") 135 elif var.condition: 136 if var.condition == "1": 137 out.emit(f"Py_DECREF({var.name});\n") 138 elif var.condition != "0": 139 out.emit(f"Py_XDECREF({var.name});\n") 140 else: 141 out.emit(f"Py_DECREF({var.name});\n") 142 143 144def replace_sync_sp( 145 out: CWriter, 146 tkn: Token, 147 tkn_iter: Iterator[Token], 148 uop: Uop, 149 stack: Stack, 150 inst: Instruction | None, 151) -> None: 152 next(tkn_iter) 153 next(tkn_iter) 154 next(tkn_iter) 155 stack.flush(out) 156 157 158def replace_check_eval_breaker( 159 out: CWriter, 160 tkn: Token, 161 tkn_iter: Iterator[Token], 162 uop: Uop, 163 stack: Stack, 164 inst: Instruction | None, 165) -> None: 166 next(tkn_iter) 167 next(tkn_iter) 168 next(tkn_iter) 169 if not uop.properties.ends_with_eval_breaker: 170 out.emit_at("CHECK_EVAL_BREAKER();", tkn) 171 172 173REPLACEMENT_FUNCTIONS = { 174 "EXIT_IF": replace_deopt, 175 "DEOPT_IF": replace_deopt, 176 "ERROR_IF": replace_error, 177 "ERROR_NO_POP": replace_error_no_pop, 178 "DECREF_INPUTS": replace_decrefs, 179 "CHECK_EVAL_BREAKER": replace_check_eval_breaker, 180 "SYNC_SP": replace_sync_sp, 181} 182 183ReplacementFunctionType = Callable[ 184 [CWriter, Token, Iterator[Token], Uop, Stack, Instruction | None], None 185] 186 187 188def emit_tokens( 189 out: CWriter, 190 uop: Uop, 191 stack: Stack, 192 inst: Instruction | None, 193 replacement_functions: Mapping[ 194 str, ReplacementFunctionType 195 ] = REPLACEMENT_FUNCTIONS, 196) -> None: 197 tkns = uop.body[1:-1] 198 if not tkns: 199 return 200 tkn_iter = iter(tkns) 201 out.start_line() 202 for tkn in tkn_iter: 203 if tkn.kind == "IDENTIFIER" and tkn.text in replacement_functions: 204 replacement_functions[tkn.text](out, tkn, tkn_iter, uop, stack, inst) 205 else: 206 out.emit(tkn) 207 208 209def cflags(p: Properties) -> str: 210 flags: list[str] = [] 211 if p.oparg: 212 flags.append("HAS_ARG_FLAG") 213 if p.uses_co_consts: 214 flags.append("HAS_CONST_FLAG") 215 if p.uses_co_names: 216 flags.append("HAS_NAME_FLAG") 217 if p.jumps: 218 flags.append("HAS_JUMP_FLAG") 219 if p.has_free: 220 flags.append("HAS_FREE_FLAG") 221 if p.uses_locals: 222 flags.append("HAS_LOCAL_FLAG") 223 if p.eval_breaker: 224 flags.append("HAS_EVAL_BREAK_FLAG") 225 if p.deopts: 226 flags.append("HAS_DEOPT_FLAG") 227 if p.side_exit: 228 flags.append("HAS_EXIT_FLAG") 229 if not p.infallible: 230 flags.append("HAS_ERROR_FLAG") 231 if p.error_without_pop: 232 flags.append("HAS_ERROR_NO_POP_FLAG") 233 if p.escapes: 234 flags.append("HAS_ESCAPES_FLAG") 235 if p.pure: 236 flags.append("HAS_PURE_FLAG") 237 if p.oparg_and_1: 238 flags.append("HAS_OPARG_AND_1_FLAG") 239 if flags: 240 return " | ".join(flags) 241 else: 242 return "0" 243