Skip to content

Commit adcd220

Browse files
authored
bpo-40222: "Zero cost" exception handling (pythonGH-25729)
"Zero cost" exception handling. * Uses a lookup table to determine how to handle exceptions. * Removes SETUP_FINALLY and POP_TOP block instructions, eliminating (most of) the runtime overhead of try statements. * Reduces the size of the frame object by about 60%.
1 parent b32c8e9 commit adcd220

30 files changed

+6615
-5688
lines changed

Diff for: Doc/library/dis.rst

+27-31
Original file line numberDiff line numberDiff line change
@@ -616,13 +616,6 @@ the original TOS1.
616616
.. versionadded:: 3.5
617617

618618

619-
.. opcode:: SETUP_ASYNC_WITH
620-
621-
Creates a new frame object.
622-
623-
.. versionadded:: 3.5
624-
625-
626619

627620
**Miscellaneous opcodes**
628621

@@ -692,28 +685,29 @@ iterations of the loop.
692685
opcode implements ``from module import *``.
693686

694687

695-
.. opcode:: POP_BLOCK
696-
697-
Removes one block from the block stack. Per frame, there is a stack of
698-
blocks, denoting :keyword:`try` statements, and such.
699-
700-
701688
.. opcode:: POP_EXCEPT
702689

703-
Removes one block from the block stack. The popped block must be an exception
704-
handler block, as implicitly created when entering an except handler. In
705-
addition to popping extraneous values from the frame stack, the last three
706-
popped values are used to restore the exception state.
690+
Pops three values from the stack, which are used to restore the exception state.
707691

708692

709693
.. opcode:: RERAISE
710694

711695
Re-raises the exception currently on top of the stack. If oparg is non-zero,
712-
restores ``f_lasti`` of the current frame to its value when the exception was raised.
696+
pops an additional value from the stack which is used to set ``f_lasti``
697+
of the current frame.
713698

714699
.. versionadded:: 3.9
715700

716701

702+
.. opcode:: PUSH_EXC_INFO
703+
704+
Pops the three values from the stack. Pushes the current exception to the top of the stack.
705+
Pushes the three values originally popped back to the stack.
706+
Used in exception handlers.
707+
708+
.. versionadded:: 3.11
709+
710+
717711
.. opcode:: WITH_EXCEPT_START
718712

719713
Calls the function in position 7 on the stack with the top three
@@ -724,6 +718,17 @@ iterations of the loop.
724718
.. versionadded:: 3.9
725719

726720

721+
.. opcode:: POP_EXCEPT_AND_RERAISE
722+
723+
Pops the exception currently on top of the stack. Pops the integer value on top
724+
of the stack and sets the ``f_lasti`` attribute of the frame with that value.
725+
Then pops the next exception from the stack uses it to restore the current exception.
726+
Finally it re-raises the originally popped exception.
727+
Used in excpetion handler cleanup.
728+
729+
.. versionadded:: 3.11
730+
731+
727732
.. opcode:: LOAD_ASSERTION_ERROR
728733

729734
Pushes :exc:`AssertionError` onto the stack. Used by the :keyword:`assert`
@@ -738,18 +743,15 @@ iterations of the loop.
738743
by :opcode:`CALL_FUNCTION` to construct a class.
739744

740745

741-
.. opcode:: SETUP_WITH (delta)
746+
.. opcode:: BEFORE_WITH (delta)
742747

743748
This opcode performs several operations before a with block starts. First,
744749
it loads :meth:`~object.__exit__` from the context manager and pushes it onto
745750
the stack for later use by :opcode:`WITH_EXCEPT_START`. Then,
746-
:meth:`~object.__enter__` is called, and a finally block pointing to *delta*
747-
is pushed. Finally, the result of calling the ``__enter__()`` method is pushed onto
748-
the stack. The next opcode will either ignore it (:opcode:`POP_TOP`), or
749-
store it in (a) variable(s) (:opcode:`STORE_FAST`, :opcode:`STORE_NAME`, or
750-
:opcode:`UNPACK_SEQUENCE`).
751+
:meth:`~object.__enter__` is called. Finally, the result of calling the
752+
``__enter__()`` method is pushed onto the stack.
751753

752-
.. versionadded:: 3.2
754+
.. versionadded:: 3.11
753755

754756

755757
.. opcode:: COPY_DICT_WITHOUT_KEYS
@@ -1039,12 +1041,6 @@ All of the following opcodes use their arguments.
10391041
Loads the global named ``co_names[namei]`` onto the stack.
10401042

10411043

1042-
.. opcode:: SETUP_FINALLY (delta)
1043-
1044-
Pushes a try block from a try-finally or try-except clause onto the block
1045-
stack. *delta* points to the finally block or the first except block.
1046-
1047-
10481044
.. opcode:: LOAD_FAST (var_num)
10491045

10501046
Pushes a reference to the local ``co_varnames[var_num]`` onto the stack.

Diff for: Doc/whatsnew/3.11.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ Optimizations
9191
=============
9292

9393

94+
95+
9496
Build and C API Changes
9597
=======================
9698

97-
99+
* :c:func:`PyFrame_BlockSetup` and :c:func:`PyFrame_BlockPop` have been removed.
98100

99101
Deprecated
100102
==========

Diff for: Include/cpython/code.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct PyCodeObject {
4040
PyObject *co_name; /* unicode (name, for reference) */
4141
PyObject *co_linetable; /* string (encoding addr<->lineno mapping) See
4242
Objects/lnotab_notes.txt for details. */
43+
PyObject *co_exceptiontable; /* Byte string encoding exception handling table */
4344
void *co_zombieframe; /* for optimization only (see frameobject.c) */
4445
PyObject *co_weakreflist; /* to support weakrefs to code objects */
4546
/* Scratch space for extra data relating to the code object.
@@ -117,12 +118,12 @@ PyAPI_DATA(PyTypeObject) PyCode_Type;
117118
PyAPI_FUNC(PyCodeObject *) PyCode_New(
118119
int, int, int, int, int, PyObject *, PyObject *,
119120
PyObject *, PyObject *, PyObject *, PyObject *,
120-
PyObject *, PyObject *, int, PyObject *);
121+
PyObject *, PyObject *, int, PyObject *, PyObject *);
121122

122123
PyAPI_FUNC(PyCodeObject *) PyCode_NewWithPosOnlyArgs(
123124
int, int, int, int, int, int, PyObject *, PyObject *,
124125
PyObject *, PyObject *, PyObject *, PyObject *,
125-
PyObject *, PyObject *, int, PyObject *);
126+
PyObject *, PyObject *, int, PyObject *, PyObject *);
126127
/* same as struct above */
127128

128129
/* Creates a new empty code object with the specified source location. */

Diff for: Include/cpython/frameobject.h

+3-12
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,14 @@ struct _frame {
3434
PyObject *f_locals; /* local symbol table (any mapping) */
3535
PyObject **f_valuestack; /* points after the last local */
3636
PyObject *f_trace; /* Trace function */
37-
int f_stackdepth; /* Depth of value stack */
38-
char f_trace_lines; /* Emit per-line trace events? */
39-
char f_trace_opcodes; /* Emit per-opcode trace events? */
40-
4137
/* Borrowed reference to a generator, or NULL */
4238
PyObject *f_gen;
43-
39+
int f_stackdepth; /* Depth of value stack */
4440
int f_lasti; /* Last instruction if called */
4541
int f_lineno; /* Current line number. Only valid if non-zero */
46-
int f_iblock; /* index in f_blockstack */
4742
PyFrameState f_state; /* What state the frame is in */
48-
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
43+
char f_trace_lines; /* Emit per-line trace events? */
44+
char f_trace_opcodes; /* Emit per-opcode trace events? */
4945
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
5046
};
5147

@@ -77,11 +73,6 @@ _PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *);
7773

7874
/* The rest of the interface is specific for frame objects */
7975

80-
/* Block management functions */
81-
82-
PyAPI_FUNC(void) PyFrame_BlockSetup(PyFrameObject *, int, int, int);
83-
PyAPI_FUNC(PyTryBlock *) PyFrame_BlockPop(PyFrameObject *);
84-
8576
/* Conversions between "fast locals" and locals in dictionary */
8677

8778
PyAPI_FUNC(void) PyFrame_LocalsToFast(PyFrameObject *, int);

Diff for: Include/opcode.h

+11-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Lib/ctypes/test/test_values.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ class struct_frozen(Structure):
8080
continue
8181
items.append((entry.name.decode("ascii"), entry.size))
8282

83-
expected = [("__hello__", 137),
84-
("__phello__", -137),
85-
("__phello__.spam", 137),
83+
expected = [("__hello__", 142),
84+
("__phello__", -142),
85+
("__phello__.spam", 142),
8686
]
8787
self.assertEqual(items, expected, "PyImport_FrozenModules example "
8888
"in Doc/library/ctypes.rst may be out of date")

0 commit comments

Comments
 (0)