/* * Copyright (c) 2010 Sun Microsystems, Inc. All rights reserved. * * Sun Microsystems, Inc. has intellectual property rights relating to technology embodied in the product * that is described in this document. In particular, and without limitation, these intellectual property * rights may include one or more of the U.S. patents listed at http://www.sun.com/patents and one or * more additional patents or pending patent applications in the U.S. and in other countries. * * U.S. Government Rights - Commercial software. Government users are subject to the Sun * Microsystems, Inc. standard license agreement and applicable provisions of the FAR and its * supplements. * * Use is subject to license terms. Sun, Sun Microsystems, the Sun logo, Java and Solaris are trademarks or * registered trademarks of Sun Microsystems, Inc. in the U.S. and other countries. All SPARC trademarks * are used under license and are trademarks or registered trademarks of SPARC International, Inc. in the * U.S. and other countries. * * UNIX is a registered trademark in the U.S. and other countries, exclusively licensed through X/Open * Company, Ltd. */ package com.sun.cri.bytecode; import static com.sun.cri.bytecode.Bytecodes.*; import java.io.*; /** * A utility to process a bytecode stream to replace certain standard * JVM instructions with extended bytecode instructions. The extended * instructions are for: * *
    *
  1. Supporting a Word type hierarchy that is separate from the Object type hierarchy. *
  2. Supporting low-level, non-Java operations such as reading/writing registers. *
  3. Other builtins necessary for writing a meta-circular VM. *
* * The transformations made by this utility assume the input code has been verified * (or would successfully pass verification). * * @author Doug Simon */ public final class BytecodeIntrinsifier { /** * Set to true to trace the bytecode processing. */ private static final boolean DEBUG = false; /** * The interface implemented by a client that submits code to be intrinsified. * This interface is comprised of a number of methods for the bytecode instructions * that are amenable to intrinsification. * * @author Doug Simon */ public abstract static class IntrinsifierClient { public abstract void invokevirtual(BytecodeIntrinsifier bi, int cpi); public abstract void invokestatic(BytecodeIntrinsifier bi, int cpi); public abstract void invokespecial(BytecodeIntrinsifier bi, int cpi); public abstract void invokeinterface(BytecodeIntrinsifier bi, int cpi); public abstract void getstatic(BytecodeIntrinsifier bi, int cpi); public abstract void putstatic(BytecodeIntrinsifier bi, int cpi); public abstract void getfield(BytecodeIntrinsifier bi, int cpi); public abstract void putfield(BytecodeIntrinsifier bi, int cpi); public abstract void jnicall(BytecodeIntrinsifier bi, int cpi); } /** * The state of the JVM local variables and operand stack at a given bytecode position. */ static class Frame { /** * The operand stack depth. */ final int sp; /** * The local variable state. */ final boolean[] locals; /** * The operand stack state. */ final boolean[] stack; /** * Specifies if the basic block denoted by this frame has been {@linkplain BytecodeIntrinsifier#parseBlock(int) parsed}. */ boolean visited; /** * The next {@code Frame} object in a linked list. */ final Frame next; /** * The bytecode position of this frame. */ final int bci; /** * Creates a {@code Frame} object. * * @param bci the bytecode position * @param sp the operand stack depth * @param stack the operand stack state * @param locals the local variable state * @param next the next {@code Frame} in a linked list */ Frame(int bci, int sp, boolean[] stack, boolean[] locals, Frame next) { this.bci = bci; this.sp = sp; this.stack = stack; this.locals = locals; this.next = next; } /** * Updates the given operand stack and local variable state based on this frame. * * @param stack the operand stack state modified to match the operand stack state of this frame * @param locals the local variable state modified to match the local variable state of this frame * @return the stack depth of this frame */ int get(boolean[] stack, boolean[] locals) { for (int i = 0; i < sp; ++i) { stack[i] = this.stack[i]; } for (int i = sp; i < stack.length; ++i) { stack[i] = false; } for (int i = 0; i < locals.length; ++i) { locals[i] = this.locals[i]; } return sp; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(bci).append("[sp=").append(sp); if (stack != null) { sb.append(" stack="); for (int i = 0; i < stack.length; ++i) { sb.append(i < sp ? (stack[i] ? '1' : '0') : '_'); } } if (locals != null) { sb.append(" locals="); for (boolean isWord : locals) { sb.append(isWord ? '1' : '0'); } } return sb.append(']').toString(); } } /** * The work list of basic blocks to be {@linkplain #parseBlock parsed}. */ private Frame todo; /** * The work list of exception handler entry basic blocks to be {@linkplain #parseBlock parsed}. * This list is processed once the {@linkplain #todo primary} work list is completed. */ private Frame todoHandler; /** * Sentinel value used in {@link #frameMap} to denote that a given * instruction ({@code bci}) has been processed. This required as without a * pre-pass to establish basic block boundaries, it's possible for * {@linkplain #parseBlock(int) block parsing} to cross a block boundary at * a (as yet unknown) control flow merge point. There's no need to * re-process the instructions after the merge point when the other branch * to the merge point is subsequently discovered. */ private static final Frame VISITED = new Frame(-1, 0, null, null, null); static { VISITED.visited = true; } /** * A map from each {@code bci} to a {@link Frame} object. A entry equal to * {@link #VISITED} in this map for a given {@code bci} indicates that the * instruction at {@code bci} has already be processed. A {@code null} entry * means that the instruction has not yet been processes and not a known * basic block entry point. A non-null entry not equals to {@link #VISITED} * indicates a known basic block entry point and the value of the * {@link Frame} object indicates the frame state to be used when parsing * the block. */ private final Frame[] frameMap; /** * A map from each {@code bci} to the head of a list of {@link Handler} objects denoting the handlers active at that {@code bci}. */ private final Handler[] handlerMap; /** * The position of the opcode of the instruction currently being processed. */ private int opcodeBci; /** * The current bytecode position. */ private int bci; /** * The method being processed. */ private final Object method; /** * The intrinsification client. */ private final IntrinsifierClient intrinsifier; /** * Indicates if one or more instructions were replaced with an extended instruction. */ private boolean changed; private final byte[] inCode; private final byte[] outCode; private final boolean[] locals; private final boolean[] stack; private int sp; static class Handler { Frame frame; final Handler next; final int bci; Handler(int bci, Handler next) { this.bci = bci; this.next = next; } } /** * Creates an object used to process some given bytecode for the purpose of intrinsification. * * @param intrinsifier the client side of intrinsification * @param method the method being processed. The only use of this value is to call its {@link Object#toString()} method for debug tracing. * @param inCode the code to be processed * @param outCode the code array in which the processed code is written. If {@code null}, then processing is performed 'in situ' on {@code inCode} * @param handlers the exception handler table as an array of triplets (start {@code bci}, end {@code bci}, handler {@code bci}) or * {@code null} if the method has no exception handlers * @param maxStack the maximum amount of stack used by {@code inCode} * @param locals the initial state of the local variables derived from the signature of the method */ public BytecodeIntrinsifier(IntrinsifierClient intrinsifier, Object method, byte[] inCode, byte[] outCode, int[] handlers, int maxStack, boolean[] locals) { this.intrinsifier = intrinsifier; this.stack = new boolean[maxStack]; this.locals = new boolean[locals.length]; this.inCode = inCode; if (outCode == null) { this.outCode = inCode; } else { if (outCode != inCode) { assert inCode.length == outCode.length : "outCode.length does not match inCode.length"; System.arraycopy(inCode, 0, outCode, 0, inCode.length); } this.outCode = outCode; } this.method = method; this.frameMap = new Frame[inCode.length]; this.handlerMap = initHandlerMap(inCode, handlers, maxStack, locals); Frame entryFrame = new Frame(0, 0, new boolean[maxStack], locals, todo); todo = entryFrame; frameMap[0] = entryFrame; } private Handler[] initHandlerMap(byte[] code, int[] handlers, int maxStack, boolean[] locals) { if (handlers == null) { return null; } Handler[] handlerMap = new Handler[code.length]; for (int i = 0; i < handlers.length; i += 3) { int start = handlers[i]; int end = handlers[i + 1]; int handler = handlers[i + 2]; Handler previous = null; Handler previousExisting = null; for (int bci = start; bci < end; ++bci) { Handler existing = handlerMap[bci]; if (previous != null && previous.bci == handler && previous.next == previousExisting) { handlerMap[bci] = previous; } else { handlerMap[bci] = new Handler(handler, existing); previous = handlerMap[bci]; previousExisting = existing; } } } return handlerMap; } /** * Interprets a push of a single-slot value to the stack. * * @param word specifies if the value pushed is a Word value */ public void push1(boolean word) { stack[sp] = word; sp++; } /** * Interprets a push of a single-slot, non-Word value to the stack. */ public void push1() { stack[sp++] = false; } /** * Interprets a push of a double-slot value to the stack. By definition, the value * cannot be a Word value as Word values have the same slot size as Object values * which is one slot. */ public void push2() { stack[sp++] = false; stack[sp++] = false; } private static final PrintStream debugOut = System.out; /** * Records a bytecode modification. */ private void recordChange() { if (DEBUG) { debugOut.println(" ---> " + nameOf(inCode[opcodeBci] & 0xff)); } changed = true; } /** * Interprets a load of a local variable onto the stack. * * @param index the local variable index * @param wordOpcode if the local variable is a Word value, then the load instruction is replaced with this opcode */ public void loadRefOrWord(int index, int wordOpcode) { boolean isWord = locals[index]; stack[sp++] = isWord; if (isWord) { outCode[opcodeBci] = (byte) wordOpcode; recordChange(); } } /** * Interprets a load of a single-slot, non-Word local variable onto the stack. * * @param index the local variable index */ public void load1(int index) { stack[sp++] = locals[index]; } /** * Interprets a load of a double-slot, non-Word local variable onto the stack. * * @param index the local variable index */ public void load2(int index) { stack[sp] = locals[index]; stack[sp + 1] = locals[index + 1]; sp += 2; } /** * Interprets a store to a local variable by popping the top value off the stack. * * @param index the local variable index * @param wordOpcode if the local variable is a Word value, then the store instruction is replaced with this opcode */ public void storeRefOrWord(int index, int wordOpcode) { boolean isWord = stack[--sp]; locals[index] = isWord; if (isWord) { inCode[opcodeBci] = (byte) wordOpcode; recordChange(); } } /** * Interprets a store to a single-slot, non-Word local variable by popping the top value off the stack. * * @param index the local variable index */ public void store1(int index) { locals[index] = stack[--sp]; } /** * Interprets a store to a double-slot, non-Word local variable by popping the top value off the stack. * * @param index the local variable index */ public void store2(int index) { locals[index] = stack[sp - 2]; locals[index + 1] = stack[sp - 1]; sp -= 2; } /** * Pops a single-slot value off the stack. * * @return whether the popped value was a Word */ public boolean pop1() { return stack[--sp]; } /** * Pops n-slots off the stack. * * @param amount the number of slots to pop */ public void pop(int amount) { sp -= amount; assert sp >= 0; } /** * Determines if the top value on the stack is a Word. */ public boolean top() { return stack[sp - 1]; } /** * Reads an unsigned byte from the code stream. * * @return the unsigned byte value read */ private int readU1() { return inCode[bci++] & 0xff; } /** * Reads an unsigned 2-byte value from the code stream. * * @return the unsigned 2-byte value read */ private int readU2() { final int result = Bytes.beU2(inCode, bci); bci += 2; return result; } /** * Reads an unsigned 1 or 2-byte value from the code stream. * * @param wide if {@code true}, a 2 byte value is read * @return the unsigned 1 or 2 byte value read */ private int readVarIndex(boolean wide) { if (wide) { return readU2(); } return readU1(); } /** * Reads a signed 2-byte value from the code stream. * * @return the signed 2-byte value read */ private int readS2() { final int result = Bytes.beS2(inCode, bci); bci += 2; return result; } /** * Reads a signed 4-byte value from the code stream. * * @return the signed 4-byte value read */ private int readS4() { final int result = Bytes.beS4(inCode, bci); bci += 4; return result; } private void align4() { bci = (bci + 3) & ~0x3; } /** * Notifies this object that the current {@code INVOKE...} instruction is being replaced. * * @param opcode the replacement opcode * @param operand the replacement 2-byte operand */ public void intrinsify(int opcode, int operand) { int oldOpcode = inCode[opcodeBci] & 0xff; assert oldOpcode >= INVOKEVIRTUAL && oldOpcode <= INVOKEINTERFACE; assert operand >> 16 == 0 || operand >> 16 == -1 : "operand value cannot be encoded in 16 bits: " + operand; outCode[opcodeBci] = (byte) opcode; outCode[opcodeBci + 1] = (byte) (operand >> 8); outCode[opcodeBci + 2] = (byte) (operand >> 0); recordChange(); } /** * Performs abstract interpretation of the code with respect to the flow of Word typed values. * This interpretation only parses each bytecode instruction at most once (unreachable code * is never processed) by following control flow edges. That is, a basic block is not * interpreted unless it is reachable along a control flow edge from another basic block * that is (transitively) reachable from the entry basic block. * * It is assumed that the input code verifies. The means that all uses of a variable * will match with a definition along all paths to the use and that the type (is a word * or not) will be the same along each path. * * @return {@code true} if one or more instructions were modified */ public boolean run() { try { if (DEBUG) { debugOut.println(method); } parseBlocks(); while (todoHandler != null) { todo = todoHandler; todoHandler = null; parseBlocks(); } return changed; } catch (Throwable e) { int opcode = inCode[opcodeBci] & 0xff; Frame errorFrame = new Frame(opcodeBci, sp, stack, locals, null); throw (InternalError) new InternalError("Error while preprocessing " + method + " {" + nameOf(opcode) + " @ " + errorFrame + "}").initCause(e); } } /** * Parses and processes all the basic blocks on the {@linkplain #todo to do} list. */ public void parseBlocks() { while (todo != null) { if (DEBUG) { debugOut.println(" TODO: " + todoAsString()); } Frame head = todo; todo = head.next; assert !head.visited; sp = head.get(stack, locals); head.visited = true; this.bci = head.bci; parseBlock(); if (DEBUG) { debugOut.println(" =============="); } } } /** * Parses and processes a single basic block of bytecode. * * @param ci the entry point to the block */ private void parseBlock() { while (true) { opcodeBci = bci; int opcode = readU1(); int length = lengthOf(opcode); int nextBci = bci + length - 1; boolean wide; if (opcode == WIDE) { opcode = readU1(); wide = true; if (opcode == IINC) { nextBci = opcodeBci + 6; } else { // ?LOAD, ?STORE, or RET nextBci = opcodeBci + 4; } } else { wide = false; } if (DEBUG) { debugOut.println(" sp=" + sp + "\t" + opcodeBci + ": " + nameOf(opcode)); assert nameOf(opcode) != null : opcode; } switch (opcode) { // Checkstyle: stop case NOP : case IINC : case GOTO : case RET : case RETURN : case WIDE : case GOTO_W : case JSR_W : case BREAKPOINT : case LALOAD : case DALOAD : case LNEG : case DNEG : case L2D : case D2L : case INEG : case FNEG : case I2F : case F2I : case I2B : case I2C : case I2S : case NEWARRAY : case ANEWARRAY : case ARRAYLENGTH : case CHECKCAST : case INSTANCEOF : break; case ACONST_NULL : 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 : case BIPUSH : case SIPUSH : case LDC : case LDC_W : case JSR : case NEW : case I2L : case I2D : case F2L : case F2D : push1(); break; case LDC2_W : case LCONST_0 : case LCONST_1 : case DCONST_0 : case DCONST_1 : push2(); break; case POP : case IFEQ : case IFNE : case IFLT : case IFGE : case IFGT : case IFLE : case TABLESWITCH : case LOOKUPSWITCH : case IRETURN : case FRETURN : case ATHROW : case MONITORENTER : case MONITOREXIT : case IFNULL : case IFNONNULL : case IALOAD : case FALOAD : case AALOAD : case BALOAD : case CALOAD : case SALOAD : case IADD : case FADD : case ISUB : case FSUB : case IMUL : case FMUL : case IDIV : case FDIV : case IREM : case FREM : case ISHL : case ISHR : case IUSHR : case LSHL : case LSHR : case LUSHR : case IAND : case IOR : case IXOR : case L2I : case L2F : case D2I : case D2F : case FCMPL : case FCMPG : pop1(); break; case POP2 : case IF_ICMPEQ : case IF_ICMPNE : case IF_ICMPLT : case IF_ICMPGE : case IF_ICMPGT : case IF_ICMPLE : case IF_ACMPEQ : case IF_ACMPNE : case DRETURN : case LADD : case DADD : case LSUB : case DSUB : case LMUL : case DMUL : case LDIV : case DDIV : case LRETURN : case LREM : case DREM : case LAND : case LOR : case LXOR : pop(2); break; case ILOAD : case FLOAD : load1(readVarIndex(wide)); break; case ILOAD_0 : case FLOAD_0 : load1(0); break; case ILOAD_1 : case FLOAD_1 : load1(1); break; case ILOAD_2 : case FLOAD_2 : load1(2); break; case ILOAD_3 : case FLOAD_3 : load1(3); break; case LLOAD : case DLOAD : load2(readVarIndex(wide)); break; case LLOAD_0 : case DLOAD_0 : load2(0); break; case LLOAD_1 : case DLOAD_1 : load2(1); break; case LLOAD_2 : case DLOAD_2 : load2(2); break; case LLOAD_3 : case DLOAD_3 : load2(3); break; case ISTORE : case FSTORE : store1(readVarIndex(wide)); break; case ISTORE_0 : case FSTORE_0 : store1(0); break; case ISTORE_1 : case FSTORE_1 : store1(1); break; case ISTORE_2 : case FSTORE_2 : store1(2); break; case ISTORE_3 : case FSTORE_3 : store1(3); break; case LSTORE : case DSTORE : store2(readVarIndex(wide)); break; case LSTORE_0 : case DSTORE_0 : store2(0); break; case LSTORE_1 : case DSTORE_1 : store2(1); break; case LSTORE_2 : case DSTORE_2 : store2(2); break; case LSTORE_3 : case DSTORE_3 : store2(3); break; case IASTORE : case FASTORE : case AASTORE : case BASTORE : case CASTORE : case SASTORE : case LCMP : case DCMPL : case DCMPG : pop(3); break; case LASTORE : case DASTORE : pop(4); break; case DUP: push1(top()); break; case DUP_X1: { boolean value1 = pop1(); boolean value2 = pop1(); push1(value1); push1(value2); push1(value1); break; } case DUP_X2: { boolean value1 = pop1(); boolean value2 = pop1(); boolean value3 = pop1(); push1(value1); push1(value3); push1(value2); push1(value1); break; } case DUP2: { boolean value1 = pop1(); boolean value2 = pop1(); push1(value2); push1(value1); push1(value2); push1(value1); break; } case DUP2_X1: { boolean value1 = pop1(); boolean value2 = pop1(); boolean value3 = pop1(); push1(value2); push1(value1); push1(value3); push1(value2); push1(value1); break; } case DUP2_X2: { boolean value1 = pop1(); boolean value2 = pop1(); boolean value3 = pop1(); boolean value4 = pop1(); push1(value2); push1(value1); push1(value4); push1(value3); push1(value2); push1(value1); break; } case SWAP: { boolean value1 = pop1(); boolean value2 = pop1(); push1(value1); push1(value2); break; } case MULTIANEWARRAY: { readU2(); int dimensions = readU1(); pop(dimensions); push1(); break; } case ARETURN: { if (pop1()) { outCode[opcodeBci] = (byte) WRETURN; recordChange(); } break; } case ALOAD: loadRefOrWord(readVarIndex(wide), WLOAD); break; case ALOAD_0: loadRefOrWord(0, WLOAD_0); break; case ALOAD_1: loadRefOrWord(1, WLOAD_1); break; case ALOAD_2: loadRefOrWord(2, WLOAD_2); break; case ALOAD_3: loadRefOrWord(3, WLOAD_3); break; case ASTORE: storeRefOrWord(readVarIndex(wide), WSTORE); break; case ASTORE_0: storeRefOrWord(0, WSTORE_0); break; case ASTORE_1: storeRefOrWord(1, WSTORE_1); break; case ASTORE_2: storeRefOrWord(2, WSTORE_2); break; case ASTORE_3: storeRefOrWord(3, WSTORE_3); break; case INVOKEVIRTUAL: intrinsifier.invokevirtual(this, readU2()); break; case INVOKESPECIAL: intrinsifier.invokespecial(this, readU2()); break; case INVOKESTATIC: intrinsifier.invokestatic(this, readU2()); break; case INVOKEINTERFACE: intrinsifier.invokeinterface(this, readU2()); break; case GETFIELD: intrinsifier.getfield(this, readU2()); break; case PUTFIELD: intrinsifier.putfield(this, readU2()); break; case GETSTATIC: intrinsifier.getstatic(this, readU2()); break; case PUTSTATIC: intrinsifier.putstatic(this, readU2()); break; case JNICALL: intrinsifier.jnicall(this, readU2()); break; default: throw new InternalError("unexpected opcode " + opcode + " [" + nameOf(opcode) + "]"); } // Process exception handlers (if any) if (handlerMap != null && canTrap(opcode)) { for (Handler handler = handlerMap[opcodeBci]; handler != null; handler = handler.next) { Frame frame = handler.frame; if (frame == null) { frame = frameMap[handler.bci]; if (frame == null) { // It's only necessary to initialize the handler frame state // from one control flow in-edge. Given the precondition // about verified code, all variables used within a // handler must have been defined along *all* in-edges. frame = new Frame(handler.bci, 1, new boolean[stack.length], locals.clone(), todoHandler); frameMap[handler.bci] = frame; todoHandler = frame; } handler.frame = frame; } else { assert frameMap[handler.bci] == frame; } } } if (isBlockEnd(opcode) || frameMap[nextBci] != null) { if (isBranch(opcode)) { if (length == 3) { merge(opcodeBci + readS2()); } else { assert length == 5; merge(opcodeBci + readS4()); } if (isConditionalBranch(opcode)) { merge(opcodeBci + length); } } else if (opcode == TABLESWITCH) { align4(); final int defaultOffset = readS4(); final int lowMatch = readS4(); final int highMatch = readS4(); final int numberOfCases = highMatch - lowMatch + 1; merge(opcodeBci + defaultOffset); for (int i = 0; i < numberOfCases; i++) { merge(opcodeBci + readS4()); } } else if (opcode == LOOKUPSWITCH) { align4(); final int defaultOffset = readS4(); final int numberOfCases = readS4(); merge(opcodeBci + defaultOffset); for (int i = 0; i < numberOfCases; i++) { readS4(); merge(opcodeBci + readS4()); } } return; } // Mark the next instruction as visited (it may be an as yet unknown control flow merge point) frameMap[nextBci] = VISITED; bci = nextBci; } } private void merge(int bci) { Frame frame = frameMap[bci]; if (frame == null) { frame = new Frame(bci, sp, stack.clone(), locals.clone(), todo); frameMap[bci] = frame; todo = frame; } if (!frame.visited) { assert isTodo(frame) : frame; } } // DEBUG support private boolean isTodo(Frame frame) { for (Frame f = todo; f != null; f = f.next) { if (f == frame) { return true; } } return false; } // DEBUG support private String todoAsString() { StringBuilder sb = new StringBuilder(); sb.append('['); for (Frame f = todo; f != null; f = f.next) { if (sb.length() != 1) { sb.append(", "); } sb.append(f.bci); } return sb.append(']').toString(); } }