Merge "Temporarily ignore jctf tests on Windows"
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 1d5f38c..7dab70a 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -79,6 +79,7 @@
private static DexProgramClass[] sortClasses(
DexApplication application, DexProgramClass[] classes) {
+ Arrays.sort(classes, (o1, o2) -> o1.type.descriptor.slowCompareTo(o2.type.descriptor));
SortingProgramClassVisitor classVisitor = new SortingProgramClassVisitor(application, classes);
classVisitor.run(classes);
return classVisitor.getSortedClasses();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b0ab940..118d0eb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -268,7 +268,7 @@
}
@Override
- public boolean traceInstruction(int index, IRBuilder builder) {
+ public int traceInstruction(int index, IRBuilder builder) {
Instruction dex = code.instructions[index];
int offset = dex.getOffset();
assert !dex.isPayload();
@@ -279,7 +279,7 @@
for (int relativeOffset : targets) {
builder.ensureNormalSuccessorBlock(offset, offset + relativeOffset);
}
- return true;
+ return index;
}
if (dex.canThrow()) {
// If the instruction can throw and is in a try block, add edges to its catch successors.
@@ -309,10 +309,10 @@
if (!(dex instanceof Throw)) {
builder.ensureNormalSuccessorBlock(offset, dex.getOffset() + dex.getSize());
}
- return true;
+ return index;
}
// Close the block if the instruction is a throw, otherwise the block remains open.
- return dex instanceof Throw;
+ return dex instanceof Throw ? index : -1;
}
if (dex.isSwitch()) {
// TODO(zerny): Remove this from block computation.
@@ -322,14 +322,14 @@
builder.ensureNormalSuccessorBlock(offset, target);
}
builder.ensureNormalSuccessorBlock(offset, offset + dex.getSize());
- return true;
+ return index;
}
// TODO(zerny): Remove this from block computation.
if (dex.hasPayload()) {
arrayFilledDataPayloadResolver.addPayloadUser((FillArrayData) dex);
}
// This instruction does not close the block.
- return false;
+ return -1;
}
private boolean inTryRange(Try tryItem, int offset) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index d24c8b4..8cd9a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -106,7 +106,7 @@
*/
public class IRBuilder {
- private static final int INITIAL_BLOCK_OFFSET = -1;
+ public static final int INITIAL_BLOCK_OFFSET = -1;
// SSA construction uses a worklist of basic blocks reachable from the entry and their
// instruction offsets.
@@ -165,7 +165,7 @@
}
}
- private static class BlockInfo {
+ public static class BlockInfo {
BasicBlock block = new BasicBlock();
IntSet normalPredecessors = new IntArraySet();
IntSet normalSuccessors = new IntArraySet();
@@ -282,6 +282,10 @@
this.options = options;
}
+ public Int2ReferenceSortedMap<BlockInfo> getCFG() {
+ return targets;
+ }
+
private void addToWorklist(BasicBlock block, int firstInstructionIndex) {
// TODO(ager): Filter out the ones that are already in the worklist, mark bit in block?
if (!block.isFilled()) {
@@ -318,8 +322,11 @@
// Process each instruction until the block is closed.
for (int index = startOfBlockIndex; index < source.instructionCount(); ++index) {
markIndexProcessed(index);
- boolean closed = source.traceInstruction(index, this);
- if (closed) {
+ int closedAt = source.traceInstruction(index, this);
+ if (closedAt != -1) {
+ if (closedAt + 1 < source.instructionCount()) {
+ ensureBlockWithoutEnqueuing(source.instructionOffset(closedAt + 1));
+ }
break;
}
// If the next instruction starts a block, fall through to it.
@@ -1079,6 +1086,7 @@
public void addMoveException(int dest) {
Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
+ assert out.getDebugInfo() == null;
MoveException instruction = new MoveException(out);
assert !instruction.instructionTypeCanThrow();
if (!currentBlock.getInstructions().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 1d8f18d..12be2c2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -28,17 +28,21 @@
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.ir.conversion.JarState.Local;
import com.android.tools.r8.ir.conversion.JarState.Slot;
import com.android.tools.r8.logging.Log;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Set;
import java.util.function.BiConsumer;
import org.objectweb.asm.Handle;
@@ -109,6 +113,16 @@
}
}
+ private static class JarStateWorklistItem {
+ BlockInfo blockInfo;
+ int instructionIndex;
+
+ public JarStateWorklistItem(BlockInfo blockInfo, int instructionIndex) {
+ this.blockInfo = blockInfo;
+ this.instructionIndex = instructionIndex;
+ }
+ }
+
// Various descriptors.
private static final String INT_ARRAY_DESC = "[I";
private static final String REFLECT_ARRAY_DESC = "Ljava/lang/reflect/Array;";
@@ -294,6 +308,70 @@
builder.addDebugUninitialized(localRegister, constType(localType));
}
}
+ computeBlockEntryJarStates(builder);
+ }
+
+ private void computeBlockEntryJarStates(IRBuilder builder) {
+ Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG();
+ Queue<JarStateWorklistItem> worklist = new LinkedList<>();
+ BlockInfo entry = CFG.get(IRBuilder.INITIAL_BLOCK_OFFSET);
+ if (CFG.get(0) != null) {
+ entry = CFG.get(0);
+ }
+ worklist.add(new JarStateWorklistItem(entry, 0));
+ state.recordStateForTarget(0, this);
+ for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
+ state.restoreState(item.instructionIndex);
+ // If the block being restored is a try-catch handler push the exception on the stack.
+ for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
+ TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i);
+ if (tryCatchBlockNode.handler == getInstruction(item.instructionIndex)) {
+ state.push(THROWABLE_TYPE);
+ break;
+ }
+ }
+ // Iterate each of the instructions in the block to compute the outgoing JarState.
+ for (int i = item.instructionIndex; i <= instructionCount(); ++i) {
+ // If we are at the end of the instruction stream or if we have reached the start
+ // of a new block, propagate the state to all successors and add the ones
+ // that changed to the worklist.
+ if (i == instructionCount() || (i != item.instructionIndex && CFG.containsKey(i))) {
+ item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
+ if (state.recordStateForTarget(offset, this)) {
+ if (offset >= 0) {
+ worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
+ }
+ }
+ });
+ item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining(offset -> {
+ if (state.recordStateForExceptionalTarget(offset, this)) {
+ if (offset >= 0) {
+ worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
+ }
+ }
+ });
+ break;
+ }
+
+ AbstractInsnNode insn = getInstruction(i);
+ updateState(insn);
+
+ if (!isControlFlowInstruction(insn)) {
+ updateStateForLocalVariableEnd(insn);
+ }
+ }
+ }
+ }
+
+ private void updateStateForLocalVariableEnd(AbstractInsnNode insn) {
+ assert !isControlFlowInstruction(insn);
+ if (!(insn.getNext() instanceof LabelNode)) {
+ return;
+ }
+ // If the label is the end of any local-variable scopes end the locals.
+ LabelNode label = (LabelNode) insn.getNext();
+ List<Local> locals = state.getLocalsToClose(label);
+ state.closeLocals(locals);
}
@Override
@@ -307,7 +385,6 @@
private void buildExceptionalPostlude(IRBuilder builder) {
assert isSynchronized();
- assert state.isInvalid();
generatingMethodSynchronization = true;
int exceptionRegister = 0; // We are exiting the method so we just overwrite register 0.
builder.addMoveException(exceptionRegister);
@@ -323,14 +400,10 @@
@Override
public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
- assert !state.isInvalid();
- state.recordStateForTarget(fallthroughInstructionIndex, this);
- state.invalidateState();
}
@Override
public void closedCurrentBlock() {
- assert state.isInvalid();
}
@Override
@@ -343,8 +416,8 @@
currentInstruction = insn;
assert verifyExceptionEdgesAreRecorded(insn);
- // Restore the state if invalid.
- if (state.isInvalid()) {
+ // If a new block is starting here, we restore the computed JarState.
+ if (builder.getCFG().containsKey(instructionIndex) || instructionIndex == 0) {
state.restoreState(instructionIndex);
// If the block being restored is a try-catch handler push the exception on the stack.
for (int i = 0; i < node.tryCatchBlocks.size(); i++) {
@@ -405,7 +478,7 @@
@Override
public DebugLocalInfo getCurrentLocal(int register) {
- return state.getLocalInfoForRegister(register);
+ return generatingMethodSynchronization ? null : state.getLocalInfoForRegister(register);
}
@Override
@@ -518,15 +591,15 @@
}
@Override
- public boolean traceInstruction(int index, IRBuilder builder) {
+ public int traceInstruction(int index, IRBuilder builder) {
AbstractInsnNode insn = getInstruction(index);
// Exit early on no-op instructions.
if (insn instanceof LabelNode || insn instanceof LineNumberNode) {
- return false;
+ return -1;
}
// If this instruction exits, close this block.
if (isReturn(insn)) {
- return true;
+ return index;
}
// For each target ensure a basic block and close this block.
int[] targets = getTargets(insn);
@@ -535,7 +608,7 @@
for (int target : targets) {
builder.ensureNormalSuccessorBlock(index, target);
}
- return true;
+ return index;
}
if (canThrow(insn)) {
List<TryCatchBlock> tryCatchBlocks = getTryHandlers(insn);
@@ -555,13 +628,13 @@
if (!isThrow(insn)) {
builder.ensureNormalSuccessorBlock(index, getOffset(insn.getNext()));
}
- return true;
+ return index;
}
// If the throwable instruction is "throw" it closes the block.
- return isThrow(insn);
+ return isThrow(insn) ? index : -1;
}
// This instruction does not close the block.
- return false;
+ return -1;
}
private List<TryCatchBlock> getPotentialTryHandlers(AbstractInsnNode insn) {
@@ -987,6 +1060,674 @@
}
}
+ // State updating procedures.
+
+ private void updateState(AbstractInsnNode insn) {
+ switch (insn.getType()) {
+ case AbstractInsnNode.INSN:
+ updateState((InsnNode) insn);
+ break;
+ case AbstractInsnNode.INT_INSN:
+ updateState((IntInsnNode) insn);
+ break;
+ case AbstractInsnNode.VAR_INSN:
+ updateState((VarInsnNode) insn);
+ break;
+ case AbstractInsnNode.TYPE_INSN:
+ updateState((TypeInsnNode) insn);
+ break;
+ case AbstractInsnNode.FIELD_INSN:
+ updateState((FieldInsnNode) insn);
+ break;
+ case AbstractInsnNode.METHOD_INSN:
+ updateState((MethodInsnNode) insn);
+ break;
+ case AbstractInsnNode.INVOKE_DYNAMIC_INSN:
+ updateState((InvokeDynamicInsnNode) insn);
+ break;
+ case AbstractInsnNode.JUMP_INSN:
+ updateState((JumpInsnNode) insn);
+ break;
+ case AbstractInsnNode.LABEL:
+ updateState((LabelNode) insn);
+ break;
+ case AbstractInsnNode.LDC_INSN:
+ updateState((LdcInsnNode) insn);
+ break;
+ case AbstractInsnNode.IINC_INSN:
+ updateState((IincInsnNode) insn);
+ break;
+ case AbstractInsnNode.TABLESWITCH_INSN:
+ updateState((TableSwitchInsnNode) insn);
+ break;
+ case AbstractInsnNode.LOOKUPSWITCH_INSN:
+ updateState((LookupSwitchInsnNode) insn);
+ break;
+ case AbstractInsnNode.MULTIANEWARRAY_INSN:
+ updateState((MultiANewArrayInsnNode) insn);
+ break;
+ case AbstractInsnNode.LINE:
+ updateState((LineNumberNode) insn);
+ break;
+ default:
+ throw new Unreachable("Unexpected instruction " + insn);
+ }
+ }
+
+ private void updateState(InsnNode insn) {
+ int opcode = insn.getOpcode();
+ switch (opcode) {
+ case Opcodes.NOP:
+ // Intentionally left empty.
+ break;
+ case Opcodes.ACONST_NULL:
+ state.push(JarState.NULL_TYPE);
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ state.push(Type.INT_TYPE);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ state.push(Type.LONG_TYPE);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ state.push(Type.FLOAT_TYPE);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ state.push(Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD: {
+ state.pop();
+ Slot array = state.pop(JarState.ARRAY_TYPE);
+ Type elementType = getArrayElementType(array.type);
+ if (elementType == null) {
+ // We propagate the null type, which will then get resolved to an
+ // actual type if we have a non-null type on another flow edge.
+ elementType = JarState.NULL_TYPE;
+ }
+ state.push(elementType);
+ break;
+ }
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE: {
+ state.pop();
+ state.pop();
+ state.pop();
+ break;
+ }
+ case Opcodes.POP: {
+ Slot value = state.pop();
+ assert value.isCategory1();
+ break;
+ }
+ case Opcodes.POP2: {
+ Slot value = state.pop();
+ if (value.isCategory1()) {
+ Slot value2 = state.pop();
+ assert value2.isCategory1();
+ }
+ break;
+ }
+ case Opcodes.DUP: {
+ Slot value = state.peek();
+ assert value.isCategory1();
+ state.push(value.type);
+ break;
+ }
+ case Opcodes.DUP_X1: {
+ // Stack transformation: ..., v2, v1 -> ..., v1, v2, v1
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert value1.isCategory1() && value2.isCategory1();
+ int stack2 = state.push(value1.type);
+ int stack1 = state.push(value2.type);
+ state.push(value1.type);
+ assert value2.register == stack2;
+ assert value1.register == stack1;
+ break;
+ }
+ case Opcodes.DUP_X2: {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert value1.isCategory1();
+ if (value2.isCategory1()) {
+ Slot value3 = state.pop();
+ assert value3.isCategory1();
+ // Stack transformation: ..., v3, v2, v1 -> ..., v1, v3, v2, v1
+ updateStateForDupOneBelowTwo(value3, value2, value1);
+ } else {
+ // Stack transformation: ..., w2, v1 -> ..., v1, w2, v1
+ updateStateForDupOneBelowOne(value2, value1);
+ }
+ break;
+ }
+ case Opcodes.DUP2: {
+ Slot value1 = state.pop();
+ if (value1.isCategory1()) {
+ Slot value2 = state.pop();
+ // Stack transformation: ..., v2, v1 -> ..., v2, v1, v2, v1
+ assert value2.isCategory1();
+ state.push(value2.type);
+ state.push(value1.type);
+ state.push(value2.type);
+ state.push(value1.type);
+ } else {
+ // Stack transformation: ..., w1 -> ..., w1, w1
+ state.push(value1.type);
+ state.push(value1.type);
+ }
+ break;
+ }
+ case Opcodes.DUP2_X1: {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert value2.isCategory1();
+ if (value1.isCategory1()) {
+ // Stack transformation: ..., v3, v2, v1 -> v2, v1, v3, v2, v1
+ Slot value3 = state.pop();
+ assert value3.isCategory1();
+ updateStateForDupTwoBelowOne(value3, value2, value1);
+ } else {
+ // Stack transformation: ..., v2, w1 -> ..., w1, v2, w1
+ updateStateForDupOneBelowOne(value2, value1);
+ }
+ break;
+ }
+ case Opcodes.DUP2_X2: {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ if (!value1.isCategory1() && !value2.isCategory1()) {
+ // State transformation: ..., w2, w1 -> w1, w2, w1
+ updateStateForDupOneBelowOne(value2, value1);
+ } else {
+ Slot value3 = state.pop();
+ if (!value1.isCategory1()) {
+ assert value2.isCategory1();
+ assert value3.isCategory1();
+ // State transformation: ..., v3, v2, w1 -> w1, v3, v2, w1
+ updateStateForDupOneBelowTwo(value3, value2, value1);
+ } else if (!value3.isCategory1()) {
+ assert value1.isCategory1();
+ assert value2.isCategory1();
+ // State transformation: ..., w3, v2, v1 -> v2, v1, w3, v2, v1
+ updateStateForDupTwoBelowOne(value3, value2, value1);
+ } else {
+ Slot value4 = state.pop();
+ assert value1.isCategory1();
+ assert value2.isCategory1();
+ assert value3.isCategory1();
+ assert value4.isCategory1();
+ // State transformation: ..., v4, v3, v2, v1 -> v2, v1, v4, v3, v2, v1
+ updateStateForDupTwoBelowTwo(value4, value3, value2, value1);
+ }
+ }
+ break;
+ }
+ case Opcodes.SWAP: {
+ Slot value1 = state.pop();
+ Slot value2 = state.pop();
+ assert value1.isCategory1() && value2.isCategory1();
+ state.push(value1.type);
+ state.push(value2.type);
+ break;
+ }
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM: {
+ Type type = opType(opcode);
+ state.pop();
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG: {
+ Type type = opType(opcode);
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR: {
+ Type type = opType(opcode);
+ state.pop();
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.IAND:
+ case Opcodes.LAND: {
+ Type type = opcode == Opcodes.IAND ? Type.INT_TYPE : Type.LONG_TYPE;
+ state.pop();
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.IOR:
+ case Opcodes.LOR: {
+ Type type = opcode == Opcodes.IOR ? Type.INT_TYPE : Type.LONG_TYPE;
+ state.pop();
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.IXOR:
+ case Opcodes.LXOR: {
+ Type type = opcode == Opcodes.IXOR ? Type.INT_TYPE : Type.LONG_TYPE;
+ state.pop();
+ state.pop();
+ state.push(type);
+ break;
+ }
+ case Opcodes.I2L:
+ updateStateForConversion(Type.INT_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.I2F:
+ updateStateForConversion(Type.INT_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.I2D:
+ updateStateForConversion(Type.INT_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.L2I:
+ updateStateForConversion(Type.LONG_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.L2F:
+ updateStateForConversion(Type.LONG_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.L2D:
+ updateStateForConversion(Type.LONG_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.F2I:
+ updateStateForConversion(Type.FLOAT_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.F2L:
+ updateStateForConversion(Type.FLOAT_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.F2D:
+ updateStateForConversion(Type.FLOAT_TYPE, Type.DOUBLE_TYPE);
+ break;
+ case Opcodes.D2I:
+ updateStateForConversion(Type.DOUBLE_TYPE, Type.INT_TYPE);
+ break;
+ case Opcodes.D2L:
+ updateStateForConversion(Type.DOUBLE_TYPE, Type.LONG_TYPE);
+ break;
+ case Opcodes.D2F:
+ updateStateForConversion(Type.DOUBLE_TYPE, Type.FLOAT_TYPE);
+ break;
+ case Opcodes.I2B:
+ updateStateForConversion(Type.INT_TYPE, Type.BYTE_TYPE);
+ break;
+ case Opcodes.I2C:
+ updateStateForConversion(Type.INT_TYPE, Type.CHAR_TYPE);
+ break;
+ case Opcodes.I2S:
+ updateStateForConversion(Type.INT_TYPE, Type.SHORT_TYPE);
+ break;
+ case Opcodes.LCMP: {
+ state.pop();
+ state.pop();
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG: {
+ state.pop();
+ state.pop();
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG: {
+ state.pop();
+ state.pop();
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ case Opcodes.IRETURN: {
+ state.pop();
+ break;
+ }
+ case Opcodes.LRETURN: {
+ state.pop();
+ break;
+ }
+ case Opcodes.FRETURN: {
+ state.pop();
+ break;
+ }
+ case Opcodes.DRETURN: {
+ state.pop();
+ break;
+ }
+ case Opcodes.ARETURN: {
+ state.pop(JarState.REFERENCE_TYPE);
+ break;
+ }
+ case Opcodes.RETURN: {
+ break;
+ }
+ case Opcodes.ARRAYLENGTH: {
+ state.pop(JarState.ARRAY_TYPE);
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ case Opcodes.ATHROW: {
+ state.pop(JarState.OBJECT_TYPE);
+ break;
+ }
+ case Opcodes.MONITORENTER: {
+ state.pop(JarState.REFERENCE_TYPE);
+ break;
+ }
+ case Opcodes.MONITOREXIT: {
+ state.pop(JarState.REFERENCE_TYPE);
+ break;
+ }
+ default:
+ throw new Unreachable("Unexpected Insn opcode: " + insn.getOpcode());
+ }
+ }
+
+ private void updateStateForDupOneBelowTwo(Slot value3, Slot value2, Slot value1) {
+ state.push(value1.type);
+ state.push(value3.type);
+ state.push(value2.type);
+ state.push(value1.type);
+ }
+
+ private void updateStateForDupOneBelowOne(Slot value2, Slot value1) {
+ state.push(value1.type);
+ state.push(value2.type);
+ state.push(value1.type);
+ }
+
+ private void updateStateForDupTwoBelowOne(Slot value3, Slot value2, Slot value1) {
+ state.push(value2.type);
+ state.push(value1.type);
+ state.push(value3.type);
+ state.push(value2.type);
+ state.push(value1.type);
+ }
+
+ private void updateStateForDupTwoBelowTwo(Slot value4, Slot value3, Slot value2, Slot value1) {
+ state.push(value2.type);
+ state.push(value1.type);
+ state.push(value4.type);
+ state.push(value3.type);
+ state.push(value2.type);
+ state.push(value1.type);
+ }
+
+ private void updateState(IntInsnNode insn) {
+ switch (insn.getOpcode()) {
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH: {
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ case Opcodes.NEWARRAY: {
+ String desc = arrayTypeDesc(insn.operand);
+ Type type = Type.getType(desc);
+ state.pop();
+ state.push(type);
+ break;
+ }
+ default:
+ throw new Unreachable("Unexpected IntInsn opcode: " + insn.getOpcode());
+ }
+ }
+
+ private void updateState(VarInsnNode insn) {
+ int opcode = insn.getOpcode();
+ Type expectedType;
+ switch (opcode) {
+ case Opcodes.ILOAD:
+ case Opcodes.ISTORE:
+ expectedType = Type.INT_TYPE;
+ break;
+ case Opcodes.FLOAD:
+ case Opcodes.FSTORE:
+ expectedType = Type.FLOAT_TYPE;
+ break;
+ case Opcodes.LLOAD:
+ case Opcodes.LSTORE:
+ expectedType = Type.LONG_TYPE;
+ break;
+ case Opcodes.DLOAD:
+ case Opcodes.DSTORE:
+ expectedType = Type.DOUBLE_TYPE;
+ break;
+ case Opcodes.ALOAD:
+ case Opcodes.ASTORE:
+ expectedType = JarState.REFERENCE_TYPE;
+ break;
+ case Opcodes.RET: {
+ throw new Unreachable("RET should be handled by the ASM jsr inliner");
+ }
+ default:
+ throw new Unreachable("Unexpected VarInsn opcode: " + insn.getOpcode());
+ }
+ if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
+ Slot src = state.readLocal(insn.var, expectedType);
+ state.push(src.type);
+ } else {
+ assert Opcodes.ISTORE <= opcode && opcode <= Opcodes.ASTORE;
+ Slot slot = state.pop();
+ if (slot.type == JarState.NULL_TYPE && expectedType != JarState.REFERENCE_TYPE) {
+ state.writeLocal(insn.var, expectedType);
+ } else {
+ state.writeLocal(insn.var, slot.type);
+ }
+ }
+ }
+
+ private void updateState(TypeInsnNode insn) {
+ Type type = Type.getObjectType(insn.desc);
+ switch (insn.getOpcode()) {
+ case Opcodes.NEW: {
+ state.push(type);
+ break;
+ }
+ case Opcodes.ANEWARRAY: {
+ Type arrayType = makeArrayType(type);
+ state.pop();
+ state.push(arrayType);
+ break;
+ }
+ case Opcodes.CHECKCAST: {
+ // Pop the top value and push it back on with the checked type.
+ state.pop(type);
+ state.push(type);
+ break;
+ }
+ case Opcodes.INSTANCEOF: {
+ state.pop(JarState.REFERENCE_TYPE);
+ state.push(Type.INT_TYPE);
+ break;
+ }
+ default:
+ throw new Unreachable("Unexpected TypeInsn opcode: " + insn.getOpcode());
+ }
+
+ }
+
+ private void updateState(FieldInsnNode insn) {
+ Type type = Type.getType(insn.desc);
+ switch (insn.getOpcode()) {
+ case Opcodes.GETSTATIC:
+ state.push(type);
+ break;
+ case Opcodes.PUTSTATIC:
+ state.pop();
+ break;
+ case Opcodes.GETFIELD: {
+ state.pop(JarState.OBJECT_TYPE);
+ state.push(type);
+ break;
+ }
+ case Opcodes.PUTFIELD: {
+ state.pop();
+ state.pop(JarState.OBJECT_TYPE);
+ break;
+ }
+ default:
+ throw new Unreachable("Unexpected FieldInsn opcode: " + insn.getOpcode());
+ }
+ }
+
+ private void updateState(MethodInsnNode insn) {
+ updateStateForInvoke(insn.desc, insn.getOpcode() != Opcodes.INVOKESTATIC);
+ }
+
+ private void updateState(InvokeDynamicInsnNode insn) {
+ updateStateForInvoke(insn.desc, false /* receiver passed explicitly */);
+ }
+
+ private void updateStateForInvoke(String desc, boolean implicitReceiver) {
+ // Pop arguments.
+ Type[] parameterTypes = Type.getArgumentTypes(desc);
+ state.popReverse(parameterTypes.length);
+ // Pop implicit receiver if needed.
+ if (implicitReceiver) {
+ state.pop();
+ }
+ // Push return value if needed.
+ Type returnType = Type.getReturnType(desc);
+ if (returnType != Type.VOID_TYPE) {
+ state.push(returnType);
+ }
+ }
+
+ private void updateState(JumpInsnNode insn) {
+ int[] targets = getTargets(insn);
+ int opcode = insn.getOpcode();
+ if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
+ assert targets.length == 2;
+ if (opcode <= Opcodes.IFLE) {
+ state.pop();
+ } else {
+ state.pop();
+ state.pop();
+ }
+ } else {
+ switch (opcode) {
+ case Opcodes.GOTO: {
+ assert targets.length == 1;
+ break;
+ }
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL: {
+ state.pop();
+ break;
+ }
+ case Opcodes.JSR: {
+ throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+ }
+ default:
+ throw new Unreachable("Unexpected JumpInsn opcode: " + insn.getOpcode());
+ }
+ }
+ }
+
+ private void updateState(LabelNode insn) {
+ // Open the scope of locals starting at this point.
+ if (insn != initialLabel) {
+ state.openLocals(insn);
+ }
+ }
+
+ private void updateState(LdcInsnNode insn) {
+ if (insn.cst instanceof Type) {
+ Type type = (Type) insn.cst;
+ state.push(type);
+ } else if (insn.cst instanceof String) {
+ state.push(STRING_TYPE);
+ } else if (insn.cst instanceof Long) {
+ state.push(Type.LONG_TYPE);
+ } else if (insn.cst instanceof Double) {
+ state.push(Type.DOUBLE_TYPE);
+ } else if (insn.cst instanceof Integer) {
+ state.push(Type.INT_TYPE);
+ } else {
+ assert insn.cst instanceof Float;
+ state.push(Type.FLOAT_TYPE);
+ }
+ }
+
+ private void updateState(IincInsnNode insn) {
+ state.readLocal(insn.var, Type.INT_TYPE);
+ }
+
+ private void updateState(TableSwitchInsnNode insn) {
+ state.pop();
+ }
+
+ private void updateState(LookupSwitchInsnNode insn) {
+ state.pop();
+ }
+
+ private void updateState(MultiANewArrayInsnNode insn) {
+ // Type of the full array.
+ Type arrayType = Type.getObjectType(insn.desc);
+ state.popReverse(insn.dims, Type.INT_TYPE);
+ state.push(arrayType);
+ }
+
+ private void updateState(LineNumberNode insn) {
+ // Intentionally empty.
+ }
+
+ private void updateStateForConversion(Type from, Type to) {
+ state.pop();
+ state.push(to);
+ }
+
// IR instruction building procedures.
private void build(AbstractInsnNode insn, IRBuilder builder) {
@@ -1509,7 +2250,6 @@
processLocalVariablesAtControlEdge(insn, builder);
}
builder.addThrow(register);
- state.invalidateState();
}
private void addReturn(InsnNode insn, MoveType type, int register, IRBuilder builder) {
@@ -1520,7 +2260,6 @@
} else {
builder.addReturn(type, register);
}
- state.invalidateState();
}
private void dupOneBelowTwo(Slot value3, Slot value2, Slot value1, IRBuilder builder) {
@@ -1913,10 +2652,6 @@
throw new Unreachable("Unexpected JumpInsn opcode: " + insn.getOpcode());
}
}
- for (int target : targets) {
- state.recordStateForTarget(target, this);
- }
- state.invalidateState();
}
private void build(LabelNode insn, IRBuilder builder) {
@@ -1926,11 +2661,6 @@
builder.addDebugLocalStart(local.slot.register, local.info);
}
}
- // Processing local-variable ends is done before closing potential control-flow edges.
- // Record the state for all the try-catch handlers that are active at this label.
- for (TryCatchBlock tryCatchBlock : getTryHandlers(insn)) {
- state.recordStateForExceptionalTarget(tryCatchBlock.getHandler(), this);
- }
}
private void build(LdcInsnNode insn, IRBuilder builder) {
@@ -1968,6 +2698,7 @@
}
private void build(LookupSwitchInsnNode insn, IRBuilder builder) {
+ processLocalVariablesAtControlEdge(insn, builder);
int[] keys = new int[insn.keys.size()];
for (int i = 0; i < insn.keys.size(); i++) {
keys[i] = (int) insn.keys.get(i);
@@ -1979,15 +2710,12 @@
IRBuilder builder) {
int index = state.pop(Type.INT_TYPE).register;
int fallthroughOffset = getOffset(dflt);
- state.recordStateForTarget(fallthroughOffset, this);
int[] labelOffsets = new int[labels.size()];
for (int i = 0; i < labels.size(); i++) {
int offset = getOffset((LabelNode) labels.get(i));
labelOffsets[i] = offset;
- state.recordStateForTarget(offset, this);
}
builder.addSwitch(type, index, keys, fallthroughOffset, labelOffsets);
- state.invalidateState();
}
private void build(MultiANewArrayInsnNode insn, IRBuilder builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index e45fcb2..9e59c48 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -40,13 +40,6 @@
// Type representative for an address type (used by JSR/RET).
public static final Type ADDR_TYPE = Type.getObjectType("<address>");
- private static final Slot INVALID_SLOT = new Slot(-1, null);
- private static final Local INVALID_LOCAL = new Local(INVALID_SLOT, null);
-
- public boolean isInvalid() {
- return stack.peek() == INVALID_SLOT;
- }
-
// Typed mapping from a local slot or stack slot to a virtual register.
public static class Slot {
public final int register;
@@ -54,7 +47,7 @@
@Override
public String toString() {
- return this == INVALID_SLOT ? "<invalid slot>" : "r" + register + ":" + type;
+ return "r" + register + ":" + type;
}
public Slot(int register, Type type) {
@@ -356,15 +349,6 @@
// State procedures.
- public void invalidateState() {
- for (int i = 0; i < locals.length; i++) {
- locals[i] = INVALID_LOCAL;
- }
- stack.clear();
- stack.push(INVALID_SLOT);
- topOfStack = -1;
- }
-
public boolean hasState(int offset) {
return targetStates.get(offset) != null;
}
@@ -374,49 +358,100 @@
assert snapshot != null;
assert locals.length == snapshot.locals.length;
for (int i = 0; i < locals.length; i++) {
- assert locals[i] == INVALID_LOCAL;
locals[i] = snapshot.locals[i];
}
- assert stack.peek() == INVALID_SLOT;
- assert topOfStack == -1;
stack.clear();
stack.addAll(snapshot.stack);
topOfStack = startOfStack + 2 * stack.size();
}
- public void recordStateForTarget(int target, JarSourceCode source) {
- recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack), source);
+ public boolean recordStateForTarget(int target, JarSourceCode source) {
+ return recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack), source);
}
- public void recordStateForExceptionalTarget(int target, JarSourceCode source) {
- recordStateForTarget(target, locals.clone(), ImmutableList.of(), source);
+ public boolean recordStateForExceptionalTarget(int target, JarSourceCode source) {
+ return recordStateForTarget(target, locals.clone(), ImmutableList.of(), source);
}
- private void recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack,
+ private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack,
JarSourceCode source) {
- Snapshot snapshot = targetStates.get(target);
- if (snapshot == null) {
- if (!localVariables.isEmpty()) {
- for (int i = 0; i < locals.length; i++) {
- if (locals[i] != null) {
- locals[i] = new Local(locals[i].slot, null);
- }
- }
- // TODO(zerny): Precompute and sort the local ranges.
- for (LocalVariableNode node : localVariables.keySet()) {
- int startOffset = source.getOffset(node.start);
- int endOffset = source.getOffset(node.end);
- if (startOffset <= target && target < endOffset) {
- int register = getLocalRegister(node.index, Type.getType(node.desc));
- Local local = locals[register];
- locals[register] = new Local(local.slot, localVariables.get(node));
- }
+ if (!localVariables.isEmpty()) {
+ for (int i = 0; i < locals.length; i++) {
+ if (locals[i] != null) {
+ locals[i] = new Local(locals[i].slot, null);
}
}
- targetStates.put(target, new Snapshot(locals, stack));
- return;
+ // TODO(zerny): Precompute and sort the local ranges.
+ for (LocalVariableNode node : localVariables.keySet()) {
+ int startOffset = source.getOffset(node.start);
+ int endOffset = source.getOffset(node.end);
+ if (startOffset <= target && target < endOffset) {
+ int register = getLocalRegister(node.index, Type.getType(node.desc));
+ Local local = locals[register];
+ locals[register] = new Local(local.slot, localVariables.get(node));
+ }
+ }
}
- assert verifyStack(stack, snapshot.stack);
+ Snapshot snapshot = targetStates.get(target);
+ if (snapshot != null) {
+ Local[] newLocals = mergeLocals(snapshot.locals, locals);
+ ImmutableList<Slot> newStack = mergeStacks(snapshot.stack, stack);
+ if (newLocals != snapshot.locals || newStack != snapshot.stack) {
+ targetStates.put(target, new Snapshot(newLocals, newStack));
+ return true;
+ }
+ // The snapshot is up to date - no new type information recoded.
+ return false;
+ }
+ targetStates.put(target, new Snapshot(locals, stack));
+ return true;
+ }
+
+ private ImmutableList<Slot> mergeStacks(
+ ImmutableList<Slot> currentStack, ImmutableList<Slot> newStack) {
+ assert currentStack.size() == newStack.size();
+ List<Slot> mergedStack = null;
+ for (int i = 0; i < currentStack.size(); i++) {
+ assert currentStack.get(i).isCompatibleWith(newStack.get(i).type);
+ if (currentStack.get(i).type == JarState.NULL_TYPE &&
+ newStack.get(i).type != JarState.NULL_TYPE) {
+ if (mergedStack == null) {
+ mergedStack = new ArrayList<>();
+ mergedStack.addAll(currentStack.subList(0, i));
+ }
+ mergedStack.add(newStack.get(i));
+ } else if (mergedStack != null) {
+ mergedStack.add(currentStack.get(i));
+ }
+ }
+ return mergedStack != null ? ImmutableList.copyOf(mergedStack) : currentStack;
+ }
+
+ private Local[] mergeLocals(Local[] currentLocals, Local[] newLocals) {
+ assert currentLocals.length == newLocals.length;
+ Local[] mergedLocals = null;
+ for (int i = 0; i < currentLocals.length; i++) {
+ Local currentLocal = currentLocals[i];
+ Local newLocal = newLocals[i];
+ if (currentLocal == null || newLocal == null) {
+ continue;
+ }
+ // If this assert triggers we can get different debug information for the same local
+ // on different control-flow paths and we will have to merge them.
+ assert currentLocal.info == newLocal.info;
+ if (currentLocal.slot.type == JarState.NULL_TYPE &&
+ newLocal.slot.type != JarState.NULL_TYPE) {
+ if (mergedLocals == null) {
+ mergedLocals = new Local[currentLocals.length];
+ System.arraycopy(currentLocals, 0, mergedLocals, 0, i);
+ }
+ Slot newSlot = new Slot(newLocal.slot.register, newLocal.slot.type);
+ mergedLocals[i] = new Local(newSlot, newLocal.info);
+ } else if (mergedLocals != null) {
+ mergedLocals[i] = currentLocals[i];
+ }
+ }
+ return mergedLocals != null ? mergedLocals : currentLocals;
}
private static boolean verifyStack(List<Slot> stack, List<Slot> other) {
@@ -446,9 +481,6 @@
public static String stackToString(Collection<Slot> stack) {
List<String> strings = new ArrayList<>(stack.size());
for (Slot slot : stack) {
- if (slot == INVALID_SLOT) {
- return "<stack invalidated>";
- }
strings.add(slot.type.toString());
}
StringBuilder builder = new StringBuilder("{ ");
@@ -466,9 +498,6 @@
StringBuilder builder = new StringBuilder("{ ");
boolean first = true;
for (Local local : locals) {
- if (local == INVALID_LOCAL) {
- return "<locals invalidated>";
- }
if (!first) {
builder.append(", ");
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index ba03f82..a36091d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -33,9 +33,10 @@
* <p>The instruction at {@code index} is traced and its target blocks are marked by using
* {@code IRBuilder.ensureSuccessorBlock} (and {@code ensureBlockWithoutEnqueuing}).
*
- * @return True if the instruction closes the current block, false otherwise.
+ * @return If the instruction closes the block, the last index of the block,
+ * otherwise -1.
*/
- boolean traceInstruction(int instructionIndex, IRBuilder builder);
+ int traceInstruction(int instructionIndex, IRBuilder builder);
void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex);
void closedCurrentBlock();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 42b5220..4ab72f6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -812,9 +812,9 @@
}
@Override
- public boolean traceInstruction(int instructionIndex, IRBuilder builder) {
+ public int traceInstruction(int instructionIndex, IRBuilder builder) {
// There is just one block, and after the last instruction it is closed.
- return instructionIndex == outline.templateInstructions.size();
+ return instructionIndex == outline.templateInstructions.size() ? instructionIndex : -1;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
index 8c97f8f..22cf6d5 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
@@ -124,8 +124,8 @@
}
@Override
- public final boolean traceInstruction(int instructionIndex, IRBuilder builder) {
- return instructionIndex == constructors.size() - 1;
+ public final int traceInstruction(int instructionIndex, IRBuilder builder) {
+ return (instructionIndex == constructors.size() - 1) ? instructionIndex : -1;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
index 2132b4e..548d9d0 100644
--- a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
+++ b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
@@ -6,7 +6,6 @@
import static junit.framework.TestCase.assertEquals;
import com.google.common.collect.ImmutableList;
-import org.junit.Ignore;
import org.junit.Test;
public class BooleanByteConfusion extends JasminTestBase {
@@ -21,7 +20,6 @@
}
@Test
- @Ignore("b/62438166")
public void booleanByteConfusion() throws Exception {
JasminBuilder builder = new JasminBuilder();
JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index d05c800..f540bc0 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -11,6 +11,8 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationWriter;
@@ -39,6 +41,7 @@
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
@@ -52,12 +55,14 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -207,6 +212,113 @@
MainDexList.parse(mainDexList, factory);
}
+ @Test
+ public void checkDeterminism() throws Exception {
+ // Synthesize a dex containing a few empty classes including some in the default package.
+ // Everything can fit easaly in a single dex file.
+ String[] classes = {
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "A1",
+ "A2",
+ "A3",
+ "A4",
+ "A5",
+ "maindexlist/A",
+ "maindexlist/B",
+ "maindexlist/C",
+ "maindexlist/D",
+ "maindexlist/E",
+ "maindexlist/F",
+ "maindexlist/A1",
+ "maindexlist/A2",
+ "maindexlist/A3",
+ "maindexlist/A4",
+ "maindexlist/A5"
+ };
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ for (String name : classes) {
+ jasminBuilder.addClass(name);
+ }
+ Path input = temp.newFolder().toPath().resolve("input.zip");
+ ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.Indexed);
+
+ // Prepare different main dex lists.
+ ArrayList<Path> mainLists = new ArrayList<>();
+ // Lets first without a main dex list.
+ mainLists.add(null);
+
+ // List with all classes.
+ List<String> mainList = new ArrayList<>();
+ for (int i = 0; i < classes.length; i++) {
+ mainList.add(classes[i] + ".class");
+ }
+ addMainListFile(mainLists, mainList);
+
+ // Full list in reverse order
+ addMainListFile(mainLists, Lists.reverse(mainList));
+
+ // Partial list without first entries (those in default package).
+ mainList.clear();
+ for (int i = classes.length / 2; i < classes.length; i++) {
+ mainList.add(classes[i] + ".class");
+ }
+ addMainListFile(mainLists, mainList);
+
+ // Same in reverese order
+ addMainListFile(mainLists, Lists.reverse(mainList));
+
+ // Mixed partial list.
+ mainList.clear();
+ for (int i = 0; i < classes.length; i += 2) {
+ mainList.add(classes[i] + ".class");
+ }
+ addMainListFile(mainLists, mainList);
+
+ // Another different mixed partial list.
+ mainList.clear();
+ for (int i = 1; i < classes.length; i += 2) {
+ mainList.add(classes[i] + ".class");
+ }
+ addMainListFile(mainLists, mainList);
+
+ // Build with all main dex lists.
+ Path tmp = temp.getRoot().toPath();
+ for (int i = 0; i < mainLists.size(); i++) {
+ Path out = tmp.resolve(String.valueOf(i));
+ Files.createDirectories(out);
+ D8Command.Builder builder = D8Command.builder()
+ .addProgramFiles(input)
+ .setOutputPath(out);
+ if (mainLists.get(i) != null) {
+ builder.setMainDexListFile(mainLists.get(i));
+ }
+ ToolHelper.runD8(builder.build());
+ }
+
+ // Check: no secondary dex and resulting dex is always the same.
+ assertFalse(Files.exists(tmp.resolve(String.valueOf(0)).resolve("classes2.dex")));
+ byte[] ref = Files.readAllBytes(
+ tmp.resolve(String.valueOf(0)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
+ for (int i = 1; i < mainLists.size(); i++) {
+ assertFalse(Files.exists(tmp.resolve(String.valueOf(i)).resolve("classes2.dex")));
+ byte[] checked = Files.readAllBytes(
+ tmp.resolve(String.valueOf(i)).resolve(FileUtils.DEFAULT_DEX_FILENAME));
+ assertArrayEquals(ref, checked);
+ }
+ }
+
+ private void addMainListFile(ArrayList<Path> mainLists, List<String> content)
+ throws IOException {
+ Path listFile = temp.newFile().toPath();
+ FileUtils.writeTextFile(listFile, content);
+ mainLists.add(listFile);
+ }
+
private static String typeToEntry(String type) {
return type.replace(".", "/") + CLASS_EXTENSION;
}
@@ -265,7 +377,7 @@
for (String clazz : classes) {
DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
DexType type = factory.createType(desc);
- DexEncodedMethod[] virtualMethods = new DexEncodedMethod[methodCount];
+ DexEncodedMethod[] directMethods = new DexEncodedMethod[methodCount];
for (int i = 0; i < methodCount; i++) {
DexAccessFlags access = new DexAccessFlags();
access.setPublic();
@@ -285,7 +397,7 @@
IRCode ir = code.buildIR(method, options);
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir);
method.setCode(ir, allocator, factory);
- virtualMethods[i] = method;
+ directMethods[i] = method;
}
builder.addProgramClass(
new DexProgramClass(
@@ -298,8 +410,8 @@
DexAnnotationSet.empty(),
DexEncodedField.EMPTY_ARRAY,
DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- virtualMethods));
+ directMethods,
+ DexEncodedMethod.EMPTY_ARRAY));
}
DexApplication application = builder.build();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
@@ -342,8 +454,8 @@
}
@Override
- public boolean traceInstruction(int instructionIndex, IRBuilder builder) {
- return true;
+ public int traceInstruction(int instructionIndex, IRBuilder builder) {
+ return instructionIndex;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/maindexlist/many-classes.zip b/src/test/java/com/android/tools/r8/maindexlist/many-classes.zip
index 3c1530d..fd4ecdc 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/many-classes.zip
+++ b/src/test/java/com/android/tools/r8/maindexlist/many-classes.zip
Binary files differ
diff --git a/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip b/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
index 97b5fde..9ad745f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
+++ b/src/test/java/com/android/tools/r8/maindexlist/two-large-classes.zip
Binary files differ