]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-119689: generate stack effect metadata for pseudo instructions (#119691)
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>
Wed, 29 May 2024 09:47:56 +0000 (10:47 +0100)
committerGitHub <noreply@github.com>
Wed, 29 May 2024 09:47:56 +0000 (09:47 +0000)
Include/internal/pycore_opcode_metadata.h
Lib/test/test_generated_cases.py
Misc/NEWS.d/next/Core and Builtins/2024-05-28-22-49-56.gh-issue-119689.VwLFD5.rst [new file with mode: 0644]
Python/bytecodes.c
Python/compile.c
Tools/cases_generator/analyzer.py
Tools/cases_generator/interpreter_definition.md
Tools/cases_generator/opcode_metadata_generator.py
Tools/cases_generator/parsing.py
Tools/cases_generator/stack.py

index f805be04985ef29089f88004009c28534c2aa929..d3535800139a66a13a2947c198555eb5458edfab 100644 (file)
@@ -259,12 +259,16 @@ int _PyOpcode_num_popped(int opcode, int oparg)  {
             return 1;
         case IS_OP:
             return 2;
+        case JUMP:
+            return 0;
         case JUMP_BACKWARD:
             return 0;
         case JUMP_BACKWARD_NO_INTERRUPT:
             return 0;
         case JUMP_FORWARD:
             return 0;
+        case JUMP_NO_INTERRUPT:
+            return 0;
         case LIST_APPEND:
             return 2 + (oparg-1);
         case LIST_EXTEND:
@@ -297,6 +301,8 @@ int _PyOpcode_num_popped(int opcode, int oparg)  {
             return 1;
         case LOAD_BUILD_CLASS:
             return 0;
+        case LOAD_CLOSURE:
+            return 0;
         case LOAD_COMMON_CONSTANT:
             return 0;
         case LOAD_CONST:
@@ -347,6 +353,8 @@ int _PyOpcode_num_popped(int opcode, int oparg)  {
             return 1;
         case NOP:
             return 0;
+        case POP_BLOCK:
+            return 0;
         case POP_EXCEPT:
             return 1;
         case POP_JUMP_IF_FALSE:
@@ -385,6 +393,12 @@ int _PyOpcode_num_popped(int opcode, int oparg)  {
             return 2;
         case SETUP_ANNOTATIONS:
             return 0;
+        case SETUP_CLEANUP:
+            return 0;
+        case SETUP_FINALLY:
+            return 0;
+        case SETUP_WITH:
+            return 0;
         case SET_ADD:
             return 2 + (oparg-1);
         case SET_FUNCTION_ATTRIBUTE:
@@ -405,6 +419,8 @@ int _PyOpcode_num_popped(int opcode, int oparg)  {
             return 1;
         case STORE_FAST_LOAD_FAST:
             return 1;
+        case STORE_FAST_MAYBE_NULL:
+            return 1;
         case STORE_FAST_STORE_FAST:
             return 2;
         case STORE_GLOBAL:
@@ -692,12 +708,16 @@ int _PyOpcode_num_pushed(int opcode, int oparg)  {
             return 0;
         case IS_OP:
             return 1;
+        case JUMP:
+            return 0;
         case JUMP_BACKWARD:
             return 0;
         case JUMP_BACKWARD_NO_INTERRUPT:
             return 0;
         case JUMP_FORWARD:
             return 0;
+        case JUMP_NO_INTERRUPT:
+            return 0;
         case LIST_APPEND:
             return 1 + (oparg-1);
         case LIST_EXTEND:
@@ -730,6 +750,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg)  {
             return 1 + (oparg & 1);
         case LOAD_BUILD_CLASS:
             return 1;
+        case LOAD_CLOSURE:
+            return 1;
         case LOAD_COMMON_CONSTANT:
             return 1;
         case LOAD_CONST:
@@ -780,6 +802,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg)  {
             return 2;
         case NOP:
             return 0;
+        case POP_BLOCK:
+            return 0;
         case POP_EXCEPT:
             return 0;
         case POP_JUMP_IF_FALSE:
@@ -818,6 +842,12 @@ int _PyOpcode_num_pushed(int opcode, int oparg)  {
             return 2;
         case SETUP_ANNOTATIONS:
             return 0;
+        case SETUP_CLEANUP:
+            return 2;
+        case SETUP_FINALLY:
+            return 1;
+        case SETUP_WITH:
+            return 1;
         case SET_ADD:
             return 1 + (oparg-1);
         case SET_FUNCTION_ATTRIBUTE:
@@ -838,6 +868,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg)  {
             return 0;
         case STORE_FAST_LOAD_FAST:
             return 1;
+        case STORE_FAST_MAYBE_NULL:
+            return 0;
         case STORE_FAST_STORE_FAST:
             return 0;
         case STORE_GLOBAL:
index fb85222fdcce74368f3987c965f399ac68e26a92..41eeb9c0705741105f899ce475116da3d48901d3 100644 (file)
@@ -485,7 +485,7 @@ class TestGeneratedCases(unittest.TestCase):
 
     def test_pseudo_instruction_no_flags(self):
         input = """
-        pseudo(OP) = {
+        pseudo(OP, (in -- out1, out2)) = {
             OP1,
         };
 
@@ -504,7 +504,7 @@ class TestGeneratedCases(unittest.TestCase):
 
     def test_pseudo_instruction_with_flags(self):
         input = """
-        pseudo(OP, (HAS_ARG, HAS_JUMP)) = {
+        pseudo(OP, (in1, in2 --), (HAS_ARG, HAS_JUMP)) = {
             OP1,
         };
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-28-22-49-56.gh-issue-119689.VwLFD5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-28-22-49-56.gh-issue-119689.VwLFD5.rst
new file mode 100644 (file)
index 0000000..56be313
--- /dev/null
@@ -0,0 +1 @@
+Generate stack effect metadata for pseudo instructions from bytecodes.c.
index 274c5c22447e4c6f905f200a3856be8a897352f5..9a8198515dea5eaecf2d463933cbbf8f97b8146d 100644 (file)
@@ -213,7 +213,7 @@ dummy_func(
             }
         }
 
-        pseudo(LOAD_CLOSURE) = {
+        pseudo(LOAD_CLOSURE, (-- unused)) = {
             LOAD_FAST,
         };
 
@@ -259,7 +259,7 @@ dummy_func(
             SETLOCAL(oparg, value);
         }
 
-        pseudo(STORE_FAST_MAYBE_NULL) = {
+        pseudo(STORE_FAST_MAYBE_NULL, (unused --)) = {
             STORE_FAST,
         };
 
@@ -2393,12 +2393,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,
         };
@@ -2895,19 +2895,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,
         };
 
index e6efae33eb45e47dcc959d9f2bb5a3580f2adbf7..3a80577e0f2b2daaa7c2a41c8952919b7ff439aa 100644 (file)
@@ -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
index fdb635486b9531715a67696da6688ec81535925e..e44bebd8f3c4a423f41bfb761f7b69198a3edf4f 100644 (file)
@@ -235,6 +235,7 @@ class Instruction:
 @dataclass
 class PseudoInstruction:
     name: str
+    stack: StackEffect
     targets: list[Instruction]
     flags: list[str]
     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")
     )
 
-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)
     ]
@@ -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,
     )
index 889f58fc3e1a75d5894c0895c347d60437cf999f..ba09931c54164606fc13984351d19c4c3b5cf823 100644 (file)
@@ -124,7 +124,13 @@ and a piece of C code describing its semantics::
     "family" "(" NAME ")" = "{" NAME ("," NAME)+ [","] "}" ";"
 
   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:
index 04fecb235f18cde5593273ae794cdd52cde6aba0..2632eb89ce80cd653c70d8cd95be252d6f11f622 100644 (file)
@@ -10,6 +10,7 @@ import sys
 from analyzer import (
     Analysis,
     Instruction,
+    PseudoInstruction,
     analyze_files,
     Skip,
     Uop,
@@ -94,12 +95,18 @@ 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():
+    def add(inst: Instruction | PseudoInstruction) -> None:
         stack = get_stack_effect(inst)
         popped = (-stack.base_offset).to_c()
         pushed = (stack.top_offset - stack.base_offset).to_c()
         popped_data.append((inst.name, popped))
         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, "pushed", sorted(pushed_data))
 
index 0d54820e4e71fbe5f4ef03480c4db3e783cdff94..cc897ff2cbe9aab63509110af00bae407bf93e00 100644 (file)
@@ -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
 
@@ -409,16 +411,18 @@ class Parser(PLexer):
             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:
index 5aecac39aef5e2dc787ecacbca961431c15b0ecb..7f07a6805b1cb6556d46ab567ef8f895c4687bd6 100644 (file)
@@ -1,7 +1,8 @@
 import re
-from analyzer import StackItem, Instruction, Uop
+from analyzer import StackItem, StackEffect, Instruction, Uop, PseudoInstruction
 from dataclasses import dataclass
 from cwriter import CWriter
+from typing import Iterator
 
 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()} */"
 
 
-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 : Instruction | PseudoInstruction) -> Iterator[StackEffect]:
+        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