PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

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

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