cpython: d3fee4c64654 (original) (raw)
--- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -5,44 +5,28 @@ from io import StringIO import unittest from math import copysign -def disassemble(func):
- f = StringIO()
- tmp = sys.stdout
- sys.stdout = f
- try:
dis.dis(func)[](#l1.12)
- finally:
sys.stdout = tmp[](#l1.14)
- result = f.getvalue()
- f.close()
- return result
+from test.bytecode_helper import BytecodeTestCase -def dis_single(line):
- - -class TestTranforms(unittest.TestCase): +class TestTranforms(BytecodeTestCase): def test_unot(self): # UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE' def unot(x): if not x == 2: del x
asm = disassemble(unot)[](#l1.32)
for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'):[](#l1.33)
self.assertNotIn(elem, asm)[](#l1.34)
for elem in ('POP_JUMP_IF_TRUE',):[](#l1.35)
self.assertIn(elem, asm)[](#l1.36)
self.assertNotInBytecode(unot, 'UNARY_NOT')[](#l1.37)
self.assertNotInBytecode(unot, 'POP_JUMP_IF_FALSE')[](#l1.38)
self.assertInBytecode(unot, 'POP_JUMP_IF_TRUE')[](#l1.39)
def test_elim_inversion_of_is_or_in(self):
for line, elem in ([](#l1.42)
('not a is b', '(is not)',),[](#l1.43)
('not a in b', '(not in)',),[](#l1.44)
('not a is not b', '(is)',),[](#l1.45)
('not a not in b', '(in)',),[](#l1.46)
for line, cmp_op in ([](#l1.47)
('not a is b', 'is not',),[](#l1.48)
('not a in b', 'not in',),[](#l1.49)
('not a is not b', 'is',),[](#l1.50)
('not a not in b', 'in',),[](#l1.51) ):[](#l1.52)
asm = dis_single(line)[](#l1.53)
self.assertIn(elem, asm)[](#l1.54)
code = compile(line, '', 'single')[](#l1.55)
self.assertInBytecode(code, 'COMPARE_OP', cmp_op)[](#l1.56)
def test_global_as_constant(self): # LOAD_GLOBAL None/True/False --> LOAD_CONST None/True/False @@ -56,17 +40,14 @@ class TestTranforms(unittest.TestCase): def h(x): False return x
for func, name in ((f, 'None'), (g, 'True'), (h, 'False')):[](#l1.64)
asm = disassemble(func)[](#l1.65)
for elem in ('LOAD_GLOBAL',):[](#l1.66)
self.assertNotIn(elem, asm)[](#l1.67)
for elem in ('LOAD_CONST', '('+name+')'):[](#l1.68)
self.assertIn(elem, asm)[](#l1.69)
for func, elem in ((f, None), (g, True), (h, False)):[](#l1.70)
self.assertNotInBytecode(func, 'LOAD_GLOBAL')[](#l1.71)
self.assertInBytecode(func, 'LOAD_CONST', elem)[](#l1.72) def f():[](#l1.73) 'Adding a docstring made this test fail in Py2.5.0'[](#l1.74) return None[](#l1.75)
self.assertIn('LOAD_CONST', disassemble(f))[](#l1.76)
self.assertNotIn('LOAD_GLOBAL', disassemble(f))[](#l1.77)
self.assertNotInBytecode(f, 'LOAD_GLOBAL')[](#l1.78)
self.assertInBytecode(f, 'LOAD_CONST', None)[](#l1.79)
def test_while_one(self): # Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx @@ -74,11 +55,10 @@ class TestTranforms(unittest.TestCase): while 1: pass return list
asm = disassemble(f)[](#l1.87) for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):[](#l1.88)
self.assertNotIn(elem, asm)[](#l1.89)
self.assertNotInBytecode(f, elem)[](#l1.90) for elem in ('JUMP_ABSOLUTE',):[](#l1.91)
self.assertIn(elem, asm)[](#l1.92)
self.assertInBytecode(f, elem)[](#l1.93)
def test_pack_unpack(self): for line, elem in ( @@ -86,28 +66,30 @@ class TestTranforms(unittest.TestCase): ('a, b = a, b', 'ROT_TWO',), ('a, b, c = a, b, c', 'ROT_THREE',), ):
asm = dis_single(line)[](#l1.101)
self.assertIn(elem, asm)[](#l1.102)
self.assertNotIn('BUILD_TUPLE', asm)[](#l1.103)
self.assertNotIn('UNPACK_TUPLE', asm)[](#l1.104)
code = compile(line,'','single')[](#l1.105)
self.assertInBytecode(code, elem)[](#l1.106)
self.assertNotInBytecode(code, 'BUILD_TUPLE')[](#l1.107)
self.assertNotInBytecode(code, 'UNPACK_TUPLE')[](#l1.108)
def test_folding_of_tuples_of_constants(self): for line, elem in (
('a = 1,2,3', '((1, 2, 3))'),[](#l1.112)
('("a","b","c")', "(('a', 'b', 'c'))"),[](#l1.113)
('a,b,c = 1,2,3', '((1, 2, 3))'),[](#l1.114)
('(None, 1, None)', '((None, 1, None))'),[](#l1.115)
('((1, 2), 3, 4)', '(((1, 2), 3, 4))'),[](#l1.116)
('a = 1,2,3', (1, 2, 3)),[](#l1.117)
('("a","b","c")', ('a', 'b', 'c')),[](#l1.118)
('a,b,c = 1,2,3', (1, 2, 3)),[](#l1.119)
('(None, 1, None)', (None, 1, None)),[](#l1.120)
('((1, 2), 3, 4)', ((1, 2), 3, 4)),[](#l1.121) ):[](#l1.122)
asm = dis_single(line)[](#l1.123)
self.assertIn(elem, asm)[](#l1.124)
self.assertNotIn('BUILD_TUPLE', asm)[](#l1.125)
code = compile(line,'','single')[](#l1.126)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.127)
self.assertNotInBytecode(code, 'BUILD_TUPLE')[](#l1.128)
# Long tuples should be folded too.
asm = dis_single(repr(tuple(range(10000))))[](#l1.131)
code = compile(repr(tuple(range(10000))),'','single')[](#l1.132)
self.assertNotInBytecode(code, 'BUILD_TUPLE')[](#l1.133) # One LOAD_CONST for the tuple, one for the None return value[](#l1.134)
self.assertEqual(asm.count('LOAD_CONST'), 2)[](#l1.135)
self.assertNotIn('BUILD_TUPLE', asm)[](#l1.136)
load_consts = [instr for instr in dis.get_instructions(code)[](#l1.137)
if instr.opname == 'LOAD_CONST'][](#l1.138)
self.assertEqual(len(load_consts), 2)[](#l1.139)
# Bug 1053819: Tuple of constants misidentified when presented with: # . . . opcode_with_arg 100 unary_opcode BUILD_TUPLE 1 . . . @@ -129,14 +111,14 @@ class TestTranforms(unittest.TestCase): def test_folding_of_lists_of_constants(self): for line, elem in ( # in/not in constants with BUILD_LIST should be folded to a tuple:
('a in [1,2,3]', '(1, 2, 3)'),[](#l1.147)
('a not in ["a","b","c"]', "(('a', 'b', 'c'))"),[](#l1.148)
('a in [None, 1, None]', '((None, 1, None))'),[](#l1.149)
('a not in [(1, 2), 3, 4]', '(((1, 2), 3, 4))'),[](#l1.150)
('a in [1,2,3]', (1, 2, 3)),[](#l1.151)
('a not in ["a","b","c"]', ('a', 'b', 'c')),[](#l1.152)
('a in [None, 1, None]', (None, 1, None)),[](#l1.153)
('a not in [(1, 2), 3, 4]', ((1, 2), 3, 4)),[](#l1.154) ):[](#l1.155)
asm = dis_single(line)[](#l1.156)
self.assertIn(elem, asm)[](#l1.157)
self.assertNotIn('BUILD_LIST', asm)[](#l1.158)
code = compile(line, '', 'single')[](#l1.159)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.160)
self.assertNotInBytecode(code, 'BUILD_LIST')[](#l1.161)
def test_folding_of_sets_of_constants(self): for line, elem in ( @@ -147,18 +129,9 @@ class TestTranforms(unittest.TestCase): ('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})), ('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})), ):
asm = dis_single(line)[](#l1.169)
self.assertNotIn('BUILD_SET', asm)[](#l1.170)
# Verify that the frozenset 'elem' is in the disassembly[](#l1.172)
# The ordering of the elements in repr( frozenset ) isn't[](#l1.173)
# guaranteed, so we jump through some hoops to ensure that we have[](#l1.174)
# the frozenset we expect:[](#l1.175)
self.assertIn('frozenset', asm)[](#l1.176)
# Extract the frozenset literal from the disassembly:[](#l1.177)
m = re.match(r'.*(frozenset\({.*}\)).*', asm, re.DOTALL)[](#l1.178)
self.assertTrue(m)[](#l1.179)
self.assertEqual(eval(m.group(1)), elem)[](#l1.180)
code = compile(line, '', 'single')[](#l1.181)
self.assertNotInBytecode(code, 'BUILD_SET')[](#l1.182)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.183)
# Ensure that the resulting code actually works: def f(a): @@ -176,98 +149,103 @@ class TestTranforms(unittest.TestCase): def test_folding_of_binops_on_constants(self): for line, elem in (
('a = 2+3+4', '(9)'), # chained fold[](#l1.191)
('"@"*4', "('@@@@')"), # check string ops[](#l1.192)
('a="abc" + "def"', "('abcdef')"), # check string ops[](#l1.193)
('a = 3**4', '(81)'), # binary power[](#l1.194)
('a = 3*4', '(12)'), # binary multiply[](#l1.195)
('a = 13//4', '(3)'), # binary floor divide[](#l1.196)
('a = 14%4', '(2)'), # binary modulo[](#l1.197)
('a = 2+3', '(5)'), # binary add[](#l1.198)
('a = 13-4', '(9)'), # binary subtract[](#l1.199)
('a = (12,13)[1]', '(13)'), # binary subscr[](#l1.200)
('a = 13 << 2', '(52)'), # binary lshift[](#l1.201)
('a = 13 >> 2', '(3)'), # binary rshift[](#l1.202)
('a = 13 & 7', '(5)'), # binary and[](#l1.203)
('a = 13 ^ 7', '(10)'), # binary xor[](#l1.204)
('a = 13 | 7', '(15)'), # binary or[](#l1.205)
('a = 2+3+4', 9), # chained fold[](#l1.206)
('"@"*4', '@@@@'), # check string ops[](#l1.207)
('a="abc" + "def"', 'abcdef'), # check string ops[](#l1.208)
('a = 3**4', 81), # binary power[](#l1.209)
('a = 3*4', 12), # binary multiply[](#l1.210)
('a = 13//4', 3), # binary floor divide[](#l1.211)
('a = 14%4', 2), # binary modulo[](#l1.212)
('a = 2+3', 5), # binary add[](#l1.213)
('a = 13-4', 9), # binary subtract[](#l1.214)
('a = (12,13)[1]', 13), # binary subscr[](#l1.215)
('a = 13 << 2', 52), # binary lshift[](#l1.216)
('a = 13 >> 2', 3), # binary rshift[](#l1.217)
('a = 13 & 7', 5), # binary and[](#l1.218)
('a = 13 ^ 7', 10), # binary xor[](#l1.219)
('a = 13 | 7', 15), # binary or[](#l1.220) ):[](#l1.221)
asm = dis_single(line)[](#l1.222)
self.assertIn(elem, asm, asm)[](#l1.223)
self.assertNotIn('BINARY_', asm)[](#l1.224)
code = compile(line, '', 'single')[](#l1.225)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.226)
for instr in dis.get_instructions(code):[](#l1.227)
self.assertFalse(instr.opname.startswith('BINARY_'))[](#l1.228)
# Verify that unfoldables are skipped
asm = dis_single('a=2+"b"')[](#l1.231)
self.assertIn('(2)', asm)[](#l1.232)
self.assertIn("('b')", asm)[](#l1.233)
code = compile('a=2+"b"', '', 'single')[](#l1.234)
self.assertInBytecode(code, 'LOAD_CONST', 2)[](#l1.235)
self.assertInBytecode(code, 'LOAD_CONST', 'b')[](#l1.236)
# Verify that large sequences do not result from folding
asm = dis_single('a="x"*1000')[](#l1.239)
self.assertIn('(1000)', asm)[](#l1.240)
code = compile('a="x"*1000', '', 'single')[](#l1.241)
self.assertInBytecode(code, 'LOAD_CONST', 1000)[](#l1.242)
def test_binary_subscr_on_unicode(self): # valid code get optimized
asm = dis_single('"foo"[0]')[](#l1.246)
self.assertIn("('f')", asm)[](#l1.247)
self.assertNotIn('BINARY_SUBSCR', asm)[](#l1.248)
asm = dis_single('"\u0061\uffff"[1]')[](#l1.249)
self.assertIn("('\\uffff')", asm)[](#l1.250)
self.assertNotIn('BINARY_SUBSCR', asm)[](#l1.251)
asm = dis_single('"\U00012345abcdef"[3]')[](#l1.252)
self.assertIn("('c')", asm)[](#l1.253)
self.assertNotIn('BINARY_SUBSCR', asm)[](#l1.254)
code = compile('"foo"[0]', '', 'single')[](#l1.255)
self.assertInBytecode(code, 'LOAD_CONST', 'f')[](#l1.256)
self.assertNotInBytecode(code, 'BINARY_SUBSCR')[](#l1.257)
code = compile('"\u0061\uffff"[1]', '', 'single')[](#l1.258)
self.assertInBytecode(code, 'LOAD_CONST', '\uffff')[](#l1.259)
self.assertNotInBytecode(code,'BINARY_SUBSCR')[](#l1.260)
# With PEP 393, non-BMP char get optimized[](#l1.262)
code = compile('"\U00012345"[0]', '', 'single')[](#l1.263)
self.assertInBytecode(code, 'LOAD_CONST', '\U00012345')[](#l1.264)
self.assertNotInBytecode(code, 'BINARY_SUBSCR')[](#l1.265)
# invalid code doesn't get optimized # out of range
asm = dis_single('"fuu"[10]')[](#l1.269)
self.assertIn('BINARY_SUBSCR', asm)[](#l1.270)
code = compile('"fuu"[10]', '', 'single')[](#l1.271)
self.assertInBytecode(code, 'BINARY_SUBSCR')[](#l1.272)
def test_folding_of_unaryops_on_constants(self): for line, elem in (
('-0.5', '(-0.5)'), # unary negative[](#l1.276)
('-0.0', '(-0.0)'), # -0.0[](#l1.277)
('-(1.0-1.0)','(-0.0)'), # -0.0 after folding[](#l1.278)
('-0', '(0)'), # -0[](#l1.279)
('~-2', '(1)'), # unary invert[](#l1.280)
('+1', '(1)'), # unary positive[](#l1.281)
('-0.5', -0.5), # unary negative[](#l1.282)
('-0.0', -0.0), # -0.0[](#l1.283)
('-(1.0-1.0)', -0.0), # -0.0 after folding[](#l1.284)
('-0', 0), # -0[](#l1.285)
('~-2', 1), # unary invert[](#l1.286)
('+1', 1), # unary positive[](#l1.287) ):[](#l1.288)
asm = dis_single(line)[](#l1.289)
self.assertIn(elem, asm, asm)[](#l1.290)
self.assertNotIn('UNARY_', asm)[](#l1.291)
code = compile(line, '', 'single')[](#l1.292)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.293)
for instr in dis.get_instructions(code):[](#l1.294)
self.assertFalse(instr.opname.startswith('UNARY_'))[](#l1.295)
# Check that -0.0 works after marshaling def negzero(): return -(1.0-1.0)
self.assertNotIn('UNARY_', disassemble(negzero))[](#l1.301)
self.assertTrue(copysign(1.0, negzero()) < 0)[](#l1.302)
for instr in dis.get_instructions(code):[](#l1.303)
self.assertFalse(instr.opname.startswith('UNARY_'))[](#l1.304)
# Verify that unfoldables are skipped
for line, elem in ([](#l1.307)
('-"abc"', "('abc')"), # unary negative[](#l1.308)
('~"abc"', "('abc')"), # unary invert[](#l1.309)
for line, elem, opname in ([](#l1.310)
('-"abc"', 'abc', 'UNARY_NEGATIVE'),[](#l1.311)
('~"abc"', 'abc', 'UNARY_INVERT'),[](#l1.312) ):[](#l1.313)
asm = dis_single(line)[](#l1.314)
self.assertIn(elem, asm, asm)[](#l1.315)
self.assertIn('UNARY_', asm)[](#l1.316)
code = compile(line, '', 'single')[](#l1.317)
self.assertInBytecode(code, 'LOAD_CONST', elem)[](#l1.318)
self.assertInBytecode(code, opname)[](#l1.319)
def test_elim_extra_return(self): # RETURN LOAD_CONST None RETURN --> RETURN def f(x): return x
asm = disassemble(f)[](#l1.325)
self.assertNotIn('LOAD_CONST', asm)[](#l1.326)
self.assertNotIn('(None)', asm)[](#l1.327)
self.assertEqual(asm.split().count('RETURN_VALUE'), 1)[](#l1.328)
self.assertNotInBytecode(f, 'LOAD_CONST', None)[](#l1.329)
returns = [instr for instr in dis.get_instructions(f)[](#l1.330)
if instr.opname == 'RETURN_VALUE'][](#l1.331)
self.assertEqual(len(returns), 1)[](#l1.332)
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
asm = disassemble(f)[](#l1.338)
self.assertNotIn('JUMP_FORWARD', asm)[](#l1.339)
self.assertNotIn('JUMP_ABSOLUTE', asm)[](#l1.340)
self.assertEqual(asm.split().count('RETURN_VALUE'), 2)[](#l1.341)
self.assertNotInBytecode(f, 'JUMP_FORWARD')[](#l1.342)
self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')[](#l1.343)
returns = [instr for instr in dis.get_instructions(f)[](#l1.344)
if instr.opname == 'RETURN_VALUE'][](#l1.345)
self.assertEqual(len(returns), 2)[](#l1.346)
def test_elim_jump_after_return1(self): # Eliminate dead code: jumps immediately after returns can't be reached @@ -280,48 +258,53 @@ class TestTranforms(unittest.TestCase): if cond1: return 4 return 5 return 6
asm = disassemble(f)[](#l1.354)
self.assertNotIn('JUMP_FORWARD', asm)[](#l1.355)
self.assertNotIn('JUMP_ABSOLUTE', asm)[](#l1.356)
self.assertEqual(asm.split().count('RETURN_VALUE'), 6)[](#l1.357)
self.assertNotInBytecode(f, 'JUMP_FORWARD')[](#l1.358)
self.assertNotInBytecode(f, 'JUMP_ABSOLUTE')[](#l1.359)
returns = [instr for instr in dis.get_instructions(f)[](#l1.360)
if instr.opname == 'RETURN_VALUE'][](#l1.361)
self.assertEqual(len(returns), 6)[](#l1.362)
def test_elim_jump_after_return2(self): # Eliminate dead code: jumps immediately after returns can't be reached def f(cond1, cond2): while 1: if cond1: return 4
asm = disassemble(f)[](#l1.369)
self.assertNotIn('JUMP_FORWARD', asm)[](#l1.370)
self.assertNotInBytecode(f, 'JUMP_FORWARD')[](#l1.371) # There should be one jump for the while loop.[](#l1.372)
self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1)[](#l1.373)
self.assertEqual(asm.split().count('RETURN_VALUE'), 2)[](#l1.374)
returns = [instr for instr in dis.get_instructions(f)[](#l1.375)
if instr.opname == 'JUMP_ABSOLUTE'][](#l1.376)
self.assertEqual(len(returns), 1)[](#l1.377)
returns = [instr for instr in dis.get_instructions(f)[](#l1.378)
if instr.opname == 'RETURN_VALUE'][](#l1.379)
self.assertEqual(len(returns), 2)[](#l1.380)
def test_make_function_doesnt_bail(self): def f(): def g()->1+1: pass return g
asm = disassemble(f)[](#l1.387)
self.assertNotIn('BINARY_ADD', asm)[](#l1.388)
self.assertNotInBytecode(f, 'BINARY_ADD')[](#l1.389)
def test_constant_folding(self): # Issue #11244: aggressive constant folding. exprs = [
"3 * -5",[](#l1.394)
"-3 * 5",[](#l1.395)
"2 * (3 * 4)",[](#l1.396)
"(2 * 3) * 4",[](#l1.397)
"(-1, 2, 3)",[](#l1.398)
"(1, -2, 3)",[](#l1.399)
"(1, 2, -3)",[](#l1.400)
"(1, 2, -3) * 6",[](#l1.401)
"lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}",[](#l1.402)
'3 * -5',[](#l1.403)
'-3 * 5',[](#l1.404)
'2 * (3 * 4)',[](#l1.405)
'(2 * 3) * 4',[](#l1.406)
'(-1, 2, 3)',[](#l1.407)
'(1, -2, 3)',[](#l1.408)
'(1, 2, -3)',[](#l1.409)
'(1, 2, -3) * 6',[](#l1.410)
'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',[](#l1.411) ][](#l1.412) for e in exprs:[](#l1.413)
asm = dis_single(e)[](#l1.414)
self.assertNotIn('UNARY_', asm, e)[](#l1.415)
self.assertNotIn('BINARY_', asm, e)[](#l1.416)
self.assertNotIn('BUILD_', asm, e)[](#l1.417)
code = compile(e, '', 'single')[](#l1.418)
for instr in dis.get_instructions(code):[](#l1.419)
self.assertFalse(instr.opname.startswith('UNARY_'))[](#l1.420)
self.assertFalse(instr.opname.startswith('BINARY_'))[](#l1.421)
self.assertFalse(instr.opname.startswith('BUILD_'))[](#l1.422)
+ class TestBuglets(unittest.TestCase): @@ -343,7 +326,7 @@ def test_main(verbose=None): support.run_unittest(*test_classes) # verify reference counting