|
|
|
|
|
*/ |
|
/* |
|
* 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: MethodGenerator.java,v 1.2.4.1 2005/09/05 11:16:47 pvedula Exp $ |
|
*/ |
|
|
|
package com.sun.org.apache.xalan.internal.xsltc.compiler.util; |
|
|
|
import com.sun.org.apache.bcel.internal.Const; |
|
import com.sun.org.apache.bcel.internal.classfile.Field; |
|
import com.sun.org.apache.bcel.internal.classfile.Method; |
|
import com.sun.org.apache.bcel.internal.generic.ALOAD; |
|
import com.sun.org.apache.bcel.internal.generic.ASTORE; |
|
import com.sun.org.apache.bcel.internal.generic.BranchHandle; |
|
import com.sun.org.apache.bcel.internal.generic.BranchInstruction; |
|
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen; |
|
import com.sun.org.apache.bcel.internal.generic.DLOAD; |
|
import com.sun.org.apache.bcel.internal.generic.DSTORE; |
|
import com.sun.org.apache.bcel.internal.generic.FLOAD; |
|
import com.sun.org.apache.bcel.internal.generic.FSTORE; |
|
import com.sun.org.apache.bcel.internal.generic.GETFIELD; |
|
import com.sun.org.apache.bcel.internal.generic.GOTO; |
|
import com.sun.org.apache.bcel.internal.generic.ICONST; |
|
import com.sun.org.apache.bcel.internal.generic.ILOAD; |
|
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE; |
|
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL; |
|
import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC; |
|
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL; |
|
import com.sun.org.apache.bcel.internal.generic.ISTORE; |
|
import com.sun.org.apache.bcel.internal.generic.IfInstruction; |
|
import com.sun.org.apache.bcel.internal.generic.IndexedInstruction; |
|
import com.sun.org.apache.bcel.internal.generic.Instruction; |
|
import com.sun.org.apache.bcel.internal.generic.InstructionConst; |
|
import com.sun.org.apache.bcel.internal.generic.InstructionHandle; |
|
import com.sun.org.apache.bcel.internal.generic.InstructionList; |
|
import com.sun.org.apache.bcel.internal.generic.InstructionTargeter; |
|
import com.sun.org.apache.bcel.internal.generic.LLOAD; |
|
import com.sun.org.apache.bcel.internal.generic.LSTORE; |
|
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen; |
|
import com.sun.org.apache.bcel.internal.generic.LocalVariableInstruction; |
|
import com.sun.org.apache.bcel.internal.generic.MethodGen; |
|
import com.sun.org.apache.bcel.internal.generic.NEW; |
|
import com.sun.org.apache.bcel.internal.generic.PUTFIELD; |
|
import com.sun.org.apache.bcel.internal.generic.RET; |
|
import com.sun.org.apache.bcel.internal.generic.Select; |
|
import com.sun.org.apache.bcel.internal.generic.TargetLostException; |
|
import com.sun.org.apache.bcel.internal.generic.Type; |
|
import com.sun.org.apache.xalan.internal.xsltc.compiler.Pattern; |
|
import com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC; |
|
import java.util.ArrayList; |
|
import java.util.Collections; |
|
import java.util.HashMap; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Stack; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class MethodGenerator extends MethodGen |
|
implements com.sun.org.apache.xalan.internal.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 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; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Map<Pattern, InstructionList> _preCompiled = new HashMap<>(); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected class LocalVariableRegistry { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected List<Object> _variables = new ArrayList<>(); |
|
|
|
|
|
|
|
*/ |
|
protected Map<String, Object> _nameToLVGMap = new HashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
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 |
|
|
|
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 |
|
|
|
Object localsInSlot = _variables.get(slot); |
|
if (localsInSlot != null) { |
|
if (localsInSlot instanceof LocalVariableGen) { |
|
List<LocalVariableGen> listOfLocalsInSlot = new ArrayList<>(); |
|
listOfLocalsInSlot.add((LocalVariableGen)localsInSlot); |
|
listOfLocalsInSlot.add(lvg); |
|
_variables.set(slot, listOfLocalsInSlot); |
|
} else { |
|
((List<LocalVariableGen>) localsInSlot).add(lvg); |
|
} |
|
} else { |
|
_variables.set(slot, lvg); |
|
} |
|
} |
|
|
|
registerByName(lvg); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (localsInSlot != null) { |
|
if (localsInSlot instanceof LocalVariableGen) { |
|
LocalVariableGen lvg = (LocalVariableGen)localsInSlot; |
|
if (offsetInLocalVariableGenRange(lvg, offset)) { |
|
return lvg; |
|
} |
|
} else { |
|
@SuppressWarnings("unchecked") |
|
List<LocalVariableGen> listOfLocalsInSlot = |
|
(List<LocalVariableGen>) localsInSlot; |
|
|
|
for (LocalVariableGen lvg : listOfLocalsInSlot) { |
|
if (offsetInLocalVariableGenRange(lvg, offset)) { |
|
return lvg; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected void registerByName(LocalVariableGen lvg) { |
|
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName()); |
|
|
|
if (duplicateNameEntry == null) { |
|
_nameToLVGMap.put(lvg.getName(), lvg); |
|
} else { |
|
List<LocalVariableGen> sameNameList; |
|
|
|
if (duplicateNameEntry instanceof ArrayList) { |
|
sameNameList = (List<LocalVariableGen>)duplicateNameEntry; |
|
sameNameList.add(lvg); |
|
} else { |
|
sameNameList = new ArrayList<>(); |
|
sameNameList.add((LocalVariableGen)duplicateNameEntry); |
|
sameNameList.add(lvg); |
|
} |
|
|
|
_nameToLVGMap.put(lvg.getName(), sameNameList); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected void removeByNameTracking(LocalVariableGen lvg) { |
|
Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName()); |
|
|
|
if (duplicateNameEntry instanceof ArrayList) { |
|
List<LocalVariableGen> sameNameList = |
|
(List<LocalVariableGen>)duplicateNameEntry; |
|
for (int i = 0; i < sameNameList.size(); i++) { |
|
if (sameNameList.get(i) == lvg) { |
|
sameNameList.remove(i); |
|
break; |
|
} |
|
} |
|
} else { |
|
_nameToLVGMap.remove(lvg); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected LocalVariableGen lookUpByName(String name) { |
|
LocalVariableGen lvg = null; |
|
Object duplicateNameEntry = _nameToLVGMap.get(name); |
|
|
|
if (duplicateNameEntry instanceof ArrayList) { |
|
List<LocalVariableGen> sameNameList = |
|
(List<LocalVariableGen>)duplicateNameEntry; |
|
|
|
for (int i = 0; i < sameNameList.size(); i++) { |
|
lvg = sameNameList.get(i); |
|
if (lvg.getName() == null ? name == null : lvg.getName().equals(name)) { |
|
break; |
|
} |
|
} |
|
} else { |
|
lvg = (LocalVariableGen) duplicateNameEntry; |
|
} |
|
|
|
return lvg; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("unchecked") |
|
protected LocalVariableGen[] getLocals(boolean includeRemoved) { |
|
LocalVariableGen[] locals = null; |
|
List<LocalVariableGen> 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) { |
|
List<LocalVariableGen> slotList = |
|
(List<LocalVariableGen>)slotEntries; |
|
|
|
for (int j = 0; j < slotList.size(); j++) { |
|
allVarsEverDeclared.add(slotList.get(i)); |
|
} |
|
} else { |
|
allVarsEverDeclared.add((LocalVariableGen)slotEntries); |
|
} |
|
} |
|
} |
|
} else { |
|
for (Map.Entry<String, Object> nameVarsPair : _nameToLVGMap.entrySet()) { |
|
Object vars = nameVarsPair.getValue(); |
|
if (vars != null) { |
|
if (vars instanceof ArrayList) { |
|
List<LocalVariableGen> varsList = |
|
(List<LocalVariableGen>) vars; |
|
for (int i = 0; i < varsList.size(); i++) { |
|
allVarsEverDeclared.add(varsList.get(i)); |
|
} |
|
} else { |
|
allVarsEverDeclared.add((LocalVariableGen)vars); |
|
} |
|
} |
|
} |
|
} |
|
|
|
locals = new LocalVariableGen[allVarsEverDeclared.size()]; |
|
allVarsEverDeclared.toArray(locals); |
|
|
|
return locals; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (lvgStart == null) { |
|
lvgStart = getInstructionList().getStart(); |
|
} |
|
|
|
// If no end handle is recorded for the LocalVariableGen, it is assumed |
|
|
|
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 |
|
|
|
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"))); |
|
} |
|
|
|
|
|
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() { |
|
|
|
|
|
int maxLocals = super.getMaxLocals(); |
|
int prevLocals = maxLocals; |
|
|
|
|
|
final LocalVariableGen[] localVars = super.getLocalVariables(); |
|
if (localVars != null) { |
|
if (localVars.length > maxLocals) |
|
maxLocals = localVars.length; |
|
} |
|
|
|
|
|
if (maxLocals < 5) maxLocals = 5; |
|
|
|
super.setMaxLocals(maxLocals); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void addInstructionList(Pattern pattern, InstructionList ilist) { |
|
_preCompiled.put(pattern, ilist); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public InstructionList getInstructionList(Pattern pattern) { |
|
return _preCompiled.get(pattern); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private class Chunk implements Comparable<Object> { |
|
|
|
|
|
|
|
*/ |
|
private InstructionHandle m_start; |
|
|
|
|
|
|
|
|
|
*/ |
|
private InstructionHandle m_end; |
|
|
|
|
|
|
|
|
|
*/ |
|
private int m_size; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Chunk(InstructionHandle start, InstructionHandle end) { |
|
m_start = start; |
|
m_end = end; |
|
m_size = end.getPosition() - start.getPosition(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean isAdjacentTo(Chunk neighbour) { |
|
return getChunkEnd().getNext() == neighbour.getChunkStart(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
InstructionHandle getChunkStart() { |
|
return m_start; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
InstructionHandle getChunkEnd() { |
|
return m_end; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int getChunkSize() { |
|
return m_size; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int compareTo(Object comparand) { |
|
return getChunkSize() - ((Chunk)comparand).getChunkSize(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private List<Chunk> getCandidateChunks(ClassGenerator classGen, |
|
int totalMethodSize) { |
|
Iterator<InstructionHandle> instructions = getInstructionList().iterator(); |
|
List<Chunk> candidateChunks = new ArrayList<>(); |
|
List<InstructionHandle> currLevelChunks = new ArrayList<>(); |
|
Stack<List<InstructionHandle>> 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 |
|
|
|
do { |
|
// Get the next instruction. The loop will perform one extra |
|
// iteration after it reaches the end of the InstructionList, with |
|
|
|
currentHandle = instructions.hasNext() |
|
? 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 - |
|
|
|
if (firstInstruction) { |
|
openChunkAtCurrLevel = true; |
|
currLevelChunks.add(currentHandle); |
|
firstInstruction = false; |
|
} |
|
|
|
|
|
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 |
|
|
|
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) { |
|
List<InstructionHandle> 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 |
|
|
|
if (!openChunkAtCurrLevel) { |
|
nestedSubChunks = currLevelChunks; |
|
currLevelChunks = subChunkStack.pop(); |
|
} |
|
|
|
// Get the handle for the start of this chunk (the last entry |
|
|
|
InstructionHandle chunkStart = |
|
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 |
|
|
|
if (chunkSize <= TARGET_METHOD_SIZE) { |
|
currLevelChunks.add(currentHandle); |
|
} else { |
|
if (!openChunkAtCurrLevel) { |
|
int childChunkCount = nestedSubChunks.size() / 2; |
|
if (childChunkCount > 0) { |
|
Chunk[] childChunks = new Chunk[childChunkCount]; |
|
|
|
|
|
for (int i = 0; i < childChunkCount; i++) { |
|
InstructionHandle start = nestedSubChunks.get(i*2); |
|
InstructionHandle end = nestedSubChunks.get(i*2+1); |
|
|
|
childChunks[i] = new Chunk(start, end); |
|
} |
|
|
|
|
|
List<Chunk> mergedChildChunks = |
|
mergeAdjacentChunks(childChunks); |
|
|
|
// Add chunks that mean minimum size requirements |
|
|
|
for (Chunk mergedChunk : mergedChildChunks) { |
|
int mergedSize = mergedChunk.getChunkSize(); |
|
|
|
if (mergedSize >= MINIMUM_OUTLINEABLE_CHUNK_SIZE |
|
&& mergedSize <= TARGET_METHOD_SIZE) { |
|
candidateChunks.add(mergedChunk); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
currLevelChunks.remove(currLevelChunks.size() - 1); |
|
} |
|
|
|
// currLevelChunks contains pairs of InstructionHandles. If |
|
// its size is an odd number, the loop has encountered the |
|
|
|
openChunkAtCurrLevel = ((currLevelChunks.size() & 0x1) == 1); |
|
} |
|
|
|
} while (currentHandle != null); |
|
|
|
return candidateChunks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private List<Chunk> 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; |
|
|
|
List<Chunk> 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 |
|
|
|
for (int i = 1; i < chunks.length; i++) { |
|
if (!chunks[i-1].isAdjacentTo(chunks[i])) { |
|
int lengthOfRun = i - startOfCurrentRun; |
|
|
|
|
|
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; |
|
|
|
|
|
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 |
|
|
|
for (int numToMerge = maximumRunOfChunks; numToMerge>1; numToMerge--) { |
|
|
|
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 |
|
|
|
for (int mergeStart = runStart; |
|
mergeStart+numToMerge-1 <= runEnd && !foundChunksToMerge; |
|
mergeStart++) { |
|
int mergeEnd = mergeStart + numToMerge - 1; |
|
int mergeSize = 0; |
|
|
|
|
|
for (int j = mergeStart; j <= mergeEnd; j++) { |
|
mergeSize = mergeSize + chunks[j].getChunkSize(); |
|
} |
|
|
|
// If the current subrun is small enough to outline, |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
for (int i = 0; i < chunks.length; i++) { |
|
if (!chunkWasMerged[i]) { |
|
mergedChunks.add(chunks[i]); |
|
} |
|
} |
|
|
|
return mergedChunks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Method[] outlineChunks(ClassGenerator classGen, |
|
int originalMethodSize) { |
|
List<Method> 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, |
|
|
|
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 |
|
|
|
do { |
|
// Get all the best candidates for outlining, and sort them in |
|
|
|
List<Chunk> 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 |
|
|
|
for (int i = candidateChunks.size()-1; |
|
i >= 0 && currentMethodSize > TARGET_METHOD_SIZE; |
|
i--) { |
|
Chunk chunkToOutline = 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(); |
|
|
|
|
|
currentMethodSize = |
|
lastInst.getPosition() |
|
+ lastInst.getInstruction().getLength(); |
|
} |
|
} while (moreMethodsOutlined && currentMethodSize > TARGET_METHOD_SIZE); |
|
|
|
// Outlining failed to reduce the size of the current method |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Method outline(InstructionHandle first, InstructionHandle last, |
|
String outlinedMethodName, ClassGenerator classGen) { |
|
|
|
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 |
|
|
|
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, |
|
com.sun.org.apache.bcel.internal.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 |
|
|
|
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); |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
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; |
|
|
|
InstructionHandle outlinedMethodCallSetup = |
|
oldMethCopyInIL.append(new NEW(cpg.addClass(argTypeName))); |
|
oldMethCopyInIL.append(InstructionConst.DUP); |
|
oldMethCopyInIL.append(InstructionConst.DUP); |
|
oldMethCopyInIL.append( |
|
new INVOKESPECIAL(cpg.addMethodref(argTypeName, "<init>", "()V"))); |
|
|
|
// Generate code to invoke the new outlined method, and place the code |
|
|
|
InstructionHandle outlinedMethodRef; |
|
|
|
if (isStaticMethod) { |
|
outlinedMethodRef = |
|
oldMethCopyOutIL.append( |
|
new INVOKESTATIC(cpg.addMethodref( |
|
classGen.getClassName(), |
|
outlinedMethodName, |
|
outlinedMethodGen.getSignature()))); |
|
} else { |
|
oldMethCopyOutIL.append(InstructionConst.THIS); |
|
oldMethCopyOutIL.append(InstructionConst.SWAP); |
|
outlinedMethodRef = |
|
oldMethCopyOutIL.append( |
|
new INVOKEVIRTUAL(cpg.addMethodref( |
|
classGen.getClassName(), |
|
outlinedMethodName, |
|
outlinedMethodGen.getSignature()))); |
|
} |
|
|
|
// Used to keep track of the first in a sequence of |
|
|
|
boolean chunkStartTargetMappingsPending = false; |
|
InstructionHandle pendingTargetMappingHandle = null; |
|
|
|
|
|
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 |
|
|
|
HashMap<InstructionHandle, InstructionHandle> targetMap = new HashMap<>(); |
|
|
|
// Keeps track of the mapping from local variables in the old method |
|
|
|
HashMap<LocalVariableGen, LocalVariableGen> localVarMap = new HashMap<>(); |
|
|
|
HashMap<LocalVariableGen, InstructionHandle> revisedLocalVarStart = new HashMap<>(); |
|
HashMap<LocalVariableGen, InstructionHandle> 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 |
|
|
|
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 |
|
|
|
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(); |
|
|
|
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 |
|
|
|
IndexedInstruction lvi = (IndexedInstruction)c; |
|
int oldLocalVarIndex = lvi.getIndex(); |
|
LocalVariableGen oldLVG = |
|
getLocalVariableRegistry() |
|
.lookupRegisteredLocalVariable(oldLocalVarIndex, |
|
ih.getPosition()); |
|
LocalVariableGen newLVG = localVarMap.get(oldLVG); |
|
|
|
// Has the code already mapped this local variable to a |
|
|
|
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 |
|
|
|
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 |
|
|
|
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(); |
|
|
|
|
|
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 |
|
|
|
oldMethCopyInIL.append( |
|
InstructionConst.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 |
|
|
|
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 |
|
|
|
newMethCopyInIL.append( |
|
InstructionConst.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 |
|
|
|
newMethCopyOutIL.append( |
|
InstructionConst.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 |
|
|
|
oldMethCopyOutIL.append( |
|
InstructionConst.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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
InstructionHandle ih = first; |
|
InstructionHandle ch = newIL.getStart(); |
|
|
|
while (ch != null) { |
|
|
|
Instruction i = ih.getInstruction(); |
|
Instruction c = ch.getInstruction(); |
|
|
|
if (i instanceof BranchInstruction) { |
|
BranchInstruction bc = (BranchInstruction)c; |
|
BranchInstruction bi = (BranchInstruction)i; |
|
InstructionHandle itarget = bi.getTarget(); |
|
|
|
|
|
InstructionHandle newTarget = targetMap.get(itarget); |
|
|
|
bc.setTarget(newTarget); |
|
|
|
// Handle LOOKUPSWITCH or TABLESWITCH which may have many |
|
|
|
if (bi instanceof Select) { |
|
InstructionHandle[] itargets = ((Select)bi).getTargets(); |
|
InstructionHandle[] ctargets = ((Select)bc).getTargets(); |
|
|
|
|
|
for (int j=0; j < itargets.length; j++) { |
|
ctargets[j] = 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 |
|
|
|
IndexedInstruction lvi = (IndexedInstruction)c; |
|
int oldLocalVarIndex = lvi.getIndex(); |
|
LocalVariableGen oldLVG = |
|
getLocalVariableRegistry() |
|
.lookupRegisteredLocalVariable(oldLocalVarIndex, |
|
ih.getPosition()); |
|
LocalVariableGen newLVG = 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. |
|
|
|
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 |
|
|
|
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 |
|
|
|
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) { |
|
LocalVariableGen newLVG = localVarMap.get(targeter); |
|
if (newLVG != null) { |
|
outlinedMethodGen.removeLocalVariable(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 |
|
|
|
if (!(i instanceof MarkerInstruction)) { |
|
ch = ch.getNext(); |
|
} |
|
ih = ih.getNext(); |
|
|
|
} |
|
|
|
|
|
oldMethCopyOutIL.append(InstructionConst.POP); |
|
|
|
for (Map.Entry<LocalVariableGen, InstructionHandle> lvgRangeStartPair : |
|
revisedLocalVarStart.entrySet()) { |
|
LocalVariableGen lvg = lvgRangeStartPair.getKey(); |
|
InstructionHandle startInst = lvgRangeStartPair.getValue(); |
|
|
|
lvg.setStart(startInst); |
|
} |
|
|
|
for (Map.Entry<LocalVariableGen, InstructionHandle> lvgRangeEndPair : |
|
revisedLocalVarEnd.entrySet()) { |
|
LocalVariableGen lvg = lvgRangeEndPair.getKey(); |
|
InstructionHandle endInst = lvgRangeEndPair.getValue(); |
|
|
|
lvg.setEnd(endInst); |
|
} |
|
|
|
xsltc.dumpClass(copyAreaCG.getJavaClass()); |
|
|
|
// Assemble the instruction lists so that the old method invokes the |
|
|
|
InstructionList oldMethodIL = getInstructionList(); |
|
|
|
oldMethodIL.insert(first, oldMethCopyInIL); |
|
oldMethodIL.insert(first, oldMethCopyOutIL); |
|
|
|
|
|
newIL.insert(newMethCopyInIL); |
|
newIL.append(newMethCopyOutIL); |
|
newIL.append(InstructionConst.RETURN); |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
if (lvgTargeter.getStart() == lostTarget) { |
|
lvgTargeter.setStart(outlinedMethodRef); |
|
} |
|
if (lvgTargeter.getEnd() == lostTarget) { |
|
lvgTargeter.setEnd(outlinedMethodRef); |
|
} |
|
} else { |
|
targeters[j].updateTarget(lostTarget, |
|
outlinedMethodCallSetup); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
String[] exceptions = getExceptions(); |
|
for (int i = 0; i < exceptions.length; i++) { |
|
outlinedMethodGen.addException(exceptions[i]); |
|
} |
|
|
|
return outlinedMethodGen.getThisMethod(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private int m_totalChunks = 0; |
|
|
|
|
|
|
|
|
|
*/ |
|
private int m_openChunks = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void markChunkStart() { |
|
|
|
getInstructionList() |
|
.append(OutlineableChunkStart.OUTLINEABLECHUNKSTART); |
|
m_totalChunks++; |
|
m_openChunks++; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void 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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (instructionListSize > MAX_BRANCH_TARGET_OFFSET) { |
|
boolean ilChanged = widenConditionalBranchTargetOffsets(); |
|
|
|
// If any branch instructions needed widening, recompute the size |
|
|
|
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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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. |
|
|
|
case Const.GOTO: |
|
case Const.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(), |
|
|
|
case Const.TABLESWITCH: |
|
case Const.LOOKUPSWITCH: |
|
maxOffsetChange = maxOffsetChange + 3; |
|
break; |
|
// Instructions that might be rewritten by this method as a |
|
// conditional branch followed by an unconditional branch. |
|
|
|
case Const.IF_ACMPEQ: |
|
case Const.IF_ACMPNE: |
|
case Const.IF_ICMPEQ: |
|
case Const.IF_ICMPGE: |
|
case Const.IF_ICMPGT: |
|
case Const.IF_ICMPLE: |
|
case Const.IF_ICMPLT: |
|
case Const.IF_ICMPNE: |
|
case Const.IFEQ: |
|
case Const.IFGE: |
|
case Const.IFGT: |
|
case Const.IFLE: |
|
case Const.IFLT: |
|
case Const.IFNE: |
|
case Const.IFNONNULL: |
|
case Const.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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
InstructionHandle nextHandle = oldIfHandle.getNext(); |
|
IfInstruction invertedIfInst = oldIfInst.negate(); |
|
BranchHandle invertedIfHandle = il.append(oldIfHandle, |
|
invertedIfInst); |
|
|
|
// Append an unconditional branch to the target of the |
|
|
|
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 |
|
|
|
if (nextHandle == null) { |
|
nextHandle = il.append(gotoHandle, InstructionConst.NOP); |
|
} |
|
|
|
|
|
invertedIfHandle.updateTarget(target, nextHandle); |
|
|
|
// If anything still "points" to the old IF instruction, |
|
// make adjustments to refer to either the new IF or GOTO |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
ih = gotoHandle; |
|
|
|
|
|
ilChanged = true; |
|
} |
|
} |
|
} |
|
|
|
|
|
return ilChanged; |
|
} |
|
} |