mirror of
https://github.com/python/cpython.git
synced 2025-08-26 19:55:24 +00:00
Produce cleaner bytecode for 'with' and 'async with' by generating separate code for normal and exceptional paths. (#6641)
Remove BEGIN_FINALLY, END_FINALLY, CALL_FINALLY and POP_FINALLY bytecodes. Implement finally blocks by code duplication. Reimplement frame.lineno setter using line numbers rather than bytecode offsets.
This commit is contained in:
parent
5dcc06f6e0
commit
fee552669f
15 changed files with 4789 additions and 4754 deletions
222
Python/ceval.c
222
Python/ceval.c
|
@ -79,7 +79,7 @@ static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
|
|||
static PyObject * special_lookup(PyThreadState *, PyObject *, _Py_Identifier *);
|
||||
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
|
||||
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
|
||||
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int);
|
||||
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
|
||||
|
||||
#define NAME_ERROR_MSG \
|
||||
"name '%.200s' is not defined"
|
||||
|
@ -2017,7 +2017,12 @@ main_loop:
|
|||
PyObject *iter = _PyCoro_GetAwaitableIter(iterable);
|
||||
|
||||
if (iter == NULL) {
|
||||
int opcode_at_minus_3 = 0;
|
||||
if ((next_instr - first_instr) > 2) {
|
||||
opcode_at_minus_3 = _Py_OPCODE(next_instr[-3]);
|
||||
}
|
||||
format_awaitable_error(tstate, Py_TYPE(iterable),
|
||||
opcode_at_minus_3,
|
||||
_Py_OPCODE(next_instr[-2]));
|
||||
}
|
||||
|
||||
|
@ -2128,104 +2133,13 @@ main_loop:
|
|||
DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(POP_FINALLY): {
|
||||
/* If oparg is 0 at the top of the stack are 1 or 6 values:
|
||||
Either:
|
||||
- TOP = NULL or an integer
|
||||
or:
|
||||
- (TOP, SECOND, THIRD) = exc_info()
|
||||
- (FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER
|
||||
|
||||
If oparg is 1 the value for 'return' was additionally pushed
|
||||
at the top of the stack.
|
||||
*/
|
||||
PyObject *res = NULL;
|
||||
if (oparg) {
|
||||
res = POP();
|
||||
}
|
||||
case TARGET(RERAISE): {
|
||||
PyObject *exc = POP();
|
||||
if (exc == NULL || PyLong_CheckExact(exc)) {
|
||||
Py_XDECREF(exc);
|
||||
}
|
||||
else {
|
||||
Py_DECREF(exc);
|
||||
Py_DECREF(POP());
|
||||
Py_DECREF(POP());
|
||||
|
||||
PyObject *type, *value, *traceback;
|
||||
_PyErr_StackItem *exc_info;
|
||||
PyTryBlock *b = PyFrame_BlockPop(f);
|
||||
if (b->b_type != EXCEPT_HANDLER) {
|
||||
_PyErr_SetString(tstate, PyExc_SystemError,
|
||||
"popped block is not an except handler");
|
||||
Py_XDECREF(res);
|
||||
goto error;
|
||||
}
|
||||
assert(STACK_LEVEL() == (b)->b_level + 3);
|
||||
exc_info = tstate->exc_info;
|
||||
type = exc_info->exc_type;
|
||||
value = exc_info->exc_value;
|
||||
traceback = exc_info->exc_traceback;
|
||||
exc_info->exc_type = POP();
|
||||
exc_info->exc_value = POP();
|
||||
exc_info->exc_traceback = POP();
|
||||
Py_XDECREF(type);
|
||||
Py_XDECREF(value);
|
||||
Py_XDECREF(traceback);
|
||||
}
|
||||
if (oparg) {
|
||||
PUSH(res);
|
||||
}
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(CALL_FINALLY): {
|
||||
PyObject *ret = PyLong_FromLong(INSTR_OFFSET());
|
||||
if (ret == NULL) {
|
||||
goto error;
|
||||
}
|
||||
PUSH(ret);
|
||||
JUMPBY(oparg);
|
||||
FAST_DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(BEGIN_FINALLY): {
|
||||
/* Push NULL onto the stack for using it in END_FINALLY,
|
||||
POP_FINALLY, WITH_CLEANUP_START and WITH_CLEANUP_FINISH.
|
||||
*/
|
||||
PUSH(NULL);
|
||||
FAST_DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(END_FINALLY): {
|
||||
PREDICTED(END_FINALLY);
|
||||
/* At the top of the stack are 1 or 6 values:
|
||||
Either:
|
||||
- TOP = NULL or an integer
|
||||
or:
|
||||
- (TOP, SECOND, THIRD) = exc_info()
|
||||
- (FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER
|
||||
*/
|
||||
PyObject *exc = POP();
|
||||
if (exc == NULL) {
|
||||
FAST_DISPATCH();
|
||||
}
|
||||
else if (PyLong_CheckExact(exc)) {
|
||||
int ret = _PyLong_AsInt(exc);
|
||||
Py_DECREF(exc);
|
||||
if (ret == -1 && _PyErr_Occurred(tstate)) {
|
||||
goto error;
|
||||
}
|
||||
JUMPTO(ret);
|
||||
FAST_DISPATCH();
|
||||
}
|
||||
else {
|
||||
assert(PyExceptionClass_Check(exc));
|
||||
PyObject *val = POP();
|
||||
PyObject *tb = POP();
|
||||
_PyErr_Restore(tstate, exc, val, tb);
|
||||
goto exception_unwind;
|
||||
}
|
||||
PyObject *val = POP();
|
||||
PyObject *tb = POP();
|
||||
assert(PyExceptionClass_Check(exc));
|
||||
PyErr_Restore(exc, val, tb);
|
||||
goto exception_unwind;
|
||||
}
|
||||
|
||||
case TARGET(END_ASYNC_FOR): {
|
||||
|
@ -3302,111 +3216,31 @@ main_loop:
|
|||
DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(WITH_CLEANUP_START): {
|
||||
/* At the top of the stack are 1 or 6 values indicating
|
||||
how/why we entered the finally clause:
|
||||
- TOP = NULL
|
||||
case TARGET(WITH_EXCEPT_START): {
|
||||
/* At the top of the stack are 7 values:
|
||||
- (TOP, SECOND, THIRD) = exc_info()
|
||||
(FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER
|
||||
Below them is EXIT, the context.__exit__ or context.__aexit__
|
||||
bound method.
|
||||
In the first case, we must call
|
||||
EXIT(None, None, None)
|
||||
otherwise we must call
|
||||
EXIT(TOP, SECOND, THIRD)
|
||||
|
||||
In the first case, we remove EXIT from the
|
||||
stack, leaving TOP, and push TOP on the stack.
|
||||
Otherwise we shift the bottom 3 values of the
|
||||
stack down, replace the empty spot with NULL, and push
|
||||
None on the stack.
|
||||
|
||||
Finally we push the result of the call.
|
||||
- (FOURTH, FIFTH, SIXTH) = previous exception for EXCEPT_HANDLER
|
||||
- SEVENTH: the context.__exit__ bound method
|
||||
We call SEVENTH(TOP, SECOND, THIRD).
|
||||
Then we push again the TOP exception and the __exit__
|
||||
return value.
|
||||
*/
|
||||
PyObject *exit_func;
|
||||
PyObject *exc, *val, *tb, *res;
|
||||
|
||||
val = tb = Py_None;
|
||||
exc = TOP();
|
||||
if (exc == NULL) {
|
||||
STACK_SHRINK(1);
|
||||
exit_func = TOP();
|
||||
SET_TOP(exc);
|
||||
exc = Py_None;
|
||||
}
|
||||
else {
|
||||
assert(PyExceptionClass_Check(exc));
|
||||
PyObject *tp2, *exc2, *tb2;
|
||||
PyTryBlock *block;
|
||||
val = SECOND();
|
||||
tb = THIRD();
|
||||
tp2 = FOURTH();
|
||||
exc2 = PEEK(5);
|
||||
tb2 = PEEK(6);
|
||||
exit_func = PEEK(7);
|
||||
SET_VALUE(7, tb2);
|
||||
SET_VALUE(6, exc2);
|
||||
SET_VALUE(5, tp2);
|
||||
/* UNWIND_EXCEPT_HANDLER will pop this off. */
|
||||
SET_FOURTH(NULL);
|
||||
/* We just shifted the stack down, so we have
|
||||
to tell the except handler block that the
|
||||
values are lower than it expects. */
|
||||
assert(f->f_iblock > 0);
|
||||
block = &f->f_blockstack[f->f_iblock - 1];
|
||||
assert(block->b_type == EXCEPT_HANDLER);
|
||||
assert(block->b_level > 0);
|
||||
block->b_level--;
|
||||
}
|
||||
|
||||
val = SECOND();
|
||||
tb = THIRD();
|
||||
assert(exc != Py_None);
|
||||
assert(!PyLong_Check(exc));
|
||||
exit_func = PEEK(7);
|
||||
PyObject *stack[4] = {NULL, exc, val, tb};
|
||||
res = _PyObject_Vectorcall(exit_func, stack + 1,
|
||||
3 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
|
||||
Py_DECREF(exit_func);
|
||||
if (res == NULL)
|
||||
goto error;
|
||||
|
||||
Py_INCREF(exc); /* Duplicating the exception on the stack */
|
||||
PUSH(exc);
|
||||
PUSH(res);
|
||||
PREDICT(WITH_CLEANUP_FINISH);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
case TARGET(WITH_CLEANUP_FINISH): {
|
||||
PREDICTED(WITH_CLEANUP_FINISH);
|
||||
/* TOP = the result of calling the context.__exit__ bound method
|
||||
SECOND = either None or exception type
|
||||
|
||||
If SECOND is None below is NULL or the return address,
|
||||
otherwise below are 7 values representing an exception.
|
||||
*/
|
||||
PyObject *res = POP();
|
||||
PyObject *exc = POP();
|
||||
int err;
|
||||
|
||||
if (exc != Py_None)
|
||||
err = PyObject_IsTrue(res);
|
||||
else
|
||||
err = 0;
|
||||
|
||||
Py_DECREF(res);
|
||||
Py_DECREF(exc);
|
||||
|
||||
if (err < 0)
|
||||
goto error;
|
||||
else if (err > 0) {
|
||||
/* There was an exception and a True return.
|
||||
* We must manually unwind the EXCEPT_HANDLER block
|
||||
* which was created when the exception was caught,
|
||||
* otherwise the stack will be in an inconsistent state.
|
||||
*/
|
||||
PyTryBlock *b = PyFrame_BlockPop(f);
|
||||
assert(b->b_type == EXCEPT_HANDLER);
|
||||
UNWIND_EXCEPT_HANDLER(b);
|
||||
PUSH(NULL);
|
||||
}
|
||||
PREDICT(END_FINALLY);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
|
@ -3776,6 +3610,10 @@ exception_unwind:
|
|||
PUSH(val);
|
||||
PUSH(exc);
|
||||
JUMPTO(handler);
|
||||
if (_Py_TracingPossible(ceval)) {
|
||||
/* Make sure that we trace line after exception */
|
||||
instr_prev = INT_MAX;
|
||||
}
|
||||
/* Resume normal execution */
|
||||
goto main_loop;
|
||||
}
|
||||
|
@ -5477,7 +5315,7 @@ format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg)
|
|||
}
|
||||
|
||||
static void
|
||||
format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int prevopcode)
|
||||
format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int prevprevopcode, int prevopcode)
|
||||
{
|
||||
if (type->tp_as_async == NULL || type->tp_as_async->am_await == NULL) {
|
||||
if (prevopcode == BEFORE_ASYNC_WITH) {
|
||||
|
@ -5486,7 +5324,7 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int prevopcode
|
|||
"that does not implement __await__: %.100s",
|
||||
type->tp_name);
|
||||
}
|
||||
else if (prevopcode == WITH_CLEANUP_START) {
|
||||
else if (prevopcode == WITH_EXCEPT_START || (prevopcode == CALL_FUNCTION && prevprevopcode == DUP_TOP)) {
|
||||
_PyErr_Format(tstate, PyExc_TypeError,
|
||||
"'async with' received an object from __aexit__ "
|
||||
"that does not implement __await__: %.100s",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue