Miasm2
 All Classes Namespaces Files Functions Variables Typedefs Properties Macros
depgraph.py
Go to the documentation of this file.
1 """Provide dependency graph"""
2 import itertools
3 from collections import deque
4 from UserDict import IterableUserDict
5 
6 try:
7  import z3
8 except ImportError:
9  pass
10 
11 import miasm2.expression.expression as m2_expr
12 from miasm2.core.graph import DiGraph
13 from miasm2.core.asmbloc import asm_label, expr_is_label
14 from miasm2.expression.simplifications import expr_simp
15 from miasm2.ir.symbexec import symbexec
16 from miasm2.ir.ir import irbloc
17 from miasm2.ir.translators import Translator
18 
19 
21 
22  """Node elements of a DependencyGraph
23 
24  A dependency node stands for the dependency on the @element at line number
25  @line_nb in the IRblock named @label, *before* the evaluation of this
26  line.
27  """
28 
29  __slots__ = ["_label", "_element", "_line_nb", "_modifier",
30  "_step", "_nostep_repr", "_hash"]
31 
32  def __init__(self, label, element, line_nb, step, modifier=False):
33  """Create a dependency node with:
34  @label: asm_label instance
35  @element: Expr instance
36  @line_nb: int
37  @modifier: bool
38  """
39  self._label = label
40  self._element = element
41  self._line_nb = line_nb
42  self._modifier = modifier
43  self._step = step
44  self._nostep_repr = (self._label, self._line_nb, self._element)
45  self._hash = hash(
46  (self._label, self._element, self._line_nb, self._step))
47 
48  def __hash__(self):
49  """Returns a hash of @self to uniquely identify @self"""
50  return self._hash
51 
52  def __eq__(self, depnode):
53  """Returns True if @self and @depnode are equals.
54  The attribute 'step' is not considered in the comparison.
55  """
56  if not isinstance(depnode, self.__class__):
57  return False
58  return (self.label == depnode.label and
59  self.element == depnode.element and
60  self.line_nb == depnode.line_nb and
61  self.step == depnode.step)
62 
63  def __cmp__(self, node):
64  """Compares @self with @node. The step attribute is not taken into
65  account in the comparison.
66  """
67  if not isinstance(node, self.__class__):
68  raise ValueError("Compare error between %s, %s" % (self.__class__,
69  node.__class__))
70  return cmp((self.label, self.element, self.line_nb),
71  (node.label, node.element, node.line_nb))
72 
73  def __str__(self):
74  """Returns a string representation of DependencyNode"""
75  return "<%s %s %s %s M:%s S:%s>" % (self.__class__.__name__,
76  self.label.name, self.element,
77  self.line_nb, self.modifier,
78  self.step)
79 
80  def __repr__(self):
81  """Returns a string representation of DependencyNode"""
82  return self.__str__()
83 
84  @property
85  def nostep_repr(self):
86  """Returns a representation of @self ignoring the step attribute"""
87  return self._nostep_repr
88 
89  @property
90  def label(self):
91  "Name of the current IRBlock"
92  return self._label
93 
94  @property
95  def element(self):
96  "Current tracked Expr"
97  return self._element
98 
99  @property
100  def line_nb(self):
101  "Line in the current IRBlock"
102  return self._line_nb
103 
104  @property
105  def step(self):
106  "Step of the current node"
107  return self._step
108 
109  @property
110  def modifier(self):
111  """Evaluating the current line involves a modification of tracked
112  dependencies"""
113  return self._modifier
114 
115  @modifier.setter
116  def modifier(self, value):
117  """Evaluating the current line involves a modification of tracked
118  dependencies if @value.
119  @value: boolean"""
120  self._modifier = value
121 
122 
123 class CacheWrapper(IterableUserDict):
124 
125  """Wrapper class for cache dictionnary"""
126 
127  def __init__(self, dct=None):
128  """Create a CacheWrapper with value @dct."""
129  IterableUserDict.__init__(self, dct)
130  self._nostep_cache = None
131  self._nostep_keys = None
132 
133  def __eq__(self, cache):
134  """Returns True if the nostep caches are equals"""
135  if self.nostep_keys != cache.nostep_keys:
136  return False
137  return self.nostep_cache == cache.nostep_cache
138 
139  @property
140  def nostep_keys(self):
141  """List of dictonnary keys without the step attribute.
142  The list is generated once when the method is called and not updated
143  afterward.
144  """
145  if self._nostep_keys is None:
146  self._nostep_keys = set(key.nostep_repr for key in self.data)
147  return self._nostep_keys
148 
149  @property
150  def nostep_cache(self):
151  """Dictionnary of DependencyNode and their dependencies,
152  without the step attribute.
153  The dictionnary is generated once when the method is called for the
154  first time and not updated afterward.
155  """
156  if self._nostep_cache is None:
157  self._nostep_cache = {}
158  for (node, values) in self.data.iteritems():
159  self._nostep_cache.setdefault(node.nostep_repr, set()).update(
160  set(val.nostep_repr for val in values))
161  return self._nostep_cache
162 
163 
165 
166  """Internal structure for the DependencyGraph algorithm"""
167  __slots__ = ["_label", "_history", "_pending", "_cache"]
168 
169  def __init__(self, label, history):
170  """Create a DependencyDict
171  @label: asm_label, current IRblock label
172  @history: list of DependencyDict
173  """
174  self._label = label
175  self._history = history
176  self._pending = set()
177 
178  # DepNode -> set(DepNode)
180 
181  def __eq__(self, depdict):
182  if not isinstance(depdict, self.__class__):
183  return False
184  return (self._label == depdict.label and
185  self.cache == depdict.cache)
186 
187  def __cmp__(self, depdict):
188  if not isinstance(depdict, self.__class__):
189  raise ValueError("Compare error %s != %s" % (self.__class__,
190  depdict.__class__))
191  return cmp((self._label, self._cache, self._pending),
192  (depdict.label, depdict.cache, depdict.pending))
193 
194  def is_head(self, depnode):
195  """Return True iff @depnode is at the head of the current block
196  @depnode: DependencyNode instance"""
197  return (self.label == depnode.label and
198  depnode.line_nb == 0)
199 
200  def copy(self):
201  "Return a copy of itself"
202 
203  # Initialize
204  new_history = list(self.history)
205  depdict = DependencyDict(self.label, new_history)
206 
207  # Copy values
208  for key, values in self.cache.iteritems():
209  depdict.cache[key] = set(values)
210  depdict.pending.update(self.pending)
211 
212  return depdict
213 
214  def extend(self, label):
215  """Return a copy of itself, with itself in history and pending clean
216  @label: asm_label instance for the new DependencyDict's label
217  """
218  depdict = DependencyDict(label, list(self.history) + [self])
219  for key, values in self.cache.iteritems():
220  depdict.cache[key] = set(values)
221  return depdict
222 
223  def heads(self):
224  """Return an iterator on the list of heads as defined in 'is_head'"""
225  for key in self.cache:
226  if self.is_head(key):
227  yield key
228 
229  @property
230  def label(self):
231  "Label of the current block"
232  return self._label
233 
234  @property
235  def history(self):
236  """List of DependencyDict needed to reach the current DependencyDict
237  The first is the oldest"""
238  return self._history
239 
240  @property
241  def cache(self):
242  "Dictionnary of DependencyNode and their dependencies"
243  return self._cache
244 
245  @property
246  def pending(self):
247  """Dictionnary of DependencyNode and their dependencies, waiting for
248  resolution"""
249  return self._pending
250 
251  def _get_modifiers_in_cache(self, nodes_heads):
252  """Find modifier nodes in cache starting from @nodes_heads.
253  Returns new cache"""
254  # 'worklist_depnode' order is needed (depth first)
255  worklist_depnodes = list(nodes_heads)
256  # Temporary cache
257  cache = {}
258  # Partially resolved 'cache' elements
259  worklist = []
260 
261  # Build worklist and cache for non modifiers
262  while worklist_depnodes:
263  depnode = worklist_depnodes.pop()
264  # Resolve node dependencies
265  if depnode in cache:
266  # Depnode previously resolved
267  continue
268 
269  if depnode not in self._cache:
270  # Final node
271  if not depnode.modifier:
272  cache[depnode] = []
273  continue
274 
275  # Propagate to son
276  dependencies = self._cache[depnode]
277  for son in dependencies:
278  worklist_depnodes.append(son)
279  # Save partially resolved dependency
280  worklist.append((depnode, dependencies))
281 
282  # Convert worklist to cache
283  while worklist:
284  depnode, dependencies = worklist.pop()
285  parallels = []
286  for node in dependencies:
287  if node.modifier:
288  parallels.append([node])
289  else:
290  parallels.append(cache[node])
291  out = set()
292  for parallel in itertools.product(*[p for p in parallels if p]):
293  out.update(parallel)
294  cache[depnode] = out
295 
296  return cache
297 
298  def clean_modifiers_in_cache(self, node_heads):
299  """Remove intermediary states (non modifier depnodes) in the internal
300  cache values"""
301 
302  self._cache = CacheWrapper(self._get_modifiers_in_cache(node_heads))
303 
304  def _build_depgraph(self, depnode):
305  """Recursively build the final list of DiGraph, and clean up unmodifier
306  nodes
307  @depnode: starting node
308  """
309 
310  if depnode not in self._cache or \
311  not self._cache[depnode]:
312  # There is no dependency
313  graph = DiGraph()
314  graph.add_node(depnode)
315  return graph
316 
317  # Recursion
318  dependencies = list(self._cache[depnode])
319 
320  graphs = []
321  for sub_depnode in dependencies:
322  graphs.append(self._build_depgraph(sub_depnode))
323 
324  # head(graphs[i]) == dependencies[i]
325  graph = DiGraph()
326  graph.add_node(depnode)
327  for head in dependencies:
328  graph.add_uniq_edge(head, depnode)
329 
330  for subgraphs in itertools.product(graphs):
331  for sourcegraph in subgraphs:
332  for node in sourcegraph.nodes():
333  graph.add_node(node)
334  for edge in sourcegraph.edges():
335  graph.add_uniq_edge(*edge)
336 
337  # Update the running queue
338  return graph
339 
340  def as_graph(self, starting_nodes):
341  """Return a DiGraph corresponding to computed dependencies, with
342  @starting_nodes as leafs
343  @starting_nodes: set of DependencyNode instance
344  """
345 
346  # Build subgraph for each starting_node
347  subgraphs = []
348  for starting_node in starting_nodes:
349  subgraphs.append(self._build_depgraph(starting_node))
350 
351  # Merge subgraphs into a final DiGraph
352  graph = DiGraph()
353  for sourcegraph in subgraphs:
354  for node in sourcegraph.nodes():
355  graph.add_node(node)
356  for edge in sourcegraph.edges():
357  graph.add_uniq_edge(*edge)
358  return graph
359 
360  def filter_used_nodes(self, node_heads):
361  """Keep only depnodes which are in the path of @node_heads in the
362  internal cache
363  @node_heads: set of DependencyNode instance
364  """
365  # Init
366  todo = set(node_heads)
367  used_nodes = set()
368 
369  # Map
370  while todo:
371  node = todo.pop()
372  if node in used_nodes:
373  continue
374  used_nodes.add(node)
375  if not node in self._cache:
376  continue
377  for sub_node in self._cache[node]:
378  todo.add(sub_node)
379 
380  # Remove unused elements
381  for key in list(self._cache.keys()):
382  if key not in used_nodes:
383  del self._cache[key]
384 
385  def filter_unmodifier_loops(self, implicit, irdst):
386  """
387  Remove unmodifier node creating dependency loops over
388  pending elements in cache.
389  @implicit: boolean
390  @irdst: ExprId instance of IRDst register
391  """
392 
393  previous_dict = None
394  # Get pending nodes of last time the label was handled
395  for hist_dict in reversed(self.history):
396  if hist_dict.label == self.label:
397  previous_dict = hist_dict
398  break
399 
400  if not previous_dict:
401  return
402 
403  nostep_pending = [node.nostep_repr for node in self.pending]
404 
405  to_remove = set()
406  for depnode in previous_dict.pending:
407  if (depnode.nostep_repr not in nostep_pending or
408  implicit and depnode.element == irdst):
409  continue
410 
411  to_remove.update(self._non_modifier_in_loop(depnode))
412 
413  # Replace unused keys by previous ones
414  for key in to_remove:
415  if depnode.nostep_repr == key.nostep_repr:
416  self._cache[depnode] = self._cache.get(key, set()).copy()
417  self.pending.discard(key)
418  self.pending.add(depnode)
419 
420  # Replace occurences of key to remove
421  for dependencies in self._cache.itervalues():
422  if key in dependencies:
423  dependencies.remove(key)
424  dependencies.add(depnode)
425 
426  if self._cache.has_key(key):
427  del self._cache[key]
428 
429  def _non_modifier_in_loop(self, depnode):
430  """
431  Walk from @depnode until a node with the same nostep_repr is
432  encountered.
433  Returns a set of unmodifier nodes met in the path if no modifier was
434  found.
435  Returns set() if there exist a modifier node on the path.
436  """
437  if not self.cache.has_key(depnode):
438  return set()
439  # Init
440  todo = set(self.cache[depnode])
441  unmodifier_nodes = []
442 
443  # Map
444  while todo:
445  node = todo.pop()
446  if node in unmodifier_nodes:
447  continue
448  if node.modifier:
449  return set()
450  unmodifier_nodes.append(node)
451  if not node in self._cache:
452  continue
453  if node.nostep_repr == depnode.nostep_repr:
454  unmodifier_nodes.append(node)
455  break
456 
457  for sub_node in self._cache[node]:
458  todo.add(sub_node)
459 
460  return unmodifier_nodes
461 
462 
464 
465  """Container and methods for DependencyGraph results"""
466 
467  def __init__(self, ira, final_depdict, input_depnodes):
468  """Instance a DependencyResult
469  @ira: IRAnalysis instance
470  @final_depdict: DependencyDict instance
471  @input_depnodes: set of DependencyNode instance
472  """
473  # Store arguments
474  self._ira = ira
475  self._depdict = final_depdict
476  self._input_depnodes = input_depnodes
477 
478  # Init lazy elements
479  self._graph = None
480  self._has_loop = None
481 
482  @property
483  def graph(self):
484  """Returns a DiGraph instance representing the DependencyGraph"""
485  if self._graph is None:
486  self._graph = self._depdict.as_graph(self._input_depnodes)
487  return self._graph
488 
489  @property
490  def history(self):
491  """List of depdict corresponding to the blocks encountered in the
492  analysis"""
493  return list(self._depdict.history) + [self._depdict]
494 
495  @property
496  def unresolved(self):
497  """Set of nodes whose dependencies weren't found"""
498  return set(node.nostep_repr for node in self._depdict.pending
499  if node.element != self._ira.IRDst)
500 
501  @property
502  def relevant_nodes(self):
503  """Set of nodes directly and indirectly influencing
504  @self.input_depnodes"""
505  output = set()
506  for depnodes in self._depdict.cache.values():
507  output.update(depnodes)
508  return output
509 
510  @property
511  def relevant_labels(self):
512  """List of labels containing nodes influencing @self.input_depnodes.
513  The history order is preserved.
514  """
515  # Get used labels
516  used_labels = set(depnode.label for depnode in self.relevant_nodes)
517 
518  # Keep history order
519  output = []
520  for label in [depdict.label for depdict in self.history]:
521  if label in used_labels:
522  output.append(label)
523 
524  return output
525 
526  @property
527  def input(self):
528  """Set of DependencyGraph start nodes"""
529  return self._input_depnodes
530 
531  @property
532  def has_loop(self):
533  """True if current dictionnary has a loop"""
534  if self._has_loop is None:
535  self._has_loop = (len(self.relevant_labels) !=
536  len(set(self.relevant_labels)))
537  return self._has_loop
538 
539  def emul(self, ctx=None, step=False):
540  """Symbolic execution of relevant nodes according to the history
541  Return the values of input nodes' elements
542  @ctx: (optional) Initial context as dictionnary
543  @step: (optional) Verbose execution
544 
545  Warning: The emulation is not sound if the input nodes depend on loop
546  variant.
547  """
548  # Init
549  ctx_init = self._ira.arch.regs.regs_init
550  if ctx is not None:
551  ctx_init.update(ctx)
552  depnodes = self.relevant_nodes
553  affects = []
554 
555  # Build a single affectation block according to history
556  for label in self.relevant_labels[::-1]:
557  affected_lines = set(depnode.line_nb for depnode in depnodes
558  if depnode.label == label)
559  irs = self._ira.blocs[label].irs
560  for line_nb in sorted(affected_lines):
561  affects.append(irs[line_nb])
562 
563  # Eval the block
564  temp_label = asm_label("Temp")
565  symb_exec = symbexec(self._ira, ctx_init)
566  symb_exec.emulbloc(irbloc(temp_label, affects), step=step)
567 
568  # Return only inputs values (others could be wrongs)
569  return {depnode.element: symb_exec.symbols[depnode.element]
570  for depnode in self.input}
571 
572 
574 
575  """Stand for a result of a DependencyGraph with implicit option
576 
577  Provide path constraints using the z3 solver"""
578  __slots__ = ["_ira", "_depdict", "_input_depnodes", "_graph",
579  "_has_loop", "_solver"]
580 
581  # Z3 Solver instance
582  _solver = None
583 
584  def emul(self, ctx=None, step=False):
585  # Init
586  ctx_init = self._ira.arch.regs.regs_init
587  if ctx is not None:
588  ctx_init.update(ctx)
589  depnodes = self.relevant_nodes
590  solver = z3.Solver()
591  symb_exec = symbexec(self._ira, ctx_init)
592  temp_label = asm_label("Temp")
593  history = self.relevant_labels[::-1]
594  history_size = len(history)
595 
596  for hist_nb, label in enumerate(history):
597  # Build block with relevant lines only
598  affected_lines = set(depnode.line_nb for depnode in depnodes
599  if depnode.label == label)
600  irs = self._ira.blocs[label].irs
601  affects = []
602 
603  for line_nb in sorted(affected_lines):
604  affects.append(irs[line_nb])
605 
606  # Emul the block and get back destination
607  dst = symb_exec.emulbloc(irbloc(temp_label, affects), step=step)
608 
609  # Add constraint
610  if hist_nb + 1 < history_size:
611  next_label = history[hist_nb + 1]
612  expected = symb_exec.eval_expr(m2_expr.ExprId(next_label, 32))
613  constraint = m2_expr.ExprAff(dst, expected)
614  solver.add(Translator.to_language("z3").from_expr(constraint))
615 
616  # Save the solver
617  self._solver = solver
618 
619  # Return only inputs values (others could be wrongs)
620  return {depnode.element: symb_exec.symbols[depnode.element]
621  for depnode in self.input}
622 
623  @property
624  def is_satisfiable(self):
625  """Return True iff the solution path admits at least one solution
626  PRE: 'emul'
627  """
628  return self._solver.check().r > 0
629 
630  @property
631  def constraints(self):
632  """If satisfiable, return a valid solution as a Z3 Model instance"""
633  if not self.is_satisfiable:
634  raise ValueError("Unsatisfiable")
635  return self._solver.model()
636 
637 
639 
640  "Stand for an element (expression, depnode, ...) to follow or not"
641  __slots__ = ["follow", "element"]
642 
643  def __init__(self, follow, element):
644  self.follow = follow
645  self.element = element
646 
647  @staticmethod
648  def to_depnodes(follow_exprs, label, line, modifier, step):
649  """Build a set of FollowExpr(DependencyNode) from the @follow_exprs set
650  of FollowExpr
651  @follow_exprs: set of FollowExpr
652  @label: asm_label instance
653  @line: integer
654  @modifier: boolean
655  @step: integer
656  """
657  dependencies = set()
658  for follow_expr in follow_exprs:
659  dependencies.add(FollowExpr(follow_expr.follow,
660  DependencyNode(label,
661  follow_expr.element,
662  line,
663  step,
664  modifier=modifier)))
665  return dependencies
666 
667  @staticmethod
668  def extract_depnodes(follow_exprs, only_follow=False):
669  """Extract depnodes from a set of FollowExpr(Depnodes)
670  @only_follow: (optional) extract only elements to follow"""
671  return set(follow_expr.element
672  for follow_expr in follow_exprs
673  if not(only_follow) or follow_expr.follow)
674 
675 
677 
678  """Implementation of a dependency graph
679 
680  A dependency graph contains DependencyNode as nodes. The oriented edges
681  stand for a dependency.
682  The dependency graph is made of the lines of a group of IRblock
683  *explicitely* or *implicitely* involved in the equation of given element.
684  """
685 
686  def __init__(self, ira, implicit=False, apply_simp=True, follow_mem=True,
687  follow_call=True):
688  """Create a DependencyGraph linked to @ira
689  The IRA graph must have been computed
690 
691  @ira: IRAnalysis instance
692  @implicit: (optional) Imply implicit dependencies
693 
694  Following arguments define filters used to generate dependencies
695  @apply_simp: (optional) Apply expr_simp
696  @follow_mem: (optional) Track memory syntactically
697  @follow_call: (optional) Track through "call"
698  """
699  # Init
700  self._ira = ira
701  self._implicit = implicit
702  self._step_counter = itertools.count()
703  self._current_step = next(self._step_counter)
704 
705  # The IRA graph must be computed
706  assert hasattr(self._ira, 'g')
707 
708  # Create callback filters. The order is relevant.
709  self._cb_follow = []
710  if apply_simp:
711  self._cb_follow.append(self._follow_simp_expr)
712  self._cb_follow.append(lambda exprs: self._follow_exprs(exprs,
713  follow_mem,
714  follow_call))
715  self._cb_follow.append(self._follow_nolabel)
716 
717  @property
718  def step_counter(self):
719  "Iteration counter"
720  return self._step_counter
721 
722  @property
723  def current_step(self):
724  "Current value of iteration counter"
725  return self._current_step
726 
727  def inc_step(self):
728  "Increment and return the current step"
729  self._current_step = next(self._step_counter)
730  return self._current_step
731 
732  @staticmethod
733  def _follow_simp_expr(exprs):
734  """Simplify expression so avoid tracking useless elements,
735  as: XOR EAX, EAX
736  """
737  follow = set()
738  for expr in exprs:
739  follow.add(expr_simp(expr))
740  return follow, set()
741 
742  @staticmethod
743  def get_expr(expr, follow, nofollow):
744  """Update @follow/@nofollow according to insteresting nodes
745  Returns same expression (non modifier visitor).
746 
747  @expr: expression to handle
748  @follow: set of nodes to follow
749  @nofollow: set of nodes not to follow
750  """
751  if isinstance(expr, m2_expr.ExprId):
752  follow.add(expr)
753  elif isinstance(expr, m2_expr.ExprInt):
754  nofollow.add(expr)
755  return expr
756 
757  @staticmethod
758  def follow_expr(expr, follow, nofollow, follow_mem=False, follow_call=False):
759  """Returns True if we must visit sub expressions.
760  @expr: expression to browse
761  @follow: set of nodes to follow
762  @nofollow: set of nodes not to follow
763  @follow_mem: force the visit of memory sub expressions
764  @follow_call: force the visit of call sub expressions
765  """
766  if not follow_mem and isinstance(expr, m2_expr.ExprMem):
767  nofollow.add(expr)
768  return False
769  if not follow_call and expr.is_function_call():
770  nofollow.add(expr)
771  return False
772  return True
773 
774  @classmethod
775  def _follow_exprs(cls, exprs, follow_mem=False, follow_call=False):
776  """Extracts subnodes from exprs and returns followed/non followed
777  expressions according to @follow_mem/@follow_call
778 
779  """
780  follow, nofollow = set(), set()
781  for expr in exprs:
782  expr.visit(lambda x: cls.get_expr(x, follow, nofollow),
783  lambda x: cls.follow_expr(x, follow, nofollow,
784  follow_mem, follow_call))
785  return follow, nofollow
786 
787  @staticmethod
788  def _follow_nolabel(exprs):
789  """Do not follow labels"""
790  follow = set()
791  for expr in exprs:
792  if not expr_is_label(expr):
793  follow.add(expr)
794 
795  return follow, set()
796 
797  def _follow_apply_cb(self, expr):
798  """Apply callback functions to @expr
799  @expr : FollowExpr instance"""
800  follow = set([expr])
801  nofollow = set()
802 
803  for callback in self._cb_follow:
804  follow, nofollow_tmp = callback(follow)
805  nofollow.update(nofollow_tmp)
806 
807  out = set(FollowExpr(True, expr) for expr in follow)
808  out.update(set(FollowExpr(False, expr) for expr in nofollow))
809  return out
810 
811  def _get_irs(self, label):
812  "Return the irs associated to @label"
813  return self._ira.blocs[label].irs
814 
815  def _get_affblock(self, depnode):
816  """Return the list of ExprAff associtiated to @depnode.
817  LINE_NB must be > 0"""
818  return self._get_irs(depnode.label)[depnode.line_nb - 1]
819 
820  def _direct_depnode_dependencies(self, depnode):
821  """Compute and return the dependencies involved by @depnode,
822  over the instruction @depnode.line_,.
823  Return a set of FollowExpr"""
824 
825  if isinstance(depnode.element, m2_expr.ExprInt):
826  # A constant does not have any dependency
827  output = set()
828 
829  elif depnode.line_nb == 0:
830  # Beginning of a block, inter-block resolving is not done here
831  output = set()
832 
833  else:
834  # Intra-block resolving
835  # Get dependencies
836  read = set()
837  modifier = False
838 
839  for affect in self._get_affblock(depnode):
840  if affect.dst == depnode.element:
841  elements = self._follow_apply_cb(affect.src)
842  read.update(elements)
843  modifier = True
844 
845  # If it's not a modifier affblock, reinject current element
846  if not modifier:
847  read = set([FollowExpr(True, depnode.element)])
848 
849  # Build output
850  output = FollowExpr.to_depnodes(read, depnode.label,
851  depnode.line_nb - 1, modifier,
852  self.current_step)
853  return output
854 
855  def _resolve_intrablock_dep(self, depdict):
856  """Resolve the dependencies of nodes in @depdict.pending inside
857  @depdict.label until a fixed point is reached.
858  @depdict: DependencyDict to update"""
859 
860  # Prepare the work list
861  todo = set(depdict.pending)
862 
863  # Pending states will be handled
864  depdict.pending.clear()
865 
866  while todo:
867  depnode = todo.pop()
868  if isinstance(depnode.element, m2_expr.ExprInt):
869  # A constant does not have any dependency
870  continue
871 
872  if depdict.is_head(depnode):
873  depdict.pending.add(depnode)
874  # A head cannot have dependencies inside the current IRblock
875  continue
876 
877  # Find dependency of the current depnode
878  sub_depnodes = self._direct_depnode_dependencies(depnode)
879  depdict.cache[depnode] = FollowExpr.extract_depnodes(sub_depnodes)
880 
881  # Add to the worklist its dependencies
882  todo.update(FollowExpr.extract_depnodes(sub_depnodes,
883  only_follow=True))
884 
885  # Pending states will be overriden in cache
886  for depnode in depdict.pending:
887  try:
888  del depdict.cache[depnode]
889  except KeyError:
890  continue
891 
892  def _get_previousblocks(self, label):
893  """Return an iterator on predecessors blocks of @label, with their
894  lengths"""
895  preds = self._ira.g.predecessors_iter(label)
896  for pred_label in preds:
897  length = len(self._get_irs(pred_label))
898  yield (pred_label, length)
899 
900  def _compute_interblock_dep(self, depnodes, heads):
901  """Create a DependencyDict from @depnodes, and propagate
902  DependencyDicts through all blocs
903  """
904  # Create a DependencyDict which will only contain our depnodes
905  current_depdict = DependencyDict(list(depnodes)[0].label, [])
906  current_depdict.pending.update(depnodes)
907 
908  # Init the work list
909  done = {}
910  todo = deque([current_depdict])
911 
912  while todo:
913  depdict = todo.popleft()
914 
915  # Update the dependencydict until fixed point is reached
916  self._resolve_intrablock_dep(depdict)
917  self.inc_step()
918 
919  # Clean irrelevant path
920  depdict.filter_unmodifier_loops(self._implicit, self._ira.IRDst)
921 
922  # Avoid infinite loops
923  label = depdict.label
924  if depdict in done.get(label, []):
925  continue
926  done.setdefault(label, []).append(depdict)
927 
928  # No more dependencies
929  if len(depdict.pending) == 0:
930  yield depdict.copy()
931  continue
932 
933  # Has a predecessor ?
934  is_final = True
935 
936  # Propagate the DependencyDict to all parents
937  for label, irb_len in self._get_previousblocks(depdict.label):
938  is_final = False
939 
940  # Duplicate the DependencyDict
941  new_depdict = depdict.extend(label)
942 
943  if self._implicit:
944  # Implicit dependencies: IRDst will be link with heads
945  implicit_depnode = DependencyNode(label, self._ira.IRDst,
946  irb_len,
947  self.current_step,
948  modifier=False)
949 
950  # Create links between DependencyDict
951  for depnode_head in depdict.pending:
952  # Follow the head element in the parent
953  new_depnode = DependencyNode(label, depnode_head.element,
954  irb_len,
955  self.current_step)
956  # The new node has to be analysed
957  new_depdict.cache[depnode_head] = set([new_depnode])
958  new_depdict.pending.add(new_depnode)
959 
960  # Handle implicit dependencies
961  if self._implicit:
962  new_depdict.cache[depnode_head].add(implicit_depnode)
963  new_depdict.pending.add(implicit_depnode)
964 
965  # Manage the new element
966  todo.append(new_depdict)
967 
968  # Return the node if it's a final one, ie. it's a head (in graph
969  # or defined by caller)
970  if is_final or depdict.label in heads:
971  yield depdict.copy()
972 
973  def get(self, label, elements, line_nb, heads):
974  """Compute the dependencies of @elements at line number @line_nb in
975  the block named @label in the current IRA, before the execution of
976  this line. Dependency check stop if one of @heads is reached
977  @label: asm_label instance
978  @element: set of Expr instances
979  @line_nb: int
980  @heads: set of asm_label instances
981  Return an iterator on DiGraph(DependencyNode)
982  """
983 
984  # Init the algorithm
985  input_depnodes = set()
986  for element in elements:
987  input_depnodes.add(DependencyNode(label, element, line_nb,
988  self.current_step))
989 
990  # Compute final depdicts
991  depdicts = self._compute_interblock_dep(input_depnodes, heads)
992 
993  # Unify solutions
994  unified = []
995  cls_res = DependencyResultImplicit if self._implicit else \
996  DependencyResult
997 
998  for final_depdict in depdicts:
999  # Keep only relevant nodes
1000  final_depdict.clean_modifiers_in_cache(input_depnodes)
1001  final_depdict.filter_used_nodes(input_depnodes)
1002 
1003  # Remove duplicate solutions
1004  if final_depdict not in unified:
1005  unified.append(final_depdict)
1006 
1007  # Return solutions as DiGraph
1008  yield cls_res(self._ira, final_depdict, input_depnodes)
1009 
1010  def get_from_depnodes(self, depnodes, heads):
1011  """Alias for the get() method. Use the attributes of @depnodes as
1012  argument.
1013  PRE: Labels and lines of depnodes have to be equals
1014  @depnodes: set of DependencyNode instances
1015  @heads: set of asm_label instances
1016  """
1017  lead = list(depnodes)[0]
1018  elements = set(depnode.element for depnode in depnodes)
1019  return self.get(lead.label, elements, lead.line_nb, heads)
1020 
1021  def get_from_end(self, label, elements, heads):
1022  """Alias for the get() method. Consider that the dependency is asked at
1023  the end of the block named @label.
1024  @label: asm_label instance
1025  @elements: set of Expr instances
1026  @heads: set of asm_label instances
1027  """
1028  return self.get(label, elements, len(self._get_irs(label)), heads)