new parameter decomposition, can't chain results yet
[IRC.git] / Robust / src / Analysis / OwnershipAnalysis / OwnershipAnalysis.java
1 package Analysis.OwnershipAnalysis;
2
3 import Analysis.CallGraph.*;
4 import IR.*;
5 import IR.Flat.*;
6 import IR.Tree.Modifiers;
7 import java.util.*;
8 import java.io.*;
9
10
11 public class OwnershipAnalysis {
12
13
14   ///////////////////////////////////////////
15   //
16   //  Public interface to discover possible
17   //  aliases in the program under analysis
18   //
19   ///////////////////////////////////////////
20
21   public HashSet<AllocationSite>
22   getFlaggedAllocationSitesReachableFromTask(TaskDescriptor td) {
23     checkAnalysisComplete();
24     return getFlaggedAllocationSitesReachableFromTaskPRIVATE(td);
25   }
26
27   public AllocationSite getAllocationSiteFromFlatNew(FlatNew fn) {
28     checkAnalysisComplete();
29     return getAllocationSiteFromFlatNewPRIVATE(fn);
30   }
31
32   public AllocationSite getAllocationSiteFromHeapRegionNodeID(Integer id) {
33     checkAnalysisComplete();
34     return mapHrnIdToAllocationSite.get(id);
35   }
36
37   public Set<HeapRegionNode> createsPotentialAliases(Descriptor taskOrMethod,
38                                          int paramIndex1,
39                                          int paramIndex2) {
40     checkAnalysisComplete();
41     OwnershipGraph og = getGraphOfAllContextsFromDescriptor(taskOrMethod);
42     assert(og != null);
43     return og.hasPotentialAlias(paramIndex1, paramIndex2);
44   }
45
46   public Set<HeapRegionNode> createsPotentialAliases(Descriptor taskOrMethod,
47                                          int paramIndex,
48                                          AllocationSite alloc) {
49     checkAnalysisComplete();
50     OwnershipGraph og = getGraphOfAllContextsFromDescriptor(taskOrMethod);
51     assert(og != null);
52     return og.hasPotentialAlias(paramIndex, alloc);
53   }
54
55   public Set<HeapRegionNode> createsPotentialAliases(Descriptor taskOrMethod,
56                                          AllocationSite alloc,
57                                          int paramIndex) {
58     checkAnalysisComplete();
59     OwnershipGraph og = getGraphOfAllContextsFromDescriptor(taskOrMethod);
60     assert(og != null);
61     return og.hasPotentialAlias(paramIndex, alloc);
62   }
63
64   public Set<HeapRegionNode> createsPotentialAliases(Descriptor taskOrMethod,
65                                          AllocationSite alloc1,
66                                          AllocationSite alloc2) {
67     checkAnalysisComplete();
68     OwnershipGraph og = getGraphOfAllContextsFromDescriptor(taskOrMethod);
69     assert(og != null);
70     return og.hasPotentialAlias(alloc1, alloc2);
71   }
72
73
74   protected OwnershipGraph getGraphOfAllContextsFromDescriptor(Descriptor d) {
75     checkAnalysisComplete();
76
77     assert d != null;
78
79     OwnershipGraph og = new OwnershipGraph( allocationDepth, typeUtil );
80
81     assert mapDescriptorToAllMethodContexts.containsKey( d );
82     HashSet<MethodContext> contexts = mapDescriptorToAllMethodContexts.get( d );
83     Iterator<MethodContext> mcItr = contexts.iterator();
84     while( mcItr.hasNext() ) {
85       MethodContext mc = mcItr.next();
86
87       OwnershipGraph ogContext = mapMethodContextToCompleteOwnershipGraph.get(mc);
88       assert ogContext != null;
89
90       og.merge( ogContext );
91     }
92
93     return og;
94   }
95
96
97   public String prettyPrintNodeSet( Set<HeapRegionNode> s ) {    
98     checkAnalysisComplete();
99
100     String out = "{\n";
101
102     Iterator<HeapRegionNode> i = s.iterator();
103     while( i.hasNext() ) {
104       HeapRegionNode n = i.next();
105
106       AllocationSite as = n.getAllocationSite();
107       if( as == null ) {
108         out += "  "+n.toString()+",\n";
109       } else {
110         out += "  "+n.toString()+": "+as.toStringVerbose()+",\n";
111       }
112     }
113
114     out += "}\n";
115     return out;
116   }
117
118
119   // use the methods given above to check every possible alias
120   // between task parameters and flagged allocation sites reachable
121   // from the task
122   public void writeAllAliases(String outputFile, String timeReport) throws java.io.IOException {
123     checkAnalysisComplete();
124
125     BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile) );
126
127     bw.write("Conducting ownership analysis with allocation depth = "+allocationDepth+"\n");
128     bw.write(timeReport+"\n");
129
130     // look through every task for potential aliases
131     Iterator taskItr = state.getTaskSymbolTable().getDescriptorsIterator();
132     while( taskItr.hasNext() ) {
133       TaskDescriptor td = (TaskDescriptor) taskItr.next();
134
135       bw.write("\n---------"+td+"--------\n");
136
137       HashSet<AllocationSite> allocSites = getFlaggedAllocationSitesReachableFromTask(td);
138
139       Set<HeapRegionNode> common;
140
141       // for each task parameter, check for aliases with
142       // other task parameters and every allocation site
143       // reachable from this task
144       boolean foundSomeAlias = false;
145
146       FlatMethod fm = state.getMethodFlat(td);
147       for( int i = 0; i < fm.numParameters(); ++i ) {
148
149         // for the ith parameter check for aliases to all
150         // higher numbered parameters
151         for( int j = i + 1; j < fm.numParameters(); ++j ) {
152           common = createsPotentialAliases(td, i, j);
153           if( !common.isEmpty() ) {
154             foundSomeAlias = true;
155             bw.write("Potential alias between parameters "+i+" and "+j+".\n");
156             bw.write(prettyPrintNodeSet( common )+"\n" );
157           }
158         }
159
160         // for the ith parameter, check for aliases against
161         // the set of allocation sites reachable from this
162         // task context
163         Iterator allocItr = allocSites.iterator();
164         while( allocItr.hasNext() ) {
165           AllocationSite as = (AllocationSite) allocItr.next();
166           common = createsPotentialAliases(td, i, as);
167           if( !common.isEmpty() ) {
168             foundSomeAlias = true;
169             bw.write("Potential alias between parameter "+i+" and "+as.getFlatNew()+".\n");
170             bw.write(prettyPrintNodeSet( common )+"\n" );
171           }
172         }
173       }
174
175       // for each allocation site check for aliases with
176       // other allocation sites in the context of execution
177       // of this task
178       HashSet<AllocationSite> outerChecked = new HashSet<AllocationSite>();
179       Iterator allocItr1 = allocSites.iterator();
180       while( allocItr1.hasNext() ) {
181         AllocationSite as1 = (AllocationSite) allocItr1.next();
182
183         Iterator allocItr2 = allocSites.iterator();
184         while( allocItr2.hasNext() ) {
185           AllocationSite as2 = (AllocationSite) allocItr2.next();
186           
187           if( !outerChecked.contains(as2) ) {
188             common = createsPotentialAliases(td, as1, as2);
189
190             if( !common.isEmpty() ) {
191               foundSomeAlias = true;
192               bw.write("Potential alias between "+as1.getFlatNew()+" and "+as2.getFlatNew()+".\n");
193               bw.write(prettyPrintNodeSet( common )+"\n" );
194             }
195           }
196         }
197
198         outerChecked.add(as1);
199       }
200
201       if( !foundSomeAlias ) {
202         bw.write("No aliases between flagged objects in Task "+td+".\n");
203       }
204     }
205
206     bw.write( "\n"+computeAliasContextHistogram() );
207     bw.close();
208   }
209
210
211   // this version of writeAllAliases is for Java programs that have no tasks
212   public void writeAllAliasesJava(String outputFile, String timeReport) throws java.io.IOException {
213     checkAnalysisComplete();
214
215     assert !state.TASK;    
216
217     BufferedWriter bw = new BufferedWriter(new FileWriter(outputFile) );
218
219     bw.write("Conducting ownership analysis with allocation depth = "+allocationDepth+"\n");
220     bw.write(timeReport+"\n\n");
221
222     boolean foundSomeAlias = false;
223
224     Descriptor d = typeUtil.getMain();
225     HashSet<AllocationSite> allocSites = getFlaggedAllocationSites(d);
226
227     // for each allocation site check for aliases with
228     // other allocation sites in the context of execution
229     // of this task
230     HashSet<AllocationSite> outerChecked = new HashSet<AllocationSite>();
231     Iterator allocItr1 = allocSites.iterator();
232     while( allocItr1.hasNext() ) {
233       AllocationSite as1 = (AllocationSite) allocItr1.next();
234       
235       Iterator allocItr2 = allocSites.iterator();
236       while( allocItr2.hasNext() ) {
237         AllocationSite as2 = (AllocationSite) allocItr2.next();
238         
239         if( !outerChecked.contains(as2) ) {       
240           Set<HeapRegionNode> common = createsPotentialAliases(d, as1, as2);
241
242           if( !common.isEmpty() ) {
243             foundSomeAlias = true;
244             bw.write("Potential alias between "+as1.getDisjointId()+" and "+as2.getDisjointId()+".\n");
245             bw.write( prettyPrintNodeSet( common )+"\n" );
246           }
247         }
248       }
249       
250       outerChecked.add(as1);
251     }
252     
253     if( !foundSomeAlias ) {
254       bw.write("No aliases between flagged objects found.\n");
255     }
256
257     bw.write( "\n"+computeAliasContextHistogram() );
258     bw.close();
259   }
260   ///////////////////////////////////////////
261   //
262   // end public interface
263   //
264   ///////////////////////////////////////////
265
266   protected void checkAnalysisComplete() {
267     if( !analysisComplete ) {
268       throw new Error("Warning: public interface method called while analysis is running.");
269     }
270   }
271
272
273
274
275
276   // data from the compiler
277   public State     state;
278   public CallGraph callGraph;
279   public TypeUtil  typeUtil;
280   public int       allocationDepth;
281
282   // for public interface methods to warn that they
283   // are grabbing results during analysis
284   private boolean analysisComplete;
285
286   // used to identify HeapRegionNode objects
287   // A unique ID equates an object in one
288   // ownership graph with an object in another
289   // graph that logically represents the same
290   // heap region
291   // start at 10 and increment to leave some
292   // reserved IDs for special purposes
293   static private int uniqueIDcount = 10;
294
295   // Use these data structures to track progress of
296   // processing all methods in the program, and by methods
297   // TaskDescriptor and MethodDescriptor are combined
298   // together, with a common parent class Descriptor
299   private Hashtable<MethodContext, OwnershipGraph>           mapMethodContextToInitialParamAllocGraph;
300   public  Hashtable<MethodContext, OwnershipGraph>           mapMethodContextToCompleteOwnershipGraph;
301   private Hashtable<FlatNew,       AllocationSite>           mapFlatNewToAllocationSite;
302   private Hashtable<Descriptor,    HashSet<AllocationSite> > mapDescriptorToAllocationSiteSet;
303   private Hashtable<MethodContext, Integer>                  mapMethodContextToNumUpdates;
304   private Hashtable<Descriptor,    HashSet<MethodContext> >  mapDescriptorToAllMethodContexts;
305   private Hashtable<MethodContext, HashSet<MethodContext> >  mapMethodContextToDependentContexts;
306   private Hashtable<Integer,       AllocationSite>           mapHrnIdToAllocationSite;
307
308   // Use these data structures to track progress of one pass of
309   // processing the FlatNodes of a particular method
310   private HashSet  <FlatNode>                 flatNodesToVisit;
311   private Hashtable<FlatNode, OwnershipGraph> mapFlatNodeToOwnershipGraph;
312   private HashSet  <FlatReturnNode>           returnNodesToCombineForCompleteOwnershipGraph;
313
314   // descriptorsToAnalyze identifies the set of tasks and methods
315   // that are reachable from the program tasks, this set is initialized
316   // and then remains static
317   public HashSet<Descriptor> descriptorsToAnalyze;
318
319   // descriptorsToVisit is initialized to descriptorsToAnalyze and is
320   // reduced by visiting a descriptor during analysis.  When dependents
321   // must be scheduled, only those contained in descriptorsToAnalyze
322   // should be re-added to this queue
323   private PriorityQueue<MethodContextQWrapper> methodContextsToVisitQ;
324   private Set          <MethodContext>         methodContextsToVisitSet;
325   private Hashtable<Descriptor, Integer> mapDescriptorToPriority;
326
327
328   // special field descriptors for array elements
329   public static final String arrayElementFieldName = "___element_";
330   private static Hashtable<TypeDescriptor, FieldDescriptor> mapTypeToArrayField =
331     new Hashtable<TypeDescriptor, FieldDescriptor>();
332
333
334   // for controlling DOT file output
335   private boolean writeDOTs;
336   private boolean writeAllDOTs;
337
338
339
340   // this analysis generates an ownership graph for every task
341   // in the program
342   public OwnershipAnalysis(State state,
343                            TypeUtil tu,
344                            CallGraph callGraph,
345                            int allocationDepth,
346                            boolean writeDOTs,
347                            boolean writeAllDOTs,
348                            String aliasFile) throws java.io.IOException {
349
350     analysisComplete = false;
351
352     this.state           = state;
353     this.typeUtil        = tu;
354     this.callGraph       = callGraph;
355     this.allocationDepth = allocationDepth;
356     this.writeDOTs       = writeDOTs;
357     this.writeAllDOTs    = writeAllDOTs;
358
359     descriptorsToAnalyze = new HashSet<Descriptor>();
360
361     mapMethodContextToInitialParamAllocGraph =
362       new Hashtable<MethodContext, OwnershipGraph>();
363
364     mapMethodContextToCompleteOwnershipGraph =
365       new Hashtable<MethodContext, OwnershipGraph>();
366
367     mapFlatNewToAllocationSite =
368       new Hashtable<FlatNew, AllocationSite>();
369
370     mapDescriptorToAllocationSiteSet =
371       new Hashtable<Descriptor, HashSet<AllocationSite> >();
372
373     mapDescriptorToAllMethodContexts = 
374       new Hashtable<Descriptor, HashSet<MethodContext> >();
375
376     mapMethodContextToDependentContexts =
377       new Hashtable<MethodContext, HashSet<MethodContext> >();
378
379     mapDescriptorToPriority = 
380       new Hashtable<Descriptor, Integer>();
381
382     mapHrnIdToAllocationSite =
383       new Hashtable<Integer, AllocationSite>();
384
385
386     if( writeAllDOTs ) {
387       mapMethodContextToNumUpdates = new Hashtable<MethodContext, Integer>();
388     }
389
390
391     double timeStartAnalysis = (double) System.nanoTime();
392
393
394     if( state.TASK ) {
395       // initialize methods to visit as the set of all tasks in the
396       // program and then any method that could be called starting
397       // from those tasks
398       Iterator taskItr = state.getTaskSymbolTable().getDescriptorsIterator();
399       while( taskItr.hasNext() ) {
400         Descriptor d = (Descriptor) taskItr.next();
401         scheduleAllCallees(d);
402       }
403
404     } else {
405       // we are not in task mode, just normal Java, so start with
406       // the main method
407       Descriptor d = typeUtil.getMain();
408       scheduleAllCallees(d);
409     }
410
411
412     // before beginning analysis, initialize every scheduled method
413     // with an ownership graph that has populated parameter index tables
414     // by analyzing the first node which is always a FlatMethod node
415     Iterator<Descriptor> dItr = descriptorsToAnalyze.iterator();
416     while( dItr.hasNext() ) {
417       Descriptor d  = dItr.next();
418       OwnershipGraph og = new OwnershipGraph(allocationDepth, typeUtil);
419
420       FlatMethod fm;
421       if( d instanceof MethodDescriptor ) {
422         fm = state.getMethodFlat( (MethodDescriptor) d);
423       } else {
424         assert d instanceof TaskDescriptor;
425         fm = state.getMethodFlat( (TaskDescriptor) d);
426       }
427
428       MethodContext mc = new MethodContext( d );
429       assert !mapDescriptorToAllMethodContexts.containsKey( d );
430       HashSet<MethodContext> s = new HashSet<MethodContext>();
431       s.add( mc );
432       mapDescriptorToAllMethodContexts.put( d, s );
433
434       //System.out.println("Previsiting " + mc);
435
436       og = analyzeFlatNode(mc, fm, null, og);
437       setGraphForMethodContext(mc, og);
438     }
439
440     // as mentioned above, analyze methods one-by-one, possibly revisiting
441     // a method if the methods that it calls are updated
442     analyzeMethods();
443     analysisComplete = true;
444
445
446     double timeEndAnalysis = (double) System.nanoTime();
447     double dt = (timeEndAnalysis - timeStartAnalysis)/(Math.pow( 10.0, 9.0 ) );
448     String treport = String.format( "The reachability analysis took %.3f sec.", dt );
449     System.out.println( treport );
450
451     if( writeDOTs && !writeAllDOTs ) {
452       writeFinalContextGraphs();      
453     }  
454
455     if( aliasFile != null ) {
456       if( state.TASK ) {
457         writeAllAliases(aliasFile, treport);
458       } else {
459         writeAllAliasesJava(aliasFile, treport);
460       }
461     }
462   }
463
464   // called from the constructor to help initialize the set
465   // of methods that needs to be analyzed by ownership analysis
466   private void scheduleAllCallees(Descriptor d) {
467     if( descriptorsToAnalyze.contains(d) ) {
468       return;
469     }
470     descriptorsToAnalyze.add(d);
471
472     // start with all method calls to further schedule
473     Set moreMethodsToCheck = moreMethodsToCheck = callGraph.getMethodCalls(d);
474
475     if( d instanceof MethodDescriptor ) {
476       // see if this method has virtual dispatch
477       Set virtualMethods = callGraph.getMethods( (MethodDescriptor)d);
478       moreMethodsToCheck.addAll(virtualMethods);
479     }
480
481     // keep following any further methods identified in
482     // the call chain
483     Iterator methItr = moreMethodsToCheck.iterator();
484     while( methItr.hasNext() ) {
485       Descriptor m = (Descriptor) methItr.next();
486       scheduleAllCallees(m);
487     }
488   }
489
490
491   // manage the set of tasks and methods to be analyzed
492   // and be sure to reschedule tasks/methods when the methods
493   // they call are updated
494   private void analyzeMethods() throws java.io.IOException {  
495
496     // first gather all of the method contexts to analyze
497     HashSet<MethodContext> allContexts = new HashSet<MethodContext>();
498     Iterator<Descriptor> itrd2a = descriptorsToAnalyze.iterator();
499     while( itrd2a.hasNext() ) {
500       HashSet<MethodContext> mcs = mapDescriptorToAllMethodContexts.get( itrd2a.next() );
501       assert mcs != null;
502
503       Iterator<MethodContext> itrmc = mcs.iterator();
504       while( itrmc.hasNext() ) {
505         allContexts.add( itrmc.next() );
506       }
507     }
508
509     // topologically sort them according to the caller graph so leaf calls are
510     // ordered first; use that ordering to give method contexts priorities
511     LinkedList<MethodContext> sortedMethodContexts = topologicalSort( allContexts );   
512
513     methodContextsToVisitQ   = new PriorityQueue<MethodContextQWrapper>();
514     methodContextsToVisitSet = new HashSet<MethodContext>();
515
516     int p = 0;
517     Iterator<MethodContext> mcItr = sortedMethodContexts.iterator();
518     while( mcItr.hasNext() ) {
519       MethodContext mc = mcItr.next();
520       mapDescriptorToPriority.put( mc.getDescriptor(), new Integer( p ) );
521       methodContextsToVisitQ.add( new MethodContextQWrapper( p, mc ) );
522       methodContextsToVisitSet.add( mc );
523       ++p;
524     }
525
526     // analyze methods from the priority queue until it is empty
527     while( !methodContextsToVisitQ.isEmpty() ) {
528       MethodContext mc = methodContextsToVisitQ.poll().getMethodContext();
529       assert methodContextsToVisitSet.contains( mc );
530       methodContextsToVisitSet.remove( mc );
531
532       // because the task or method descriptor just extracted
533       // was in the "to visit" set it either hasn't been analyzed
534       // yet, or some method that it depends on has been
535       // updated.  Recompute a complete ownership graph for
536       // this task/method and compare it to any previous result.
537       // If there is a change detected, add any methods/tasks
538       // that depend on this one to the "to visit" set.
539
540       System.out.println("Analyzing " + mc);
541
542       Descriptor d = mc.getDescriptor();
543       FlatMethod fm;
544       if( d instanceof MethodDescriptor ) {
545         fm = state.getMethodFlat( (MethodDescriptor) d);
546       } else {
547         assert d instanceof TaskDescriptor;
548         fm = state.getMethodFlat( (TaskDescriptor) d);
549       }
550
551       OwnershipGraph og = analyzeFlatMethod(mc, fm);
552       OwnershipGraph ogPrev = mapMethodContextToCompleteOwnershipGraph.get(mc);
553       if( !og.equals(ogPrev) ) {
554         setGraphForMethodContext(mc, og);
555
556         Iterator<MethodContext> depsItr = iteratorDependents( mc );
557         while( depsItr.hasNext() ) {
558           MethodContext mcNext = depsItr.next();
559
560           if( !methodContextsToVisitSet.contains( mcNext ) ) {
561             methodContextsToVisitQ.add( new MethodContextQWrapper( mapDescriptorToPriority.get( mcNext.getDescriptor() ), 
562                                                                    mcNext ) );
563             methodContextsToVisitSet.add( mcNext );
564           }
565         }
566       }
567     }
568
569   }
570
571
572   // keep passing the Descriptor of the method along for debugging
573   // and dot file writing
574   private OwnershipGraph
575   analyzeFlatMethod(MethodContext mc,
576                     FlatMethod flatm) throws java.io.IOException {
577
578     // initialize flat nodes to visit as the flat method
579     // because it is the entry point
580
581     flatNodesToVisit = new HashSet<FlatNode>();
582     flatNodesToVisit.add(flatm);
583
584     // initilize the mapping of flat nodes in this flat method to
585     // ownership graph results to an empty mapping
586     mapFlatNodeToOwnershipGraph = new Hashtable<FlatNode, OwnershipGraph>();
587
588     // initialize the set of return nodes that will be combined as
589     // the final ownership graph result to return as an empty set
590     returnNodesToCombineForCompleteOwnershipGraph = new HashSet<FlatReturnNode>();
591
592
593     while( !flatNodesToVisit.isEmpty() ) {
594       FlatNode fn = (FlatNode) flatNodesToVisit.iterator().next();
595       flatNodesToVisit.remove(fn);
596
597       //System.out.println( "  "+fn );
598
599       // perform this node's contributions to the ownership
600       // graph on a new copy, then compare it to the old graph
601       // at this node to see if anything was updated.
602       OwnershipGraph og = new OwnershipGraph(allocationDepth, typeUtil);
603
604       // start by merging all node's parents' graphs
605       for( int i = 0; i < fn.numPrev(); ++i ) {
606         FlatNode pn = fn.getPrev(i);
607         if( mapFlatNodeToOwnershipGraph.containsKey(pn) ) {
608           OwnershipGraph ogParent = mapFlatNodeToOwnershipGraph.get(pn);
609           og.merge(ogParent);
610         }
611       }
612
613       // apply the analysis of the flat node to the
614       // ownership graph made from the merge of the
615       // parent graphs
616       og = analyzeFlatNode(mc,
617                            fn,
618                            returnNodesToCombineForCompleteOwnershipGraph,
619                            og);
620
621       
622
623      
624       if( takeDebugSnapshots && 
625           mc.getDescriptor().getSymbol().equals( mcDescSymbolDebug ) ) {
626         debugSnapshot(og,fn);
627       }
628      
629
630
631       // if the results of the new graph are different from
632       // the current graph at this node, replace the graph
633       // with the update and enqueue the children for
634       // processing
635       OwnershipGraph ogPrev = mapFlatNodeToOwnershipGraph.get(fn);
636       if( !og.equals(ogPrev) ) {
637         mapFlatNodeToOwnershipGraph.put(fn, og);
638
639         for( int i = 0; i < fn.numNext(); i++ ) {
640           FlatNode nn = fn.getNext(i);
641           flatNodesToVisit.add(nn);
642         }
643       }
644     }
645
646     // end by merging all return nodes into a complete
647     // ownership graph that represents all possible heap
648     // states after the flat method returns
649     OwnershipGraph completeGraph = new OwnershipGraph(allocationDepth, typeUtil);
650     Iterator retItr = returnNodesToCombineForCompleteOwnershipGraph.iterator();
651     while( retItr.hasNext() ) {
652       FlatReturnNode frn = (FlatReturnNode) retItr.next();
653       assert mapFlatNodeToOwnershipGraph.containsKey(frn);
654       OwnershipGraph ogr = mapFlatNodeToOwnershipGraph.get(frn);
655       completeGraph.merge(ogr);
656     }
657
658     return completeGraph;
659   }
660
661
662   private OwnershipGraph
663   analyzeFlatNode(MethodContext mc,
664                   FlatNode fn,
665                   HashSet<FlatReturnNode> setRetNodes,
666                   OwnershipGraph og) throws java.io.IOException {
667
668     TempDescriptor lhs;
669     TempDescriptor rhs;
670     FieldDescriptor fld;
671
672     // use node type to decide what alterations to make
673     // to the ownership graph
674     switch( fn.kind() ) {
675
676     case FKind.FlatMethod:
677       FlatMethod fm = (FlatMethod) fn;
678
679       // there should only be one FlatMethod node as the
680       // parent of all other FlatNode objects, so take
681       // the opportunity to construct the initial graph by
682       // adding parameters labels to new heap regions
683       // AND this should be done once globally so that the
684       // parameter IDs are consistent between analysis
685       // iterations, so if this step has been done already
686       // just merge in the cached version
687       OwnershipGraph ogInitParamAlloc = mapMethodContextToInitialParamAllocGraph.get(mc);
688       if( ogInitParamAlloc == null ) {
689
690         // if the method context has aliased parameters, make sure
691         // there is a blob region for all those param to reference
692         Set<Integer> aliasedParamIndices = mc.getAliasedParamIndices();
693
694         if( !aliasedParamIndices.isEmpty() ) {
695           og.makeAliasedParamHeapRegionNode();
696         }
697
698         // set up each parameter
699         for( int i = 0; i < fm.numParameters(); ++i ) {
700           TempDescriptor tdParam    = fm.getParameter( i );
701           TypeDescriptor typeParam  = tdParam.getType();
702           Integer        paramIndex = new Integer( i );
703
704           if( typeParam.isImmutable() && !typeParam.isArray() ) {
705             // don't bother with this primitive parameter, it
706             // cannot affect reachability
707             continue;
708           }
709
710           if( aliasedParamIndices.contains( paramIndex ) ) {
711             // use the alias blob but give parameters their
712             // own primary obj region
713             og.assignTempEqualToAliasedParam( tdParam,
714                                               paramIndex );         
715           } else {
716             // this parameter is not aliased to others, give it
717             // a fresh primary obj and secondary object
718             og.assignTempEqualToParamAlloc( tdParam,
719                                             mc.getDescriptor() instanceof TaskDescriptor,
720                                             paramIndex );
721           }
722         }
723         
724         // add additional edges for aliased regions if necessary
725         if( !aliasedParamIndices.isEmpty() ) {
726           og.addParam2ParamAliasEdges( fm, aliasedParamIndices );
727         }
728         
729         // clean up reachability on initial parameter shapes
730         og.globalSweep();
731
732         // this maps tokens to parameter indices and vice versa
733         // for when this method is a callee
734         og.prepareParamTokenMaps( fm );
735
736         // cache the graph
737         OwnershipGraph ogResult = new OwnershipGraph(allocationDepth, typeUtil);
738         ogResult.merge(og);
739         mapMethodContextToInitialParamAllocGraph.put(mc, ogResult);
740
741       } else {
742         // or just leverage the cached copy
743         og.merge(ogInitParamAlloc);
744       }
745       break;
746       
747     case FKind.FlatOpNode:
748       FlatOpNode fon = (FlatOpNode) fn;
749       if( fon.getOp().getOp() == Operation.ASSIGN ) {
750         lhs = fon.getDest();
751         rhs = fon.getLeft();
752         og.assignTempXEqualToTempY(lhs, rhs);
753       }
754       break;
755
756     case FKind.FlatCastNode:
757       FlatCastNode fcn = (FlatCastNode) fn;
758       lhs = fcn.getDst();
759       rhs = fcn.getSrc();
760
761       TypeDescriptor td = fcn.getType();
762       assert td != null;
763       
764       og.assignTypedTempXEqualToTempY(lhs, rhs, td);
765       break;
766
767     case FKind.FlatFieldNode:
768       FlatFieldNode ffn = (FlatFieldNode) fn;
769       lhs = ffn.getDst();
770       rhs = ffn.getSrc();
771       fld = ffn.getField();
772       if( !fld.getType().isImmutable() || fld.getType().isArray() ) {
773         og.assignTempXEqualToTempYFieldF(lhs, rhs, fld);
774       }
775       break;
776
777     case FKind.FlatSetFieldNode:
778       FlatSetFieldNode fsfn = (FlatSetFieldNode) fn;
779       lhs = fsfn.getDst();
780       fld = fsfn.getField();
781       rhs = fsfn.getSrc();
782       if( !fld.getType().isImmutable() || fld.getType().isArray() ) {
783         og.assignTempXFieldFEqualToTempY(lhs, fld, rhs);
784       }
785       break;
786
787     case FKind.FlatElementNode:
788       FlatElementNode fen = (FlatElementNode) fn;
789       lhs = fen.getDst();
790       rhs = fen.getSrc();
791       if( !lhs.getType().isImmutable() || lhs.getType().isArray() ) {
792
793         assert rhs.getType() != null;
794         assert rhs.getType().isArray();
795         
796         TypeDescriptor  tdElement = rhs.getType().dereference();
797         FieldDescriptor fdElement = getArrayField( tdElement );
798   
799         og.assignTempXEqualToTempYFieldF(lhs, rhs, fdElement);
800       }
801       break;
802
803     case FKind.FlatSetElementNode:
804       FlatSetElementNode fsen = (FlatSetElementNode) fn;
805       lhs = fsen.getDst();
806       rhs = fsen.getSrc();
807       if( !rhs.getType().isImmutable() || rhs.getType().isArray() ) {
808
809         assert lhs.getType() != null;
810         assert lhs.getType().isArray();
811         
812         TypeDescriptor  tdElement = lhs.getType().dereference();
813         FieldDescriptor fdElement = getArrayField( tdElement );
814
815         og.assignTempXFieldFEqualToTempY(lhs, fdElement, rhs);
816       }
817       break;
818
819     case FKind.FlatNew:
820       FlatNew fnn = (FlatNew) fn;
821       lhs = fnn.getDst();
822       if( !lhs.getType().isImmutable() || lhs.getType().isArray() ) {
823         AllocationSite as = getAllocationSiteFromFlatNewPRIVATE(fnn);
824         og.assignTempEqualToNewAlloc(lhs, as);
825       }
826       break;
827
828     case FKind.FlatCall:
829       FlatCall fc = (FlatCall) fn;
830       MethodDescriptor md = fc.getMethod();
831       FlatMethod flatm = state.getMethodFlat(md);
832       OwnershipGraph ogMergeOfAllPossibleCalleeResults = new OwnershipGraph(allocationDepth, typeUtil);
833
834       if( md.isStatic() ) {
835         // a static method is simply always the same, makes life easy
836         ogMergeOfAllPossibleCalleeResults = og;
837
838         Set<Integer> aliasedParamIndices = 
839           ogMergeOfAllPossibleCalleeResults.calculateAliasedParamSet(fc, md.isStatic(), flatm);
840
841         MethodContext mcNew = new MethodContext( md, aliasedParamIndices );
842         Set contexts = mapDescriptorToAllMethodContexts.get( md );
843         assert contexts != null;
844         contexts.add( mcNew );
845
846         addDependent( mc, mcNew );
847
848         OwnershipGraph onlyPossibleCallee = mapMethodContextToCompleteOwnershipGraph.get( mcNew );
849
850         if( onlyPossibleCallee == null ) {
851           // if this method context has never been analyzed just schedule it for analysis
852           // and skip over this call site for now
853           if( !methodContextsToVisitSet.contains( mcNew ) ) {
854             methodContextsToVisitQ.add( new MethodContextQWrapper( mapDescriptorToPriority.get( md ), 
855                                                                    mcNew ) );
856             methodContextsToVisitSet.add( mcNew );
857           }
858           
859         } else {
860           ogMergeOfAllPossibleCalleeResults.resolveMethodCall(fc, md.isStatic(), flatm, onlyPossibleCallee, mc, null);
861         }
862
863       } else {
864         // if the method descriptor is virtual, then there could be a
865         // set of possible methods that will actually be invoked, so
866         // find all of them and merge all of their results together
867         TypeDescriptor typeDesc = fc.getThis().getType();
868         Set possibleCallees = callGraph.getMethods(md, typeDesc);
869
870         Iterator i = possibleCallees.iterator();
871         while( i.hasNext() ) {
872           MethodDescriptor possibleMd = (MethodDescriptor) i.next();
873           FlatMethod pflatm = state.getMethodFlat(possibleMd);
874
875           // don't alter the working graph (og) until we compute a result for every
876           // possible callee, merge them all together, then set og to that
877           OwnershipGraph ogCopy = new OwnershipGraph(allocationDepth, typeUtil);
878           ogCopy.merge(og);
879
880           Set<Integer> aliasedParamIndices = 
881             ogCopy.calculateAliasedParamSet(fc, possibleMd.isStatic(), pflatm);
882
883           MethodContext mcNew = new MethodContext( possibleMd, aliasedParamIndices );
884           Set contexts = mapDescriptorToAllMethodContexts.get( md );
885           assert contexts != null;
886           contexts.add( mcNew );
887
888           addDependent( mc, mcNew );
889
890           OwnershipGraph ogPotentialCallee = mapMethodContextToCompleteOwnershipGraph.get( mcNew );
891
892           if( ogPotentialCallee == null ) {
893             // if this method context has never been analyzed just schedule it for analysis
894             // and skip over this call site for now
895             if( !methodContextsToVisitSet.contains( mcNew ) ) {
896               methodContextsToVisitQ.add( new MethodContextQWrapper( mapDescriptorToPriority.get( md ), 
897                                                                      mcNew ) );
898               methodContextsToVisitSet.add( mcNew );
899             }
900             
901           } else {
902             ogCopy.resolveMethodCall(fc, possibleMd.isStatic(), pflatm, ogPotentialCallee, mc, null);
903           }
904
905           ogMergeOfAllPossibleCalleeResults.merge(ogCopy);
906         }
907       }
908
909       og = ogMergeOfAllPossibleCalleeResults;
910       break;
911
912     case FKind.FlatReturnNode:
913       FlatReturnNode frn = (FlatReturnNode) fn;
914       rhs = frn.getReturnTemp();
915       if( rhs != null && !rhs.getType().isImmutable() ) {
916         og.assignReturnEqualToTemp(rhs);
917       }
918       setRetNodes.add(frn);
919       break;
920     }
921
922     return og;
923   }
924
925
926   // this method should generate integers strictly greater than zero!
927   // special "shadow" regions are made from a heap region by negating
928   // the ID
929   static public Integer generateUniqueHeapRegionNodeID() {
930     ++uniqueIDcount;
931     return new Integer(uniqueIDcount);
932   }
933
934
935   static public FieldDescriptor getArrayField( TypeDescriptor tdElement ) {
936     FieldDescriptor fdElement = mapTypeToArrayField.get( tdElement );
937     if( fdElement == null ) {
938       fdElement = new FieldDescriptor(new Modifiers(Modifiers.PUBLIC),
939                                       tdElement,
940                                       arrayElementFieldName,
941                                       null,
942                                       false);
943       mapTypeToArrayField.put( tdElement, fdElement );
944     }
945     return fdElement;
946   }
947
948
949   private void setGraphForMethodContext(MethodContext mc, OwnershipGraph og) {
950
951     mapMethodContextToCompleteOwnershipGraph.put(mc, og);
952
953     if( writeDOTs && writeAllDOTs ) {
954       if( !mapMethodContextToNumUpdates.containsKey(mc) ) {
955         mapMethodContextToNumUpdates.put(mc, new Integer(0) );
956       }
957       Integer n = mapMethodContextToNumUpdates.get(mc);
958       try {
959         og.writeGraph(mc, n, true, true, true, false, false);
960       } catch( IOException e ) {}
961       mapMethodContextToNumUpdates.put(mc, n + 1);
962     }
963   }
964
965
966   private void addDependent( MethodContext caller, MethodContext callee ) {
967     HashSet<MethodContext> deps = mapMethodContextToDependentContexts.get( callee );
968     if( deps == null ) {
969       deps = new HashSet<MethodContext>();
970     }
971     deps.add( caller );
972     mapMethodContextToDependentContexts.put( callee, deps );
973   }
974
975   private Iterator<MethodContext> iteratorDependents( MethodContext callee ) {
976     HashSet<MethodContext> deps = mapMethodContextToDependentContexts.get( callee );
977     if( deps == null ) {
978       deps = new HashSet<MethodContext>();
979       mapMethodContextToDependentContexts.put( callee, deps );
980     }
981     return deps.iterator();
982   }
983
984
985   private void writeFinalContextGraphs() {
986     // arguments to writeGraph are:
987     // boolean writeLabels,
988     // boolean labelSelect,
989     // boolean pruneGarbage,
990     // boolean writeReferencers
991     // boolean writeParamMappings
992
993     Set entrySet = mapMethodContextToCompleteOwnershipGraph.entrySet();
994     Iterator itr = entrySet.iterator();
995     while( itr.hasNext() ) {
996       Map.Entry      me = (Map.Entry)      itr.next();
997       MethodContext  mc = (MethodContext)  me.getKey();
998       OwnershipGraph og = (OwnershipGraph) me.getValue();
999
1000       try {
1001         og.writeGraph(mc, true, true, true, false, false);
1002       } catch( IOException e ) {}    
1003     }
1004   }
1005   
1006
1007   // return just the allocation site associated with one FlatNew node
1008   private AllocationSite getAllocationSiteFromFlatNewPRIVATE(FlatNew fn) {
1009
1010     if( !mapFlatNewToAllocationSite.containsKey(fn) ) {
1011       AllocationSite as = new AllocationSite(allocationDepth, fn, fn.getDisjointId());
1012
1013       // the newest nodes are single objects
1014       for( int i = 0; i < allocationDepth; ++i ) {
1015         Integer id = generateUniqueHeapRegionNodeID();
1016         as.setIthOldest(i, id);
1017         mapHrnIdToAllocationSite.put( id, as );
1018       }
1019
1020       // the oldest node is a summary node
1021       Integer idSummary = generateUniqueHeapRegionNodeID();
1022       as.setSummary(idSummary);
1023
1024       mapFlatNewToAllocationSite.put(fn, as);
1025     }
1026
1027     return mapFlatNewToAllocationSite.get(fn);
1028   }
1029
1030
1031   // return all allocation sites in the method (there is one allocation
1032   // site per FlatNew node in a method)
1033   private HashSet<AllocationSite> getAllocationSiteSet(Descriptor d) {
1034     if( !mapDescriptorToAllocationSiteSet.containsKey(d) ) {
1035       buildAllocationSiteSet(d);
1036     }
1037
1038     return mapDescriptorToAllocationSiteSet.get(d);
1039
1040   }
1041
1042   private void buildAllocationSiteSet(Descriptor d) {
1043     HashSet<AllocationSite> s = new HashSet<AllocationSite>();
1044
1045     FlatMethod fm;
1046     if( d instanceof MethodDescriptor ) {
1047       fm = state.getMethodFlat( (MethodDescriptor) d);
1048     } else {
1049       assert d instanceof TaskDescriptor;
1050       fm = state.getMethodFlat( (TaskDescriptor) d);
1051     }
1052
1053     // visit every node in this FlatMethod's IR graph
1054     // and make a set of the allocation sites from the
1055     // FlatNew node's visited
1056     HashSet<FlatNode> visited = new HashSet<FlatNode>();
1057     HashSet<FlatNode> toVisit = new HashSet<FlatNode>();
1058     toVisit.add(fm);
1059
1060     while( !toVisit.isEmpty() ) {
1061       FlatNode n = toVisit.iterator().next();
1062
1063       if( n instanceof FlatNew ) {
1064         s.add(getAllocationSiteFromFlatNewPRIVATE( (FlatNew) n) );
1065       }
1066
1067       toVisit.remove(n);
1068       visited.add(n);
1069
1070       for( int i = 0; i < n.numNext(); ++i ) {
1071         FlatNode child = n.getNext(i);
1072         if( !visited.contains(child) ) {
1073           toVisit.add(child);
1074         }
1075       }
1076     }
1077
1078     mapDescriptorToAllocationSiteSet.put(d, s);
1079   }
1080
1081
1082   private HashSet<AllocationSite> getFlaggedAllocationSites(Descriptor dIn) {
1083     
1084     HashSet<AllocationSite> out     = new HashSet<AllocationSite>();
1085     HashSet<Descriptor>     toVisit = new HashSet<Descriptor>();
1086     HashSet<Descriptor>     visited = new HashSet<Descriptor>();
1087
1088     toVisit.add(dIn);
1089
1090     while( !toVisit.isEmpty() ) {
1091       Descriptor d = toVisit.iterator().next();
1092       toVisit.remove(d);
1093       visited.add(d);
1094
1095       HashSet<AllocationSite> asSet = getAllocationSiteSet(d);
1096       Iterator asItr = asSet.iterator();
1097       while( asItr.hasNext() ) {
1098         AllocationSite as = (AllocationSite) asItr.next();
1099         if( as.getDisjointId() != null ) {
1100           out.add(as);
1101         }
1102       }
1103
1104       // enqueue callees of this method to be searched for
1105       // allocation sites also
1106       Set callees = callGraph.getCalleeSet(d);
1107       if( callees != null ) {
1108         Iterator methItr = callees.iterator();
1109         while( methItr.hasNext() ) {
1110           MethodDescriptor md = (MethodDescriptor) methItr.next();
1111
1112           if( !visited.contains(md) ) {
1113             toVisit.add(md);
1114           }
1115         }
1116       }
1117     }
1118     
1119     return out;
1120   }
1121
1122
1123   private HashSet<AllocationSite>
1124   getFlaggedAllocationSitesReachableFromTaskPRIVATE(TaskDescriptor td) {
1125
1126     HashSet<AllocationSite> asSetTotal = new HashSet<AllocationSite>();
1127     HashSet<Descriptor>     toVisit    = new HashSet<Descriptor>();
1128     HashSet<Descriptor>     visited    = new HashSet<Descriptor>();
1129
1130     toVisit.add(td);
1131
1132     // traverse this task and all methods reachable from this task
1133     while( !toVisit.isEmpty() ) {
1134       Descriptor d = toVisit.iterator().next();
1135       toVisit.remove(d);
1136       visited.add(d);
1137
1138       HashSet<AllocationSite> asSet = getAllocationSiteSet(d);
1139       Iterator asItr = asSet.iterator();
1140       while( asItr.hasNext() ) {
1141         AllocationSite as = (AllocationSite) asItr.next();
1142         TypeDescriptor typed = as.getType();
1143         if( typed != null ) {
1144           ClassDescriptor cd = typed.getClassDesc();
1145           if( cd != null && cd.hasFlags() ) {
1146             asSetTotal.add(as);
1147           }
1148         }
1149       }
1150
1151       // enqueue callees of this method to be searched for
1152       // allocation sites also
1153       Set callees = callGraph.getCalleeSet(d);
1154       if( callees != null ) {
1155         Iterator methItr = callees.iterator();
1156         while( methItr.hasNext() ) {
1157           MethodDescriptor md = (MethodDescriptor) methItr.next();
1158
1159           if( !visited.contains(md) ) {
1160             toVisit.add(md);
1161           }
1162         }
1163       }
1164     }
1165
1166
1167     return asSetTotal;
1168   }
1169
1170
1171   private LinkedList<MethodContext> topologicalSort( HashSet<MethodContext> set ) {
1172     HashSet   <MethodContext> discovered = new HashSet   <MethodContext>();
1173     LinkedList<MethodContext> sorted     = new LinkedList<MethodContext>();
1174   
1175     Iterator<MethodContext> itr = set.iterator();
1176     while( itr.hasNext() ) {
1177       MethodContext mc = itr.next();
1178           
1179       if( !discovered.contains( mc ) ) {
1180         dfsVisit( set, mc, sorted, discovered );
1181       }
1182     }
1183     
1184     return sorted;
1185   }
1186   
1187   private void dfsVisit( HashSet<MethodContext> set,
1188                          MethodContext mc,
1189                          LinkedList<MethodContext> sorted,
1190                          HashSet   <MethodContext> discovered ) {
1191     discovered.add( mc );
1192     
1193     Descriptor d = mc.getDescriptor();
1194     if( d instanceof MethodDescriptor ) {
1195       MethodDescriptor md = (MethodDescriptor) d;      
1196       Iterator itr = callGraph.getCallerSet( md ).iterator();
1197       while( itr.hasNext() ) {
1198         Descriptor dCaller = (Descriptor) itr.next();
1199         
1200         // only consider the callers in the original set to analyze
1201         Set<MethodContext> callerContexts = mapDescriptorToAllMethodContexts.get( dCaller );
1202         if( callerContexts == null )
1203           continue;     
1204         
1205         // since the analysis hasn't started, there should be exactly one
1206         // context if there are any at all
1207         assert callerContexts.size() == 1;      
1208         MethodContext mcCaller = callerContexts.iterator().next();
1209         assert set.contains( mcCaller );
1210
1211         if( !discovered.contains( mcCaller ) ) {
1212           dfsVisit( set, mcCaller, sorted, discovered );
1213         }
1214       }
1215     }
1216
1217     sorted.addFirst( mc );
1218   }
1219
1220
1221
1222   private String computeAliasContextHistogram() {
1223     
1224     Hashtable<Integer, Integer> mapNumContexts2NumDesc = 
1225       new Hashtable<Integer, Integer>();
1226   
1227     Iterator itr = mapDescriptorToAllMethodContexts.entrySet().iterator();
1228     while( itr.hasNext() ) {
1229       Map.Entry me = (Map.Entry) itr.next();
1230       HashSet<MethodContext> s = (HashSet<MethodContext>) me.getValue();
1231       
1232       Integer i = mapNumContexts2NumDesc.get( s.size() );
1233       if( i == null ) {
1234         i = new Integer( 0 );
1235       }
1236       mapNumContexts2NumDesc.put( s.size(), i + 1 );
1237     }   
1238
1239     String s = "";
1240     int total = 0;
1241
1242     itr = mapNumContexts2NumDesc.entrySet().iterator();
1243     while( itr.hasNext() ) {
1244       Map.Entry me = (Map.Entry) itr.next();
1245       Integer c0 = (Integer) me.getKey();
1246       Integer d0 = (Integer) me.getValue();
1247       total += d0;
1248       s += String.format( "%4d methods had %4d unique alias contexts.\n", d0, c0 );
1249     }
1250
1251     s += String.format( "\n%4d total methods analayzed.\n", total );
1252
1253     return s;
1254   }
1255
1256
1257
1258   // insert a call to debugSnapshot() somewhere in the analysis 
1259   // to get successive captures of the analysis state
1260   boolean takeDebugSnapshots = false;
1261   String mcDescSymbolDebug = "addFirst";
1262   boolean stopAfterCapture = true;
1263
1264   // increments every visit to debugSnapshot, don't fiddle with it
1265   int debugCounter = 0;
1266
1267   // the value of debugCounter to start reporting the debugCounter
1268   // to the screen to let user know what debug iteration we're at
1269   int numStartCountReport = 0;
1270
1271   // the frequency of debugCounter values to print out, 0 no report
1272   int freqCountReport = 0;
1273
1274   // the debugCounter value at which to start taking snapshots
1275   int iterStartCapture = 0;
1276
1277   // the number of snapshots to take
1278   int numIterToCapture = 40;
1279
1280   void debugSnapshot(OwnershipGraph og, FlatNode fn) {
1281     if( debugCounter > iterStartCapture + numIterToCapture ) {
1282       return;
1283     }
1284
1285     ++debugCounter;
1286     if( debugCounter > numStartCountReport &&
1287         freqCountReport > 0 &&
1288         debugCounter % freqCountReport == 0 ) {
1289       System.out.println("    @@@ debug counter = "+debugCounter);
1290     }
1291     if( debugCounter > iterStartCapture ) {
1292       System.out.println("    @@@ capturing debug "+(debugCounter-iterStartCapture)+" @@@");
1293       String graphName = String.format("snap%04d",debugCounter-iterStartCapture);
1294       if( fn != null ) {
1295         graphName = graphName+fn;
1296       }
1297       try {
1298         // arguments to writeGraph are:
1299         // boolean writeLabels,
1300         // boolean labelSelect,
1301         // boolean pruneGarbage,
1302         // boolean writeReferencers
1303         // boolean writeParamMappings
1304
1305         //og.writeGraph(graphName, true, true, true, false, false);
1306         og.writeGraph(graphName, true, true, true, false, false);
1307       } catch( Exception e ) {
1308         System.out.println("Error writing debug capture.");
1309         System.exit(0);
1310       }
1311     }
1312
1313     if( debugCounter == iterStartCapture + numIterToCapture && stopAfterCapture ) {
1314       System.out.println("Stopping analysis after debug captures.");
1315       System.exit(0);
1316     }
1317   }
1318 }