• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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