Skip to content

Commit

Permalink
pythongh-119689: generate stack effect metadata for pseudo instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
iritkatriel committed May 28, 2024
1 parent ae9140f commit b64fa5c
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 72 deletions.
32 changes: 32 additions & 0 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Lib/test/test_generated_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ def test_unused_caches(self):

def test_pseudo_instruction_no_flags(self):
input = """
pseudo(OP) = {
pseudo(OP, (in -- out1, out2)) = {
OP1,
};
Expand All @@ -504,7 +504,7 @@ def test_pseudo_instruction_no_flags(self):

def test_pseudo_instruction_with_flags(self):
input = """
pseudo(OP, (HAS_ARG, HAS_JUMP)) = {
pseudo(OP, (in1, in2 --), (HAS_ARG, HAS_JUMP)) = {
OP1,
};
Expand Down
24 changes: 16 additions & 8 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ dummy_func(
}
}

pseudo(LOAD_CLOSURE) = {
pseudo(LOAD_CLOSURE, (-- unused)) = {
LOAD_FAST,
};

Expand Down Expand Up @@ -259,7 +259,7 @@ dummy_func(
SETLOCAL(oparg, value);
}

pseudo(STORE_FAST_MAYBE_NULL) = {
pseudo(STORE_FAST_MAYBE_NULL, (unused --)) = {
STORE_FAST,
};

Expand Down Expand Up @@ -2391,12 +2391,12 @@ dummy_func(
#endif /* _Py_TIER2 */
}

pseudo(JUMP) = {
pseudo(JUMP, (--)) = {
JUMP_FORWARD,
JUMP_BACKWARD,
};

pseudo(JUMP_NO_INTERRUPT) = {
pseudo(JUMP_NO_INTERRUPT, (--)) = {
JUMP_FORWARD,
JUMP_BACKWARD_NO_INTERRUPT,
};
Expand Down Expand Up @@ -2893,19 +2893,27 @@ dummy_func(
ERROR_IF(res == NULL, error);
}

pseudo(SETUP_FINALLY, (HAS_ARG)) = {
pseudo(SETUP_FINALLY, (-- unused), (HAS_ARG)) = {
/* If an exception is raised, restore the stack position
* and push one value before jumping to the handler.
*/
NOP,
};

pseudo(SETUP_CLEANUP, (HAS_ARG)) = {
pseudo(SETUP_CLEANUP, (-- unused, unused), (HAS_ARG)) = {
/* As SETUP_FINALLY, but push lasti as well */
NOP,
};

pseudo(SETUP_WITH, (HAS_ARG)) = {
pseudo(SETUP_WITH, (-- unused), (HAS_ARG)) = {
/* If an exception is raised, restore the stack position to the
* position before the result of __(a)enter__ and push 2 values
* before jumping to the handler.
*/
NOP,
};

pseudo(POP_BLOCK) = {
pseudo(POP_BLOCK, (--)) = {
NOP,
};

Expand Down
57 changes: 14 additions & 43 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -703,51 +703,22 @@ compiler_set_qualname(struct compiler *c)
static int
stack_effect(int opcode, int oparg, int jump)
{
if (0 <= opcode && opcode <= MAX_REAL_OPCODE) {
if (_PyOpcode_Deopt[opcode] != opcode) {
// Specialized instructions are not supported.
return PY_INVALID_STACK_EFFECT;
}
int popped = _PyOpcode_num_popped(opcode, oparg);
int pushed = _PyOpcode_num_pushed(opcode, oparg);
if (popped < 0 || pushed < 0) {
return PY_INVALID_STACK_EFFECT;
}
return pushed - popped;
if (opcode < 0) {
return PY_INVALID_STACK_EFFECT;
}

// Pseudo ops
switch (opcode) {
case POP_BLOCK:
case JUMP:
case JUMP_NO_INTERRUPT:
return 0;

/* Exception handling pseudo-instructions */
case SETUP_FINALLY:
/* 0 in the normal flow.
* Restore the stack position and push 1 value before jumping to
* the handler if an exception be raised. */
return jump ? 1 : 0;
case SETUP_CLEANUP:
/* As SETUP_FINALLY, but pushes lasti as well */
return jump ? 2 : 0;
case SETUP_WITH:
/* 0 in the normal flow.
* Restore the stack position to the position before the result
* of __(a)enter__ and push 2 values before jumping to the handler
* if an exception be raised. */
return jump ? 1 : 0;

case STORE_FAST_MAYBE_NULL:
return -1;
case LOAD_CLOSURE:
return 1;
default:
return PY_INVALID_STACK_EFFECT;
if ((opcode <= MAX_REAL_OPCODE) && (_PyOpcode_Deopt[opcode] != opcode)) {
// Specialized instructions are not supported.
return PY_INVALID_STACK_EFFECT;
}

return PY_INVALID_STACK_EFFECT; /* not reachable */
int popped = _PyOpcode_num_popped(opcode, oparg);
int pushed = _PyOpcode_num_pushed(opcode, oparg);
if (popped < 0 || pushed < 0) {
return PY_INVALID_STACK_EFFECT;
}
if (IS_BLOCK_PUSH_OPCODE(opcode) && !jump) {
return 0;
}
return pushed - popped;
}

int
Expand Down
4 changes: 3 additions & 1 deletion Tools/cases_generator/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ def is_super(self) -> bool:
@dataclass
class PseudoInstruction:
name: str
stack: StackEffect
targets: list[Instruction]
flags: list[str]
opcode: int = -1
Expand Down Expand Up @@ -295,7 +296,7 @@ def convert_stack_item(item: parser.StackEffect, replace_op_arg_1: str | None) -
item.name, item.type, cond, (item.size or "1")
)

def analyze_stack(op: parser.InstDef, replace_op_arg_1: str | None = None) -> StackEffect:
def analyze_stack(op: parser.InstDef | parser.Pseudo, replace_op_arg_1: str | None = None) -> StackEffect:
inputs: list[StackItem] = [
convert_stack_item(i, replace_op_arg_1) for i in op.inputs if isinstance(i, parser.StackEffect)
]
Expand Down Expand Up @@ -706,6 +707,7 @@ def add_pseudo(
) -> None:
pseudos[pseudo.name] = PseudoInstruction(
pseudo.name,
analyze_stack(pseudo),
[instructions[target] for target in pseudo.targets],
pseudo.flags,
)
Expand Down
4 changes: 3 additions & 1 deletion Tools/cases_generator/opcode_metadata_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import argparse
import itertools
import os.path
import sys

Expand Down Expand Up @@ -94,7 +95,8 @@ def emit_stack_effect_function(
def generate_stack_effect_functions(analysis: Analysis, out: CWriter) -> None:
popped_data: list[tuple[str, str]] = []
pushed_data: list[tuple[str, str]] = []
for inst in analysis.instructions.values():
for inst in itertools.chain(analysis.instructions.values(),
analysis.pseudos.values()):
stack = get_stack_effect(inst)
popped = (-stack.base_offset).to_c()
pushed = (stack.top_offset - stack.base_offset).to_c()
Expand Down
24 changes: 14 additions & 10 deletions Tools/cases_generator/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ class Family(Node):
@dataclass
class Pseudo(Node):
name: str
inputs: list[InputEffect]
outputs: list[OutputEffect]
flags: list[str] # instr flags to set on the pseudo instruction
targets: list[str] # opcodes this can be replaced by

Expand Down Expand Up @@ -409,16 +411,18 @@ def pseudo_def(self) -> Pseudo | None:
if self.expect(lx.LPAREN):
if tkn := self.expect(lx.IDENTIFIER):
if self.expect(lx.COMMA):
flags = self.flags()
else:
flags = []
if self.expect(lx.RPAREN):
if self.expect(lx.EQUALS):
if not self.expect(lx.LBRACE):
raise self.make_syntax_error("Expected {")
if members := self.members():
if self.expect(lx.RBRACE) and self.expect(lx.SEMI):
return Pseudo(tkn.text, flags, members)
inp, outp = self.io_effect()
if self.expect(lx.COMMA):
flags = self.flags()
else:
flags = []
if self.expect(lx.RPAREN):
if self.expect(lx.EQUALS):
if not self.expect(lx.LBRACE):
raise self.make_syntax_error("Expected {")
if members := self.members():
if self.expect(lx.RBRACE) and self.expect(lx.SEMI):
return Pseudo(tkn.text, inp, outp, flags, members)
return None

def members(self) -> list[str] | None:
Expand Down
21 changes: 14 additions & 7 deletions Tools/cases_generator/stack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from analyzer import StackItem, Instruction, Uop
from analyzer import StackItem, Instruction, Uop, PseudoInstruction
from dataclasses import dataclass
from cwriter import CWriter

Expand Down Expand Up @@ -208,13 +208,20 @@ def as_comment(self) -> str:
return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */"


def get_stack_effect(inst: Instruction) -> Stack:
def get_stack_effect(inst: Instruction | PseudoInstruction) -> Stack:
stack = Stack()
for uop in inst.parts:
if not isinstance(uop, Uop):
continue
for var in reversed(uop.stack.inputs):
def stacks(inst):
if isinstance(inst, Instruction):
for uop in inst.parts:
if isinstance(uop, Uop):
yield uop.stack
else:
assert isinstance(inst, PseudoInstruction)
yield inst.stack

for s in stacks(inst):
for var in reversed(s.inputs):
stack.pop(var)
for i, var in enumerate(uop.stack.outputs):
for var in s.outputs:
stack.push(var)
return stack

0 comments on commit b64fa5c

Please sign in to comment.