| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| */ |
| /* |
| * 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.Constants; |
| 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.InstructionConstants; |
| 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.Map; |
| import java.util.Stack; |
| |
| /** |
| * @author Jacek Ambroziak |
| * @author Santiago Pericas-Geertsen |
| */ |
| 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 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 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; |
| } |
| |
| /** |
| * 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(); |
| int prevLocals = maxLocals; |
| |
| // 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 _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 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, |
| 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 |
| // 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; |
| } |
| } |