/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.adaptive.database.callgraph;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.TreeSet;
import org.jikesrvm.ArchitectureSpecific.VM_CodeArray;
import org.jikesrvm.VM;
import org.jikesrvm.adaptive.controller.VM_Controller;
import org.jikesrvm.adaptive.measurements.VM_Decayable;
import org.jikesrvm.adaptive.measurements.VM_Reportable;
import org.jikesrvm.adaptive.util.VM_UnResolvedCallSite;
import org.jikesrvm.adaptive.util.VM_UnResolvedWeightedCallTargets;
import org.jikesrvm.classloader.VM_Method;
import org.jikesrvm.classloader.VM_MethodReference;
// TODO - deal with subarch
/**
* A partial call graph (PCG) is a partial mapping from callsites
* to weighted targets.
*/
public final class VM_PartialCallGraph implements VM_Decayable, VM_Reportable {
/**
* The dynamic call graph, which is a mapping from
* VM_CallSites to VM_WeightedCallTargets.
*/
private final HashMap<VM_CallSite, VM_WeightedCallTargets> callGraph =
new HashMap<VM_CallSite, VM_WeightedCallTargets>();
private final HashMap<VM_UnResolvedCallSite, VM_UnResolvedWeightedCallTargets> unresolvedCallGraph =
new HashMap<VM_UnResolvedCallSite, VM_UnResolvedWeightedCallTargets>();
/**
* sum of all edge weights in the call graph
*/
private double totalEdgeWeights;
/**
* Initial seed weight; saved for use in the reset method
*/
private final double seedWeight;
/**
* Create a partial call graph.
* @param initialWeight an initial value for totalEdgeWeights.
* Used by AOS to cause inlining based in the dynamic call graph
* to initially be conservative until sufficient samples have
* accumulated that there is more confidence in the accuracy
* of the call graph.
*/
public VM_PartialCallGraph(double initialWeight) {
seedWeight = initialWeight; // save for rest function
totalEdgeWeights = initialWeight;
}
/**
* Reset data
*/
public synchronized void reset() {
callGraph.clear();
totalEdgeWeights = seedWeight;
}
/**
* @return sum of all edge weights in the partial call graph
*/
public double getTotalEdgeWeights() { return totalEdgeWeights; }
/**
* Visit the WeightedCallTargets for every call site send them the
* decay message.
*/
public synchronized void decay() {
double rate = VM_Controller.options.DCG_DECAY_RATE;
// if we are dumping dynamic call graph, don't decay the graph
if (VM_Controller.options.DYNAMIC_CALL_FILE_OUTPUT != null) return;
for (VM_WeightedCallTargets ct : callGraph.values()) {
ct.decay(rate);
}
totalEdgeWeights /= rate;
}
/**
* @param caller caller method
* @param bcIndex bytecode index in caller method
* @return the VM_WeightedCallTargets currently associated with the
* given caller bytecodeIndex pair.
*/
public VM_WeightedCallTargets getCallTargets(VM_Method caller, int bcIndex) {
VM_MethodReference callerRef = caller.getMemberRef().asMethodReference();
VM_UnResolvedWeightedCallTargets unresolvedTargets =
unresolvedCallGraph.get(new VM_UnResolvedCallSite(callerRef, bcIndex));
if (unresolvedTargets != null) {
final VM_Method fCaller = caller;
final int fBcIndex = bcIndex;
final VM_PartialCallGraph pg = this;
unresolvedTargets.visitTargets(new VM_UnResolvedWeightedCallTargets.Visitor() {
public void visit(VM_MethodReference calleeRef, double weight) {
VM_Method callee = calleeRef.getResolvedMember();
if (callee != null) {
pg.incrementEdge(fCaller, fBcIndex, callee, (float) weight);
}
}
});
}
return getCallTargets(new VM_CallSite(caller, bcIndex));
}
/**
* @param callSite the callsite to look for
* @return the VM_WeightedCallTargets currently associated with callSite.
*/
public synchronized VM_WeightedCallTargets getCallTargets(VM_CallSite callSite) {
return callGraph.get(callSite);
}
/**
* Increment the edge represented by the input parameters,
* creating it if it is not already in the call graph.
*
* @param caller method making the call
* @param bcIndex call site, if -1 then no call site is specified.
* @param callee method called
*/
public synchronized void incrementEdge(VM_Method caller, int bcIndex, VM_Method callee) {
augmentEdge(caller, bcIndex, callee, 1);
}
/**
* Increment the edge represented by the input parameters,
* creating it if it is not already in the call graph.
*
* @param caller method making the call
* @param bcIndex call site, if -1 then no call site is specified.
* @param callee method called
* @param weight the frequency of this calling edge
*/
public synchronized void incrementEdge(VM_Method caller, int bcIndex, VM_Method callee, float weight) {
augmentEdge(caller, bcIndex, callee, (double) weight);
}
/**
* For the calling edge we read from a profile, we may not have
* the methods loaded in yet. Therefore, we will record the method
* reference infomation first, the next time we resolved the method,
* we will promote it into the regular call graph.
* Increment the edge represented by the input parameters,
* creating it if it is not already in the call graph.
*
* @param callerRef method making the call
* @param bcIndex call site, if -1 then no call site is specified.
* @param calleeRef method called
* @param weight the frequency of this calling edge
*/
public synchronized void incrementUnResolvedEdge(VM_MethodReference callerRef, int bcIndex,
VM_MethodReference calleeRef, float weight) {
VM_UnResolvedCallSite callSite = new VM_UnResolvedCallSite(callerRef, bcIndex);
VM_UnResolvedWeightedCallTargets targets = unresolvedCallGraph.get(callSite);
if (targets == null) {
targets = VM_UnResolvedWeightedCallTargets.create(calleeRef, weight);
unresolvedCallGraph.put(callSite, targets);
} else {
VM_UnResolvedWeightedCallTargets orig = targets;
targets = targets.augmentCount(calleeRef, weight);
if (orig != targets) {
unresolvedCallGraph.put(callSite, targets);
}
}
}
/**
* Increment the edge represented by the input parameters,
* creating it if it is not already in the call graph.
*
* @param caller method making the call
* @param bcIndex call site, if -1 then no call site is specified.
* @param callee method called
* @param weight the frequency of this calling edge
*/
private void augmentEdge(VM_Method caller, int bcIndex, VM_Method callee, double weight) {
VM_CallSite callSite = new VM_CallSite(caller, bcIndex);
VM_WeightedCallTargets targets = callGraph.get(callSite);
if (targets == null) {
targets = VM_WeightedCallTargets.create(callee, weight);
callGraph.put(callSite, targets);
} else {
VM_WeightedCallTargets orig = targets;
targets = targets.augmentCount(callee, weight);
if (orig != targets) {
callGraph.put(callSite, targets);
}
}
totalEdgeWeights += weight;
}
/**
* Dump out set of edges in sorted order.
*/
public synchronized void report() {
System.out.println("Partial Call Graph");
System.out.println(" Number of callsites " + callGraph.size() + ", total weight: " + totalEdgeWeights);
System.out.println();
TreeSet<VM_CallSite> tmp = new TreeSet<VM_CallSite>(new OrderByTotalWeight());
tmp.addAll(callGraph.keySet());
for (final VM_CallSite cs : tmp) {
VM_WeightedCallTargets ct = callGraph.get(cs);
ct.visitTargets(new VM_WeightedCallTargets.Visitor() {
public void visit(VM_Method callee, double weight) {
System.out.println(weight + " <" + cs.getMethod() + ", " + cs.getBytecodeIndex() + ", " + callee + ">");
}
});
System.out.println();
}
}
/**
* Dump all profile data to the given file
*/
public synchronized void dumpGraph() {
dumpGraph(VM_Controller.options.DYNAMIC_CALL_FILE_OUTPUT);
}
/**
* Dump all profile data to the given file
* @param fn output file name
*/
public synchronized void dumpGraph(String fn) {
final BufferedWriter f;
try {
f = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fn), "ISO-8859-1"));
} catch (IOException e) {
VM.sysWrite("\n\nVM_PartialCallGraph.dumpGraph: Error opening output file!!\n\n");
return;
}
TreeSet<VM_CallSite> tmp = new TreeSet<VM_CallSite>(new OrderByTotalWeight());
tmp.addAll(callGraph.keySet());
for (final VM_CallSite cs : tmp) {
VM_WeightedCallTargets ct = callGraph.get(cs);
ct.visitTargets(new VM_WeightedCallTargets.Visitor() {
public void visit(VM_Method callee, double weight) {
VM_CodeArray callerArray = (VM_CodeArray) cs.getMethod().getCurrentEntryCodeArray(false);
VM_CodeArray calleeArray = (VM_CodeArray) callee.getCurrentEntryCodeArray(false);
try {
f.write("CallSite " +
cs.getMethod().getMemberRef() +
" " +
callerArray.length() +
" " +
+cs.getBytecodeIndex() +
" " +
callee.getMemberRef() +
" " +
+calleeArray.length() +
" weight: " +
weight +
"\n");
f.flush();
} catch (IOException exc) {
System.err.println("I/O error writing to dynamic call graph profile.");
}
}
});
}
}
/**
* Used to compare two call sites by total weight.
*/
private final class OrderByTotalWeight implements Comparator<VM_CallSite> {
public int compare(VM_CallSite o1, VM_CallSite o2) {
if (o1.equals(o2)) return 0;
double w1 = callGraph.get(o1).totalWeight();
double w2 = callGraph.get(o2).totalWeight();
if (w1 < w2) { return 1; }
if (w1 > w2) { return -1; }
// equal weights; sort lexicographically
return o1.toString().compareTo(o2.toString());
}
}
}