blob: 3a79066ccbcec6b6a4f722b44be3567555c1cfb9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* $Id$
*/
package org.apache.xalan.xsltc.compiler.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.BranchHandle;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.DLOAD;
import org.apache.bcel.generic.DSTORE;
import org.apache.bcel.generic.FLOAD;
import org.apache.bcel.generic.FSTORE;
import org.apache.bcel.generic.GETFIELD;
import org.apache.bcel.generic.GOTO;
import org.apache.bcel.generic.ICONST;
import org.apache.bcel.generic.IfInstruction;
import org.apache.bcel.generic.ILOAD;
import org.apache.bcel.generic.IndexedInstruction;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.ISTORE;
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.InstructionTargeter;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.LLOAD;
import org.apache.bcel.generic.LSTORE;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.NEW;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.RET;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.generic.Type;
import org.apache.xalan.xsltc.compiler.Pattern;
import org.apache.xalan.xsltc.compiler.XSLTC;
/**
* @author Jacek Ambroziak
* @author Santiago Pericas-Geertsen
*/
public class MethodGenerator extends MethodGen
implements org.apache.xalan.xsltc.compiler.Constants {
protected static final int INVALID_INDEX = -1;
private static final String START_ELEMENT_SIG
= "(" + STRING_SIG + ")V";
private static final String END_ELEMENT_SIG
= START_ELEMENT_SIG;
private InstructionList _mapTypeSub;
private static final int DOM_INDEX = 1;
private static final int ITERATOR_INDEX = 2;
private static final int HANDLER_INDEX = 3;
private static final int MAX_METHOD_SIZE = 65535;
private static final int MAX_BRANCH_TARGET_OFFSET = 32767;
private static final int MIN_BRANCH_TARGET_OFFSET = -32768;
private static final int TARGET_METHOD_SIZE = 60000;
private static final int MINIMUM_OUTLINEABLE_CHUNK_SIZE = 1000;
private Instruction _iloadCurrent;
private Instruction _istoreCurrent;
private final Instruction _astoreHandler;
private final Instruction _aloadHandler;
private final Instruction _astoreIterator;
private final Instruction _aloadIterator;
private final Instruction _aloadDom;
private final Instruction _astoreDom;
private final Instruction _startElement;
private final Instruction _endElement;
private final Instruction _startDocument;
private final Instruction _endDocument;
private final Instruction _attribute;
private final Instruction _uniqueAttribute;
private final Instruction _namespace;
private final Instruction _setStartNode;
private final Instruction _reset;
private final Instruction _nextNode;
private SlotAllocator _slotAllocator;
private boolean _allocatorInit = false;
private LocalVariableRegistry _localVariableRegistry;
/**
* A mapping between patterns and instruction lists used by
* test sequences to avoid compiling the same pattern multiple
* times. Note that patterns whose kernels are "*", "node()"
* and "@*" can between shared by test sequences.
*/
private Hashtable _preCompiled = new Hashtable();
public MethodGenerator(int access_flags, Type return_type,
Type[] arg_types, String[] arg_names,
String method_name, String class_name,
InstructionList il, ConstantPoolGen cpg) {
super(access_flags, return_type, arg_types, arg_names, method_name,
class_name, il, cpg);
_astoreHandler = new ASTORE(HANDLER_INDEX);
_aloadHandler = new ALOAD(HANDLER_INDEX);
_astoreIterator = new ASTORE(ITERATOR_INDEX);
_aloadIterator = new ALOAD(ITERATOR_INDEX);
_aloadDom = new ALOAD(DOM_INDEX);
_astoreDom = new ASTORE(DOM_INDEX);
final int startElement =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"startElement",
START_ELEMENT_SIG);
_startElement = new INVOKEINTERFACE(startElement, 2);
final int endElement =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"endElement",
END_ELEMENT_SIG);
_endElement = new INVOKEINTERFACE(endElement, 2);
final int attribute =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"addAttribute",
"("
+ STRING_SIG
+ STRING_SIG
+ ")V");
_attribute = new INVOKEINTERFACE(attribute, 3);
final int uniqueAttribute =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"addUniqueAttribute",
"("
+ STRING_SIG
+ STRING_SIG
+ "I)V");
_uniqueAttribute = new INVOKEINTERFACE(uniqueAttribute, 4);
final int namespace =
cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"namespaceAfterStartElement",
"("
+ STRING_SIG
+ STRING_SIG
+ ")V");
_namespace = new INVOKEINTERFACE(namespace, 3);
int index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"startDocument",
"()V");
_startDocument = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
"endDocument",
"()V");
_endDocument = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(NODE_ITERATOR,
SET_START_NODE,
SET_START_NODE_SIG);
_setStartNode = new INVOKEINTERFACE(index, 2);
index = cpg.addInterfaceMethodref(NODE_ITERATOR,
"reset", "()"+NODE_ITERATOR_SIG);
_reset = new INVOKEINTERFACE(index, 1);
index = cpg.addInterfaceMethodref(NODE_ITERATOR, NEXT, NEXT_SIG);
_nextNode = new INVOKEINTERFACE(index, 1);
_slotAllocator = new SlotAllocator();
_slotAllocator.initialize(getLocalVariableRegistry().getLocals(false));
_allocatorInit = true;
}
/**
* Allocates a local variable. If the slot allocator has already been
* initialized, then call addLocalVariable2() so that the new variable
* is known to the allocator. Failing to do this may cause the allocator
* to return a slot that is already in use.
*/
public LocalVariableGen addLocalVariable(String name, Type type,
InstructionHandle start,
InstructionHandle end)
{
LocalVariableGen lvg;
if (_allocatorInit) {
lvg = addLocalVariable2(name, type, start);
} else {
lvg = super.addLocalVariable(name, type, start, end);
getLocalVariableRegistry().registerLocalVariable(lvg);
}
return lvg;
}
public LocalVariableGen addLocalVariable2(String name, Type type,
InstructionHandle start)
{
LocalVariableGen lvg = super.addLocalVariable(name, type,
_slotAllocator.allocateSlot(type),
start, null);
getLocalVariableRegistry().registerLocalVariable(lvg);
return lvg;
}
private LocalVariableRegistry getLocalVariableRegistry() {
if (_localVariableRegistry == null) {
_localVariableRegistry = new LocalVariableRegistry();
}
return _localVariableRegistry;
}
/**
* Keeps track of all local variables used in the method.
* <p>The
* {@link MethodGen#addLocalVariable(String,Type,InstructionHandle,InstructionHandle)}</code>
* and
* {@link MethodGen#addLocalVariable(String,Type,int,InstructionHandle,InstructionHandle)}</code>
* methods of {@link MethodGen} will only keep track of
* {@link LocalVariableGen} object until it'ss removed by a call to
* {@link MethodGen#removeLocalVariable(LocalVariableGen)}.</p>
* <p>In order to support efficient copying of local variables to outlined
* methods by
* {@link #outline(InstructionHandle,InstructionHandle,String,ClassGenerator)},
* this class keeps track of all local variables defined by the method.</p>
*/
protected class LocalVariableRegistry {
/**
* <p>A <code>java.lang.ArrayList</code> of all
* {@link LocalVariableGen}s created for this method, indexed by the
* slot number of the local variable. The JVM stack frame of local
* variables is divided into "slots". A single slot can be used to
* store more than one variable in a method, without regard to type, so
* long as the byte code keeps the ranges of the two disjoint.</p>
* <p>If only one registration of use of a particular slot occurs, the
* corresponding entry of <code>_variables</code> contains the
* <code>LocalVariableGen</code>; if more than one occurs, the
* corresponding entry contains all such <code>LocalVariableGen</code>s
* registered for the same slot; and if none occurs, the entry will be
* <code>null</code>.
*/
protected ArrayList _variables = new ArrayList();
/**
* Maps a name to a {@link LocalVariableGen}
*/
protected HashMap _nameToLVGMap = new HashMap();
/**
* Registers a {@link org.apache.bcel.generic.LocalVariableGen}
* for this method.
* <p><b>Preconditions:</b>
* <ul>
* <li>The range of instructions for <code>lvg</code> does not
* overlap with the range of instructions for any
* <code>LocalVariableGen</code> with the same slot index previously
* registered for this method. <b><em>(Unchecked.)</em></b></li>
* </ul></p>
* @param lvg The variable to be registered
*/
protected void registerLocalVariable(LocalVariableGen lvg) {
int slot = lvg.getIndex();
int registrySize = _variables.size();
// If the LocalVariableGen uses a slot index beyond any previously
// encountered, expand the _variables, padding with intervening null
// entries as required.
if (slot >= registrySize) {
for (int i = registrySize; i < slot; i++) {
_variables.add(null);
}
_variables.add(lvg);
} else {
// If the LocalVariableGen reuses a slot, make sure the entry
// in _variables contains an ArrayList and add the newly
// registered LocalVariableGen to the list. If the entry in
// _variables just contains null padding, store the
// LocalVariableGen directly.
Object localsInSlot = _variables.get(slot);
if (localsInSlot != null) {
if (localsInSlot instanceof LocalVariableGen) {
ArrayList listOfLocalsInSlot = new ArrayList();
listOfLocalsInSlot.add(localsInSlot);
listOfLocalsInSlot.add(lvg);
_variables.set(slot, listOfLocalsInSlot);
} else {
((ArrayList) localsInSlot).add(lvg);
}
} else {
_variables.set(slot, lvg);
}
}
registerByName(lvg);
}
/**
* <p>Find which {@link LocalVariableGen}, if any, is registered for a
* particular JVM local stack frame slot at a particular position in the
* byte code for the method.</p>
* <p><b>Preconditions:</b>
* <ul>
* <li>The {@link InstructionList#setPositions()} has been called for
* the {@link InstructionList} associated with this
* {@link MethodGenerator}.</li>
* </ul></p>
* @param slot the JVM local stack frame slot number
* @param offset the position in the byte code
* @return the <code>LocalVariableGen</code> for the local variable
* stored in the relevant slot at the relevant offset; <code>null</code>
* if there is none.
*/
protected LocalVariableGen lookupRegisteredLocalVariable(int slot,
int offset) {
Object localsInSlot = (_variables != null) ? _variables.get(slot)
: null;
// If this slot index was never used, _variables.get will return
// null; if it was used once, it will return the LocalVariableGen;
// more than once it will return an ArrayList of all the
// LocalVariableGens for variables stored in that slot. For each
// LocalVariableGen, check whether its range includes the
// specified offset, and return the first such encountered.
if (localsInSlot != null) {
if (localsInSlot instanceof LocalVariableGen) {
LocalVariableGen lvg = (LocalVariableGen)localsInSlot;
if (offsetInLocalVariableGenRange(lvg, offset)) {
return lvg;
}
} else {
ArrayList listOfLocalsInSlot = (ArrayList) localsInSlot;
int size = listOfLocalsInSlot.size();
for (int i = 0; i < size; i++) {
LocalVariableGen lvg =
(LocalVariableGen)listOfLocalsInSlot.get(i);
if (offsetInLocalVariableGenRange(lvg, offset)) {
return lvg;
}
}
}
}
// No local variable stored in the specified slot at the specified
return null;
}
/**
* <p>Set up a mapping of the name of the specified
* {@link LocalVariableGen} object to the <code>LocalVariableGen</code>
* itself.</p>
* <p>This is a bit of a hack. XSLTC is relying on the fact that the
* name that is being looked up won't be duplicated, which isn't
* guaranteed. It replaces code which used to call
* {@link MethodGen#getLocalVariables()} and looped through the
* <code>LocalVariableGen</code> objects it contained to find the one
* with the specified name. However, <code>getLocalVariables()</code>
* has the side effect of setting the start and end for any
* <code>LocalVariableGen</code> which did not already have them
* set, which causes problems for outlining..</p>
* <p>See also {@link #lookUpByName(String)} and
* {@link #removeByNameTracking(LocalVariableGen)}</P
* @param lvg a <code>LocalVariableGen</code>
*/
protected void registerByName(LocalVariableGen lvg) {
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());
if (duplicateNameEntry == null) {
_nameToLVGMap.put(lvg.getName(), lvg);
} else {
ArrayList sameNameList;
if (duplicateNameEntry instanceof ArrayList) {
sameNameList = (ArrayList) duplicateNameEntry;
sameNameList.add(lvg);
} else {
sameNameList = new ArrayList();
sameNameList.add(duplicateNameEntry);
sameNameList.add(lvg);
}
_nameToLVGMap.put(lvg.getName(), sameNameList);
}
}
/**
* Remove the mapping from the name of the specified
* {@link LocalVariableGen} to itself.
* See also {@link #registerByName(LocalVariableGen)} and
* {@link #lookUpByName(String)}
* @param lvg a <code>LocalVariableGen</code>
*/
protected void removeByNameTracking(LocalVariableGen lvg) {
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());
if (duplicateNameEntry instanceof ArrayList) {
ArrayList sameNameList = (ArrayList) duplicateNameEntry;
for (int i = 0; i < sameNameList.size(); i++) {
if (sameNameList.get(i) == lvg) {
sameNameList.remove(i);
break;
}
}
} else {
_nameToLVGMap.remove(lvg);
}
}
/**
* <p>Given the name of a variable, finds a {@link LocalVariableGen}
* corresponding to it.</p>
* <p>See also {@link #registerByName(LocalVariableGen)} and
* {@link #removeByNameTracking(LocalVariableGen)}</p>
* @param name
* @return
*/
protected LocalVariableGen lookUpByName(String name) {
LocalVariableGen lvg = null;
Object duplicateNameEntry = _nameToLVGMap.get(name);
if (duplicateNameEntry instanceof ArrayList) {
ArrayList sameNameList = (ArrayList) duplicateNameEntry;
for (int i = 0; i < sameNameList.size(); i++) {
lvg = (LocalVariableGen)sameNameList.get(i);
if (lvg.getName() == name) {
break;
}
}
} else {
lvg = (LocalVariableGen) duplicateNameEntry;
}
return lvg;
}
/**
* <p>Gets all {@link LocalVariableGen} objects for this method.</p>
* <p>When the <code>includeRemoved</code> argument has the value
* <code>false</code>, this method replaces uses of
* {@link MethodGen#getLocalVariables()} which has
* a side-effect of setting the start and end range for any
* <code>LocalVariableGen</code> if either was <code>null</code>. That
* side-effect causes problems for outlining of code in XSLTC.
* @param includeRemoved Specifies whether all local variables ever
* declared should be returned (<code>true</code>) or only those not
* removed (<code>false</code>)
* @return an array of <code>LocalVariableGen</code> containing all the
* local variables
*/
protected LocalVariableGen[] getLocals(boolean includeRemoved) {
LocalVariableGen[] locals = null;
ArrayList allVarsEverDeclared = new ArrayList();
if (includeRemoved) {
int slotCount = allVarsEverDeclared.size();
for (int i = 0; i < slotCount; i++) {
Object slotEntries = _variables.get(i);
if (slotEntries != null) {
if (slotEntries instanceof ArrayList) {
ArrayList slotList = (ArrayList) slotEntries;
for (int j = 0; j < slotList.size(); j++) {
allVarsEverDeclared.add(slotList.get(i));
}
} else {
allVarsEverDeclared.add(slotEntries);
}
}
}
} else {
Iterator nameVarsPairsIter = _nameToLVGMap.entrySet().iterator();
while (nameVarsPairsIter.hasNext()) {
Map.Entry nameVarsPair =
(Map.Entry) nameVarsPairsIter.next();
Object vars = nameVarsPair.getValue();
if (vars != null) {
if (vars instanceof ArrayList) {
ArrayList varsList = (ArrayList) vars;
for (int i = 0; i < varsList.size(); i++) {
allVarsEverDeclared.add(varsList.get(i));
}
} else {
allVarsEverDeclared.add(vars);
}
}
}
}
locals = new LocalVariableGen[allVarsEverDeclared.size()];
allVarsEverDeclared.toArray(locals);
return locals;
}
}
/**
* Determines whether a particular variable is in use at a particular offset
* in the byte code for this method.
* <p><b>Preconditions:</b>
* <ul>
* <li>The {@link InstructionList#setPositions()} has been called for the
* {@link InstructionList} associated with this {@link MethodGenerator}.
* </li></ul></p>
* @param lvg the {@link LocalVariableGen} for the variable
* @param offset the position in the byte code
* @return <code>true</code> if and only if the specified variable is in
* use at the particular byte code offset.
*/
boolean offsetInLocalVariableGenRange(LocalVariableGen lvg, int offset) {
InstructionHandle lvgStart = lvg.getStart();
InstructionHandle lvgEnd = lvg.getEnd();
// If no start handle is recorded for the LocalVariableGen, it is
// assumed to be in use from the beginning of the method.
if (lvgStart == null) {
lvgStart = getInstructionList().getStart();
}
// If no end handle is recorded for the LocalVariableGen, it is assumed
// to be in use to the end of the method.
if (lvgEnd == null) {
lvgEnd = getInstructionList().getEnd();
}
// Does the range of the instruction include the specified offset?
// Note that the InstructionHandle.getPosition method returns the
// offset of the beginning of an instruction. A LocalVariableGen's
// range includes the end instruction itself, so that instruction's
// length must be taken into consideration in computing whether the
// varible is in range at a particular offset.
return ((lvgStart.getPosition() <= offset)
&& (lvgEnd.getPosition()
+ lvgEnd.getInstruction().getLength() >= offset));
}
public void removeLocalVariable(LocalVariableGen lvg) {
_slotAllocator.releaseSlot(lvg);
getLocalVariableRegistry().removeByNameTracking(lvg);
super.removeLocalVariable(lvg);
}
public Instruction loadDOM() {
return _aloadDom;
}
public Instruction storeDOM() {
return _astoreDom;
}
public Instruction storeHandler() {
return _astoreHandler;
}
public Instruction loadHandler() {
return _aloadHandler;
}
public Instruction storeIterator() {
return _astoreIterator;
}
public Instruction loadIterator() {
return _aloadIterator;
}
public final Instruction setStartNode() {
return _setStartNode;
}
public final Instruction reset() {
return _reset;
}
public final Instruction nextNode() {
return _nextNode;
}
public final Instruction startElement() {
return _startElement;
}
public final Instruction endElement() {
return _endElement;
}
public final Instruction startDocument() {
return _startDocument;
}
public final Instruction endDocument() {
return _endDocument;
}
public final Instruction attribute() {
return _attribute;
}
public final Instruction uniqueAttribute() {
return _uniqueAttribute;
}
public final Instruction namespace() {
return _namespace;
}
public Instruction loadCurrentNode() {
if (_iloadCurrent == null) {
int idx = getLocalIndex("current");
if (idx > 0)
_iloadCurrent = new ILOAD(idx);
else
_iloadCurrent = new ICONST(0);
}
return _iloadCurrent;
}
public Instruction storeCurrentNode() {
return _istoreCurrent != null
? _istoreCurrent
: (_istoreCurrent = new ISTORE(getLocalIndex("current")));
}
/** by default context node is the same as current node. MK437 */
public Instruction loadContextNode() {
return loadCurrentNode();
}
public Instruction storeContextNode() {
return storeCurrentNode();
}
public int getLocalIndex(String name) {
return getLocalVariable(name).getIndex();
}
public LocalVariableGen getLocalVariable(String name) {
return getLocalVariableRegistry().lookUpByName(name);
}
public void setMaxLocals() {
// Get the current number of local variable slots
int maxLocals = super.getMaxLocals();
// Get numer of actual variables
final LocalVariableGen[] localVars = super.getLocalVariables();
if (localVars != null) {
if (localVars.length > maxLocals)
maxLocals = localVars.length;
}
// We want at least 5 local variable slots (for parameters)
if (maxLocals < 5) maxLocals = 5;
super.setMaxLocals(maxLocals);
}
/**
* Add a pre-compiled pattern to this mode.
*/
public void addInstructionList(Pattern pattern, InstructionList ilist) {
_preCompiled.put(pattern, ilist);
}
/**
* Get the instruction list for a pre-compiled pattern. Used by
* test sequences to avoid compiling patterns more than once.
*/
public InstructionList getInstructionList(Pattern pattern) {
return (InstructionList) _preCompiled.get(pattern);
}
/**
* Used to keep track of an outlineable chunk of instructions in the
* current method. See {@link OutlineableChunkStart} and
* {@link OutlineableChunkEnd} for more information.
*/
private static class Chunk implements Comparable {
/**
* {@link InstructionHandle} of the first instruction in the outlineable
* chunk.
*/
private InstructionHandle m_start;
/**
* {@link org.apache.bcel.generic.InstructionHandle} of the first
* instruction in the outlineable chunk.
*/
private InstructionHandle m_end;
/**
* Number of bytes in the instructions contained in this outlineable
* chunk.
*/
private int m_size;
/**
* <p>Constructor for an outlineable {@link MethodGenerator.Chunk}.</p>
* <p><b>Preconditions:</b>
* <ul>
* <li>The {@link InstructionList#setPositions()} has been called for
* the {@link InstructionList} associated with this
* {@link MethodGenerator}.</li>
* </ul></p>
* @param start The {@link InstructionHandle} of the first
* instruction in the outlineable chunk.
* @param end The {@link InstructionHandle} of the last
* instruction in the outlineable chunk.
*/
Chunk(InstructionHandle start, InstructionHandle end) {
m_start = start;
m_end = end;
m_size = end.getPosition() - start.getPosition();
}
/**
* Determines whether this outlineable {@link MethodGenerator.Chunk} is
* followed immediately by the argument
* <code>MethodGenerator.Chunk</code>, with no other intervening
* instructions, including {@link OutlineableChunkStart} or
* {@link OutlineableChunkEnd} instructions.
* @param neighbour an outlineable {@link MethodGenerator.Chunk}
* @return <code>true</code> if and only if the argument chunk
* immediately follows <code>this</code> chunk
*/
boolean isAdjacentTo(Chunk neighbour) {
return getChunkEnd().getNext() == neighbour.getChunkStart();
}
/**
* Getter method for the start of this {@linke MethodGenerator.Chunk}
* @return the {@link org.apache.bcel.generic.InstructionHandle} of the
* start of this chunk
*/
InstructionHandle getChunkStart() {
return m_start;
}
/**
* Getter method for the end of this {@link MethodGenerator.Chunk}
* @return the {@link InstructionHandle} of the start of this chunk
*/
InstructionHandle getChunkEnd() {
return m_end;
}
/**
* The size of this {@link MethodGenerator.Chunk}
* @return the number of bytes in the byte code represented by this
* chunk.
*/
int getChunkSize() {
return m_size;
}
/**
* Implements the <code>java.util.Comparable.compareTo(Object)</code>
* method.
* @return
* <ul>
* <li>A positive <code>int</code> if the length of <code>this</code>
* chunk in bytes is greater than that of <code>comparand</code></li>
* <li>A negative <code>int</code> if the length of <code>this</code>
* chunk in bytes is less than that of <code>comparand</code></li>
* <li>Zero, otherwise.</li>
* </ul>
*/
public int compareTo(Object comparand) {
return getChunkSize() - ((Chunk)comparand).getChunkSize();
}
}
/**
* Find the outlineable chunks in this method that would be the best choices
* to outline, based on size and position in the method.
* @param classGen The {@link ClassGen} with which the generated methods
* will be associated
* @param totalMethodSize the size of the bytecode in the original method
* @return a <code>java.util.ArrayList</code> containing the
* {@link MethodGenerator.Chunk}s that may be outlined from this method
*/
private ArrayList getCandidateChunks(ClassGenerator classGen,
int totalMethodSize) {
Iterator instructions = getInstructionList().iterator();
ArrayList candidateChunks = new ArrayList();
ArrayList currLevelChunks = new ArrayList();
Stack subChunkStack = new Stack();
boolean openChunkAtCurrLevel = false;
boolean firstInstruction = true;
InstructionHandle currentHandle;
if (m_openChunks != 0) {
String msg =
(new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS))
.toString();
throw new InternalError(msg);
}
// Scan instructions in the method, keeping track of the nesting level
// of outlineable chunks.
//
// currLevelChunks
// keeps track of the child chunks of a chunk. For each chunk,
// there will be a pair of entries: the InstructionHandles for the
// start and for the end of the chunk
// subChunkStack
// a stack containing the partially accumulated currLevelChunks for
// each chunk that's still open at the current position in the
// InstructionList.
// candidateChunks
// the list of chunks which have been accepted as candidates chunks
// for outlining
do {
// Get the next instruction. The loop will perform one extra
// iteration after it reaches the end of the InstructionList, with
// currentHandle set to null.
currentHandle = instructions.hasNext()
? (InstructionHandle) instructions.next()
: null;
Instruction inst =
(currentHandle != null) ? currentHandle.getInstruction()
: null;
// At the first iteration, create a chunk representing all the
// code in the method. This is done just to simplify the logic -
// this chunk can never be outlined because it will be too big.
if (firstInstruction) {
openChunkAtCurrLevel = true;
currLevelChunks.add(currentHandle);
firstInstruction = false;
}
// Found a new chunk
if (inst instanceof OutlineableChunkStart) {
// If last MarkerInstruction encountered was an
// OutlineableChunkStart, this represents the first chunk
// nested within that previous chunk - push the list of chunks
// from the outer level onto the stack
if (openChunkAtCurrLevel) {
subChunkStack.push(currLevelChunks);
currLevelChunks = new ArrayList();
}
openChunkAtCurrLevel = true;
currLevelChunks.add(currentHandle);
// Close off an open chunk
} else if (currentHandle == null
|| inst instanceof OutlineableChunkEnd) {
ArrayList nestedSubChunks = null;
// If the last MarkerInstruction encountered was an
// OutlineableChunkEnd, it means that the current instruction
// marks the end of a chunk that contained child chunks.
// Those children might need to be examined below in case they
// are better candidates for outlining than the current chunk.
if (!openChunkAtCurrLevel) {
nestedSubChunks = currLevelChunks;
currLevelChunks = (ArrayList)subChunkStack.pop();
}
// Get the handle for the start of this chunk (the last entry
// in currLevelChunks)
InstructionHandle chunkStart =
(InstructionHandle) currLevelChunks.get(
currLevelChunks.size()-1);
int chunkEndPosition =
(currentHandle != null) ? currentHandle.getPosition()
: totalMethodSize;
int chunkSize = chunkEndPosition - chunkStart.getPosition();
// Two ranges of chunk size to consider:
//
// 1. [0,TARGET_METHOD_SIZE]
// Keep this chunk in consideration as a candidate,
// and ignore its subchunks, if any - there's nothing to be
// gained by outlining both the current chunk and its
// children!
//
// 2. (TARGET_METHOD_SIZE,+infinity)
// Ignore this chunk - it's too big. Add its subchunks
// as candidates, after merging adjacent chunks to produce
// chunks that are as large as possible
if (chunkSize <= TARGET_METHOD_SIZE) {
currLevelChunks.add(currentHandle);
} else {
if (!openChunkAtCurrLevel) {
int childChunkCount = nestedSubChunks.size() / 2;
if (childChunkCount > 0) {
Chunk[] childChunks = new Chunk[childChunkCount];
// Gather all the child chunks of the current chunk
for (int i = 0; i < childChunkCount; i++) {
InstructionHandle start =
(InstructionHandle) nestedSubChunks
.get(i*2);
InstructionHandle end =
(InstructionHandle) nestedSubChunks
.get(i*2+1);
childChunks[i] = new Chunk(start, end);
}
// Merge adjacent siblings
ArrayList mergedChildChunks =
mergeAdjacentChunks(childChunks);
// Add chunks that mean minimum size requirements
// to the list of candidate chunks for outlining
for (int i = 0; i < mergedChildChunks.size(); i++) {
Chunk mergedChunk =
(Chunk)mergedChildChunks.get(i);
int mergedSize = mergedChunk.getChunkSize();
if (mergedSize >= MINIMUM_OUTLINEABLE_CHUNK_SIZE
&& mergedSize <= TARGET_METHOD_SIZE) {
candidateChunks.add(mergedChunk);
}
}
}
}
// Drop the chunk which was too big
currLevelChunks.remove(currLevelChunks.size() - 1);
}
// currLevelChunks contains pairs of InstructionHandles. If
// its size is an odd number, the loop has encountered the
// start of a chunk at this level, but not its end.
openChunkAtCurrLevel = ((currLevelChunks.size() & 0x1) == 1);
}
} while (currentHandle != null);
return candidateChunks;
}
/**
* Merge adjacent sibling chunks to produce larger candidate chunks for
* outlining
* @param chunks array of sibling {@link MethodGenerator.Chunk}s that are
* under consideration for outlining. Chunks must be in
* the order encountered in the {@link InstructionList}
* @return a <code>java.util.ArrayList</code> of
* <code>MethodGenerator.Chunk</code>s maximally merged
*/
private ArrayList mergeAdjacentChunks(Chunk[] chunks) {
int[] adjacencyRunStart = new int[chunks.length];
int[] adjacencyRunLength = new int[chunks.length];
boolean[] chunkWasMerged = new boolean[chunks.length];
int maximumRunOfChunks = 0;
int startOfCurrentRun;
int numAdjacentRuns = 0;
ArrayList mergedChunks = new ArrayList();
startOfCurrentRun = 0;
// Loop through chunks, and record in adjacencyRunStart where each
// run of adjacent chunks begins and how many are in that run. For
// example, given chunks A B C D E F, if A is adjacent to B, but not
// to C, and C, D, E and F are all adjacent,
// adjacencyRunStart[0] == 0; adjacencyRunLength[0] == 2
// adjacencyRunStart[1] == 2; adjacencyRunLength[1] == 4
for (int i = 1; i < chunks.length; i++) {
if (!chunks[i-1].isAdjacentTo(chunks[i])) {
int lengthOfRun = i - startOfCurrentRun;
// Track the longest run of chunks found
if (maximumRunOfChunks < lengthOfRun) {
maximumRunOfChunks = lengthOfRun;
}
if (lengthOfRun > 1 ) {
adjacencyRunLength[numAdjacentRuns] = lengthOfRun;
adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun;
numAdjacentRuns++;
}
startOfCurrentRun = i;
}
}
if (chunks.length - startOfCurrentRun > 1) {
int lengthOfRun = chunks.length - startOfCurrentRun;
// Track the longest run of chunks found
if (maximumRunOfChunks < lengthOfRun) {
maximumRunOfChunks = lengthOfRun;
}
adjacencyRunLength[numAdjacentRuns] =
chunks.length - startOfCurrentRun;
adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun;
numAdjacentRuns++;
}
// Try merging adjacent chunks to come up with better sized chunks for
// outlining. This algorithm is not optimal, but it should be
// reasonably fast. Consider an example like this, where four chunks
// of the sizes specified in brackets are adjacent. The best way of
// combining these chunks would be to merge the first pair and merge
// the last three to form two chunks, but the algorithm will merge the
// three in the middle instead, leaving three chunks in all.
// [25000] [25000] [20000] [1000] [20000]
// Start by trying to merge the maximum number of adjacent chunks, and
// work down from there.
for (int numToMerge = maximumRunOfChunks; numToMerge>1; numToMerge--) {
// Look at each run of adjacent chunks
for (int run = 0; run < numAdjacentRuns; run++) {
int runStart = adjacencyRunStart[run];
int runEnd = runStart + adjacencyRunLength[run] - 1;
boolean foundChunksToMerge = false;
// Within the current run of adjacent chunks, look at all
// "subruns" of length numToMerge, until we run out or find
// a subrun that can be merged.
for (int mergeStart = runStart;
mergeStart+numToMerge-1 <= runEnd && !foundChunksToMerge;
mergeStart++) {
int mergeEnd = mergeStart + numToMerge - 1;
int mergeSize = 0;
// Find out how big the subrun is
for (int j = mergeStart; j <= mergeEnd; j++) {
mergeSize = mergeSize + chunks[j].getChunkSize();
}
// If the current subrun is small enough to outline,
// merge it, and split the remaining chunks in the run
if (mergeSize <= TARGET_METHOD_SIZE) {
foundChunksToMerge = true;
for (int j = mergeStart; j <= mergeEnd; j++) {
chunkWasMerged[j] = true;
}
mergedChunks.add(
new Chunk(chunks[mergeStart].getChunkStart(),
chunks[mergeEnd].getChunkEnd()));
// Adjust the length of the current run of adjacent
// chunks to end at the newly merged chunk...
adjacencyRunLength[run] =
adjacencyRunStart[run] - mergeStart;
int trailingRunLength = runEnd - mergeEnd;
// and any chunks that follow the newly merged chunk
// in the current run of adjacent chunks form another
// new run of adjacent chunks
if (trailingRunLength >= 2) {
adjacencyRunStart[numAdjacentRuns] = mergeEnd + 1;
adjacencyRunLength[numAdjacentRuns] =
trailingRunLength;
numAdjacentRuns++;
}
}
}
}
}
// Make a final pass for any chunk that wasn't merged with a sibling
// and include it in the list of chunks after merging.
for (int i = 0; i < chunks.length; i++) {
if (!chunkWasMerged[i]) {
mergedChunks.add(chunks[i]);
}
}
return mergedChunks;
}
/**
* Breaks up the IL for this {@link MethodGenerator} into separate
* outlined methods so that no method exceeds the 64KB limit on the length
* of the byte code associated with a method.
* @param classGen The {@link ClassGen} with which the generated methods
* will be associated
* @param originalMethodSize The number of bytes of bytecode represented by
* the {@link InstructionList} of this method
* @return an array of the outlined <code>Method</code>s and the original
* method itself
*/
public Method[] outlineChunks(ClassGenerator classGen,
int originalMethodSize) {
ArrayList methodsOutlined = new ArrayList();
int currentMethodSize = originalMethodSize;
int outlinedCount = 0;
boolean moreMethodsOutlined;
String originalMethodName = getName();
// Special handling for initialization methods. No other methods can
// include the less than and greater than characters in their names,
// so we munge the names here.
if (originalMethodName.equals("<init>")) {
originalMethodName = "$lt$init$gt$";
} else if (originalMethodName.equals("<clinit>")) {
originalMethodName = "$lt$clinit$gt$";
}
// Loop until the original method comes in under the JVM limit or
// the loop was unable to outline any more methods
do {
// Get all the best candidates for outlining, and sort them in
// ascending order of size
ArrayList candidateChunks = getCandidateChunks(classGen,
currentMethodSize);
Collections.sort(candidateChunks);
moreMethodsOutlined = false;
// Loop over the candidates for outlining, from the largest to the
// smallest and outline them one at a time, until the loop has
// outlined all or the original method comes in under the JVM
// limit on the size of a method.
for (int i = candidateChunks.size()-1;
i >= 0 && currentMethodSize > TARGET_METHOD_SIZE;
i--) {
Chunk chunkToOutline = (Chunk)candidateChunks.get(i);
methodsOutlined.add(outline(chunkToOutline.getChunkStart(),
chunkToOutline.getChunkEnd(),
originalMethodName + "$outline$"
+ outlinedCount,
classGen));
outlinedCount++;
moreMethodsOutlined = true;
InstructionList il = getInstructionList();
InstructionHandle lastInst = il.getEnd();
il.setPositions();
// Check the size of the method now
currentMethodSize =
lastInst.getPosition()
+ lastInst.getInstruction().getLength();
}
} while (moreMethodsOutlined && currentMethodSize > TARGET_METHOD_SIZE);
// Outlining failed to reduce the size of the current method
// sufficiently. Throw an internal error.
if (currentMethodSize > MAX_METHOD_SIZE) {
String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_METHOD_TOO_BIG))
.toString();
throw new InternalError(msg);
}
Method[] methodsArr = new Method[methodsOutlined.size() + 1];
methodsOutlined.toArray(methodsArr);
methodsArr[methodsOutlined.size()] = getThisMethod();
return methodsArr;
}
/**
* Given an outlineable chunk of code in the current {@link MethodGenerator}
* move ("outline") the chunk to a new method, and replace the chunk in the
* old method with a reference to that new method. No
* {@link OutlineableChunkStart} or {@link OutlineableChunkEnd} instructions
* are copied.
* @param first The {@link InstructionHandle} of the first instruction in
* the chunk to outline
* @param last The <code>InstructionHandle</code> of the last instruction in
* the chunk to outline
* @param outlinedMethodName The name of the new method
* @param classGen The {@link ClassGenerator} of which the original
* and new methods will be members
* @return The new {@link Method} containing the outlined code.
*/
private Method outline(InstructionHandle first, InstructionHandle last,
String outlinedMethodName, ClassGenerator classGen) {
// We're not equipped to deal with exception handlers yet. Bail out!
if (getExceptionHandlers().length != 0) {
String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_TRY_CATCH))
.toString();
throw new InternalError(msg);
}
int outlineChunkStartOffset = first.getPosition();
int outlineChunkEndOffset = last.getPosition()
+ last.getInstruction().getLength();
ConstantPoolGen cpg = getConstantPool();
// Create new outlined method with signature:
//
// private final outlinedMethodName(CopyLocals copyLocals);
//
// CopyLocals is an object that is used to copy-in/copy-out local
// variables that are used by the outlined method. Only locals whose
// value is potentially set or referenced outside the range of the
// chunk that is being outlined will be represented in CopyLocals. The
// type of the variable for copying local variables is actually
// generated to be unique - it is not named CopyLocals.
//
// The outlined method never needs to be referenced outside of this
// class, and will never be overridden, so we mark it private final.
final InstructionList newIL = new InstructionList();
final XSLTC xsltc = classGen.getParser().getXSLTC();
final String argTypeName = xsltc.getHelperClassName();
final Type[] argTypes =
new Type[] {(new ObjectType(argTypeName)).toJCType()};
final String argName = "copyLocals";
final String[] argNames = new String[] {argName};
int methodAttributes = ACC_PRIVATE | ACC_FINAL;
final boolean isStaticMethod = (getAccessFlags() & ACC_STATIC) != 0;
if (isStaticMethod) {
methodAttributes = methodAttributes | ACC_STATIC;
}
final MethodGenerator outlinedMethodGen =
new MethodGenerator(methodAttributes,
org.apache.bcel.generic.Type.VOID,
argTypes, argNames, outlinedMethodName,
getClassName(), newIL, cpg);
// Create class for copying local variables to the outlined method.
// The fields the class will need to contain will be determined as the
// code in the outlineable chunk is examined.
ClassGenerator copyAreaCG
= new ClassGenerator(argTypeName, OBJECT_CLASS, argTypeName+".java",
ACC_FINAL | ACC_PUBLIC | ACC_SUPER, null,
classGen.getStylesheet()) {
public boolean isExternal() {
return true;
}
};
ConstantPoolGen copyAreaCPG = copyAreaCG.getConstantPool();
copyAreaCG.addEmptyConstructor(ACC_PUBLIC);
// Number of fields in the copy class
int copyAreaFieldCount = 0;
// The handle for the instruction after the last one to be outlined.
// Note that this should never end up being null. An outlineable chunk
// won't contain a RETURN instruction or other branch out of the chunk,
// and the JVM specification prohibits code in a method from just
// "falling off the end" so this should always point to a valid handle.
InstructionHandle limit = last.getNext();
// InstructionLists for copying values into and out of an instance of
// CopyLocals:
// oldMethCoypInIL - from locals in old method into an instance
// of the CopyLocals class (oldMethCopyInIL)
// oldMethCopyOutIL - from CopyLocals back into locals in the old
// method
// newMethCopyInIL - from CopyLocals into locals in the new
// method
// newMethCopyOutIL - from locals in new method into the instance
// of the CopyLocals class
InstructionList oldMethCopyInIL = new InstructionList();
InstructionList oldMethCopyOutIL = new InstructionList();
InstructionList newMethCopyInIL = new InstructionList();
InstructionList newMethCopyOutIL = new InstructionList();
// Allocate instance of class in which we'll copy in or copy out locals
// and make two copies: last copy is used to invoke constructor;
// other two are used for references to fields in the CopyLocals object
InstructionHandle outlinedMethodCallSetup =
oldMethCopyInIL.append(new NEW(cpg.addClass(argTypeName)));
oldMethCopyInIL.append(InstructionConstants.DUP);
oldMethCopyInIL.append(InstructionConstants.DUP);
oldMethCopyInIL.append(
new INVOKESPECIAL(cpg.addMethodref(argTypeName, "<init>", "()V")));
// Generate code to invoke the new outlined method, and place the code
// on oldMethCopyOutIL
InstructionHandle outlinedMethodRef;
if (isStaticMethod) {
outlinedMethodRef =
oldMethCopyOutIL.append(
new INVOKESTATIC(cpg.addMethodref(
classGen.getClassName(),
outlinedMethodName,
outlinedMethodGen.getSignature())));
} else {
oldMethCopyOutIL.append(InstructionConstants.THIS);
oldMethCopyOutIL.append(InstructionConstants.SWAP);
outlinedMethodRef =
oldMethCopyOutIL.append(
new INVOKEVIRTUAL(cpg.addMethodref(
classGen.getClassName(),
outlinedMethodName,
outlinedMethodGen.getSignature())));
}
// Used to keep track of the first in a sequence of
// OutlineableChunkStart instructions
boolean chunkStartTargetMappingsPending = false;
InstructionHandle pendingTargetMappingHandle = null;
// Used to keep track of the last instruction that was copied
InstructionHandle lastCopyHandle = null;
// Keeps track of the mapping from instruction handles in the old
// method to instruction handles in the outlined method. Only need
// to track instructions that are targeted by something else in the
// generated BCEL
HashMap targetMap = new HashMap();
// Keeps track of the mapping from local variables in the old method
// to local variables in the outlined method.
HashMap localVarMap = new HashMap();
HashMap revisedLocalVarStart = new HashMap();
HashMap revisedLocalVarEnd = new HashMap();
// Pass 1: Make copies of all instructions, append them to the new list
// and associate old instruction references with the new ones, i.e.,
// a 1:1 mapping. The special marker instructions are not copied.
// Also, identify local variables whose values need to be copied into or
// out of the new outlined method, and builds up targetMap and
// localVarMap as described above. The code identifies those local
// variables first so that they can have fixed slots in the stack
// frame for the outlined method assigned them ahead of all those
// variables that don't need to exist for the entirety of the outlined
// method invocation.
for (InstructionHandle ih = first; ih != limit; ih = ih.getNext()) {
Instruction inst = ih.getInstruction();
// MarkerInstructions are not copied, so if something else targets
// one, the targetMap will point to the nearest copied sibling
// InstructionHandle: for an OutlineableChunkEnd, the nearest
// preceding sibling; for an OutlineableChunkStart, the nearest
// following sibling.
if (inst instanceof MarkerInstruction) {
if (ih.hasTargeters()) {
if (inst instanceof OutlineableChunkEnd) {
targetMap.put(ih, lastCopyHandle);
} else {
if (!chunkStartTargetMappingsPending) {
chunkStartTargetMappingsPending = true;
pendingTargetMappingHandle = ih;
}
}
}
} else {
// Copy the instruction and append it to the outlined method's
// InstructionList.
Instruction c = inst.copy(); // Use clone for shallow copy
if (c instanceof BranchInstruction) {
lastCopyHandle = newIL.append((BranchInstruction)c);
} else {
lastCopyHandle = newIL.append(c);
}
if (c instanceof LocalVariableInstruction
|| c instanceof RET) {
// For any instruction that touches a local variable,
// check whether the local variable's value needs to be
// copied into or out of the outlined method. If so,
// generate the code to perform the necessary copying, and
// use localVarMap to map the variable in the original
// method to the variable in the new method.
IndexedInstruction lvi = (IndexedInstruction)c;
int oldLocalVarIndex = lvi.getIndex();
LocalVariableGen oldLVG =
getLocalVariableRegistry()
.lookupRegisteredLocalVariable(oldLocalVarIndex,
ih.getPosition());
LocalVariableGen newLVG =
(LocalVariableGen)localVarMap.get(oldLVG);
// Has the code already mapped this local variable to a
// local in the new method?
if (localVarMap.get(oldLVG) == null) {
// Determine whether the local variable needs to be
// copied into or out of the outlined by checking
// whether the range of instructions in which the
// variable is accessible is outside the range of
// instructions in the outlineable chunk.
// Special case a chunk start offset of zero: a local
// variable live at that position must be a method
// parameter, so the code doesn't need to check whether
// the variable is live before that point; being live
// at offset zero is sufficient to know that the value
// must be copied in to the outlined method.
boolean copyInLocalValue =
offsetInLocalVariableGenRange(oldLVG,
(outlineChunkStartOffset != 0)
? outlineChunkStartOffset-1
: 0);
boolean copyOutLocalValue =
offsetInLocalVariableGenRange(oldLVG,
outlineChunkEndOffset+1);
// For any variable that needs to be copied into or out
// of the outlined method, create a field in the
// CopyLocals class, and generate the necessary code for
// copying the value.
if (copyInLocalValue || copyOutLocalValue) {
String varName = oldLVG.getName();
Type varType = oldLVG.getType();
newLVG = outlinedMethodGen.addLocalVariable(varName,
varType,
null,
null);
int newLocalVarIndex = newLVG.getIndex();
String varSignature = varType.getSignature();
// Record the mapping from the old local to the new
localVarMap.put(oldLVG, newLVG);
copyAreaFieldCount++;
String copyAreaFieldName =
"field" + copyAreaFieldCount;
copyAreaCG.addField(
new Field(ACC_PUBLIC,
copyAreaCPG.addUtf8(copyAreaFieldName),
copyAreaCPG.addUtf8(varSignature),
null, copyAreaCPG.getConstantPool()));
int fieldRef = cpg.addFieldref(argTypeName,
copyAreaFieldName,
varSignature);
if (copyInLocalValue) {
// Generate code for the old method to store the
// value of the local into the correct field in
// CopyLocals prior to invocation of the
// outlined method.
oldMethCopyInIL.append(
InstructionConstants.DUP);
InstructionHandle copyInLoad =
oldMethCopyInIL.append(
loadLocal(oldLocalVarIndex, varType));
oldMethCopyInIL.append(new PUTFIELD(fieldRef));
// If the end of the live range of the old
// variable was in the middle of the outlined
// chunk. Make the load of its value the new
// end of its range.
if (!copyOutLocalValue) {
revisedLocalVarEnd.put(oldLVG, copyInLoad);
}
// Generate code for start of the outlined
// method to copy the value from a field in
// CopyLocals to the new local in the outlined
// method
newMethCopyInIL.append(
InstructionConstants.ALOAD_1);
newMethCopyInIL.append(new GETFIELD(fieldRef));
newMethCopyInIL.append(
storeLocal(newLocalVarIndex, varType));
}
if (copyOutLocalValue) {
// Generate code for the end of the outlined
// method to copy the value from the new local
// variable into a field in CopyLocals
// method
newMethCopyOutIL.append(
InstructionConstants.ALOAD_1);
newMethCopyOutIL.append(
loadLocal(newLocalVarIndex, varType));
newMethCopyOutIL.append(new PUTFIELD(fieldRef));
// Generate code to copy the value from a field
// in CopyLocals into a local in the original
// method following invocation of the outlined
// method.
oldMethCopyOutIL.append(
InstructionConstants.DUP);
oldMethCopyOutIL.append(new GETFIELD(fieldRef));
InstructionHandle copyOutStore =
oldMethCopyOutIL.append(
storeLocal(oldLocalVarIndex, varType));
// If the start of the live range of the old
// variable was in the middle of the outlined
// chunk. Make this store into it the new start
// of its range.
if (!copyInLocalValue) {
revisedLocalVarStart.put(oldLVG,
copyOutStore);
}
}
}
}
}
if (ih.hasTargeters()) {
targetMap.put(ih, lastCopyHandle);
}
// If this is the first instruction copied following a sequence
// of OutlineableChunkStart instructions, indicate that the
// sequence of old instruction all map to this newly created
// instruction
if (chunkStartTargetMappingsPending) {
do {
targetMap.put(pendingTargetMappingHandle,
lastCopyHandle);
pendingTargetMappingHandle =
pendingTargetMappingHandle.getNext();
} while(pendingTargetMappingHandle != ih);
chunkStartTargetMappingsPending = false;
}
}
}
// Pass 2: Walk old and new instruction lists, updating branch targets
// and local variable references in the new list
InstructionHandle ih = first;
InstructionHandle ch = newIL.getStart();
while (ch != null) {
// i == old instruction; c == copied instruction
Instruction i = ih.getInstruction();
Instruction c = ch.getInstruction();
if (i instanceof BranchInstruction) {
BranchInstruction bc = (BranchInstruction)c;
BranchInstruction bi = (BranchInstruction)i;
InstructionHandle itarget = bi.getTarget(); // old target
// New target must be in targetMap
InstructionHandle newTarget =
(InstructionHandle)targetMap.get(itarget);
bc.setTarget(newTarget);
// Handle LOOKUPSWITCH or TABLESWITCH which may have many
// target instructions
if (bi instanceof Select) {
InstructionHandle[] itargets = ((Select)bi).getTargets();
InstructionHandle[] ctargets = ((Select)bc).getTargets();
// Update all targets
for (int j=0; j < itargets.length; j++) {
ctargets[j] =
(InstructionHandle)targetMap.get(itargets[j]);
}
}
} else if (i instanceof LocalVariableInstruction
|| i instanceof RET) {
// For any instruction that touches a local variable,
// map the location of the variable in the original
// method to its location in the new method.
IndexedInstruction lvi = (IndexedInstruction)c;
int oldLocalVarIndex = lvi.getIndex();
LocalVariableGen oldLVG =
getLocalVariableRegistry()
.lookupRegisteredLocalVariable(oldLocalVarIndex,
ih.getPosition());
LocalVariableGen newLVG =
(LocalVariableGen)localVarMap.get(oldLVG);
int newLocalVarIndex;
if (newLVG == null) {
// Create new variable based on old variable - use same
// name and type, but we will let the variable be active
// for the entire outlined method.
// LocalVariableGen oldLocal = oldLocals[oldLocalVarIndex];
String varName = oldLVG.getName();
Type varType = oldLVG.getType();
newLVG = outlinedMethodGen.addLocalVariable(varName,
varType,
null,
null);
newLocalVarIndex = newLVG.getIndex();
localVarMap.put(oldLVG, newLVG);
// The old variable's live range was wholly contained in
// the outlined chunk. There should no longer be stores
// of values into it or loads of its value, so we can just
// mark its live range as the reference to the outlined
// method.
revisedLocalVarStart.put(oldLVG, outlinedMethodRef);
revisedLocalVarEnd.put(oldLVG, outlinedMethodRef);
} else {
newLocalVarIndex = newLVG.getIndex();
}
lvi.setIndex(newLocalVarIndex);
}
// If the old instruction marks the end of the range of a local
// variable, make sure that any slots on the stack reserved for
// local variables are made available for reuse by calling
// MethodGenerator.removeLocalVariable
if (ih.hasTargeters()) {
InstructionTargeter[] targeters = ih.getTargeters();
for (int idx = 0; idx < targeters.length; idx++) {
InstructionTargeter targeter = targeters[idx];
if (targeter instanceof LocalVariableGen
&& ((LocalVariableGen)targeter).getEnd()==ih) {
Object newLVG = localVarMap.get(targeter);
if (newLVG != null) {
outlinedMethodGen.removeLocalVariable(
(LocalVariableGen)newLVG);
}
}
}
}
// If the current instruction in the original list was a marker,
// it wasn't copied, so don't advance through the list of copied
// instructions yet.
if (!(i instanceof MarkerInstruction)) {
ch = ch.getNext();
}
ih = ih.getNext();
}
// POP the reference to the CopyLocals object from the stack
oldMethCopyOutIL.append(InstructionConstants.POP);
// Now that the generation of the outlined code is complete, update
// the old local variables with new start and end ranges, as required.
Iterator revisedLocalVarStartPairIter = revisedLocalVarStart.entrySet()
.iterator();
while (revisedLocalVarStartPairIter.hasNext()) {
Map.Entry lvgRangeStartPair =
(Map.Entry)revisedLocalVarStartPairIter.next();
LocalVariableGen lvg = (LocalVariableGen)lvgRangeStartPair.getKey();
InstructionHandle startInst =
(InstructionHandle)lvgRangeStartPair.getValue();
lvg.setStart(startInst);
}
Iterator revisedLocalVarEndPairIter = revisedLocalVarEnd.entrySet()
.iterator();
while (revisedLocalVarEndPairIter.hasNext()) {
Map.Entry lvgRangeEndPair =
(Map.Entry)revisedLocalVarEndPairIter.next();
LocalVariableGen lvg = (LocalVariableGen)lvgRangeEndPair.getKey();
InstructionHandle endInst =
(InstructionHandle)lvgRangeEndPair.getValue();
lvg.setEnd(endInst);
}
xsltc.dumpClass(copyAreaCG.getJavaClass());
// Assemble the instruction lists so that the old method invokes the
// new outlined method
InstructionList oldMethodIL = getInstructionList();
oldMethodIL.insert(first, oldMethCopyInIL);
oldMethodIL.insert(first, oldMethCopyOutIL);
// Insert the copying code into the outlined method
newIL.insert(newMethCopyInIL);
newIL.append(newMethCopyOutIL);
newIL.append(InstructionConstants.RETURN);
// Discard instructions in outlineable chunk from old method
try {
oldMethodIL.delete(first, last);
} catch (TargetLostException e) {
InstructionHandle[] targets = e.getTargets();
// If there were still references to old instructions lingering,
// clean those up. The only instructions targetting the deleted
// instructions should have been part of the chunk that was just
// deleted, except that instructions might branch to the start of
// the outlined chunk; similarly, all the live ranges of local
// variables should have been adjusted, except for unreferenced
// variables.
for (int i = 0; i < targets.length; i++) {
InstructionHandle lostTarget = targets[i];
InstructionTargeter[] targeters = lostTarget.getTargeters();
for (int j = 0; j < targeters.length; j++) {
if (targeters[j] instanceof LocalVariableGen) {
LocalVariableGen lvgTargeter =
(LocalVariableGen) targeters[j];
// In the case of any lingering variable references,
// just make the live range point to the outlined
// function reference. Such variables should be unused
// anyway.
if (lvgTargeter.getStart() == lostTarget) {
lvgTargeter.setStart(outlinedMethodRef);
}
if (lvgTargeter.getEnd() == lostTarget) {
lvgTargeter.setEnd(outlinedMethodRef);
}
} else {
targeters[j].updateTarget(lostTarget,
outlinedMethodCallSetup);
}
}
}
}
// Make a copy for the new method of all exceptions that might be thrown
String[] exceptions = getExceptions();
for (int i = 0; i < exceptions.length; i++) {
outlinedMethodGen.addException(exceptions[i]);
}
return outlinedMethodGen.getThisMethod();
}
/**
* Helper method to generate an instance of a subclass of
* {@link LoadInstruction} based on the specified {@link Type} that will
* load the specified local variable
* @param index the JVM stack frame index of the variable that is to be
* loaded
* @param type the {@link Type} of the variable
* @return the generated {@link LoadInstruction}
*/
private static Instruction loadLocal(int index, Type type) {
if (type == Type.BOOLEAN) {
return new ILOAD(index);
} else if (type == Type.INT) {
return new ILOAD(index);
} else if (type == Type.SHORT) {
return new ILOAD(index);
} else if (type == Type.LONG) {
return new LLOAD(index);
} else if (type == Type.BYTE) {
return new ILOAD(index);
} else if (type == Type.CHAR) {
return new ILOAD(index);
} else if (type == Type.FLOAT) {
return new FLOAD(index);
} else if (type == Type.DOUBLE) {
return new DLOAD(index);
} else {
return new ALOAD(index);
}
}
/**
* Helper method to generate an instance of a subclass of
* {@link StoreInstruction} based on the specified {@link Type} that will
* store a value in the specified local variable
* @param index the JVM stack frame index of the variable that is to be
* stored
* @param type the {@link Type} of the variable
* @return the generated {@link StoredInstruction}
*/
private static Instruction storeLocal(int index, Type type) {
if (type == Type.BOOLEAN) {
return new ISTORE(index);
} else if (type == Type.INT) {
return new ISTORE(index);
} else if (type == Type.SHORT) {
return new ISTORE(index);
} else if (type == Type.LONG) {
return new LSTORE(index);
} else if (type == Type.BYTE) {
return new ISTORE(index);
} else if (type == Type.CHAR) {
return new ISTORE(index);
} else if (type == Type.FLOAT) {
return new FSTORE(index);
} else if (type == Type.DOUBLE) {
return new DSTORE(index);
} else {
return new ASTORE(index);
}
}
/**
* Track the number of outlineable chunks seen.
*/
private int m_totalChunks = 0;
/**
* Track the number of outlineable chunks started but not yet ended. Used
* to detect imbalances in byte code generation.
*/
private int m_openChunks = 0;
/**
* Mark the end of the method's
* {@link InstructionList} as the start of an outlineable chunk of code.
* The outlineable chunk begins after the {@link InstructionHandle} that is
* at the end of the method's {@link InstructionList}, or at the start of
* the method if the <code>InstructionList</code> is empty.
* See {@link OutlineableChunkStart} for more information.
*/
public void markChunkStart() {
// m_chunkTree.markChunkStart();
getInstructionList()
.append(OutlineableChunkStart.OUTLINEABLECHUNKSTART);
m_totalChunks++;
m_openChunks++;
}
/**
* Mark the end of an outlineable chunk of code. See
* {@link OutlineableChunkStart} for more information.
*/
public void markChunkEnd() {
// m_chunkTree.markChunkEnd();
getInstructionList()
.append(OutlineableChunkEnd.OUTLINEABLECHUNKEND);
m_openChunks--;
if (m_openChunks < 0) {
String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS))
.toString();
throw new InternalError(msg);
}
}
/**
* <p>Get all {@link Method}s generated by this {@link MethodGenerator}.
* The {@link MethodGen#getMethod()} only returns a single
* <code>Method</code> object. This method takes into account the Java
* Virtual Machine Specification limit of 64KB on the size of a method, and
* may return more than one <code>Method</code>.</p>
* <p>If the code associated with the <code>MethodGenerator</code> would
* exceed the 64KB limit, this method will attempt to split the code in
* the {@link InstructionList} associated with this
* <code>MethodGenerator</code> into several methods.</p>
* @param classGen the {@link ClassGenerator} of which these methods are
* members
* @return an array of all the <code>Method</code>s generated
*/
Method[] getGeneratedMethods(ClassGenerator classGen) {
Method[] generatedMethods;
InstructionList il = getInstructionList();
InstructionHandle last = il.getEnd();
il.setPositions();
int instructionListSize =
last.getPosition() + last.getInstruction().getLength();
// Need to look for any branch target offsets that exceed the range
// [-32768,32767]
if (instructionListSize > MAX_BRANCH_TARGET_OFFSET) {
boolean ilChanged = widenConditionalBranchTargetOffsets();
// If any branch instructions needed widening, recompute the size
// of the byte code for the method
if (ilChanged) {
il.setPositions();
last = il.getEnd();
instructionListSize =
last.getPosition() + last.getInstruction().getLength();
}
}
if (instructionListSize > MAX_METHOD_SIZE) {
generatedMethods = outlineChunks(classGen, instructionListSize);
} else {
generatedMethods = new Method[] {getThisMethod()};
}
return generatedMethods;
}
protected Method getThisMethod() {
stripAttributes(true);
setMaxLocals();
setMaxStack();
removeNOPs();
return getMethod();
}
/**
* <p>Rewrites branches to avoid the JVM limits of relative branch
* offsets. There is no need to invoke this method if the bytecode for the
* {@link MethodGenerator} does not exceed 32KB.</p>
* <p>The Java Virtual Machine Specification permits the code portion of a
* method to be up to 64KB in length. However, some control transfer
* instructions specify relative offsets as a signed 16-bit quantity,
* limiting the range to a subset of the instructions that might be in a
* method.</p>
* <p>The <code>TABLESWITCH</code> and <code>LOOKUPSWITCH</code>
* instructions always use 32-bit signed relative offsets, so they are
* immune to this problem.</p>
* <p>The <code>GOTO</code> and <code>JSR</code>
* instructions come in two forms, one of which uses 16-bit relative
* offsets, and the other of which uses 32-bit relative offsets. The BCEL
* library decides whether to use the wide form of <code>GOTO</code> or
* <code>JSR</code>instructions based on the relative offset of the target
* of the instruction without any intervention by the user of the
* library.</p>
* <p>This leaves the various conditional branch instructions,
* <code>IFEQ</code>, <code>IFNULL</code>, <code>IF_ICMPEQ</code>,
* <em>et al.</em>, all of which use 16-bit signed relative offsets, with no
* 32-bit wide form available.</p>
* <p>This method scans the {@link InstructionList} associated with this
* {@link MethodGenerator} and finds all conditional branch instructions
* that might exceed the 16-bit limitation for relative branch offsets.
* The logic of each such instruction is inverted, and made to target the
* instruction which follows it. An unconditional branch to the original
* target of the instruction is then inserted between the conditional
* branch and the instruction which previously followed it. The
* unconditional branch is permitted to have a 16-bit or a 32-bit relative
* offset, as described above. For example,
* <code>
* 1234: NOP
* ...
* 55278: IFEQ -54044
* 55280: NOP
* </code>
* is rewritten as
* <code>
* 1234: NOP
* ...
* 55278: IFNE 7
* 55280: GOTO_W -54046
* 55285: NOP
* </code></p>
* <p><b>Preconditions:</b>
* <ul><li>The {@link InstructionList#setPositions()} has been called for
* the <code>InstructionList</code> associated with this
* <code>MethodGenerator</code>.
* </li></ul></p>
* <p><b>Postconditions:</b>
* <ul><li>Any further changes to the <code>InstructionList</code> for this
* <code>MethodGenerator</code> will invalidate the changes made by this
* method.</li></ul>
* </p>
* @return <code>true</code> if the <code>InstructionList</code> was
* modified; <code>false</code> otherwise
* @see The Java Virtual Machine Specification, Second Edition
*/
boolean widenConditionalBranchTargetOffsets() {
boolean ilChanged = false;
int maxOffsetChange = 0;
InstructionList il = getInstructionList();
// Loop through all the instructions, finding those that would be
// affected by inserting new instructions in the InstructionList, and
// calculating the maximum amount by which the relative offset between
// two instructions could possibly change.
// In part this loop duplicates code in
// org.apache.bcel.generic.InstructionList.setPosition(), which does
// this to determine whether to use 16-bit or 32-bit offsets for GOTO
// and JSR instructions. Ideally, that method would do the same for
// conditional branch instructions, but it doesn't, so we duplicate the
// processing here.
for (InstructionHandle ih = il.getStart();
ih != null;
ih = ih.getNext()) {
Instruction inst = ih.getInstruction();
switch (inst.getOpcode()) {
// Instructions that may have 16-bit or 32-bit branch targets.
// The size of the branch offset might increase by two bytes.
case Constants.GOTO:
case Constants.JSR:
maxOffsetChange = maxOffsetChange + 2;
break;
// Instructions that contain padding for alignment purposes
// Up to three bytes of padding might be needed. For greater
// accuracy, we should be able to discount any padding already
// added to these instructions by InstructionList.setPosition(),
// their APIs do not expose that information.
case Constants.TABLESWITCH:
case Constants.LOOKUPSWITCH:
maxOffsetChange = maxOffsetChange + 3;
break;
// Instructions that might be rewritten by this method as a
// conditional branch followed by an unconditional branch.
// The unconditional branch would require five bytes.
case Constants.IF_ACMPEQ:
case Constants.IF_ACMPNE:
case Constants.IF_ICMPEQ:
case Constants.IF_ICMPGE:
case Constants.IF_ICMPGT:
case Constants.IF_ICMPLE:
case Constants.IF_ICMPLT:
case Constants.IF_ICMPNE:
case Constants.IFEQ:
case Constants.IFGE:
case Constants.IFGT:
case Constants.IFLE:
case Constants.IFLT:
case Constants.IFNE:
case Constants.IFNONNULL:
case Constants.IFNULL:
maxOffsetChange = maxOffsetChange + 5;
break;
}
}
// Now that the maximum number of bytes by which the method might grow
// has been determined, look for conditional branches to see which
// might possibly exceed the 16-bit relative offset.
for (InstructionHandle ih = il.getStart();
ih != null;
ih = ih.getNext()) {
Instruction inst = ih.getInstruction();
if (inst instanceof IfInstruction) {
IfInstruction oldIfInst = (IfInstruction)inst;
BranchHandle oldIfHandle = (BranchHandle)ih;
InstructionHandle target = oldIfInst.getTarget();
int relativeTargetOffset = target.getPosition()
- oldIfHandle.getPosition();
// Consider the worst case scenario in which the conditional
// branch and its target are separated by all the instructions
// in the method that might increase in size. If that results
// in a relative offset that cannot be represented as a 32-bit
// signed quantity, rewrite the instruction as described above.
if ((relativeTargetOffset - maxOffsetChange
< MIN_BRANCH_TARGET_OFFSET)
|| (relativeTargetOffset + maxOffsetChange
> MAX_BRANCH_TARGET_OFFSET)) {
// Invert the logic of the IF instruction, and append
// that to the InstructionList following the original IF
// instruction
InstructionHandle nextHandle = oldIfHandle.getNext();
IfInstruction invertedIfInst = oldIfInst.negate();
BranchHandle invertedIfHandle = il.append(oldIfHandle,
invertedIfInst);
// Append an unconditional branch to the target of the
// original IF instruction after the new IF instruction
BranchHandle gotoHandle = il.append(invertedIfHandle,
new GOTO(target));
// If the original IF was the last instruction in
// InstructionList, add a new no-op to act as the target
// of the new IF
if (nextHandle == null) {
nextHandle = il.append(gotoHandle, NOP);
}
// Make the new IF instruction branch around the GOTO
invertedIfHandle.updateTarget(target, nextHandle);
// If anything still "points" to the old IF instruction,
// make adjustments to refer to either the new IF or GOTO
// instruction
if (oldIfHandle.hasTargeters()) {
InstructionTargeter[] targeters =
oldIfHandle.getTargeters();
for (int i = 0; i < targeters.length; i++) {
InstructionTargeter targeter = targeters[i];
// Ideally, one should simply be able to use
// InstructionTargeter.updateTarget to change
// references to the old IF instruction to the new
// IF instruction. However, if a LocalVariableGen
// indicated the old IF marked the end of the range
// in which the IF variable is in use, the live
// range of the variable must extend to include the
// newly created GOTO instruction. The need for
// this sort of specific knowledge of an
// implementor of the InstructionTargeter interface
// makes the code more fragile. Future implementors
// of the interface might have similar requirements
// which wouldn't be accommodated seemlessly.
if (targeter instanceof LocalVariableGen) {
LocalVariableGen lvg =
(LocalVariableGen) targeter;
if (lvg.getStart() == oldIfHandle) {
lvg.setStart(invertedIfHandle);
} else if (lvg.getEnd() == oldIfHandle) {
lvg.setEnd(gotoHandle);
}
} else {
targeter.updateTarget(oldIfHandle,
invertedIfHandle);
}
}
}
try {
il.delete(oldIfHandle);
} catch (TargetLostException tle) {
// This can never happen - we updated the list of
// instructions that target the deleted instruction
// prior to deleting it.
String msg =
new ErrorMsg(ErrorMsg.OUTLINE_ERR_DELETED_TARGET,
tle.getMessage()).toString();
throw new InternalError(msg);
}
// Adjust the pointer in the InstructionList to point after
// the newly inserted IF instruction
ih = gotoHandle;
// Indicate that this method rewrote at least one IF
ilChanged = true;
}
}
}
// Did this method rewrite any IF instructions?
return ilChanged;
}
}