/* * Copyright (c) 2007, 2012, 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.bytecode.refmaps; import static com.sun.cri.bytecode.Bytecodes.*; import java.util.*; import com.sun.cri.bytecode.*; import com.sun.max.annotate.*; import com.sun.max.program.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.bytecode.*; import com.sun.max.vm.classfile.*; import com.sun.max.vm.classfile.constant.*; import com.sun.max.vm.classfile.stackmap.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.thread.*; import com.sun.max.vm.type.*; import com.sun.max.vm.verifier.types.*; /** * An abstract interpreter for computing the reference map for the local variables and operand stack at any BCI in * a method. Each concrete interpreter denotes if it is suitable for use in an * {@linkplain #performsAllocation() allocation free} context such as during a garbage collection. */ public abstract class ReferenceMapInterpreter { private static String TraceRefMapInterpretationOf; static { VMOptions.addFieldOption("-XX:", "TraceRefMapInterpretationOf", "Trace ref map interpretation of methods whose name or declaring class contains <value>."); } /** * Initializes frames for each basic block in the {@linkplain ReferenceMapInterpreterContext#classMethodActor() method} * associated with a given interpretation context. The returned frames are encoded in a format unique to a specific * interpreter which can be retrieved by calling {@link #from(Object)} on the returned value. */ public static Object createFrames(ReferenceMapInterpreterContext context) { final CodeAttribute codeAttribute = context.codeAttribute(); final int maxStack = codeAttribute.maxStack; final int maxLocals = codeAttribute.maxLocals; final ReferenceMapInterpreter interpreter; if (maxStack <= CompactReferenceMapInterpreter.MAX_STACK && (maxStack + maxLocals) < CompactReferenceMapInterpreter.MAX_SLOTS) { // Cannot use the shared thread-local compact reference map interpreter (i.e. VmThread.current().compactReferenceMapInterpreter()) // here as a GC may be triggered while initializing the frames. interpreter = new CompactReferenceMapInterpreter(); } else { interpreter = new StandardReferenceMapInterpreter(); } return interpreter.createFrames0(context); } /** * Gets the interpreter that compatible with the frames encoded in {@code blockFrames}. * * @param blockFrames a value that was obtained by calling {@link #createFrames(ReferenceMapInterpreterContext)}. */ public static ReferenceMapInterpreter from(Object blockFrames) { if (blockFrames instanceof int[]) { return VmThread.current().compactReferenceMapInterpreter(); } assert blockFrames instanceof StandardReferenceMapInterpreter.Frame[]; return new StandardReferenceMapInterpreter(); } private ConstantPool constantPool; private CodeAttribute codeAttribute; private byte[] code; private int bci; private int sp; private ReferenceMapInterpreterContext context; protected int maxStack() { return codeAttribute.maxStack; } protected int maxLocals() { return codeAttribute.maxLocals; } /** * Merges the current frame state into the block denoted by a given block index. * * @param targetBlockIndex * @return true if the target block's frame state was changed as a result of the merge */ protected abstract boolean mergeInto(int targetBlockIndex, int stackDepth); /** * Determines if a given {@linkplain VerificationType verification type} denotes a reference type. This method is * necessary as the verification type hierarchy does not model the special {@linkplain Word word} types. Decoding of * a verification type occurs in the context of a method as certain verification types refer to the constant pool * and BCIs of the method. * * @param receiverTypeIsWord specifies if the type of 'this' in the enclosing context is a Word type. If the * enclosing context is a static method, this value will be {@code false}. * @param code the bytecode of the enclosing context * @param constantPool the constant pool of the enclosing context * @param type the verification type to be tested * @return true if {@code type} denotes a reference kind */ public static boolean isReference(boolean receiverTypeIsWord, byte[] code, ConstantPool constantPool, VerificationType type) { if (VerificationType.UNINITIALIZED_THIS == type) { return !receiverTypeIsWord; } else if (VerificationType.UNINITIALIZED.isAssignableFrom(type)) { final UninitializedNewType uninitializedNewType = (UninitializedNewType) type; final int bciOfNew = uninitializedNewType.bci(); final int constantPoolIndex = ((code[bciOfNew + 1] & 0xFF) << 8) | (code[bciOfNew + 2] & 0xFF); final TypeDescriptor typeDescriptor = constantPool.classAt(constantPoolIndex).typeDescriptor(); return typeDescriptor.toKind().isReference; } else if (VerificationType.REFERENCE.isAssignableFrom(type)) { final TypeDescriptor typeDescriptor = type.typeDescriptor(); if (typeDescriptor == null || typeDescriptor.toKind().isReference) { return true; } } return false; } private boolean merge(int targetBlockIndex) { return mergeInto(targetBlockIndex, sp); } private boolean merge(int targetBlock1Index, int targetBlock2Index) { return merge(targetBlock1Index) | merge(targetBlock2Index); } /** * Performs any initialization necessary before interpretation begins. * <p> * Sub-classes must override this method to either get a hold of the block frames from {@code context} or allocate * them if {@link ReferenceMapInterpreterContext#blockFrames()} returns null. The overriding method must also initially * call {@code super.resetInterpreter(context)}. */ protected void resetInterpreter(ReferenceMapInterpreterContext context) { final CodeAttribute codeAttribute = context.codeAttribute(); this.constantPool = codeAttribute.cp; this.codeAttribute = codeAttribute; this.code = codeAttribute.code(); this.context = context; this.sp = 0; this.bci = -1; } /** * Determines if this interpreter performs any allocation during a call to * {@link #finalizeFrames(ReferenceMapInterpreterContext)} or * {@link #interpretReferenceSlots(ReferenceMapInterpreterContext, ReferenceSlotVisitor, BCIIterator)}. */ public abstract boolean performsAllocation(); /** * Helper class used by {@link ReferenceMapInterpreter#createFrames0(ReferenceMapInterpreterContext)}. */ class FramesInitialization implements FrameModel, VerificationRegistry { int activeLocals; final boolean[] isSecondDoubleWord; public FramesInitialization() { isSecondDoubleWord = new boolean[codeAttribute.maxLocals]; } // Implementation of ParameterVisitor /** * Interprets the parameters in a method's signature to initialize the frame state of the entry block. */ public void interpret(SignatureDescriptor signature) { for (int i = 0; i < signature.numberOfParameters(); ++i) { final TypeDescriptor parameter = signature.parameterDescriptorAt(i); final Kind parameterKind = parameter.toKind(); if (parameterKind.isReference) { updateLocal(activeLocals, true); } activeLocals += parameterKind.stackSlots; } } // Implementation of FrameModel public int activeLocals() { return activeLocals; } public void chopLocals(int numberOfLocals) { for (int i = 0; i < numberOfLocals; i++) { if (isSecondDoubleWord[activeLocals - 1]) { isSecondDoubleWord[activeLocals - 1] = false; activeLocals -= 2; } else { activeLocals--; } } adjustCurrentFrame(); } public void clear() { activeLocals = 0; sp = 0; Arrays.fill(isSecondDoubleWord, false); adjustCurrentFrame(); } public void clearStack() { sp = 0; adjustCurrentFrame(); } private void adjustCurrentFrame() { for (int sp = ReferenceMapInterpreter.this.sp; sp < maxStack(); ++sp) { updateStack(sp, false); } for (int local = activeLocals; local < maxLocals(); ++local) { updateLocal(local, false); } } private boolean isReference(VerificationType type) { return ReferenceMapInterpreter.isReference(context.classMethodActor().holder().kind.isWord, code, constantPool, type); } public void push(VerificationType type) { if (type.isCategory2()) { pushCategory2(); } else { pushCategory1(isReference(type)); } } public void store(VerificationType type, int index) { if (type.isCategory2()) { storeCategory2(index); isSecondDoubleWord[index] = false; isSecondDoubleWord[index + 1] = true; activeLocals = Math.max(activeLocals, index + 2); } else { isSecondDoubleWord[index] = false; storeCategory1(index, isReference(type)); activeLocals = Math.max(activeLocals, index + 1); } } // Implementation of VerificationRegistry public int clearSubroutines() { throw ProgramError.unexpected(); } public ConstantPool constantPool() { return constantPool; } public VerificationType getObjectType(TypeDescriptor typeDescriptor) { if (typeDescriptor.toKind().isWord) { return VerificationType.WORD; } return VerificationType.OBJECT; } public Subroutine getSubroutine(int entryBCI, int maxLocals) { throw ProgramError.unexpected(); } public UninitializedNewType getUninitializedNewType(int bci) { return new UninitializedNewType(bci); } public VerificationType getVerificationType(TypeDescriptor typeDescriptor) { return VerificationType.getVerificationType(typeDescriptor, this); } public ClassActor resolve(TypeDescriptor type) { throw ProgramError.unexpected(); } } /** * Creates a set of frames to be used when interpreting the * {@linkplain ReferenceMapInterpreterContext#classMethodActor() method} associated with a given interpretation context. * The first frame is initialized based on the signature of the method. Additionally, if the method has a * {@linkplain CodeAttribute#stackMapTable() stack map attribute}, then the frames for the blocks that have a * corresponding entry in the stack map table are initialized from the entry. * * TODO: The motivation for initializing frames based on a StackMapTable attribute is to reduce the number of * iterations performed in {@link #finalizeFrames(ReferenceMapInterpreterContext)}. Analysis is still required * to determine if this trade-off provides a win in the common case. */ private Object createFrames0(ReferenceMapInterpreterContext context) { resetInterpreter(context); final FramesInitialization framesInitialization = new FramesInitialization(); final ClassMethodActor classMethodActor = context.classMethodActor(); if (!classMethodActor.isStatic()) { final VerificationType receiverType = classMethodActor.holder().kind.isReference ? VerificationType.OBJECT : VerificationType.WORD; framesInitialization.store(receiverType, 0); } framesInitialization.interpret(classMethodActor.descriptor()); merge(0); final StackMapTable stackMapTable = codeAttribute.stackMapTable(); if (stackMapTable != null) { final StackMapFrame[] stackMapFrames = stackMapTable.getFrames(framesInitialization); int previousFrameBCI = -1; for (int frameIndex = 0; frameIndex != stackMapFrames.length; ++frameIndex) { final StackMapFrame stackMapFrame = stackMapFrames[frameIndex]; stackMapFrame.applyTo(framesInitialization); final int bci = stackMapFrame.getBCI(previousFrameBCI); final int blockIndex = context.blockIndexFor(bci); final int blockStartBCI = context.blockStartBCI(blockIndex); if (bci == blockStartBCI) { merge(blockIndex); } else { //ProgramWarning.message("Ignoring StackMapTable frame for non-block start BCI: " + bci); } previousFrameBCI = bci; } } final Object frames = frames(); assert frames != null; this.context = null; return frames; } /** * Gets the frames for the basic blocks. * * @return the interpreter sub-class specific encoding of the frames for each basic block */ protected abstract Object frames(); /** * Ensures that the frames for all <i>reachable</i> basic blocks are completely initialized and performs interpretation iteratively * if necessary until they are. * <p> * If {@link #performsAllocation()} returns false for this interpreter, then this method is guaranteed not to perform * any allocation. * * @param context the interpretation context for a method */ public void finalizeFrames(ReferenceMapInterpreterContext context) { boolean trace = traceRefMapInterpretation(context.classMethodActor()); if (trace) { Log.print("Finalizing ref map interpreter frames for "); Log.printMethod(context.classMethodActor(), true); } assert this.context == null; resetInterpreter(context); final int numberOfBlocks = context.numberOfBlocks(); boolean changed = false; // If the map for each block is initialized, then they are in their final state // and so there's no need to run the iterative algorithm below to compute them for (int blockIndex = 0; blockIndex < numberOfBlocks; ++blockIndex) { if (!isFrameInitialized(blockIndex)) { changed = true; break; } } // This only runs if the map for at least one block was uninitialized while (changed) { changed = false; for (int blockIndex = 0; blockIndex < numberOfBlocks; ++blockIndex) { if (isFrameInitialized(blockIndex)) { changed = interpretBlock(blockIndex, null, null, trace) || changed; } } } this.context = null; } /** * Perform interpretation of the basic blocks containing the BCIs yielded by a given BCI * iterator. * <p> * If {@link #performsAllocation()} returns {@code false} for this interpreter, then this method is guaranteed not * to perform any allocation. * * @param context the interpretation context for a method * @param visitor the visitor to notify of reference slots in the frame state at the BCIs yielded by * {@code bciIter} * @param bciIter the BCIs at which {@code visitor} should notified of the references * in the abstract interpretation state */ public final void interpretReferenceSlots(ReferenceMapInterpreterContext context, ReferenceSlotVisitor visitor, BCIIterator bciIter) { boolean trace = traceRefMapInterpretation(context.classMethodActor()); if (trace) { Log.print("Interpreting ref slots for "); Log.printMethod(context.classMethodActor(), true); } assert this.context == null; resetInterpreter(context); bciIter.reset(); for (int bci = bciIter.bci(); bci != -1; bci = bciIter.bci()) { final int blockIndex = blockIndexFor(bci); if (!isFrameInitialized(blockIndex)) { bciIter.next(); } else { interpretBlock(blockIndex, bciIter, visitor, trace); } } this.context = null; } /** * Determines if ref map interpretation for a given method should be traced. */ private boolean traceRefMapInterpretation(ClassMethodActor method) { if (TraceRefMapInterpretationOf == null) { return false; } if (method.name().contains(TraceRefMapInterpretationOf)) { return true; } return method.holder().name().contains(TraceRefMapInterpretationOf); } abstract boolean isLocalRef(int index); abstract boolean isStackRef(int index); void popAndStoreRefOrWord(int index) { final boolean isRef = isStackRef(--sp); storeCategory1(index, isRef); } void popAndStoreCategory1(int index) { popCategory1(); storeCategory1(index, false); } void popAndStoreCategory2(int index) { popCategory2(); storeCategory2(index); } void storeCategory1(int index, boolean isRef) { updateLocal(index, isRef); } void storeCategory2(int index) { updateLocal(index, false); updateLocal(index + 1, false); } void loadAndPushRefOrWord(int index) { assert sp < maxStack(); updateStack(sp++, isLocalRef(index)); } void pushRef() { assert sp < maxStack(); updateStack(sp++, true); } void pushCategory1() { assert sp < maxStack(); updateStack(sp++, false); } void pushCategory1(boolean isRef) { assert sp < maxStack(); updateStack(sp++, isRef); } void pushCategory2() { assert sp < maxStack() - 1; updateStack(sp++, false); updateStack(sp++, false); } void push(Kind kind) { if (kind.isReference) { pushRef(); } else { if (!kind.isCategory1) { pushCategory2(); } else { if (kind != Kind.VOID) { pushCategory1(); } } } } boolean topIsRef() { return isStackRef(sp - 1); } void popCategory2() { assert sp > 1; sp = sp - 2; } boolean popCategory1() { assert sp > 0; return isStackRef(--sp); } boolean pop(Kind kind) { if (kind.isCategory1) { return popCategory1(); } popCategory2(); return false; } abstract void updateStack(int index, boolean isRef); abstract void updateLocal(int index, boolean isRef); private void skip1() { bci++; } private void skip2() { bci += 2; } private void alignBCI() { final int remainder = bci % 4; if (remainder != 0) { bci += 4 - remainder; } } private byte readByte() { return code[bci++]; } private int readUnsigned1() { return readByte() & 0xff; } private int readUnsigned2() { final int high = readByte() & 0xff; final int low = readByte() & 0xff; return (high << 8) | low; } private int readSigned2() { final int high = readByte(); final int low = readByte() & 0xff; return (high << 8) | low; } private int readSigned4() { final int b3 = readByte() << 24; final int b2 = (readByte() & 0xff) << 16; final int b1 = (readByte() & 0xff) << 8; final int b0 = readByte() & 0xff; return b3 | b2 | b1 | b0; } private void visitReferencesAtCurrentBCI(ReferenceSlotVisitor visitor, boolean parametersPopped) { if (!parametersPopped) { for (int i = 0; i < maxLocals(); i++) { if (isLocalRef(i)) { visitor.visitReferenceInLocalVariable(i); } } } for (int i = 0; i < sp; i++) { if (isStackRef(i)) { visitor.visitReferenceOnOperandStack(i, parametersPopped); } } } private int blockIndexFor(int bci) { return context.blockIndexFor(bci); } private int blockStartBCI(int blockIndex) { return context.blockStartBCI(blockIndex); } /** * Determines if the frame for the block denoted by a given index is initialized. * * @param blockIndex the index of the block to test */ public abstract boolean isFrameInitialized(int blockIndex); /** * Interprets a given basic block. * * @param blockIndex the index of the block to be interpreted * @param trace TODO * @return true if the entry state of a control flow successor of this block was modified as a result of the * interpretation */ private boolean interpretBlock(int blockIndex, BCIIterator bciIter, ReferenceSlotVisitor visitor, boolean trace) { final int sp = resetAtBlock(blockIndex); return interpretBlock0(blockIndex, sp, bciIter, visitor, trace); } /** * Configures this interpreter for interpreting a given basic block. * * @param blockIndex the index of the block to be interpreted * @return the stack depth upon entry to the block */ abstract int resetAtBlock(int blockIndex); private boolean mergeWithExceptionHandlers(int bci) { boolean changed = false; for (ExceptionHandler handler = context.exceptionHandlersActiveAt(bci); handler != null; handler = handler.next()) { changed = mergeInto(blockIndexFor(handler.bci()), -1) || changed; } return changed; } // private boolean interpretBlock0(int blockIndex, int sp, BCIIterator bciIter, ReferenceSlotVisitor visitor, boolean trace) { // if (MaxineVM.isHosted()) { // // This indirection is simply for debugging the interpreter loop. // try { // return interpretBlock0(blockIndex, sp, bciIter, visitor, false); // } catch (Throwable e) { // System.err.println("Re-interpreting block after error: "); // e.printStackTrace(); // System.err.println(context); // CodeAttributePrinter.print(System.err, codeAttribute); // return interpretBlock0(blockIndex, sp, bciIter, visitor, true); // } // } // return interpretBlock0(blockIndex, sp, bciIter, visitor, true); // } @HOSTED_ONLY public String[] framesToStrings(ReferenceMapInterpreterContext context) { assert this.context == null; resetInterpreter(context); final int numberOfBlocks = context.numberOfBlocks(); final String[] frameStrings = new String[numberOfBlocks]; for (int blockIndex = 0; blockIndex < numberOfBlocks; ++blockIndex) { resetAtBlock(blockIndex); frameStrings[blockIndex] = currentFrameToString(); } this.context = null; return frameStrings; } @HOSTED_ONLY private String currentFrameToString() { final StringBuilder sb = new StringBuilder("locals[").append(maxLocals()).append("] = { "); for (int i = 0; i != maxLocals(); ++i) { if (isLocalRef(i)) { sb.append(i).append(" "); } } sb.append("}, stack[").append(sp).append("] = { "); for (int i = 0; i != sp; ++i) { if (isStackRef(i)) { sb.append(i).append(" "); } } return sb.append("}").toString(); } private void logCurrentFrame() { Log.print(" locals["); Log.print(maxLocals()); Log.print("] = { "); for (int i = 0; i != maxLocals(); ++i) { if (isLocalRef(i)) { Log.print(i); Log.print(' '); } } Log.print("}, stack["); Log.print(sp); Log.print("] = { "); for (int i = 0; i != sp; ++i) { if (isStackRef(i)) { Log.print(i); Log.print(' '); } } Log.println('}'); } /** * Interprets a given basic block. * * @param blockIndex the index of the block to be interpreted * @param sp the stack depth upon entry to the block * @param trace TODO * @return true if the entry state of a control flow successor of this block was modified as a result of the * interpretation */ @INLINE private boolean interpretBlock0(int blockIndex, int sp, BCIIterator bciIter, ReferenceSlotVisitor visitor, boolean trace) { assert sp >= 0; bci = blockStartBCI(blockIndex); this.sp = sp; final int endBCI = blockStartBCI(blockIndex + 1); int opcode = -1; int opcodeBCI; boolean changed = false; int searchBCI = bciIter == null ? -1 : bciIter.bci(); if (trace) { Log.print("Interpreting block "); Log.print(blockIndex); Log.print(", sp="); Log.print(sp); Log.print(", search bci="); Log.println(searchBCI); } while (bci != endBCI) { opcodeBCI = bci; changed = mergeWithExceptionHandlers(opcodeBCI) || changed; final boolean atSearchBCI = opcodeBCI == searchBCI; opcode = readUnsigned1(); if (atSearchBCI) { // Record BEFORE popping invoke parameters, // because this is not the baseline-to-baseline call yet. visitReferencesAtCurrentBCI(visitor, false); } if (trace) { logCurrentFrame(); Log.print(opcodeBCI); Log.print(": "); Log.println(Bytecodes.baseNameOf(opcode)); } switch (opcode) { case NOP: { break; } case ACONST_NULL: { pushRef(); break; } case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: case FCONST_0: case FCONST_1: case FCONST_2: { pushCategory1(); break; } case LCONST_0: case LCONST_1: case DCONST_0: case DCONST_1: { pushCategory2(); break; } case BIPUSH: { skip1(); pushCategory1(); break; } case SIPUSH: { skip2(); pushCategory1(); break; } case LDC_W: case LDC: { final int index = opcode == Bytecodes.LDC ? readUnsigned1() : readUnsigned2(); final ConstantPool.Tag tag = constantPool.tagAt(index); switch (tag) { case FLOAT: case INTEGER: { pushCategory1(); break; } case OBJECT: case STRING: case CLASS: { pushRef(); break; } default: { throw ProgramError.unknownCase(); } } break; } case LDC2_W: { skip2(); pushCategory2(); break; } case ILOAD: case FLOAD: { skip1(); // index pushCategory1(); break; } case LLOAD: case DLOAD: { skip1(); // index pushCategory2(); break; } case ALOAD: { loadAndPushRefOrWord(readUnsigned1()); break; } case ILOAD_0: case ILOAD_1: case ILOAD_2: case ILOAD_3: case FLOAD_0: case FLOAD_1: case FLOAD_2: case FLOAD_3: { pushCategory1(); break; } case LLOAD_0: case LLOAD_1: case LLOAD_2: case LLOAD_3: case DLOAD_0: case DLOAD_1: case DLOAD_2: case DLOAD_3: { pushCategory2(); break; } case ALOAD_0: case ALOAD_1: case ALOAD_2: case ALOAD_3: { loadAndPushRefOrWord(opcode - ALOAD_0); break; } case IALOAD: case BALOAD: case CALOAD: case SALOAD: case FALOAD: { popCategory1(); popCategory1(); pushCategory1(); break; } case LALOAD: case DALOAD: { popCategory1(); popCategory1(); pushCategory2(); break; } case AALOAD: { popCategory1(); popCategory1(); pushRef(); break; } case FSTORE: case ISTORE: { popAndStoreCategory1(readUnsigned1()); break; } case DSTORE: case LSTORE: { popAndStoreCategory2(readUnsigned1()); break; } case ASTORE: { popAndStoreRefOrWord(readUnsigned1()); break; } case ISTORE_0: case ISTORE_1: case ISTORE_2: case ISTORE_3: { popAndStoreCategory1(opcode - ISTORE_0); break; } case FSTORE_0: case FSTORE_1: case FSTORE_2: case FSTORE_3: { popAndStoreCategory1(opcode - FSTORE_0); break; } case LSTORE_0: case LSTORE_1: case LSTORE_2: case LSTORE_3: { popAndStoreCategory2(opcode - LSTORE_0); break; } case DSTORE_0: case DSTORE_1: case DSTORE_2: case DSTORE_3: { popAndStoreCategory2(opcode - DSTORE_0); break; } case ASTORE_0: case ASTORE_1: case ASTORE_2: case ASTORE_3: { popAndStoreRefOrWord(opcode - ASTORE_0); break; } case IASTORE: case FASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: { popCategory1(); popCategory1(); popCategory1(); break; } case LASTORE: case DASTORE: { popCategory2(); popCategory1(); popCategory1(); break; } case POP: { popCategory1(); break; } case POP2: { popCategory2(); break; } case DUP: { if (isStackRef(this.sp - 1)) { pushRef(); } else { pushCategory1(); } break; } case DUP_X1: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); pushCategory1(value1); pushCategory1(value2); pushCategory1(value1); break; } case DUP_X2: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); final boolean value3 = popCategory1(); pushCategory1(value1); pushCategory1(value3); pushCategory1(value2); pushCategory1(value1); break; } case DUP2: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); pushCategory1(value2); pushCategory1(value1); pushCategory1(value2); pushCategory1(value1); break; } case DUP2_X1: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); final boolean value3 = popCategory1(); pushCategory1(value2); pushCategory1(value1); pushCategory1(value3); pushCategory1(value2); pushCategory1(value1); break; } case DUP2_X2: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); final boolean value3 = popCategory1(); final boolean value4 = popCategory1(); pushCategory1(value2); pushCategory1(value1); pushCategory1(value4); pushCategory1(value3); pushCategory1(value2); pushCategory1(value1); break; } case SWAP: { final boolean value1 = popCategory1(); final boolean value2 = popCategory1(); pushCategory1(value1); pushCategory1(value2); break; } case IADD: case FADD: case FSUB: case ISUB: case IREM: case FREM: case IDIV: case FDIV: case IMUL: case FMUL: case ISHL: case LSHL: case ISHR: case LSHR: case IUSHR: case LUSHR: case IAND: case IOR: case IXOR: case FCMPL: case FCMPG: { popCategory1(); break; } case LADD: case DADD: case LSUB: case DSUB: case LMUL: case DMUL: case LDIV: case DDIV: case LREM: case DREM: case LAND: case LOR: case LXOR: { popCategory2(); break; } case INEG: case LNEG: case FNEG: case DNEG: case I2F: case L2D: case F2I: case D2L: case I2B: case I2C: case I2S: { break; } case IINC: { skip2(); break; } case I2D: case I2L: case F2L: case F2D: { popCategory1(); pushCategory2(); break; } case L2I: case L2F: case D2I: case D2F: { popCategory2(); pushCategory1(); break; } case LCMP: case DCMPL: case DCMPG: { popCategory2(); popCategory2(); pushCategory1(); break; } case IFNULL: case IFNONNULL: case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: { popCategory1(); final int offset = readSigned2(); final int targetBCI = opcodeBCI + offset; if (atSearchBCI) { bciIter.next(); } return merge(blockIndexFor(targetBCI), blockIndex + 1); } case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: { popCategory1(); popCategory1(); final int offset = readSigned2(); final int targetBCI = opcodeBCI + offset; if (atSearchBCI) { bciIter.next(); } return merge(blockIndexFor(targetBCI), blockIndex + 1); } case GOTO: { final int offset = readSigned2(); final int targetBCI = opcodeBCI + offset; if (atSearchBCI) { bciIter.next(); } return merge(blockIndexFor(targetBCI)); } case GOTO_W: { final int offset = readSigned4(); final int targetBCI = opcodeBCI + offset; if (atSearchBCI) { bciIter.next(); } return merge(blockIndexFor(targetBCI)); } case JSR_W: case JSR: case RET: { throw FatalError.unexpected("jsr/ret found"); } case TABLESWITCH: { popCategory1(); alignBCI(); final int defaultOffset = readSigned4(); final int lowMatch = readSigned4(); final int highMatch = readSigned4(); final int numberOfCases = highMatch - lowMatch + 1; changed = merge(blockIndexFor(opcodeBCI + defaultOffset)); for (int i = 0; i < numberOfCases; i++) { int targetBCI = opcodeBCI + readSigned4(); changed = merge(blockIndexFor(targetBCI)) || changed; } if (atSearchBCI) { bciIter.next(); } return changed; } case LOOKUPSWITCH: { popCategory1(); alignBCI(); final int defaultOffset = readSigned4(); final int numberOfCases = readSigned4(); changed = merge(blockIndexFor(opcodeBCI + defaultOffset)); for (int i = 0; i < numberOfCases; i++) { readSigned4(); int targetBCI = opcodeBCI + readSigned4(); changed = merge(blockIndexFor(targetBCI)) || changed; } if (atSearchBCI) { bciIter.next(); } return changed; } case IRETURN: case FRETURN: case ARETURN: { popCategory1(); if (atSearchBCI) { bciIter.next(); } return changed; } case LRETURN: case DRETURN: { popCategory2(); if (atSearchBCI) { bciIter.next(); } return changed; } case RETURN: { if (atSearchBCI) { bciIter.next(); } return changed; } case GETSTATIC: { final int index = readUnsigned2(); final FieldRefConstant fieldConstant = constantPool.fieldAt(index); final TypeDescriptor type = fieldConstant.type(constantPool); final Kind kind = type.toKind(); if (kind.isReference) { pushRef(); } else { if (kind.isCategory1) { pushCategory1(); } else { pushCategory2(); } } break; } case PUTSTATIC: { final int index = readUnsigned2(); final FieldRefConstant fieldConstant = constantPool.fieldAt(index); final TypeDescriptor type = fieldConstant.type(constantPool); final Kind kind = type.toKind(); if (kind.isCategory1) { popCategory1(); } else { popCategory2(); } break; } case GETFIELD: { popCategory1(); // instance containing the field final int index = readUnsigned2(); final FieldRefConstant fieldConstant = constantPool.fieldAt(index); final TypeDescriptor type = fieldConstant.type(constantPool); final Kind kind = type.toKind(); if (kind.isReference) { pushRef(); } else { if (kind.isCategory1) { pushCategory1(); } else { pushCategory2(); } } break; } case PUTFIELD: { final int index = readUnsigned2(); final FieldRefConstant fieldConstant = constantPool.fieldAt(index); final TypeDescriptor type = fieldConstant.type(constantPool); final Kind kind = type.toKind(); if (kind.isCategory1) { popCategory1(); } else { popCategory2(); } popCategory1(); // instance containing the field break; } case JNICALL: { final int index = readUnsigned2(); final SignatureDescriptor methodSignature = SignatureDescriptor.create(constantPool.utf8At(index)); for (int i = methodSignature.numberOfParameters() - 1; i >= 0; --i) { final TypeDescriptor parameter = methodSignature.parameterDescriptorAt(i); pop(parameter.toKind()); } // Pop the last synthetic parameter which is the native function address popCategory1(); if (atSearchBCI) { visitReferencesAtCurrentBCI(visitor, true); } push(methodSignature.resultKind()); break; } case INVOKESPECIAL: case INVOKEVIRTUAL: case INVOKEINTERFACE: case INVOKESTATIC: { final int index = readUnsigned2(); if (opcode == Bytecodes.INVOKEINTERFACE) { skip2(); } final MethodRefConstant methodConstant = constantPool.methodAt(index); final SignatureDescriptor methodSignature = methodConstant.signature(constantPool); for (int i = methodSignature.numberOfParameters() - 1; i >= 0; --i) { final TypeDescriptor parameter = methodSignature.parameterDescriptorAt(i); pop(parameter.toKind()); } if (opcode != Bytecodes.INVOKESTATIC) { popCategory1(); // receiver } if (atSearchBCI) { // Record AFTER popping the parameters. // They will be accounted for in the callee frame, // with potentially different stack slot kinds - see JVM spec. visitReferencesAtCurrentBCI(visitor, true); } push(methodSignature.resultKind()); break; } case NEW: { final int index = readUnsigned2(); push(constantPool.classAt(index).typeDescriptor().toKind()); break; } case NEWARRAY: { skip1(); popCategory1(); pushRef(); break; } case ANEWARRAY: { skip2(); popCategory1(); pushRef(); break; } case ARRAYLENGTH: { popCategory1(); pushCategory1(); break; } case ATHROW: { popCategory1(); if (atSearchBCI) { bciIter.next(); } return changed; } case CHECKCAST: { popCategory1(); final int index = readUnsigned2(); push(constantPool.classAt(index).typeDescriptor().toKind()); break; } case INSTANCEOF: { skip2(); popCategory1(); pushCategory1(); break; } case MONITORENTER: case MONITOREXIT: { popCategory1(); break; } case WIDE: { final int widenedOpcode = readUnsigned1(); final int index = readUnsigned2(); switch (widenedOpcode) { case ILOAD: case FLOAD: { pushCategory1(); break; } case LLOAD: case DLOAD: { pushCategory2(); break; } case ALOAD: { loadAndPushRefOrWord(index); break; } case ISTORE: case FSTORE: { popAndStoreCategory1(index); break; } case LSTORE: case DSTORE: { popAndStoreCategory2(index); break; } case ASTORE: { popAndStoreRefOrWord(index); break; } case IINC: { skip2(); break; } default: { throw ProgramError.unexpected(); } } break; } case MULTIANEWARRAY: { skip2(); final int dimensions = readUnsigned1(); for (int i = 0; i < dimensions; i++) { popCategory1(); } pushRef(); break; } default: { String name = Bytecodes.nameOf(opcode); FatalError.unexpected("Unknown bytcode: " + name); } } if (atSearchBCI) { searchBCI = bciIter.next(); if (searchBCI == -1 || searchBCI >= endBCI) { return false; } } } assert !Bytecodes.isStop(opcode); // Merge into successor return merge(blockIndex + 1) || changed; } }