diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 794d104d59..d06acc9837 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -18,6 +18,27 @@ def count_instr_recursively(f, opname): class TestTranforms(BytecodeTestCase): + def check_jump_targets(self, code): + instructions = list(dis.get_instructions(code)) + targets = {instr.offset: instr for instr in instructions} + for instr in instructions: + if 'JUMP_' not in instr.opname: + continue + tgt = targets[instr.argval] + # jump to unconditional jump + if tgt.opname in ('JUMP_ABSOLUTE', 'JUMP_FORWARD'): + self.fail(f'{instr.opname} at {instr.offset} ' + f'jumps to {tgt.opname} at {tgt.offset}') + # unconditional jump to RETURN_VALUE + if (instr.opname in ('JUMP_ABSOLUTE', 'JUMP_FORWARD') and + tgt.opname == 'RETURN_VALUE'): + self.fail(f'{instr.opname} at {instr.offset} ' + f'jumps to {tgt.opname} at {tgt.offset}') + # JUMP_IF_*_OR_POP jump to conditional jump + if '_OR_POP' in instr.opname and 'JUMP_IF_' in tgt.opname: + self.fail(f'{instr.opname} at {instr.offset} ' + f'jumps to {tgt.opname} at {tgt.offset}') + def test_unot(self): # UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE' def unot(x): @@ -259,13 +280,77 @@ class TestTranforms(BytecodeTestCase): def test_elim_jump_to_return(self): # JUMP_FORWARD to RETURN --> RETURN def f(cond, true_value, false_value): - return true_value if cond else false_value + # Intentionally use two-line expression to test issue37213. + return (true_value if cond + else false_value) + self.check_jump_targets(f) self.assertNotInBytecode(f, 'JUMP_FORWARD') self.assertNotInBytecode(f, 'JUMP_ABSOLUTE') returns = [instr for instr in dis.get_instructions(f) if instr.opname == 'RETURN_VALUE'] self.assertEqual(len(returns), 2) + def test_elim_jump_to_uncond_jump(self): + # POP_JUMP_IF_FALSE to JUMP_FORWARD --> POP_JUMP_IF_FALSE to non-jump + # JUMP_FORWARD to JUMP_FORWARD --> JUMP_FORWARD to non-jump + def f(): + if a: + if b: + # Intentionally use two-line expression to test issue37213. + if (c + or d): + foo() + else: + bar() + else: + baz() + self.check_jump_targets(f) + + def test_elim_jump_to_uncond_jump2(self): + # POP_JUMP_IF_FALSE to JUMP_ABSOLUTE --> POP_JUMP_IF_FALSE to non-jump + # JUMP_FORWARD to JUMP_ABSOLUTE --> JUMP_FORWARD to non-jump + def f(): + while a: + if b: + # Intentionally use two-line expression to test issue37213. + if (c + or d): + a = foo() + else: + a = bar() + self.check_jump_targets(f) + + def test_elim_jump_to_uncond_jump3(self): + # Intentionally use two-line expressions to test issue37213. + # JUMP_IF_FALSE_OR_POP to JUMP_IF_FALSE_OR_POP --> JUMP_IF_FALSE_OR_POP to non-jump + def f(a, b, c): + return ((a and b) + and c) + self.check_jump_targets(f) + self.assertEqual(count_instr_recursively(f, 'JUMP_IF_FALSE_OR_POP'), 2) + # JUMP_IF_TRUE_OR_POP to JUMP_IF_TRUE_OR_POP --> JUMP_IF_TRUE_OR_POP to non-jump + def f(a, b, c): + return ((a or b) + or c) + self.check_jump_targets(f) + self.assertEqual(count_instr_recursively(f, 'JUMP_IF_TRUE_OR_POP'), 2) + # JUMP_IF_FALSE_OR_POP to JUMP_IF_TRUE_OR_POP --> POP_JUMP_IF_FALSE to non-jump + def f(a, b, c): + return ((a and b) + or c) + self.check_jump_targets(f) + self.assertNotInBytecode(f, 'JUMP_IF_FALSE_OR_POP') + self.assertInBytecode(f, 'JUMP_IF_TRUE_OR_POP') + self.assertInBytecode(f, 'POP_JUMP_IF_FALSE') + # JUMP_IF_TRUE_OR_POP to JUMP_IF_FALSE_OR_POP --> POP_JUMP_IF_TRUE to non-jump + def f(a, b, c): + return ((a or b) + and c) + self.check_jump_targets(f) + self.assertNotInBytecode(f, 'JUMP_IF_TRUE_OR_POP') + self.assertInBytecode(f, 'JUMP_IF_FALSE_OR_POP') + self.assertInBytecode(f, 'POP_JUMP_IF_TRUE') + def test_elim_jump_after_return1(self): # Eliminate dead code: jumps immediately after returns can't be reached def f(cond1, cond2):