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