Otawa  0.10
HalfAbsInt.h
Go to the documentation of this file.
1 /*
2  * HalfAbsInt class interface.
3  *
4  * This file is part of OTAWA
5  * Copyright (c) 2007, IRIT UPS.
6  *
7  * OTAWA is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * OTAWA is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with OTAWA; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  */
22 
23 
24 #ifndef OTAWA_DFA_HAI_HALFABSINT_H
25 #define OTAWA_DFA_HAI_HALFABSINT_H
26 
27 
28 #include <elm/assert.h>
29 #include <elm/genstruct/VectorQueue.h>
30 #include <elm/genstruct/Vector.h>
31 #include <otawa/cfg/CFG.h>
32 #include <otawa/cfg/BasicBlock.h>
33 #include <otawa/cfg/Edge.h>
34 #include <otawa/util/Dominance.h>
36 #include <otawa/prop/Identifier.h>
37 #include <otawa/prog/WorkSpace.h>
38 
39 
40 // debugging
41 #if defined(NDEBUG) || !defined(HAI_DEBUG)
42 # define HAI_TRACE(x)
43 #else
44 # define HAI_TRACE(x) cerr << x << io::endl
45 #endif
46 
47 namespace otawa { namespace dfa { namespace hai {
48 
49 // context definition
50 typedef enum hai_context_t {
51  CTX_LOOP = 0,
54 
55 // configuration
56 extern Identifier<bool> FIXED;
61 
62 // abstract interpretater
63 template <class FixPoint>
64 class HalfAbsInt {
65 private:
66  FixPoint& fp;
74  typename FixPoint::Domain in,out;
75  elm::genstruct::Vector<Edge*> call_edges; /* call_edge == current node's call-edge to another CFG */
77  bool enter_call; /* enter_call == true: we need to process this call. enter_call == false: already processed (call return) */
78  bool fixpoint;
79  bool mainEntry;
80 
82  inline bool isEdgeDone(Edge *edge);
83  inline bool tryAddToWorkList(BasicBlock *bb);
85  void inputProcessing(typename FixPoint::Domain &entdom);
86  void outputProcessing();
87  void addSuccessors();
88 
89 public:
90  inline typename FixPoint::FixPointState *getFixPointState(BasicBlock *bb);
91  int solve(otawa::CFG *main_cfg = 0,
92  typename FixPoint::Domain *entdom = 0, BasicBlock *start_bb = 0);
93  inline HalfAbsInt(FixPoint& _fp, WorkSpace& _fw);
94  inline ~HalfAbsInt(void);
95  inline typename FixPoint::Domain backEdgeUnion(BasicBlock *bb);
96  inline typename FixPoint::Domain entryEdgeUnion(BasicBlock *bb);
97 };
98 
99 template <class FixPoint>
101 
102 template <class FixPoint>
103 inline HalfAbsInt<FixPoint>::HalfAbsInt(FixPoint& _fp, WorkSpace& _fw)
104 : fp(_fp),
105  fw(_fw),
106  entry_cfg(**ENTRY_CFG(_fw)),
107  cur_cfg(0),
108  current(0),
109  in(_fp.bottom()),
110  out(_fp.bottom()),
111  next_edge(0),
112  enter_call(0),
113  fixpoint(0),
114  mainEntry(false)
115 {
119  fp.init(this);
120 }
121 
122 
123 template <class FixPoint>
125  delete workList;
126 
127  // clean remaining states
129  cfgs.add(&entry_cfg);
130  for(int i = 0; i < cfgs.count(); i++)
131  for(CFG::BBIterator bb(cfgs[i]); bb; bb++)
132  for(BasicBlock::OutIterator out(bb); out; out++) {
133  this->fp.unmarkEdge(*out);
134  if(out->kind() == Edge::CALL && !cfgs.contains(out->calledCFG()))
135  cfgs.add(out->calledCFG());
136  }
137 }
138 
139 
140 
141 template <class FixPoint>
142 inline typename FixPoint::FixPointState *HalfAbsInt<FixPoint>::getFixPointState(BasicBlock *bb) {
143  return(FIXPOINT_STATE(bb));
144 }
145 
146 /* This function computes the IN state, and unmark the not-needed-anymore edges.
147  */
148 template <class FixPoint>
149 void HalfAbsInt<FixPoint>::inputProcessing(typename FixPoint::Domain &entdom) {
150  fp.assign(in, fp.bottom());
151 
152  // main entry case
153  if (mainEntry) {
154  fp.assign(in, entdom);
155  mainEntry = false;
156  }
157 
158  // function-call entry case, state is on the called CFG's entry
159  else if (current->isEntry()) {
160  fp.assign(in, *fp.getMark(current));
161  fp.unmarkEdge(current);
162  }
163 
164  // call return case, merge return-state for all functions called from this node
165  else if (!next_edge && !call_edges.isEmpty()) {
166 
167  // join return edges of functions
168  for (elm::genstruct::Vector<Edge*>::Iterator iter(call_edges); iter; iter++) {
169  fp.lub(in, *fp.getMark(*iter));
170  fp.unmarkEdge(*iter);
171  }
172 
173  // cleanup entry edges
174  for(BasicBlock::InIterator in(current); in; in++)
175  fp.unmarkEdge(*in);
176  }
177 
178  // loop header case: launch fixPoint()
179  else if(LOOP_HEADER(current)) {
180 
181  // Compute the IN thanks to the fixpoint handler
182  if(FIRST_ITER(current))
183  FIXPOINT_STATE(current) = fp.newState();
184  fp.fixPoint(current, fixpoint, in, FIRST_ITER(current));
185  HAI_TRACE("\t\tat loop header " << current << ", fixpoint reached = " << fixpoint);
186  if (FIRST_ITER(current)) {
187  ASSERT(!fixpoint);
188  FIRST_ITER(current) = false;
189  }
190  FIXED(current) = fixpoint;
191 
192  /* Unmark edges depending on the fixpoint status.
193  * If fixpoint, unmark all in-edges, else unmark only back-edges
194  */
195 
196  /* In any case, the values of the back-edges are not needed anymore */
197  for (BasicBlock::InIterator inedge(current); inedge; inedge++) {
198  if (inedge->kind() == Edge::CALL)
199  continue;
200  if (Dominance::dominates(current, inedge->source())) {
201  fp.unmarkEdge(*inedge);
202  //HAI_TRACE("unmarking back-edge: " << inedge->source() << " -> " << inedge->target());
203  }
204  }
205 
206  if (fixpoint) {
207  /* Cleanups associated with end of the processing of a loop */
208  fp.fixPointReached(current);
209  delete FIXPOINT_STATE(current);
210  FIXPOINT_STATE(current) = 0;
211  FIRST_ITER(current) = true;
212 
213  /* The values of the entry edges are not needed anymore */
214  for (BasicBlock::InIterator inedge(current); inedge; inedge++) {
215  if (inedge->kind() == Edge::CALL)
216  continue;
217  if (HAI_BYPASS_TARGET(current) && (inedge->kind() == Edge::VIRTUAL_RETURN))
218  continue;
219  if (!Dominance::dominates(current, inedge->source())) {
220  //HAI_TRACE("unmarking entry-edge " << inedge->source() << " -> " << inedge->target());
221  fp.unmarkEdge(*inedge);
222  }
223  }
224  if (HAI_BYPASS_TARGET(current)) {
225  fp.unmarkEdge(current);
226  }
227  }
228  }
229 
230  /* Case of the simple basic block: IN = union of the OUTs of the predecessors. */
231  else {
232 
233  /* Un-mark all the in-edges since the values are not needed. */
234  for (BasicBlock::InIterator inedge(current); inedge; inedge++) {
235  ASSERT(inedge->kind() != Edge::CALL);
236  if (HAI_BYPASS_TARGET(current) && (inedge->kind() == Edge::VIRTUAL_RETURN))
237  continue;
238  typename FixPoint::Domain *edgeState = fp.getMark(*inedge);
239  //HAI_TRACE("got state: " << *edgeState << "\n");
240 
241  ASSERT(edgeState);
242  fp.updateEdge(*inedge, *edgeState);
243  fp.lub(in, *edgeState);
244  if(!next_edge)
245  fp.unmarkEdge(*inedge);
246  //HAI_TRACE("unmarking in-edge " << inedge->source() << " -> " << inedge->target());
247 
248  }
249  if (HAI_BYPASS_TARGET(current)) {
250  typename FixPoint::Domain *bypassState = fp.getMark(current);
251  ASSERT(bypassState);
252  fp.lub(in, *bypassState);
253  fp.unmarkEdge(current);
254  //HAI_TRACE("unmarking bypass in-edge " << HAI_BYPASS_TARGET(current) << " -> " << current);
255  }
256  }
257 }
258 
259 /* This function computes the out-state, propagates it to the needed edges, and
260  * try to add the next node(s) to the worklist.
261  */
262 template <class FixPoint>
264 
265  // Fixpoint reached: activate the associated loop-exit-edges
266  if(LOOP_HEADER(current) && fixpoint) {
267  genstruct::Vector<BasicBlock*> alreadyAdded;
268  if ((!EXIT_LIST(current)) || EXIT_LIST(current)->isEmpty())
269  cerr << "WARNING: infinite loop found at " << current << io::endl;
270  else
271  for (elm::genstruct::Vector<Edge*>::Iterator iter(**EXIT_LIST(current)); iter; iter++) {
272  HAI_TRACE("\t\tpushing edge " << iter->source() << " -> " << iter->target());
273  if(!alreadyAdded.contains(iter->target()) && tryAddToWorkList(iter->target()))
274  alreadyAdded.add(iter->target());
275  fp.leaveContext(*fp.getMark(*iter), current, CTX_LOOP);
276  }
277  }
278 
279  // Exit from function: pop callstack, mark edge with return state for the caller
280  else if (current->isExit() && (callStack->length() > 0)) {
281 
282  // apply current block
283  HAI_TRACE("\t\tupdating exit block while returning from call at " << current);
284  fp.update(out, in, current);
285 
286  // restore previous context
287  Edge *edge = callStack->pop();
288  cur_cfg = cfgStack->pop();
289  HAI_TRACE("\t\treturning to CFG " << cur_cfg->label());
290  fp.leaveContext(out, cur_cfg->entry(), CTX_FUNC);
291 
292  // record function output state
293  fp.markEdge(edge, out);
294  workList->push(edge->source());
295  }
296 
297  // process call node, and there is still function calls to process
298  else if (next_edge) {
299  /* If first time, mark entry points. */
300  if (enter_call) {
301  HAI_TRACE("\t\tupdating for BB " << current << " while visiting call-node for the first time");
302  fp.update(out, in, current);
303  for (elm::genstruct::Vector<Edge*>::Iterator iter(call_edges); iter; iter++) {
304  // Mark all the function entries
305  BasicBlock *func_entry = iter->calledCFG()->entry();
306  fp.markEdge(func_entry, out);
307  }
308  }
309 
310  // save current context
311  callStack->push(next_edge);
312  cfgStack->push(cur_cfg);
313 
314  // setup new context
315  cur_cfg = next_edge->calledCFG();
316  HAI_TRACE("\tcalling CFG " << cur_cfg->label());
317  workList->push(cur_cfg->entry());
318  fp.enterContext(out, cur_cfg->entry(), CTX_FUNC);
319  }
320 
321  // visit call-node for the last-time, propagate state to successors
322  else if (!call_edges.isEmpty()) {
323  HAI_TRACE("\t\treturning from function calls at " << current);
324  fp.assign(out, in);
325  addSuccessors();
326  }
327 
328  // Standard case, update and propagate state to successors
329  else {
330  HAI_TRACE("\t\tupdating " << current);
331  fp.update(out, in, current);
332  fp.blockInterpreted(current, in, out, cur_cfg, callStack);
333  addSuccessors();
334  }
335 }
336 
337 template <class FixPoint>
339 
340  for (BasicBlock::OutIterator outedge(current); outedge; outedge++) {
341  if (outedge->kind() == Edge::CALL)
342  continue;
343 
344  if (HAI_BYPASS_SOURCE(current) && outedge->kind() == Edge::VIRTUAL_CALL)
345  continue;
346  if (HAI_DONT_ENTER(outedge->target()))
347  continue;
348 
349  fp.markEdge(*outedge, out);
350 
351  //HAI_TRACE("marking edge " << outedge->source() << " -> " << outedge->target() << "\n");
352 
353  tryAddToWorkList(outedge->target());
354  }
355 
356  if (HAI_BYPASS_SOURCE(current)) {
357 
358  fp.markEdge(HAI_BYPASS_SOURCE(current), out);
359 
360  HAI_TRACE("marking bypass out-edge " << current << " -> " << HAI_BYPASS_SOURCE(current));
361 
362  tryAddToWorkList(HAI_BYPASS_SOURCE(current));
363  }
364 }
365 
366 template <class FixPoint>
368  Edge *next_edge = 0;
369  enter_call = true;
370  call_edges.clear();
371  for (BasicBlock::OutIterator outedge(bb); outedge; outedge++) {
372  if ((outedge->kind() == Edge::CALL) && (!HAI_DONT_ENTER(outedge->calledCFG()))) {
373  call_edges.add(*outedge);
374  if (fp.getMark(*outedge)) {
375  enter_call = false;
376  } else if (!next_edge)
377  next_edge = *outedge;
378  }
379  }
380  return(next_edge);
381 }
382 
383 
384 template <class FixPoint>
385 int HalfAbsInt<FixPoint>::solve(otawa::CFG *main_cfg, typename FixPoint::Domain *entdom, BasicBlock *start_bb) {
386  int iterations = 0;
387  typename FixPoint::Domain default_entry(fp.entry());
388 
389  // initialization
390  workList->clear();
391  callStack->clear();
392  mainEntry = true;
393  if(!main_cfg)
394  main_cfg = &entry_cfg;
395  if (!start_bb)
396  start_bb = main_cfg->entry();
397  if(!entdom)
398  entdom = &default_entry;
399 
400  ASSERT(main_cfg);
401  ASSERT(start_bb);
402 
403  // work list initialization
404  cur_cfg = main_cfg;
405  workList->push(start_bb);
406 
407  // main loop
408  while (!workList->isEmpty()) {
409  iterations++;
410  fixpoint = false;
411  current = workList->pop();
412  next_edge = detectCalls(enter_call, call_edges, current);
413 
414  HAI_TRACE("\n\tPROCESSING " << current);
415  inputProcessing(*entdom);
416  HAI_TRACE("\t\tunion of inputs = " << in);
417  outputProcessing();
418  HAI_TRACE("\t\toutput = " << out);
419  }
420  return(iterations);
421 }
422 
423 
424 template <class FixPoint>
425 inline typename FixPoint::Domain HalfAbsInt<FixPoint>::backEdgeUnion(BasicBlock *bb) {
426  typename FixPoint::Domain result(fp.bottom());
427 
428  HAI_TRACE("\t\tjoining back edges");
429  if (FIRST_ITER(bb)) {
430  /* If this is the first iteration, the back edge union is Bottom. */
431  return(result);
432  }
433  for (BasicBlock::InIterator inedge(bb); inedge; inedge++) {
434  if (inedge->kind() == Edge::CALL)
435  continue;
436  if (Dominance::dominates(bb, inedge->source())) {
437  typename FixPoint::Domain *edgeState = fp.getMark(*inedge);
438  ASSERT(edgeState);
439  HAI_TRACE("\t\t\twith " << *inedge << " = " << *edgeState);
440  fp.lub(result, *edgeState);
441  }
442 
443  }
444  //HAI_TRACE("\t\tgiving " << result);
445  return(result);
446 }
447 
448 
449 
450 template <class FixPoint>
451 inline typename FixPoint::Domain HalfAbsInt<FixPoint>::entryEdgeUnion(BasicBlock *bb) {
452  typename FixPoint::Domain result(fp.bottom());
453 
454  HAI_TRACE("\t\tunion of input edges");
455  for (BasicBlock::InIterator inedge(bb); inedge; inedge++) {
456  if (inedge->kind() == Edge::CALL)
457  continue;
458  if (HAI_BYPASS_TARGET(bb) && (inedge->kind() == Edge::VIRTUAL_RETURN))
459  continue;
460  if (!Dominance::dominates(bb, inedge->source())) {
461  typename FixPoint::Domain *edgeState = fp.getMark(*inedge);
462  ASSERT(edgeState);
463  HAI_TRACE("\t\t\twith " << *inedge << " = " << *edgeState);
464  fp.lub(result, *edgeState);
465  }
466 
467  }
468  if (HAI_BYPASS_TARGET(bb)) {
469  typename FixPoint::Domain *bypassState = fp.getMark(bb);
470  ASSERT(bypassState);
471  fp.lub(result, *bypassState);
472  }
473  fp.enterContext(result, bb, CTX_LOOP);
474  //HAI_TRACE("\t\tgiving " << result);
475  return(result);
476 }
477 
478 
479 template <class FixPoint>
481  bool add = true;
482  for (BasicBlock::InIterator inedge(bb); inedge; inedge++) {
483  if (inedge->kind() == Edge::CALL)
484  continue;
485  if (HAI_BYPASS_TARGET(bb) && (inedge->kind() == Edge::VIRTUAL_RETURN))
486  continue;
487  if (!isEdgeDone(*inedge)) {
488  add = false;
489  //HAI_TRACE("edge is not done !\n");
490  }
491  }
492  if (HAI_BYPASS_TARGET(bb)) {
493  typename FixPoint::Domain *bypassState = fp.getMark(bb);
494  if (!bypassState)
495  add = false;
496  }
497 
498  if (add) {
499  if (LOOP_HEADER(bb)) {
500 # ifdef DEBUG
501  if (FIRST_ITER(bb) == true)
502  cerr << "Ignoring back-edges for loop header " << bb->number() << " because it's the first iteration.\n";
503 # endif
504  }
505  HAI_TRACE("\t\tadding " << bb << " to worklist");
506  workList->push(bb);
507  }
508  return(add);
509 }
510 
511 
512 template <class FixPoint>
514  /*
515  * If we have a back edge and the target is a loop header at its first iteration, then we may add the target to
516  * the worklist even if the edge status is not calculated yet.
517  *
518  * If other case, we test if the edge status is calculated. If yes, we need to make another check: if the edge is a loop
519  * exit edge, then the loop's fixpoint must have been reached.
520  *
521  * XXX The evaluation order of the conditions is important XXX
522  */
523  return (
524  (fp.getMark(edge) && (!LOOP_EXIT_EDGE(edge) || FIXED(LOOP_EXIT_EDGE(edge))))
525  || (Dominance::dominates(edge->target(), edge->source()) && FIRST_ITER(edge->target()))
526 
527  );
528 }
529 
530 
531 } } } // otawa::dfa:hai
532 
533 #endif // OTAWA_DFA_HAI_HALFABSINT_H
534 
Definition: CFG.h:48
Identifier< bool > FIXED
This property is attached to the loop headers, and is true if the FixPoint for the associated loop ha...
bool tryAddToWorkList(BasicBlock *bb)
Try to add the BasicBlock to the worklist.
Definition: HalfAbsInt.h:480
FixPoint::Domain backEdgeUnion(BasicBlock *bb)
Given a loop header, returns the union of the value of its back edges.
Definition: HalfAbsInt.h:425
dtd::Element edge(dtd::make("edge", _EDGE).attr(source).attr(target).attr(called))
HalfAbsInt(FixPoint &_fp, WorkSpace &_fw)
Definition: HalfAbsInt.h:103
Edges of this kind are only found in a virtual CFG.
Definition: Edge.h:43
bool mainEntry
Definition: HalfAbsInt.h:79
elm::genstruct::Vector< CFG * > * cfgStack
Definition: HalfAbsInt.h:72
BasicBlock * source(void) const
Definition: Edge.h:52
Edge * detectCalls(bool &enter_call, elm::genstruct::Vector< Edge * > &call_edges, BasicBlock *bb)
Definition: HalfAbsInt.h:367
bool fixpoint
Definition: HalfAbsInt.h:78
BasicBlock * current
Definition: HalfAbsInt.h:73
Identifier< BasicBlock * > LOOP_EXIT_EDGE
Is defined for an Edge if this Edge is the exit-edge of any loop.
Kind of an edge matching a sub-program call.
Definition: Edge.h:40
Identifier< elm::genstruct::Vector< Edge * > * > EXIT_LIST
Defined for any BasicBlock that is a loop header.
Identifier< bool > HAI_DONT_ENTER
This property, when set to TRUE on a BasicBlock or a CFG, prevents HalfAbsInt from following edges to...
elm::genstruct::Vector< Edge * > * callStack
Definition: HalfAbsInt.h:71
Identifier< bool > LOOP_HEADER
Identifier for marking basic blocks that are entries of loops.
sys::SystemInStream & in
BasicBlock * entry(void)
Get the entry basic block of the CFG.
Definition: CFG.h:63
FixPoint::Domain entryEdgeUnion(BasicBlock *bb)
Given a loop header, returns the union of the value of is entry edges (the entry edges are the in-edg...
Definition: HalfAbsInt.h:451
Edge * next_edge
Definition: HalfAbsInt.h:76
dtd::Element bb(dtd::make("bb", _BB).attr(id).attr(address).attr(size))
io::Output cerr
Control Flow Graph representation.
Definition: CFG.h:42
Identifier< BasicBlock * > HAI_BYPASS_TARGET
Identifier< CFG * > ENTRY_CFG
This property may be used to pass the entry CFG to a CFG processor or is used by the CFG processor to...
Definition: CFGProcessor.h:64
FixPoint::Domain in
Definition: HalfAbsInt.h:74
void outputProcessing()
Definition: HalfAbsInt.h:263
Implements abstract interpretation.
Definition: HalfAbsInt.h:64
bool enter_call
Definition: HalfAbsInt.h:77
static Identifier< typename FixPoint::FixPointState * > FIXPOINT_STATE
Definition: HalfAbsInt.h:81
A workspace represents a program, its run-time and all information about WCET computation or any othe...
Definition: WorkSpace.h:67
static bool dominates(BasicBlock *bb1, BasicBlock *bb2)
Test if the first basic block dominates the second one.
Definition: util_Dominance.cpp:164
~HalfAbsInt(void)
Definition: HalfAbsInt.h:124
Definition: BasicBlock.h:165
int number(void) const
Get the number hooked on this basic block, that is, value of ID_Index property.
Definition: BasicBlock.h:146
Identifier< bool > FIRST_ITER
This property is attached for the loop header, and is true if the first iteration of the associated l...
FixPoint & fp
Definition: HalfAbsInt.h:66
sys::SystemOutStream & out
elm::genstruct::Vector< BasicBlock * > * workList
Definition: HalfAbsInt.h:70
hai_context_t
Definition: HalfAbsInt.h:50
Definition: BasicBlock.h:160
CFG * cur_cfg
Definition: HalfAbsInt.h:69
inst add(int d, int a, int b)
Definition: inst.h:163
This class represents edges in the CFG representation.
Definition: Edge.h:33
Definition: HalfAbsInt.h:51
CFG & entry_cfg
Definition: HalfAbsInt.h:68
BasicBlock * target(void) const
Definition: Edge.h:53
#define HAI_TRACE(x)
Definition: HalfAbsInt.h:42
FixPoint::Domain out
Definition: HalfAbsInt.h:74
int solve(otawa::CFG *main_cfg=0, typename FixPoint::Domain *entdom=0, BasicBlock *start_bb=0)
Do the abstract interpretation of the CFG.
Definition: HalfAbsInt.h:385
This is the minimal definition of a basic block.
Definition: BasicBlock.h:43
void inputProcessing(typename FixPoint::Domain &entdom)
Definition: HalfAbsInt.h:149
WorkSpace & fw
Definition: HalfAbsInt.h:67
bool contains(const T &value) const
FixPoint::FixPointState * getFixPointState(BasicBlock *bb)
Get the FixPointState of a loop.
Definition: HalfAbsInt.h:142
Identifier< BasicBlock * > HAI_BYPASS_SOURCE
This property enables the user to create a virtual "bypass" edge from the source block to the target ...
void addSuccessors()
Definition: HalfAbsInt.h:338
int count(void) const
Definition: HalfAbsInt.h:52
bool isEdgeDone(Edge *edge)
Tests if an edge verify the conditions needed to add its target BasicBlock.
Definition: HalfAbsInt.h:513
elm::genstruct::Vector< Edge * > call_edges
Definition: HalfAbsInt.h:75
Edges of this kind are only found in a virtual CFG.
Definition: Edge.h:42