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