test_peepholer.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import dis
  2. import sys
  3. from cStringIO import StringIO
  4. import unittest
  5. def disassemble(func):
  6. f = StringIO()
  7. tmp = sys.stdout
  8. sys.stdout = f
  9. dis.dis(func)
  10. sys.stdout = tmp
  11. result = f.getvalue()
  12. f.close()
  13. return result
  14. def dis_single(line):
  15. return disassemble(compile(line, '', 'single'))
  16. class TestTranforms(unittest.TestCase):
  17. def test_unot(self):
  18. # UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE
  19. def unot(x):
  20. if not x == 2:
  21. del x
  22. asm = disassemble(unot)
  23. for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'):
  24. self.assertNotIn(elem, asm)
  25. self.assertIn('POP_JUMP_IF_TRUE', asm)
  26. def test_elim_inversion_of_is_or_in(self):
  27. for line, elem in (
  28. ('not a is b', '(is not)',),
  29. ('not a in b', '(not in)',),
  30. ('not a is not b', '(is)',),
  31. ('not a not in b', '(in)',),
  32. ):
  33. asm = dis_single(line)
  34. self.assertIn(elem, asm)
  35. def test_none_as_constant(self):
  36. # LOAD_GLOBAL None --> LOAD_CONST None
  37. def f(x):
  38. None
  39. return x
  40. asm = disassemble(f)
  41. for elem in ('LOAD_GLOBAL',):
  42. self.assertNotIn(elem, asm)
  43. for elem in ('LOAD_CONST', '(None)'):
  44. self.assertIn(elem, asm)
  45. def f():
  46. 'Adding a docstring made this test fail in Py2.5.0'
  47. return None
  48. self.assertIn('LOAD_CONST', disassemble(f))
  49. self.assertNotIn('LOAD_GLOBAL', disassemble(f))
  50. def test_while_one(self):
  51. # Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx
  52. def f():
  53. while 1:
  54. pass
  55. return list
  56. asm = disassemble(f)
  57. for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
  58. self.assertNotIn(elem, asm)
  59. for elem in ('JUMP_ABSOLUTE',):
  60. self.assertIn(elem, asm)
  61. def test_pack_unpack(self):
  62. for line, elem in (
  63. ('a, = a,', 'LOAD_CONST',),
  64. ('a, b = a, b', 'ROT_TWO',),
  65. ('a, b, c = a, b, c', 'ROT_THREE',),
  66. ):
  67. asm = dis_single(line)
  68. self.assertIn(elem, asm)
  69. self.assertNotIn('BUILD_TUPLE', asm)
  70. self.assertNotIn('UNPACK_TUPLE', asm)
  71. def test_folding_of_tuples_of_constants(self):
  72. for line, elem in (
  73. ('a = 1,2,3', '((1, 2, 3))'),
  74. ('("a","b","c")', "(('a', 'b', 'c'))"),
  75. ('a,b,c = 1,2,3', '((1, 2, 3))'),
  76. ('(None, 1, None)', '((None, 1, None))'),
  77. ('((1, 2), 3, 4)', '(((1, 2), 3, 4))'),
  78. ):
  79. asm = dis_single(line)
  80. self.assertIn(elem, asm)
  81. self.assertNotIn('BUILD_TUPLE', asm)
  82. # Bug 1053819: Tuple of constants misidentified when presented with:
  83. # . . . opcode_with_arg 100 unary_opcode BUILD_TUPLE 1 . . .
  84. # The following would segfault upon compilation
  85. def crater():
  86. (~[
  87. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  88. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  89. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  90. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  91. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  92. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  93. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  94. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  95. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  96. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
  97. ],)
  98. def test_folding_of_binops_on_constants(self):
  99. for line, elem in (
  100. ('a = 2+3+4', '(9)'), # chained fold
  101. ('"@"*4', "('@@@@')"), # check string ops
  102. ('a="abc" + "def"', "('abcdef')"), # check string ops
  103. ('a = 3**4', '(81)'), # binary power
  104. ('a = 3*4', '(12)'), # binary multiply
  105. ('a = 13//4', '(3)'), # binary floor divide
  106. ('a = 14%4', '(2)'), # binary modulo
  107. ('a = 2+3', '(5)'), # binary add
  108. ('a = 13-4', '(9)'), # binary subtract
  109. ('a = (12,13)[1]', '(13)'), # binary subscr
  110. ('a = 13 << 2', '(52)'), # binary lshift
  111. ('a = 13 >> 2', '(3)'), # binary rshift
  112. ('a = 13 & 7', '(5)'), # binary and
  113. ('a = 13 ^ 7', '(10)'), # binary xor
  114. ('a = 13 | 7', '(15)'), # binary or
  115. ):
  116. asm = dis_single(line)
  117. self.assertIn(elem, asm, asm)
  118. self.assertNotIn('BINARY_', asm)
  119. # Verify that unfoldables are skipped
  120. asm = dis_single('a=2+"b"')
  121. self.assertIn('(2)', asm)
  122. self.assertIn("('b')", asm)
  123. # Verify that large sequences do not result from folding
  124. asm = dis_single('a="x"*1000')
  125. self.assertIn('(1000)', asm)
  126. def test_binary_subscr_on_unicode(self):
  127. # unicode strings don't get optimized
  128. asm = dis_single('u"foo"[0]')
  129. self.assertNotIn("(u'f')", asm)
  130. self.assertIn('BINARY_SUBSCR', asm)
  131. asm = dis_single('u"\u0061\uffff"[1]')
  132. self.assertNotIn("(u'\\uffff')", asm)
  133. self.assertIn('BINARY_SUBSCR', asm)
  134. # out of range
  135. asm = dis_single('u"fuu"[10]')
  136. self.assertIn('BINARY_SUBSCR', asm)
  137. # non-BMP char (see #5057)
  138. asm = dis_single('u"\U00012345"[0]')
  139. self.assertIn('BINARY_SUBSCR', asm)
  140. asm = dis_single('u"\U00012345abcdef"[3]')
  141. self.assertIn('BINARY_SUBSCR', asm)
  142. def test_folding_of_unaryops_on_constants(self):
  143. for line, elem in (
  144. ('`1`', "('1')"), # unary convert
  145. ('-0.5', '(-0.5)'), # unary negative
  146. ('~-2', '(1)'), # unary invert
  147. ):
  148. asm = dis_single(line)
  149. self.assertIn(elem, asm, asm)
  150. self.assertNotIn('UNARY_', asm)
  151. # Verify that unfoldables are skipped
  152. for line, elem in (
  153. ('-"abc"', "('abc')"), # unary negative
  154. ('~"abc"', "('abc')"), # unary invert
  155. ):
  156. asm = dis_single(line)
  157. self.assertIn(elem, asm, asm)
  158. self.assertIn('UNARY_', asm)
  159. def test_elim_extra_return(self):
  160. # RETURN LOAD_CONST None RETURN --> RETURN
  161. def f(x):
  162. return x
  163. asm = disassemble(f)
  164. self.assertNotIn('LOAD_CONST', asm)
  165. self.assertNotIn('(None)', asm)
  166. self.assertEqual(asm.split().count('RETURN_VALUE'), 1)
  167. def test_elim_jump_to_return(self):
  168. # JUMP_FORWARD to RETURN --> RETURN
  169. def f(cond, true_value, false_value):
  170. return true_value if cond else false_value
  171. asm = disassemble(f)
  172. self.assertNotIn('JUMP_FORWARD', asm)
  173. self.assertNotIn('JUMP_ABSOLUTE', asm)
  174. self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
  175. def test_elim_jump_after_return1(self):
  176. # Eliminate dead code: jumps immediately after returns can't be reached
  177. def f(cond1, cond2):
  178. if cond1: return 1
  179. if cond2: return 2
  180. while 1:
  181. return 3
  182. while 1:
  183. if cond1: return 4
  184. return 5
  185. return 6
  186. asm = disassemble(f)
  187. self.assertNotIn('JUMP_FORWARD', asm)
  188. self.assertNotIn('JUMP_ABSOLUTE', asm)
  189. self.assertEqual(asm.split().count('RETURN_VALUE'), 6)
  190. def test_elim_jump_after_return2(self):
  191. # Eliminate dead code: jumps immediately after returns can't be reached
  192. def f(cond1, cond2):
  193. while 1:
  194. if cond1: return 4
  195. asm = disassemble(f)
  196. self.assertNotIn('JUMP_FORWARD', asm)
  197. # There should be one jump for the while loop.
  198. self.assertEqual(asm.split().count('JUMP_ABSOLUTE'), 1)
  199. self.assertEqual(asm.split().count('RETURN_VALUE'), 2)
  200. def test_main(verbose=None):
  201. import sys
  202. from test import test_support
  203. test_classes = (TestTranforms,)
  204. with test_support.check_py3k_warnings(
  205. ("backquote not supported", SyntaxWarning)):
  206. test_support.run_unittest(*test_classes)
  207. # verify reference counting
  208. if verbose and hasattr(sys, "gettotalrefcount"):
  209. import gc
  210. counts = [None] * 5
  211. for i in xrange(len(counts)):
  212. test_support.run_unittest(*test_classes)
  213. gc.collect()
  214. counts[i] = sys.gettotalrefcount()
  215. print counts
  216. if __name__ == "__main__":
  217. test_main(verbose=True)