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:
Mark Shannon 2021-05-07 15:19:19 +01:00 committed by GitHub
parent b32c8e9795
commit adcd220556
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 6614 additions and 5687 deletions

View file

@ -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)) {