mirror of
https://github.com/python/cpython.git
synced 2025-09-15 05:06:12 +00:00
bpo-40222: "Zero cost" exception handling (GH-25729)
"Zero cost" exception handling. * Uses a lookup table to determine how to handle exceptions. * Removes SETUP_FINALLY and POP_TOP block instructions, eliminating (most of) the runtime overhead of try statements. * Reduces the size of the frame object by about 60%.
This commit is contained in:
parent
b32c8e9795
commit
adcd220556
30 changed files with 6614 additions and 5687 deletions
484
Python/compile.c
484
Python/compile.c
|
@ -67,6 +67,14 @@
|
|||
*/
|
||||
#define MAX_ALLOWED_STACK_USE (STACK_USE_GUIDELINE * 100)
|
||||
|
||||
|
||||
/* Pseudo-instructions used in the compiler,
|
||||
* but turned into NOPs by the assembler. */
|
||||
#define SETUP_FINALLY 255
|
||||
#define SETUP_CLEANUP 254
|
||||
#define SETUP_WITH 253
|
||||
#define POP_BLOCK 252
|
||||
|
||||
#define IS_TOP_LEVEL_AWAIT(c) ( \
|
||||
(c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT) \
|
||||
&& (c->u->u_ste->ste_type == ModuleBlock))
|
||||
|
@ -74,10 +82,23 @@
|
|||
struct instr {
|
||||
unsigned char i_opcode;
|
||||
int i_oparg;
|
||||
struct basicblock_ *i_target; /* target block (if jump instruction) */
|
||||
/* target block (if jump instruction) */
|
||||
struct basicblock_ *i_target;
|
||||
/* target block when exception is raised, should not be set by front-end. */
|
||||
struct basicblock_ *i_except;
|
||||
int i_lineno;
|
||||
};
|
||||
|
||||
typedef struct excepthandler {
|
||||
struct instr *setup;
|
||||
int offset;
|
||||
} ExceptHandler;
|
||||
|
||||
typedef struct exceptstack {
|
||||
struct basicblock_ *handlers[CO_MAXBLOCKS+1];
|
||||
int depth;
|
||||
} ExceptStack;
|
||||
|
||||
#define LOG_BITS_PER_INT 5
|
||||
#define MASK_LOW_LOG_BITS 31
|
||||
|
||||
|
@ -101,7 +122,7 @@ is_relative_jump(struct instr *i)
|
|||
static inline int
|
||||
is_jump(struct instr *i)
|
||||
{
|
||||
return is_bit_set_in_table(_PyOpcode_Jump, i->i_opcode);
|
||||
return i->i_opcode >= SETUP_WITH || is_bit_set_in_table(_PyOpcode_Jump, i->i_opcode);
|
||||
}
|
||||
|
||||
typedef struct basicblock_ {
|
||||
|
@ -124,12 +145,18 @@ typedef struct basicblock_ {
|
|||
int b_predecessors;
|
||||
/* Basic block has no fall through (it ends with a return, raise or jump) */
|
||||
unsigned b_nofallthrough : 1;
|
||||
/* Basic block is an exception handler that preserves lasti */
|
||||
unsigned b_preserve_lasti : 1;
|
||||
/* Used by compiler passes to mark whether they have visited a basic block. */
|
||||
unsigned b_visited : 1;
|
||||
/* Basic block exits scope (it ends with a return or raise) */
|
||||
unsigned b_exit : 1;
|
||||
/* depth of stack upon entry of block, computed by stackdepth() */
|
||||
int b_startdepth;
|
||||
/* instruction offset for block, computed by assemble_jump_offsets() */
|
||||
int b_offset;
|
||||
/* Exception stack at start of block, used by assembler to create the exception handling table */
|
||||
ExceptStack *b_exceptstack;
|
||||
} basicblock;
|
||||
|
||||
/* fblockinfo tracks the current frame block.
|
||||
|
@ -305,6 +332,8 @@ static int compiler_match(struct compiler *, stmt_ty);
|
|||
static int compiler_pattern_subpattern(struct compiler *, pattern_ty,
|
||||
pattern_context *);
|
||||
|
||||
static void clean_basic_block(basicblock *bb);
|
||||
|
||||
static PyCodeObject *assemble(struct compiler *, int addNone);
|
||||
static PyObject *__doc__, *__annotations__;
|
||||
|
||||
|
@ -1029,11 +1058,6 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
case INPLACE_OR:
|
||||
return -1;
|
||||
|
||||
case SETUP_WITH:
|
||||
/* 1 in the normal flow.
|
||||
* Restore the stack position and push 6 values before jumping to
|
||||
* the handler if an exception be raised. */
|
||||
return jump ? 6 : 1;
|
||||
case RETURN_VALUE:
|
||||
return -1;
|
||||
case IMPORT_STAR:
|
||||
|
@ -1048,6 +1072,8 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
return 0;
|
||||
case POP_EXCEPT:
|
||||
return -3;
|
||||
case POP_EXCEPT_AND_RERAISE:
|
||||
return -7;
|
||||
|
||||
case STORE_NAME:
|
||||
return -1;
|
||||
|
@ -1111,14 +1137,26 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
case LOAD_GLOBAL:
|
||||
return 1;
|
||||
|
||||
/* Exception handling */
|
||||
/* Exception handling pseudo-instructions */
|
||||
case SETUP_FINALLY:
|
||||
/* 0 in the normal flow.
|
||||
* Restore the stack position and push 6 values before jumping to
|
||||
* Restore the stack position and push 3 values before jumping to
|
||||
* the handler if an exception be raised. */
|
||||
return jump ? 6 : 0;
|
||||
return jump ? 3 : 0;
|
||||
case SETUP_CLEANUP:
|
||||
/* As SETUP_FINALLY, but pushes lasti as well */
|
||||
return jump ? 4 : 0;
|
||||
case SETUP_WITH:
|
||||
/* 0 in the normal flow.
|
||||
* Restore the stack position to the position before the result
|
||||
* of __(a)enter__ and push 4 values before jumping to the handler
|
||||
* if an exception be raised. */
|
||||
return jump ? -1 + 4 : 0;
|
||||
|
||||
case RERAISE:
|
||||
return -3;
|
||||
case PUSH_EXC_INFO:
|
||||
return 3;
|
||||
|
||||
case WITH_EXCEPT_START:
|
||||
return 1;
|
||||
|
@ -1165,13 +1203,9 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
/* Iterators and generators */
|
||||
case GET_AWAITABLE:
|
||||
return 0;
|
||||
case SETUP_ASYNC_WITH:
|
||||
/* 0 in the normal flow.
|
||||
* Restore the stack position to the position before the result
|
||||
* of __aenter__ and push 6 values before jumping to the handler
|
||||
* if an exception be raised. */
|
||||
return jump ? -1 + 6 : 0;
|
||||
|
||||
case BEFORE_ASYNC_WITH:
|
||||
case BEFORE_WITH:
|
||||
return 1;
|
||||
case GET_AITER:
|
||||
return 0;
|
||||
|
@ -1180,7 +1214,7 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
case GET_YIELD_FROM_ITER:
|
||||
return 0;
|
||||
case END_ASYNC_FOR:
|
||||
return -7;
|
||||
return -4;
|
||||
case FORMAT_VALUE:
|
||||
/* If there's a fmt_spec on the stack, we go from 2->1,
|
||||
else 1->1. */
|
||||
|
@ -1238,7 +1272,7 @@ compiler_addop_line(struct compiler *c, int opcode, int line)
|
|||
basicblock *b;
|
||||
struct instr *i;
|
||||
int off;
|
||||
assert(!HAS_ARG(opcode));
|
||||
assert(!HAS_ARG(opcode) || IS_ARTIFICIAL(opcode));
|
||||
off = compiler_next_instr(c->u->u_curblock);
|
||||
if (off < 0)
|
||||
return 0;
|
||||
|
@ -1815,6 +1849,7 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
|
|||
if (preserve_tos) {
|
||||
ADDOP(c, ROT_FOUR);
|
||||
}
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_EXCEPT);
|
||||
return 1;
|
||||
|
||||
|
@ -1845,6 +1880,7 @@ compiler_unwind_fblock(struct compiler *c, struct fblockinfo *info,
|
|||
if (preserve_tos) {
|
||||
ADDOP(c, ROT_FOUR);
|
||||
}
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_EXCEPT);
|
||||
if (info->fb_datum) {
|
||||
ADDOP_LOAD_CONST(c, Py_None);
|
||||
|
@ -3072,14 +3108,15 @@ compiler_continue(struct compiler *c)
|
|||
static int
|
||||
compiler_try_finally(struct compiler *c, stmt_ty s)
|
||||
{
|
||||
basicblock *body, *end, *exit;
|
||||
basicblock *body, *end, *exit, *cleanup;
|
||||
|
||||
body = compiler_new_block(c);
|
||||
end = compiler_new_block(c);
|
||||
exit = compiler_new_block(c);
|
||||
if (body == NULL || end == NULL || exit == NULL)
|
||||
cleanup = compiler_new_block(c);
|
||||
if (body == NULL || end == NULL || exit == NULL || cleanup == NULL) {
|
||||
return 0;
|
||||
|
||||
}
|
||||
/* `try` block */
|
||||
ADDOP_JUMP(c, SETUP_FINALLY, end);
|
||||
compiler_use_next_block(c, body);
|
||||
|
@ -3098,11 +3135,17 @@ compiler_try_finally(struct compiler *c, stmt_ty s)
|
|||
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, exit);
|
||||
/* `finally` block */
|
||||
compiler_use_next_block(c, end);
|
||||
|
||||
c->u->u_lineno = -1;
|
||||
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
|
||||
ADDOP(c, PUSH_EXC_INFO);
|
||||
if (!compiler_push_fblock(c, FINALLY_END, end, NULL, NULL))
|
||||
return 0;
|
||||
VISIT_SEQ(c, stmt, s->v.Try.finalbody);
|
||||
compiler_pop_fblock(c, FINALLY_END, end);
|
||||
ADDOP_I(c, RERAISE, 0);
|
||||
compiler_use_next_block(c, cleanup);
|
||||
ADDOP(c, POP_EXCEPT_AND_RERAISE);
|
||||
compiler_use_next_block(c, exit);
|
||||
return 1;
|
||||
}
|
||||
|
@ -3140,14 +3183,15 @@ compiler_try_finally(struct compiler *c, stmt_ty s)
|
|||
static int
|
||||
compiler_try_except(struct compiler *c, stmt_ty s)
|
||||
{
|
||||
basicblock *body, *orelse, *except, *end;
|
||||
basicblock *body, *orelse, *except, *end, *cleanup;
|
||||
Py_ssize_t i, n;
|
||||
|
||||
body = compiler_new_block(c);
|
||||
except = compiler_new_block(c);
|
||||
orelse = compiler_new_block(c);
|
||||
end = compiler_new_block(c);
|
||||
if (body == NULL || except == NULL || orelse == NULL || end == NULL)
|
||||
cleanup = compiler_new_block(c);
|
||||
if (body == NULL || except == NULL || orelse == NULL || end == NULL || cleanup == NULL)
|
||||
return 0;
|
||||
ADDOP_JUMP(c, SETUP_FINALLY, except);
|
||||
compiler_use_next_block(c, body);
|
||||
|
@ -3159,15 +3203,20 @@ compiler_try_except(struct compiler *c, stmt_ty s)
|
|||
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, orelse);
|
||||
n = asdl_seq_LEN(s->v.Try.handlers);
|
||||
compiler_use_next_block(c, except);
|
||||
|
||||
c->u->u_lineno = -1;
|
||||
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
|
||||
ADDOP(c, PUSH_EXC_INFO);
|
||||
/* Runtime will push a block here, so we need to account for that */
|
||||
if (!compiler_push_fblock(c, EXCEPTION_HANDLER, NULL, NULL, NULL))
|
||||
return 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
excepthandler_ty handler = (excepthandler_ty)asdl_seq_GET(
|
||||
s->v.Try.handlers, i);
|
||||
if (!handler->v.ExceptHandler.type && i < n-1)
|
||||
return compiler_error(c, "default 'except:' must be last");
|
||||
SET_LOC(c, handler);
|
||||
if (!handler->v.ExceptHandler.type && i < n-1) {
|
||||
return compiler_error(c, "default 'except:' must be last");
|
||||
}
|
||||
except = compiler_new_block(c);
|
||||
if (except == NULL)
|
||||
return 0;
|
||||
|
@ -3202,7 +3251,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
|
|||
*/
|
||||
|
||||
/* second try: */
|
||||
ADDOP_JUMP(c, SETUP_FINALLY, cleanup_end);
|
||||
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup_end);
|
||||
compiler_use_next_block(c, cleanup_body);
|
||||
if (!compiler_push_fblock(c, HANDLER_CLEANUP, cleanup_body, NULL, handler->v.ExceptHandler.name))
|
||||
return 0;
|
||||
|
@ -3211,6 +3260,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
|
|||
VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body);
|
||||
compiler_pop_fblock(c, HANDLER_CLEANUP, cleanup_body);
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_EXCEPT);
|
||||
/* name = None; del name; # Mark as artificial */
|
||||
c->u->u_lineno = -1;
|
||||
|
@ -3224,6 +3274,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
|
|||
|
||||
/* name = None; del name; # Mark as artificial */
|
||||
c->u->u_lineno = -1;
|
||||
|
||||
ADDOP_LOAD_CONST(c, Py_None);
|
||||
compiler_nameop(c, handler->v.ExceptHandler.name, Store);
|
||||
compiler_nameop(c, handler->v.ExceptHandler.name, Del);
|
||||
|
@ -3246,15 +3297,18 @@ compiler_try_except(struct compiler *c, stmt_ty s)
|
|||
compiler_pop_fblock(c, HANDLER_CLEANUP, cleanup_body);
|
||||
/* name = None; del name; # Mark as artificial */
|
||||
c->u->u_lineno = -1;
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_EXCEPT);
|
||||
ADDOP_JUMP(c, JUMP_FORWARD, end);
|
||||
}
|
||||
compiler_use_next_block(c, except);
|
||||
}
|
||||
compiler_pop_fblock(c, EXCEPTION_HANDLER, NULL);
|
||||
/* Mark as artificial */
|
||||
c->u->u_lineno = -1;
|
||||
compiler_pop_fblock(c, EXCEPTION_HANDLER, NULL);
|
||||
ADDOP_I(c, RERAISE, 0);
|
||||
compiler_use_next_block(c, cleanup);
|
||||
ADDOP(c, POP_EXCEPT_AND_RERAISE);
|
||||
compiler_use_next_block(c, orelse);
|
||||
VISIT_SEQ(c, stmt, s->v.Try.orelse);
|
||||
compiler_use_next_block(c, end);
|
||||
|
@ -4764,6 +4818,8 @@ compiler_async_comprehension_generator(struct compiler *c,
|
|||
compiler_pop_fblock(c, ASYNC_COMPREHENSION_GENERATOR, start);
|
||||
|
||||
compiler_use_next_block(c, except);
|
||||
//c->u->u_lineno = -1;
|
||||
|
||||
ADDOP(c, END_ASYNC_FOR);
|
||||
|
||||
return 1;
|
||||
|
@ -4944,20 +5000,24 @@ compiler_visit_keyword(struct compiler *c, keyword_ty k)
|
|||
*/
|
||||
|
||||
static int
|
||||
compiler_with_except_finish(struct compiler *c) {
|
||||
compiler_with_except_finish(struct compiler *c, basicblock * cleanup) {
|
||||
basicblock *exit;
|
||||
exit = compiler_new_block(c);
|
||||
if (exit == NULL)
|
||||
return 0;
|
||||
ADDOP_JUMP(c, POP_JUMP_IF_TRUE, exit);
|
||||
NEXT_BLOCK(c);
|
||||
ADDOP_I(c, RERAISE, 1);
|
||||
ADDOP_I(c, RERAISE, 4);
|
||||
compiler_use_next_block(c, cleanup);
|
||||
ADDOP(c, POP_EXCEPT_AND_RERAISE);
|
||||
compiler_use_next_block(c, exit);
|
||||
ADDOP(c, POP_TOP);
|
||||
ADDOP(c, POP_TOP);
|
||||
ADDOP(c, POP_TOP);
|
||||
ADDOP(c, POP_BLOCK);
|
||||
ADDOP(c, POP_EXCEPT);
|
||||
ADDOP(c, POP_TOP);
|
||||
ADDOP(c, POP_TOP);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -4988,7 +5048,7 @@ compiler_with_except_finish(struct compiler *c) {
|
|||
static int
|
||||
compiler_async_with(struct compiler *c, stmt_ty s, int pos)
|
||||
{
|
||||
basicblock *block, *final, *exit;
|
||||
basicblock *block, *final, *exit, *cleanup;
|
||||
withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos);
|
||||
|
||||
assert(s->kind == AsyncWith_kind);
|
||||
|
@ -5001,7 +5061,8 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
|
|||
block = compiler_new_block(c);
|
||||
final = compiler_new_block(c);
|
||||
exit = compiler_new_block(c);
|
||||
if (!block || !final || !exit)
|
||||
cleanup = compiler_new_block(c);
|
||||
if (!block || !final || !exit || !cleanup)
|
||||
return 0;
|
||||
|
||||
/* Evaluate EXPR */
|
||||
|
@ -5012,9 +5073,9 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
|
|||
ADDOP_LOAD_CONST(c, Py_None);
|
||||
ADDOP(c, YIELD_FROM);
|
||||
|
||||
ADDOP_JUMP(c, SETUP_ASYNC_WITH, final);
|
||||
ADDOP_JUMP(c, SETUP_WITH, final);
|
||||
|
||||
/* SETUP_ASYNC_WITH pushes a finally block. */
|
||||
/* SETUP_WITH pushes a finally block. */
|
||||
compiler_use_next_block(c, block);
|
||||
if (!compiler_push_fblock(c, ASYNC_WITH, block, final, s)) {
|
||||
return 0;
|
||||
|
@ -5055,13 +5116,17 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
|
|||
|
||||
/* For exceptional outcome: */
|
||||
compiler_use_next_block(c, final);
|
||||
c->u->u_lineno = -1;
|
||||
|
||||
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
|
||||
ADDOP(c, PUSH_EXC_INFO);
|
||||
ADDOP(c, WITH_EXCEPT_START);
|
||||
ADDOP(c, GET_AWAITABLE);
|
||||
ADDOP_LOAD_CONST(c, Py_None);
|
||||
ADDOP(c, YIELD_FROM);
|
||||
compiler_with_except_finish(c);
|
||||
compiler_with_except_finish(c, cleanup);
|
||||
|
||||
compiler_use_next_block(c, exit);
|
||||
compiler_use_next_block(c, exit);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -5090,7 +5155,7 @@ compiler_use_next_block(c, exit);
|
|||
static int
|
||||
compiler_with(struct compiler *c, stmt_ty s, int pos)
|
||||
{
|
||||
basicblock *block, *final, *exit;
|
||||
basicblock *block, *final, *exit, *cleanup;
|
||||
withitem_ty item = asdl_seq_GET(s->v.With.items, pos);
|
||||
|
||||
assert(s->kind == With_kind);
|
||||
|
@ -5098,12 +5163,14 @@ compiler_with(struct compiler *c, stmt_ty s, int pos)
|
|||
block = compiler_new_block(c);
|
||||
final = compiler_new_block(c);
|
||||
exit = compiler_new_block(c);
|
||||
if (!block || !final || !exit)
|
||||
cleanup = compiler_new_block(c);
|
||||
if (!block || !final || !exit || !cleanup)
|
||||
return 0;
|
||||
|
||||
/* Evaluate EXPR */
|
||||
VISIT(c, expr, item->context_expr);
|
||||
/* Will push bound __exit__ */
|
||||
ADDOP(c, BEFORE_WITH);
|
||||
ADDOP_JUMP(c, SETUP_WITH, final);
|
||||
|
||||
/* SETUP_WITH pushes a finally block. */
|
||||
|
@ -5146,8 +5213,12 @@ compiler_with(struct compiler *c, stmt_ty s, int pos)
|
|||
|
||||
/* For exceptional outcome: */
|
||||
compiler_use_next_block(c, final);
|
||||
c->u->u_lineno = -1;
|
||||
|
||||
ADDOP_JUMP(c, SETUP_CLEANUP, cleanup);
|
||||
ADDOP(c, PUSH_EXC_INFO);
|
||||
ADDOP(c, WITH_EXCEPT_START);
|
||||
compiler_with_except_finish(c);
|
||||
compiler_with_except_finish(c, cleanup);
|
||||
|
||||
compiler_use_next_block(c, exit);
|
||||
return 1;
|
||||
|
@ -6383,11 +6454,13 @@ compiler_match(struct compiler *c, stmt_ty s)
|
|||
*/
|
||||
|
||||
struct assembler {
|
||||
PyObject *a_bytecode; /* string containing bytecode */
|
||||
PyObject *a_bytecode; /* bytes containing bytecode */
|
||||
int a_offset; /* offset into bytecode */
|
||||
int a_nblocks; /* number of reachable blocks */
|
||||
PyObject *a_lnotab; /* string containing lnotab */
|
||||
PyObject *a_lnotab; /* bytes containing lnotab */
|
||||
int a_lnotab_off; /* offset into lnotab */
|
||||
PyObject *a_except_table; /* bytes containing exception table */
|
||||
int a_except_table_off; /* offset into exception table */
|
||||
int a_prevlineno; /* lineno of last emitted line in line table */
|
||||
int a_lineno; /* lineno of last emitted instruction */
|
||||
int a_lineno_start; /* bytecode start offset of current lineno */
|
||||
|
@ -6466,7 +6539,8 @@ stackdepth(struct compiler *c)
|
|||
instr->i_opcode == JUMP_FORWARD ||
|
||||
instr->i_opcode == RETURN_VALUE ||
|
||||
instr->i_opcode == RAISE_VARARGS ||
|
||||
instr->i_opcode == RERAISE)
|
||||
instr->i_opcode == RERAISE ||
|
||||
instr->i_opcode == POP_EXCEPT_AND_RERAISE)
|
||||
{
|
||||
/* remaining code is dead */
|
||||
next = NULL;
|
||||
|
@ -6488,6 +6562,7 @@ assemble_init(struct assembler *a, int nblocks, int firstlineno)
|
|||
memset(a, 0, sizeof(struct assembler));
|
||||
a->a_prevlineno = a->a_lineno = firstlineno;
|
||||
a->a_lnotab = NULL;
|
||||
a->a_except_table = NULL;
|
||||
a->a_bytecode = PyBytes_FromStringAndSize(NULL, DEFAULT_CODE_SIZE);
|
||||
if (a->a_bytecode == NULL) {
|
||||
goto error;
|
||||
|
@ -6496,6 +6571,10 @@ assemble_init(struct assembler *a, int nblocks, int firstlineno)
|
|||
if (a->a_lnotab == NULL) {
|
||||
goto error;
|
||||
}
|
||||
a->a_except_table = PyBytes_FromStringAndSize(NULL, DEFAULT_LNOTAB_SIZE);
|
||||
if (a->a_except_table == NULL) {
|
||||
goto error;
|
||||
}
|
||||
if ((size_t)nblocks > SIZE_MAX / sizeof(basicblock *)) {
|
||||
PyErr_NoMemory();
|
||||
goto error;
|
||||
|
@ -6504,6 +6583,7 @@ assemble_init(struct assembler *a, int nblocks, int firstlineno)
|
|||
error:
|
||||
Py_XDECREF(a->a_bytecode);
|
||||
Py_XDECREF(a->a_lnotab);
|
||||
Py_XDECREF(a->a_except_table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -6512,6 +6592,7 @@ assemble_free(struct assembler *a)
|
|||
{
|
||||
Py_XDECREF(a->a_bytecode);
|
||||
Py_XDECREF(a->a_lnotab);
|
||||
Py_XDECREF(a->a_except_table);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -6541,6 +6622,253 @@ assemble_emit_linetable_pair(struct assembler *a, int bdelta, int ldelta)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
is_block_push(struct instr *instr)
|
||||
{
|
||||
int opcode = instr->i_opcode;
|
||||
return opcode == SETUP_FINALLY || opcode == SETUP_WITH || opcode == SETUP_CLEANUP;
|
||||
}
|
||||
|
||||
static basicblock *
|
||||
push_except_block(ExceptStack *stack, struct instr *setup) {
|
||||
assert(is_block_push(setup));
|
||||
int opcode = setup->i_opcode;
|
||||
basicblock * target = setup->i_target;
|
||||
if (opcode == SETUP_WITH || opcode == SETUP_CLEANUP) {
|
||||
target->b_preserve_lasti = 1;
|
||||
}
|
||||
stack->handlers[++stack->depth] = target;
|
||||
return target;
|
||||
}
|
||||
|
||||
static basicblock *
|
||||
pop_except_block(ExceptStack *stack) {
|
||||
assert(stack->depth > 0);
|
||||
return stack->handlers[--stack->depth];
|
||||
}
|
||||
|
||||
static basicblock *
|
||||
except_stack_top(ExceptStack *stack) {
|
||||
return stack->handlers[stack->depth];
|
||||
}
|
||||
|
||||
static ExceptStack *
|
||||
make_except_stack(void) {
|
||||
ExceptStack *new = PyMem_Malloc(sizeof(ExceptStack));
|
||||
if (new == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
new->depth = 0;
|
||||
new->handlers[0] = NULL;
|
||||
return new;
|
||||
}
|
||||
|
||||
static ExceptStack *
|
||||
copy_except_stack(ExceptStack *stack) {
|
||||
ExceptStack *copy = PyMem_Malloc(sizeof(ExceptStack));
|
||||
if (copy == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
memcpy(copy, stack, sizeof(ExceptStack));
|
||||
return copy;
|
||||
}
|
||||
|
||||
static int
|
||||
label_exception_targets(basicblock *entry) {
|
||||
int nblocks = 0;
|
||||
for (basicblock *b = entry; b != NULL; b = b->b_next) {
|
||||
b->b_visited = 0;
|
||||
nblocks++;
|
||||
}
|
||||
basicblock **todo_stack = PyMem_Malloc(sizeof(basicblock *)*nblocks);
|
||||
if (todo_stack == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return -1;
|
||||
}
|
||||
ExceptStack *except_stack = make_except_stack();
|
||||
if (except_stack == NULL) {
|
||||
PyMem_Free(todo_stack);
|
||||
PyErr_NoMemory();
|
||||
return -1;
|
||||
}
|
||||
except_stack->depth = 0;
|
||||
todo_stack[0] = entry;
|
||||
entry->b_visited = 1;
|
||||
entry->b_exceptstack = except_stack;
|
||||
basicblock **todo = &todo_stack[1];
|
||||
basicblock *handler = NULL;
|
||||
while (todo > todo_stack) {
|
||||
todo--;
|
||||
basicblock *b = todo[0];
|
||||
assert(b->b_visited == 1);
|
||||
except_stack = b->b_exceptstack;
|
||||
assert(except_stack != NULL);
|
||||
b->b_exceptstack = NULL;
|
||||
handler = except_stack_top(except_stack);
|
||||
for (int i = 0; i < b->b_iused; i++) {
|
||||
struct instr *instr = &b->b_instr[i];
|
||||
if (is_block_push(instr)) {
|
||||
if (!instr->i_target->b_visited) {
|
||||
ExceptStack *copy = copy_except_stack(except_stack);
|
||||
if (copy == NULL) {
|
||||
goto error;
|
||||
}
|
||||
instr->i_target->b_exceptstack = copy;
|
||||
todo[0] = instr->i_target;
|
||||
instr->i_target->b_visited = 1;
|
||||
todo++;
|
||||
}
|
||||
handler = push_except_block(except_stack, instr);
|
||||
}
|
||||
else if (instr->i_opcode == POP_BLOCK) {
|
||||
handler = pop_except_block(except_stack);
|
||||
}
|
||||
else if (is_jump(instr)) {
|
||||
instr->i_except = handler;
|
||||
assert(i == b->b_iused -1);
|
||||
if (!instr->i_target->b_visited) {
|
||||
if (b->b_nofallthrough == 0) {
|
||||
ExceptStack *copy = copy_except_stack(except_stack);
|
||||
if (copy == NULL) {
|
||||
goto error;
|
||||
}
|
||||
instr->i_target->b_exceptstack = copy;
|
||||
}
|
||||
else {
|
||||
instr->i_target->b_exceptstack = except_stack;
|
||||
except_stack = NULL;
|
||||
}
|
||||
todo[0] = instr->i_target;
|
||||
instr->i_target->b_visited = 1;
|
||||
todo++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
instr->i_except = handler;
|
||||
}
|
||||
}
|
||||
if (b->b_nofallthrough == 0 && !b->b_next->b_visited) {
|
||||
assert(except_stack != NULL);
|
||||
b->b_next->b_exceptstack = except_stack;
|
||||
todo[0] = b->b_next;
|
||||
b->b_next->b_visited = 1;
|
||||
todo++;
|
||||
}
|
||||
else if (except_stack != NULL) {
|
||||
PyMem_Free(except_stack);
|
||||
}
|
||||
}
|
||||
#ifdef Py_DEBUG
|
||||
for (basicblock *b = entry; b != NULL; b = b->b_next) {
|
||||
assert(b->b_exceptstack == NULL);
|
||||
}
|
||||
#endif
|
||||
PyMem_Free(todo_stack);
|
||||
return 0;
|
||||
error:
|
||||
PyMem_Free(todo_stack);
|
||||
PyMem_Free(except_stack);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
convert_exception_handlers_to_nops(basicblock *entry) {
|
||||
for (basicblock *b = entry; b != NULL; b = b->b_next) {
|
||||
for (int i = 0; i < b->b_iused; i++) {
|
||||
struct instr *instr = &b->b_instr[i];
|
||||
if (is_block_push(instr) || instr->i_opcode == POP_BLOCK) {
|
||||
instr->i_opcode = NOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_except_byte(struct assembler *a, int byte) {
|
||||
unsigned char *p = (unsigned char *) PyBytes_AS_STRING(a->a_except_table);
|
||||
p[a->a_except_table_off++] = byte;
|
||||
}
|
||||
|
||||
#define CONTINUATION_BIT 64
|
||||
|
||||
static void
|
||||
assemble_emit_exception_table_item(struct assembler *a, int value, int msb)
|
||||
{
|
||||
assert ((msb | 128) == 128);
|
||||
assert(value >= 0 && value < (1 << 30));
|
||||
if (value >= 1 << 24) {
|
||||
write_except_byte(a, (value >> 24) | CONTINUATION_BIT | msb);
|
||||
msb = 0;
|
||||
}
|
||||
if (value >= 1 << 18) {
|
||||
write_except_byte(a, ((value >> 18)&0x3f) | CONTINUATION_BIT | msb);
|
||||
msb = 0;
|
||||
}
|
||||
if (value >= 1 << 12) {
|
||||
write_except_byte(a, ((value >> 12)&0x3f) | CONTINUATION_BIT | msb);
|
||||
msb = 0;
|
||||
}
|
||||
if (value >= 1 << 6) {
|
||||
write_except_byte(a, ((value >> 6)&0x3f) | CONTINUATION_BIT | msb);
|
||||
msb = 0;
|
||||
}
|
||||
write_except_byte(a, (value&0x3f) | msb);
|
||||
}
|
||||
|
||||
/* See Objects/exception_table_notes.txt for details of layout */
|
||||
#define MAX_SIZE_OF_ENTRY 20
|
||||
|
||||
static int
|
||||
assemble_emit_exception_table_entry(struct assembler *a, int start, int end, basicblock *handler)
|
||||
{
|
||||
Py_ssize_t len = PyBytes_GET_SIZE(a->a_except_table);
|
||||
if (a->a_except_table_off + MAX_SIZE_OF_ENTRY >= len) {
|
||||
if (_PyBytes_Resize(&a->a_except_table, len * 2) < 0)
|
||||
return 0;
|
||||
}
|
||||
int size = end-start;
|
||||
assert(end > start);
|
||||
int target = handler->b_offset;
|
||||
int depth = handler->b_preserve_lasti ? handler->b_startdepth-4 : handler->b_startdepth-3;
|
||||
assert(depth >= 0);
|
||||
int depth_lasti = (depth<<1) | handler->b_preserve_lasti;
|
||||
assemble_emit_exception_table_item(a, start, (1<<7));
|
||||
assemble_emit_exception_table_item(a, size, 0);
|
||||
assemble_emit_exception_table_item(a, target, 0);
|
||||
assemble_emit_exception_table_item(a, depth_lasti, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
assemble_exception_table(struct assembler *a)
|
||||
{
|
||||
basicblock *b;
|
||||
int ioffset = 0;
|
||||
basicblock *handler = NULL;
|
||||
int start = -1;
|
||||
for (b = a->a_entry; b != NULL; b = b->b_next) {
|
||||
ioffset = b->b_offset;
|
||||
for (int i = 0; i < b->b_iused; i++) {
|
||||
struct instr *instr = &b->b_instr[i];
|
||||
if (instr->i_except != handler) {
|
||||
if (handler != NULL) {
|
||||
RETURN_IF_FALSE(assemble_emit_exception_table_entry(a, start, ioffset, handler));
|
||||
}
|
||||
start = ioffset;
|
||||
handler = instr->i_except;
|
||||
}
|
||||
ioffset += instrsize(instr->i_oparg);
|
||||
}
|
||||
}
|
||||
if (handler != NULL) {
|
||||
RETURN_IF_FALSE(assemble_emit_exception_table_entry(a, start, ioffset, handler));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Appends a range to the end of the line number table. See
|
||||
* Objects/lnotab_notes.txt for the description of the line number table. */
|
||||
|
||||
|
@ -6793,7 +7121,7 @@ merge_const_one(struct compiler *c, PyObject **obj)
|
|||
}
|
||||
|
||||
static PyCodeObject *
|
||||
makecode(struct compiler *c, struct assembler *a, PyObject *consts)
|
||||
makecode(struct compiler *c, struct assembler *a, PyObject *consts, int maxdepth)
|
||||
{
|
||||
PyCodeObject *co = NULL;
|
||||
PyObject *names = NULL;
|
||||
|
@ -6804,7 +7132,7 @@ makecode(struct compiler *c, struct assembler *a, PyObject *consts)
|
|||
Py_ssize_t nlocals;
|
||||
int nlocals_int;
|
||||
int flags;
|
||||
int posorkeywordargcount, posonlyargcount, kwonlyargcount, maxdepth;
|
||||
int posorkeywordargcount, posonlyargcount, kwonlyargcount;
|
||||
|
||||
names = dict_keys_inorder(c->u->u_names, 0);
|
||||
varnames = dict_keys_inorder(c->u->u_varnames, 0);
|
||||
|
@ -6846,23 +7174,11 @@ makecode(struct compiler *c, struct assembler *a, PyObject *consts)
|
|||
posonlyargcount = Py_SAFE_DOWNCAST(c->u->u_posonlyargcount, Py_ssize_t, int);
|
||||
posorkeywordargcount = Py_SAFE_DOWNCAST(c->u->u_argcount, Py_ssize_t, int);
|
||||
kwonlyargcount = Py_SAFE_DOWNCAST(c->u->u_kwonlyargcount, Py_ssize_t, int);
|
||||
maxdepth = stackdepth(c);
|
||||
if (maxdepth < 0) {
|
||||
Py_DECREF(consts);
|
||||
goto error;
|
||||
}
|
||||
if (maxdepth > MAX_ALLOWED_STACK_USE) {
|
||||
PyErr_Format(PyExc_SystemError,
|
||||
"excessive stack use: stack is %d deep",
|
||||
maxdepth);
|
||||
Py_DECREF(consts);
|
||||
goto error;
|
||||
}
|
||||
co = PyCode_NewWithPosOnlyArgs(posonlyargcount+posorkeywordargcount,
|
||||
posonlyargcount, kwonlyargcount, nlocals_int,
|
||||
maxdepth, flags, a->a_bytecode, consts, names,
|
||||
varnames, freevars, cellvars, c->c_filename,
|
||||
c->u->u_name, c->u->u_firstlineno, a->a_lnotab);
|
||||
c->u->u_name, c->u->u_firstlineno, a->a_lnotab, a->a_except_table);
|
||||
Py_DECREF(consts);
|
||||
error:
|
||||
Py_XDECREF(names);
|
||||
|
@ -7015,6 +7331,25 @@ assemble(struct compiler *c, int addNone)
|
|||
goto error;
|
||||
}
|
||||
|
||||
int maxdepth = stackdepth(c);
|
||||
if (maxdepth < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (maxdepth > MAX_ALLOWED_STACK_USE) {
|
||||
PyErr_Format(PyExc_SystemError,
|
||||
"excessive stack use: stack is %d deep",
|
||||
maxdepth);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (label_exception_targets(entryblock)) {
|
||||
goto error;
|
||||
}
|
||||
convert_exception_handlers_to_nops(entryblock);
|
||||
for (basicblock *b = a.a_entry; b != NULL; b = b->b_next) {
|
||||
clean_basic_block(b);
|
||||
}
|
||||
|
||||
/* Can't modify the bytecode after computing jump offsets. */
|
||||
assemble_jump_offsets(&a, c);
|
||||
|
||||
|
@ -7024,6 +7359,10 @@ assemble(struct compiler *c, int addNone)
|
|||
if (!assemble_emit(&a, &b->b_instr[j]))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!assemble_exception_table(&a)) {
|
||||
return 0;
|
||||
}
|
||||
if (!assemble_line_range(&a)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -7031,6 +7370,11 @@ assemble(struct compiler *c, int addNone)
|
|||
if (_PyBytes_Resize(&a.a_lnotab, a.a_lnotab_off) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (_PyBytes_Resize(&a.a_except_table, a.a_except_table_off) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!merge_const_one(c, &a.a_lnotab)) {
|
||||
goto error;
|
||||
}
|
||||
|
@ -7041,7 +7385,7 @@ assemble(struct compiler *c, int addNone)
|
|||
goto error;
|
||||
}
|
||||
|
||||
co = makecode(c, &a, consts);
|
||||
co = makecode(c, &a, consts, maxdepth);
|
||||
error:
|
||||
Py_XDECREF(consts);
|
||||
assemble_free(&a);
|
||||
|
@ -7417,9 +7761,10 @@ error:
|
|||
|
||||
|
||||
static void
|
||||
clean_basic_block(basicblock *bb, int prev_lineno) {
|
||||
clean_basic_block(basicblock *bb) {
|
||||
/* Remove NOPs when legal to do so. */
|
||||
int dest = 0;
|
||||
int prev_lineno = -1;
|
||||
for (int src = 0; src < bb->b_iused; src++) {
|
||||
int lineno = bb->b_instr[src].i_lineno;
|
||||
if (bb->b_instr[src].i_opcode == NOP) {
|
||||
|
@ -7451,7 +7796,6 @@ clean_basic_block(basicblock *bb, int prev_lineno) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (dest != src) {
|
||||
bb->b_instr[dest] = bb->b_instr[src];
|
||||
|
@ -7472,6 +7816,7 @@ normalize_basic_block(basicblock *bb) {
|
|||
case RETURN_VALUE:
|
||||
case RAISE_VARARGS:
|
||||
case RERAISE:
|
||||
case POP_EXCEPT_AND_RERAISE:
|
||||
bb->b_exit = 1;
|
||||
bb->b_nofallthrough = 1;
|
||||
break;
|
||||
|
@ -7588,9 +7933,9 @@ propogate_line_numbers(struct assembler *a) {
|
|||
if (is_jump(&b->b_instr[b->b_iused-1])) {
|
||||
switch (b->b_instr[b->b_iused-1].i_opcode) {
|
||||
/* Note: Only actual jumps, not exception handlers */
|
||||
case SETUP_ASYNC_WITH:
|
||||
case SETUP_WITH:
|
||||
case SETUP_FINALLY:
|
||||
case SETUP_CLEANUP:
|
||||
continue;
|
||||
}
|
||||
basicblock *target = b->b_instr[b->b_iused-1].i_target;
|
||||
|
@ -7619,7 +7964,7 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
|
|||
if (optimize_basic_block(c, b, consts)) {
|
||||
return -1;
|
||||
}
|
||||
clean_basic_block(b, -1);
|
||||
clean_basic_block(b);
|
||||
assert(b->b_predecessors == 0);
|
||||
}
|
||||
if (mark_reachable(a)) {
|
||||
|
@ -7632,16 +7977,10 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
|
|||
b->b_nofallthrough = 0;
|
||||
}
|
||||
}
|
||||
basicblock *pred = NULL;
|
||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||
int prev_lineno = -1;
|
||||
if (pred && pred->b_iused) {
|
||||
prev_lineno = pred->b_instr[pred->b_iused-1].i_lineno;
|
||||
}
|
||||
clean_basic_block(b, prev_lineno);
|
||||
pred = b->b_nofallthrough ? NULL : b;
|
||||
}
|
||||
eliminate_empty_basic_blocks(a->a_entry);
|
||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||
clean_basic_block(b);
|
||||
}
|
||||
/* Delete jump instructions made redundant by previous step. If a non-empty
|
||||
block ends with a jump instruction, check if the next non-empty block
|
||||
reached through normal flow control is the target of that jump. If it
|
||||
|
@ -7657,7 +7996,6 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
|
|||
assert(b->b_next->b_iused);
|
||||
b->b_nofallthrough = 0;
|
||||
b_last_instr->i_opcode = NOP;
|
||||
clean_basic_block(b, -1);
|
||||
maybe_empty_blocks = 1;
|
||||
}
|
||||
}
|
||||
|
@ -7691,12 +8029,13 @@ ensure_exits_have_lineno(struct compiler *c)
|
|||
/* Copy all exit blocks without line number that are targets of a jump.
|
||||
*/
|
||||
for (basicblock *b = c->u->u_blocks; b != NULL; b = b->b_list) {
|
||||
entry = b;
|
||||
if (b->b_iused > 0 && is_jump(&b->b_instr[b->b_iused-1])) {
|
||||
switch (b->b_instr[b->b_iused-1].i_opcode) {
|
||||
/* Note: Only actual jumps, not exception handlers */
|
||||
case SETUP_ASYNC_WITH:
|
||||
case SETUP_WITH:
|
||||
case SETUP_FINALLY:
|
||||
case SETUP_CLEANUP:
|
||||
continue;
|
||||
}
|
||||
basicblock *target = b->b_instr[b->b_iused-1].i_target;
|
||||
|
@ -7709,7 +8048,6 @@ ensure_exits_have_lineno(struct compiler *c)
|
|||
b->b_instr[b->b_iused-1].i_target = new_target;
|
||||
}
|
||||
}
|
||||
entry = b;
|
||||
}
|
||||
assert(entry != NULL);
|
||||
if (is_exit_without_lineno(entry)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue