8000 Perform throws in the bytecode · python/cpython@973e566 · GitHub
[go: up one dir, main page]

Skip to content

Commit 973e566

Browse files
committed
Perform throws in the bytecode
1 parent 5c3201e commit 973e566

File tree

9 files changed

+113
-105
lines changed

9 files changed

+113
-105
lines changed

Include/opcode.h

Lines changed: 19 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ def _write_atomic(path, data, mode=0o666):
396396
# Python 3.11a6 3486 (Use inline caching for PRECALL and CALL)
397397
# Python 3.11a6 3487 (Remove the adaptive "oparg counter" mechanism)
398398
# Python 3.11a6 3488 (LOAD_GLOBAL can push additional NULL)
399+
# Python 3.11a6 3489 (Add THROW)
399400

400401
# Python 3.12 will start with magic number 3500
401402

@@ -410,7 +411,7 @@ def _write_atomic(path, data, mode=0o666):
410411
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
411412
# in PC/launcher.c must also be updated.
412413

413-
MAGIC_NUMBER = (3488).to_bytes(2, 'little') + b'\r\n'
414+
MAGIC_NUMBER = (3489).to_bytes(2, 'little') + b'\r\n'
414415
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
415416

416417
_PYCACHE = '__pycache__'

Lib/opcode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ def jabs_op(name, op, entries=0):
166166
hasfree.append(138)
167167
def_op('DELETE_DEREF', 139)
168168
hasfree.append(139)
169+
jrel_op('THROW', 140)
169170

170171
def_op('CALL_FUNCTION_EX', 142) # Flags
171172

Lib/test/test_asyncgen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ def test3(anext):
669669
agen = agenfn()
670670
with contextlib.closing(anext(agen, "default").__await__()) as g:
671671
self.assertEqual(g.send(None), 1)
672+
breakpoint()
672673
g.close()
673674
with self.assertRaisesRegex(RuntimeError, 'cannot reuse'):
674675
self.assertEqual(g.send(None), 1)

Objects/frameobject.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ mark_stacks(PyCodeObject *code_obj, int len)
252252
stacks[i+1] = next_stack;
253253
break;
254254
case SEND:
255+
case THROW:
255256
j = get_arg(code, i) + i + 1;
256257
assert(j < len);
257258
assert(stacks[j] == UNINITIALIZED || stacks[j] == pop_value(next_stack));

Objects/genobject.c

Lines changed: 8 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -417,93 +417,28 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
417417
PyObject *typ, PyObject *val, PyObject *tb)
418418
{
419419
PyObject *yf = _PyGen_yf(gen);
420-
421420
if (yf) {
422-
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
423-
PyObject *ret;
424-
int err;
425-
if (PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit) &&
426-
close_on_genexit
427-
) {
421+
if (close_on_genexit &&
422+
PyErr_GivenExceptionMatches(typ, PyExc_GeneratorExit))
423+
{
428424
/* Asynchronous generators *should not* be closed right away.
429425
We have to allow some awaits to work it through, hence the
430426
`close_on_genexit` parameter here.
431427
*/
428+
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
432429
PyFrameState state = frame->f_state;
433430
frame->f_state = FRAME_EXECUTING;
434-
err = gen_close_iter(yf);
431+
int err = gen_close_iter(yf);
435432
frame->f_state = state;
436433
Py_DECREF(yf);
437-
if (err < 0)
434+
if (err < 0) {
438435
return gen_send_ex(gen, Py_None, 1, 0);
439-
goto throw_here;
440-
}
441-
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
442-
/* `yf` is a generator or a coroutine. */
443-
PyThreadState *tstate = _PyThreadState_GET();
444-
/* Since we are fast-tracking things by skipping the eval loop,
445-
we need to update the current frame so the stack trace
446-
will be reported correctly to the user. */
447-
/* XXX We should probably be updating the current frame
448-
somewhere in ceval.c. */
449-
_PyInterpreterFrame *prev = tstate->cframe->current_frame;
450-
frame->previous = prev;
451-
tstate->cframe->current_frame = frame;
452-
/* Close the generator that we are currently iterating with
453-
'yield from' or awaiting on with 'await'. */
454-
PyFrameState state = frame->f_state;
455-
frame->f_state = FRAME_EXECUTING;
456-
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
457-
typ, val, tb);
458-
frame->f_state = state;
459-
tstate->cframe->current_frame = prev;
460-
frame->previous = NULL;
461-
} else {
462-
/* `yf` is an iterator or a coroutine-like object. */
463-
PyObject *meth;
464-
if (_PyObject_LookupAttr(yf, &_Py_ID(throw), &meth) < 0) {
465-
Py_DECREF(yf);
466-
return NULL;
467-
}
468-
if (meth == NULL) {
469-
Py_DECREF(yf);
470-
goto throw_here;
471436
}
472-
PyFrameState state = frame->f_state;
473-
frame->f_state = FRAME_EXECUTING;
474-
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
475-
frame->f_state = state;
476-
Py_DECREF(meth);
477437
}
478-
Py_DECREF(yf);
479-
if (!ret) {
480-
PyObject *val;
481-
/* Pop subiterator from stack */
482-
assert(gen->gi_frame_valid);
483-
ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);
484-
assert(ret == yf);
485-
Py_DECREF(ret);
486-
// XXX: Performing this jump ourselves is awkward and problematic.
487-
// See https://github.com/python/cpython/pull/31968.
488-
/* Termination repetition of SEND loop */
489-
assert(frame->f_lasti >= 0);
490-
_Py_CODEUNIT *code = _PyCode_CODE(gen->gi_code);
491-
/* Backup to SEND */
492-
frame->f_lasti--;
493-
assert(_Py_OPCODE(code[frame->f_lasti]) == SEND);
494-
int jump = _Py_OPARG(code[frame->f_lasti]);
495-
frame->f_lasti += jump;
496-
if (_PyGen_FetchStopIterationValue(&val) == 0) {
497-
ret = gen_send(gen, val);
498-
Py_DECREF(val);
499-
} else {
500-
ret = gen_send_ex(gen, Py_None, 1, 0);
501-
}
438+
else {
439+
Py_DECREF(yf);
502440
}
503-
return ret;
504441
}
505-
506-
throw_here:
507442
/* First, check the traceback argument, replacing None with
508443
NULL. */
509444
if (tb == Py_None) {

Python/ceval.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,54 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
25702570
DISPATCH();
25712571
}
25722572

2573+
TARGET(THROW) {
2574+
assert(frame->is_entry);
2575+
assert(throwflag);
2576+
PyObject *exc_value = POP();
2577+
PyObject *exc_type = Py_NewRef(Py_TYPE(exc_value));
2578+
PyObject *exc_traceback = PyException_GetTraceback(exc_value);
2579+
PyErr_NormalizeException(&exc_type, &exc_value, &exc_traceback);
2580+
PyObject *last_send = POP();
2581+
Py_DECREF(last_send);
2582+
PyObject *receiver = TOP();
2583+
PyObject *throw;
2584+
int found = _PyObject_LookupAttr(receiver, &_Py_ID(throw), &throw);
2585+
if (found < 0) {
2586+
if (PyErr_GivenExceptionMatches(exc_value, PyExc_GeneratorExit))
2587+
{
2588+
PyErr_Clear();
2589+
_PyErr_Restore(tstate, exc_type, exc_value, exc_traceback);
2590+
}
2591+
goto error;
2592+
}
2593+
if (found == 0) {
2594+
_PyErr_Restore(tstate, exc_type, exc_value, exc_traceback);
2595+
goto error;
2596+
}
2597+
PyObject *retval = PyObject_CallFunctionObjArgs(
2598+
throw, exc_type, exc_value, exc_traceback, NULL);
2599+
Py_DECREF(throw);
2600+
Py_DECREF(exc_type);
2601+
Py_DECREF(exc_value);
2602+
Py_XDECREF(exc_traceback);
2603+
if (retval) {
2604+
PUSH(retval);
2605+
DISPATCH();
2606+
}
2607+
if (tstate->c_tracefunc &&
2608+
_PyErr_ExceptionMatches(tstate, PyExc_StopIteration))
2609+
{
2610+
call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate,
2611+
frame);
2612+
}
2613+
if (_PyGen_FetchStopIterationValue(&TOP())) {
2614+
goto error;
2615+
}
2616+
Py_DECREF(receiver);
2617+
JUMPBY(oparg);
2618+
DISPATCH();
2619+
}
2620+
25732621
TARGET(ASYNC_GEN_WRAP) {
25742622
PyObject *v = TOP();
25752623
assert(frame->f_code->co_flags & CO_ASYNC_GENERATOR);
@@ -2583,6 +2631,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
25832631
}
25842632

25852633
TARGET(YIELD_VALUE) {
2634+
// NOTE: It's important that YIELD_VALUE never raises an exception!
2635+
// The compiler treats *anything* raised here as a throw() call.
25862636
assert(frame->is_entry);
25872637
PyObject *retval = POP();
25882638
frame->f_state = FRAME_SUSPENDED;

Python/compile.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,8 @@ stack_effect(int opcode, int oparg, int jump)
947947
return jump > 0 ? -1 : 1;
948948
case SEND:
949949
return jump > 0 ? -1 : 0;
950+
case THROW:
951+
return jump > 0 ? -2 : -1;
950952
case STORE_ATTR:
951953
return -2;
952954
case DELETE_ATTR:
@@ -1875,19 +1877,34 @@ compiler_call_exit_with_nones(struct compiler *c) {
18751877
static int
18761878
compiler_add_yield_from(struct compiler *c, int await)
18771879
{
1878-
basicblock *start, *resume, *exit;
1879-
start = compiler_new_block(c);
1880-
resume = compiler_new_block(c);
1880+
1881+
basicblock *body, *except, *exit;
1882+
basicblock *start = compiler_new_block(c);
1883+
body = compiler_new_block(c);
1884+
except = compiler_new_block(c);
18811885
exit = compiler_new_block(c);
1882-
if (start == NULL || resume == NULL || exit == NULL) {
1886+
if (body == NULL || except == NULL || exit == NULL)
18831887
return 0;
1884-
}
1888+
18851889
compiler_use_next_block(c, start);
18861890
ADDOP_JUMP(c, SEND, exit);
1887-
compiler_use_next_block(c, resume);
1891+
1892+
1893+
compiler_use_next_block(c, body);
1894+
ADDOP_JUMP(c, SETUP_FINALLY, except);
1895+
RETURN_IF_FALSE(compiler_push_fblock(c, TRY_EXCEPT, body, NULL, NULL));
1896+
// The only way YIELD_VALUE can raise is if throw() is called:
18881897
ADDOP(c, YIELD_VALUE);
1898+
compiler_pop_fblock(c, TRY_EXCEPT, body);
1899+
ADDOP_NOLINE(c, POP_BLOCK);
1900+
18891901
ADDOP_I(c, RESUME, await ? 3 : 2);
18901902
ADDOP_JUMP(c, JUMP_NO_INTERRUPT, start);
1903+
1904+
compiler_use_next_block(c, except);
1905+
ADDOP_JUMP(c, THROW, exit);
1906+
ADDOP_JUMP(c, JUMP_NO_INTERRUPT, body);
1907+
18911908
compiler_use_next_block(c, exit);
18921909
return 1;
18931910
}

Python/opcode_targets.h

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
0