Index: Include/pydebug.h =================================================================== --- Include/pydebug.h (revision 73330) +++ Include/pydebug.h (working copy) @@ -26,6 +26,7 @@ PyAPI_DATA(int) _Py_QnewFlag; /* Warn about 3.x issues */ PyAPI_DATA(int) Py_Py3kWarningFlag; +PyAPI_DATA(int) Py_UnreachableWarningFlag; /* this is a wrapper around getenv() that pays attention to Py_IgnoreEnvironmentFlag. It should be used for getting variables like Index: Lib/test/test_deadcode.py =================================================================== --- Lib/test/test_deadcode.py (revision 0) +++ Lib/test/test_deadcode.py (revision 0) @@ -0,0 +1,410 @@ + +"""Test dead code elimination.""" + +import unittest +from test import test_support + +class TestDeadCode(unittest.TestCase): + + def assertNoDeadCode(self, f): + self.assert_(0xDEADC0DE not in f.func_code.co_consts) + + def test_implies(self): + def f(x, y): + if x != 0: + if y != 0: + return 1 + z = 0xDEADC0DE + else: + return 1 + return 0 + + self.assertEquals(f(0, 0), 1) + self.assertEquals(f(0, 1), 1) + self.assertEquals(f(1, 0), 0) + self.assertEquals(f(1, 1), 1) + self.assertNoDeadCode(f) + + def test_func(self): + def f(): + return 1 + z = 0xDEADC0DE + + self.assertEquals(f(), 1) + self.assertNoDeadCode(f) + + def test_func_raise(self): + def f(): + raise RuntimeError + z = 0xDEADC0DE + + self.assertRaises(RuntimeError, f) + self.assertNoDeadCode(f) + + def test_if_0(self): + def f(): + if 0: + z = 0xDEADC0DE + else: + return 1 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(), 1) + self.assertNoDeadCode(f) + + def test_if_1(self): + def f(): + if 1: + return 1 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(), 1) + self.assertNoDeadCode(f) + + def test_if_x(self): + def f(x): + if x: + return 0 + z = 0xDEADC0DE + return 1 + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 0) + self.assertNoDeadCode(f) + + def test_if_x_else(self): + def f(x): + if x: + return 0 + z = 0xDEADC0DE + else: + return 1 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 0) + self.assertNoDeadCode(f) + + def test_for(self): + def f(x): + for i in (1, 2, 3): + if x: + break + return 1 + z = 0xDEADC0DE + return 0 + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 0) + self.assertNoDeadCode(f) + + def test_for_continue(self): + def f(): + for i in (1, 2, 3): + continue + z = 0xDEADC0DE + return i + + self.assertEquals(f(), 3) + self.assertNoDeadCode(f) + + def test_for_break(self): + def f(): + for i in (1, 2, 3): + break + z = 0xDEADC0DE + return i + + self.assertEquals(f(), 1) + self.assertNoDeadCode(f) + + def test_while_0(self): + def f(): + while 0: + z = 0xDEADC0DE + else: + return 0 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(), 0) + self.assertNoDeadCode(f) + + def test_while_1(self): + def f(x): + while 1: + if x: + break + return 1 + z = 0xDEADC0DE + else: + z = 0xDEADC0DE + return 0 + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 0) + self.assertNoDeadCode(f) + + def test_while_x(self): + def f(x): + while x: + if x == 2: + break + return 0 + z = 0xDEADC0DE + else: + return 1 + z = 0xDEADC0DE + return 2 + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 0) + self.assertEquals(f(2), 2) + self.assertNoDeadCode(f) + + def test_while_continue(self): + def f(x): + while x: + x -= 1 + continue + z = 0xDEADC0DE + return x + + self.assertEquals(f(0), 0) + self.assertEquals(f(1), 0) + self.assertEquals(f(3), 0) + self.assertNoDeadCode(f) + + def test_while_break(self): + def f(x): + while x: + x -= 1 + break + z = 0xDEADC0DE + return x + + self.assertEquals(f(0), 0) + self.assertEquals(f(1), 0) + self.assertEquals(f(3), 2) + self.assertNoDeadCode(f) + + def test_while_try_continue_except(self): + def f(x): + while x: + x -= 1 + try: + continue + z = 0xDEADC0DE + except: + pass + return x + + self.assertEquals(f(3), 0) + self.assertNoDeadCode(f) + + def test_while_try_except_continue(self): + def f(x): + while x: + x -= 1 + try: + z = 1 / x + except: + continue + z = 0xDEADC0DE + return x + + self.assertEquals(f(3), 0) + self.assertNoDeadCode(f) + + def test_while_try_except_else_continue(self): + def f(x): + while x: + x -= 1 + try: + z = 1 / x + except: + pass + else: + continue + z = 0xDEADC0DE + return x + + self.assertEquals(f(3), 0) + self.assertNoDeadCode(f) + + def test_while_try_continue_finally(self): + def f(x): + while x: + x -= 1 + try: + continue + z = 0xDEADC0DE + finally: + pass + return x + + self.assertEquals(f(3), 0) + self.assertNoDeadCode(f) + + def test_while_with_continue(self): + def f(x,y): + while x: + x -= 1 + with y as v: + continue + z = 0xDEADC0DE + return x + + class w: + def __init__(self, v = 0): + self.v = v + def __enter__(self): + return self.v + def __exit__(self, *exc_info): + return True + + self.assertEquals(f(0, w()), 0) + self.assertEquals(f(1, w()), 0) + self.assertEquals(f(3, w()), 0) + self.assertNoDeadCode(f) + + def test_with(self): + def f(x): + with x as v: + return 1 / v + z = 0xDEADC0DE + return 0 + + class w: + def __init__(self, v = 0): + self.v = v + def __enter__(self): + return self.v + def __exit__(self, *exc_info): + return True + + self.assertEquals(f(w(0)), 0) + self.assertEquals(f(w(1)), 1) + self.assertNoDeadCode(f) + + def test_try_finally(self): + def f(x): + try: + if x: + pass + finally: + pass + return 0 + + self.assertEquals(f(0), 0) + + def test_try_return_finally(self): + def f(): + try: + return 1 + z = 0xDEADC0DE + finally: + pass + z = 0xDEADC0DE + + self.assertEquals(f(), 1) + self.assertNoDeadCode(f) + + def test_try_finally_return(self): + def f(): + try: + pass + finally: + return 2 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(), 2) + self.assertNoDeadCode(f) + + def test_try_return_finally_return(self): + def f(): + try: + return 1 + z = 0xDEADC0DE + finally: + return 2 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(), 2) + self.assertNoDeadCode(f) + + def test_try_return_except(self): + def f(x): + try: + return 1 / x + z = 0xDEADC0DE + except ZeroDivisionError: + pass + else: + z = 0xDEADC0DE + return 2 + + self.assertEquals(f(0), 2) + self.assertEquals(f(1), 1) + self.assertNoDeadCode(f) + + def test_try_return_except_else(self): + def f(x): + try: + return 1 / x + z = 0xDEADC0DE + except: + pass + else: + z = 0xDEADC0DE + return 2 + + self.assertEquals(f(0), 2) + self.assertEquals(f(1), 1) + self.assertNoDeadCode(f) + + def test_try_return_except_return_else(self): + def f(x): + try: + return 1 / x + z = 0xDEADC0DE + except ZeroDivisionError: + return 2 + z = 0xDEADC0DE + else: + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(0), 2) + self.assertEquals(f(1), 1) + self.assertNoDeadCode(f) + + def test_try_except_return_else_return(self): + def f(x): + try: + z = 1 / x + except ZeroDivisionError: + return 1 + z = 0xDEADC0DE + else: + return 2 + z = 0xDEADC0DE + z = 0xDEADC0DE + + self.assertEquals(f(0), 1) + self.assertEquals(f(1), 2) + self.assertNoDeadCode(f) + + +def test_main(): + test_support.run_unittest(TestDeadCode) + + +if __name__ == "__main__": + test_main() + Index: Lib/test/test_peepholer.py =================================================================== --- Lib/test/test_peepholer.py (revision 73330) +++ Lib/test/test_peepholer.py (working copy) @@ -202,10 +202,79 @@ self.assertEqual(asm.split().count('RETURN_VALUE'), 2) +class TestLastOpcode(unittest.TestCase): + # Test that functions that end with something other than RETURN_VALUE + # don't cause the peephole optimizer to bail out + + def test_try_finally(self): + # Ends with END_FINALLY + def f(x): + try: + return (1 + 2) / x + finally: + pass + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_try_except(self): + # Ends with END_FINALLY + def f(x): + try: + return (1 + 2) / x + except ZeroDivisionError: + return 0 + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_raise_0(self): + # Ends with RAISE_VARARGS 0 + def f(): + z = 1 + 2 + raise + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_raise_1(self): + # Ends with RAISE_VARARGS 1 + def f(): + z = 1 + 2 + raise ZeroDivisionError + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_raise_2(self): + # Ends with RAISE_VARARGS 2 + def f(): + z = 1 + 2 + raise ZeroDivisionError, None + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_raise_3(self): + # Ends with RAISE_VARARGS 3 + def f(): + z = 1 + 2 + raise ZeroDivisionError, None, None + + asm = disassemble(f) + self.assert_('(3)' in asm) + self.assert_('BINARY_ADD' not in asm) + + def test_main(verbose=None): import sys from test import test_support - test_classes = (TestTranforms,) + test_classes = (TestTranforms, TestLastOpcode) test_support.run_unittest(*test_classes) # verify reference counting Index: Modules/main.c =================================================================== --- Modules/main.c (revision 73330) +++ Modules/main.c (working copy) @@ -40,7 +40,7 @@ static int orig_argc; /* command line options */ -#define BASE_OPTS "3bBc:dEhiJm:OQ:sStuUvVW:xX?" +#define BASE_OPTS "3bBc:dEhiJm:OQ:rsStuUvVW:xX?" #ifndef RISCOS #define PROGRAM_OPTS BASE_OPTS @@ -315,6 +315,10 @@ Py_DebugFlag++; break; + case 'r': + Py_UnreachableWarningFlag++; + break; + case '3': Py_Py3kWarningFlag++; if (!Py_DivisionWarningFlag) Index: Python/compile.c =================================================================== --- Python/compile.c (revision 73330) +++ Python/compile.c (working copy) @@ -65,8 +65,13 @@ struct basicblock_ *b_next; /* b_seen is used to perform a DFS of basicblocks. */ unsigned b_seen : 1; - /* b_return is true if a RETURN_VALUE opcode is inserted. */ - unsigned b_return : 1; + /* b_terminated is true if a terminating instruction is inserted. + One of RETURN_VALUE, JUMP_*, {BREAK|CONTINUE}_LOOP, RAISE_VARARGS, + and sometimes END_FINALLY */ + unsigned b_terminated : 1; + /* b_reachable is true if this block is reachable either by + being a jump target or falling through from the previous block */ + unsigned b_reachable : 1; /* depth of stack upon entry of block, computed by stackdepth() */ int b_startdepth; /* instruction offset for block, computed by assemble_jump_offsets() */ @@ -144,6 +149,10 @@ PyArena *c_arena; /* pointer to memory allocation arena */ }; +/* True if the insertion point in the current block is reachable */ +#define IN_LIVE_CODE(C) \ + ((C)->u->u_curblock->b_reachable && !(C)->u->u_curblock->b_terminated) + static int compiler_enter_scope(struct compiler *, identifier, void *, int); static void compiler_free(struct compiler *); static basicblock *compiler_new_block(struct compiler *); @@ -564,6 +573,7 @@ basicblock *block = compiler_new_block(c); if (block == NULL) return NULL; + block->b_reachable = 1; c->u->u_curblock = block; return block; } @@ -574,6 +584,8 @@ basicblock *block = compiler_new_block(c); if (block == NULL) return NULL; + if (IN_LIVE_CODE(c)) + block->b_reachable = 1; c->u->u_curblock->b_next = block; c->u->u_curblock = block; return block; @@ -583,6 +595,8 @@ compiler_use_next_block(struct compiler *c, basicblock *block) { assert(block != NULL); + if (IN_LIVE_CODE(c)) + block->b_reachable = 1; c->u->u_curblock->b_next = block; c->u->u_curblock = block; return block; @@ -898,6 +912,7 @@ basicblock *b; struct instr *i; int off; + assert(IN_LIVE_CODE(c)); off = compiler_next_instr(c, c->u->u_curblock); if (off < 0) return 0; @@ -905,8 +920,9 @@ i = &b->b_instr[off]; i->i_opcode = opcode; i->i_hasarg = 0; - if (opcode == RETURN_VALUE) - b->b_return = 1; + if (opcode == RETURN_VALUE || + opcode == BREAK_LOOP) + b->b_terminated = 1; compiler_set_lineno(c, off); return 1; } @@ -1025,6 +1041,7 @@ { struct instr *i; int off; + assert(IN_LIVE_CODE(c)); off = compiler_next_instr(c, c->u->u_curblock); if (off < 0) return 0; @@ -1032,6 +1049,8 @@ i->i_opcode = opcode; i->i_oparg = oparg; i->i_hasarg = 1; + if (opcode == RAISE_VARARGS) + c->u->u_curblock->b_terminated = 1; compiler_set_lineno(c, off); return 1; } @@ -1043,6 +1062,7 @@ int off; assert(b != NULL); + assert(IN_LIVE_CODE(c)); off = compiler_next_instr(c, c->u->u_curblock); if (off < 0) return 0; @@ -1054,6 +1074,11 @@ i->i_jabs = 1; else i->i_jrel = 1; + if (opcode == CONTINUE_LOOP || + opcode == JUMP_FORWARD || + opcode == JUMP_ABSOLUTE) + c->u->u_curblock->b_terminated = 1; + b->b_reachable = 1; compiler_set_lineno(c, off); return 1; } @@ -1610,7 +1635,8 @@ VISIT(c, expr, s->v.If.test); ADDOP_JABS(c, POP_JUMP_IF_FALSE, next); VISIT_SEQ(c, stmt, s->v.If.body); - ADDOP_JREL(c, JUMP_FORWARD, end); + if (IN_LIVE_CODE(c)) + ADDOP_JREL(c, JUMP_FORWARD, end); if (s->v.If.orelse) { compiler_use_next_block(c, next); VISIT_SEQ(c, stmt, s->v.If.orelse); @@ -1639,7 +1665,8 @@ ADDOP_JREL(c, FOR_ITER, cleanup); VISIT(c, expr, s->v.For.target); VISIT_SEQ(c, stmt, s->v.For.body); - ADDOP_JABS(c, JUMP_ABSOLUTE, start); + if (IN_LIVE_CODE(c)) + ADDOP_JABS(c, JUMP_ABSOLUTE, start); compiler_use_next_block(c, cleanup); ADDOP(c, POP_BLOCK); compiler_pop_fblock(c, LOOP, start); @@ -1685,7 +1712,8 @@ ADDOP_JABS(c, POP_JUMP_IF_FALSE, anchor); } VISIT_SEQ(c, stmt, s->v.While.body); - ADDOP_JABS(c, JUMP_ABSOLUTE, loop); + if (IN_LIVE_CODE(c)) + ADDOP_JABS(c, JUMP_ABSOLUTE, loop); /* XXX should the two POP instructions be in a separate block if there is no else clause ? @@ -1774,6 +1802,8 @@ compiler_try_finally(struct compiler *c, stmt_ty s) { basicblock *body, *end; + int body_ends_live_code; + body = compiler_new_block(c); end = compiler_new_block(c); if (body == NULL || end == NULL) @@ -1784,15 +1814,25 @@ if (!compiler_push_fblock(c, FINALLY_TRY, body)) return 0; VISIT_SEQ(c, stmt, s->v.TryFinally.body); - ADDOP(c, POP_BLOCK); + body_ends_live_code = IN_LIVE_CODE(c); + if (body_ends_live_code) + ADDOP(c, POP_BLOCK); compiler_pop_fblock(c, FINALLY_TRY, body); - ADDOP_O(c, LOAD_CONST, Py_None, consts); + if (body_ends_live_code) + ADDOP_O(c, LOAD_CONST, Py_None, consts); compiler_use_next_block(c, end); if (!compiler_push_fblock(c, FINALLY_END, end)) return 0; VISIT_SEQ(c, stmt, s->v.TryFinally.finalbody); - ADDOP(c, END_FINALLY); + if (IN_LIVE_CODE(c)) { + ADDOP(c, END_FINALLY); + if (!body_ends_live_code) { + /* Didn't arrive here by falling through from body + so END_FINALLY will not fall through either */ + c->u->u_curblock->b_terminated = 1; + } + } compiler_pop_fblock(c, FINALLY_END, end); return 1; @@ -1846,9 +1886,11 @@ if (!compiler_push_fblock(c, EXCEPT, body)) return 0; VISIT_SEQ(c, stmt, s->v.TryExcept.body); - ADDOP(c, POP_BLOCK); + if (IN_LIVE_CODE(c)) + ADDOP(c, POP_BLOCK); compiler_pop_fblock(c, EXCEPT, body); - ADDOP_JREL(c, JUMP_FORWARD, orelse); + if (IN_LIVE_CODE(c)) + ADDOP_JREL(c, JUMP_FORWARD, orelse); n = asdl_seq_LEN(s->v.TryExcept.handlers); compiler_use_next_block(c, except); for (i = 0; i < n; i++) { @@ -1876,10 +1918,15 @@ } ADDOP(c, POP_TOP); VISIT_SEQ(c, stmt, handler->v.ExceptHandler.body); - ADDOP_JREL(c, JUMP_FORWARD, end); + if (IN_LIVE_CODE(c)) + ADDOP_JREL(c, JUMP_FORWARD, end); compiler_use_next_block(c, except); } - ADDOP(c, END_FINALLY); + if (IN_LIVE_CODE(c)) { + ADDOP(c, END_FINALLY); + /* END_FINALLY will reraise the exception and not fall through */ + c->u->u_curblock->b_terminated = 1; + } compiler_use_next_block(c, orelse); VISIT_SEQ(c, stmt, s->v.TryExcept.orelse); compiler_use_next_block(c, end); @@ -2087,6 +2134,15 @@ c->u->u_lineno = s->lineno; c->u->u_lineno_set = false; + if (!IN_LIVE_CODE(c)) { + const char *msg = "code is unreachable"; + if (Py_UnreachableWarningFlag && + PyErr_WarnExplicit(PyExc_SyntaxWarning, msg, c->c_filename, + c->u->u_lineno, NULL, NULL) == -1) + return 0; + return 1; + } + switch (s->kind) { case FunctionDef_kind: return compiler_function(c, s); @@ -2842,10 +2898,12 @@ VISIT_SEQ(c, stmt, s->v.With.body); /* End of try block; start the finally block */ - ADDOP(c, POP_BLOCK); + if (IN_LIVE_CODE(c)) + ADDOP(c, POP_BLOCK); compiler_pop_fblock(c, FINALLY_TRY, block); - ADDOP_O(c, LOAD_CONST, Py_None, consts); + if (IN_LIVE_CODE(c)) + ADDOP_O(c, LOAD_CONST, Py_None, consts); compiler_use_next_block(c, finally); if (!compiler_push_fblock(c, FINALLY_END, finally)) return 0; @@ -3782,9 +3840,15 @@ dump_basicblock(const basicblock *b) { const char *seen = b->b_seen ? "seen " : ""; - const char *b_return = b->b_return ? "return " : ""; - fprintf(stderr, "used: %d, depth: %d, offset: %d %s%s\n", - b->b_iused, b->b_startdepth, b->b_offset, seen, b_return); + const char *b_terminated = b->b_terminated ? "term " : ""; + const char *b_reachable = b->b_reachable ? "reachable " : ""; + fprintf(stderr, "used: %d, depth: %d, offset: %d %s%s%s\n", + b->b_iused, + b->b_startdepth, + b->b_offset, + seen, + b_terminated, + b_reachable); if (b->b_instr) { int i; for (i = 0; i < b->b_iused; i++) { @@ -3804,10 +3868,8 @@ PyCodeObject *co = NULL; /* Make sure every block that falls off the end returns None. - XXX NEXT_BLOCK() isn't quite right, because if the last - block ends with a jump or return b_next shouldn't set. */ - if (!c->u->u_curblock->b_return) { + if (IN_LIVE_CODE(c)) { NEXT_BLOCK(c); if (addNone) ADDOP_O(c, LOAD_CONST, Py_None, consts); Index: Python/peephole.c =================================================================== --- Python/peephole.c (revision 73330) +++ Python/peephole.c (working copy) @@ -327,12 +327,18 @@ codestr = (unsigned char *)memcpy(codestr, PyString_AS_STRING(code), codelen); - /* Verify that RETURN_VALUE terminates the codestring. This allows + /* Verify that RETURN_VALUE, END_FINALLY, or RAISE_VARARGS + terminates the codestring. This allows the various transformation patterns to look ahead several instructions without additional checks to make sure they are not looking beyond the end of the code string. */ - if (codestr[codelen-1] != RETURN_VALUE) + if (codestr[codelen-1] != RETURN_VALUE && + codestr[codelen-1] != END_FINALLY && + !(codelen >= 3 && + codestr[codelen-3] == RAISE_VARARGS && + codestr[codelen-2] >= 0 && codestr[codelen-2] <= 3 && + codestr[codelen-1] == 0)) goto exitUnchanged; /* Mapping to new jump targets after NOPs are removed */ Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 73330) +++ Python/pythonrun.c (working copy) @@ -87,6 +87,7 @@ true divisions (which they will be in 2.3). */ int _Py_QnewFlag = 0; int Py_NoUserSiteDirectory = 0; /* for -s and site.py */ +int Py_UnreachableWarningFlag = 0; /* Warn when unreachable code is detected */ /* PyModule_GetWarningsModule is no longer necessary as of 2.6 since _warnings is builtin. This API should not be used. */