1 """Provide dependency graph"""
3 from collections
import deque
4 from UserDict
import IterableUserDict
22 """Node elements of a DependencyGraph
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
29 __slots__ = [
"_label",
"_element",
"_line_nb",
"_modifier",
30 "_step",
"_nostep_repr",
"_hash"]
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
49 """Returns a hash of @self to uniquely identify @self"""
53 """Returns True if @self and @depnode are equals.
54 The attribute 'step' is not considered in the comparison.
56 if not isinstance(depnode, self.__class__):
58 return (self.
label == depnode.label
and
64 """Compares @self with @node. The step attribute is not taken into
65 account in the comparison.
67 if not isinstance(node, self.__class__):
68 raise ValueError(
"Compare error between %s, %s" % (self.__class__,
71 (node.label, node.element, node.line_nb))
74 """Returns a string representation of DependencyNode"""
75 return "<%s %s %s %s M:%s S:%s>" % (self.__class__.__name__,
81 """Returns a string representation of DependencyNode"""
86 """Returns a representation of @self ignoring the step attribute"""
91 "Name of the current IRBlock"
96 "Current tracked Expr"
101 "Line in the current IRBlock"
106 "Step of the current node"
111 """Evaluating the current line involves a modification of tracked
117 """Evaluating the current line involves a modification of tracked
118 dependencies if @value.
125 """Wrapper class for cache dictionnary"""
128 """Create a CacheWrapper with value @dct."""
129 IterableUserDict.__init__(self, dct)
134 """Returns True if the nostep caches are equals"""
141 """List of dictonnary keys without the step attribute.
142 The list is generated once when the method is called and not updated
146 self.
_nostep_keys = set(key.nostep_repr
for key
in self.data)
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.
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))
166 """Internal structure for the DependencyGraph algorithm"""
167 __slots__ = [
"_label",
"_history",
"_pending",
"_cache"]
170 """Create a DependencyDict
171 @label: asm_label, current IRblock label
172 @history: list of DependencyDict
182 if not isinstance(depdict, self.__class__):
184 return (self.
_label == depdict.label
and
188 if not isinstance(depdict, self.__class__):
189 raise ValueError(
"Compare error %s != %s" % (self.__class__,
192 (depdict.label, depdict.cache, depdict.pending))
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)
201 "Return a copy of itself"
204 new_history = list(self.
history)
208 for key, values
in self.cache.iteritems():
209 depdict.cache[key] = set(values)
210 depdict.pending.update(self.
pending)
215 """Return a copy of itself, with itself in history and pending clean
216 @label: asm_label instance for the new DependencyDict's label
219 for key, values
in self.cache.iteritems():
220 depdict.cache[key] = set(values)
224 """Return an iterator on the list of heads as defined in 'is_head'"""
225 for key
in self.
cache:
231 "Label of the current block"
236 """List of DependencyDict needed to reach the current DependencyDict
237 The first is the oldest"""
242 "Dictionnary of DependencyNode and their dependencies"
247 """Dictionnary of DependencyNode and their dependencies, waiting for
252 """Find modifier nodes in cache starting from @nodes_heads.
255 worklist_depnodes = list(nodes_heads)
262 while worklist_depnodes:
263 depnode = worklist_depnodes.pop()
269 if depnode
not in self.
_cache:
271 if not depnode.modifier:
276 dependencies = self.
_cache[depnode]
277 for son
in dependencies:
278 worklist_depnodes.append(son)
280 worklist.append((depnode, dependencies))
284 depnode, dependencies = worklist.pop()
286 for node
in dependencies:
288 parallels.append([node])
290 parallels.append(cache[node])
292 for parallel
in itertools.product(*[p
for p
in parallels
if p]):
299 """Remove intermediary states (non modifier depnodes) in the internal
305 """Recursively build the final list of DiGraph, and clean up unmodifier
307 @depnode: starting node
310 if depnode
not in self.
_cache or \
314 graph.add_node(depnode)
318 dependencies = list(self.
_cache[depnode])
321 for sub_depnode
in dependencies:
326 graph.add_node(depnode)
327 for head
in dependencies:
328 graph.add_uniq_edge(head, depnode)
330 for subgraphs
in itertools.product(graphs):
331 for sourcegraph
in subgraphs:
332 for node
in sourcegraph.nodes():
334 for edge
in sourcegraph.edges():
335 graph.add_uniq_edge(*edge)
341 """Return a DiGraph corresponding to computed dependencies, with
342 @starting_nodes as leafs
343 @starting_nodes: set of DependencyNode instance
348 for starting_node
in starting_nodes:
353 for sourcegraph
in subgraphs:
354 for node
in sourcegraph.nodes():
356 for edge
in sourcegraph.edges():
357 graph.add_uniq_edge(*edge)
361 """Keep only depnodes which are in the path of @node_heads in the
363 @node_heads: set of DependencyNode instance
366 todo = set(node_heads)
372 if node
in used_nodes:
375 if not node
in self.
_cache:
377 for sub_node
in self.
_cache[node]:
381 for key
in list(self._cache.keys()):
382 if key
not in used_nodes:
387 Remove unmodifier node creating dependency loops over
388 pending elements in cache.
390 @irdst: ExprId instance of IRDst register
395 for hist_dict
in reversed(self.
history):
396 if hist_dict.label == self.
label:
397 previous_dict = hist_dict
400 if not previous_dict:
403 nostep_pending = [node.nostep_repr
for node
in self.
pending]
406 for depnode
in previous_dict.pending:
407 if (depnode.nostep_repr
not in nostep_pending
or
408 implicit
and depnode.element == irdst):
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)
421 for dependencies
in self._cache.itervalues():
422 if key
in dependencies:
423 dependencies.remove(key)
424 dependencies.add(depnode)
426 if self._cache.has_key(key):
431 Walk from @depnode until a node with the same nostep_repr is
433 Returns a set of unmodifier nodes met in the path if no modifier was
435 Returns set() if there exist a modifier node on the path.
437 if not self.cache.has_key(depnode):
440 todo = set(self.
cache[depnode])
441 unmodifier_nodes = []
446 if node
in unmodifier_nodes:
450 unmodifier_nodes.append(node)
451 if not node
in self.
_cache:
453 if node.nostep_repr == depnode.nostep_repr:
454 unmodifier_nodes.append(node)
457 for sub_node
in self.
_cache[node]:
460 return unmodifier_nodes
465 """Container and methods for DependencyGraph results"""
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
484 """Returns a DiGraph instance representing the DependencyGraph"""
491 """List of depdict corresponding to the blocks encountered in the
493 return list(self._depdict.history) + [self.
_depdict]
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)
503 """Set of nodes directly and indirectly influencing
504 @self.input_depnodes"""
506 for depnodes
in self._depdict.cache.values():
507 output.update(depnodes)
512 """List of labels containing nodes influencing @self.input_depnodes.
513 The history order is preserved.
516 used_labels = set(depnode.label
for depnode
in self.
relevant_nodes)
520 for label
in [depdict.label
for depdict
in self.
history]:
521 if label
in used_labels:
528 """Set of DependencyGraph start nodes"""
533 """True if current dictionnary has a loop"""
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
545 Warning: The emulation is not sound if the input nodes depend on loop
549 ctx_init = self._ira.arch.regs.regs_init
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])
566 symb_exec.emulbloc(
irbloc(temp_label, affects), step=step)
569 return {depnode.element: symb_exec.symbols[depnode.element]
570 for depnode
in self.
input}
575 """Stand for a result of a DependencyGraph with implicit option
577 Provide path constraints using the z3 solver"""
578 __slots__ = [
"_ira",
"_depdict",
"_input_depnodes",
"_graph",
579 "_has_loop",
"_solver"]
584 def emul(self, ctx=None, step=False):
586 ctx_init = self._ira.arch.regs.regs_init
594 history_size = len(history)
596 for hist_nb, label
in enumerate(history):
598 affected_lines = set(depnode.line_nb
for depnode
in depnodes
599 if depnode.label == label)
600 irs = self._ira.blocs[label].irs
603 for line_nb
in sorted(affected_lines):
604 affects.append(irs[line_nb])
607 dst = symb_exec.emulbloc(
irbloc(temp_label, affects), step=step)
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))
620 return {depnode.element: symb_exec.symbols[depnode.element]
621 for depnode
in self.
input}
625 """Return True iff the solution path admits at least one solution
628 return self._solver.check().r > 0
632 """If satisfiable, return a valid solution as a Z3 Model instance"""
634 raise ValueError(
"Unsatisfiable")
635 return self._solver.model()
640 "Stand for an element (expression, depnode, ...) to follow or not"
641 __slots__ = [
"follow",
"element"]
649 """Build a set of FollowExpr(DependencyNode) from the @follow_exprs set
651 @follow_exprs: set of FollowExpr
652 @label: asm_label instance
658 for follow_expr
in follow_exprs:
659 dependencies.add(
FollowExpr(follow_expr.follow,
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)
678 """Implementation of a dependency graph
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.
686 def __init__(self, ira, implicit=False, apply_simp=True, follow_mem=True,
688 """Create a DependencyGraph linked to @ira
689 The IRA graph must have been computed
691 @ira: IRAnalysis instance
692 @implicit: (optional) Imply implicit dependencies
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"
706 assert hasattr(self.
_ira,
'g')
712 self._cb_follow.append(
lambda exprs: self.
_follow_exprs(exprs,
724 "Current value of iteration counter"
728 "Increment and return the current step"
734 """Simplify expression so avoid tracking useless elements,
744 """Update @follow/@nofollow according to insteresting nodes
745 Returns same expression (non modifier visitor).
747 @expr: expression to handle
748 @follow: set of nodes to follow
749 @nofollow: set of nodes not to follow
751 if isinstance(expr, m2_expr.ExprId):
753 elif isinstance(expr, m2_expr.ExprInt):
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
766 if not follow_mem
and isinstance(expr, m2_expr.ExprMem):
769 if not follow_call
and expr.is_function_call():
776 """Extracts subnodes from exprs and returns followed/non followed
777 expressions according to @follow_mem/@follow_call
780 follow, nofollow = set(), set()
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
789 """Do not follow labels"""
798 """Apply callback functions to @expr
799 @expr : FollowExpr instance"""
804 follow, nofollow_tmp = callback(follow)
805 nofollow.update(nofollow_tmp)
807 out = set(
FollowExpr(
True, expr)
for expr
in follow)
808 out.update(set(
FollowExpr(
False, expr)
for expr
in nofollow))
812 "Return the irs associated to @label"
813 return self._ira.blocs[label].irs
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]
821 """Compute and return the dependencies involved by @depnode,
822 over the instruction @depnode.line_,.
823 Return a set of FollowExpr"""
825 if isinstance(depnode.element, m2_expr.ExprInt):
829 elif depnode.line_nb == 0:
840 if affect.dst == depnode.element:
842 read.update(elements)
847 read = set([
FollowExpr(
True, depnode.element)])
850 output = FollowExpr.to_depnodes(read, depnode.label,
851 depnode.line_nb - 1, modifier,
856 """Resolve the dependencies of nodes in @depdict.pending inside
857 @depdict.label until a fixed point is reached.
858 @depdict: DependencyDict to update"""
861 todo = set(depdict.pending)
864 depdict.pending.clear()
868 if isinstance(depnode.element, m2_expr.ExprInt):
872 if depdict.is_head(depnode):
873 depdict.pending.add(depnode)
879 depdict.cache[depnode] = FollowExpr.extract_depnodes(sub_depnodes)
882 todo.update(FollowExpr.extract_depnodes(sub_depnodes,
886 for depnode
in depdict.pending:
888 del depdict.cache[depnode]
893 """Return an iterator on predecessors blocks of @label, with their
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)
901 """Create a DependencyDict from @depnodes, and propagate
902 DependencyDicts through all blocs
906 current_depdict.pending.update(depnodes)
910 todo = deque([current_depdict])
913 depdict = todo.popleft()
920 depdict.filter_unmodifier_loops(self.
_implicit, self._ira.IRDst)
923 label = depdict.label
924 if depdict
in done.get(label, []):
926 done.setdefault(label, []).append(depdict)
929 if len(depdict.pending) == 0:
941 new_depdict = depdict.extend(label)
951 for depnode_head
in depdict.pending:
957 new_depdict.cache[depnode_head] = set([new_depnode])
958 new_depdict.pending.add(new_depnode)
962 new_depdict.cache[depnode_head].
add(implicit_depnode)
963 new_depdict.pending.add(implicit_depnode)
966 todo.append(new_depdict)
970 if is_final
or depdict.label
in heads:
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
980 @heads: set of asm_label instances
981 Return an iterator on DiGraph(DependencyNode)
985 input_depnodes = set()
986 for element
in elements:
995 cls_res = DependencyResultImplicit
if self.
_implicit else \
998 for final_depdict
in depdicts:
1000 final_depdict.clean_modifiers_in_cache(input_depnodes)
1001 final_depdict.filter_used_nodes(input_depnodes)
1004 if final_depdict
not in unified:
1005 unified.append(final_depdict)
1008 yield cls_res(self.
_ira, final_depdict, input_depnodes)
1011 """Alias for the get() method. Use the attributes of @depnodes as
1013 PRE: Labels and lines of depnodes have to be equals
1014 @depnodes: set of DependencyNode instances
1015 @heads: set of asm_label instances
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)
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
1028 return self.
get(label, elements, len(self.
_get_irs(label)), heads)
def _resolve_intrablock_dep
def _get_modifiers_in_cache
def filter_unmodifier_loops
def _direct_depnode_dependencies
def clean_modifiers_in_cache
def _non_modifier_in_loop
def _compute_interblock_dep