001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.adaptive.database.callgraph;
014    
015    import java.io.BufferedWriter;
016    import java.io.FileOutputStream;
017    import java.io.IOException;
018    import java.io.OutputStreamWriter;
019    import java.util.Comparator;
020    import java.util.HashMap;
021    import java.util.TreeSet;
022    import org.jikesrvm.ArchitectureSpecific.CodeArray;
023    import org.jikesrvm.VM;
024    import org.jikesrvm.adaptive.controller.Controller;
025    import org.jikesrvm.adaptive.measurements.Decayable;
026    import org.jikesrvm.adaptive.measurements.Reportable;
027    import org.jikesrvm.adaptive.util.UnResolvedCallSite;
028    import org.jikesrvm.adaptive.util.UnResolvedWeightedCallTargets;
029    import org.jikesrvm.classloader.RVMMethod;
030    import org.jikesrvm.classloader.MethodReference;
031    
032    /**
033     * A partial call graph (PCG) is a partial mapping from callsites
034     * to weighted targets.
035     */
036    public final class PartialCallGraph implements Decayable, Reportable {
037    
038      /**
039       * The dynamic call graph, which is a mapping from
040       * CallSites to WeightedCallTargets.
041       */
042      private final HashMap<CallSite, WeightedCallTargets> callGraph =
043          new HashMap<CallSite, WeightedCallTargets>();
044      private final HashMap<UnResolvedCallSite, UnResolvedWeightedCallTargets> unresolvedCallGraph =
045          new HashMap<UnResolvedCallSite, UnResolvedWeightedCallTargets>();
046    
047      /**
048       * sum of all edge weights in the call graph
049       */
050      private double totalEdgeWeights;
051    
052      /**
053       * Initial seed weight; saved for use in the reset method
054       */
055      private final double seedWeight;
056    
057      /**
058       * Create a partial call graph.
059       * @param initialWeight an initial value for totalEdgeWeights.
060       *        Used by AOS to cause inlining based in the dynamic call graph
061       *        to initially be conservative until sufficient samples have
062       *        accumulated that there is more confidence in the accuracy
063       *        of the call graph.
064       */
065      public PartialCallGraph(double initialWeight) {
066        seedWeight = initialWeight; // save for rest function
067        totalEdgeWeights = initialWeight;
068      }
069    
070      @Override
071      public synchronized void reset() {
072        callGraph.clear();
073        totalEdgeWeights = seedWeight;
074      }
075    
076      /**
077       * @return sum of all edge weights in the partial call graph
078       */
079      public double getTotalEdgeWeights() { return totalEdgeWeights; }
080    
081      /**
082       * Visit the WeightedCallTargets for every call site send them the
083       * decay message.
084       */
085      @Override
086      public synchronized void decay() {
087        double rate = Controller.options.DCG_DECAY_RATE;
088        // if we are dumping dynamic call graph, don't decay the graph
089        if (Controller.options.DYNAMIC_CALL_FILE_OUTPUT != null) return;
090    
091        for (WeightedCallTargets ct : callGraph.values()) {
092          ct.decay(rate);
093        }
094        totalEdgeWeights /= rate;
095      }
096    
097      /**
098       * @param caller caller method
099       * @param bcIndex bytecode index in caller method
100       * @return the WeightedCallTargets currently associated with the
101       *         given caller bytecodeIndex pair.
102       */
103      public WeightedCallTargets getCallTargets(RVMMethod caller, int bcIndex) {
104        MethodReference callerRef = caller.getMemberRef().asMethodReference();
105        UnResolvedWeightedCallTargets unresolvedTargets =
106            unresolvedCallGraph.get(new UnResolvedCallSite(callerRef, bcIndex));
107        if (unresolvedTargets != null) {
108          final RVMMethod fCaller = caller;
109          final int fBcIndex = bcIndex;
110          final PartialCallGraph pg = this;
111          unresolvedTargets.visitTargets(new UnResolvedWeightedCallTargets.Visitor() {
112            @Override
113            public void visit(MethodReference calleeRef, double weight) {
114              RVMMethod callee = calleeRef.getResolvedMember();
115              if (callee != null) {
116                pg.incrementEdge(fCaller, fBcIndex, callee, (float) weight);
117              }
118            }
119          });
120        }
121        return getCallTargets(new CallSite(caller, bcIndex));
122      }
123    
124      /**
125       * @param callSite the callsite to look for
126       * @return the WeightedCallTargets currently associated with callSite.
127       */
128      public synchronized WeightedCallTargets getCallTargets(CallSite callSite) {
129        return callGraph.get(callSite);
130      }
131    
132      /**
133       * Increment the edge represented by the input parameters,
134       * creating it if it is not already in the call graph.
135       *
136       * @param caller   method making the call
137       * @param bcIndex  call site, if -1 then no call site is specified.
138       * @param callee   method called
139       */
140      public synchronized void incrementEdge(RVMMethod caller, int bcIndex, RVMMethod callee) {
141        augmentEdge(caller, bcIndex, callee, 1);
142      }
143    
144      /**
145       * Increment the edge represented by the input parameters,
146       * creating it if it is not already in the call graph.
147       *
148       * @param caller   method making the call
149       * @param bcIndex  call site, if -1 then no call site is specified.
150       * @param callee   method called
151       * @param weight   the frequency of this calling edge
152       */
153      public synchronized void incrementEdge(RVMMethod caller, int bcIndex, RVMMethod callee, float weight) {
154        augmentEdge(caller, bcIndex, callee, weight);
155      }
156    
157      /**
158       * For the calling edge we read from a profile, we may not have
159       * the methods loaded in yet. Therefore, we will record the method
160       * reference information first, the next time we resolved the method,
161       * we will promote it into the regular call graph.
162       * Increment the edge represented by the input parameters,
163       * creating it if it is not already in the call graph.
164       *
165       * @param callerRef   method making the call
166       * @param bcIndex     call site, if -1 then no call site is specified.
167       * @param calleeRef   method called
168       * @param weight      the frequency of this calling edge
169       */
170      public synchronized void incrementUnResolvedEdge(MethodReference callerRef, int bcIndex,
171                                                       MethodReference calleeRef, float weight) {
172        UnResolvedCallSite callSite = new UnResolvedCallSite(callerRef, bcIndex);
173        UnResolvedWeightedCallTargets targets = unresolvedCallGraph.get(callSite);
174        if (targets == null) {
175          targets = UnResolvedWeightedCallTargets.create(calleeRef, weight);
176          unresolvedCallGraph.put(callSite, targets);
177        } else {
178          UnResolvedWeightedCallTargets orig = targets;
179          targets = targets.augmentCount(calleeRef, weight);
180          if (orig != targets) {
181            unresolvedCallGraph.put(callSite, targets);
182          }
183        }
184      }
185    
186      /**
187       * Increment the edge represented by the input parameters,
188       * creating it if it is not already in the call graph.
189       *
190       * @param caller   method making the call
191       * @param bcIndex  call site, if -1 then no call site is specified.
192       * @param callee   method called
193       * @param weight   the frequency of this calling edge
194       */
195      private void augmentEdge(RVMMethod caller, int bcIndex, RVMMethod callee, double weight) {
196        CallSite callSite = new CallSite(caller, bcIndex);
197        WeightedCallTargets targets = callGraph.get(callSite);
198        if (targets == null) {
199          targets = WeightedCallTargets.create(callee, weight);
200          callGraph.put(callSite, targets);
201        } else {
202          WeightedCallTargets orig = targets;
203          targets = targets.augmentCount(callee, weight);
204          if (orig != targets) {
205            callGraph.put(callSite, targets);
206          }
207        }
208        totalEdgeWeights += weight;
209      }
210    
211      /**
212       * Dump out set of edges in sorted order.
213       */
214      @Override
215      public synchronized void report() {
216        System.out.println("Partial Call Graph");
217        System.out.println("  Number of callsites " + callGraph.size() + ", total weight: " + totalEdgeWeights);
218        System.out.println();
219    
220        TreeSet<CallSite> tmp = new TreeSet<CallSite>(new OrderByTotalWeight());
221        tmp.addAll(callGraph.keySet());
222    
223        for (final CallSite cs : tmp) {
224          WeightedCallTargets ct = callGraph.get(cs);
225          ct.visitTargets(new WeightedCallTargets.Visitor() {
226            @Override
227            public void visit(RVMMethod callee, double weight) {
228              System.out.println(weight + " <" + cs.getMethod() + ", " + cs.getBytecodeIndex() + ", " + callee + ">");
229            }
230          });
231          System.out.println();
232        }
233      }
234    
235      /**
236       * Dump all profile data to the given file
237       */
238      public synchronized void dumpGraph() {
239        dumpGraph(Controller.options.DYNAMIC_CALL_FILE_OUTPUT);
240      }
241    
242      /**
243       * Dump all profile data to the given file
244       * @param fn output file name
245       */
246      public synchronized void dumpGraph(String fn) {
247        final BufferedWriter f;
248        try {
249          f = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fn), "ISO-8859-1"));
250        } catch (IOException e) {
251          VM.sysWrite("\n\nPartialCallGraph.dumpGraph: Error opening output file!!\n\n");
252          return;
253        }
254        TreeSet<CallSite> tmp = new TreeSet<CallSite>(new OrderByTotalWeight());
255        tmp.addAll(callGraph.keySet());
256    
257        for (final CallSite cs : tmp) {
258          WeightedCallTargets ct = callGraph.get(cs);
259          ct.visitTargets(new WeightedCallTargets.Visitor() {
260            @Override
261            public void visit(RVMMethod callee, double weight) {
262              CodeArray callerArray = cs.getMethod().getCurrentEntryCodeArray();
263              CodeArray calleeArray = callee.getCurrentEntryCodeArray();
264              try {
265                f.write("CallSite " +
266                        cs.getMethod().getMemberRef() +
267                        " " +
268                        callerArray.length() +
269                        " " +
270                        +cs.getBytecodeIndex() +
271                        " " +
272                        callee.getMemberRef() +
273                        " " +
274                        +calleeArray.length() +
275                        " weight: " +
276                        weight +
277                        "\n");
278                f.flush();
279              } catch (IOException exc) {
280                System.err.println("I/O error writing to dynamic call graph profile.");
281              }
282            }
283          });
284        }
285      }
286    
287      /**
288       * Used to compare two call sites by total weight.
289       */
290      private final class OrderByTotalWeight implements Comparator<CallSite> {
291        @Override
292        public int compare(CallSite o1, CallSite o2) {
293          if (o1.equals(o2)) return 0;
294          double w1 = callGraph.get(o1).totalWeight();
295          double w2 = callGraph.get(o2).totalWeight();
296          if (w1 < w2) { return 1; }
297          if (w1 > w2) { return -1; }
298          // equal weights; sort lexicographically
299          return o1.toString().compareTo(o2.toString());
300        }
301      }
302    
303    }