cpython/Tools/cases_generator/optimizer_generator.py

249 lines
7.8 KiB
Python

"""Generate the cases for the tier 2 optimizer.
Reads the instruction definitions from bytecodes.c and optimizer_bytecodes.c
Writes the cases to optimizer_cases.c.h, which is #included in Python/optimizer_analysis.c.
"""
import argparse
from analyzer import (
Analysis,
Instruction,
Uop,
analyze_files,
StackItem,
analysis_error,
)
from generators_common import (
DEFAULT_INPUT,
ROOT,
write_header,
Emitter,
TokenIterator,
)
from cwriter import CWriter
from typing import TextIO, Iterator
from lexer import Token
from stack import Local, Stack, StackError, Storage
DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h"
DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_posix()
def validate_uop(override: Uop, uop: Uop) -> None:
# To do
pass
def type_name(var: StackItem) -> str:
if var.is_array():
return f"_Py_UopsSymbol **"
if var.type:
return var.type
return f"_Py_UopsSymbol *"
def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None:
variables = {"unused"}
if not skip_inputs:
for var in reversed(uop.stack.inputs):
if var.used and var.name not in variables:
variables.add(var.name)
if var.condition:
out.emit(f"{type_name(var)}{var.name} = NULL;\n")
else:
out.emit(f"{type_name(var)}{var.name};\n")
for var in uop.stack.outputs:
if var.peek:
continue
if var.name not in variables:
variables.add(var.name)
if var.condition:
out.emit(f"{type_name(var)}{var.name} = NULL;\n")
else:
out.emit(f"{type_name(var)}{var.name};\n")
def decref_inputs(
out: CWriter,
tkn: Token,
tkn_iter: TokenIterator,
uop: Uop,
stack: Stack,
inst: Instruction | None,
) -> None:
next(tkn_iter)
next(tkn_iter)
next(tkn_iter)
out.emit_at("", tkn)
def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None:
for var in reversed(uop.stack.inputs):
stack.pop(var)
top_offset = stack.top_offset.copy()
for var in uop.stack.outputs:
if var.is_array() and not var.peek and not var.name == "unused":
c_offset = top_offset.to_c()
out.emit(f"{var.name} = &stack_pointer[{c_offset}];\n")
top_offset.push(var)
for var in uop.stack.outputs:
local = Local.undefined(var)
stack.push(local)
if var.name != "unused" and not var.peek:
local.defined = True
if var.is_array():
if var.size == "1":
out.emit(f"{var.name}[0] = sym_new_not_null(ctx);\n")
else:
out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n")
out.emit(f"{var.name}[_i] = sym_new_not_null(ctx);\n")
out.emit("}\n")
elif var.name == "null":
out.emit(f"{var.name} = sym_new_null(ctx);\n")
else:
out.emit(f"{var.name} = sym_new_not_null(ctx);\n")
class OptimizerEmitter(Emitter):
def emit_save(self, storage: Storage) -> None:
storage.flush(self.out)
def emit_reload(self, storage: Storage) -> None:
pass
def write_uop(
override: Uop | None,
uop: Uop,
out: CWriter,
stack: Stack,
debug: bool,
skip_inputs: bool,
) -> None:
locals: dict[str, Local] = {}
prototype = override if override else uop
try:
out.start_line()
if override:
code_list, storage = Storage.for_uop(stack, prototype, extract_bits=False)
for code in code_list:
out.emit(code)
if debug:
args = []
for input in prototype.stack.inputs:
if not input.peek or override:
args.append(input.name)
out.emit(f'DEBUG_PRINTF({", ".join(args)});\n')
if override:
for cache in uop.caches:
if cache.name != "unused":
if cache.size == 4:
type = cast = "PyObject *"
else:
type = f"uint{cache.size*16}_t "
cast = f"uint{cache.size*16}_t"
out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n")
if override:
emitter = OptimizerEmitter(out)
# No reference management of inputs needed.
for var in storage.inputs: # type: ignore[possibly-undefined]
var.defined = False
storage = emitter.emit_tokens(override, storage, None)
out.start_line()
storage.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False)
else:
emit_default(out, uop, stack)
out.start_line()
stack.flush(out, cast_type="_Py_UopsSymbol *", extract_bits=False)
except StackError as ex:
raise analysis_error(ex.args[0], prototype.body[0]) # from None
SKIPS = ("_EXTENDED_ARG",)
def generate_abstract_interpreter(
filenames: list[str],
abstract: Analysis,
base: Analysis,
outfile: TextIO,
debug: bool,
) -> None:
write_header(__file__, filenames, outfile)
out = CWriter(outfile, 2, False)
out.emit("\n")
base_uop_names = set([uop.name for uop in base.uops.values()])
for abstract_uop_name in abstract.uops:
assert (
abstract_uop_name in base_uop_names
), f"All abstract uops should override base uops, but {abstract_uop_name} is not."
for uop in base.uops.values():
override: Uop | None = None
if uop.name in abstract.uops:
override = abstract.uops[uop.name]
validate_uop(override, uop)
if uop.properties.tier == 1:
continue
if uop.replicates:
continue
if uop.is_super():
continue
if not uop.is_viable():
out.emit(f"/* {uop.name} is not a viable micro-op for tier 2 */\n\n")
continue
out.emit(f"case {uop.name}: {{\n")
if override:
declare_variables(override, out, skip_inputs=False)
else:
declare_variables(uop, out, skip_inputs=True)
stack = Stack()
write_uop(override, uop, out, stack, debug, skip_inputs=(override is None))
out.start_line()
out.emit("break;\n")
out.emit("}")
out.emit("\n\n")
def generate_tier2_abstract_from_files(
filenames: list[str], outfilename: str, debug: bool = False
) -> None:
assert len(filenames) == 2, "Need a base file and an abstract cases file."
base = analyze_files([filenames[0]])
abstract = analyze_files([filenames[1]])
with open(outfilename, "w") as outfile:
generate_abstract_interpreter(filenames, abstract, base, outfile, debug)
arg_parser = argparse.ArgumentParser(
description="Generate the code for the tier 2 interpreter.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
arg_parser.add_argument(
"-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT
)
arg_parser.add_argument("input", nargs="*", help="Abstract interpreter definition file")
arg_parser.add_argument(
"base", nargs="*", help="The base instruction definition file(s)"
)
arg_parser.add_argument("-d", "--debug", help="Insert debug calls", action="store_true")
if __name__ == "__main__":
args = arg_parser.parse_args()
if not args.input:
args.base.append(DEFAULT_INPUT)
args.input.append(DEFAULT_ABSTRACT_INPUT)
else:
args.base.append(args.input[-1])
args.input.pop()
abstract = analyze_files(args.input)
base = analyze_files(args.base)
with open(args.output, "w") as outfile:
generate_abstract_interpreter(args.input, abstract, base, outfile, args.debug)