/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2011, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.jcopter.inline;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph;
import com.jopdesign.common.code.CallString;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.common.misc.Ternary;
import com.jopdesign.common.processormodel.ProcessorModel;
import com.jopdesign.common.type.TypeHelper;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.jcopter.analysis.AnalysisManager;
import com.jopdesign.jcopter.analysis.ExecFrequencyProvider;
import com.jopdesign.jcopter.analysis.MethodCacheAnalysis;
import com.jopdesign.jcopter.analysis.StacksizeAnalysis;
import com.jopdesign.jcopter.greedy.Candidate;
import com.jopdesign.jcopter.greedy.CodeOptimizer;
import com.jopdesign.wcet.WCETProcessorModel;
import com.jopdesign.wcet.jop.MethodCache;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.DUP;
import org.apache.bcel.generic.GOTO;
import org.apache.bcel.generic.IFNONNULL;
import org.apache.bcel.generic.IINC;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.POP;
import org.apache.bcel.generic.POP2;
import org.apache.bcel.generic.RETURN;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.generic.Type;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Stefan Hepp (stefan@stefant.org)
*/
public class InlineOptimizer implements CodeOptimizer {
public static final Logger logger = Logger.getLogger(JCopter.LOG_INLINE+".InlineOptimizer");
private final JCopter jcopter;
private final InlineConfig config;
private final AppInfo appInfo;
private final ProcessorModel processorModel;
private final InlineHelper helper;
private final Map<InstructionHandle,CallString> callstrings;
private boolean preciseSizeEstimate;
private boolean preciseCycleEstimate;
private int storeCycles;
private int checkNPCycles;
private int deltaReturnCycles;
private boolean updateDFA;
private int countInvokeSites;
private int countDevirtualized;
protected class InlineCandidate extends Candidate {
// TODO having a context with a callstring != EMPTY is not yet fully supported (callgraph/analysis-updating!)
private final ExecutionContext context;
private final InvokeSite invokeSite;
private final MethodInfo invokee;
private final boolean needsNPCheck;
private final boolean needsEmptyStack;
private int maxLocals;
private int deltaCodesize;
private int deltaLocals;
private long localGain;
private boolean isLastInvoke;
private boolean isLastLocalInvoke;
private long invokeCacheCosts;
private long returnCacheCosts;
private long invokeeDeltaReturnCosts;
protected InlineCandidate(InvokeSite invokeSite, MethodInfo invokee,
boolean needsNPCheck, boolean needsEmptyStack, int maxLocals)
{
super(invokeSite.getInvoker(), invokeSite.getInstructionHandle(), invokeSite.getInstructionHandle());
this.context = new ExecutionContext(invokeSite.getInvoker());
this.invokeSite = invokeSite;
this.invokee = invokee;
this.needsNPCheck = needsNPCheck;
this.needsEmptyStack = needsEmptyStack;
this.maxLocals = maxLocals;
}
public InstructionHandle getInvokeInstruction() {
return start;
}
@Override
public boolean optimize(AnalysisManager analyses, StacksizeAnalysis stacksize) {
// should we check again for stack and locals size? nah ..
MethodCode code = getMethod().getCode();
InstructionList il = code.getInstructionList();
// To avoid problems with loosing targets we insert our code after the invoke and remove
// the invoke afterwards, retargeting to the next instruction
assert(start == end);
InstructionHandle invoke = start;
InstructionHandle next = end.getNext();
// insert the prologue
insertPrologue(il, next);
// insert the invokee code
Map<InvokeSite,InvokeSite> invokeMap;
CallString callString = getInlineCallString(code, invoke).push(invokeSite);
invokeMap = insertInvokee(analyses, callString, il, next);
// remove the invoke, retarget to next instruction, update start and end handlers
start = invoke.getNext();
end = next.getPrev();
if (end == null) {
// if we inline an empty static method with no arguments at the beginning of the code, end is null
start = null;
}
if (end == invoke) {
// inlined an empty method .. tricky
start = null;
end = null;
}
InstructionHandle tmp = invoke.getNext();
try {
il.delete(invoke);
} catch (TargetLostException e) {
code.retarget(e, tmp);
}
// Not really needed, but makes debugging easier
il.setPositions();
// finally, we need to update the analyses
for (CallGraph cg : analyses.getCallGraphs()) {
updateCallgraph(cg, invokeMap);
}
updateAnalyses(analyses, invokeMap);
// We already updated the callgraph, so no need to call checkLastInvoke, instead just check the main graph.
// Note that we do not need to check if any other method has been removed, because we added edges
// to all targets of all invokesites of the removed method.
// Also note, that although a method may still be in the main graph, it may have been removed from the
// target- or WCA-callgraph. Here we want to know if a method can be removed from the whole application.
isLastInvoke = !analyses.getAppInfoCallGraph().containsMethod(invokee);
return true;
}
private void insertPrologue(InstructionList il, InstructionHandle next)
{
int paramOffset = invokee.isStatic() ? maxLocals : maxLocals + 1;
// store all parameters in the slots used for the inlined code, except the this-reference
Type[] types = invokee.getArgumentTypes();
paramOffset += TypeHelper.getNumSlots(types);
for (int i = types.length - 1; i >= 0; i--) {
paramOffset -= types[i].getSize();
Instruction store = TypeHelper.createStoreInstruction(types[i], paramOffset);
il.insert(next, store);
}
if (invokee.isStatic()) {
return;
}
// store the this reference
InstructionHandle store = il.insert(next, new ASTORE(maxLocals));
// insert nullpointer check
if (needsNPCheck) {
// we popped all arguments, so there must be the this-ref left on the TOS before the store
il.insert(store, new DUP());
il.insert(store, new IFNONNULL(store));
// throwing a null reference throws a nullpointer-exception, just what we want.
// TODO this could insert a new invokesite (changing the cache,..), but we just ignore this for now!
il.insert(store, new ATHROW());
}
// TODO saving the stack is currently not implemented..
assert(!needsEmptyStack);
}
private Map<InvokeSite,InvokeSite> insertInvokee(AnalysisManager analyses, CallString callString,
InstructionList il, InstructionHandle next) {
MethodCode code = getMethod().getCode();
MethodCode invokeeCode = invokee.getCode();
InstructionList iList = invokeeCode.getInstructionList(true, false);
Map<InstructionHandle,InstructionHandle> instrMap = new LinkedHashMap<InstructionHandle, InstructionHandle>();
Map<InvokeSite,InvokeSite> invokeMap = new LinkedHashMap<InvokeSite, InvokeSite>();
StacksizeAnalysis stacksize = analyses.getStacksizeAnalysis(invokee);
// first copy all instruction handles
for (InstructionHandle src = iList.getStart(); src != null; src = src.getNext()) {
InstructionHandle ih = copyInstruction(code, stacksize, il, src, next);
if (ih == null) continue;
code.copyCustomValues(invokee, ih, src);
if (code.isInvokeSite(ih)) {
InvokeSite newInvoke = code.getInvokeSite(ih);
InvokeSite oldInvoke = invokeeCode.getInvokeSite(src);
invokeMap.put(oldInvoke, newInvoke);
// update inline-callstring for this instruction
setInlineCallString(code, ih, callString);
}
instrMap.put(src, ih);
}
remapTargets(instrMap, next);
// update dataflow results
if (updateDFA) {
jcopter.getDfaTool().copyResults(invokeSite.getInvoker(), instrMap);
}
return invokeMap;
}
private InstructionHandle copyInstruction(MethodCode invokerCode, StacksizeAnalysis stacksize,
InstructionList il,
InstructionHandle src, InstructionHandle next)
{
InstructionHandle ih;
Instruction instr = src.getInstruction();
Instruction c = invokerCode.copyFrom(invokee.getClassInfo(), instr);
if (instr instanceof LocalVariableInstruction) {
// remap local variables
int slot = maxLocals + ((LocalVariableInstruction)instr).getIndex();
((LocalVariableInstruction)c).setIndex(slot);
ih = il.insert(next, c);
} else if (instr instanceof ReturnInstruction) {
// replace return with goto, for last instruction we use fallthrough
if (src.getNext() != null) {
c = new GOTO(next);
ih = il.insert(next, (BranchInstruction)c);
} else {
ih = null;
}
// we need to check if there is a single value left on the stack, else we need to
// store the value in maxLocals (we can overwrite whatever it holds), pop everything else end restore it!
// make sure that ih refers to the *first* new instruction
int stack = stacksize.getStacksizeBefore(src);
Type type = ((ReturnInstruction) instr).getType();
stack -= type.getSize();
if (stack != 0) {
// create xSTORE,[POP,..],xLOAD before GOTO, ih must refer to STORE
Instruction store = TypeHelper.createStoreInstruction(type, maxLocals);
ih = il.insert(ih == null ? next : ih, store);
Instruction load = TypeHelper.createLoadInstruction(type, maxLocals);
il.append(ih, load);
while (stack > 0) {
if (stack > 1) {
il.append(ih, new POP2());
stack -= 2;
} else {
il.append(ih, new POP());
stack--;
}
}
}
} else if (c instanceof BranchInstruction) {
ih = il.insert(next, (BranchInstruction) c);
} else {
ih = il.insert(next, c);
}
return ih;
}
private void remapTargets(Map<InstructionHandle,InstructionHandle> instrMap, InstructionHandle next) {
for (Map.Entry<InstructionHandle,InstructionHandle> e : instrMap.entrySet()) {
InstructionHandle oldIh = e.getKey();
InstructionHandle newIh = e.getValue();
Instruction i = oldIh.getInstruction();
Instruction c = newIh.getInstruction();
// Note that this skips over the goto instructions we inserted instead of returns, this is intentional
if (i instanceof BranchInstruction) {
BranchInstruction bi = (BranchInstruction) i;
BranchInstruction bc = (BranchInstruction) c;
InstructionHandle target = bi.getTarget(); // old target
// New target is in hash map
if (bi instanceof Select) {
// Either LOOKUPSWITCH or TABLESWITCH
InstructionHandle[] targets = ((Select) bi).getTargets();
for (int j = 0; j < targets.length; j++) {
InstructionHandle newTarget = instrMap.get(targets[j]);
((Select)bc).setTarget(j, newTarget != null ? newTarget : next);
}
}
// If the old instruction targets an instruction which we removed, we retarget the new instruction to
// the instruction after the inlined code. We can do this because we only remove return instructions.
InstructionHandle newTarget = instrMap.get(target);
bc.setTarget(newTarget != null ? newTarget : next);
}
}
}
private void updateCallgraph(CallGraph cg, Map<InvokeSite,InvokeSite> invokeMap) {
// This is a quick hack: inlining does not change acyclic property,
Ternary acyclic = cg.getAcyclicity();
// First we need to copy the execution contexts with new callstrings:
// We need to create a copy of the node-list of the invoker in case we have a recursive method
List<ExecutionContext> nodes = new ArrayList<ExecutionContext>(cg.getNodes(getMethod()));
for (ExecutionContext invoker : nodes) {
// for all invokee contexts reachable via the invokesite ..
for (ExecutionContext invokeeNode : cg.getInvokedNodes(invoker, invokeSite, invokee)) {
// .. copy all invoked contexts
for (ExecutionContext child : cg.getChildren(invokeeNode)) {
ExecutionContext newInvokee;
if (!child.getCallString().isEmpty()) {
// construct a new callstring using the context of the invoker and by replacing
// the invokeSite in the old invokee with the inlined invokeSite
CallString cs = invoker.getCallString();
cs = cs.push( invokeMap.get( child.getCallString().top() ), appInfo.getCallstringLength());
// we need to copy the new nodes recursively since childs with depth up to max callstring
// length can contain the old invokesite and must now contain the new one
newInvokee = cg.copyNodeRecursive(child, cs, appInfo.getCallstringLength());
} else {
newInvokee = child;
}
cg.addEdge(invoker, newInvokee);
}
}
}
// Now we remove the inlined edge(s) and contexts from the graph
if (!cg.removeNodes(invokeSite, invokee, true)) {
// we did not remove an exec context, this happens when callstring length is 0
// only way to handle this is to check all invokesites of this method, see if the invokee is still in
// the set of invokees.
if (isLastLocalInvoke) {
cg.removeEdges(getMethod(), invokee, true);
}
}
if (acyclic != Ternary.UNKNOWN) {
cg.setAcyclicity(acyclic == Ternary.TRUE);
}
}
private void updateAnalyses(AnalysisManager analyses, Map<InvokeSite,InvokeSite> invokeMap) {
analyses.getExecFrequencyAnalysis().inline(invokeSite, invokee, new LinkedHashSet<InvokeSite>(invokeMap.values()) );
analyses.getMethodCacheAnalysis().inline(this, invokeSite, invokee);
}
@Override
public boolean recalculate(AnalysisManager analyses, StacksizeAnalysis stacksize) {
// maxLocals: may have changed if other optimizations introduced new locals outside their range
// TODO if other optimizations add locals which are live in our region, we need to increase maxLocals
// this is currently not supported by the GreedyOptimizer (either need to keep track of locals in
// GreedyOptimizer or extend StacksizeAnalysis to keep track of live locals per IH too)
// deltaLocals: maxLocals may change in invokee, so we need to update
deltaLocals = invokee.getCode().getMaxLocals();
// deltaCodesize: codesize of invokee may have changed
deltaCodesize = calcDeltaCodesize(analyses);
// check if max locals and stacksize still checks out (codesize is checked by the CandidateSelector)
if (!checkConstraints(stacksize)) {
return false;
}
// isLastInvoke,isLastLocalInvoke: may have changed due to previous inlining
isLastLocalInvoke = checkIsLastLocalInvoke();
isLastInvoke = isLastLocalInvoke && checkIsLastInvoker();
// localGain: could have changed due to codesize changes, or cache-miss-count changes
localGain = calcLocalGain(analyses);
calcCacheMissCosts(analyses, deltaCodesize);
// TODO we might need to save the stack now if exception-handlers have been added, check this
return true;
}
private void calcCacheMissCosts(AnalysisManager analyses, int deltaBytes) {
MethodCache cache = analyses.getJCopter().getMethodCache();
int invokerBytes = invokeSite.getInvoker().getCode().getNumberOfBytes();
int invokerWords = MiscUtils.bytesToWords(invokerBytes);
int invokeeWords = invokee.getCode().getNumberOfWords();
// we save the cache miss costs for the invoke and the return of the old invoke
invokeCacheCosts = cache.getMissPenaltyOnInvoke(invokerWords, invokeSite.getInvokeInstruction());
returnCacheCosts = cache.getMissPenaltyOnReturn(invokeeWords, invokeSite.getInvokeeRef().getDescriptor().getType());
// for every return in the inlined code, we have additional return cache miss costs
int newWords = MiscUtils.bytesToWords(invokerBytes + deltaBytes);
// TODO this is not quite correct, the old invoke return is the invokesite in the invokee, not the invoker,
// but this currently returns the maximum value for all returns anyway
// XXX [bh] now we do use the instruction information: therefore switched to use other form which
// ignores the instruction
invokeeDeltaReturnCosts = cache.getMissPenalty(newWords, false) -
cache.getMissPenalty(invokeeWords, false);
}
@Override
public int getDeltaLocalCodesize() {
return deltaCodesize;
}
@Override
public Collection<MethodInfo> getUnreachableMethods() {
return isLastInvoke ? Collections.singleton(invokee) : null;
}
@Override
public Collection<MethodInfo> getRemovedInvokees() {
return isLastLocalInvoke ? Collections.singleton(invokee) : Collections.<MethodInfo>emptyList();
}
@Override
public int getMaxLocalsInRegion() {
return maxLocals + deltaLocals;
}
@Override
public long getLocalGain() {
return localGain;
}
@Override
public long getDeltaCacheMissCosts(AnalysisManager analyses, ExecFrequencyProvider ecp) {
MethodCacheAnalysis mca = analyses.getMethodCacheAnalysis();
// we save the invoke costs per invoke and return cache miss
long costs = -mca.getInvokeReturnCacheCosts(ecp, invokeSite, invokeCacheCosts, returnCacheCosts);
// the costs increase because all invokes in the invokee will now have a higher return
// cache miss cost!
// Need to find out how many return misses there are in the new code, and how many return misses are in the
// old code, and how big the cache miss cost difference is..
costs += mca.getReturnMissCount(ecp, new ExecutionContext(invokee, new CallString(invokeSite))) *
invokeeDeltaReturnCosts;
// TODO the number of invoke and return misses can also change, we need to add those costs too!
return costs;
}
@Override
public Collection<CallString> getRequiredContext() {
return context.getCallString().length() > 0 ? Collections.singleton(context.getCallString()) : null;
}
@Override
public String toString() {
return invokeSite + " <= " + invokee;
}
private boolean checkConstraints(StacksizeAnalysis stacksize) {
int codeSize = getMethod().getCode().getNumberOfBytes() + deltaCodesize;
if (codeSize > processorModel.getMaxMethodSize()) {
return false;
}
if (processorModel.getMaxLocals() < maxLocals + deltaLocals) {
return false;
}
int stack = stacksize.getStacksizeBefore(getInvokeInstruction());
stack -= TypeHelper.getNumInvokeSlots(invokee);
stack += invokee.getCode().getMaxStack();
if (processorModel.getMaxStackSize() < stack) {
return false;
}
return true;
}
private int calcDeltaCodesize(AnalysisManager analyses) {
int delta = 0;
// we remove the invokesite
delta -= processorModel.getNumberOfBytes(getMethod(), getInvokeInstruction().getInstruction());
// .. add a prologue ..
if (needsNPCheck) {
// DUP IFNONNULL ATHROW
delta += 5;
}
if (needsEmptyStack) {
// TODO if we need to save the stack, we need to account for this as well
}
if (!invokee.isStatic()) {
// ASTORE this
delta += getLoadStoreSize(0);
}
// xSTORE parameters: over-approximate by assuming 2/4 bytes per store
delta += invokee.getArgumentTypes().length * getLoadStoreSize(TypeHelper.getNumInvokeSlots(invokee));
// if preciseEstimate is false, just use JVM codesize and ignore all other changes..
if (!preciseSizeEstimate) {
delta += invokee.getCode().getNumberOfBytes(false);
return delta;
}
StacksizeAnalysis stacksize = analyses.getStacksizeAnalysis(invokee);
// .. and finally we inline the code, but with some modifications
InstructionHandle ih = invokee.getCode().getInstructionList(true, false).getStart();
while (ih != null) {
Instruction instr = ih.getInstruction();
if (instr instanceof ReturnInstruction) {
// we replace this with goto, and since method-size is limited to 16bit, we do not need the wide version
if (ih.getNext() != null) {
delta += 3;
}
// if we need to pop unused values from the stack, account for this as well
int stack = stacksize.getStacksizeBefore(ih);
stack -= ((ReturnInstruction)instr).getType().getSize();
if (stack > 0) {
delta += 2*getLoadStoreSize(0);
delta += (stack+1)/2;
}
} else if (instr instanceof LocalVariableInstruction) {
// we map the local vars to higher indices, might increase code size
int idx = ((LocalVariableInstruction)instr).getIndex();
if (instr instanceof IINC) {
delta += maxLocals + idx > 255 ? 6 : 3;
} else {
delta += getLoadStoreSize(maxLocals + idx);
}
} else {
delta += processorModel.getNumberOfBytes(invokee, instr);
}
ih = ih.getNext();
}
return delta;
}
private int getLoadStoreSize(int slot) {
int pos = maxLocals + slot;
return pos > 255 ? 4 : (pos > 3 ? 2 : 1);
}
private boolean checkIsLastInvoker() {
CallGraph cg = appInfo.getCallGraph();
// check if the invokee is invoked in any other method
for (ExecutionContext node : cg.getNodes(invokee)) {
for (ExecutionContext parent : cg.getParents(node)) {
if (!parent.getMethodInfo().equals(invokeSite.getInvoker())) {
return false;
}
}
}
return true;
}
private boolean checkIsLastLocalInvoke() {
for (ExecutionContext node : appInfo.getCallGraph().getReferencedMethods(invokeSite.getInvoker())) {
if (!node.getMethodInfo().equals(invokee)) continue;
if (node.getCallString().isEmpty()) {
// This is a problem, we can only find out if we check all invokes in this method.
if (searchInvokeSites()) {
return false;
}
} else {
if (!node.getCallString().top().equals(invokeSite)) {
return false;
}
}
}
return true;
}
private boolean searchInvokeSites() {
for (InvokeSite site : getMethod().getCode().getInvokeSites()) {
if (invokeSite.equals(site)) continue;
// this checks the same callgraph we want to prune, but there is actually no difference to
// checking the type hierarchy, since this returns only methods which override the invoked method.
Set<MethodInfo> methods = appInfo.findImplementations(site);
if (methods.isEmpty() || methods.contains(invokee)) {
return true;
}
}
return false;
}
private long calcLocalGain(AnalysisManager analyses) {
// gain without cache costs for single invoke..
long gain = jcopter.getWCETProcessorModel().getExecutionTime(context, invokeSite.getInstructionHandle());
if (!preciseCycleEstimate) {
// we loose some gain due to the prologue
gain -= invokee.getArgumentTypes().length * storeCycles;
if ( !invokee.isStatic() ) gain -= storeCycles;
if (needsNPCheck) gain -= checkNPCycles;
// we gain something since we execute a goto now instead of a return (assuming no exceptions are thrown)
// TODO there may be no goto at all if there is only one return at the end
gain += deltaReturnCycles;
} else {
throw new AppInfoError("Precise cycle gain calculation not yet implemented!");
}
// TODO we may also loose some speed because we changed local-var slots
return gain;
}
}
public InlineOptimizer(JCopter jcopter, InlineConfig config) {
this.jcopter = jcopter;
this.config = config;
this.appInfo = AppInfo.getSingleton();
this.processorModel = appInfo.getProcessorModel();
this.helper = new InlineHelper(jcopter, config);
this.callstrings = new LinkedHashMap<InstructionHandle, CallString>();
// TODO get from config, preciseCycleEstimate is not yet fully implemented
preciseSizeEstimate = true;
preciseCycleEstimate = false;
}
public void setUpdateDFA(boolean updateDFA) {
this.updateDFA = updateDFA;
}
public boolean doUpdateDFA() {
return updateDFA;
}
@Override
public void initialize(AnalysisManager analyses, Collection<MethodInfo> roots) {
if (!preciseCycleEstimate) {
ExecutionContext dummy = new ExecutionContext(roots.iterator().next());
WCETProcessorModel pm = analyses.getJCopter().getWCETProcessorModel();
InstructionList il = new InstructionList();
// TODO very messy approximation of exec time
storeCycles = (int) pm.getExecutionTime(dummy, il.append(new ASTORE(10)));
checkNPCycles = 0;
checkNPCycles += (int) pm.getExecutionTime(dummy, il.append(new DUP()));
checkNPCycles += (int) pm.getExecutionTime(dummy, il.append(new IFNONNULL(il.append(new ATHROW()))));
deltaReturnCycles = (int) pm.getExecutionTime(dummy, il.append(new RETURN()));
deltaReturnCycles -= (int) pm.getExecutionTime(dummy, il.append(new GOTO(il.getEnd())));
}
countInvokeSites = 0;
countDevirtualized = 0;
}
@Override
public Collection<Candidate> findCandidates(MethodInfo method, AnalysisManager analyses,
StacksizeAnalysis stacksize, int maxLocals)
{
InstructionList il = method.getCode().getInstructionList(true, false);
return findCandidates(method, analyses, stacksize, maxLocals, il.getStart(), il.getEnd());
}
@Override
public Collection<Candidate> findCandidates(MethodInfo method, AnalysisManager analyses, StacksizeAnalysis stacksize,
int maxLocals, InstructionHandle start, InstructionHandle end)
{
List<Candidate> candidates = new LinkedList<Candidate>();
MethodCode code = method.getCode();
InstructionHandle next = end.getNext();
for (InstructionHandle ih = start; ih != next; ih = ih.getNext()) {
if (code.isInvokeSite(ih)) {
InvokeSite site = code.getInvokeSite(ih);
// since we update the appInfo callgraph, the callstring only contains the invokesite and no
// inlined methods
CallString cs = new CallString(site);
countInvokeSites++;
MethodInfo invokee = helper.devirtualize(cs);
if (invokee == null) continue;
countDevirtualized++;
// for the initial check and the DFA lookup we need the old callstring
cs = getInlineCallString(code, ih).push(site);
Candidate candidate = checkInvoke(code, cs, site, invokee, maxLocals);
if (candidate == null) {
continue;
}
// initial check for locals and stack, calculate gain and codesize
if (!candidate.recalculate(analyses, stacksize)) {
continue;
}
candidates.add(candidate);
}
}
return candidates;
}
@Override
public void printStatistics() {
logger.info("Found invoke sites: "+countInvokeSites+
", not devirtualized: "+(countInvokeSites-countDevirtualized));
}
private Candidate checkInvoke(MethodCode code, CallString cs, InvokeSite invokeSite, MethodInfo invokee,
int maxLocals)
{
if (!helper.canInline(cs, invokeSite, invokee)) {
return null;
}
boolean needsEmptyStack = helper.needsEmptyStack(invokeSite, invokee);
boolean needsNPCheck = helper.needsNullpointerCheck(cs, invokee, true);
if (needsEmptyStack) {
// Not supported for now..
return null;
}
return new InlineCandidate(invokeSite, invokee, needsNPCheck, needsEmptyStack, maxLocals);
}
/**
* Get the callstring starting at the method to optimize to the invokesite to inline, containing all
* original invokesites in the unoptimized code (if the invokesite to inline has been inlined before).
* This is required to lookup results in analyses for which callstrings are not updated by the inliner (e.g. DFA)
* and to check for recursive invokes.
* Note that the AppInfo callgraph is updated, so this callstring must NOT be used for lookups there.
*
* @param code the code of the method to optimize.
* @param ih the invokesite to inline
* @return the callstring of all methods which have been inlined into the method leading to the invokesite.
*/
private CallString getInlineCallString(MethodCode code, InstructionHandle ih) {
CallString cs = callstrings.get(ih);
return cs == null ? CallString.EMPTY : cs;
}
private CallString setInlineCallString(MethodCode code, InstructionHandle ih, CallString cs) {
// TODO we might want to use InstructionHandle CustomKeys, we want those callstrings to be copied if code is copied.
// and if handles are reused, we need to make sure that the values are removed from the map.
CallString old = callstrings.put(ih, cs);
return old == null ? CallString.EMPTY : old;
}
}