PageRenderTime 62ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/evelyn559/pypy
Python | 484 lines | 481 code | 2 blank | 1 comment | 2 complexity | c02249ef55b5294d756ea810d7321d56 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 == '...':
  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. def match_var(self, v1, exp_v2):
  305. assert v1 != '_'
  306. if exp_v2 == '_':
  307. return True
  308. if self.is_const(v1) or self.is_const(exp_v2):
  309. return v1[:-1].startswith(exp_v2[:-1])
  310. if v1 not in self.alpha_map:
  311. self.alpha_map[v1] = exp_v2
  312. return self.alpha_map[v1] == exp_v2
  313. def match_descr(self, descr, exp_descr):
  314. if descr == exp_descr or exp_descr == '...':
  315. return True
  316. self._assert(exp_descr is not None and re.match(exp_descr, descr), "descr mismatch")
  317. def _assert(self, cond, message):
  318. if not cond:
  319. raise InvalidMatch(message, frame=sys._getframe(1))
  320. def match_op(self, op, (exp_opname, exp_res, exp_args, exp_descr, _)):
  321. self._assert(op.name == exp_opname, "operation mismatch")
  322. self.match_var(op.res, exp_res)
  323. if exp_args != ['...']:
  324. self._assert(len(op.args) == len(exp_args), "wrong number of arguments")
  325. for arg, exp_arg in zip(op.args, exp_args):
  326. self._assert(self.match_var(arg, exp_arg), "variable mismatch: %r instead of %r" % (arg, exp_arg))
  327. self.match_descr(op.descr, exp_descr)
  328. def _next_op(self, iter_ops, assert_raises=False):
  329. try:
  330. op = iter_ops.next()
  331. except StopIteration:
  332. self._assert(assert_raises, "not enough operations")
  333. return
  334. else:
  335. self._assert(not assert_raises, "operation list too long")
  336. return op
  337. def match_until(self, until_op, iter_ops):
  338. while True:
  339. op = self._next_op(iter_ops)
  340. try:
  341. # try to match the op, but be sure not to modify the
  342. # alpha-renaming map in case the match does not work
  343. alpha_map = self.alpha_map.copy()
  344. self.match_op(op, until_op)
  345. except InvalidMatch:
  346. # it did not match: rollback the alpha_map, and just skip this
  347. # operation
  348. self.alpha_map = alpha_map
  349. else:
  350. # it matched! The '...' operator ends here
  351. return op
  352. def match_loop(self, expected_ops, ignore_ops):
  353. """
  354. A note about partial matching: the '...' operator is non-greedy,
  355. i.e. it matches all the operations until it finds one that matches
  356. what is after the '...'
  357. """
  358. iter_exp_ops = iter(expected_ops)
  359. iter_ops = RevertableIterator(self.ops)
  360. for exp_op in iter_exp_ops:
  361. try:
  362. if exp_op == '...':
  363. # loop until we find an operation which matches
  364. try:
  365. exp_op = iter_exp_ops.next()
  366. except StopIteration:
  367. # the ... is the last line in the expected_ops, so we just
  368. # return because it matches everything until the end
  369. return
  370. op = self.match_until(exp_op, iter_ops)
  371. else:
  372. while True:
  373. op = self._next_op(iter_ops)
  374. if op.name not in ignore_ops:
  375. break
  376. self.match_op(op, exp_op)
  377. except InvalidMatch, e:
  378. if exp_op[4] is False: # optional operation
  379. iter_ops.revert_one()
  380. continue # try to match with the next exp_op
  381. e.opindex = iter_ops.index - 1
  382. raise
  383. #
  384. # make sure we exhausted iter_ops
  385. self._next_op(iter_ops, assert_raises=True)
  386. def match(self, expected_src, ignore_ops=[]):
  387. def format(src, opindex=None):
  388. if src is None:
  389. return ''
  390. text = str(py.code.Source(src).deindent().indent())
  391. lines = text.splitlines(True)
  392. if opindex is not None and 0 <= opindex <= len(lines):
  393. lines.insert(opindex, '\n\t===== HERE =====\n')
  394. return ''.join(lines)
  395. #
  396. expected_src = self.preprocess_expected_src(expected_src)
  397. expected_ops = self.parse_ops(expected_src)
  398. try:
  399. self.match_loop(expected_ops, ignore_ops)
  400. except InvalidMatch, e:
  401. print '@' * 40
  402. print "Loops don't match"
  403. print "================="
  404. print e.args
  405. print e.msg
  406. print
  407. print "Ignore ops:", ignore_ops
  408. print "Got:"
  409. print format(self.src, e.opindex)
  410. print
  411. print "Expected:"
  412. print format(expected_src)
  413. raise # always propagate the exception in case of mismatch
  414. else:
  415. return True
  416. class RevertableIterator(object):
  417. def __init__(self, sequence):
  418. self.sequence = sequence
  419. self.index = 0
  420. def __iter__(self):
  421. return self
  422. def next(self):
  423. index = self.index
  424. if index == len(self.sequence):
  425. raise StopIteration
  426. self.index = index + 1
  427. return self.sequence[index]
  428. def revert_one(self):
  429. self.index -= 1