gh-119689: generate stack effect metadata for pseudo instructions (#119691)

This commit is contained in:
Irit Katriel 2024-05-29 10:47:56 +01:00 committed by GitHub
parent 7ca74a760a
commit c1e9647107
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 112 additions and 73 deletions

View File

@ -259,12 +259,16 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 1; return 1;
case IS_OP: case IS_OP:
return 2; return 2;
case JUMP:
return 0;
case JUMP_BACKWARD: case JUMP_BACKWARD:
return 0; return 0;
case JUMP_BACKWARD_NO_INTERRUPT: case JUMP_BACKWARD_NO_INTERRUPT:
return 0; return 0;
case JUMP_FORWARD: case JUMP_FORWARD:
return 0; return 0;
case JUMP_NO_INTERRUPT:
return 0;
case LIST_APPEND: case LIST_APPEND:
return 2 + (oparg-1); return 2 + (oparg-1);
case LIST_EXTEND: case LIST_EXTEND:
@ -297,6 +301,8 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 1; return 1;
case LOAD_BUILD_CLASS: case LOAD_BUILD_CLASS:
return 0; return 0;
case LOAD_CLOSURE:
return 0;
case LOAD_COMMON_CONSTANT: case LOAD_COMMON_CONSTANT:
return 0; return 0;
case LOAD_CONST: case LOAD_CONST:
@ -347,6 +353,8 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 1; return 1;
case NOP: case NOP:
return 0; return 0;
case POP_BLOCK:
return 0;
case POP_EXCEPT: case POP_EXCEPT:
return 1; return 1;
case POP_JUMP_IF_FALSE: case POP_JUMP_IF_FALSE:
@ -385,6 +393,12 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 2; return 2;
case SETUP_ANNOTATIONS: case SETUP_ANNOTATIONS:
return 0; return 0;
case SETUP_CLEANUP:
return 0;
case SETUP_FINALLY:
return 0;
case SETUP_WITH:
return 0;
case SET_ADD: case SET_ADD:
return 2 + (oparg-1); return 2 + (oparg-1);
case SET_FUNCTION_ATTRIBUTE: case SET_FUNCTION_ATTRIBUTE:
@ -405,6 +419,8 @@ int _PyOpcode_num_popped(int opcode, int oparg) {
return 1; return 1;
case STORE_FAST_LOAD_FAST: case STORE_FAST_LOAD_FAST:
return 1; return 1;
case STORE_FAST_MAYBE_NULL:
return 1;
case STORE_FAST_STORE_FAST: case STORE_FAST_STORE_FAST:
return 2; return 2;
case STORE_GLOBAL: case STORE_GLOBAL:
@ -692,12 +708,16 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 0; return 0;
case IS_OP: case IS_OP:
return 1; return 1;
case JUMP:
return 0;
case JUMP_BACKWARD: case JUMP_BACKWARD:
return 0; return 0;
case JUMP_BACKWARD_NO_INTERRUPT: case JUMP_BACKWARD_NO_INTERRUPT:
return 0; return 0;
case JUMP_FORWARD: case JUMP_FORWARD:
return 0; return 0;
case JUMP_NO_INTERRUPT:
return 0;
case LIST_APPEND: case LIST_APPEND:
return 1 + (oparg-1); return 1 + (oparg-1);
case LIST_EXTEND: case LIST_EXTEND:
@ -730,6 +750,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 1 + (oparg & 1); return 1 + (oparg & 1);
case LOAD_BUILD_CLASS: case LOAD_BUILD_CLASS:
return 1; return 1;
case LOAD_CLOSURE:
return 1;
case LOAD_COMMON_CONSTANT: case LOAD_COMMON_CONSTANT:
return 1; return 1;
case LOAD_CONST: case LOAD_CONST:
@ -780,6 +802,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 2; return 2;
case NOP: case NOP:
return 0; return 0;
case POP_BLOCK:
return 0;
case POP_EXCEPT: case POP_EXCEPT:
return 0; return 0;
case POP_JUMP_IF_FALSE: case POP_JUMP_IF_FALSE:
@ -818,6 +842,12 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 2; return 2;
case SETUP_ANNOTATIONS: case SETUP_ANNOTATIONS:
return 0; return 0;
case SETUP_CLEANUP:
return 2;
case SETUP_FINALLY:
return 1;
case SETUP_WITH:
return 1;
case SET_ADD: case SET_ADD:
return 1 + (oparg-1); return 1 + (oparg-1);
case SET_FUNCTION_ATTRIBUTE: case SET_FUNCTION_ATTRIBUTE:
@ -838,6 +868,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg) {
return 0; return 0;
case STORE_FAST_LOAD_FAST: case STORE_FAST_LOAD_FAST:
return 1; return 1;
case STORE_FAST_MAYBE_NULL:
return 0;
case STORE_FAST_STORE_FAST: case STORE_FAST_STORE_FAST:
return 0; return 0;
case STORE_GLOBAL: case STORE_GLOBAL:

View File

@ -485,7 +485,7 @@ class TestGeneratedCases(unittest.TestCase):
def test_pseudo_instruction_no_flags(self): def test_pseudo_instruction_no_flags(self):
input = """ input = """
pseudo(OP) = { pseudo(OP, (in -- out1, out2)) = {
OP1, OP1,
}; };
@ -504,7 +504,7 @@ class TestGeneratedCases(unittest.TestCase):
def test_pseudo_instruction_with_flags(self): def test_pseudo_instruction_with_flags(self):
input = """ input = """
pseudo(OP, (HAS_ARG, HAS_JUMP)) = { pseudo(OP, (in1, in2 --), (HAS_ARG, HAS_JUMP)) = {
OP1, OP1,
}; };

View File

@ -0,0 +1 @@
Generate stack effect metadata for pseudo instructions from bytecodes.c.

View File

@ -213,7 +213,7 @@ dummy_func(
} }
} }
pseudo(LOAD_CLOSURE) = { pseudo(LOAD_CLOSURE, (-- unused)) = {
LOAD_FAST, LOAD_FAST,
}; };
@ -259,7 +259,7 @@ dummy_func(
SETLOCAL(oparg, value); SETLOCAL(oparg, value);
} }
pseudo(STORE_FAST_MAYBE_NULL) = { pseudo(STORE_FAST_MAYBE_NULL, (unused --)) = {
STORE_FAST, STORE_FAST,
}; };
@ -2393,12 +2393,12 @@ dummy_func(
#endif /* _Py_TIER2 */ #endif /* _Py_TIER2 */
} }
pseudo(JUMP) = { pseudo(JUMP, (--)) = {
JUMP_FORWARD, JUMP_FORWARD,
JUMP_BACKWARD, JUMP_BACKWARD,
}; };
pseudo(JUMP_NO_INTERRUPT) = { pseudo(JUMP_NO_INTERRUPT, (--)) = {
JUMP_FORWARD, JUMP_FORWARD,
JUMP_BACKWARD_NO_INTERRUPT, JUMP_BACKWARD_NO_INTERRUPT,
}; };
@ -2895,19 +2895,27 @@ dummy_func(
ERROR_IF(res == NULL, error); 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, NOP,
}; };
pseudo(SETUP_CLEANUP, (HAS_ARG)) = { pseudo(SETUP_CLEANUP, (-- unused, unused), (HAS_ARG)) = {
/* As SETUP_FINALLY, but push lasti as well */
NOP, 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, NOP,
}; };
pseudo(POP_BLOCK) = { pseudo(POP_BLOCK, (--)) = {
NOP, NOP,
}; };

View File

@ -703,51 +703,22 @@ compiler_set_qualname(struct compiler *c)
static int static int
stack_effect(int opcode, int oparg, int jump) stack_effect(int opcode, int oparg, int jump)
{ {
if (0 <= opcode && opcode <= MAX_REAL_OPCODE) { if (opcode < 0) {
if (_PyOpcode_Deopt[opcode] != opcode) { return PY_INVALID_STACK_EFFECT;
// 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 <= MAX_REAL_OPCODE) && (_PyOpcode_Deopt[opcode] != opcode)) {
// Pseudo ops // Specialized instructions are not supported.
switch (opcode) { return PY_INVALID_STACK_EFFECT;
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;
} }
int popped = _PyOpcode_num_popped(opcode, oparg);
return PY_INVALID_STACK_EFFECT; /* not reachable */ 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 int

View File

@ -235,6 +235,7 @@ class Instruction:
@dataclass @dataclass
class PseudoInstruction: class PseudoInstruction:
name: str name: str
stack: StackEffect
targets: list[Instruction] targets: list[Instruction]
flags: list[str] flags: list[str]
opcode: int = -1 opcode: int = -1
@ -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") 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] = [ inputs: list[StackItem] = [
convert_stack_item(i, replace_op_arg_1) for i in op.inputs if isinstance(i, parser.StackEffect) convert_stack_item(i, replace_op_arg_1) for i in op.inputs if isinstance(i, parser.StackEffect)
] ]
@ -706,6 +707,7 @@ def add_pseudo(
) -> None: ) -> None:
pseudos[pseudo.name] = PseudoInstruction( pseudos[pseudo.name] = PseudoInstruction(
pseudo.name, pseudo.name,
analyze_stack(pseudo),
[instructions[target] for target in pseudo.targets], [instructions[target] for target in pseudo.targets],
pseudo.flags, pseudo.flags,
) )

View File

@ -124,7 +124,13 @@ and a piece of C code describing its semantics::
"family" "(" NAME ")" = "{" NAME ("," NAME)+ [","] "}" ";" "family" "(" NAME ")" = "{" NAME ("," NAME)+ [","] "}" ";"
pseudo: pseudo:
"pseudo" "(" NAME ")" = "{" NAME ("," NAME)+ [","] "}" ";" "pseudo" "(" NAME "," stack_effect ["," "(" flags ")"]")" = "{" NAME ("," NAME)+ [","] "}" ";"
flags:
flag ("|" flag)*
flag:
HAS_ARG | HAS_DEOPT | etc..
``` ```
The following definitions may occur: The following definitions may occur:

View File

@ -10,6 +10,7 @@ import sys
from analyzer import ( from analyzer import (
Analysis, Analysis,
Instruction, Instruction,
PseudoInstruction,
analyze_files, analyze_files,
Skip, Skip,
Uop, Uop,
@ -94,12 +95,18 @@ def emit_stack_effect_function(
def generate_stack_effect_functions(analysis: Analysis, out: CWriter) -> None: def generate_stack_effect_functions(analysis: Analysis, out: CWriter) -> None:
popped_data: list[tuple[str, str]] = [] popped_data: list[tuple[str, str]] = []
pushed_data: list[tuple[str, str]] = [] pushed_data: list[tuple[str, str]] = []
for inst in analysis.instructions.values(): def add(inst: Instruction | PseudoInstruction) -> None:
stack = get_stack_effect(inst) stack = get_stack_effect(inst)
popped = (-stack.base_offset).to_c() popped = (-stack.base_offset).to_c()
pushed = (stack.top_offset - stack.base_offset).to_c() pushed = (stack.top_offset - stack.base_offset).to_c()
popped_data.append((inst.name, popped)) popped_data.append((inst.name, popped))
pushed_data.append((inst.name, pushed)) pushed_data.append((inst.name, pushed))
for inst in analysis.instructions.values():
add(inst)
for pseudo in analysis.pseudos.values():
add(pseudo)
emit_stack_effect_function(out, "popped", sorted(popped_data)) emit_stack_effect_function(out, "popped", sorted(popped_data))
emit_stack_effect_function(out, "pushed", sorted(pushed_data)) emit_stack_effect_function(out, "pushed", sorted(pushed_data))

View File

@ -138,6 +138,8 @@ class Family(Node):
@dataclass @dataclass
class Pseudo(Node): class Pseudo(Node):
name: str name: str
inputs: list[InputEffect]
outputs: list[OutputEffect]
flags: list[str] # instr flags to set on the pseudo instruction flags: list[str] # instr flags to set on the pseudo instruction
targets: list[str] # opcodes this can be replaced by targets: list[str] # opcodes this can be replaced by
@ -409,16 +411,18 @@ class Parser(PLexer):
if self.expect(lx.LPAREN): if self.expect(lx.LPAREN):
if tkn := self.expect(lx.IDENTIFIER): if tkn := self.expect(lx.IDENTIFIER):
if self.expect(lx.COMMA): if self.expect(lx.COMMA):
flags = self.flags() inp, outp = self.io_effect()
else: if self.expect(lx.COMMA):
flags = [] flags = self.flags()
if self.expect(lx.RPAREN): else:
if self.expect(lx.EQUALS): flags = []
if not self.expect(lx.LBRACE): if self.expect(lx.RPAREN):
raise self.make_syntax_error("Expected {") if self.expect(lx.EQUALS):
if members := self.members(): if not self.expect(lx.LBRACE):
if self.expect(lx.RBRACE) and self.expect(lx.SEMI): raise self.make_syntax_error("Expected {")
return Pseudo(tkn.text, flags, members) if members := self.members():
if self.expect(lx.RBRACE) and self.expect(lx.SEMI):
return Pseudo(tkn.text, inp, outp, flags, members)
return None return None
def members(self) -> list[str] | None: def members(self) -> list[str] | None:

View File

@ -1,7 +1,8 @@
import re import re
from analyzer import StackItem, Instruction, Uop from analyzer import StackItem, StackEffect, Instruction, Uop, PseudoInstruction
from dataclasses import dataclass from dataclasses import dataclass
from cwriter import CWriter from cwriter import CWriter
from typing import Iterator
UNUSED = {"unused"} UNUSED = {"unused"}
@ -208,13 +209,20 @@ class Stack:
return f"/* Variables: {[v.name for v in self.variables]}. Base offset: {self.base_offset.to_c()}. Top offset: {self.top_offset.to_c()} */" 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() stack = Stack()
for uop in inst.parts: def stacks(inst : Instruction | PseudoInstruction) -> Iterator[StackEffect]:
if not isinstance(uop, Uop): if isinstance(inst, Instruction):
continue for uop in inst.parts:
for var in reversed(uop.stack.inputs): 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) stack.pop(var)
for i, var in enumerate(uop.stack.outputs): for var in s.outputs:
stack.push(var) stack.push(var)
return stack return stack