Skip to content

Commit 576f356

Browse files
committed
Produce cleaner bytecode for 'with' and 'async with' by generating separate code for normal and exceptional paths.
1 parent 8dcb17f commit 576f356

File tree

5 files changed

+157
-58
lines changed

5 files changed

+157
-58
lines changed

Include/opcode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ extern "C" {
3030
#define BINARY_TRUE_DIVIDE 27
3131
#define INPLACE_FLOOR_DIVIDE 28
3232
#define INPLACE_TRUE_DIVIDE 29
33+
#define RERAISE 48
3334
#define GET_AITER 50
3435
#define GET_ANEXT 51
3536
#define BEFORE_ASYNC_WITH 52
3637
#define BEGIN_FINALLY 53
38+
#define WITH_EXCEPT_START 54
3739
#define INPLACE_ADD 55
3840
#define INPLACE_SUBTRACT 56
3941
#define INPLACE_MULTIPLY 57

Lib/opcode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ def jabs_op(name, op):
8484
def_op('INPLACE_FLOOR_DIVIDE', 28)
8585
def_op('INPLACE_TRUE_DIVIDE', 29)
8686

87+
def_op('RERAISE', 48)
88+
8789
def_op('GET_AITER', 50)
8890
def_op('GET_ANEXT', 51)
8991
def_op('BEFORE_ASYNC_WITH', 52)
9092
def_op('BEGIN_FINALLY', 53)
93+
def_op('WITH_EXCEPT_START', 54)
9194

9295
def_op('INPLACE_ADD', 55)
9396
def_op('INPLACE_SUBTRACT', 56)

Python/ceval.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1898,6 +1898,15 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
18981898
DISPATCH();
18991899
}
19001900

1901+
TARGET(RERAISE) {
1902+
PyObject *exc = POP();
1903+
PyObject *val = POP();
1904+
PyObject *tb = POP();
1905+
assert(PyExceptionClass_Check(exc));
1906+
PyErr_Restore(exc, val, tb);
1907+
goto exception_unwind;
1908+
}
1909+
19011910
TARGET(POP_FINALLY) {
19021911
/* If oparg is 0 at the top of the stack are 1 or 6 values:
19031912
Either:
@@ -3008,6 +3017,37 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
30083017
DISPATCH();
30093018
}
30103019

3020+
TARGET(WITH_EXCEPT_START) {
3021+
/* At the top of the stack are 7 values:
3022+
- (TOP, SECOND, THIRD) = exc_info()
3023+
- (FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER
3024+
- SEVENTH: the context.__exit__ bound method
3025+
We call SEVENTH(TOP, SECOND, THIRD).
3026+
Then we push again the TOP exception and the __exit__
3027+
return value.
3028+
*/
3029+
PyObject *exit_func;
3030+
PyObject *exit_stack[3];
3031+
PyObject *exc, *val, *tb, *res;
3032+
3033+
exc = TOP();
3034+
val = SECOND();
3035+
tb = THIRD();
3036+
assert(exc != Py_None);
3037+
assert(!PyLong_Check(exc));
3038+
3039+
exit_func = PEEK(7);
3040+
exit_stack[0] = exc;
3041+
exit_stack[1] = val;
3042+
exit_stack[2] = tb;
3043+
res = _PyObject_FastCall(exit_func, exit_stack, 3);
3044+
if (res == NULL)
3045+
goto error;
3046+
3047+
PUSH(res);
3048+
DISPATCH();
3049+
}
3050+
30113051
TARGET(WITH_CLEANUP_START) {
30123052
/* At the top of the stack are 1 or 6 values indicating
30133053
how/why we entered the finally clause:

Python/compile.c

Lines changed: 110 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,9 +1040,14 @@ PyCompile_OpcodeStackEffect(int opcode, int oparg)
10401040
* This is the main reason of using this opcode instead of
10411041
* "LOAD_CONST None". */
10421042
return 6;
1043+
case RERAISE:
1044+
return -3;
10431045
case CALL_FINALLY:
10441046
return 1;
10451047

1048+
case WITH_EXCEPT_START:
1049+
return 1;
1050+
10461051
case LOAD_FAST:
10471052
return 1;
10481053
case STORE_FAST:
@@ -1474,6 +1479,15 @@ compiler_pop_fblock(struct compiler *c, enum fblocktype t, basicblock *b)
14741479
assert(u->u_fblock[u->u_nfblocks].fb_block == b);
14751480
}
14761481

1482+
static int
1483+
compiler_call_exit_with_nones(struct compiler *c) {
1484+
ADDOP_O(c, LOAD_CONST, Py_None, consts);
1485+
ADDOP(c, DUP_TOP);
1486+
ADDOP(c, DUP_TOP);
1487+
ADDOP_I(c, CALL_FUNCTION, 3);
1488+
return 1;
1489+
}
1490+
14771491
/* Unwind a frame block. If preserve_tos is true, the TOS before
14781492
* popping the blocks will be restored afterwards.
14791493
*/
@@ -1511,15 +1525,14 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
15111525
ADDOP(c, POP_BLOCK);
15121526
if (preserve_tos)
15131527
ADDOP(c, ROT_TWO);
1514-
ADDOP(c, BEGIN_FINALLY);
1515-
ADDOP(c, WITH_CLEANUP_START);
1528+
if(!compiler_call_exit_with_nones(c))
1529+
return 0;
15161530
if (info->fb_type == ASYNC_WITH) {
15171531
ADDOP(c, GET_AWAITABLE);
15181532
ADDOP_O(c, LOAD_CONST, Py_None, consts);
15191533
ADDOP(c, YIELD_FROM);
15201534
}
1521-
ADDOP(c, WITH_CLEANUP_FINISH);
1522-
ADDOP_I(c, POP_FINALLY, 0);
1535+
ADDOP(c, POP_TOP);
15231536
return 1;
15241537

15251538
case HANDLER_CLEANUP:
@@ -4282,6 +4295,22 @@ expr_constant(expr_ty e)
42824295
return -1;
42834296
}
42844297

4298+
static int
4299+
compiler_with_except_finish(struct compiler *c) {
4300+
basicblock *exit;
4301+
exit = compiler_new_block(c);
4302+
if (exit == NULL)
4303+
return 0;
4304+
ADDOP_JABS(c, POP_JUMP_IF_TRUE, exit);
4305+
ADDOP(c, RERAISE);
4306+
compiler_use_next_block(c, exit);
4307+
ADDOP(c, POP_TOP);
4308+
ADDOP(c, POP_TOP);
4309+
ADDOP(c, POP_TOP);
4310+
ADDOP(c, POP_EXCEPT);
4311+
ADDOP(c, POP_TOP);
4312+
return 1;
4313+
}
42854314

42864315
/*
42874316
Implements the async with statement.
@@ -4310,14 +4339,16 @@ expr_constant(expr_ty e)
43104339
static int
43114340
compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43124341
{
4313-
basicblock *block, *finally;
4342+
basicblock *block, *final1, *final2, *exit;
43144343
withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos);
43154344

43164345
assert(s->kind == AsyncWith_kind);
43174346

43184347
block = compiler_new_block(c);
4319-
finally = compiler_new_block(c);
4320-
if (!block || !finally)
4348+
final1 = compiler_new_block(c);
4349+
final2 = compiler_new_block(c);
4350+
exit = compiler_new_block(c);
4351+
if (!block || !final1 || !final2 || !exit)
43214352
return 0;
43224353

43234354
/* Evaluate EXPR */
@@ -4328,11 +4359,11 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43284359
ADDOP_O(c, LOAD_CONST, Py_None, consts);
43294360
ADDOP(c, YIELD_FROM);
43304361

4331-
ADDOP_JREL(c, SETUP_ASYNC_WITH, finally);
4362+
ADDOP_JREL(c, SETUP_ASYNC_WITH, final2);
43324363

43334364
/* SETUP_ASYNC_WITH pushes a finally block. */
43344365
compiler_use_next_block(c, block);
4335-
if (!compiler_push_fblock(c, ASYNC_WITH, block, finally)) {
4366+
if (!compiler_push_fblock(c, ASYNC_WITH, block, final2)) {
43364367
return 0;
43374368
}
43384369

@@ -4353,74 +4384,88 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43534384

43544385
/* End of try block; start the finally block */
43554386
ADDOP(c, POP_BLOCK);
4356-
ADDOP(c, BEGIN_FINALLY);
43574387
compiler_pop_fblock(c, ASYNC_WITH, block);
43584388

4359-
compiler_use_next_block(c, finally);
4360-
if (!compiler_push_fblock(c, FINALLY_END, finally, NULL))
4389+
compiler_use_next_block(c, final1);
4390+
if (!compiler_push_fblock(c, FINALLY_END, final1, NULL))
43614391
return 0;
43624392

4363-
/* Finally block starts; context.__exit__ is on the stack under
4364-
the exception or return information. Just issue our magic
4365-
opcode. */
4366-
ADDOP(c, WITH_CLEANUP_START);
4393+
/* `finally` block for successful outcome:
4394+
* call __exit__(None, None, None)
4395+
*/
4396+
if(!compiler_call_exit_with_nones(c))
4397+
return 0;
4398+
ADDOP(c, GET_AWAITABLE);
4399+
ADDOP_O(c, LOAD_CONST, Py_None, consts);
4400+
ADDOP(c, YIELD_FROM);
4401+
4402+
ADDOP(c, POP_TOP);
4403+
4404+
compiler_pop_fblock(c, FINALLY_END, final1);
4405+
ADDOP_JABS(c, JUMP_ABSOLUTE, exit);
43674406

4407+
/* `finally` block for exceptional outcome */
4408+
compiler_use_next_block(c, final2);
4409+
if (!compiler_push_fblock(c, FINALLY_END, final2, NULL))
4410+
return 0;
4411+
4412+
ADDOP(c, WITH_EXCEPT_START);
43684413
ADDOP(c, GET_AWAITABLE);
43694414
ADDOP_O(c, LOAD_CONST, Py_None, consts);
43704415
ADDOP(c, YIELD_FROM);
4416+
compiler_with_except_finish(c);
43714417

4372-
ADDOP(c, WITH_CLEANUP_FINISH);
4418+
compiler_pop_fblock(c, FINALLY_END, final2);
43734419

4374-
/* Finally block ends. */
4375-
ADDOP(c, END_FINALLY);
4376-
compiler_pop_fblock(c, FINALLY_END, finally);
4420+
compiler_use_next_block(c, exit);
43774421
return 1;
43784422
}
43794423

43804424

43814425
/*
43824426
Implements the with statement from PEP 343.
4383-
4384-
The semantics outlined in that PEP are as follows:
4385-
43864427
with EXPR as VAR:
43874428
BLOCK
4388-
4389-
It is implemented roughly as:
4390-
4391-
context = EXPR
4392-
exit = context.__exit__ # not calling it
4393-
value = context.__enter__()
4394-
try:
4395-
VAR = value # if VAR present in the syntax
4396-
BLOCK
4397-
finally:
4398-
if an exception was raised:
4399-
exc = copy of (exception, instance, traceback)
4400-
else:
4401-
exc = (None, None, None)
4402-
exit(*exc)
4429+
is implemented as:
4430+
<code for EXPR>
4431+
SETUP_WITH E
4432+
<code to store to VAR> or POP_TOP
4433+
<code for BLOCK>
4434+
LOAD_CONST (None, None, None)
4435+
CALL_FUNCTION_EX 0
4436+
JUMP_FORWARD EXIT
4437+
E: WITH_EXCEPT_START (calls EXPR.__exit__)
4438+
POP_JUMP_IF_TRUE T:
4439+
RERAISE
4440+
T: POP_TOP * 3 (remove exception from stack)
4441+
POP_EXCEPT
4442+
POP_TOP
4443+
EXIT:
44034444
*/
4445+
44044446
static int
44054447
compiler_with(struct compiler *c, stmt_ty s, int pos)
44064448
{
4407-
basicblock *block, *finally;
4449+
basicblock *block, *final1, *final2, *exit;
44084450
withitem_ty item = asdl_seq_GET(s->v.With.items, pos);
44094451

44104452
assert(s->kind == With_kind);
44114453

44124454
block = compiler_new_block(c);
4413-
finally = compiler_new_block(c);
4414-
if (!block || !finally)
4455+
final1 = compiler_new_block(c);
4456+
final2 = compiler_new_block(c);
4457+
exit = compiler_new_block(c);
4458+
if (!block || !final1 || !final2 || !exit)
44154459
return 0;
44164460

44174461
/* Evaluate EXPR */
44184462
VISIT(c, expr, item->context_expr);
4419-
ADDOP_JREL(c, SETUP_WITH, finally);
4463+
/* Will push bound __exit__ */
4464+
ADDOP_JREL(c, SETUP_WITH, final2);
44204465

4421-
/* SETUP_WITH pushes a finally block. */
4466+
/* SETUP_ASYNC_WITH pushes a finally block. */
44224467
compiler_use_next_block(c, block);
4423-
if (!compiler_push_fblock(c, WITH, block, finally)) {
4468+
if (!compiler_push_fblock(c, WITH, block, final2)) {
44244469
return 0;
44254470
}
44264471

@@ -4439,24 +4484,33 @@ compiler_with(struct compiler *c, stmt_ty s, int pos)
44394484
else if (!compiler_with(c, s, pos))
44404485
return 0;
44414486

4442-
/* End of try block; start the finally block */
4487+
/* End of try block; start the finally blocks */
44434488
ADDOP(c, POP_BLOCK);
4444-
ADDOP(c, BEGIN_FINALLY);
44454489
compiler_pop_fblock(c, WITH, block);
44464490

4447-
compiler_use_next_block(c, finally);
4448-
if (!compiler_push_fblock(c, FINALLY_END, finally, NULL))
4491+
/* `finally` block for successful outcome:
4492+
* call __exit__(None, None, None)
4493+
*/
4494+
compiler_use_next_block(c, final1);
4495+
if (!compiler_push_fblock(c, FINALLY_END, final1, NULL))
44494496
return 0;
44504497

4451-
/* Finally block starts; context.__exit__ is on the stack under
4452-
the exception or return information. Just issue our magic
4453-
opcode. */
4454-
ADDOP(c, WITH_CLEANUP_START);
4455-
ADDOP(c, WITH_CLEANUP_FINISH);
4498+
if(!compiler_call_exit_with_nones(c))
4499+
return 0;
4500+
ADDOP(c, POP_TOP);
4501+
compiler_pop_fblock(c, FINALLY_END, final1);
4502+
ADDOP_JREL(c, JUMP_FORWARD, exit);
44564503

4457-
/* Finally block ends. */
4458-
ADDOP(c, END_FINALLY);
4459-
compiler_pop_fblock(c, FINALLY_END, finally);
4504+
/* `finally` block for exceptional outcome */
4505+
compiler_use_next_block(c, final2);
4506+
if (!compiler_push_fblock(c, FINALLY_END, final2, NULL))
4507+
return 0;
4508+
4509+
ADDOP(c, WITH_EXCEPT_START);
4510+
compiler_with_except_finish(c);
4511+
compiler_pop_fblock(c, FINALLY_END, final2);
4512+
4513+
compiler_use_next_block(c, exit);
44604514
return 1;
44614515
}
44624516

Python/opcode_targets.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ static void *opcode_targets[256] = {
4747
&&_unknown_opcode,
4848
&&_unknown_opcode,
4949
&&_unknown_opcode,
50-
&&_unknown_opcode,
50+
&&TARGET_RERAISE,
5151
&&_unknown_opcode,
5252
&&TARGET_GET_AITER,
5353
&&TARGET_GET_ANEXT,
5454
&&TARGET_BEFORE_ASYNC_WITH,
5555
&&TARGET_BEGIN_FINALLY,
56-
&&_unknown_opcode,
56+
&&TARGET_WITH_EXCEPT_START,
5757
&&TARGET_INPLACE_ADD,
5858
&&TARGET_INPLACE_SUBTRACT,
5959
&&TARGET_INPLACE_MULTIPLY,

0 commit comments

Comments
 (0)