bpo-42349: Compiler clean up. More yak-shaving for PEP 626. (GH-23267)

Make sure that CFG from compiler front-end is correct. Be a bit more aggressive in the compiler back-end.
This commit is contained in:
Mark Shannon 2020-11-17 19:30:14 +00:00 committed by GitHub
parent fa96608513
commit 266b462238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 4765 additions and 4669 deletions

View File

@ -752,6 +752,30 @@ if 1:
self.assertEqual(None, opcodes[0].argval) self.assertEqual(None, opcodes[0].argval)
self.assertEqual('RETURN_VALUE', opcodes[1].opname) self.assertEqual('RETURN_VALUE', opcodes[1].opname)
def test_consts_in_conditionals(self):
def and_true(x):
return True and x
def and_false(x):
return False and x
def or_true(x):
return True or x
def or_false(x):
return False or x
funcs = [and_true, and_false, or_true, or_false]
# Check that condition is removed.
for func in funcs:
with self.subTest(func=func):
opcodes = list(dis.get_instructions(func))
self.assertEqual(2, len(opcodes))
self.assertIn('LOAD_', opcodes[0].opname)
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
def test_big_dict_literal(self): def test_big_dict_literal(self):
# The compiler has a flushing point in "compiler_dict" that calls compiles # The compiler has a flushing point in "compiler_dict" that calls compiles
# a portion of the dictionary literal when the loop that iterates over the items # a portion of the dictionary literal when the loop that iterates over the items

View File

@ -145,30 +145,24 @@ def bug1333982(x=[]):
pass pass
dis_bug1333982 = """\ dis_bug1333982 = """\
%3d 0 LOAD_CONST 1 (0) %3d 0 LOAD_ASSERTION_ERROR
2 POP_JUMP_IF_TRUE 26 2 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>)
4 LOAD_ASSERTION_ERROR 4 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>')
6 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>) 6 MAKE_FUNCTION 0
8 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>') 8 LOAD_FAST 0 (x)
10 MAKE_FUNCTION 0 10 GET_ITER
12 LOAD_FAST 0 (x) 12 CALL_FUNCTION 1
14 GET_ITER
16 CALL_FUNCTION 1
%3d 18 LOAD_CONST 4 (1) %3d 14 LOAD_CONST 4 (1)
%3d 20 BINARY_ADD %3d 16 BINARY_ADD
22 CALL_FUNCTION 1 18 CALL_FUNCTION 1
24 RAISE_VARARGS 1 20 RAISE_VARARGS 1
%3d >> 26 LOAD_CONST 0 (None)
28 RETURN_VALUE
""" % (bug1333982.__code__.co_firstlineno + 1, """ % (bug1333982.__code__.co_firstlineno + 1,
__file__, __file__,
bug1333982.__code__.co_firstlineno + 1, bug1333982.__code__.co_firstlineno + 1,
bug1333982.__code__.co_firstlineno + 2, bug1333982.__code__.co_firstlineno + 2,
bug1333982.__code__.co_firstlineno + 1, bug1333982.__code__.co_firstlineno + 1)
bug1333982.__code__.co_firstlineno + 3)
_BIG_LINENO_FORMAT = """\ _BIG_LINENO_FORMAT = """\
%3d 0 LOAD_GLOBAL 0 (spam) %3d 0 LOAD_GLOBAL 0 (spam)
@ -674,8 +668,15 @@ class DisWithFileTests(DisTests):
return output.getvalue() return output.getvalue()
if sys.flags.optimize:
code_info_consts = "0: None"
else:
code_info_consts = (
"""0: 'Formatted details of methods, functions, or code.'
1: None"""
)
code_info_code_info = """\ code_info_code_info = f"""\
Name: code_info Name: code_info
Filename: (.*) Filename: (.*)
Argument count: 1 Argument count: 1
@ -685,13 +686,13 @@ Number of locals: 1
Stack size: 3 Stack size: 3
Flags: OPTIMIZED, NEWLOCALS, NOFREE Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants: Constants:
0: %r {code_info_consts}
Names: Names:
0: _format_code_info 0: _format_code_info
1: _get_code_object 1: _get_code_object
Variable names: Variable names:
0: x""" % (('Formatted details of methods, functions, or code.',) 0: x"""
if sys.flags.optimize < 2 else (None,))
@staticmethod @staticmethod
def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds):

View File

@ -0,0 +1 @@
Make sure that the compiler front-end produces a well-formed control flow graph. Be be more aggressive in the compiler back-end, as it is now safe to do so.

View File

@ -2587,6 +2587,7 @@ compiler_jump_if(struct compiler *c, expr_ty e, basicblock *next, int cond)
VISIT(c, expr, (expr_ty)asdl_seq_GET(e->v.Compare.comparators, n)); VISIT(c, expr, (expr_ty)asdl_seq_GET(e->v.Compare.comparators, n));
ADDOP_COMPARE(c, asdl_seq_GET(e->v.Compare.ops, n)); ADDOP_COMPARE(c, asdl_seq_GET(e->v.Compare.ops, n));
ADDOP_JUMP(c, cond ? POP_JUMP_IF_TRUE : POP_JUMP_IF_FALSE, next); ADDOP_JUMP(c, cond ? POP_JUMP_IF_TRUE : POP_JUMP_IF_FALSE, next);
NEXT_BLOCK(c);
basicblock *end = compiler_new_block(c); basicblock *end = compiler_new_block(c);
if (end == NULL) if (end == NULL)
return 0; return 0;
@ -2610,6 +2611,7 @@ compiler_jump_if(struct compiler *c, expr_ty e, basicblock *next, int cond)
/* general implementation */ /* general implementation */
VISIT(c, expr, e); VISIT(c, expr, e);
ADDOP_JUMP(c, cond ? POP_JUMP_IF_TRUE : POP_JUMP_IF_FALSE, next); ADDOP_JUMP(c, cond ? POP_JUMP_IF_TRUE : POP_JUMP_IF_FALSE, next);
NEXT_BLOCK(c);
return 1; return 1;
} }
@ -2829,7 +2831,7 @@ compiler_async_for(struct compiler *c, stmt_ty s)
static int static int
compiler_while(struct compiler *c, stmt_ty s) compiler_while(struct compiler *c, stmt_ty s)
{ {
basicblock *loop, *orelse, *end, *anchor = NULL; basicblock *loop, *body, *end, *anchor = NULL;
int constant = expr_constant(s->v.While.test); int constant = expr_constant(s->v.While.test);
if (constant == 0) { if (constant == 0) {
@ -2850,42 +2852,32 @@ compiler_while(struct compiler *c, stmt_ty s)
return 1; return 1;
} }
loop = compiler_new_block(c); loop = compiler_new_block(c);
body = compiler_new_block(c);
anchor = compiler_new_block(c);
end = compiler_new_block(c); end = compiler_new_block(c);
if (constant == -1) { if (loop == NULL || body == NULL || anchor == NULL || end == NULL) {
anchor = compiler_new_block(c);
if (anchor == NULL)
return 0;
}
if (loop == NULL || end == NULL)
return 0; return 0;
if (s->v.While.orelse) {
orelse = compiler_new_block(c);
if (orelse == NULL)
return 0;
} }
else
orelse = NULL;
compiler_use_next_block(c, loop); compiler_use_next_block(c, loop);
if (!compiler_push_fblock(c, WHILE_LOOP, loop, end, NULL)) if (!compiler_push_fblock(c, WHILE_LOOP, loop, end, NULL)) {
return 0; return 0;
if (constant == -1) {
if (!compiler_jump_if(c, s->v.While.test, anchor, 0))
return 0;
} }
if (constant == -1) {
if (!compiler_jump_if(c, s->v.While.test, anchor, 0)) {
return 0;
}
}
compiler_use_next_block(c, body);
VISIT_SEQ(c, stmt, s->v.While.body); VISIT_SEQ(c, stmt, s->v.While.body);
ADDOP_JUMP(c, JUMP_ABSOLUTE, loop); ADDOP_JUMP(c, JUMP_ABSOLUTE, loop);
/* XXX should the two POP instructions be in a separate block
if there is no else clause ?
*/
if (constant == -1)
compiler_use_next_block(c, anchor);
compiler_pop_fblock(c, WHILE_LOOP, loop); compiler_pop_fblock(c, WHILE_LOOP, loop);
if (orelse != NULL) /* what if orelse is just pass? */ compiler_use_next_block(c, anchor);
if (s->v.While.orelse) {
VISIT_SEQ(c, stmt, s->v.While.orelse); VISIT_SEQ(c, stmt, s->v.While.orelse);
}
compiler_use_next_block(c, end); compiler_use_next_block(c, end);
return 1; return 1;
@ -2916,6 +2908,7 @@ compiler_return(struct compiler *c, stmt_ty s)
VISIT(c, expr, s->v.Return.value); VISIT(c, expr, s->v.Return.value);
} }
ADDOP(c, RETURN_VALUE); ADDOP(c, RETURN_VALUE);
NEXT_BLOCK(c);
return 1; return 1;
} }
@ -2934,6 +2927,7 @@ compiler_break(struct compiler *c)
return 0; return 0;
} }
ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_exit); ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_exit);
NEXT_BLOCK(c);
return 1; return 1;
} }
@ -2948,6 +2942,7 @@ compiler_continue(struct compiler *c)
return compiler_error(c, "'continue' not properly in loop"); return compiler_error(c, "'continue' not properly in loop");
} }
ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_block); ADDOP_JUMP(c, JUMP_ABSOLUTE, loop->fb_block);
NEXT_BLOCK(c)
return 1; return 1;
} }
@ -3087,6 +3082,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
ADDOP(c, DUP_TOP); ADDOP(c, DUP_TOP);
VISIT(c, expr, handler->v.ExceptHandler.type); VISIT(c, expr, handler->v.ExceptHandler.type);
ADDOP_JUMP(c, JUMP_IF_NOT_EXC_MATCH, except); ADDOP_JUMP(c, JUMP_IF_NOT_EXC_MATCH, except);
NEXT_BLOCK(c);
} }
ADDOP(c, POP_TOP); ADDOP(c, POP_TOP);
if (handler->v.ExceptHandler.name) { if (handler->v.ExceptHandler.name) {
@ -3427,6 +3423,7 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
} }
} }
ADDOP_I(c, RAISE_VARARGS, (int)n); ADDOP_I(c, RAISE_VARARGS, (int)n);
NEXT_BLOCK(c);
break; break;
case Try_kind: case Try_kind:
return compiler_try(c, s); return compiler_try(c, s);
@ -4798,6 +4795,7 @@ compiler_with_except_finish(struct compiler *c) {
if (exit == NULL) if (exit == NULL)
return 0; return 0;
ADDOP_JUMP(c, POP_JUMP_IF_TRUE, exit); ADDOP_JUMP(c, POP_JUMP_IF_TRUE, exit);
NEXT_BLOCK(c);
ADDOP(c, RERAISE); ADDOP(c, RERAISE);
compiler_use_next_block(c, exit); compiler_use_next_block(c, exit);
ADDOP(c, POP_TOP); ADDOP(c, POP_TOP);
@ -5521,6 +5519,7 @@ stackdepth(struct compiler *c)
} }
} }
if (next != NULL) { if (next != NULL) {
assert(b->b_nofallthrough == 0);
stackdepth_push(&sp, next, depth); stackdepth_push(&sp, next, depth);
} }
} }
@ -6096,7 +6095,6 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
struct instr nop; struct instr nop;
nop.i_opcode = NOP; nop.i_opcode = NOP;
struct instr *target; struct instr *target;
int lineno;
for (int i = 0; i < bb->b_iused; i++) { for (int i = 0; i < bb->b_iused; i++) {
struct instr *inst = &bb->b_instr[i]; struct instr *inst = &bb->b_instr[i];
int oparg = inst->i_oparg; int oparg = inst->i_oparg;
@ -6112,23 +6110,50 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
target = &nop; target = &nop;
} }
switch (inst->i_opcode) { switch (inst->i_opcode) {
/* Skip over LOAD_CONST trueconst /* Remove LOAD_CONST const; conditional jump */
POP_JUMP_IF_FALSE xx. This improves
"while 1" performance. */
case LOAD_CONST: case LOAD_CONST:
if (nextop != POP_JUMP_IF_FALSE) { {
break; PyObject* cnt;
} int is_true;
PyObject* cnt = PyList_GET_ITEM(consts, oparg); int jump_if_true;
int is_true = PyObject_IsTrue(cnt); switch(nextop) {
if (is_true == -1) { case POP_JUMP_IF_FALSE:
goto error; case POP_JUMP_IF_TRUE:
} cnt = PyList_GET_ITEM(consts, oparg);
if (is_true == 1) { is_true = PyObject_IsTrue(cnt);
inst->i_opcode = NOP; if (is_true == -1) {
bb->b_instr[i+1].i_opcode = NOP; goto error;
}
inst->i_opcode = NOP;
jump_if_true = nextop == POP_JUMP_IF_TRUE;
if (is_true == jump_if_true) {
bb->b_instr[i+1].i_opcode = JUMP_ABSOLUTE;
bb->b_nofallthrough = 1;
}
else {
bb->b_instr[i+1].i_opcode = NOP;
}
break;
case JUMP_IF_FALSE_OR_POP:
case JUMP_IF_TRUE_OR_POP:
cnt = PyList_GET_ITEM(consts, oparg);
is_true = PyObject_IsTrue(cnt);
if (is_true == -1) {
goto error;
}
jump_if_true = nextop == JUMP_IF_TRUE_OR_POP;
if (is_true == jump_if_true) {
bb->b_instr[i+1].i_opcode = JUMP_ABSOLUTE;
bb->b_nofallthrough = 1;
}
else {
inst->i_opcode = NOP;
bb->b_instr[i+1].i_opcode = NOP;
}
break;
} }
break; break;
}
/* Try to fold tuples of constants. /* Try to fold tuples of constants.
Skip over BUILD_SEQN 1 UNPACK_SEQN 1. Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
@ -6176,16 +6201,21 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
switch(target->i_opcode) { switch(target->i_opcode) {
case POP_JUMP_IF_FALSE: case POP_JUMP_IF_FALSE:
*inst = *target; *inst = *target;
--i;
break; break;
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
case JUMP_FORWARD: case JUMP_FORWARD:
case JUMP_IF_FALSE_OR_POP: case JUMP_IF_FALSE_OR_POP:
inst->i_target = target->i_target; if (inst->i_target != target->i_target) {
inst->i_target = target->i_target;
--i;
}
break; break;
case JUMP_IF_TRUE_OR_POP: case JUMP_IF_TRUE_OR_POP:
assert (inst->i_target->b_iused == 1); assert (inst->i_target->b_iused == 1);
inst->i_opcode = POP_JUMP_IF_FALSE; inst->i_opcode = POP_JUMP_IF_FALSE;
inst->i_target = inst->i_target->b_next; inst->i_target = inst->i_target->b_next;
--i;
break; break;
} }
break; break;
@ -6194,16 +6224,21 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
switch(target->i_opcode) { switch(target->i_opcode) {
case POP_JUMP_IF_TRUE: case POP_JUMP_IF_TRUE:
*inst = *target; *inst = *target;
--i;
break; break;
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
case JUMP_FORWARD: case JUMP_FORWARD:
case JUMP_IF_TRUE_OR_POP: case JUMP_IF_TRUE_OR_POP:
inst->i_target = target->i_target; if (inst->i_target != target->i_target) {
inst->i_target = target->i_target;
--i;
}
break; break;
case JUMP_IF_FALSE_OR_POP: case JUMP_IF_FALSE_OR_POP:
assert (inst->i_target->b_iused == 1); assert (inst->i_target->b_iused == 1);
inst->i_opcode = POP_JUMP_IF_TRUE; inst->i_opcode = POP_JUMP_IF_TRUE;
inst->i_target = inst->i_target->b_next; inst->i_target = inst->i_target->b_next;
--i;
break; break;
} }
break; break;
@ -6212,7 +6247,10 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
switch(target->i_opcode) { switch(target->i_opcode) {
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
case JUMP_FORWARD: case JUMP_FORWARD:
inst->i_target = target->i_target; if (inst->i_target != target->i_target) {
inst->i_target = target->i_target;
--i;
}
break; break;
} }
break; break;
@ -6221,7 +6259,10 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
switch(target->i_opcode) { switch(target->i_opcode) {
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
case JUMP_FORWARD: case JUMP_FORWARD:
inst->i_target = target->i_target; if (inst->i_target != target->i_target) {
inst->i_target = target->i_target;
--i;
}
break; break;
} }
break; break;
@ -6231,12 +6272,17 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
assert (i == bb->b_iused-1); assert (i == bb->b_iused-1);
switch(target->i_opcode) { switch(target->i_opcode) {
case JUMP_FORWARD: case JUMP_FORWARD:
inst->i_target = target->i_target; if (inst->i_target != target->i_target) {
inst->i_target = target->i_target;
--i;
}
break; break;
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
lineno = inst->i_lineno; if (inst->i_target != target->i_target) {
*inst = *target; inst->i_target = target->i_target;
inst->i_lineno = lineno; inst->i_opcode = target->i_opcode;
--i;
}
break; break;
} }
if (inst->i_target->b_exit && inst->i_target->b_iused <= MAX_COPY_SIZE) { if (inst->i_target->b_exit && inst->i_target->b_iused <= MAX_COPY_SIZE) {
@ -6268,15 +6314,15 @@ clean_basic_block(basicblock *bb) {
for (int src = 0; src < bb->b_iused; src++) { for (int src = 0; src < bb->b_iused; src++) {
int lineno = bb->b_instr[src].i_lineno; int lineno = bb->b_instr[src].i_lineno;
if (bb->b_instr[src].i_opcode == NOP) { if (bb->b_instr[src].i_opcode == NOP) {
/* Eliminate no-op if it doesn't have a line number, or /* Eliminate no-op if it doesn't have a line number */
* if the next instruction has same line number or no line number, or
* if the previous instruction had the same line number. */
if (lineno < 0) { if (lineno < 0) {
continue; continue;
} }
/* or, if the previous instruction had the same line number. */
if (prev_lineno == lineno) { if (prev_lineno == lineno) {
continue; continue;
} }
/* or, if the next instruction has same line number or no line number */
if (src < bb->b_iused - 1) { if (src < bb->b_iused - 1) {
int next_lineno = bb->b_instr[src+1].i_lineno; int next_lineno = bb->b_instr[src+1].i_lineno;
if (next_lineno < 0 || next_lineno == lineno) { if (next_lineno < 0 || next_lineno == lineno) {
@ -6284,6 +6330,19 @@ clean_basic_block(basicblock *bb) {
continue; continue;
} }
} }
else {
basicblock* next = bb->b_next;
while (next && next->b_iused == 0) {
next = next->b_next;
}
/* or if last instruction in BB and next BB has same line number */
if (next) {
if (lineno == next->b_instr[0].i_lineno) {
continue;
}
}
}
} }
if (dest != src) { if (dest != src) {
bb->b_instr[dest] = bb->b_instr[src]; bb->b_instr[dest] = bb->b_instr[src];
@ -6295,30 +6354,36 @@ clean_basic_block(basicblock *bb) {
bb->b_iused = dest; bb->b_iused = dest;
} }
static void
normalise_basic_block(basicblock *bb) { static int
/* Remove any code following a return or re-raise, normalize_basic_block(basicblock *bb) {
and mark those blocks as exit and/or nofallthrough. */ /* Mark blocks as exit and/or nofallthrough.
Raise SystemError if CFG is malformed. */
for (int i = 0; i < bb->b_iused; i++) { for (int i = 0; i < bb->b_iused; i++) {
switch(bb->b_instr[i].i_opcode) { switch(bb->b_instr[i].i_opcode) {
case RETURN_VALUE: case RETURN_VALUE:
case RAISE_VARARGS: case RAISE_VARARGS:
case RERAISE: case RERAISE:
bb->b_iused = i+1;
bb->b_exit = 1; bb->b_exit = 1;
bb->b_nofallthrough = 1; /* fall through */
return;
case JUMP_ABSOLUTE: case JUMP_ABSOLUTE:
case JUMP_FORWARD: case JUMP_FORWARD:
bb->b_iused = i+1;
bb->b_nofallthrough = 1; bb->b_nofallthrough = 1;
return; /* fall through */
case POP_JUMP_IF_FALSE:
case POP_JUMP_IF_TRUE:
case JUMP_IF_FALSE_OR_POP:
case JUMP_IF_TRUE_OR_POP:
if (i != bb->b_iused-1) {
PyErr_SetString(PyExc_SystemError, "malformed control flow graph.");
return -1;
}
} }
} }
return 0;
} }
static int static int
mark_reachable(struct assembler *a) { mark_reachable(struct assembler *a) {
basicblock **stack, **sp; basicblock **stack, **sp;
@ -6363,7 +6428,9 @@ static int
optimize_cfg(struct assembler *a, PyObject *consts) optimize_cfg(struct assembler *a, PyObject *consts)
{ {
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) { for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
normalise_basic_block(b); if (normalize_basic_block(b)) {
return -1;
}
} }
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) { for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
if (optimize_basic_block(b, consts)) { if (optimize_basic_block(b, consts)) {

2833
Python/importlib.h generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff