1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
"""Generate the main interpreter switch.
Reads the instruction definitions from bytecodes.c.
Writes the cases to generated_cases.c.h, which is #included in ceval.c.
"""
import argparse
import os.path
import sys
from analyzer import (
Analysis,
Instruction,
Uop,
Part,
analyze_files,
Skip,
StackItem,
analysis_error,
)
from generators_common import (
DEFAULT_INPUT,
ROOT,
write_header,
emit_tokens,
)
from cwriter import CWriter
from typing import TextIO, Iterator
from lexer import Token
from stack import StackOffset, Stack, SizeMismatch
DEFAULT_OUTPUT = ROOT / "Python/generated_cases.c.h"
FOOTER = "#undef TIER_ONE\n"
def declare_variables(inst: Instruction, out: CWriter) -> None:
variables = {"unused"}
for uop in inst.parts:
if isinstance(uop, Uop):
for var in reversed(uop.stack.inputs):
if var.name not in variables:
type = var.type if var.type else "PyObject *"
variables.add(var.name)
if var.condition:
out.emit(f"{type}{var.name} = NULL;\n")
else:
out.emit(f"{type}{var.name};\n")
for var in uop.stack.outputs:
if var.name not in variables:
variables.add(var.name)
type = var.type if var.type else "PyObject *"
if var.condition:
out.emit(f"{type}{var.name} = NULL;\n")
else:
out.emit(f"{type}{var.name};\n")
def write_uop(
uop: Part, out: CWriter, offset: int, stack: Stack, inst: Instruction, braces: bool
) -> int:
# out.emit(stack.as_comment() + "\n")
if isinstance(uop, Skip):
entries = "entries" if uop.size > 1 else "entry"
out.emit(f"/* Skip {uop.size} cache {entries} */\n")
return offset + uop.size
try:
out.start_line()
if braces:
out.emit(f"// {uop.name}\n")
for var in reversed(uop.stack.inputs):
out.emit(stack.pop(var))
if braces:
out.emit("{\n")
if not uop.properties.stores_sp:
for i, var in enumerate(uop.stack.outputs):
out.emit(stack.push(var))
for cache in uop.caches:
if cache.name != "unused":
if cache.size == 4:
type = "PyObject *"
reader = "read_obj"
else:
type = f"uint{cache.size*16}_t "
reader = f"read_u{cache.size*16}"
out.emit(
f"{type}{cache.name} = {reader}(&this_instr[{offset}].cache);\n"
)
if inst.family is None:
out.emit(f"(void){cache.name};\n")
offset += cache.size
emit_tokens(out, uop, stack, inst)
if uop.properties.stores_sp:
for i, var in enumerate(uop.stack.outputs):
out.emit(stack.push(var))
if braces:
out.start_line()
out.emit("}\n")
# out.emit(stack.as_comment() + "\n")
return offset
except SizeMismatch as ex:
raise analysis_error(ex.args[0], uop.body[0])
def uses_this(inst: Instruction) -> bool:
if inst.properties.needs_this:
return True
for uop in inst.parts:
if isinstance(uop, Skip):
continue
for cache in uop.caches:
if cache.name != "unused":
return True
return False
def generate_tier1(
filenames: list[str], analysis: Analysis, outfile: TextIO, lines: bool
) -> None:
write_header(__file__, filenames, outfile)
outfile.write(
"""
#ifdef TIER_TWO
#error "This file is for Tier 1 only"
#endif
#define TIER_ONE 1
"""
)
out = CWriter(outfile, 2, lines)
out.emit("\n")
for name, inst in sorted(analysis.instructions.items()):
needs_this = uses_this(inst)
out.emit("\n")
out.emit(f"TARGET({name}) {{\n")
unused_guard = "(void)this_instr;\n" if inst.family is None else ""
if needs_this and not inst.is_target:
out.emit(f"_Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr;\n")
out.emit(unused_guard)
else:
out.emit(f"frame->instr_ptr = next_instr;\n")
out.emit(f"next_instr += {inst.size};\n")
out.emit(f"INSTRUCTION_STATS({name});\n")
if inst.is_target:
out.emit(f"PREDICTED({name});\n")
if needs_this:
out.emit(f"_Py_CODEUNIT *this_instr = next_instr - {inst.size};\n")
out.emit(unused_guard)
if inst.family is not None:
out.emit(
f"static_assert({inst.family.size} == {inst.size-1}"
', "incorrect cache size");\n'
)
declare_variables(inst, out)
offset = 1 # The instruction itself
stack = Stack()
for part in inst.parts:
# Only emit braces if more than one uop
insert_braces = len([p for p in inst.parts if isinstance(p, Uop)]) > 1
offset = write_uop(part, out, offset, stack, inst, insert_braces)
out.start_line()
if not inst.parts[-1].properties.always_exits:
stack.flush(out)
if inst.parts[-1].properties.ends_with_eval_breaker:
out.emit("CHECK_EVAL_BREAKER();\n")
out.emit("DISPATCH();\n")
out.start_line()
out.emit("}")
out.emit("\n")
outfile.write(FOOTER)
arg_parser = argparse.ArgumentParser(
description="Generate the code for the interpreter switch.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
arg_parser.add_argument(
"-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT
)
arg_parser.add_argument(
"-l", "--emit-line-directives", help="Emit #line directives", action="store_true"
)
arg_parser.add_argument(
"input", nargs=argparse.REMAINDER, help="Instruction definition file(s)"
)
def generate_tier1_from_files(
filenames: list[str], outfilename: str, lines: bool
) -> None:
data = analyze_files(filenames)
with open(outfilename, "w") as outfile:
generate_tier1(filenames, data, outfile, lines)
if __name__ == "__main__":
args = arg_parser.parse_args()
if len(args.input) == 0:
args.input.append(DEFAULT_INPUT)
data = analyze_files(args.input)
with open(args.output, "w") as outfile:
generate_tier1(args.input, data, outfile, args.emit_line_directives)
|