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 */
013package org.jikesrvm.adaptive.database.callgraph;
014
015import java.io.BufferedWriter;
016import java.io.FileOutputStream;
017import java.io.IOException;
018import java.io.OutputStreamWriter;
019import java.util.Comparator;
020import java.util.HashMap;
021import java.util.TreeSet;
022import org.jikesrvm.VM;
023import org.jikesrvm.adaptive.controller.Controller;
024import org.jikesrvm.adaptive.measurements.Decayable;
025import org.jikesrvm.adaptive.measurements.Reportable;
026import org.jikesrvm.adaptive.util.UnResolvedCallSite;
027import org.jikesrvm.adaptive.util.UnResolvedWeightedCallTargets;
028import org.jikesrvm.classloader.RVMMethod;
029import org.jikesrvm.compilers.common.CodeArray;
030import org.jikesrvm.classloader.MethodReference;
031
032/**
033 * A partial call graph (PCG) is a partial mapping from callsites
034 * to weighted targets.
035 */
036public 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() {
080    return totalEdgeWeights;
081  }
082
083  /**
084   * Visit the WeightedCallTargets for every call site send them the
085   * decay message.
086   */
087  @Override
088  public synchronized void decay() {
089    double rate = Controller.options.DCG_DECAY_RATE;
090    // if we are dumping dynamic call graph, don't decay the graph
091    if (Controller.options.DYNAMIC_CALL_FILE_OUTPUT != null) return;
092
093    for (WeightedCallTargets ct : callGraph.values()) {
094      ct.decay(rate);
095    }
096    totalEdgeWeights /= rate;
097  }
098
099  /**
100   * @param caller caller method
101   * @param bcIndex bytecode index in caller method
102   * @return the WeightedCallTargets currently associated with the
103   *         given caller bytecodeIndex pair.
104   */
105  public WeightedCallTargets getCallTargets(RVMMethod caller, int bcIndex) {
106    MethodReference callerRef = caller.getMemberRef().asMethodReference();
107    UnResolvedWeightedCallTargets unresolvedTargets =
108        unresolvedCallGraph.get(new UnResolvedCallSite(callerRef, bcIndex));
109    if (unresolvedTargets != null) {
110      final RVMMethod fCaller = caller;
111      final int fBcIndex = bcIndex;
112      final PartialCallGraph pg = this;
113      unresolvedTargets.visitTargets(new UnResolvedWeightedCallTargets.Visitor() {
114        @Override
115        public void visit(MethodReference calleeRef, double weight) {
116          RVMMethod callee = calleeRef.getResolvedMember();
117          if (callee != null) {
118            pg.incrementEdge(fCaller, fBcIndex, callee, (float) weight);
119          }
120        }
121      });
122    }
123    return getCallTargets(new CallSite(caller, bcIndex));
124  }
125
126  /**
127   * @param callSite the callsite to look for
128   * @return the WeightedCallTargets currently associated with callSite.
129   */
130  public synchronized WeightedCallTargets getCallTargets(CallSite callSite) {
131    return callGraph.get(callSite);
132  }
133
134  /**
135   * Increment the edge represented by the input parameters,
136   * creating it if it is not already in the call graph.
137   *
138   * @param caller   method making the call
139   * @param bcIndex  call site, if -1 then no call site is specified.
140   * @param callee   method called
141   */
142  public synchronized void incrementEdge(RVMMethod caller, int bcIndex, RVMMethod callee) {
143    augmentEdge(caller, bcIndex, callee, 1);
144  }
145
146  /**
147   * Increment the edge represented by the input parameters,
148   * creating it if it is not already in the call graph.
149   *
150   * @param caller   method making the call
151   * @param bcIndex  call site, if -1 then no call site is specified.
152   * @param callee   method called
153   * @param weight   the frequency of this calling edge
154   */
155  public synchronized void incrementEdge(RVMMethod caller, int bcIndex, RVMMethod callee, float weight) {
156    augmentEdge(caller, bcIndex, callee, weight);
157  }
158
159  /**
160   * For the calling edge we read from a profile, we may not have
161   * the methods loaded in yet. Therefore, we will record the method
162   * reference information first, the next time we resolved the method,
163   * we will promote it into the regular call graph.
164   * Increment the edge represented by the input parameters,
165   * creating it if it is not already in the call graph.
166   *
167   * @param callerRef   method making the call
168   * @param bcIndex     call site, if -1 then no call site is specified.
169   * @param calleeRef   method called
170   * @param weight      the frequency of this calling edge
171   */
172  public synchronized void incrementUnResolvedEdge(MethodReference callerRef, int bcIndex,
173                                                   MethodReference calleeRef, float weight) {
174    UnResolvedCallSite callSite = new UnResolvedCallSite(callerRef, bcIndex);
175    UnResolvedWeightedCallTargets targets = unresolvedCallGraph.get(callSite);
176    if (targets == null) {
177      targets = UnResolvedWeightedCallTargets.create(calleeRef, weight);
178      unresolvedCallGraph.put(callSite, targets);
179    } else {
180      UnResolvedWeightedCallTargets orig = targets;
181      targets = targets.augmentCount(calleeRef, weight);
182      if (orig != targets) {
183        unresolvedCallGraph.put(callSite, targets);
184      }
185    }
186  }
187
188  /**
189   * Increment the edge represented by the input parameters,
190   * creating it if it is not already in the call graph.
191   *
192   * @param caller   method making the call
193   * @param bcIndex  call site, if -1 then no call site is specified.
194   * @param callee   method called
195   * @param weight   the frequency of this calling edge
196   */
197  private void augmentEdge(RVMMethod caller, int bcIndex, RVMMethod callee, double weight) {
198    CallSite callSite = new CallSite(caller, bcIndex);
199    WeightedCallTargets targets = callGraph.get(callSite);
200    if (targets == null) {
201      targets = WeightedCallTargets.create(callee, weight);
202      callGraph.put(callSite, targets);
203    } else {
204      WeightedCallTargets orig = targets;
205      targets = targets.augmentCount(callee, weight);
206      if (orig != targets) {
207        callGraph.put(callSite, targets);
208      }
209    }
210    totalEdgeWeights += weight;
211  }
212
213  /**
214   * Dump out set of edges in sorted order.
215   */
216  @Override
217  public synchronized void report() {
218    System.out.println("Partial Call Graph");
219    System.out.println("  Number of callsites " + callGraph.size() + ", total weight: " + totalEdgeWeights);
220    System.out.println();
221
222    TreeSet<CallSite> tmp = new TreeSet<CallSite>(new OrderByTotalWeight());
223    tmp.addAll(callGraph.keySet());
224
225    for (final CallSite cs : tmp) {
226      WeightedCallTargets ct = callGraph.get(cs);
227      ct.visitTargets(new WeightedCallTargets.Visitor() {
228        @Override
229        public void visit(RVMMethod callee, double weight) {
230          System.out.println(weight + " <" + cs.getMethod() + ", " + cs.getBytecodeIndex() + ", " + callee + ">");
231        }
232      });
233      System.out.println();
234    }
235  }
236
237  /**
238   * Dump all profile data to the given file
239   */
240  public synchronized void dumpGraph() {
241    dumpGraph(Controller.options.DYNAMIC_CALL_FILE_OUTPUT);
242  }
243
244  /**
245   * Dump all profile data to the given file
246   * @param fn output file name
247   */
248  public synchronized void dumpGraph(String fn) {
249    final BufferedWriter f;
250    try {
251      f = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fn), "ISO-8859-1"));
252    } catch (IOException e) {
253      VM.sysWrite("\n\nPartialCallGraph.dumpGraph: Error opening output file!!\n\n");
254      return;
255    }
256    TreeSet<CallSite> tmp = new TreeSet<CallSite>(new OrderByTotalWeight());
257    tmp.addAll(callGraph.keySet());
258
259    for (final CallSite cs : tmp) {
260      WeightedCallTargets ct = callGraph.get(cs);
261      ct.visitTargets(new WeightedCallTargets.Visitor() {
262        @Override
263        public void visit(RVMMethod callee, double weight) {
264          CodeArray callerArray = cs.getMethod().getCurrentEntryCodeArray();
265          CodeArray calleeArray = callee.getCurrentEntryCodeArray();
266          try {
267            f.write("CallSite " +
268                    cs.getMethod().getMemberRef() +
269                    " " +
270                    callerArray.length() +
271                    " " +
272                    +cs.getBytecodeIndex() +
273                    " " +
274                    callee.getMemberRef() +
275                    " " +
276                    +calleeArray.length() +
277                    " weight: " +
278                    weight +
279                    "\n");
280            f.flush();
281          } catch (IOException exc) {
282            System.err.println("I/O error writing to dynamic call graph profile.");
283          }
284        }
285      });
286    }
287  }
288
289  /**
290   * Used to compare two call sites by total weight.
291   */
292  private final class OrderByTotalWeight implements Comparator<CallSite> {
293    @Override
294    public int compare(CallSite o1, CallSite o2) {
295      if (o1.equals(o2)) return 0;
296      double w1 = callGraph.get(o1).totalWeight();
297      double w2 = callGraph.get(o2).totalWeight();
298      if (w1 < w2) {
299        return 1;
300      }
301      if (w1 > w2) {
302        return -1;
303      }
304      // equal weights; sort lexicographically
305      return o1.toString().compareTo(o2.toString());
306    }
307  }
308
309}