/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim.analysis; import static kilim.Constants.ALOAD_0; import static kilim.Constants.ASTORE_0; import static kilim.Constants.DLOAD_0; import static kilim.Constants.DSTORE_0; import static kilim.Constants.D_BOOLEAN; import static kilim.Constants.D_BYTE; import static kilim.Constants.D_CHAR; import static kilim.Constants.D_DOUBLE; import static kilim.Constants.D_FIBER; import static kilim.Constants.D_FLOAT; import static kilim.Constants.D_INT; import static kilim.Constants.D_LONG; import static kilim.Constants.D_NULL; import static kilim.Constants.D_OBJECT; import static kilim.Constants.D_SHORT; import static kilim.Constants.D_STATE; import static kilim.Constants.D_VOID; import static kilim.Constants.D_UNDEFINED; import static kilim.Constants.FIBER_CLASS; import static kilim.Constants.FLOAD_0; import static kilim.Constants.FSTORE_0; import static kilim.Constants.ILOAD_0; import static kilim.Constants.ISTORE_0; import static kilim.Constants.LLOAD_0; import static kilim.Constants.LSTORE_0; import static kilim.Constants.STATE_CLASS; import static kilim.analysis.VMType.TOBJECT; import static kilim.analysis.VMType.loadVar; import static kilim.analysis.VMType.storeVar; import static kilim.analysis.VMType.toVmType; import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ASTORE; import static org.objectweb.asm.Opcodes.BIPUSH; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DCONST_0; import static org.objectweb.asm.Opcodes.DCONST_1; import static org.objectweb.asm.Opcodes.DLOAD; import static org.objectweb.asm.Opcodes.DRETURN; import static org.objectweb.asm.Opcodes.DSTORE; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.FCONST_0; import static org.objectweb.asm.Opcodes.FCONST_1; import static org.objectweb.asm.Opcodes.FCONST_2; import static org.objectweb.asm.Opcodes.FLOAD; import static org.objectweb.asm.Opcodes.FRETURN; import static org.objectweb.asm.Opcodes.FSTORE; import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.GOTO; import static org.objectweb.asm.Opcodes.I2B; import static org.objectweb.asm.Opcodes.I2C; import static org.objectweb.asm.Opcodes.I2S; import static org.objectweb.asm.Opcodes.ICONST_0; import static org.objectweb.asm.Opcodes.ICONST_M1; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.IRETURN; import static org.objectweb.asm.Opcodes.ISTORE; import static org.objectweb.asm.Opcodes.LCONST_0; import static org.objectweb.asm.Opcodes.LCONST_1; import static org.objectweb.asm.Opcodes.LLOAD; import static org.objectweb.asm.Opcodes.LRETURN; import static org.objectweb.asm.Opcodes.LSTORE; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.POP; import static org.objectweb.asm.Opcodes.POP2; import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.SIPUSH; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.TableSwitchInsnNode; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.tree.MethodInsnNode; /** * This class produces all the code associated with a specific pausable method * invocation. There are three distinct chunks of code. * <dl> * <li> <b>Rewind</b>: At the beginning of the method, right after the opening * switch statement (switch fiber.pc), which is the "rewind" portion. This stage * pushes in (mostly) dummy values on the stack and jumps to the next method * invocation in the cycle. </li> * * <li> <b>Call</b>: The actual call. We push fiber as the last argument to the * pausable method (by calling fiber.down()), before making the call. </li> * * <li> <b>Post-call</b> The bulk of the code produced by this object. </li> * </ol> * <p> * * An explanation of some terms used in the code may be useful. * * Much of this code concerns itself with storing and retrieving values from/to * the stack and the local variables. The stack consists of three logical parts:<br> * * <pre> * +--------------+----------------------+---------------+ * | Stack bottom | callee obj reference | args for call | (Top of stack) * +--------------+----------------------+---------------+ * </pre> * * The callee's object reference and the arguments at the top of stack are * consumed by the method invocation. If the call is static, there is no object * reference, of course. The bottom of the stack (which may also be empty) * refers to the part of the stack that is left over once the args are consumed. * This is the part that we need to save if we have to yield. * <p> * * As for the local variables, we are interested in saving var 0 (the "this" * pointer) and all other variables that the flow analysis has shown to be * live-in, that is, is used downstream of the call. Typically, only about 30% * of all available vars are actually used downstream, so we use the rest for * temporary storage. * <p> * * @see #genRewind(MethodVisitor) * @see #genCall(MethodVisitor) * @see #genPostCall(MethodVisitor) * */ public class CallWeaver { /** * The parent method-weaver responsible for writing the whole method */ private MethodWeaver methodWeaver; /** * The basic block that calls the pausable method */ BasicBlock bb; private LabelNode resumeLabel; LabelNode callLabel; private ValInfoList valInfoList; /** * varUsage[i] is true if the i-th var is taken. We don't touch the first * maxLocals vars. It is used for minimizing usage of extra vars. */ BitSet varUsage; /** * number of local registers required. */ int numVars; /** The canoncial name of the state class responsible for storing * the state (@see kilim.State) */ private String stateClassName; /** Memoized version of getNumArgs() */ int numArgs = -1; public CallWeaver(MethodWeaver mw, BasicBlock aBB) { methodWeaver = mw; bb = aBB; callLabel = bb.startLabel; varUsage = new BitSet(2 * bb.flow.maxLocals); resumeLabel = bb.flow.getLabelAt(bb.startPos + 1); if (resumeLabel == null) resumeLabel = new LabelNode(); assignRegisters(); stateClassName = createStateClass(); methodWeaver.ensureMaxStack(getNumBottom() + 3); // } /** * The basic block's frame tells us the number of parameters in the stack * and which local variables are needed later on. * * If the method is pausable, we'll need the bottom part of the stack and * the object reference from the top part of the stack to be able to resume * it. We don't need to worry about the arguments, because they will be * saved (if needed) in the _called_ method's state. * * The "this" arg (var0) is given special treatment. It is always saved in * all states, so it doesn't count as "data". */ private void assignRegisters() { Frame f = bb.startFrame; MethodWeaver mw = methodWeaver; varUsage.set(mw.getFiberVar()); numVars = mw.getFiberVar() + 1; // knowing fiberVar is beyond anything // that's used mw.ensureMaxVars(numVars); Usage u = bb.usage; valInfoList = new ValInfoList(); /* * Create ValInfos for each Value that needs to be saved (all live-in * vars (except var 0, if not static) and all stack bottom vars count, * except if they are duplicates of earlier ones or are constants which * can be reproduced in bytecode itself. * * Process local vars before the stack, so that we can figure out which * elements of the stack are duplicates. Saving the stack requires more * processing, and the more we can reduce it, the better. */ // ensure we don't touch any of the locals in the range that the // original // code knows about. varUsage.set(0, f.getMaxLocals()); int i = bb.flow.isStatic() ? 0 : 1; for (; i < f.getMaxLocals(); i++) { Value v = f.getLocal(i); if (u.isLiveIn(i)) { if (!(v.isConstant() || valInfoList.contains(v))) { ValInfo vi = new ValInfo(v); vi.var = i; valInfoList.add(vi); } } } /* * All stack values at the bottom (those not consumed by the called * method) will have to be saved, if they are are not already accounted * for or they are constants. */ int numBottom = getNumBottom(); for (i = 0; i < numBottom; i++) { Value v = f.getStack(i); if (!(v.isConstant() || valInfoList.contains(v))) { ValInfo vi = new ValInfo(v); valInfoList.add(vi); } } Collections.sort(valInfoList); // sorts by type and var int fieldNum = 0; for (ValInfo vi : valInfoList) { vi.fieldName = "f" + fieldNum++; } } int getStackLen() { return bb.startFrame.getStackLen(); } /** * The total number consumed by the call, including its object reference */ int getNumArgs() { if (numArgs == -1) { numArgs = TypeDesc.getNumArgumentTypes(getMethodInsn().desc) + (isStaticCall() ? 0 : 1); } return numArgs; } final boolean isStaticCall() { return getMethodInsn().getOpcode() == INVOKESTATIC; } final MethodInsnNode getMethodInsn() { return (MethodInsnNode) bb.getInstruction(bb.startPos); } int getNumBottom() { return getStackLen() - getNumArgs(); } /** * <pre> * The following template is produced in the method's prelude for each * pausable method. * F_REWIND: * for each bottom stack operand * introduce a dummy constant of the appropriate type. * (iconst_0, aconst_null, etc.) * if the call is not static, * we need the called object's object reference * ask the next state in the fiber's list * goto F_CALL: // jump to the invocation site. * </pre> * * @param mv */ void genRewind(MethodVisitor mv) { Frame f = bb.startFrame; // The last parameter to the method is fiber, but the original // code doesn't know that and will use up that slot as it // pleases. If we are going to jump directly to the basicblock // bb, we'll have to make sure it has some dummy value of that // type going in just to keep the verifier happy. for (int i = methodWeaver.getFiberArgVar(); i < f.getMaxLocals(); ) { Value v = f.getLocal(i); if (v.getTypeDesc() != D_UNDEFINED) { // if (u.isLiveIn(i)) { int vmt = toVmType(v.getTypeDesc()); mv.visitInsn(VMType.constInsn[vmt]); storeVar(mv, vmt, i); } i += v.isCategory2() ? 2 : 1; } // store dummy values in stack. The constants have to correspond // to the types of each of the bottom elements. int numBottom = getNumBottom(); // spos == stack pos. Note that it is incremented beyond the // 'for' loop below. int spos; for (spos = 0; spos < numBottom; spos++) { Value v = f.getStack(spos); if (v.isConstant()) { mv.visitInsn(VMType.constInsn[VMType.toVmType(v.getTypeDesc())]); } else { ValInfo vi = valInfoList.find(v); mv.visitInsn(VMType.constInsn[vi.vmt]); } } if (!isStaticCall()) { // The next element in the stack after numBottom is the object // reference for the callee. This can't be a dummy because it // is needed by the invokevirtual call. If this reference // is found in the local vars, we'll use it, otherwise we need // to dip into the next activation frame in the fiber to // retrieve it. Value v = f.getStack(numBottom); if (!methodWeaver.isStatic() && f.getLocal(0) == v) { // "this" is already properly initialized. mv.visitVarInsn(ALOAD, 0); } else { loadVar(mv, TOBJECT, methodWeaver.getFiberVar()); mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "getCallee", "()Ljava/lang/Object;"); mv.visitTypeInsn(CHECKCAST, getReceiverTypename()); } spos++; } int len = f.getStackLen(); // fill rest of stack with dummy args for (; spos < len; spos++) { Value v = f.getStack(spos); int vmt = VMType.toVmType(v.getTypeDesc()); mv.visitInsn(VMType.constInsn[vmt]); } mv.visitJumpInsn(GOTO, callLabel.getLabel()); } /** * Before we make the target call, we need to call fiber.down(), to update * it on the depth of the stack. genPostCall subsequently arranges to call * fiber.up(). We also need to push the fiber as the last argument to the * pausable method. We accomplish both of these objectives by having * fiber.down() do its book-keeping and return fiber, which is left on the * stack before the call is made. * * <pre> * * F_CALL: * push fiber.down() * invoke[virtual|static] classname/method modifiedDesc * </pre> * * @param mv */ static String fiberArg = D_FIBER + ')'; void genCall(MethodVisitor mv) { mv.visitLabel(callLabel.getLabel()); loadVar(mv, TOBJECT, methodWeaver.getFiberVar()); mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "down", "()" + D_FIBER); MethodInsnNode mi = getMethodInsn(); if (mi.desc.indexOf(fiberArg) == -1) { // Don't add another fiberarg if it already has one. It'll already // have one if we have copied jsr instructions and modified the // same instruction node earlier. mi.desc = mi.desc.replace(")", fiberArg); } mi.accept(mv); } /** * After the pausable method call is over, we have four possibilities. The * called method yielded, or it returned normally. Orthogonally, we have * saved state or not. fiber.up() returns the combined status * ? * <pre> * * switch (fiber.up()) { * default: * 0: goto RESUME; // Not yielding , no State -- resume normal operations * 1: goto RESTORE; // Not yielding, has State -- restore state before resuming * 2: goto SAVE; // Yielding, no state -- save state before unwinding stack * 3: goto UNWIND // Yielding, has state -- nothing to do but unwind. * } * SAVE: * ... * xRETURN * UNWIND: * ... * xRETURN * RESTORE: * ... * // fall through to RESUME * RESUME: * ... original code * </pre> * * @param mv * */ void genPostCall(MethodVisitor mv) { loadVar(mv, TOBJECT, methodWeaver.getFiberVar()); mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "up", "()I"); LabelNode restoreLabel = new LabelNode(); LabelNode saveLabel = new LabelNode(); LabelNode unwindLabel = new LabelNode(); LabelNode[] labels = new LabelNode[] { resumeLabel, restoreLabel, saveLabel, unwindLabel }; new TableSwitchInsnNode(0, 3, resumeLabel, labels).accept(mv); genSave(mv, saveLabel); genUnwind(mv, unwindLabel); genRestore(mv, restoreLabel); resumeLabel.accept(mv); } /** * Code for the case where we are yielding, and we have state built up from * a previous call. There's nothing meaningful to do except keep the * verifier happy. Pop the bottom stack, then return a dummy return value * (if this method -- note: not the called method -- returns a value) * * @param mv */ private void genUnwind(MethodVisitor mv, LabelNode unwindLabel) { unwindLabel.accept(mv); // After the call returns, the stack would be left with numBottom plus // return value // pop callee's dummy return value first String rdesc = getReturnType(); if (rdesc != D_VOID) { mv.visitInsn(TypeDesc.isDoubleWord(rdesc) ? POP2 : POP); } // pop numbottom int i = getNumBottom() - 1; // the numBottom'th element Frame f = bb.startFrame; for (; i >= 0; i--) { mv.visitInsn(f.getStack(i).isCategory1() ? POP : POP2); } // Now for the return value of this (the calling) method rdesc = TypeDesc.getReturnTypeDesc(bb.flow.desc); if (rdesc != D_VOID) { // ICONST_0; IRETURN or ACONST_NULL; ARETURN etc. int vmt = VMType.toVmType(rdesc); mv.visitInsn(VMType.constInsn[vmt]); mv.visitInsn(VMType.retInsn[vmt]); } else { mv.visitInsn(RETURN); } } private String getReturnType() { return TypeDesc.getReturnTypeDesc(getMethodInsn().desc); } /** * Yielding, but state hasn't been captured yet. We create a state object * and save each object in valInfoList in its corresponding field. * * Note that we save each stack item into a scratch register before loading * it into a field. The reason is that we need to get the State ref under * the stack item before we can do a putfield. The alternative is to load * the State item, then do a swap or a dup_x2;pop (depending on the value's * category). We'll go with the earlier approach because stack manipulations * don't seem to perform as well in the current crop of JVMs. * * @param mv */ private void genSave(MethodVisitor mv, LabelNode saveLabel) { saveLabel.accept(mv); Frame f = bb.startFrame; // pop return value if any. String retType = getReturnType(); if (retType != D_VOID) { // Yielding, so the return value from the called // function is a dummy value mv.visitInsn(TypeDesc.isDoubleWord(retType) ? POP2 : POP); } /* * Instantiate state class. Call one of new xxxState(this, pc, fiber), * or new xxxState(pc, fiber) depending whether this method is static or * not. Note that are not referring to the called method. * * pc, "the program counter" is merely the index of the call weaver in * the method weaver's list. This allows us to do a switch in the * method's entry. */ mv.visitTypeInsn(NEW, stateClassName); mv.visitInsn(DUP); // // call constructor mv.visitMethodInsn(INVOKESPECIAL, stateClassName, "<init>", "()V"); // save state in register int stateVar = allocVar(1); storeVar(mv, TOBJECT, stateVar); // state.self = this if the current executing method isn't static if (!bb.flow.isStatic()) { loadVar(mv, TOBJECT, stateVar); mv.visitVarInsn(ALOAD, 0); // for state.self == this mv.visitFieldInsn(PUTFIELD, STATE_CLASS, "self", D_OBJECT); } int pc = methodWeaver.getPC(this); loadVar(mv, TOBJECT, stateVar); // state.pc if (pc < 6) { mv.visitInsn(ICONST_0 + pc); } else { mv.visitIntInsn(BIPUSH, pc); } mv.visitFieldInsn(PUTFIELD, STATE_CLASS, "pc", D_INT); // First save bottom stack into state int i = getNumBottom() - 1; for (; i >= 0; i--) { Value v = f.getStack(i); ValInfo vi = valInfoList.find(v); if (vi == null) { // it must be a constant or a duplicate, which is why we don't // have any information on it. just pop it. mv.visitInsn(v.category() == 2 ? POP2 : POP); } else { /** * xstore <scratchVar> ; send stack elem to some local var * aload <stateVar> ; load state * xload <scratchVar> ; get stack elem back * putfield ... ; state.fx = */ int var = allocVar(vi.val.category()); storeVar(mv, vi.vmt, var); loadVar(mv, VMType.TOBJECT, stateVar); loadVar(mv, vi.vmt, var); mv.visitFieldInsn(PUTFIELD, stateClassName, vi.fieldName, vi.fieldDesc()); releaseVar(var, vi.val.category()); } } // Now load up registers into fields for (ValInfo vi : valInfoList) { // Ignore values on stack if (vi.var == -1) continue; // aload state var // xload <var> loadVar(mv, TOBJECT, stateVar); loadVar(mv, vi.vmt, vi.var); mv.visitFieldInsn(PUTFIELD, stateClassName, vi.fieldName, vi.fieldDesc()); } // Fiber.setState(state); loadVar(mv, TOBJECT, methodWeaver.getFiberVar()); loadVar(mv, TOBJECT, stateVar); mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "setState", "(" + D_STATE + ")V"); releaseVar(stateVar, 1); // Figure out the return type of the calling method and issue the // appropriate xRETURN instruction retType = TypeDesc.getReturnTypeDesc(bb.flow.desc); if (retType == D_VOID) { mv.visitInsn(RETURN); } else { int vmt = VMType.toVmType(retType); // ICONST_0, DCONST_0 etc. mv.visitInsn(VMType.constInsn[vmt]); // IRETURN, DRETURN, etc. mv.visitInsn(VMType.retInsn[vmt]); } } /** * Not yielding (resuming normally), but have stored state. We need to * restore from state before resuming. This is slightly more work than * saving state, because we have to restore constants and duplicates too. * <b> * * Note that the return value (if any) has a real value, which is why it * needs to be saved away before we can get access to the bottom elements to * pop them out. * * <pre> * If there is anything at the bottom save return value in scratch register * pop unnecessary bottom stuff. * * load fiber.curState * cast to specific state (if necessary) * astore in scratch <stateVar> * * for each value in frame.var * if val is constant or is in valInfoList, * push constant or load field (appropriately) * for each value in bottom stack * restore value similar to above * restore return value if any from scratch register * </pre> */ private void genRestore(MethodVisitor mv, LabelNode restoreLabel) { restoreLabel.accept(mv); Frame f = bb.startFrame; int numBottom = getNumBottom(); int retVar = -1; int retctype = -1; if (numBottom > 0) { // We have dummy values sitting in the stack. pop 'em. // But first, check if we have a real return value on top String retType = getReturnType(); if (retType != D_VOID) { // .. yep. save it to scratch register retctype = VMType.toVmType(retType); retVar = allocVar(VMType.category[retctype]); storeVar(mv, retctype, retVar); } // pop dummy values from stack bottom for (int i = numBottom-1; i >= 0; i--) { Value v = f.getStack(i); int insn = v.isCategory1() ? POP : POP2; mv.visitInsn(insn); } } // Restore variables from state int stateVar = -1; if (valInfoList.size() > 0) { stateVar = allocVar(1); } genRestoreVars(mv, stateVar); // Now restore the bottom values in the stack from state for (int i = 0; i < numBottom; i++) { Value v = f.getStack(i); if (v.isConstant()) { loadConstant(mv, v); } else { ValInfo vi = valInfoList.find(v); if (vi.var == -1) { loadVar(mv, TOBJECT, stateVar); mv.visitFieldInsn(GETFIELD, stateClassName, vi.fieldName, vi.fieldDesc()); checkcast(mv, v); } else { // this stack value is a duplicate of a local var, which has // already been loaded and is of the right type loadVar(mv, vi.vmt, vi.var); } } } // restore the saved return value, if any // But we would have popped and saved the return value only // if there were any dummy values in the stack bottom to be // cleared out. If not, we wouldn't have bothered // popping the return value in the first place. if (numBottom > 0) { if (retVar != -1) { loadVar(mv, retctype, retVar); } } releaseVar(stateVar, 1); if (retctype != -1) { releaseVar(retVar, VMType.category[retctype]); } // Fall through to the resumeLabel in genNY_NS, so no goto required. } void genRestoreEx(MethodVisitor mv, LabelNode restoreLabel) { restoreLabel.accept(mv); int stateVar = -1; if (valInfoList.size() > 0) { stateVar = allocVar(1); } genRestoreVars(mv, stateVar); releaseVar(stateVar, 1); } /* */ private void genRestoreVars(MethodVisitor mv, int stateVar) { Frame f = bb.startFrame; if (valInfoList.size() > 0) { // need to have state in a local variable loadVar(mv, TOBJECT, methodWeaver.getFiberVar()); mv.visitFieldInsn(GETFIELD, FIBER_CLASS, "curState", D_STATE); if (!stateClassName.equals(STATE_CLASS)) { mv.visitTypeInsn(CHECKCAST, stateClassName); } storeVar(mv, TOBJECT, stateVar); } // no need to restore "this" if it's already there. Usage u = bb.usage; int len = f.getMaxLocals(); int i = bb.flow.isStatic() ? 0 : 1; for (; i < len; i++) { if (!u.isLiveIn(i)) continue; Value v = f.getLocal(i); int vmt = VMType.toVmType(v.getTypeDesc()); if (v.isConstant()) { loadConstant(mv, v); } else { ValInfo vi = valInfoList.find(v); if (vi.var == i) { // load val from state loadVar(mv, TOBJECT, stateVar); mv.visitFieldInsn(GETFIELD, stateClassName, vi.fieldName, vi.fieldDesc()); checkcast(mv, v); // don't need to do this in the constant case } else { // It is a duplicate of another var. No need to load this var from stack assert vi.var < i; loadVar(mv, vi.vmt, vi.var); } } // Convert types from vm types to value's real type, if necessary // store into local storeVar(mv, vmt, i); } releaseVar(stateVar, 1); } private String getReceiverTypename() { MethodInsnNode min = getMethodInsn(); return min.owner; } /** * We have loaded a value of one of the five VM types into the stack and we * need to cast it to the value's type, if necessary * * @param mv * @param v */ private void checkcast(MethodVisitor mv, Value v) { String valType = v.getTypeDesc(); int vmt = VMType.toVmType(valType); switch (vmt) { case VMType.TOBJECT: if (valType == D_OBJECT || valType == D_NULL) { return; } mv.visitTypeInsn(CHECKCAST, TypeDesc.getInternalName(valType)); break; case VMType.TINT: if (valType == D_INT) return; int insn = 0; if (valType == D_SHORT) insn = I2S; else if (valType == D_BYTE) insn = I2B; else if (valType == D_CHAR) insn = I2C; else assert valType == D_BOOLEAN; mv.visitInsn(insn); break; default: break; } } private void loadConstant(MethodVisitor mv, Value v) { if (v.getTypeDesc() == D_NULL) { mv.visitInsn(ACONST_NULL); return; } Object c = v.getConstVal(); if (c instanceof Integer) { int i = (Integer) c; if (i > -1 && i <= 5) { mv.visitInsn(i + 1 + ICONST_M1); return; } else if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, i); return; } else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, i); return; } } else if (c instanceof Float) { Float f = ((Float) c).floatValue(); int insn = 0; if (f == 0.0) insn = FCONST_0; else if (f == 1.0) insn = FCONST_1; else if (f == 2.0) insn = FCONST_2; if (insn != 0) { mv.visitInsn(insn); return; } } else if (c instanceof Long) { Long l = ((Long) c).longValue(); int insn = 0; if (l == 0L) insn = LCONST_0; else if (l == 1L) insn = LCONST_1; if (insn != 0) { mv.visitInsn(insn); return; } } else if (c instanceof Double) { Double d = ((Double) c).doubleValue(); int insn = 0; if (d == 0.0) insn = DCONST_0; else if (d == 1.0) insn = DCONST_1; if (insn != 0) { mv.visitInsn(insn); return; } } // No special constants found. mv.visitLdcInsn(c); } private String createStateClass() { return valInfoList.size() == 0 ? STATE_CLASS : methodWeaver.createStateClass(valInfoList); } private int allocVar(int size) { int var; for (var = 0;; var++) { // if the var'th local is not set (if double, check the next word // too) if (!varUsage.get(var)) { if (size == 1 || !varUsage.get(var + 1)) { break; } } } varUsage.set(var); if (size == 2) { varUsage.set(var + 1); methodWeaver.ensureMaxVars(var + 2); // var is 0-based } else { methodWeaver.ensureMaxVars(var + 1); } return var; } private void releaseVar(int var, int size) { if (var == -1) return; varUsage.clear(var); if (size == 2) { varUsage.clear(var + 1); } } BasicBlock getBasicBlock() { return bb; } } class ValInfo implements Comparable<ValInfo> { /** * The var to which the value belongs. It remains undefined if it is a stack * item. */ int var = -1; /** * The value to hold. This gives us information about the type, whether the * value is duplicated and whether it is a constant value. */ Value val; /** * The type of value boiled down to one of the canonical types. */ int vmt; /** * Names of the fields in the state var: "f0", "f1", etc, according to their * position in the call weaver's valInfoList. */ String fieldName; ValInfo(Value v) { val = v; vmt = VMType.toVmType(v.getTypeDesc()); } String fieldDesc() { return VMType.fieldDesc[vmt]; } public int compareTo(ValInfo that) { if (this == that) return 0; if (this.vmt < that.vmt) return -1; if (this.vmt > that.vmt) return 1; if (this.var < that.var) return -1; if (this.var > that.var) return 1; return 0; } } class ValInfoList extends ArrayList<ValInfo> { private static final long serialVersionUID = -2339264992519046024L; public ValInfo find(Value v) { int i = indexOf(v); return (i == -1) ? null : get(i); } public int indexOf(Value v) { int len = size(); for (int i = 0; i < len; i++) { if (get(i).val == v) return i; } return -1; } public boolean contains(Value v) { return indexOf(v) != -1; } } class VMType { static final int TOBJECT = 0; static final int TINT = 1; static final int TLONG = 2; static final int TDOUBLE = 3; static final int TFLOAT = 4; static final int[] constInsn = { ACONST_NULL, ICONST_0, LCONST_0, DCONST_0, FCONST_0 }; static final int[] loadInsn = { ALOAD, ILOAD, LLOAD, DLOAD, FLOAD }; static final int[] retInsn = { ARETURN, IRETURN, LRETURN, DRETURN, FRETURN }; static final int[] ldInsn = { ALOAD_0, ILOAD_0, LLOAD_0, DLOAD_0, FLOAD_0 }; static final int[] stInsn = { ASTORE_0, ISTORE_0, LSTORE_0, DSTORE_0, FSTORE_0 }; static final int[] storeInsn = { ASTORE, ISTORE, LSTORE, DSTORE, FSTORE }; static final String[] fieldDesc = { D_OBJECT, D_INT, D_LONG, D_DOUBLE, D_FLOAT }; static final String[] abbrev = { "O", "I", "L", "D", "F" }; static final int[] category = { 1, 1, 2, 2, 1 }; static int toVmType(String type) { switch (type.charAt(0)) { case 'Z': case 'B': case 'C': case 'S': case 'I': return TINT; case 'D': return TDOUBLE; case 'F': return TFLOAT; case 'J': return TLONG; case 'N': // null case 'A': // catch handler return address case 'L': // normal type case '[': // array return TOBJECT; default: assert false : "Type " + type + " not handled"; } return ' '; } static void loadVar(MethodVisitor mv, int vmt, int var) { assert var >= 0 : "Got var = " + var; // ASM4.1 doesn't like short-form ALOAD_n instructions. Instead, we use ALOAD n. // if (var < 4) { // // short instructions like ALOAD_n exist for n = 0 .. 4 // mv.visitInsn(ldInsn[vmt] + var); // } else { mv.visitVarInsn(loadInsn[vmt], var); // } } static void storeVar(MethodVisitor mv, int vmt, int var) { assert var >= 0; // if (var < 4) { // // short instructions like ALOAD_n exist for n = 0 .. 4 // mv.visitInsn(stInsn[vmt] + var); // } else { mv.visitVarInsn(storeInsn[vmt], var); // } } }