Skip to content
19 changes: 12 additions & 7 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -709,12 +709,14 @@ iterations of the loop.
Exception representation on the stack now consist of one, not three, items.


.. opcode:: LOAD_ASSERTION_ERROR
.. opcode:: LOAD_EXCEPTION_TYPE (type)
Comment thread
brandtbucher marked this conversation as resolved.
Resolved

Pushes :exc:`AssertionError` onto the stack. Used by the :keyword:`assert`
statement.
Pushes an exception type onto the stack, depending on the value of *type*:

.. versionadded:: 3.9
* ``0``: :exc:`AssertionError`
* ``1``: :exc:`StopIteration`

.. versionadded:: 3.12


.. opcode:: LOAD_BUILD_CLASS
Expand Down Expand Up @@ -1344,10 +1346,13 @@ iterations of the loop.
.. versionadded:: 3.11


.. opcode:: SEND
.. opcode:: SEND (delta)

Equivalent to ``TOS = TOS1.send(TOS)``. Used in ``yield from`` and ``await``
statements.

Sends ``None`` to the sub-generator of this generator.
Used in ``yield from`` and ``await`` statements.
If the call raises :exc:`StopIteration`, pop both values, push its return
Comment thread
brandtbucher marked this conversation as resolved.
Resolved
value, and increment the bytecode counter by *delta*.

.. versionadded:: 3.11

Expand Down
14 changes: 7 additions & 7 deletions Include/internal/pycore_opcode.h

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

22 changes: 11 additions & 11 deletions Include/opcode.h

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

3 changes: 3 additions & 0 deletions Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
JUMP_BACKWARD = opmap['JUMP_BACKWARD']
FOR_ITER = opmap['FOR_ITER']
LOAD_ATTR = opmap['LOAD_ATTR']
LOAD_EXCEPTION_TYPE = opmap['LOAD_EXCEPTION_TYPE']

CACHE = opmap["CACHE"]

Expand Down Expand Up @@ -497,6 +498,8 @@ def _get_instructions_bytes(code, varname_from_oparg=None,
if arg & (1<<i))
elif deop == BINARY_OP:
_, argrepr = _nb_ops[arg]
elif deop == LOAD_EXCEPTION_TYPE:
argrepr = "StopIteration" if arg else "AssertionError"
yield Instruction(_all_opname[op], op,
arg, argval, argrepr,
offset, starts_line, is_jump_target, positions)
Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,10 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.12a1 3505 (Specialization/Cache for FOR_ITER)
# Python 3.12a1 3506 (Add BINARY_SLICE and STORE_SLICE instructions)
# Python 3.12a1 3507 (Set lineno of module's RESUME to 0)
# Python 3.12a1 3508 (Replace LOAD_ASSERTION_ERROR with LOAD_EXCEPTION_TYPE)

# Python 3.13 will start with 3550

#
# MAGIC must change whenever the bytecode emitted by the compiler may no
# longer be understood by older implementations of the eval loop (usually
# due to the addition of new opcodes).
Expand All @@ -424,7 +424,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3507).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3508).to_bytes(2, 'little') + b'\r\n'

_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

Expand Down
3 changes: 1 addition & 2 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ def pseudo_op(name, op, real_ops):
def_op('PRINT_EXPR', 70)
def_op('LOAD_BUILD_CLASS', 71)

def_op('LOAD_ASSERTION_ERROR', 74)
def_op('RETURN_GENERATOR', 75)

def_op('LIST_TO_TUPLE', 82)
Expand Down Expand Up @@ -187,7 +186,7 @@ def pseudo_op(name, op, real_ops):
def_op('DELETE_DEREF', 139)
hasfree.append(139)
jrel_op('JUMP_BACKWARD', 140) # Number of words to skip (backwards)

def_op('LOAD_EXCEPTION_TYPE', 141)
def_op('CALL_FUNCTION_EX', 142) # Flags

def_op('EXTENDED_ARG', 144)
Expand Down
45 changes: 36 additions & 9 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def bug1333982(x=[]):
dis_bug1333982 = """\
%3d RESUME 0

%3d LOAD_ASSERTION_ERROR
%3d LOAD_EXCEPTION_TYPE 0 (AssertionError)
LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>)
MAKE_FUNCTION 0
LOAD_FAST 0 (x)
Expand Down Expand Up @@ -507,26 +507,52 @@ async def _asyncwith(c):
LOAD_CONST 0 (None)
RETURN_VALUE

%3d >> PUSH_EXC_INFO
%3d >> LOAD_EXCEPTION_TYPE 1 (StopIteration)
CHECK_EXC_MATCH
POP_JUMP_FORWARD_IF_TRUE 1 (to 74)
RERAISE 0
>> LOAD_ATTR 0 (value)
SWAP 3
POP_TOP
POP_TOP
JUMP_BACKWARD 40 (to 22)
>> LOAD_EXCEPTION_TYPE 1 (StopIteration)
CHECK_EXC_MATCH
POP_JUMP_FORWARD_IF_TRUE 1 (to 110)
RERAISE 0
>> LOAD_ATTR 0 (value)
SWAP 3
POP_TOP
POP_TOP
JUMP_BACKWARD 41 (to 56)
>> PUSH_EXC_INFO
WITH_EXCEPT_START
GET_AWAITABLE 2
LOAD_CONST 0 (None)
>> SEND 3 (to 82)
>> SEND 20 (to 188)
YIELD_VALUE 6
RESUME 3
JUMP_BACKWARD_NO_INTERRUPT 4 (to 74)
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 86)
JUMP_BACKWARD_NO_INTERRUPT 4 (to 146)
>> LOAD_EXCEPTION_TYPE 1 (StopIteration)
CHECK_EXC_MATCH
POP_JUMP_FORWARD_IF_TRUE 1 (to 162)
RERAISE 0
>> LOAD_ATTR 0 (value)
SWAP 3
POP_TOP
POP_TOP
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 192)
RERAISE 2
>> POP_TOP
POP_EXCEPT
POP_TOP
POP_TOP
JUMP_BACKWARD 19 (to 58)
JUMP_BACKWARD 72 (to 58)
>> COPY 3
POP_EXCEPT
RERAISE 1
ExceptionTable:
2 rows
6 rows
""" % (_asyncwith.__code__.co_firstlineno,
_asyncwith.__code__.co_firstlineno + 1,
_asyncwith.__code__.co_firstlineno + 2,
Expand Down Expand Up @@ -1350,8 +1376,9 @@ async def async_def():
0: None
1: 1
Names:
0: b
1: c
0: value
1: b
2: c
Variable names:
0: a
1: d"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to also add a test that shows what you're fixing here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we're not really fixing anything (in fact, we want the behavior to stay exactly the same). All we're really doing is moving some of the error handling logic for close and throw into the bytecode, so I'm not really sure how to test that.

I'm confident that the tests in test_yield_from.TestInterestingEdgeCases exercise this path enough to prevent any behavior changes (like we've seen with other approaches to address this issue).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Compile virtual :keyword:`try`/:keyword:`except` blocks to handle exceptions
raised during :meth:`~generator.close` or :meth:`~generator.throw` calls
through a suspended frame.
2 changes: 1 addition & 1 deletion Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ PyCode_New(int argcount, int kwonlyargcount,

static const char assert0[6] = {
RESUME, 0,
LOAD_ASSERTION_ERROR, 0,
LOAD_EXCEPTION_TYPE, 0, // AssertionError
Comment thread
brandtbucher marked this conversation as resolved.
Resolved
RAISE_VARARGS, 1
};

Expand Down
7 changes: 4 additions & 3 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,13 +427,14 @@ mark_stacks(PyCodeObject *code_obj, int len)
}
case LOAD_ATTR:
{
assert(top_of_stack(next_stack) == Object);
assert(top_of_stack(next_stack) == Object ||
top_of_stack(next_stack) == Except);
int j = get_arg(code, i);
next_stack = pop_value(next_stack);
if (j & 1) {
next_stack = pop_value(next_stack);
next_stack = push_value(next_stack, Null);
next_stack = push_value(next_stack, Object);
}
next_stack = push_value(next_stack, Object);
stacks[i+1] = next_stack;
break;
}
Expand Down
21 changes: 1 addition & 20 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,26 +485,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
}
Py_DECREF(yf);
if (!ret) {
PyObject *val;
/* Pop subiterator from stack */
assert(gen->gi_frame_state < FRAME_CLEARED);
ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);
assert(ret == yf);
Py_DECREF(ret);
// XXX: Performing this jump ourselves is awkward and problematic.
// See https://github.com/python/cpython/pull/31968.
/* Termination repetition of SEND loop */
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
/* Backup to SEND */
assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND);
int jump = _Py_OPARG(frame->prev_instr[-1]);
frame->prev_instr += jump - 1;
if (_PyGen_FetchStopIterationValue(&val) == 0) {
ret = gen_send(gen, val);
Py_DECREF(val);
} else {
ret = gen_send_ex(gen, Py_None, 1, 0);
}
ret = gen_send_ex(gen, Py_None, 1, 0);
}
return ret;
}
Expand Down
8 changes: 6 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2668,6 +2668,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}

TARGET(YIELD_VALUE) {
// NOTE: It's important that YIELD_VALUE never raises an exception!
// The compiler treats any exception raised here as a failed close()
// or throw() call.
assert(oparg == STACK_LEVEL());
assert(frame->is_entry);
PyObject *retval = POP();
Expand Down Expand Up @@ -2746,8 +2749,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
}

TARGET(LOAD_ASSERTION_ERROR) {
PyObject *value = PyExc_AssertionError;
TARGET(LOAD_EXCEPTION_TYPE) {
assert(oparg < 2);
PyObject *value = oparg ? PyExc_StopIteration : PyExc_AssertionError;
Py_INCREF(value);
PUSH(value);
DISPATCH();
Expand Down
Loading