/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dx.cf.code; import java.util.ArrayList; import com.android.dx.rop.code.FillArrayDataInsn; import com.android.dx.rop.code.Insn; import com.android.dx.rop.code.PlainCstInsn; import com.android.dx.rop.code.PlainInsn; import com.android.dx.rop.code.RegOps; import com.android.dx.rop.code.RegisterSpec; import com.android.dx.rop.code.RegisterSpecList; import com.android.dx.rop.code.Rop; import com.android.dx.rop.code.Rops; import com.android.dx.rop.code.SourcePosition; import com.android.dx.rop.code.SwitchInsn; import com.android.dx.rop.code.ThrowingCstInsn; import com.android.dx.rop.code.ThrowingInsn; import com.android.dx.rop.code.TranslationAdvice; import com.android.dx.rop.cst.Constant; import com.android.dx.rop.cst.CstBaseMethodRef; import com.android.dx.rop.cst.CstFieldRef; import com.android.dx.rop.cst.CstMethodRef; import com.android.dx.rop.cst.CstNat; import com.android.dx.rop.cst.CstType; import com.android.dx.rop.cst.CstUtf8; import com.android.dx.rop.type.Type; import com.android.dx.rop.type.TypeBearer; import com.android.dx.rop.type.TypeList; import com.android.dx.util.IntList; /** * Machine implementation for use by {@link Ropper}. */ /*package*/ final class RopperMachine extends ValueAwareMachine { /** {@code non-null;} array reflection class */ private static final CstType ARRAY_REFLECT_TYPE = new CstType(Type.internClassName("java/lang/reflect/Array")); /** * {@code non-null;} method constant for use in converting * {@code multianewarray} instructions */ private static final CstMethodRef MULTIANEWARRAY_METHOD = new CstMethodRef(ARRAY_REFLECT_TYPE, new CstNat(new CstUtf8("newInstance"), new CstUtf8("(Ljava/lang/Class;[I)" + "Ljava/lang/Object;"))); /** {@code non-null;} {@link Ropper} controlling this instance */ private final Ropper ropper; /** {@code non-null;} method being converted */ private final ConcreteMethod method; /** {@code non-null;} translation advice */ private final TranslationAdvice advice; /** max locals of the method */ private final int maxLocals; /** {@code non-null;} instructions for the rop basic block in-progress */ private final ArrayList<Insn> insns; /** {@code non-null;} catches for the block currently being processed */ private TypeList catches; /** whether the catches have been used in an instruction */ private boolean catchesUsed; /** whether the block contains a {@code return} */ private boolean returns; /** primary successor index */ private int primarySuccessorIndex; /** {@code >= 0;} number of extra basic blocks required */ private int extraBlockCount; /** true if last processed block ends with a jsr or jsr_W*/ private boolean hasJsr; /** true if an exception can be thrown by the last block processed */ private boolean blockCanThrow; /** * If non-null, the ReturnAddress that was used by the terminating ret * instruction. If null, there was no ret instruction encountered. */ private ReturnAddress returnAddress; /** * {@code null-ok;} the appropriate {@code return} op or {@code null} * if it is not yet known */ private Rop returnOp; /** * {@code null-ok;} the source position for the return block or {@code null} * if it is not yet known */ private SourcePosition returnPosition; /** * Constructs an instance. * * @param ropper {@code non-null;} ropper controlling this instance * @param method {@code non-null;} method being converted * @param advice {@code non-null;} translation advice to use */ public RopperMachine(Ropper ropper, ConcreteMethod method, TranslationAdvice advice) { super(method.getEffectiveDescriptor()); if (ropper == null) { throw new NullPointerException("ropper == null"); } if (advice == null) { throw new NullPointerException("advice == null"); } this.ropper = ropper; this.method = method; this.advice = advice; this.maxLocals = method.getMaxLocals(); this.insns = new ArrayList<Insn>(25); this.catches = null; this.catchesUsed = false; this.returns = false; this.primarySuccessorIndex = -1; this.extraBlockCount = 0; this.blockCanThrow = false; this.returnOp = null; this.returnPosition = null; } /** * Gets the instructions array. It is shared and gets modified by * subsequent calls to this instance. * * @return {@code non-null;} the instructions array */ public ArrayList<Insn> getInsns() { return insns; } /** * Gets the return opcode encountered, if any. * * @return {@code null-ok;} the return opcode */ public Rop getReturnOp() { return returnOp; } /** * Gets the return position, if known. * * @return {@code null-ok;} the return position */ public SourcePosition getReturnPosition() { return returnPosition; } /** * Gets ready to start working on a new block. This will clear the * {@link #insns} list, set {@link #catches}, reset whether it has * been used, reset whether the block contains a * {@code return}, and reset {@link #primarySuccessorIndex}. */ public void startBlock(TypeList catches) { this.catches = catches; insns.clear(); catchesUsed = false; returns = false; primarySuccessorIndex = 0; extraBlockCount = 0; blockCanThrow = false; hasJsr = false; returnAddress = null; } /** * Gets whether {@link #catches} was used. This indicates that the * last instruction in the block is one of the ones that can throw. * * @return whether {@code catches} has been used */ public boolean wereCatchesUsed() { return catchesUsed; } /** * Gets whether the block just processed ended with a * {@code return}. * * @return whether the block returns */ public boolean returns() { return returns; } /** * Gets the primary successor index. This is the index into the * successors list where the primary may be found or * {@code -1} if there are successors but no primary * successor. This may return something other than * {@code -1} in the case of an instruction with no * successors at all (primary or otherwise). * * @return {@code >= -1;} the primary successor index */ public int getPrimarySuccessorIndex() { return primarySuccessorIndex; } /** * Gets how many extra blocks will be needed to represent the * block currently being translated. Each extra block should consist * of one instruction from the end of the original block. * * @return {@code >= 0;} the number of extra blocks needed */ public int getExtraBlockCount() { return extraBlockCount; } /** * @return true if at least one of the insn processed since the last * call to startBlock() can throw. */ public boolean canThrow() { return blockCanThrow; } /** * @return true if a JSR has ben encountered since the last call to * startBlock() */ public boolean hasJsr() { return hasJsr; } /** * @return {@code true} if a {@code ret} has ben encountered since * the last call to {@code startBlock()} */ public boolean hasRet() { return returnAddress != null; } /** * @return {@code null-ok;} return address of a {@code ret} * instruction if encountered since last call to startBlock(). * {@code null} if no ret instruction encountered. */ public ReturnAddress getReturnAddress() { return returnAddress; } /** {@inheritDoc} */ @Override public void run(Frame frame, int offset, int opcode) { /* * This is the stack pointer after the opcode's arguments have been * popped. */ int stackPointer = maxLocals + frame.getStack().size(); // The sources have to be retrieved before super.run() gets called. RegisterSpecList sources = getSources(opcode, stackPointer); int sourceCount = sources.size(); super.run(frame, offset, opcode); SourcePosition pos = method.makeSourcePosistion(offset); RegisterSpec localTarget = getLocalTarget(); int destCount = resultCount(); RegisterSpec dest; if (destCount == 0) { dest = null; switch (opcode) { case ByteOps.POP: case ByteOps.POP2: { // These simply don't appear in the rop form. return; } } } else if (localTarget != null) { dest = localTarget; } else if (destCount == 1) { dest = RegisterSpec.make(stackPointer, result(0)); } else { /* * This clause only ever applies to the stack manipulation * ops that have results (that is, dup* and swap but not * pop*). * * What we do is first move all the source registers into * the "temporary stack" area defined for the method, and * then move stuff back down onto the main "stack" in the * arrangement specified by the stack op pattern. * * Note: This code ends up emitting a lot of what will * turn out to be superfluous moves (e.g., moving back and * forth to the same local when doing a dup); however, * that makes this code a bit easier (and goodness knows * it doesn't need any extra complexity), and all the SSA * stuff is going to want to deal with this sort of * superfluous assignment anyway, so it should be a wash * in the end. */ int scratchAt = ropper.getFirstTempStackReg(); RegisterSpec[] scratchRegs = new RegisterSpec[sourceCount]; for (int i = 0; i < sourceCount; i++) { RegisterSpec src = sources.get(i); TypeBearer type = src.getTypeBearer(); RegisterSpec scratch = src.withReg(scratchAt); insns.add(new PlainInsn(Rops.opMove(type), pos, scratch, src)); scratchRegs[i] = scratch; scratchAt += src.getCategory(); } for (int pattern = getAuxInt(); pattern != 0; pattern >>= 4) { int which = (pattern & 0x0f) - 1; RegisterSpec scratch = scratchRegs[which]; TypeBearer type = scratch.getTypeBearer(); insns.add(new PlainInsn(Rops.opMove(type), pos, scratch.withReg(stackPointer), scratch)); stackPointer += type.getType().getCategory(); } return; } TypeBearer destType = (dest != null) ? dest : Type.VOID; Constant cst = getAuxCst(); int ropOpcode; Rop rop; Insn insn; if (opcode == ByteOps.MULTIANEWARRAY) { blockCanThrow = true; // Add the extra instructions for handling multianewarray. extraBlockCount = 6; /* * Add an array constructor for the int[] containing all the * dimensions. */ RegisterSpec dimsReg = RegisterSpec.make(dest.getNextReg(), Type.INT_ARRAY); rop = Rops.opFilledNewArray(Type.INT_ARRAY, sourceCount); insn = new ThrowingCstInsn(rop, pos, sources, catches, CstType.INT_ARRAY); insns.add(insn); // Add a move-result for the new-filled-array rop = Rops.opMoveResult(Type.INT_ARRAY); insn = new PlainInsn(rop, pos, dimsReg, RegisterSpecList.EMPTY); insns.add(insn); /* * Add a const-class instruction for the specified array * class. */ /* * Remove as many dimensions from the originally specified * class as are given in the explicit list of dimensions, * so as to pass the right component class to the standard * Java library array constructor. */ Type componentType = ((CstType) cst).getClassType(); for (int i = 0; i < sourceCount; i++) { componentType = componentType.getComponentType(); } RegisterSpec classReg = RegisterSpec.make(dest.getReg(), Type.CLASS); if (componentType.isPrimitive()) { /* * The component type is primitive (e.g., int as opposed * to Integer), so we have to fetch the corresponding * TYPE class. */ CstFieldRef typeField = CstFieldRef.forPrimitiveType(componentType); insn = new ThrowingCstInsn(Rops.GET_STATIC_OBJECT, pos, RegisterSpecList.EMPTY, catches, typeField); } else { /* * The component type is an object type, so just make a * normal class reference. */ insn = new ThrowingCstInsn(Rops.CONST_OBJECT, pos, RegisterSpecList.EMPTY, catches, new CstType(componentType)); } insns.add(insn); // Add a move-result-pseudo for the get-static or const rop = Rops.opMoveResultPseudo(classReg.getType()); insn = new PlainInsn(rop, pos, classReg, RegisterSpecList.EMPTY); insns.add(insn); /* * Add a call to the "multianewarray method," that is, * Array.newInstance(class, dims). Note: The result type * of newInstance() is Object, which is why the last * instruction in this sequence is a cast to the right * type for the original instruction. */ RegisterSpec objectReg = RegisterSpec.make(dest.getReg(), Type.OBJECT); insn = new ThrowingCstInsn( Rops.opInvokeStatic(MULTIANEWARRAY_METHOD.getPrototype()), pos, RegisterSpecList.make(classReg, dimsReg), catches, MULTIANEWARRAY_METHOD); insns.add(insn); // Add a move-result. rop = Rops.opMoveResult(MULTIANEWARRAY_METHOD.getPrototype() .getReturnType()); insn = new PlainInsn(rop, pos, objectReg, RegisterSpecList.EMPTY); insns.add(insn); /* * And finally, set up for the remainder of this method to * add an appropriate cast. */ opcode = ByteOps.CHECKCAST; sources = RegisterSpecList.make(objectReg); } else if (opcode == ByteOps.JSR) { // JSR has no Rop instruction hasJsr = true; return; } else if (opcode == ByteOps.RET) { try { returnAddress = (ReturnAddress)arg(0); } catch (ClassCastException ex) { throw new RuntimeException( "Argument to RET was not a ReturnAddress", ex); } // RET has no Rop instruction. return; } ropOpcode = jopToRopOpcode(opcode, cst); rop = Rops.ropFor(ropOpcode, destType, sources, cst); Insn moveResult = null; if (dest != null && rop.isCallLike()) { /* * We're going to want to have a move-result in the next * basic block. */ extraBlockCount++; moveResult = new PlainInsn( Rops.opMoveResult(((CstBaseMethodRef) cst).getPrototype() .getReturnType()), pos, dest, RegisterSpecList.EMPTY); dest = null; } else if (dest != null && rop.canThrow()) { /* * We're going to want to have a move-result-pseudo in the * next basic block. */ extraBlockCount++; moveResult = new PlainInsn( Rops.opMoveResultPseudo(dest.getTypeBearer()), pos, dest, RegisterSpecList.EMPTY); dest = null; } if (ropOpcode == RegOps.NEW_ARRAY) { /* * In the original bytecode, this was either a primitive * array constructor "newarray" or an object array * constructor "anewarray". In the former case, there is * no explicit constant, and in the latter, the constant * is for the element type and not the array type. The rop * instruction form for both of these is supposed to be * the resulting array type, so we initialize / alter * "cst" here, accordingly. Conveniently enough, the rop * opcode already gets constructed with the proper array * type. */ cst = CstType.intern(rop.getResult()); } else if ((cst == null) && (sourceCount == 2)) { TypeBearer lastType = sources.get(1).getTypeBearer(); if (lastType.isConstant() && advice.hasConstantOperation(rop, sources.get(0), sources.get(1))) { /* * The target architecture has an instruction that can * build in the constant found in the second argument, * so pull it out of the sources and just use it as a * constant here. */ cst = (Constant) lastType; sources = sources.withoutLast(); rop = Rops.ropFor(ropOpcode, destType, sources, cst); } } SwitchList cases = getAuxCases(); ArrayList<Constant> initValues = getInitValues(); boolean canThrow = rop.canThrow(); blockCanThrow |= canThrow; if (cases != null) { if (cases.size() == 0) { // It's a default-only switch statement. It can happen! insn = new PlainInsn(Rops.GOTO, pos, null, RegisterSpecList.EMPTY); primarySuccessorIndex = 0; } else { IntList values = cases.getValues(); insn = new SwitchInsn(rop, pos, dest, sources, values); primarySuccessorIndex = values.size(); } } else if (ropOpcode == RegOps.RETURN) { /* * Returns get turned into the combination of a move (if * non-void and if the return doesn't already mention * register 0) and a goto (to the return block). */ if (sources.size() != 0) { RegisterSpec source = sources.get(0); TypeBearer type = source.getTypeBearer(); if (source.getReg() != 0) { insns.add(new PlainInsn(Rops.opMove(type), pos, RegisterSpec.make(0, type), source)); } } insn = new PlainInsn(Rops.GOTO, pos, null, RegisterSpecList.EMPTY); primarySuccessorIndex = 0; updateReturnOp(rop, pos); returns = true; } else if (cst != null) { if (canThrow) { insn = new ThrowingCstInsn(rop, pos, sources, catches, cst); catchesUsed = true; primarySuccessorIndex = catches.size(); } else { insn = new PlainCstInsn(rop, pos, dest, sources, cst); } } else if (canThrow) { insn = new ThrowingInsn(rop, pos, sources, catches); catchesUsed = true; if (opcode == ByteOps.ATHROW) { /* * The op athrow is the only one where it's possible * to have non-empty successors and yet not have a * primary successor. */ primarySuccessorIndex = -1; } else { primarySuccessorIndex = catches.size(); } } else { insn = new PlainInsn(rop, pos, dest, sources); } insns.add(insn); if (moveResult != null) { insns.add(moveResult); } /* * If initValues is non-null, it means that the parser has * seen a group of compatible constant initialization * bytecodes that are applied to the current newarray. The * action we take here is to convert these initialization * bytecodes into a single fill-array-data ROP which lays out * all the constant values in a table. */ if (initValues != null) { extraBlockCount++; insn = new FillArrayDataInsn(Rops.FILL_ARRAY_DATA, pos, RegisterSpecList.make(moveResult.getResult()), initValues, cst); insns.add(insn); } } /** * Helper for {@link #run}, which gets the list of sources for the. * instruction. * * @param opcode the opcode being translated * @param stackPointer {@code >= 0;} the stack pointer after the * instruction's arguments have been popped * @return {@code non-null;} the sources */ private RegisterSpecList getSources(int opcode, int stackPointer) { int count = argCount(); if (count == 0) { // We get an easy out if there aren't any sources. return RegisterSpecList.EMPTY; } int localIndex = getLocalIndex(); RegisterSpecList sources; if (localIndex >= 0) { // The instruction is operating on a local variable. sources = new RegisterSpecList(1); sources.set(0, RegisterSpec.make(localIndex, arg(0))); } else { sources = new RegisterSpecList(count); int regAt = stackPointer; for (int i = 0; i < count; i++) { RegisterSpec spec = RegisterSpec.make(regAt, arg(i)); sources.set(i, spec); regAt += spec.getCategory(); } switch (opcode) { case ByteOps.IASTORE: { /* * The Java argument order for array stores is * (array, index, value), but the rop argument * order is (value, array, index). The following * code gets the right arguments in the right * places. */ if (count != 3) { throw new RuntimeException("shouldn't happen"); } RegisterSpec array = sources.get(0); RegisterSpec index = sources.get(1); RegisterSpec value = sources.get(2); sources.set(0, value); sources.set(1, array); sources.set(2, index); break; } case ByteOps.PUTFIELD: { /* * Similar to above: The Java argument order for * putfield is (object, value), but the rop * argument order is (value, object). */ if (count != 2) { throw new RuntimeException("shouldn't happen"); } RegisterSpec obj = sources.get(0); RegisterSpec value = sources.get(1); sources.set(0, value); sources.set(1, obj); break; } } } sources.setImmutable(); return sources; } /** * Sets or updates the information about the return block. * * @param op {@code non-null;} the opcode to use * @param pos {@code non-null;} the position to use */ private void updateReturnOp(Rop op, SourcePosition pos) { if (op == null) { throw new NullPointerException("op == null"); } if (pos == null) { throw new NullPointerException("pos == null"); } if (returnOp == null) { returnOp = op; returnPosition = pos; } else { if (returnOp != op) { throw new SimException("return op mismatch: " + op + ", " + returnOp); } if (pos.getLine() > returnPosition.getLine()) { // Pick the largest line number to be the "canonical" return. returnPosition = pos; } } } /** * Gets the register opcode for the given Java opcode. * * @param jop {@code >= 0;} the Java opcode * @param cst {@code null-ok;} the constant argument, if any * @return {@code >= 0;} the corresponding register opcode */ private int jopToRopOpcode(int jop, Constant cst) { switch (jop) { case ByteOps.POP: case ByteOps.POP2: case ByteOps.DUP: case ByteOps.DUP_X1: case ByteOps.DUP_X2: case ByteOps.DUP2: case ByteOps.DUP2_X1: case ByteOps.DUP2_X2: case ByteOps.SWAP: case ByteOps.JSR: case ByteOps.RET: case ByteOps.MULTIANEWARRAY: { // These need to be taken care of specially. break; } case ByteOps.NOP: { return RegOps.NOP; } case ByteOps.LDC: case ByteOps.LDC2_W: { return RegOps.CONST; } case ByteOps.ILOAD: case ByteOps.ISTORE: { return RegOps.MOVE; } case ByteOps.IALOAD: { return RegOps.AGET; } case ByteOps.IASTORE: { return RegOps.APUT; } case ByteOps.IADD: case ByteOps.IINC: { return RegOps.ADD; } case ByteOps.ISUB: { return RegOps.SUB; } case ByteOps.IMUL: { return RegOps.MUL; } case ByteOps.IDIV: { return RegOps.DIV; } case ByteOps.IREM: { return RegOps.REM; } case ByteOps.INEG: { return RegOps.NEG; } case ByteOps.ISHL: { return RegOps.SHL; } case ByteOps.ISHR: { return RegOps.SHR; } case ByteOps.IUSHR: { return RegOps.USHR; } case ByteOps.IAND: { return RegOps.AND; } case ByteOps.IOR: { return RegOps.OR; } case ByteOps.IXOR: { return RegOps.XOR; } case ByteOps.I2L: case ByteOps.I2F: case ByteOps.I2D: case ByteOps.L2I: case ByteOps.L2F: case ByteOps.L2D: case ByteOps.F2I: case ByteOps.F2L: case ByteOps.F2D: case ByteOps.D2I: case ByteOps.D2L: case ByteOps.D2F: { return RegOps.CONV; } case ByteOps.I2B: { return RegOps.TO_BYTE; } case ByteOps.I2C: { return RegOps.TO_CHAR; } case ByteOps.I2S: { return RegOps.TO_SHORT; } case ByteOps.LCMP: case ByteOps.FCMPL: case ByteOps.DCMPL: { return RegOps.CMPL; } case ByteOps.FCMPG: case ByteOps.DCMPG: { return RegOps.CMPG; } case ByteOps.IFEQ: case ByteOps.IF_ICMPEQ: case ByteOps.IF_ACMPEQ: case ByteOps.IFNULL: { return RegOps.IF_EQ; } case ByteOps.IFNE: case ByteOps.IF_ICMPNE: case ByteOps.IF_ACMPNE: case ByteOps.IFNONNULL: { return RegOps.IF_NE; } case ByteOps.IFLT: case ByteOps.IF_ICMPLT: { return RegOps.IF_LT; } case ByteOps.IFGE: case ByteOps.IF_ICMPGE: { return RegOps.IF_GE; } case ByteOps.IFGT: case ByteOps.IF_ICMPGT: { return RegOps.IF_GT; } case ByteOps.IFLE: case ByteOps.IF_ICMPLE: { return RegOps.IF_LE; } case ByteOps.GOTO: { return RegOps.GOTO; } case ByteOps.LOOKUPSWITCH: { return RegOps.SWITCH; } case ByteOps.IRETURN: case ByteOps.RETURN: { return RegOps.RETURN; } case ByteOps.GETSTATIC: { return RegOps.GET_STATIC; } case ByteOps.PUTSTATIC: { return RegOps.PUT_STATIC; } case ByteOps.GETFIELD: { return RegOps.GET_FIELD; } case ByteOps.PUTFIELD: { return RegOps.PUT_FIELD; } case ByteOps.INVOKEVIRTUAL: { return RegOps.INVOKE_VIRTUAL; } case ByteOps.INVOKESPECIAL: { /* * Determine whether the opcode should be * INVOKE_DIRECT or INVOKE_SUPER. See vmspec-2 section 6 * on "invokespecial" as well as section 4.8.2 (7th * bullet point) for the gory details. */ CstBaseMethodRef ref = (CstBaseMethodRef) cst; if (ref.isInstanceInit() || (ref.getDefiningClass() == method.getDefiningClass()) || !method.getAccSuper()) { return RegOps.INVOKE_DIRECT; } return RegOps.INVOKE_SUPER; } case ByteOps.INVOKESTATIC: { return RegOps.INVOKE_STATIC; } case ByteOps.INVOKEINTERFACE: { return RegOps.INVOKE_INTERFACE; } case ByteOps.NEW: { return RegOps.NEW_INSTANCE; } case ByteOps.NEWARRAY: case ByteOps.ANEWARRAY: { return RegOps.NEW_ARRAY; } case ByteOps.ARRAYLENGTH: { return RegOps.ARRAY_LENGTH; } case ByteOps.ATHROW: { return RegOps.THROW; } case ByteOps.CHECKCAST: { return RegOps.CHECK_CAST; } case ByteOps.INSTANCEOF: { return RegOps.INSTANCE_OF; } case ByteOps.MONITORENTER: { return RegOps.MONITOR_ENTER; } case ByteOps.MONITOREXIT: { return RegOps.MONITOR_EXIT; } } throw new RuntimeException("shouldn't happen"); } }