Skip to content

Commit ece8d8f

Browse files
markshannonMark Shannon
authored andcommitted
Produce cleaner bytecode for 'with' and 'async with' by generating separate code for normal and exceptional paths.
1 parent e756f66 commit ece8d8f

File tree

6 files changed

+159
-59
lines changed

6 files changed

+159
-59
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)

Objects/frameobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
264264
PyTryBlock *b = &f->f_blockstack[f->f_iblock];
265265
delta = (f->f_stacktop - f->f_valuestack) - b->b_level;
266266
if (b->b_type == SETUP_FINALLY &&
267-
code[b->b_handler] == WITH_CLEANUP_START)
267+
code[b->b_handler] == WITH_EXCEPT_START)
268268
{
269269
/* Pop the exit function. */
270270
delta++;

Python/ceval.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,15 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
18441844
DISPATCH();
18451845
}
18461846

1847+
TARGET(RERAISE) {
1848+
PyObject *exc = POP();
1849+
PyObject *val = POP();
1850+
PyObject *tb = POP();
1851+
assert(PyExceptionClass_Check(exc));
1852+
PyErr_Restore(exc, val, tb);
1853+
goto exception_unwind;
1854+
}
1855+
18471856
TARGET(POP_FINALLY) {
18481857
/* If oparg is 0 at the top of the stack are 1 or 6 values:
18491858
Either:
@@ -2954,6 +2963,37 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
29542963
DISPATCH();
29552964
}
29562965

2966+
TARGET(WITH_EXCEPT_START) {
2967+
/* At the top of the stack are 7 values:
2968+
- (TOP, SECOND, THIRD) = exc_info()
2969+
- (FOURTH, FITH, SIXTH) = previous exception for EXCEPT_HANDLER
2970+
- SEVENTH: the context.__exit__ bound method
2971+
We call SEVENTH(TOP, SECOND, THIRD).
2972+
Then we push again the TOP exception and the __exit__
2973+
return value.
2974+
*/
2975+
PyObject *exit_func;
2976+
PyObject *exit_stack[3];
2977+
PyObject *exc, *val, *tb, *res;
2978+
2979+
exc = TOP();
2980+
val = SECOND();
2981+
tb = THIRD();
2982+
assert(exc != Py_None);
2983+
assert(!PyLong_Check(exc));
2984+
2985+
exit_func = PEEK(7);
2986+
exit_stack[0] = exc;
2987+
exit_stack[1] = val;
2988+
exit_stack[2] = tb;
2989+
res = _PyObject_FastCall(exit_func, exit_stack, 3);
2990+
if (res == NULL)
2991+
goto error;
2992+
2993+
PUSH(res);
2994+
DISPATCH();
2995+
}
2996+
29572997
TARGET(WITH_CLEANUP_START) {
29582998
/* At the top of the stack are 1 or 6 values indicating
29592999
how/why we entered the finally clause:

Python/compile.c

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,9 +1052,14 @@ stack_effect(int opcode, int oparg, int jump)
10521052
* This is the main reason of using this opcode instead of
10531053
* "LOAD_CONST None". */
10541054
return 6;
1055+
case RERAISE:
1056+
return -3;
10551057
case CALL_FINALLY:
10561058
return jump ? 1 : 0;
10571059

1060+
case WITH_EXCEPT_START:
1061+
return 1;
1062+
10581063
case LOAD_FAST:
10591064
return 1;
10601065
case STORE_FAST:
@@ -1490,6 +1495,15 @@ compiler_pop_fblock(struct compiler *c, enum fblocktype t, basicblock *b)
14901495
assert(u->u_fblock[u->u_nfblocks].fb_block == b);
14911496
}
14921497

1498+
static int
1499+
compiler_call_exit_with_nones(struct compiler *c) {
1500+
ADDOP_O(c, LOAD_CONST, Py_None, consts);
1501+
ADDOP(c, DUP_TOP);
1502+
ADDOP(c, DUP_TOP);
1503+
ADDOP_I(c, CALL_FUNCTION, 3);
1504+
return 1;
1505+
}
1506+
14931507
/* Unwind a frame block. If preserve_tos is true, the TOS before
14941508
* popping the blocks will be restored afterwards.
14951509
*/
@@ -1528,15 +1542,15 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
15281542
if (preserve_tos) {
15291543
ADDOP(c, ROT_TWO);
15301544
}
1531-
ADDOP(c, BEGIN_FINALLY);
1532-
ADDOP(c, WITH_CLEANUP_START);
1545+
if(!compiler_call_exit_with_nones(c)) {
1546+
return 0;
1547+
}
15331548
if (info->fb_type == ASYNC_WITH) {
15341549
ADDOP(c, GET_AWAITABLE);
15351550
ADDOP_O(c, LOAD_CONST, Py_None, consts);
15361551
ADDOP(c, YIELD_FROM);
15371552
}
1538-
ADDOP(c, WITH_CLEANUP_FINISH);
1539-
ADDOP_I(c, POP_FINALLY, 0);
1553+
ADDOP(c, POP_TOP);
15401554
return 1;
15411555

15421556
case HANDLER_CLEANUP:
@@ -4310,6 +4324,22 @@ expr_constant(expr_ty e)
43104324
return -1;
43114325
}
43124326

4327+
static int
4328+
compiler_with_except_finish(struct compiler *c) {
4329+
basicblock *exit;
4330+
exit = compiler_new_block(c);
4331+
if (exit == NULL)
4332+
return 0;
4333+
ADDOP_JABS(c, POP_JUMP_IF_TRUE, exit);
4334+
ADDOP(c, RERAISE);
4335+
compiler_use_next_block(c, exit);
4336+
ADDOP(c, POP_TOP);
4337+
ADDOP(c, POP_TOP);
4338+
ADDOP(c, POP_TOP);
4339+
ADDOP(c, POP_EXCEPT);
4340+
ADDOP(c, POP_TOP);
4341+
return 1;
4342+
}
43134343

43144344
/*
43154345
Implements the async with statement.
@@ -4338,14 +4368,16 @@ expr_constant(expr_ty e)
43384368
static int
43394369
compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43404370
{
4341-
basicblock *block, *finally;
4371+
basicblock *block, *final1, *final2, *exit;
43424372
withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos);
43434373

43444374
assert(s->kind == AsyncWith_kind);
43454375

43464376
block = compiler_new_block(c);
4347-
finally = compiler_new_block(c);
4348-
if (!block || !finally)
4377+
final1 = compiler_new_block(c);
4378+
final2 = compiler_new_block(c);
4379+
exit = compiler_new_block(c);
4380+
if (!block || !final1 || !final2 || !exit)
43494381
return 0;
43504382

43514383
/* Evaluate EXPR */
@@ -4356,11 +4388,11 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43564388
ADDOP_O(c, LOAD_CONST, Py_None, consts);
43574389
ADDOP(c, YIELD_FROM);
43584390

4359-
ADDOP_JREL(c, SETUP_ASYNC_WITH, finally);
4391+
ADDOP_JREL(c, SETUP_ASYNC_WITH, final2);
43604392

43614393
/* SETUP_ASYNC_WITH pushes a finally block. */
43624394
compiler_use_next_block(c, block);
4363-
if (!compiler_push_fblock(c, ASYNC_WITH, block, finally)) {
4395+
if (!compiler_push_fblock(c, ASYNC_WITH, block, final2)) {
43644396
return 0;
43654397
}
43664398

@@ -4381,74 +4413,88 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
43814413

43824414
/* End of try block; start the finally block */
43834415
ADDOP(c, POP_BLOCK);
4384-
ADDOP(c, BEGIN_FINALLY);
43854416
compiler_pop_fblock(c, ASYNC_WITH, block);
43864417

4387-
compiler_use_next_block(c, finally);
4388-
if (!compiler_push_fblock(c, FINALLY_END, finally, NULL))
4418+
compiler_use_next_block(c, final1);
4419+
if (!compiler_push_fblock(c, FINALLY_END, final1, NULL))
43894420
return 0;
43904421

4391-
/* Finally block starts; context.__exit__ is on the stack under
4392-
the exception or return information. Just issue our magic
4393-
opcode. */
4394-
ADDOP(c, WITH_CLEANUP_START);
4422+
/* `finally` block for successful outcome:
4423+
* call __exit__(None, None, None)
4424+
*/
4425+
if(!compiler_call_exit_with_nones(c))
4426+
return 0;
4427+
ADDOP(c, GET_AWAITABLE);
4428+
ADDOP_O(c, LOAD_CONST, Py_None, consts);
4429+
ADDOP(c, YIELD_FROM);
4430+
4431+
ADDOP(c, POP_TOP);
4432+
4433+
compiler_pop_fblock(c, FINALLY_END, final1);
4434+
ADDOP_JABS(c, JUMP_ABSOLUTE, exit);
4435+
4436+
/* `finally` block for exceptional outcome */
4437+
compiler_use_next_block(c, final2);
4438+
if (!compiler_push_fblock(c, FINALLY_END, final2, NULL))
4439+
return 0;
43954440

4441+
ADDOP(c, WITH_EXCEPT_START);
43964442
ADDOP(c, GET_AWAITABLE);
43974443
ADDOP_O(c, LOAD_CONST, Py_None, consts);
43984444
ADDOP(c, YIELD_FROM);
4445+
compiler_with_except_finish(c);
43994446

4400-
ADDOP(c, WITH_CLEANUP_FINISH);
4447+
compiler_pop_fblock(c, FINALLY_END, final2);
44014448

4402-
/* Finally block ends. */
4403-
ADDOP(c, END_FINALLY);
4404-
compiler_pop_fblock(c, FINALLY_END, finally);
4449+
compiler_use_next_block(c, exit);
44054450
return 1;
44064451
}
44074452

44084453

44094454
/*
44104455
Implements the with statement from PEP 343.
4411-
4412-
The semantics outlined in that PEP are as follows:
4413-
44144456
with EXPR as VAR:
44154457
BLOCK
4416-
4417-
It is implemented roughly as:
4418-
4419-
context = EXPR
4420-
exit = context.__exit__ # not calling it
4421-
value = context.__enter__()
4422-
try:
4423-
VAR = value # if VAR present in the syntax
4424-
BLOCK
4425-
finally:
4426-
if an exception was raised:
4427-
exc = copy of (exception, instance, traceback)
4428-
else:
4429-
exc = (None, None, None)
4430-
exit(*exc)
4458+
is implemented as:
4459+
<code for EXPR>
4460+
SETUP_WITH E
4461+
<code to store to VAR> or POP_TOP
4462+
<code for BLOCK>
4463+
LOAD_CONST (None, None, None)
4464+
CALL_FUNCTION_EX 0
4465+
JUMP_FORWARD EXIT
4466+
E: WITH_EXCEPT_START (calls EXPR.__exit__)
4467+
POP_JUMP_IF_TRUE T:
4468+
RERAISE
4469+
T: POP_TOP * 3 (remove exception from stack)
4470+
POP_EXCEPT
4471+
POP_TOP
4472+
EXIT:
44314473
*/
4474+
44324475
static int
44334476
compiler_with(struct compiler *c, stmt_ty s, int pos)
44344477
{
4435-
basicblock *block, *finally;
4478+
basicblock *block, *final1, *final2, *exit;
44364479
withitem_ty item = asdl_seq_GET(s->v.With.items, pos);
44374480

44384481
assert(s->kind == With_kind);
44394482

44404483
block = compiler_new_block(c);
4441-
finally = compiler_new_block(c);
4442-
if (!block || !finally)
4484+
final1 = compiler_new_block(c);
4485+
final2 = compiler_new_block(c);
4486+
exit = compiler_new_block(c);
4487+
if (!block || !final1 || !final2 || !exit)
44434488
return 0;
44444489

44454490
/* Evaluate EXPR */
44464491
VISIT(c, expr, item->context_expr);
4447-
ADDOP_JREL(c, SETUP_WITH, finally);
4492+
/* Will push bound __exit__ */
4493+
ADDOP_JREL(c, SETUP_WITH, final2);
44484494

4449-
/* SETUP_WITH pushes a finally block. */
4495+
/* SETUP_ASYNC_WITH pushes a finally block. */
44504496
compiler_use_next_block(c, block);
4451-
if (!compiler_push_fblock(c, WITH, block, finally)) {
4497+
if (!compiler_push_fblock(c, WITH, block, final2)) {
44524498
return 0;
44534499
}
44544500

@@ -4467,24 +4513,33 @@ compiler_with(struct compiler *c, stmt_ty s, int pos)
44674513
else if (!compiler_with(c, s, pos))
44684514
return 0;
44694515

4470-
/* End of try block; start the finally block */
4516+
/* End of try block; start the finally blocks */
44714517
ADDOP(c, POP_BLOCK);
4472-
ADDOP(c, BEGIN_FINALLY);
44734518
compiler_pop_fblock(c, WITH, block);
44744519

4475-
compiler_use_next_block(c, finally);
4476-
if (!compiler_push_fblock(c, FINALLY_END, finally, NULL))
4520+
/* `finally` block for successful outcome:
4521+
* call __exit__(None, None, None)
4522+
*/
4523+
compiler_use_next_block(c, final1);
4524+
if (!compiler_push_fblock(c, FINALLY_END, final1, NULL))
44774525
return 0;
44784526

4479-
/* Finally block starts; context.__exit__ is on the stack under
4480-
the exception or return information. Just issue our magic
4481-
opcode. */
4482-
ADDOP(c, WITH_CLEANUP_START);
4483-
ADDOP(c, WITH_CLEANUP_FINISH);
4527+
if(!compiler_call_exit_with_nones(c))
4528+
return 0;
4529+
ADDOP(c, POP_TOP);
4530+
compiler_pop_fblock(c, FINALLY_END, final1);
4531+
ADDOP_JREL(c, JUMP_FORWARD, exit);
44844532

4485-
/* Finally block ends. */
4486-
ADDOP(c, END_FINALLY);
4487-
compiler_pop_fblock(c, FINALLY_END, finally);
4533+
/* `finally` block for exceptional outcome */
4534+
compiler_use_next_block(c, final2);
4535+
if (!compiler_push_fblock(c, FINALLY_END, final2, NULL))
4536+
return 0;
4537+
4538+
ADDOP(c, WITH_EXCEPT_START);
4539+
compiler_with_except_finish(c);
4540+
compiler_pop_fblock(c, FINALLY_END, final2);
4541+
4542+
compiler_use_next_block(c, exit);
44884543
return 1;
44894544
}
44904545

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)