Mirror of the BLOAT repository https://www.cs.purdue.edu/homes/hosking/bloat/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
bloat/src/EDU/purdue/cs/bloat/inline/Inline.java

733 lines
23 KiB

/**
* All files in the distribution of BLOAT (Bytecode Level Optimization and
* Analysis tool for Java(tm)) are Copyright 1997-2001 by the Purdue
* Research Foundation of Purdue University. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package EDU.purdue.cs.bloat.inline;
import java.util.*;
import EDU.purdue.cs.bloat.editor.*;
import EDU.purdue.cs.bloat.util.*;
/**
* Inlines the code of non-virtual method call sites. These sites include calls
* to static methods and certain uses of the <tt>invokespecial</tt> method.
* There are certain metrics that can be set to effect where and how inlining is
* performed.
*/
public class Inline {
public static boolean DEBUG = false;
private int maxCodeSize; // Max number of instructions in method
private int maxCallDepth; // Max of height of call stack
private boolean inlineExceptions; // Inline methods that throw exceptions
private InlineContext context;
private Map editors; // Maps MemberRefs to their MethodEditors
/**
* Size of the largest method that can be inlined
*/
public static int CALLEE_SIZE = 100000;
private static void db(final String s) {
if (Inline.DEBUG) {
System.out.println(s);
}
}
/**
* Constructor. By default the first-level calls are only inlined one level
* deep, there is no max size on methods to inline, and methods that may
* throw exceptions are inlined.
*
* @param maxCodeSize
* The maximum number of instructions a method can grow to.
*/
public Inline(final InlineContext context, final int maxCodeSize) {
this.context = context;
this.maxCodeSize = maxCodeSize;
this.maxCallDepth = 1;
this.inlineExceptions = true;
editors = new HashMap();
}
/**
* Sets the maximum of size of a method that will be inlined. No method
* larger than this will be inlined.
*/
public void setMaxInlineSize(final int maxInlineSize) {
}
/**
* Sets the maximum number of nested calls we inline.
*/
public void setMaxCallDepth(final int maxCallDepth) {
this.maxCallDepth = maxCallDepth;
}
/**
* Sets whether or not methods that may throw exceptions (that is, have a
* non-empty "throws" declaration) are inlined.
*/
public void setInlineExceptions(final boolean inlineExceptions) {
this.inlineExceptions = inlineExceptions;
}
/**
* Scans a method and inlines non-virtual method calls according to this
* <tt>Inline</tt>'s metrics.
*/
public void inline(final MethodEditor method) {
// Go through the method and look for calls to inline
StackHeightCounter stackHeight = new StackHeightCounter(method);
List code = method.code();
boolean firstCall = true;
for (int i = 0; i < code.size(); i++) {
final Object o = code.get(i);
if (o instanceof Instruction) {
final Instruction inst = (Instruction) o;
if ((inst.opcodeClass() == Opcode.opcx_invokestatic)
|| (inst.opcodeClass() == Opcode.opcx_invokespecial)) {
final MemberRef callee = (MemberRef) inst.operand();
final Stack callStack = new Stack();
callStack.add(method.memberRef());
Inline.db(" Call: " + inst);
stackHeight.handle(inst);
final int expectedHeight = stackHeight.height();
stackHeight.unhandle(inst);
final int j = i;
i = inline(method, callee, i, callStack, stackHeight,
firstCall);
if (j == i) {
// Call was not inlined, add it to the stack
stackHeight.handle(inst);
Inline.db(" " + i + "." + stackHeight.height() + ") "
+ inst);
}
final int newHeight = stackHeight.height();
// If an exception is thrown as the last thing in a method
// newHeight will equal 0. Let's let this one slide.
Assert.isTrue((newHeight == 0)
|| (newHeight == expectedHeight),
"Inlining did not get the stack heights right: "
+ "Expected " + expectedHeight + ", got "
+ newHeight);
} else {
stackHeight.handle(inst);
Inline.db(" " + i + "." + stackHeight.height() + ") "
+ inst);
}
if (inst.isInvoke()) {
firstCall = false;
}
} else if (o instanceof Label) {
final Label label = (Label) o;
stackHeight.handle(label);
Inline.db(" " + i + "." + stackHeight.height() + ") " + label
+ (label.startsBlock() ? " (starts block)" : ""));
}
}
method.setCode(code);
if (Inline.DEBUG) {
stackHeight = new StackHeightCounter(method);
Inline.db("\nNew Code for " + method.declaringClass().name() + "."
+ method.name() + method.type());
code = method.code();
for (int j = 0; j < code.size(); j++) {
if (code.get(j) instanceof Label) {
final Label label = (Label) code.get(j);
stackHeight.handle(label);
final Iterator tryCatches = method.tryCatches().iterator();
while (tryCatches.hasNext()) {
final TryCatch tryCatch = (TryCatch) tryCatches.next();
if (tryCatch.start().equals(label)) {
System.out.println(" Begin protected region");
}
if (tryCatch.end().equals(label)) {
System.out.println(" End protected region");
}
// A Label can both end a protected region and begin
// catch
// block
if (tryCatch.handler().equals(label)) {
System.out.println(" Catch " + tryCatch.type());
}
}
System.out.println(" " + j + "." + stackHeight.height()
+ ") " + label
+ (label.startsBlock() ? " (starts block)" : ""));
} else {
final Instruction inst = (Instruction) code.get(j);
stackHeight.handle(inst);
System.out.println(" " + j + "." + stackHeight.height()
+ ") " + code.get(j));
}
}
// Print try-catch information
final Iterator tryCatches = method.tryCatches().iterator();
System.out.println("Exception information:");
while (tryCatches.hasNext()) {
final TryCatch tryCatch = (TryCatch) tryCatches.next();
System.out.println(" " + tryCatch);
}
System.out.println("");
}
}
/**
* Helper method that does most of the work. By calling this method
* recursively, we can inline more than one call deep.
*
* @param caller
* The original caller that got all of this started. Into this
* method we insert the code.
* @param callee
* The method to be inlined
* @param index
* Where in caller we insert inlined code
* @param callStack
* A stack of <tt>MemberRef</tt>s that represent the inlined
* methods that call other methods. It is used to detect
* recursion.
*
* @return The index into the caller's code array of the instruction
* following the last inlinined instruction. Start looking here
* after inline returns.
*/
private int inline(final MethodEditor caller, final MemberRef callee,
int index, final Stack callStack,
final StackHeightCounter stackHeight, boolean firstCall) {
Instruction newInst = null;
// Do we ignore the method being inlined?
if (context.ignoreMethod(callee)) {
Inline.db(" Can't inline " + callee + ": it's ignored");
return (index++);
}
// Can we inline this method
if (callStack.size() > maxCallDepth) {
Inline.db(" Can't inline " + callee + ": max call depth ("
+ maxCallDepth + ") reached");
return (index++);
} else if (callStack.contains(callee)) {
Inline.db(" Can't inline recursive call to " + callee);
return (index++);
}
// Make sure we're not inlining the static-ized version of a
// method in the call stack.
String name = callee.name();
final int b = name.indexOf("$$BLOAT");
if (b != -1) {
name = name.substring(0, b);
// Get rid of first parameter
final Type[] oldParams = callee.type().paramTypes();
final StringBuffer sb = new StringBuffer("(");
for (int p = 1; p < oldParams.length; p++) {
sb.append(oldParams[p].descriptor());
}
sb.append(")" + callee.type().returnType());
final Type newType = Type.getType(sb.toString());
final MemberRef unBloated = new MemberRef(callee.declaringClass(),
new NameAndType(name, newType));
if (callStack.contains(unBloated)) {
Inline.db(" Can't inline recursive call to " + callee);
return (index++);
}
}
final List code = caller.code();
if (code.size() > maxCodeSize) {
Inline.db(" Can't inline " + callee + ": max code size ("
+ maxCodeSize + ") reached");
return (index++);
}
MethodEditor calleeMethod = null;
try {
calleeMethod = context.editMethod(callee);
} catch (final NoSuchMethodException ex) {
System.err.println("Couldn't find method " + callee);
ex.printStackTrace(System.err);
System.exit(1);
}
if (calleeMethod.isNative()) {
Inline.db(" Can't inline " + callee + ": it's a native method");
return (index++);
}
if (calleeMethod.isSynchronized()) {
Inline.db(" Can't inline " + callee + ": it's synchronized");
return (index++);
}
if (!inlineExceptions
&& (calleeMethod.methodInfo().exceptionTypes().length > 0)) {
Inline.db(" Can't inline " + callee
+ ": it may throw an exception");
return (index++);
}
if (calleeMethod.code().size() > Inline.CALLEE_SIZE) {
Inline.db(" Can't inline " + callee + ": it's too big");
return (index++);
}
// Methods that catch exceptions are problematic. When an
// exception is thrown, it clears the stack. Ordinarily this
// isn't a problem. However, now the stack of the caller is
// cleared in addition to the stack of the callee. This is bad.
// The callee might catch the exception and deal with it.
// However, the stack has still been cleared. This really messes
// things up for the code that appears after the inlined method.
// So, if a method catches an exception, we can only inline it if
// the stack contains nothing but the parameters to the call.
if (calleeMethod.tryCatches().size() > 0) {
if (stackHeight.height() > callee.type().stackHeight()) {
Inline.db(" Can't inline " + callee
+ ": It catches an exception and there's stuff on the "
+ "stack");
return (index++);
}
}
// If the callee method catches any of the same exceptions as the
// protected region that we are currently in, then we can't inline
// the method.
final Iterator tryCatches0 = calleeMethod.tryCatches().iterator();
while (tryCatches0.hasNext()) {
final TryCatch tc1 = (TryCatch) tryCatches0.next();
final Iterator iter = stackHeight.tryCatches().iterator();
while (iter.hasNext()) {
final TryCatch tc2 = (TryCatch) iter.next();
final Type t1 = tc1.type();
final Type t2 = tc2.type();
if ((t1 != null) && (t2 != null) && t1.equals(t2)) {
Inline.db(" Can't inline " + callee
+ ": It catches the same type "
+ tc1.type().className()
+ " as the current protected region");
return (index++);
}
}
}
// If the caller is a constructor and this is the first
// invokespecial we've seen in this callee method, we can inline
// calls to the constructors of superclasses and other
// constructors in this method. So, if this IS the first call in
// a method which IS a constructor, we can inline it.
if (calleeMethod.isConstructor()
&& (!firstCall || !caller.isConstructor())) {
Inline.db(" Can't inline " + callee
+ ": It calls a normal constructor");
return (index++);
}
// Local variables are problematic. We cannot simply map the
// callee's variables to new variables in the caller because we
// cannot precisely determine the width of the variable the first
// time we see it. (For instance, Nate's generated code might use
// a local variable as a non-wide initially and then use it as a
// wide later.)
// Okay, we going to inline. Remove the calling instruction.
final Instruction call = (Instruction) code.remove(index--);
Inline.db(" Removing call: " + call);
Assert.isTrue((call.opcodeClass() == Opcode.opcx_invokestatic)
|| (call.opcodeClass() == Opcode.opcx_invokespecial),
"Removing the wrong call instruction:" + call);
callStack.push(callee);
Inline
.db(" Inlining call (" + callStack.size() + ") to "
+ callee.declaringClass() + "." + callee.name()
+ callee.type());
context.getInlineStats().noteInlined();
// First we have to pop the arguments off the stack and store them
// into the local variables. Remember that wide types occupy two
// local variables.
final Mapper mapper = new Mapper(caller);
Type[] paramTypes = callee.type().indexedParamTypes();
if (!calleeMethod.isStatic()) {
// Constructors (and any other special methods we're inlining)
// have a "this" pointer where static methods do not.
final Type[] newParams = new Type[paramTypes.length + 1];
newParams[0] = callee.declaringClass();
for (int i = 0; i < paramTypes.length; i++) {
newParams[i + 1] = paramTypes[i];
}
paramTypes = newParams;
}
final LocalVariable[] params = new LocalVariable[paramTypes.length];
Inline.db(" Indexed params:");
for (int i = 0; i < params.length; i++) {
params[i] = calleeMethod.paramAt(i);
Inline.db(" " + i + ": " + params[i]
+ (params[i] != null ? " " + params[i].type() + " " : ""));
}
for (int i = params.length - 1; i >= 0; i--) {
// Map the local variables containing the arguments to new
// local variables.
final LocalVariable param = params[i];
final Type paramType = params[i].type();
if (param.type() == null) {
continue;
}
Inline.db(" Param " + i + ": " + param + " of type " + paramType);
final LocalVariable newVar = mapper.map(param, paramType);
int opcode;
if (paramType.isReference()) {
opcode = Opcode.opcx_astore;
} else {
switch (paramType.typeCode()) {
case Type.BOOLEAN_CODE:
case Type.BYTE_CODE:
case Type.CHARACTER_CODE:
case Type.SHORT_CODE:
opcode = Opcode.opcx_istore;
break;
case Type.DOUBLE_CODE:
opcode = Opcode.opcx_dstore;
break;
case Type.LONG_CODE:
opcode = Opcode.opcx_lstore;
break;
case Type.FLOAT_CODE:
opcode = Opcode.opcx_fstore;
break;
case Type.INTEGER_CODE:
opcode = Opcode.opcx_istore;
break;
default:
throw new IllegalArgumentException("What's a " + paramType
+ "doing as a method " + "parameter");
}
}
newInst = new Instruction(opcode, newVar);
code.add(++index, newInst);
stackHeight.handle(newInst);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ newInst);
}
// Before we mess with the code, we have to patch up the try-catch
// information from the inlined method to the caller method.
final Iterator tryCatches = calleeMethod.tryCatches().iterator();
while (tryCatches.hasNext()) {
final TryCatch tryCatch = (TryCatch) tryCatches.next();
final Label start = mapper.map(tryCatch.start());
final Label end = mapper.map(tryCatch.end());
final Label handler = mapper.map(tryCatch.handler());
final TryCatch newTryCatch = new TryCatch(start, end, handler,
tryCatch.type());
caller.addTryCatch(newTryCatch);
// db("Try-catch");
// db(" Before: " + tryCatch.start() + "\t" + tryCatch.end() +
// "\t" + tryCatch.handler());
// db(" After: " + newTryCatch.start() + "\t" + newTryCatch.end()
// + "\t" + newTryCatch.handler());
}
// Go through the code in the callee method and inline it. Handle
// any calls by making a recursive call to this method. Copy each
// instruction to the method in which it is being inlined. Along
// the way convert references to local variables to their mapped
// values. Also remove return instructions. Replace them with
// loads as necessary.
final List inlineCode = calleeMethod.code();
// We don't want to introduce a new end label because it confuses
// BLOAT during CFG construction. We designate the end label as
// starting a new block in hopes that it will solve problems with
// CFG construction.
final Object last = inlineCode.get(inlineCode.size() - 1);
boolean addEndLabel;
Label endLabel;
if (last instanceof Label) {
endLabel = mapper.map((Label) last);
addEndLabel = false;
} else {
endLabel = caller.newLabel();
addEndLabel = true;
}
endLabel.setStartsBlock(true);
firstCall = true;
for (int j = 0; j < inlineCode.size(); j++) {
final Object o = inlineCode.get(j);
if (o instanceof Label) {
final Label label = (Label) o;
final Label newLabel = mapper.map(label);
code.add(++index, newLabel);
stackHeight.handle(newLabel);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ newLabel
+ (newLabel.startsBlock() ? " (starts block)" : ""));
continue;
}
Assert.isTrue(o instanceof Instruction, "What is a " + o
+ " doing in the instruction stream?");
final Instruction inst = (Instruction) inlineCode.get(j);
Object operand = inst.operand();
final int opcode = inst.opcodeClass();
if (operand instanceof LocalVariable) {
// Map local variable in the callee method to local
// variables in the caller method.
final LocalVariable local = mapper.map((LocalVariable) operand,
(inst.category() == 2 ? true : false));
operand = local;
} else if (operand instanceof Label) {
// Map labels in the callee method to labels in the caller
// method.
final Label label = mapper.map((Label) operand);
operand = label;
} else if (operand instanceof IncOperand) {
// Map the local being incremented
final IncOperand inc = (IncOperand) operand;
final LocalVariable newLocal = mapper.map(inc.var(),
Type.INTEGER);
operand = new IncOperand(newLocal, inc.incr());
} else if (operand instanceof Switch) {
// We have to patch up the Labels involved with the Switch
final Switch oldSwitch = (Switch) operand;
final Label newDefault = mapper.map(oldSwitch.defaultTarget());
final Label[] oldTargets = oldSwitch.targets();
final Label[] newTargets = new Label[oldTargets.length];
for (int i = 0; i < newTargets.length; i++) {
final Label newTarget = mapper.map(oldTargets[i]);
newTargets[i] = newTarget;
}
operand = new Switch(newDefault, newTargets, oldSwitch.values());
}
if (inst.isReturn()) {
// Insert a jump to the end of the inlined method. Any
// return value will be on top of the stack. This is where
// we want it.
newInst = new Instruction(Opcode.opcx_goto, endLabel);
code.add(++index, newInst);
stackHeight.handle(newInst);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ newInst);
} else if ((inst.opcodeClass() == Opcode.opcx_invokestatic)
|| (inst.opcodeClass() == Opcode.opcx_invokespecial)) {
// Make a recursive call. Note that this must be done after
// we add the call instruction above. But we only want to
// visit the instruction with the stackHeight if the call was
// not inlined.
newInst = new Instruction(opcode, operand);
code.add(++index, newInst);
stackHeight.handle(newInst);
final int expectedHeight = stackHeight.height();
stackHeight.unhandle(newInst);
final MemberRef nestedCall = (MemberRef) inst.operand();
final int oldIndex = index;
index = inline(caller, nestedCall, index, callStack,
stackHeight, firstCall);
if (index == oldIndex) {
stackHeight.handle(newInst);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ newInst);
}
final int newHeight = stackHeight.height();
Assert.isTrue(
(newHeight == 0) || (newHeight == expectedHeight),
"Inlining did not get the stack heights right: "
+ "Expected " + expectedHeight + ", got "
+ newHeight);
} else {
// Add the instruction
newInst = new Instruction(opcode, operand);
code.add(++index, newInst);
stackHeight.handle(newInst);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ newInst);
}
// We want to do this after we've made any recursive calls to
// inline.
if (inst.isInvoke()) {
firstCall = false;
}
}
if (addEndLabel) {
// Done inlining. Add end label.
code.add(++index, endLabel);
stackHeight.handle(endLabel);
Inline.db(" " + index + "." + stackHeight.height() + "> "
+ endLabel
+ (endLabel.startsBlock() ? " (starts block)" : ""));
}
caller.setDirty(true);
callStack.pop();
return (index);
}
}
/**
* Utility class for mapping local variables and labels. Note that when mapping
* local variables we have to be careful. We can't assume that a variable will
* retain its "wideness" throughout the method. I learned this one the hard way.
* So, we have to keep a constant difference between the mapped variables.
*/
class Mapper {
private Map varsMap; // Maps local variables
private Map labelsMap; // Maps labels
private MethodEditor method; // Method into which things are mapped
private int offset; // Start numbering new locals here
private static void db(final String s) {
if (Inline.DEBUG) {
System.out.println(s);
}
}
/**
* Constructor.
*/
public Mapper(final MethodEditor method) {
this.method = method;
varsMap = new HashMap();
labelsMap = new HashMap();
offset = method.maxLocals() + 1;
}
public Label map(final Label label) {
Label newLabel = (Label) labelsMap.get(label);
if (newLabel == null) {
newLabel = this.method.newLabel();
newLabel.setStartsBlock(label.startsBlock());
labelsMap.put(label, newLabel);
Mapper.db(" " + label + " -> " + newLabel
+ (newLabel.startsBlock() ? " (starts block)" : ""));
}
return (newLabel);
}
public LocalVariable map(final LocalVariable var, final Type type) {
LocalVariable newVar = (LocalVariable) varsMap.get(var);
if (newVar == null) {
newVar = this.method.localAt(var.index() + offset);
// newVar = this.method.newLocal(type);
varsMap.put(var, newVar);
Mapper.db(" " + var + " (" + var.index() + ") -> " + newVar
+ "(" + var.index() + "+" + offset + ")"
+ (type.isWide() ? " (" + type + ")" : ""));
}
return (newVar);
}
public LocalVariable map(final LocalVariable var, final boolean isWide) {
LocalVariable newVar = (LocalVariable) varsMap.get(var);
if (newVar == null) {
newVar = this.method.localAt(var.index() + offset);
// newVar = this.method.newLocal(isWide);
varsMap.put(var, newVar);
Mapper.db(" " + var + " (" + var.index() + ") -> " + newVar
+ "(" + var.index() + "+" + offset + ")"
+ (isWide ? " (wide)" : ""));
}
return (newVar);
}
}