/* * 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.CallString; import com.jopdesign.common.code.InvokeSite; import com.jopdesign.common.processormodel.ProcessorModel; import com.jopdesign.common.type.StackHelper; import com.jopdesign.common.type.TypeHelper; import com.jopdesign.common.type.ValueInfo; import com.jopdesign.jcopter.JCopter; import com.jopdesign.jcopter.analysis.ValueMapAnalysis; import com.jopdesign.jcopter.optimizer.AbstractOptimizer; import org.apache.bcel.generic.ARRAYLENGTH; import org.apache.bcel.generic.ArithmeticInstruction; import org.apache.bcel.generic.CHECKCAST; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.ConversionInstruction; import org.apache.bcel.generic.DUP; import org.apache.bcel.generic.DUP2; import org.apache.bcel.generic.FieldInstruction; import org.apache.bcel.generic.GETFIELD; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionConstants; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.LDC; import org.apache.bcel.generic.LDC2_W; import org.apache.bcel.generic.NOP; import org.apache.bcel.generic.POP; import org.apache.bcel.generic.POP2; import org.apache.bcel.generic.PUTFIELD; import org.apache.bcel.generic.PushInstruction; import org.apache.bcel.generic.RETURN; import org.apache.bcel.generic.ReturnInstruction; import org.apache.bcel.generic.StackInstruction; import org.apache.bcel.generic.Type; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; /** * @author Stefan Hepp (stefan@stefant.org) */ public class SimpleInliner extends AbstractOptimizer { private static class InlineData { private List<ValueInfo> params; private InstructionList prologue; private InstructionList epilogue; private int inlineStart; private int oldPrologueLength; private InvokeSite invokeSite; private InlineData() { params = new ArrayList<ValueInfo>(4); prologue = new InstructionList(); epilogue = new InstructionList(); } public void setInlineStart(int inlineStart) { this.inlineStart = inlineStart; } /** * @param oldPrologueLength number of instructions before the invokesite to replace with the prologue */ public void setOldPrologueLength(int oldPrologueLength) { this.oldPrologueLength = oldPrologueLength; } public void addPrologue(Instruction instruction) { prologue.append(instruction); } public void addEpilogue(Instruction instruction) { epilogue.append(instruction); } public void addParam(ValueInfo param) { this.params.add(param); } public void setInvokeSite(InvokeSite invokeSite) { this.invokeSite = invokeSite; } public List<ValueInfo> getParams() { return params; } public InvokeSite getInvokeSite() { return invokeSite; } public InstructionList getPrologue() { return prologue; } public InstructionList getEpilogue() { return epilogue; } public int getOldPrologueLength() { return oldPrologueLength; } public int getInlineStart() { return inlineStart; } public void reset() { params.clear(); inlineStart = 0; prologue.dispose(); epilogue.dispose(); oldPrologueLength = 0; invokeSite = null; } } private static final Logger logger = Logger.getLogger(JCopter.LOG_INLINE+".SimpleInliner"); private final InlineHelper helper; private int inlineCounter; private int candidates; private int unhandledInstructions; private int requiresNPCheck; private int signatureMismatch; private int codesizeTooLarge; private int countInvokeSites; private int countDevirtualized; public SimpleInliner(JCopter jcopter, InlineConfig inlineConfig) { super(jcopter, true); helper = new InlineHelper(jcopter, inlineConfig); } @Override public void initialize() { inlineCounter = 0; candidates = 0; unhandledInstructions = 0; requiresNPCheck = 0; signatureMismatch = 0; codesizeTooLarge = 0; countInvokeSites = 0; countDevirtualized = 0; } @Override public void optimizeMethod(MethodInfo method) { InlineData inlineData = new InlineData(); List<InvokeSite> invokes = new ArrayList<InvokeSite>( method.getCode().getInvokeSites() ); // we iterate over the invoke sites in a sorted order for the single reason that the DFA cache hack works // (else the order of the entries in the constantpool can differ) Collections.sort(invokes, new Comparator<InvokeSite>() { @Override public int compare(InvokeSite o1, InvokeSite o2) { return o1.getInstructionHandle().getPosition() - o2.getInstructionHandle().getPosition(); } }); for (InvokeSite invoke : invokes) { // The callstring contains 'original' invokesites from the unmodified callgraph, // 'invoke' refers to the new invokesite in the modified code CallString cs = new CallString(invoke); while (invoke != null) { countInvokeSites++; MethodInfo invokee = helper.devirtualize(cs); if (invokee == null) break; countDevirtualized++; // Preliminary checks if (checkInvoke(invoke, cs, invokee, inlineData)) { invoke = performSimpleInline(invoke, invokee, inlineData); inlineCounter++; if (inlineData.getInvokeSite() != null) { cs.push(inlineData.getInvokeSite()); } else { break; } } else { break; } } } // update callgraph (?) If we update the callgraph, the callstrings become invalid! // -> update callgraph only after we finished inlining of a toplevel invokesite; // collect all invokesites to collapse into toplevel invokesite; // replace old invokesite with invokesites from inlined code, add edges to not inlined methods // We could collect all nodes to merge and call callgraph.merge(), but this is not yet implemented. // Instead we simply rebuild the whole callgraph and all results which use callstrings. } @Override public void printStatistics() { logger.info("Inlined "+inlineCounter+" invoke sites."); logger.info("Found invoke sites: "+countInvokeSites+ ", not devirtualized: "+(countInvokeSites-countDevirtualized)); logger.info("Candidates: "+candidates+"; need NP check: "+ requiresNPCheck +", uncorrectable signature mismatch: "+ signatureMismatch +", unhandled instruction: "+unhandledInstructions+", codesize: "+codesizeTooLarge); } private boolean checkInvoke(InvokeSite invokeSite, CallString cs, MethodInfo invokee, InlineData inlineData) { // could be a native method, or it has not been devirtualized if (invokee == null || !invokee.hasCode()) { return false; } if (invokee.getCode().getExceptionHandlers().length > 0) { // We do not support inlining code with exception handles (as this code would be too large anyway..) return false; } // ignore methods which are most certainly too large (allow for param loading, invoke and return, // and some slack to allow for unused params) int estimate = invokee.getArgumentTypes().length * 2 + 10; if (invokee.getCode().getNumberOfBytes(false) > estimate) { return false; } // initial checks if (!helper.canInline(cs, invokeSite, invokee)) { return false; } // TODO we should check if the stack is empty and if so inline anyway? if (helper.needsEmptyStack(invokeSite, invokee)) { return false; } // If we pass all tests so far, we count this as a candidate.. candidates++; // check the invokee, the invoke site and the new code size and store the results into inlineData inlineData.reset(); // We do not check stacksize and maxLocals here, because we do not inline store instructions and we simply // assume that for simple invokees stacksize is never a problem .. if (!analyzeInvokee(cs, invokee, inlineData)) { return false; } if (!analyzeInvokeSite(invokeSite, invokee, inlineData)) { signatureMismatch++; if (logger.isTraceEnabled()) { logger.trace("Not inlining "+invokee+" at "+invokeSite+" because of signature mismatch."); } return false; } if (!analyzeCodeSize(invokeSite, invokee, inlineData)) { codesizeTooLarge++; if (logger.isTraceEnabled()) { logger.trace("Not inlining "+invokee+" at "+invokeSite+" because of codesize."); } return false; } return true; } /** * @param cs the callstring from the invoker to the invoke to inline (if recursive). Used to check DFA results. * @param invokee the invoked method to analyze * @param inlineData the map to populate with the parameters and the instructions to inline. * @return true if inlining is possible */ private boolean analyzeInvokee(CallString cs, MethodInfo invokee, InlineData inlineData) { // we allow loading of parameters, loading of constants, some instruction, and a return ValueMapAnalysis values = new ValueMapAnalysis(invokee); values.loadParameters(); InstructionList il = invokee.getCode().getInstructionList(true, false); InstructionHandle ih = il.getStart(); // we should at least have a return instruction, so even for empty methods we should fall through // generate the parameter mapping int count = 0; while (true) { Instruction instruction = ih.getInstruction(); if (instruction instanceof PushInstruction || instruction instanceof NOP) { values.transfer(instruction); ih = ih.getNext(); count++; } else { break; } } // store the mapping for (ValueInfo value : values.getValueTable().getStack()) { inlineData.addParam(value); } inlineData.setInlineStart(count); // if we do not need an NP check, we can also inline code which does not throw an exception in the same way boolean needsNPCheck = helper.needsNullpointerCheck(cs, invokee, false); boolean hasNPCheck = false; // we allow up to 5 instructions and one return before assuming that the resulting code will be too large for (int i = 0; i < 6; i++) { // now lets see what we have here as non-push instructions Instruction instruction = ih.getInstruction(); if (instruction instanceof InvokeInstruction) { if (inlineData.getInvokeSite() != null) { // only inline at most one invoke return false; } InvokeSite is = invokee.getCode().getInvokeSite(ih); inlineData.setInvokeSite(is); hasNPCheck |= !is.isInvokeStatic(); } else if (instruction instanceof FieldInstruction) { if (instruction instanceof GETFIELD) { hasNPCheck |= values.getValueTable().top().isThisReference(); } if (instruction instanceof PUTFIELD) { int down = values.getValueTable().top().isContinued() ? 2 : 1; hasNPCheck |= values.getValueTable().top(down).isThisReference(); } } else if (instruction instanceof ArithmeticInstruction || instruction instanceof ConversionInstruction || instruction instanceof StackInstruction || instruction instanceof LDC || instruction instanceof LDC2_W || instruction instanceof ARRAYLENGTH || instruction instanceof CHECKCAST || instruction instanceof NOP) { // nothing to do, just copy them } else if (instruction instanceof ReturnInstruction) { if (needsNPCheck && !hasNPCheck) { // We were nearly finished.. but NP check test failed this.requiresNPCheck++; if (logger.isTraceEnabled()) { logger.trace("Not inlining "+invokee+" because it requires a NP check."); } return false; } // we must have a return instruction now.. Check if we return the only value on the stack, // else we need to add pop instructions if (instruction instanceof RETURN) { // we do not return anything, so we must empty the stack while (values.getValueTable().getStackSize() > 0) { Instruction pop; if (values.getValueTable().top().isContinued()) { pop = new POP2(); } else { pop = new POP(); } inlineData.addEpilogue(pop); values.transfer(pop); } return true; } else { Type type = ((ReturnInstruction) instruction).getType(); // If we return a value, we only inline if the stack contains only the return value, // else we would need to move the return value down to the first stack slot and pop the rest // which would most likely produce too much code (and such a code is not generated by // javac anyway) return values.getValueTable().getStackSize() == type.getSize(); } } else { // if we encounter an instruction which we do not handle, we do not inline unhandledInstructions++; if (logger.isTraceEnabled()) { logger.trace("Not inlining "+invokee+" because of unhandled instruction "+ instruction.toString(invokee.getClassInfo().getConstantPoolGen().getConstantPool())); } return false; } // update the stack map since we need it to handle RETURN values.transfer(instruction); ih = ih.getNext(); } // too many instructions, do not inline return false; } /** * Check if the invokesite can be modified in a way so that the parameters are passed in the correct order * @param invokeSite the invokesite to inline. * @param invokee the invoked method. * @param inlineData the map to store the analyzer results * @return true if the prologue can be changed to match the expected behaviour */ private boolean analyzeInvokeSite(InvokeSite invokeSite, MethodInfo invokee, InlineData inlineData) { MethodInfo invoker = invokeSite.getInvoker(); ConstantPoolGen invokerCpg = invoker.getConstantPoolGen(); InstructionHandle invoke = invokeSite.getInstructionHandle(); // Check epilogue Type[] ret = StackHelper.produceStack(invokerCpg, invoke.getInstruction()); // works if the invoked method returns the same (single) type as the replaced instruction.. boolean match = (ret.length == 1 && TypeHelper.canAssign(invokee.getType(), ret[0])); // .. or if the invoked method returns void.. we accept that case and assume that if the invokee should // return something but doesn't then it is a JVM call and throws an exception. if (!match && !invokee.getType().equals(Type.VOID)) { return false; } // Check and build prologue Type[] args = StackHelper.consumeStack(invokerCpg, invoke.getInstruction()); List<Instruction> oldPrologue = new LinkedList<Instruction>(); int cnt = 0; InstructionHandle current = invoke; while (cnt < args.length) { if (current.hasTargeters()) { // stay within the basic block break; } current = current.getPrev(); Instruction instr = current.getInstruction(); // we only rearrange push-instructions if (!(instr instanceof PushInstruction) || (instr instanceof DUP) || (instr instanceof DUP2)) { break; } // we add this instruction to the old prologue to replace cnt++; oldPrologue.add(0, instr); } inlineData.setOldPrologueLength(cnt); List<ValueInfo> params = inlineData.getParams(); // other parameters must be used in the order they are pushed on the stack, we do not rearrange them int offset = args.length - cnt; for (int i = 0; i < offset; i++) { if (i >= params.size()) { Type t = args[i]; // unused argument, we cannot remove the push instruction so we pop it inlineData.addPrologue(t.getSize() == 2 ? new POP2() : new POP() ); } else { ValueInfo value = params.get(i); int argNum = value.getParamNr(); if (!invokee.isStatic()) { argNum++; } if (argNum != i) { return false; } } } // Now, we create a new prologue using the expected argument values and the old push instructions for (int i = offset; i < params.size(); i++) { ValueInfo value = params.get(i); if (value.isThisReference() || value.isParamReference()) { int argNum = value.getParamNr(); if (!invokee.isStatic()) { argNum++; } if (argNum < offset) { // loading a param a second time which we do not duplicate, cannot inline this return false; } // To be on the safe side, copy the instruction in case a param is used more than once Instruction instr = oldPrologue.get(argNum - offset).copy(); inlineData.addPrologue(instr); } else if (value.isNullReference()) { inlineData.addPrologue(InstructionConstants.ACONST_NULL); } else if (value.isConstantValue() || value.isStaticFieldReference()) { // We need to push a constant on the stack Instruction instr = value.getConstantValue().createPushInstruction(invoker.getConstantPoolGen()); inlineData.addPrologue(instr); } else if (!value.isContinued()) { throw new AssertionError("Unhandled value type"); } } return true; } /** * Check if the resulting code will not be larger than the older code. * @param invokeSite the invokesite to inline. * @param invokee the invoked method. * @param inlineData the map to store the analyzer results * @return true if the new code will not violate any size constrains */ private boolean analyzeCodeSize(InvokeSite invokeSite, MethodInfo invokee, InlineData inlineData) { ProcessorModel pm = AppInfo.getSingleton().getProcessorModel(); MethodInfo invoker = invokeSite.getInvoker(); // delta = new prologue + inlined code + epilogue - old prologue - invokesite int delta = 0; InstructionHandle[] il = invokee.getCode().getInstructionList().getInstructionHandles(); InstructionHandle ih = il[inlineData.getInlineStart()]; while (ih != null) { Instruction instr = ih.getInstruction(); if (instr instanceof ReturnInstruction) { break; } delta += pm.getNumberOfBytes(invokee, instr); ih = ih.getNext(); } for (InstructionHandle instr : inlineData.getPrologue().getInstructionHandles()) { delta += pm.getNumberOfBytes(invoker, instr.getInstruction()); } for (InstructionHandle instr : inlineData.getEpilogue().getInstructionHandles()) { delta += pm.getNumberOfBytes(invoker, instr.getInstruction()); } ih = invokeSite.getInstructionHandle(); for (int i = 0; i <= inlineData.getOldPrologueLength(); i++) { Instruction instr = ih.getInstruction(); delta -= pm.getNumberOfBytes(invoker, instr); ih = ih.getPrev(); } // TODO we could allow for some slack, especially if we decreased the codesize before.. return delta <= 0; } /** * Try to inline a simple getter, wrapper or stub method. * <p> * If the inlined code is again an invoke, the InvokeSite does not change because * the InstructionHandle of the invoker's invoke is kept.</p> * * @param invokeSite the invoke to replace. * @param invokee the method to inline. * @param inlineData the parameters of the invokee and the code to inline. * @return true if inlining has been performed. */ private InvokeSite performSimpleInline(InvokeSite invokeSite, MethodInfo invokee, InlineData inlineData) { MethodInfo invoker = invokeSite.getInvoker(); MethodCode invokerCode = invoker.getCode(); if (logger.isDebugEnabled()) { logger.debug("Inlining at "+invokeSite+" using " + invokee); } // Prepare code for the actual inlining helper.prepareInlining(invoker, invokee); InstructionHandle invoke = invokeSite.getInstructionHandle(); // Perform inlining: update the prologue if (inlineData.getOldPrologueLength() > 0) { InstructionHandle start = invoke; for (int i=0; i < inlineData.getOldPrologueLength(); i++) { start = start.getPrev(); } // invokeSite is not a target in this case, no need to worry about targets here invokerCode.replace(start, inlineData.getOldPrologueLength(), inlineData.getPrologue(), false); } else if (inlineData.getPrologue().getLength() > 0) { // old-prologue is empty, invokeSite may be a target.. Need to update the targets to the new prologue if (invoke.hasTargeters()) { invokerCode.retarget(invoke, inlineData.getPrologue().getStart()); } InstructionList il = invokerCode.getInstructionList(); il.insert(invoke, inlineData.getPrologue()); } // Replace the invoke MethodCode invokeeCode = invokee.getCode(); InstructionList il = invokeeCode.getInstructionList(); InstructionHandle start = invokeeCode.getInstructionHandle(inlineData.getInlineStart()); int cnt = il.getLength() - inlineData.getInlineStart(); if (il.getEnd().getInstruction() instanceof ReturnInstruction) { // do not inline the return cnt--; } InstructionHandle end = invokerCode.replace(invoke, 1, invokee, start, cnt, true); // copy source line number for first inlined instruction separately, since we skipped some instructions in // the invokee if (cnt > 0) { InstructionHandle ih = end; for (int i = 0; i < cnt; i++) { ih = ih.getPrev(); } invokerCode.setLineNumber(ih, invokeeCode.getSourceClassInfo(start), invokeeCode.getLineNumber(start)); } // insert epilogue invokerCode.getInstructionList().insert(end, inlineData.getEpilogue()); // If we inlined another invokesite, find the new invokesite and return it if (inlineData.getInvokeSite() != null) { end = end.getPrev(); // search backwards from last inlined instruction while (end != null) { if (invokerCode.isInvokeSite(end)) { return invokerCode.getInvokeSite(end); } end = end.getPrev(); } } return null; } }