mirror of
https://github.com/python/cpython.git
synced 2025-10-10 00:43:41 +00:00
gh-98831: Support conditional effects; use for LOAD_ATTR (#101333)
This commit is contained in:
parent
c4170c36b0
commit
f5a3d91b6c
6 changed files with 194 additions and 120 deletions
|
@ -26,7 +26,9 @@ DEFAULT_METADATA_OUTPUT = os.path.relpath(
|
|||
)
|
||||
BEGIN_MARKER = "// BEGIN BYTECODES //"
|
||||
END_MARKER = "// END BYTECODES //"
|
||||
RE_PREDICTED = r"^\s*(?:PREDICT\(|GO_TO_INSTRUCTION\(|DEOPT_IF\(.*?,\s*)(\w+)\);\s*(?://.*)?$"
|
||||
RE_PREDICTED = (
|
||||
r"^\s*(?:PREDICT\(|GO_TO_INSTRUCTION\(|DEOPT_IF\(.*?,\s*)(\w+)\);\s*(?://.*)?$"
|
||||
)
|
||||
UNUSED = "unused"
|
||||
BITS_PER_CODE_UNIT = 16
|
||||
|
||||
|
@ -59,7 +61,10 @@ def effect_size(effect: StackEffect) -> tuple[int, str]:
|
|||
At most one of these will be non-zero / non-empty.
|
||||
"""
|
||||
if effect.size:
|
||||
assert not effect.cond, "Array effects cannot have a condition"
|
||||
return 0, effect.size
|
||||
elif effect.cond:
|
||||
return 0, f"{maybe_parenthesize(effect.cond)} ? 1 : 0"
|
||||
else:
|
||||
return 1, ""
|
||||
|
||||
|
@ -132,7 +137,12 @@ class Formatter:
|
|||
yield
|
||||
self.emit("}")
|
||||
|
||||
def stack_adjust(self, diff: int, input_effects: list[StackEffect], output_effects: list[StackEffect]):
|
||||
def stack_adjust(
|
||||
self,
|
||||
diff: int,
|
||||
input_effects: list[StackEffect],
|
||||
output_effects: list[StackEffect],
|
||||
):
|
||||
# TODO: Get rid of 'diff' parameter
|
||||
shrink, isym = list_effect_size(input_effects)
|
||||
grow, osym = list_effect_size(output_effects)
|
||||
|
@ -150,10 +160,13 @@ class Formatter:
|
|||
if dst.name == UNUSED:
|
||||
return
|
||||
typ = f"{dst.type}" if dst.type else "PyObject *"
|
||||
init = ""
|
||||
if src:
|
||||
cast = self.cast(dst, src)
|
||||
init = f" = {cast}{src.name}"
|
||||
elif dst.cond:
|
||||
init = " = NULL"
|
||||
else:
|
||||
init = ""
|
||||
sepa = "" if typ.endswith("*") else " "
|
||||
self.emit(f"{typ}{sepa}{dst.name}{init};")
|
||||
|
||||
|
@ -162,7 +175,10 @@ class Formatter:
|
|||
return
|
||||
cast = self.cast(dst, src)
|
||||
if m := re.match(r"^PEEK\((.*)\)$", dst.name):
|
||||
self.emit(f"POKE({m.group(1)}, {cast}{src.name});")
|
||||
stmt = f"POKE({m.group(1)}, {cast}{src.name});"
|
||||
if src.cond:
|
||||
stmt = f"if ({src.cond}) {{ {stmt} }}"
|
||||
self.emit(stmt)
|
||||
elif m := re.match(r"^&PEEK\(.*\)$", dst.name):
|
||||
# NOTE: MOVE_ITEMS() does not actually exist.
|
||||
# The only supported output array forms are:
|
||||
|
@ -234,7 +250,7 @@ class Instruction:
|
|||
if self.register:
|
||||
num_regs = len(self.input_effects) + len(self.output_effects)
|
||||
num_dummies = (num_regs // 2) * 2 + 1 - num_regs
|
||||
fmt = "I" + "B"*num_regs + "X"*num_dummies
|
||||
fmt = "I" + "B" * num_regs + "X" * num_dummies
|
||||
else:
|
||||
if variable_used(inst, "oparg"):
|
||||
fmt = "IB"
|
||||
|
@ -276,9 +292,13 @@ class Instruction:
|
|||
# Write input stack effect variable declarations and initializations
|
||||
ieffects = list(reversed(self.input_effects))
|
||||
for i, ieffect in enumerate(ieffects):
|
||||
isize = string_effect_size(list_effect_size(ieffects[:i+1]))
|
||||
isize = string_effect_size(
|
||||
list_effect_size([ieff for ieff in ieffects[: i + 1]])
|
||||
)
|
||||
if ieffect.size:
|
||||
src = StackEffect(f"&PEEK({isize})", "PyObject **")
|
||||
elif ieffect.cond:
|
||||
src = StackEffect(f"({ieffect.cond}) ? PEEK({isize}) : NULL", "")
|
||||
else:
|
||||
src = StackEffect(f"PEEK({isize})", "")
|
||||
out.declare(ieffect, src)
|
||||
|
@ -304,14 +324,20 @@ class Instruction:
|
|||
|
||||
if not self.register:
|
||||
# Write net stack growth/shrinkage
|
||||
out.stack_adjust(0, self.input_effects, self.output_effects)
|
||||
out.stack_adjust(
|
||||
0,
|
||||
[ieff for ieff in self.input_effects],
|
||||
[oeff for oeff in self.output_effects],
|
||||
)
|
||||
|
||||
# Write output stack effect assignments
|
||||
oeffects = list(reversed(self.output_effects))
|
||||
for i, oeffect in enumerate(oeffects):
|
||||
if oeffect.name in self.unmoved_names:
|
||||
continue
|
||||
osize = string_effect_size(list_effect_size(oeffects[:i+1]))
|
||||
osize = string_effect_size(
|
||||
list_effect_size([oeff for oeff in oeffects[: i + 1]])
|
||||
)
|
||||
if oeffect.size:
|
||||
dst = StackEffect(f"&PEEK({osize})", "PyObject **")
|
||||
else:
|
||||
|
@ -438,6 +464,7 @@ class MacroInstruction(SuperOrMacroInstruction):
|
|||
parts: list[Component | parser.CacheEffect]
|
||||
|
||||
|
||||
AnyInstruction = Instruction | SuperInstruction | MacroInstruction
|
||||
INSTR_FMT_PREFIX = "INSTR_FMT_"
|
||||
|
||||
|
||||
|
@ -506,6 +533,7 @@ class Analyzer:
|
|||
self.supers = {}
|
||||
self.macros = {}
|
||||
self.families = {}
|
||||
thing: parser.InstDef | parser.Super | parser.Macro | parser.Family | None
|
||||
while thing := psr.definition():
|
||||
match thing:
|
||||
case parser.InstDef(name=name):
|
||||
|
@ -631,7 +659,9 @@ class Analyzer:
|
|||
parts.append(part)
|
||||
format += instr.instr_fmt
|
||||
final_sp = sp
|
||||
return SuperInstruction(super.name, stack, initial_sp, final_sp, format, super, parts)
|
||||
return SuperInstruction(
|
||||
super.name, stack, initial_sp, final_sp, format, super, parts
|
||||
)
|
||||
|
||||
def analyze_macro(self, macro: parser.Macro) -> MacroInstruction:
|
||||
components = self.check_macro_components(macro)
|
||||
|
@ -657,7 +687,9 @@ class Analyzer:
|
|||
case _:
|
||||
typing.assert_never(component)
|
||||
final_sp = sp
|
||||
return MacroInstruction(macro.name, stack, initial_sp, final_sp, format, macro, parts)
|
||||
return MacroInstruction(
|
||||
macro.name, stack, initial_sp, final_sp, format, macro, parts
|
||||
)
|
||||
|
||||
def analyze_instruction(
|
||||
self, instr: Instruction, stack: list[StackEffect], sp: int
|
||||
|
@ -710,7 +742,9 @@ class Analyzer:
|
|||
for thing in components:
|
||||
match thing:
|
||||
case Instruction() as instr:
|
||||
if any(eff.size for eff in instr.input_effects + instr.output_effects):
|
||||
if any(
|
||||
eff.size for eff in instr.input_effects + instr.output_effects
|
||||
):
|
||||
# TODO: Eventually this will be needed, at least for macros.
|
||||
self.error(
|
||||
f"Instruction {instr.name!r} has variable-sized stack effect, "
|
||||
|
@ -736,16 +770,16 @@ class Analyzer:
|
|||
|
||||
def get_stack_effect_info(
|
||||
self, thing: parser.InstDef | parser.Super | parser.Macro
|
||||
) -> tuple[Instruction|None, str, str]:
|
||||
|
||||
def effect_str(effect: list[StackEffect]) -> str:
|
||||
if getattr(thing, 'kind', None) == 'legacy':
|
||||
) -> tuple[AnyInstruction | None, str, str]:
|
||||
def effect_str(effects: list[StackEffect]) -> str:
|
||||
if getattr(thing, "kind", None) == "legacy":
|
||||
return str(-1)
|
||||
n_effect, sym_effect = list_effect_size(effect)
|
||||
n_effect, sym_effect = list_effect_size(effects)
|
||||
if sym_effect:
|
||||
return f"{sym_effect} + {n_effect}" if n_effect else sym_effect
|
||||
return str(n_effect)
|
||||
|
||||
instr: AnyInstruction | None
|
||||
match thing:
|
||||
case parser.InstDef():
|
||||
if thing.kind != "op":
|
||||
|
@ -754,34 +788,43 @@ class Analyzer:
|
|||
pushed = effect_str(instr.output_effects)
|
||||
else:
|
||||
instr = None
|
||||
popped = pushed = "", ""
|
||||
popped = ""
|
||||
pushed = ""
|
||||
case parser.Super():
|
||||
instr = self.super_instrs[thing.name]
|
||||
popped = '+'.join(effect_str(comp.instr.input_effects) for comp in instr.parts)
|
||||
pushed = '+'.join(effect_str(comp.instr.output_effects) for comp in instr.parts)
|
||||
popped = "+".join(
|
||||
effect_str(comp.instr.input_effects) for comp in instr.parts
|
||||
)
|
||||
pushed = "+".join(
|
||||
effect_str(comp.instr.output_effects) for comp in instr.parts
|
||||
)
|
||||
case parser.Macro():
|
||||
instr = self.macro_instrs[thing.name]
|
||||
parts = [comp for comp in instr.parts if isinstance(comp, Component)]
|
||||
popped = '+'.join(effect_str(comp.instr.input_effects) for comp in parts)
|
||||
pushed = '+'.join(effect_str(comp.instr.output_effects) for comp in parts)
|
||||
popped = "+".join(
|
||||
effect_str(comp.instr.input_effects) for comp in parts
|
||||
)
|
||||
pushed = "+".join(
|
||||
effect_str(comp.instr.output_effects) for comp in parts
|
||||
)
|
||||
case _:
|
||||
typing.assert_never(thing)
|
||||
return instr, popped, pushed
|
||||
|
||||
def write_stack_effect_functions(self) -> None:
|
||||
popped_data = []
|
||||
pushed_data = []
|
||||
popped_data: list[tuple[AnyInstruction, str]] = []
|
||||
pushed_data: list[tuple[AnyInstruction, str]] = []
|
||||
for thing in self.everything:
|
||||
instr, popped, pushed = self.get_stack_effect_info(thing)
|
||||
if instr is not None:
|
||||
popped_data.append( (instr, popped) )
|
||||
pushed_data.append( (instr, pushed) )
|
||||
popped_data.append((instr, popped))
|
||||
pushed_data.append((instr, pushed))
|
||||
|
||||
def write_function(direction: str, data: list[tuple[Instruction, str]]) -> None:
|
||||
self.out.emit("\n#ifndef NDEBUG");
|
||||
self.out.emit("static int");
|
||||
def write_function(direction: str, data: list[tuple[AnyInstruction, str]]) -> None:
|
||||
self.out.emit("\n#ifndef NDEBUG")
|
||||
self.out.emit("static int")
|
||||
self.out.emit(f"_PyOpcode_num_{direction}(int opcode, int oparg) {{")
|
||||
self.out.emit(" switch(opcode) {");
|
||||
self.out.emit(" switch(opcode) {")
|
||||
for instr, effect in data:
|
||||
self.out.emit(f" case {instr.name}:")
|
||||
self.out.emit(f" return {effect};")
|
||||
|
@ -789,10 +832,10 @@ class Analyzer:
|
|||
self.out.emit(" Py_UNREACHABLE();")
|
||||
self.out.emit(" }")
|
||||
self.out.emit("}")
|
||||
self.out.emit("#endif");
|
||||
self.out.emit("#endif")
|
||||
|
||||
write_function('popped', popped_data)
|
||||
write_function('pushed', pushed_data)
|
||||
write_function("popped", popped_data)
|
||||
write_function("pushed", pushed_data)
|
||||
|
||||
def write_metadata(self) -> None:
|
||||
"""Write instruction metadata to output file."""
|
||||
|
@ -865,21 +908,21 @@ class Analyzer:
|
|||
directions.extend("DIR_NONE" for _ in range(3))
|
||||
dir_op1, dir_op2, dir_op3 = directions[:3]
|
||||
self.out.emit(
|
||||
f' [{instr.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{instr.instr_fmt} }},'
|
||||
f" [{instr.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{instr.instr_fmt} }},"
|
||||
)
|
||||
|
||||
def write_metadata_for_super(self, sup: SuperInstruction) -> None:
|
||||
"""Write metadata for a super-instruction."""
|
||||
dir_op1 = dir_op2 = dir_op3 = "DIR_NONE"
|
||||
self.out.emit(
|
||||
f' [{sup.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{sup.instr_fmt} }},'
|
||||
f" [{sup.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{sup.instr_fmt} }},"
|
||||
)
|
||||
|
||||
def write_metadata_for_macro(self, mac: MacroInstruction) -> None:
|
||||
"""Write metadata for a macro-instruction."""
|
||||
dir_op1 = dir_op2 = dir_op3 = "DIR_NONE"
|
||||
self.out.emit(
|
||||
f' [{mac.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{mac.instr_fmt} }},'
|
||||
f" [{mac.name}] = {{ {dir_op1}, {dir_op2}, {dir_op3}, true, {INSTR_FMT_PREFIX}{mac.instr_fmt} }},"
|
||||
)
|
||||
|
||||
def write_instructions(self) -> None:
|
||||
|
@ -1012,7 +1055,9 @@ def extract_block_text(block: parser.Block) -> tuple[list[str], list[str]]:
|
|||
|
||||
# Separate PREDICT(...) macros from end
|
||||
predictions: list[str] = []
|
||||
while blocklines and (m := re.match(r"^\s*PREDICT\((\w+)\);\s*(?://.*)?$", blocklines[-1])):
|
||||
while blocklines and (
|
||||
m := re.match(r"^\s*PREDICT\((\w+)\);\s*(?://.*)?$", blocklines[-1])
|
||||
):
|
||||
predictions.insert(0, m.group(1))
|
||||
blocklines.pop()
|
||||
|
||||
|
@ -1029,13 +1074,22 @@ def always_exits(lines: list[str]) -> bool:
|
|||
return False
|
||||
line = line[12:]
|
||||
return line.startswith(
|
||||
("goto ", "return ", "DISPATCH", "GO_TO_", "Py_UNREACHABLE()", "ERROR_IF(true, ")
|
||||
(
|
||||
"goto ",
|
||||
"return ",
|
||||
"DISPATCH",
|
||||
"GO_TO_",
|
||||
"Py_UNREACHABLE()",
|
||||
"ERROR_IF(true, ",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def variable_used(node: parser.Node, name: str) -> bool:
|
||||
"""Determine whether a variable with a given name is used in a node."""
|
||||
return any(token.kind == "IDENTIFIER" and token.text == name for token in node.tokens)
|
||||
return any(
|
||||
token.kind == "IDENTIFIER" and token.text == name for token in node.tokens
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue