PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/module/pypyjit/test_pypy_c/model.py

https://bitbucket.org/pypy/pypy/
Python | 560 lines | 557 code | 2 blank | 1 comment | 2 complexity | ba13e61f400902783ec14d799fc28ae3 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. import py
  2. import sys
  3. import re
  4. import os.path
  5. try:
  6. from _pytest.assertion import newinterpret
  7. except ImportError: # e.g. Python 2.5
  8. newinterpret = None
  9. from rpython.tool.jitlogparser.parser import (SimpleParser, Function,
  10. TraceForOpcode)
  11. from rpython.tool.jitlogparser.storage import LoopStorage
  12. def find_ids_range(code):
  13. """
  14. Parse the given function and return a dictionary mapping "ids" to
  15. "line ranges". Ids are identified by comments with a special syntax::
  16. # "myid" corresponds to the whole line
  17. print 'foo' # ID: myid
  18. """
  19. result = {}
  20. start_lineno = code.co.co_firstlineno
  21. for i, line in enumerate(py.code.Source(code.source)):
  22. m = re.search('# ID: (\w+)', line)
  23. if m:
  24. name = m.group(1)
  25. lineno = start_lineno+i
  26. result[name] = xrange(lineno, lineno+1)
  27. return result
  28. def find_ids(code):
  29. """
  30. Parse the given function and return a dictionary mapping "ids" to
  31. "opcodes".
  32. """
  33. ids = {}
  34. ranges = find_ids_range(code)
  35. for name, linerange in ranges.iteritems():
  36. opcodes = [opcode for opcode in code.opcodes
  37. if opcode.lineno in linerange]
  38. ids[name] = opcodes
  39. return ids
  40. class Log(object):
  41. def __init__(self, rawtraces):
  42. storage = LoopStorage()
  43. traces = [SimpleParser.parse_from_input(rawtrace) for rawtrace in rawtraces]
  44. traces = storage.reconnect_loops(traces)
  45. self.loops = [TraceWithIds.from_trace(trace, storage) for trace in traces]
  46. def _filter(self, loop, is_entry_bridge=False):
  47. if is_entry_bridge == '*':
  48. return loop
  49. assert is_entry_bridge in (True, False)
  50. return PartialTraceWithIds(loop, is_entry_bridge)
  51. def loops_by_filename(self, filename, **kwds):
  52. """
  53. Return all loops which start in the file ``filename``
  54. """
  55. return [self._filter(loop, **kwds) for loop in self.loops
  56. if loop.filename == filename]
  57. def loops_by_id(self, id, **kwds):
  58. """
  59. Return all loops which contain the ID ``id``
  60. """
  61. return [self._filter(loop, **kwds) for loop in self.loops
  62. if loop.has_id(id)]
  63. @classmethod
  64. def opnames(self, oplist):
  65. return [op.name for op in oplist]
  66. class TraceWithIds(Function):
  67. def __init__(self, *args, **kwds):
  68. Function.__init__(self, *args, **kwds)
  69. self.ids = {}
  70. self.code = self.chunks[0].getcode()
  71. if not self.code and len(self.chunks)>1 and \
  72. isinstance(self.chunks[1], TraceForOpcode):
  73. # First chunk might be missing the debug_merge_point op
  74. self.code = self.chunks[1].getcode()
  75. if self.code:
  76. self.compute_ids(self.ids)
  77. @classmethod
  78. def from_trace(cls, trace, storage):
  79. res = cls.from_operations(trace.operations, storage)
  80. return res
  81. def flatten_chunks(self):
  82. """
  83. return a flat sequence of TraceForOpcode objects, including the ones
  84. inside inlined functions
  85. """
  86. for chunk in self.chunks:
  87. if isinstance(chunk, TraceForOpcode):
  88. yield chunk
  89. else:
  90. for subchunk in chunk.flatten_chunks():
  91. yield subchunk
  92. def compute_ids(self, ids):
  93. #
  94. # 1. compute the ids of self, i.e. the outer function
  95. id2opcodes = find_ids(self.code)
  96. all_my_opcodes = self.get_set_of_opcodes()
  97. for id, opcodes in id2opcodes.iteritems():
  98. if not opcodes:
  99. continue
  100. target_opcodes = set(opcodes)
  101. if all_my_opcodes.intersection(target_opcodes):
  102. ids[id] = opcodes
  103. #
  104. # 2. compute the ids of all the inlined functions
  105. for chunk in self.chunks:
  106. if isinstance(chunk, TraceWithIds) and chunk.code:
  107. chunk.compute_ids(ids)
  108. def get_set_of_opcodes(self):
  109. result = set()
  110. for chunk in self.chunks:
  111. if isinstance(chunk, TraceForOpcode):
  112. opcode = chunk.getopcode()
  113. result.add(opcode)
  114. return result
  115. def has_id(self, id):
  116. return id in self.ids
  117. def _ops_for_chunk(self, chunk, include_guard_not_invalidated):
  118. for op in chunk.operations:
  119. if op.name not in ('debug_merge_point', 'enter_portal_frame',
  120. 'leave_portal_frame') and \
  121. (op.name != 'guard_not_invalidated' or include_guard_not_invalidated):
  122. yield op
  123. def _allops(self, opcode=None, include_guard_not_invalidated=True):
  124. opcode_name = opcode
  125. for chunk in self.flatten_chunks():
  126. opcode = chunk.getopcode()
  127. if opcode_name is None or \
  128. (opcode and opcode.__class__.__name__ == opcode_name):
  129. for op in self._ops_for_chunk(chunk, include_guard_not_invalidated):
  130. yield op
  131. else:
  132. for op in chunk.operations:
  133. if op.name == 'label':
  134. yield op
  135. def allops(self, *args, **kwds):
  136. return list(self._allops(*args, **kwds))
  137. def format_ops(self, id=None, **kwds):
  138. if id is None:
  139. ops = self.allops(**kwds)
  140. else:
  141. ops = self.ops_by_id(id, **kwds)
  142. return '\n'.join(map(str, ops))
  143. def print_ops(self, *args, **kwds):
  144. print self.format_ops(*args, **kwds)
  145. def _ops_by_id(self, id, include_guard_not_invalidated=True, opcode=None):
  146. opcode_name = opcode
  147. target_opcodes = self.ids[id]
  148. loop_ops = self.allops(opcode)
  149. for chunk in self.flatten_chunks():
  150. opcode = chunk.getopcode()
  151. if opcode in target_opcodes and (opcode_name is None or
  152. opcode.__class__.__name__ == opcode_name):
  153. for op in self._ops_for_chunk(chunk, include_guard_not_invalidated):
  154. if op in loop_ops:
  155. yield op
  156. def ops_by_id(self, *args, **kwds):
  157. return list(self._ops_by_id(*args, **kwds))
  158. def match(self, expected_src, **kwds):
  159. ops = self.allops()
  160. matcher = OpMatcher(ops)
  161. return matcher.match(expected_src, **kwds)
  162. def match_by_id(self, id, expected_src, ignore_ops=[], **kwds):
  163. ops = list(self.ops_by_id(id, **kwds))
  164. matcher = OpMatcher(ops, id)
  165. return matcher.match(expected_src, ignore_ops=ignore_ops)
  166. class PartialTraceWithIds(TraceWithIds):
  167. def __init__(self, trace, is_entry_bridge=False):
  168. self.trace = trace
  169. self.is_entry_bridge = is_entry_bridge
  170. def allops(self, *args, **kwds):
  171. if self.is_entry_bridge:
  172. return self.entry_bridge_ops(*args, **kwds)
  173. else:
  174. return self.simple_loop_ops(*args, **kwds)
  175. def simple_loop_ops(self, *args, **kwds):
  176. ops = list(self._allops(*args, **kwds))
  177. labels = [op for op in ops if op.name == 'label']
  178. jumpop = self.chunks[-1].operations[-1]
  179. assert jumpop.name == 'jump'
  180. assert jumpop.getdescr() == labels[-1].getdescr()
  181. i = ops.index(labels[-1])
  182. return ops[i+1:]
  183. def entry_bridge_ops(self, *args, **kwds):
  184. ops = list(self._allops(*args, **kwds))
  185. labels = [op for op in ops if op.name == 'label']
  186. i0 = ops.index(labels[0])
  187. i1 = ops.index(labels[1])
  188. return ops[i0+1:i1]
  189. @property
  190. def chunks(self):
  191. return self.trace.chunks
  192. @property
  193. def ids(self):
  194. return self.trace.ids
  195. @property
  196. def filename(self):
  197. return self.trace.filename
  198. @property
  199. def code(self):
  200. return self.trace.code
  201. class InvalidMatch(Exception):
  202. opindex = None
  203. def __init__(self, message, frame):
  204. Exception.__init__(self, message)
  205. # copied and adapted from pytest's magic AssertionError
  206. f = py.code.Frame(frame)
  207. try:
  208. source = f.code.fullsource
  209. if source is not None:
  210. try:
  211. source = source.getstatement(f.lineno)
  212. except IndexError:
  213. source = None
  214. else:
  215. source = str(source.deindent()).strip()
  216. except py.error.ENOENT:
  217. source = None
  218. if source and source.startswith('self._assert(') and newinterpret:
  219. # transform self._assert(x, 'foo') into assert x, 'foo'
  220. source = source.replace('self._assert(', 'assert ')
  221. source = source[:-1] # remove the trailing ')'
  222. self.msg = newinterpret.interpret(source, f, should_fail=True)
  223. else:
  224. self.msg = "<could not determine information>"
  225. class OpMatcher(object):
  226. def __init__(self, ops, id=None):
  227. self.ops = ops
  228. self.id = id
  229. self.src = '\n'.join(map(str, ops))
  230. self.alpha_map = {}
  231. @classmethod
  232. def parse_ops(cls, src):
  233. ops = [cls.parse_op(line) for line in src.splitlines()]
  234. ops.append(('--end--', None, [], '...', True))
  235. return [op for op in ops if op is not None]
  236. @classmethod
  237. def parse_op(cls, line):
  238. # strip comment after '#', but not if it appears inside parentheses
  239. if '#' in line:
  240. nested = 0
  241. for i, c in enumerate(line):
  242. if c == '(':
  243. nested += 1
  244. elif c == ')':
  245. assert nested > 0, "more ')' than '(' in %r" % (line,)
  246. nested -= 1
  247. elif c == '#' and nested == 0:
  248. line = line[:i]
  249. break
  250. #
  251. if line.strip() == 'guard_not_invalidated?':
  252. return 'guard_not_invalidated', None, [], '...', False
  253. # find the resvar, if any
  254. if ' = ' in line:
  255. resvar, _, line = line.partition(' = ')
  256. resvar = resvar.strip()
  257. else:
  258. resvar = None
  259. line = line.strip()
  260. if not line:
  261. return None
  262. if line in ('...', '{{{', '}}}'):
  263. return line
  264. opname, _, args = line.partition('(')
  265. opname = opname.strip()
  266. assert args.endswith(')')
  267. args = args[:-1]
  268. args = args.split(',')
  269. args = map(str.strip, args)
  270. if args == ['']:
  271. args = []
  272. if args and args[-1].startswith('descr='):
  273. descr = args.pop()
  274. descr = descr[len('descr='):]
  275. else:
  276. descr = None
  277. return opname, resvar, args, descr, True
  278. @classmethod
  279. def preprocess_expected_src(cls, src):
  280. # all loops decrement the tick-counter at the end. The rpython code is
  281. # in jump_absolute() in pypyjit/interp.py. The string --TICK-- is
  282. # replaced with the corresponding operations, so that tests don't have
  283. # to repeat it every time
  284. ticker_check = """
  285. guard_not_invalidated?
  286. ticker0 = getfield_raw_i(#, descr=<FieldS pypysig_long_struct.c_value .*>)
  287. ticker_cond0 = int_lt(ticker0, 0)
  288. guard_false(ticker_cond0, descr=...)
  289. """
  290. src = src.replace('--TICK--', ticker_check)
  291. #
  292. # this is the ticker check generated if we have threads
  293. thread_ticker_check = """
  294. guard_not_invalidated?
  295. ticker0 = getfield_raw_i(#, descr=<FieldS pypysig_long_struct.c_value .*>)
  296. ticker1 = int_sub(ticker0, #)
  297. setfield_raw(#, ticker1, descr=<FieldS pypysig_long_struct.c_value .*>)
  298. ticker_cond0 = int_lt(ticker1, 0)
  299. guard_false(ticker_cond0, descr=...)
  300. """
  301. src = src.replace('--THREAD-TICK--', thread_ticker_check)
  302. #
  303. # this is the ticker check generated in PyFrame.handle_operation_error
  304. exc_ticker_check = """
  305. ticker2 = getfield_raw_i(#, descr=<FieldS pypysig_long_struct.c_value .*>)
  306. ticker_cond1 = int_lt(ticker2, 0)
  307. guard_false(ticker_cond1, descr=...)
  308. """
  309. src = src.replace('--EXC-TICK--', exc_ticker_check)
  310. #
  311. # ISINF is done as a macro; fix it here
  312. r = re.compile('(\w+) = --ISINF--[(](\w+)[)]')
  313. src = r.sub(r'\2\B999 = float_add(\2, ...)\n\1 = float_eq(\2\B999, \2)',
  314. src)
  315. return src
  316. @classmethod
  317. def is_const(cls, v1):
  318. return isinstance(v1, str) and v1.startswith('ConstClass(')
  319. @staticmethod
  320. def as_numeric_const(v1):
  321. # returns one of: ('int', value) ('float', value) None
  322. try:
  323. return ('int', int(v1))
  324. except ValueError:
  325. pass
  326. if '.' in v1:
  327. try:
  328. return ('float', float(v1))
  329. except ValueError:
  330. pass
  331. return None
  332. def match_var(self, v1, exp_v2):
  333. assert v1 != '_'
  334. if exp_v2 == '_': # accept anything
  335. return True
  336. if exp_v2 is None:
  337. return v1 is None
  338. assert exp_v2 != '...' # bogus use of '...' in the expected code
  339. n1 = self.as_numeric_const(v1)
  340. if exp_v2 == '#': # accept any (integer or float) number
  341. return n1 is not None
  342. n2 = self.as_numeric_const(exp_v2)
  343. if n1 is not None or n2 is not None:
  344. # at least one is a number; check that both are, and are equal
  345. return n1 == n2
  346. if self.is_const(v1) or self.is_const(exp_v2):
  347. return v1[:-1].startswith(exp_v2[:-1])
  348. if v1 not in self.alpha_map:
  349. self.alpha_map[v1] = exp_v2
  350. return self.alpha_map[v1] == exp_v2
  351. def match_descr(self, descr, exp_descr):
  352. if descr == exp_descr or exp_descr == '...':
  353. return True
  354. self._assert(exp_descr is not None and re.match(exp_descr, descr), "descr mismatch")
  355. def _assert(self, cond, message):
  356. if not cond:
  357. raise InvalidMatch(message, frame=sys._getframe(1))
  358. def match_op(self, op, (exp_opname, exp_res, exp_args, exp_descr, _)):
  359. if exp_opname == '--end--':
  360. self._assert(op == '--end--', 'got more ops than expected')
  361. return
  362. self._assert(op != '--end--', 'got less ops than expected')
  363. self._assert(op.name == exp_opname, "operation mismatch")
  364. self.match_var(op.res, exp_res)
  365. if exp_args[-1:] == ['...']: # exp_args ends with '...'
  366. exp_args = exp_args[:-1]
  367. self._assert(len(op.args) >= len(exp_args), "not enough arguments")
  368. else:
  369. self._assert(len(op.args) == len(exp_args), "wrong number of arguments")
  370. for arg, exp_arg in zip(op.args, exp_args):
  371. self._assert(self.match_var(arg, exp_arg), "variable mismatch: %r instead of %r" % (arg, exp_arg))
  372. self.match_descr(op.descr, exp_descr)
  373. def _next_op(self, iter_ops, ignore_ops=set()):
  374. try:
  375. while True:
  376. op = iter_ops.next()
  377. if op.name not in ignore_ops:
  378. break
  379. except StopIteration:
  380. return '--end--'
  381. return op
  382. def try_match(self, op, exp_op):
  383. try:
  384. # try to match the op, but be sure not to modify the
  385. # alpha-renaming map in case the match does not work
  386. alpha_map = self.alpha_map.copy()
  387. self.match_op(op, exp_op)
  388. except InvalidMatch:
  389. # it did not match: rollback the alpha_map
  390. self.alpha_map = alpha_map
  391. return False
  392. else:
  393. return True
  394. def match_until(self, until_op, iter_ops):
  395. while True:
  396. op = self._next_op(iter_ops)
  397. if self.try_match(op, until_op):
  398. # it matched! The '...' operator ends here
  399. return op
  400. self._assert(op != '--end--',
  401. 'nothing in the end of the loop matches %r' %
  402. (until_op,))
  403. def match_any_order(self, iter_exp_ops, iter_ops, ignore_ops):
  404. exp_ops = []
  405. for exp_op in iter_exp_ops:
  406. if exp_op == '}}}':
  407. break
  408. exp_ops.append(exp_op)
  409. else:
  410. assert 0, "'{{{' not followed by '}}}'"
  411. while exp_ops:
  412. op = self._next_op(iter_ops, ignore_ops=ignore_ops)
  413. # match 'op' against any of the exp_ops; the first successful
  414. # match is kept, and the exp_op gets removed from the list
  415. for i, exp_op in enumerate(exp_ops):
  416. if self.try_match(op, exp_op):
  417. del exp_ops[i]
  418. break
  419. else:
  420. self._assert(0, \
  421. "operation %r not found within the {{{ }}} block" % (op,))
  422. def match_loop(self, expected_ops, ignore_ops):
  423. """
  424. A note about partial matching: the '...' operator is non-greedy,
  425. i.e. it matches all the operations until it finds one that matches
  426. what is after the '...'. The '{{{' and '}}}' operators mark a
  427. group of lines that can match in any order.
  428. """
  429. iter_exp_ops = iter(expected_ops)
  430. iter_ops = RevertableIterator(self.ops)
  431. for exp_op in iter_exp_ops:
  432. try:
  433. if exp_op == '...':
  434. # loop until we find an operation which matches
  435. try:
  436. exp_op = iter_exp_ops.next()
  437. except StopIteration:
  438. # the ... is the last line in the expected_ops, so we just
  439. # return because it matches everything until the end
  440. return
  441. op = self.match_until(exp_op, iter_ops)
  442. elif exp_op == '{{{':
  443. self.match_any_order(iter_exp_ops, iter_ops, ignore_ops)
  444. continue
  445. else:
  446. op = self._next_op(iter_ops, ignore_ops=ignore_ops)
  447. try:
  448. self.match_op(op, exp_op)
  449. except InvalidMatch:
  450. if type(exp_op) is str or exp_op[4] is not False:
  451. raise
  452. #else: optional operation
  453. iter_ops.revert_one()
  454. continue # try to match with the next exp_op
  455. except InvalidMatch as e:
  456. e.opindex = iter_ops.index - 1
  457. raise
  458. def match(self, expected_src, ignore_ops=[]):
  459. def format(src, opindex=None):
  460. if src is None:
  461. return ''
  462. text = str(py.code.Source(src).deindent().indent())
  463. lines = text.splitlines(True)
  464. if opindex is not None and 0 <= opindex <= len(lines):
  465. lines.insert(opindex, '\n\t===== HERE =====\n')
  466. return ''.join(lines)
  467. #
  468. expected_src = self.preprocess_expected_src(expected_src)
  469. expected_ops = self.parse_ops(expected_src)
  470. try:
  471. self.match_loop(expected_ops, ignore_ops)
  472. except InvalidMatch as e:
  473. print '@' * 40
  474. print "Loops don't match"
  475. print "================="
  476. print 'loop id = %r' % (self.id,)
  477. print e.args
  478. print e.msg
  479. print
  480. print "Ignore ops:", ignore_ops
  481. print "Got:"
  482. print format(self.src, e.opindex)
  483. print
  484. print "Expected:"
  485. print format(expected_src)
  486. raise # always propagate the exception in case of mismatch
  487. else:
  488. return True
  489. class RevertableIterator(object):
  490. def __init__(self, sequence):
  491. self.sequence = sequence
  492. self.index = 0
  493. def __iter__(self):
  494. return self
  495. def next(self):
  496. index = self.index
  497. self.index = index + 1
  498. if index >= len(self.sequence):
  499. raise StopIteration
  500. return self.sequence[index]
  501. def revert_one(self):
  502. self.index -= 1