/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.vm.verifier; import static com.sun.cri.bytecode.Bytecodes.*; import static com.sun.max.vm.verifier.InstructionHandle.Flag.*; import java.io.*; import java.util.*; import com.sun.cri.bytecode.*; import com.sun.max.lang.*; import com.sun.max.util.*; import com.sun.max.vm.*; import com.sun.max.vm.classfile.*; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Branch; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Instruction; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Jsr; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Lookupswitch; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Ret; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Select; import com.sun.max.vm.verifier.TypeInferencingMethodVerifier.Tableswitch; /** * Inlines subroutines and removes dead code. * <p> * This implementation is partially derived from inlinejsr.c, a source file in the preverifier tool that is part of the standard * <a href="http://java.sun.com/javame/index.jsp">Java Platform, Micro Edition</a> distribution. * */ public class SubroutineInliner { private final TypeInferencingMethodVerifier verifier; private final boolean verbose; private final List<InstructionHandle> instructionHandles; /** * Map from each original instruction BCI to the handles representing the copies of the instruction in the * rewritten method. Each original instruction that was in a subroutine may occur more than once in the rewritten method. */ private final InstructionHandle[] instructionMap; public SubroutineInliner(TypeInferencingMethodVerifier verifier, boolean verbose) { this.verifier = verifier; this.verbose = verbose; this.instructionHandles = new ArrayList<InstructionHandle>(); this.instructionMap = new InstructionHandle[verifier.codeAttribute().code().length]; } public CodeAttribute rewriteCode() { rewriteOneSubroutine(SubroutineCall.TOP); final byte[] newCode = fixupCode(); final ExceptionHandlerEntry[] exceptionHandlerTable = fixupExceptionHandlers(newCode); final LineNumberTable lineNumberTable = fixupLineNumberTable(); final LocalVariableTable localVariableTable = fixupLocalVariableTable(); final CodeAttribute oldCodeAttribute = verifier.codeAttribute(); final CodeAttribute newCodeAttribute = new CodeAttribute( oldCodeAttribute.cp, newCode, oldCodeAttribute.maxStack, oldCodeAttribute.maxLocals, exceptionHandlerTable, lineNumberTable, localVariableTable, oldCodeAttribute.stackMapTable()); return newCodeAttribute; } private void rewriteOneSubroutine(SubroutineCall subroutineCall) { int count = 0; final int depth = subroutineCall.depth; final int codeLength = verifier.codeAttribute().code().length; InstructionHandle retHandle = null; InstructionHandle instructionHandle = null; final TypeState[] typeStateMap = verifier.typeStateMap(); int typeStateBCI = 0; while (typeStateBCI < codeLength) { final TypeState typeState = typeStateMap[typeStateBCI]; if (typeState != null && typeState.visited()) { Instruction instruction = typeState.targetedInstruction(); while (true) { final int bci = instruction.bci(); if (subroutineCall.matches(typeState.subroutineFrame())) { instructionHandle = new InstructionHandle(instruction, subroutineCall, instructionMap[bci]); instructionMap[bci] = instructionHandle; instructionHandles.add(instructionHandle); ++count; if (count == 1 && depth > 0) { // This is the first instruction included as part of the subroutine call. final InstructionHandle callerHandle = subroutineCall.caller; final Jsr caller = (Jsr) callerHandle.instruction; if (instruction != caller.target.targetedInstruction()) { // If it's not the target of the JSR that got us here the JSR will be converted into a goto. callerHandle.flag = JSR_TARGETED_GOTO; } } switch (instruction.opcode) { case JSR: case JSR_W: { final Jsr jsr = (Jsr) instruction; if (jsr.ret() == null) { // The subroutine doesn't have a RET instruction so we turn the JSR into a goto. instructionHandle.flag = JSR_SIMPLE_GOTO; } else { instructionHandle.flag = SKIP; } final SubroutineCall innerSubroutine = new SubroutineCall(jsr.target.subroutineFrame().subroutine, subroutineCall, instructionHandle); rewriteOneSubroutine(innerSubroutine); break; } case RET: { assert retHandle == null : "Multiple RET in one subroutine should have been caught during verification"; assert depth != 0 : "RET outside a subroutine should have been caught during verification"; retHandle = instructionHandle; break; } case ASTORE_0: case ASTORE_1: case ASTORE_2: case ASTORE_3: case ASTORE: { if (verifier.isRetBCIStore(instruction)) { instructionHandle.flag = SKIP; } break; } default: // do nothing break; } } if (Bytecodes.isStop(instruction.opcode)) { typeStateBCI = bci + instruction.size(); break; } instruction = instruction.next(); } } else { ++typeStateBCI; } } final int nextHandleIndex = instructionHandles.size(); if (depth > 0) { subroutineCall.setNextInstuctionHandleIndex(nextHandleIndex); if (retHandle != null) { // If the last instruction isn't a RET, convert it into a goto if (retHandle == instructionHandle) { retHandle.flag = SKIP; } else { retHandle.flag = RET_SIMPLE_GOTO; } } } } private byte[] fixupCode() { int bci = 0; for (InstructionHandle instructionHandle : instructionHandles) { instructionHandle.bci = bci; if (instructionHandle.flag != SKIP) { final Instruction instruction = instructionHandle.instruction; switch (instruction.opcode) { case TABLESWITCH: case LOOKUPSWITCH: final int oldPadSize = 3 - instruction.bci() % 4; final int newPadSize = 3 - bci % 4; bci += instruction.size() - oldPadSize + newPadSize; break; case RET: // becomes a goto with a 16-bit offset bci += 3; break; default: bci += instruction.size(); break; } } } if (verbose) { Log.println(); final String methodSignature = verifier.classMethodActor().format("%H.%n(%p)"); Log.println("Rewriting " + methodSignature); } final int newCodeSize = bci; // Create new code array try { final ByteArrayOutputStream newCodeStream = new ByteArrayOutputStream(newCodeSize); final DataOutputStream dataStream = new DataOutputStream(newCodeStream); for (InstructionHandle instructionHandle : instructionHandles) { if (verbose) { Log.println(instructionHandle + " // " + instructionHandle.flag); } if (instructionHandle.flag != SKIP) { final Instruction instruction = instructionHandle.instruction; bci = instructionHandle.bci; assert bci == newCodeStream.size(); final SubroutineCall subroutine = instructionHandle.subroutineCall; if (instruction instanceof Branch) { final Branch branch = (Branch) instruction; final int opcode = instruction.opcode; if (instruction instanceof Jsr) { final Jsr jsr = (Jsr) branch; assert instructionHandle.flag == JSR_SIMPLE_GOTO || instructionHandle.flag == JSR_TARGETED_GOTO; final SubroutineCall innerSubroutine = new SubroutineCall(jsr.target.subroutineFrame().subroutine, subroutine, instructionHandle); if (opcode == JSR_W) { assert instruction.size() == 5; dataStream.write(GOTO_W); dataStream.writeInt(calculateNewOffset(bci, innerSubroutine, branch.target.bci(), Ints.VALUE_RANGE)); } else { assert instruction.size() == 3; dataStream.write(GOTO); dataStream.writeShort(calculateNewOffset(bci, innerSubroutine, branch.target.bci(), Shorts.VALUE_RANGE)); } } else { dataStream.write(opcode); if (opcode == Bytecodes.GOTO_W) { assert instruction.size() == 5; dataStream.writeInt(calculateNewOffset(bci, subroutine, branch.target.bci(), Ints.VALUE_RANGE)); } else { assert instruction.size() == 3; dataStream.writeShort(calculateNewOffset(bci, subroutine, branch.target.bci(), Shorts.VALUE_RANGE)); } } } else { final int opcode = instruction.opcode; switch (opcode) { case RET: { final Ret ret = (Ret) instruction; SubroutineCall callingSuboutine = subroutine; int extraFramesToPop = ret.numberOfFramesPopped() - 1; while (extraFramesToPop > 0) { callingSuboutine = callingSuboutine.parent(); --extraFramesToPop; } final InstructionHandle gotoTarget = instructionHandles.get(callingSuboutine.nextInstuctionHandleIndex()); final int offset = gotoTarget.bci - bci; checkOffset(offset, Shorts.VALUE_RANGE); dataStream.write(GOTO); dataStream.writeShort(offset); break; } case TABLESWITCH: case LOOKUPSWITCH: { final Select select = (Select) instruction; dataStream.write(opcode); final int padding = 3 - bci % 4; // number of pad bytes for (int i = 0; i < padding; i++) { dataStream.writeByte(0); } // Update default target dataStream.writeInt(calculateNewOffset(bci, subroutine, select.defaultTarget.bci(), Ints.VALUE_RANGE)); if (opcode == Bytecodes.TABLESWITCH) { final Tableswitch tableswitch = (Tableswitch) select; dataStream.writeInt(tableswitch.low); dataStream.writeInt(tableswitch.high); for (TypeState target : tableswitch.caseTargets) { dataStream.writeInt(calculateNewOffset(bci, subroutine, target.bci(), Ints.VALUE_RANGE)); } } else { final Lookupswitch lookupswitch = (Lookupswitch) select; final TypeState[] caseTargets = lookupswitch.caseTargets; final int[] matches = lookupswitch.matches; dataStream.writeInt(matches.length); // npairs for (int i = 0; i != matches.length; ++i) { final TypeState target = caseTargets[i]; dataStream.writeInt(matches[i]); dataStream.writeInt(calculateNewOffset(bci, subroutine, target.bci(), Ints.VALUE_RANGE)); } } break; } default: instruction.writeTo(dataStream); break; } } } } dataStream.close(); return newCodeStream.toByteArray(); } catch (IOException ioe) { throw verifier.fatalVerifyError("IO error while fixing up code: " + ioe); } } private void checkOffset(int offset, Range allowableOffsetRange) { if (!allowableOffsetRange.contains(offset)) { verifier.verifyError("Subroutine inlining expansion caused an offset to grow beyond what a branch instruction can encode"); } } /** * Computes the offset for a branch or goto instruction where either it or its target has been inlined * (and thus resides at a new BCI). * * @param fromBCI the (possibly new) BCI of a branch or goto instruction * @param fromSubroutine the subroutine in which the branch or goto instruction resides * @param oldToBCI the old target BCI prior to subroutine inlining * @param allowableOffsetRange the valid value range for the adjusted offset * @return the computed offset * @throws VerifyError if the new offset could not be computed */ private int calculateNewOffset(int fromBCI, SubroutineCall fromSubroutine, int oldToBCI, Range allowableOffsetRange) { for (InstructionHandle target = instructionMap[oldToBCI]; target != null; target = target.next) { if (fromSubroutine.canGoto(target.subroutineCall)) { final int offset = target.bci - fromBCI; checkOffset(offset, allowableOffsetRange); return offset; } } throw verifier.fatalVerifyError("Cannot find new BCI for instruction that used to be at " + oldToBCI); } private ExceptionHandlerEntry[] fixupExceptionHandlers(byte[] newCode) { final CodeAttribute codeAttribute = verifier.codeAttribute(); final ExceptionHandlerEntry[] oldHandlers = codeAttribute.exceptionHandlerTable(); if (oldHandlers.length == 0) { return oldHandlers; } ArrayList<ExceptionHandlerEntry> newHandlers = new ArrayList<ExceptionHandlerEntry>(oldHandlers.length); for (ExceptionHandlerEntry oldHandler : oldHandlers) { // For each instruction handle that maps to this handler, match it to all instructions that go to the handler. for (InstructionHandle handlerHandle = instructionMap[oldHandler.handlerBCI()]; handlerHandle != null; handlerHandle = handlerHandle.next) { // Find all instructions that go to this handler boolean lastMatch = false; ExceptionHandlerEntry currentHandler = null; for (InstructionHandle instructionHandle : instructionHandles) { final Instruction instruction = instructionHandle.instruction; final int bci = instruction.bci(); if (instructionHandle.flag != SKIP) { final boolean match = (bci >= oldHandler.startBCI()) && (bci < oldHandler.endBCI()) && instructionHandle.subroutineCall.canGoto(handlerHandle.subroutineCall); if (match && !lastMatch) { // start a new catch frame currentHandler = new ExceptionHandlerEntry(instructionHandle.bci, oldHandler.endBCI(), handlerHandle.bci, oldHandler.catchTypeIndex()); lastMatch = true; } else if (lastMatch && !match) { currentHandler = currentHandler.changeEndBCI(instructionHandle.bci); newHandlers.add(currentHandler); lastMatch = false; } } } if (lastMatch) { assert !newHandlers.contains(currentHandler); // code end is still in the catch frame currentHandler = currentHandler.changeEndBCI(newCode.length); newHandlers.add(currentHandler); } } } return newHandlers.toArray(new ExceptionHandlerEntry[newHandlers.size()]); } private LineNumberTable fixupLineNumberTable() { final CodeAttribute codeAttribute = verifier.codeAttribute(); final LineNumberTable lineNumberTable = codeAttribute.lineNumberTable(); if (lineNumberTable.isEmpty()) { return LineNumberTable.EMPTY; } // Expand the original line number tables into a map that covers every instruction BCI in the original code. final int oldCodeLength = codeAttribute.code().length; final int[] oldBCIToLineNumberMap = new int[oldCodeLength]; final LineNumberTable.Entry[] entries = lineNumberTable.entries(); int i = 0; int endPc; int line; int pc; // Process all but the last line number table entry for (; i < entries.length - 1; i++) { line = entries[i].lineNumber(); endPc = entries[i + 1].bci(); for (pc = entries[i].bci(); pc < endPc; pc++) { oldBCIToLineNumberMap[pc] = line; } } // Process the last line number table entry line = entries[i].lineNumber(); for (pc = entries[i].bci(); pc < oldCodeLength; pc++) { oldBCIToLineNumberMap[pc] = line; } int currentLineNumber = -1; final List<LineNumberTable.Entry> newEntries = new ArrayList<LineNumberTable.Entry>(); for (InstructionHandle instructionHandle : instructionHandles) { if (instructionHandle.flag != SKIP) { final Instruction instruction = instructionHandle.instruction; final int nextLineNumber = oldBCIToLineNumberMap[instruction.bci()]; if (nextLineNumber != currentLineNumber) { final LineNumberTable.Entry entry = new LineNumberTable.Entry((char) instructionHandle.bci, (char) nextLineNumber); newEntries.add(entry); currentLineNumber = nextLineNumber; } } } return new LineNumberTable(newEntries.toArray(new LineNumberTable.Entry[newEntries.size()])); } private LocalVariableTable fixupLocalVariableTable() { final CodeAttribute codeAttribute = verifier.codeAttribute(); final LocalVariableTable localVariableTable = codeAttribute.localVariableTable(); if (localVariableTable.isEmpty()) { return LocalVariableTable.EMPTY; } final ArrayList<LocalVariableTable.Entry> newEntries = new ArrayList<LocalVariableTable.Entry>(); final LocalVariableTable.Entry[] entries = localVariableTable.entries(); for (LocalVariableTable.Entry entry : entries) { final int startPc = entry.startBCI(); final int endPc = startPc + entry.length(); // inclusive InstructionHandle lastMatchedHandle = null; int lastMatchedStartPc = -1; for (InstructionHandle instructionHandle : instructionHandles) { if (instructionHandle.flag != SKIP) { final Instruction instruction = instructionHandle.instruction; final boolean matches = instruction.bci() >= startPc && instruction.bci() <= endPc; if (lastMatchedHandle == null && matches) { lastMatchedStartPc = instructionHandle.bci; lastMatchedHandle = instructionHandle; } else if (lastMatchedHandle != null && !matches) { final LocalVariableTable.Entry newEntry = new LocalVariableTable.Entry((char) lastMatchedStartPc, (char) (lastMatchedHandle.bci - lastMatchedStartPc), (char) entry.slot(), (char) entry.nameIndex(), (char) entry.descriptorIndex(), (char) entry.signatureIndex()); newEntries.add(newEntry); lastMatchedHandle = null; } } } if (lastMatchedHandle != null) { final LocalVariableTable.Entry newEntry = new LocalVariableTable.Entry((char) lastMatchedStartPc, (char) (lastMatchedHandle.bci - lastMatchedStartPc), (char) entry.slot(), (char) entry.nameIndex(), (char) entry.descriptorIndex(), (char) entry.signatureIndex()); newEntries.add(newEntry); } } return new LocalVariableTable(newEntries); } }