PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/module/pypyjit/test_pypy_c/test_00_model.py

https://bitbucket.org/pypy/pypy/
Python | 629 lines | 586 code | 14 blank | 29 comment | 18 complexity | 67101bda15c1bc6fc7bd506f4d2ac9db MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from __future__ import with_statement
  2. import sys, os
  3. import types
  4. import subprocess
  5. import py
  6. from rpython.tool import disassembler
  7. from rpython.tool.udir import udir
  8. from rpython.tool import logparser
  9. from rpython.jit.tool.jitoutput import parse_prof
  10. from pypy.module.pypyjit.test_pypy_c.model import \
  11. Log, find_ids_range, find_ids, OpMatcher, InvalidMatch
  12. class BaseTestPyPyC(object):
  13. log_string = 'jit-log-opt,jit-log-noopt,jit-log-virtualstate,jit-summary'
  14. def setup_class(cls):
  15. if '__pypy__' not in sys.builtin_module_names:
  16. py.test.skip("must run this test with pypy")
  17. if not sys.pypy_translation_info['translation.jit']:
  18. py.test.skip("must give a pypy-c with the jit enabled")
  19. cls.tmpdir = udir.join('test-pypy-jit')
  20. cls.tmpdir.ensure(dir=True)
  21. def setup_method(self, meth):
  22. self.filepath = self.tmpdir.join(meth.im_func.func_name + '.py')
  23. def run(self, func_or_src, args=[], import_site=False,
  24. discard_stdout_before_last_line=False, **jitopts):
  25. jitopts.setdefault('threshold', 200)
  26. jitopts.setdefault('disable_unrolling', 9999)
  27. src = py.code.Source(func_or_src)
  28. if isinstance(func_or_src, types.FunctionType):
  29. funcname = func_or_src.func_name
  30. else:
  31. funcname = 'main'
  32. # write the snippet
  33. arglist = ', '.join(map(repr, args))
  34. with self.filepath.open("w") as f:
  35. # we don't want to see the small bridges created
  36. # by the checkinterval reaching the limit
  37. f.write("import sys\n")
  38. f.write("sys.setcheckinterval(10000000)\n")
  39. f.write(str(src) + "\n")
  40. f.write("print %s(%s)\n" % (funcname, arglist))
  41. #
  42. # run a child pypy-c with logging enabled
  43. logfile = self.filepath.new(ext='.log')
  44. #
  45. cmdline = [sys.executable]
  46. if not import_site:
  47. cmdline.append('-S')
  48. if jitopts:
  49. jitcmdline = ['%s=%s' % (key, value)
  50. for key, value in jitopts.items()]
  51. cmdline += ['--jit', ','.join(jitcmdline)]
  52. cmdline.append(str(self.filepath))
  53. #
  54. env = os.environ.copy()
  55. # TODO old logging system
  56. env['PYPYLOG'] = self.log_string + ':' + str(logfile)
  57. jitlogfile = str(logfile) + '.jlog'
  58. env['JITLOG'] = str(jitlogfile)
  59. pipe = subprocess.Popen(cmdline,
  60. env=env,
  61. stdout=subprocess.PIPE,
  62. stderr=subprocess.PIPE)
  63. stdout, stderr = pipe.communicate()
  64. if pipe.wait() < 0:
  65. raise IOError("subprocess was killed by signal %d" % (
  66. pipe.returncode,))
  67. if stderr.startswith('SKIP:'):
  68. py.test.skip(stderr)
  69. #if stderr.startswith('debug_alloc.h:'): # lldebug builds
  70. # stderr = ''
  71. #assert not stderr
  72. if stderr:
  73. print '*** stderr of the subprocess: ***'
  74. print stderr
  75. #
  76. if discard_stdout_before_last_line:
  77. stdout = stdout.splitlines(True)[-1]
  78. #
  79. # parse the JIT log
  80. rawlog = logparser.parse_log_file(str(logfile), verbose=False)
  81. rawtraces = logparser.extract_category(rawlog, 'jit-log-opt-')
  82. log = Log(rawtraces)
  83. log.result = eval(stdout)
  84. log.logfile = str(logfile)
  85. log.jitlogfile = jitlogfile
  86. #
  87. summaries = logparser.extract_category(rawlog, 'jit-summary')
  88. if len(summaries) > 0:
  89. log.jit_summary = parse_prof(summaries[-1])
  90. else:
  91. log.jit_summary = None
  92. #
  93. return log
  94. def run_and_check(self, src, args=[], **jitopts):
  95. log1 = self.run(src, args, threshold=-1) # without the JIT
  96. log2 = self.run(src, args, **jitopts) # with the JIT
  97. assert log1.result == log2.result
  98. # check that the JIT actually ran
  99. assert len(log2.loops_by_filename(self.filepath)) > 0
  100. class TestLog(object):
  101. def test_find_ids_range(self):
  102. def f():
  103. a = 0 # ID: myline
  104. return a
  105. #
  106. start_lineno = f.func_code.co_firstlineno
  107. code = disassembler.dis(f)
  108. ids = find_ids_range(code)
  109. assert len(ids) == 1
  110. myline_range = ids['myline']
  111. assert list(myline_range) == range(start_lineno+1, start_lineno+2)
  112. def test_find_ids(self):
  113. def f():
  114. i = 0
  115. x = 0
  116. z = x + 3 # ID: myline
  117. return z
  118. #
  119. code = disassembler.dis(f)
  120. ids = find_ids(code)
  121. assert len(ids) == 1
  122. myline = ids['myline']
  123. opcodes_names = [opcode.__class__.__name__ for opcode in myline]
  124. assert opcodes_names == ['LOAD_FAST', 'LOAD_CONST', 'BINARY_ADD', 'STORE_FAST']
  125. class TestOpMatcher_(object):
  126. def match(self, src1, src2, **kwds):
  127. from rpython.tool.jitlogparser.parser import SimpleParser
  128. loop = SimpleParser.parse_from_input(src1)
  129. matcher = OpMatcher(loop.operations)
  130. try:
  131. res = matcher.match(src2, **kwds)
  132. assert res is True
  133. return True
  134. except InvalidMatch:
  135. return False
  136. def test_match_var(self):
  137. match_var = OpMatcher([]).match_var
  138. assert match_var('v0', 'V0')
  139. assert not match_var('v0', 'V1')
  140. assert match_var('v0', 'V0')
  141. #
  142. # for ConstPtr, we allow the same alpha-renaming as for variables
  143. assert match_var('ConstPtr(ptr0)', 'PTR0')
  144. assert not match_var('ConstPtr(ptr0)', 'PTR1')
  145. assert match_var('ConstPtr(ptr0)', 'PTR0')
  146. #
  147. # for ConstClass, we want the exact matching
  148. assert match_var('ConstClass(foo)', 'ConstClass(foo)')
  149. assert not match_var('ConstClass(bar)', 'v1')
  150. assert not match_var('v2', 'ConstClass(baz)')
  151. #
  152. # the var '_' matches everything (but only on the right, of course)
  153. assert match_var('v0', '_')
  154. assert match_var('v0', 'V0')
  155. assert match_var('ConstPtr(ptr0)', '_')
  156. py.test.raises(AssertionError, "match_var('_', 'v0')")
  157. #
  158. # numerics
  159. assert match_var('1234', '1234')
  160. assert not match_var('1234', '1235')
  161. assert not match_var('v0', '1234')
  162. assert not match_var('1234', 'v0')
  163. assert match_var('1234', '#') # the '#' char matches any number
  164. assert not match_var('v0', '#')
  165. assert match_var('1234', '_') # the '_' char matches anything
  166. #
  167. # float numerics
  168. assert match_var('0.000000', '0.0')
  169. assert not match_var('0.000000', '0')
  170. assert not match_var('0', '0.0')
  171. assert not match_var('v0', '0.0')
  172. assert not match_var('0.0', 'v0')
  173. assert match_var('0.0', '#')
  174. assert match_var('0.0', '_')
  175. def test_parse_op(self):
  176. res = OpMatcher.parse_op(" a = int_add( b, 3 ) # foo")
  177. assert res == ("int_add", "a", ["b", "3"], None, True)
  178. res = OpMatcher.parse_op("guard_true(a)")
  179. assert res == ("guard_true", None, ["a"], None, True)
  180. res = OpMatcher.parse_op("setfield_gc(p0, i0, descr=<foobar>)")
  181. assert res == ("setfield_gc", None, ["p0", "i0"], "<foobar>", True)
  182. res = OpMatcher.parse_op("i1 = getfield_gc(p0, descr=<foobar>)")
  183. assert res == ("getfield_gc", "i1", ["p0"], "<foobar>", True)
  184. res = OpMatcher.parse_op("p0 = force_token()")
  185. assert res == ("force_token", "p0", [], None, True)
  186. res = OpMatcher.parse_op("guard_not_invalidated?")
  187. assert res == ("guard_not_invalidated", None, [], '...', False)
  188. def test_exact_match(self):
  189. loop = """
  190. [i0]
  191. i2 = int_add(i0, 1)
  192. jump(i2)
  193. """
  194. expected = """
  195. i5 = int_add(i2, 1)
  196. jump(i5, descr=...)
  197. """
  198. assert self.match(loop, expected)
  199. #
  200. expected = """
  201. i5 = int_sub(i2, 1)
  202. jump(i5, descr=...)
  203. """
  204. assert not self.match(loop, expected)
  205. #
  206. expected = """
  207. i5 = int_add(i2, 1)
  208. jump(i5, descr=...)
  209. extra_stuff(i5)
  210. """
  211. assert not self.match(loop, expected)
  212. #
  213. expected = """
  214. i5 = int_add(i2, 1)
  215. # missing op at the end
  216. """
  217. assert not self.match(loop, expected)
  218. #
  219. expected = """
  220. i5 = int_add(i2, 2)
  221. jump(i5, descr=...)
  222. """
  223. assert not self.match(loop, expected)
  224. def test_dotdotdot_in_operation(self):
  225. loop = """
  226. [i0, i1]
  227. jit_debug(i0, 1, ConstClass(myclass), i1)
  228. """
  229. assert self.match(loop, "jit_debug(...)")
  230. assert self.match(loop, "jit_debug(i0, ...)")
  231. assert self.match(loop, "jit_debug(i0, 1, ...)")
  232. assert self.match(loop, "jit_debug(i0, 1, _, ...)")
  233. assert self.match(loop, "jit_debug(i0, 1, _, i1, ...)")
  234. py.test.raises(AssertionError, self.match,
  235. loop, "jit_debug(i0, 1, ..., i1)")
  236. def test_match_descr(self):
  237. loop = """
  238. [p0]
  239. setfield_gc(p0, 1, descr=<foobar>)
  240. """
  241. assert self.match(loop, "setfield_gc(p0, 1, descr=<foobar>)")
  242. assert self.match(loop, "setfield_gc(p0, 1, descr=...)")
  243. assert self.match(loop, "setfield_gc(p0, 1, descr=<.*bar>)")
  244. assert not self.match(loop, "setfield_gc(p0, 1)")
  245. assert not self.match(loop, "setfield_gc(p0, 1, descr=<zzz>)")
  246. def test_partial_match(self):
  247. loop = """
  248. [i0]
  249. i1 = int_add(i0, 1)
  250. i2 = int_sub(i1, 10)
  251. i3 = int_xor(i2, 100)
  252. i4 = int_mul(i1, 1000)
  253. jump(i4)
  254. """
  255. expected = """
  256. i1 = int_add(i0, 1)
  257. ...
  258. i4 = int_mul(i1, 1000)
  259. jump(i4, descr=...)
  260. """
  261. assert self.match(loop, expected)
  262. def test_partial_match_is_non_greedy(self):
  263. loop = """
  264. [i0]
  265. i1 = int_add(i0, 1)
  266. i2 = int_sub(i1, 10)
  267. i3 = int_mul(i2, 1000)
  268. i4 = int_mul(i1, 1000)
  269. jump(i4, descr=...)
  270. """
  271. expected = """
  272. i1 = int_add(i0, 1)
  273. ...
  274. _ = int_mul(_, 1000)
  275. jump(i4, descr=...)
  276. """
  277. # this does not match, because the ... stops at the first int_mul, and
  278. # then the second one does not match
  279. assert not self.match(loop, expected)
  280. def test_partial_match_at_the_end(self):
  281. loop = """
  282. [i0]
  283. i1 = int_add(i0, 1)
  284. i2 = int_sub(i1, 10)
  285. i3 = int_xor(i2, 100)
  286. i4 = int_mul(i1, 1000)
  287. jump(i4)
  288. """
  289. expected = """
  290. i1 = int_add(i0, 1)
  291. ...
  292. """
  293. assert self.match(loop, expected)
  294. def test_ignore_opcodes(self):
  295. loop = """
  296. [i0]
  297. i1 = int_add(i0, 1)
  298. i4 = force_token()
  299. i2 = int_sub(i1, 10)
  300. jump(i4)
  301. """
  302. expected = """
  303. i1 = int_add(i0, 1)
  304. i2 = int_sub(i1, 10)
  305. jump(i4, descr=...)
  306. """
  307. assert self.match(loop, expected, ignore_ops=['force_token'])
  308. #
  309. loop = """
  310. [i0]
  311. i1 = int_add(i0, 1)
  312. i4 = force_token()
  313. """
  314. expected = """
  315. i1 = int_add(i0, 1)
  316. """
  317. assert self.match(loop, expected, ignore_ops=['force_token'])
  318. def test_match_dots_in_arguments(self):
  319. loop = """
  320. [i0]
  321. i1 = int_add(0, 1)
  322. jump(i4, descr=...)
  323. """
  324. expected = """
  325. i1 = int_add(...)
  326. jump(i4, descr=...)
  327. """
  328. assert self.match(loop, expected)
  329. def test_match_any_order(self):
  330. loop = """
  331. [i0, i1]
  332. i2 = int_add(i0, 1)
  333. i3 = int_add(i1, 2)
  334. jump(i2, i3, descr=...)
  335. """
  336. expected = """
  337. {{{
  338. i2 = int_add(i0, 1)
  339. i3 = int_add(i1, 2)
  340. }}}
  341. jump(i2, i3, descr=...)
  342. """
  343. assert self.match(loop, expected)
  344. #
  345. expected = """
  346. {{{
  347. i3 = int_add(i1, 2)
  348. i2 = int_add(i0, 1)
  349. }}}
  350. jump(i2, i3, descr=...)
  351. """
  352. assert self.match(loop, expected)
  353. #
  354. expected = """
  355. {{{
  356. i2 = int_add(i0, 1)
  357. i3 = int_add(i1, 2)
  358. i4 = int_add(i1, 3)
  359. }}}
  360. jump(i2, i3, descr=...)
  361. """
  362. assert not self.match(loop, expected)
  363. #
  364. expected = """
  365. {{{
  366. i2 = int_add(i0, 1)
  367. }}}
  368. jump(i2, i3, descr=...)
  369. """
  370. assert not self.match(loop, expected)
  371. def test_match_optional_op(self):
  372. loop = """
  373. i1 = int_add(i0, 1)
  374. """
  375. expected = """
  376. guard_not_invalidated?
  377. i1 = int_add(i0, 1)
  378. """
  379. assert self.match(loop, expected)
  380. #
  381. loop = """
  382. i1 = int_add(i0, 1)
  383. """
  384. expected = """
  385. i1 = int_add(i0, 1)
  386. guard_not_invalidated?
  387. """
  388. assert self.match(loop, expected)
  389. class TestRunPyPyC(BaseTestPyPyC):
  390. def test_run_function(self):
  391. def f(a, b):
  392. return a+b
  393. log = self.run(f, [30, 12])
  394. assert log.result == 42
  395. def test_run_src(self):
  396. src = """
  397. def f(a, b):
  398. return a+b
  399. def main(a, b):
  400. return f(a, b)
  401. """
  402. log = self.run(src, [30, 12])
  403. assert log.result == 42
  404. def test_skip(self):
  405. import pytest
  406. def f():
  407. import sys
  408. print >> sys.stderr, 'SKIP: foobar'
  409. #
  410. raises(pytest.skip.Exception, "self.run(f, [])")
  411. def test_parse_jitlog(self):
  412. def f():
  413. i = 0
  414. while i < 1003:
  415. i += 1
  416. return i
  417. #
  418. log = self.run(f)
  419. assert log.result == 1003
  420. loops = log.loops_by_filename(self.filepath)
  421. assert len(loops) == 1
  422. assert loops[0].filename == self.filepath
  423. assert len([op for op in loops[0].allops() if op.name == 'label']) == 0
  424. assert len([op for op in loops[0].allops() if op.name == 'guard_nonnull_class']) == 0
  425. #
  426. loops = log.loops_by_filename(self.filepath, is_entry_bridge=True)
  427. assert len(loops) == 1
  428. assert len([op for op in loops[0].allops() if op.name == 'label']) == 0
  429. assert len([op for op in loops[0].allops() if op.name == 'guard_nonnull_class']) > 0
  430. #
  431. loops = log.loops_by_filename(self.filepath, is_entry_bridge='*')
  432. assert len(loops) == 1
  433. assert len([op for op in loops[0].allops() if op.name == 'label']) == 2
  434. def test_loops_by_id(self):
  435. def f():
  436. i = 0
  437. while i < 1003:
  438. i += 1 # ID: increment
  439. return i
  440. #
  441. log = self.run(f)
  442. loop, = log.loops_by_id('increment')
  443. assert loop.filename == self.filepath
  444. assert loop.code.co.co_name == 'f'
  445. #
  446. ops = loop.allops()
  447. assert log.opnames(ops) == [
  448. # this is the actual loop
  449. 'int_lt', 'guard_true', 'int_add',
  450. # this is the signal checking stuff
  451. 'guard_not_invalidated', 'getfield_raw_i', 'int_lt', 'guard_false',
  452. 'jump'
  453. ]
  454. def test_ops_by_id(self):
  455. def f():
  456. i = 0
  457. while i < 1003: # ID: cond
  458. i += 1 # ID: increment
  459. a = 0 # to make sure that JUMP_ABSOLUTE is not part of the ID
  460. return i
  461. #
  462. log = self.run(f)
  463. loop, = log.loops_by_id('increment')
  464. #
  465. ops = loop.ops_by_id('increment')
  466. assert log.opnames(ops) == ['int_add']
  467. #
  468. ops = loop.ops_by_id('cond')
  469. # the 'jump' at the end is because the last opcode in the loop
  470. # coincides with the first, and so it thinks that 'jump' belongs to
  471. # the id
  472. assert log.opnames(ops) == ['int_lt', 'guard_true', 'jump']
  473. def test_ops_by_id_and_opcode(self):
  474. def f():
  475. i = 0
  476. j = 0
  477. while i < 1003:
  478. i += 1; j -= 1 # ID: foo
  479. a = 0 # to make sure that JUMP_ABSOLUTE is not part of the ID
  480. return i
  481. #
  482. log = self.run(f)
  483. loop, = log.loops_by_id('foo')
  484. #
  485. ops = loop.ops_by_id('foo', opcode='INPLACE_ADD')
  486. assert log.opnames(ops) == ['int_add']
  487. #
  488. ops = loop.ops_by_id('foo', opcode='INPLACE_SUBTRACT')
  489. assert log.opnames(ops) == ['int_sub_ovf', 'guard_no_overflow']
  490. def test_inlined_function(self):
  491. def f():
  492. def g(x):
  493. return x+1 # ID: add
  494. i = 0
  495. while i < 1003:
  496. i = g(i) # ID: call
  497. a = 0 # to make sure that JUMP_ABSOLUTE is not part of the ID
  498. return i
  499. #
  500. log = self.run(f)
  501. loop, = log.loops_by_filename(self.filepath)
  502. call_ops = log.opnames(loop.ops_by_id('call'))
  503. assert call_ops == ['guard_not_invalidated', 'force_token'] # it does not follow inlining
  504. #
  505. add_ops = log.opnames(loop.ops_by_id('add'))
  506. assert add_ops == ['int_add']
  507. #
  508. ops = log.opnames(loop.allops())
  509. assert ops == [
  510. # this is the actual loop
  511. 'int_lt', 'guard_true',
  512. 'guard_not_invalidated', 'force_token', 'int_add',
  513. # this is the signal checking stuff
  514. 'getfield_raw_i', 'int_lt', 'guard_false',
  515. 'jump'
  516. ]
  517. def test_loop_match(self):
  518. def f():
  519. i = 0
  520. while i < 1003:
  521. i += 1 # ID: increment
  522. return i
  523. #
  524. log = self.run(f)
  525. loop, = log.loops_by_id('increment')
  526. assert loop.match("""
  527. i6 = int_lt(i4, 1003)
  528. guard_true(i6, descr=...)
  529. i8 = int_add(i4, 1)
  530. # signal checking stuff
  531. guard_not_invalidated(descr=...)
  532. i10 = getfield_raw_i(..., descr=<.* pypysig_long_struct.c_value .*>)
  533. i14 = int_lt(i10, 0)
  534. guard_false(i14, descr=...)
  535. jump(..., descr=...)
  536. """)
  537. #
  538. assert loop.match("""
  539. i6 = int_lt(i4, 1003)
  540. guard_true(i6, descr=...)
  541. i8 = int_add(i4, 1)
  542. --TICK--
  543. jump(..., descr=...)
  544. """)
  545. #
  546. py.test.raises(InvalidMatch, loop.match, """
  547. i6 = int_lt(i4, 1003)
  548. guard_true(i6)
  549. i8 = int_add(i5, 1) # variable mismatch
  550. --TICK--
  551. jump(..., descr=...)
  552. """)
  553. def test_match_by_id(self):
  554. def f():
  555. i = 0
  556. j = 2000
  557. while i < 1003:
  558. i += 1 # ID: increment
  559. j -= 1 # ID: product
  560. a = 0 # to make sure that JUMP_ABSOLUTE is not part of the ID
  561. return i
  562. #
  563. log = self.run(f)
  564. loop, = log.loops_by_id('increment')
  565. assert loop.match_by_id('increment', """
  566. i1 = int_add(i0, 1)
  567. """)
  568. assert loop.match_by_id('product', """
  569. i4 = int_sub_ovf(i3, 1)
  570. guard_no_overflow(descr=...)
  571. """)
  572. def test_match_constants(self):
  573. def f():
  574. from socket import ntohs
  575. i = 0
  576. while i < 1003:
  577. i += 1
  578. j = ntohs(1) # ID: ntohs
  579. a = 0
  580. return i
  581. log = self.run(f, import_site=True)
  582. loop, = log.loops_by_id('ntohs')
  583. assert loop.match_by_id('ntohs', """
  584. i12 = call_i(ConstClass(ntohs), 1, descr=...)
  585. guard_no_exception(descr=...)
  586. """,
  587. include_guard_not_invalidated=False)
  588. #
  589. py.test.raises(InvalidMatch, loop.match_by_id, 'ntohs', """
  590. guard_not_invalidated(descr=...)
  591. i12 = call_i(ConstClass(foobar), 1, descr=...)
  592. guard_no_exception(descr=...)
  593. """)