Merge "Add Vungle to run_on_as_app"
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 8c79ea1..c5cb84e 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -26,6 +26,7 @@
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
public class LoadStoreHelper {
@@ -35,7 +36,7 @@
private final AppInfo appInfo;
private Map<Value, ConstInstruction> clonableConstants = null;
- private BasicBlock block = null;
+ private ListIterator<BasicBlock> blockIterator = null;
public LoadStoreHelper(IRCode code, TypeVerificationHelper typesHelper, AppInfo appInfo) {
this.code = code;
@@ -96,16 +97,16 @@
public void insertLoadsAndStores() {
clonableConstants = new IdentityHashMap<>();
- for (BasicBlock block : code.blocks) {
- this.block = block;
- InstructionListIterator it = block.listIterator();
+ blockIterator = code.blocks.listIterator();
+ while (blockIterator.hasNext()) {
+ InstructionListIterator it = blockIterator.next().listIterator();
while (it.hasNext()) {
it.next().insertLoadAndStores(it, this);
}
clonableConstants.clear();
}
clonableConstants = null;
- block = null;
+ blockIterator = null;
}
public void insertPhiMoves(CfRegisterAllocator allocator) {
@@ -170,8 +171,7 @@
assert !(instruction.outValue() instanceof StackValue);
if (instruction.isConstInstruction()) {
ConstInstruction constInstruction = instruction.asConstInstruction();
- assert block != null;
- if (canRemoveConstInstruction(constInstruction, block)) {
+ if (canRemoveConstInstruction(constInstruction, instruction.getBlock())) {
assert !constInstruction.isDexItemBasedConstString()
|| constInstruction.outValue().numberOfUsers() == 1;
clonableConstants.put(instruction.outValue(), constInstruction);
@@ -180,7 +180,8 @@
return;
}
assert instruction.outValue().isUsed(); // Should have removed it above.
- } else if (!instruction.outValue().isUsed()) {
+ }
+ if (!instruction.outValue().isUsed()) {
popOutValue(instruction.outValue(), instruction, it);
return;
}
@@ -189,19 +190,40 @@
Store store = new Store(oldOutValue, newOutValue);
// Move the debugging-locals liveness pertaining to the instruction to the store.
instruction.moveDebugValues(store);
- add(store, instruction, it);
- }
-
- public void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
- StackValue newOutValue = createStackValue(value, 0);
- instruction.swapOutValue(newOutValue);
- add(new Pop(newOutValue), instruction, it);
+ BasicBlock storeBlock = instruction.getBlock();
+ // If the block has catch-handlers we are not allowed to modify the locals of the block. If the
+ // instruction is throwing, the action should be moved to a new block - otherwise, the store
+ // should be inserted and the remaining instructions should be moved along with the handlers to
+ // the new block.
+ boolean hasCatchHandlers = instruction.getBlock().hasCatchHandlers();
+ if (hasCatchHandlers && instruction.instructionTypeCanThrow()) {
+ storeBlock = it.split(this.code, this.blockIterator);
+ it = storeBlock.listIterator();
+ }
+ add(store, storeBlock, instruction.getPosition(), it);
+ if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) {
+ it.split(this.code, this.blockIterator);
+ this.blockIterator.previous();
+ }
}
public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
- StackValue newOutValue = createStackValue(type, 0);
+ popOutValue(createStackValue(type, 0), instruction, it);
+ }
+
+ private void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
+ popOutValue(createStackValue(value, 0), instruction, it);
+ }
+
+ private void popOutValue(
+ StackValue newOutValue, Instruction instruction, InstructionListIterator it) {
+ BasicBlock insertBlock = instruction.getBlock();
+ if (insertBlock.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
+ insertBlock = it.split(this.code, this.blockIterator);
+ it = insertBlock.listIterator();
+ }
instruction.swapOutValue(newOutValue);
- add(new Pop(newOutValue), instruction, it);
+ add(new Pop(newOutValue), insertBlock, instruction.getPosition(), it);
}
private static class PhiMove {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 72c6903..6b9257b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -173,6 +173,8 @@
public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
public final DexString throwableDescriptor = createString("Ljava/lang/Throwable;");
+ public final DexString exceptionInInitializerErrorDescriptor =
+ createString("Ljava/lang/ExceptionInInitializerError;");
public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
@@ -228,6 +230,8 @@
public final DexType enumType = createType(enumDescriptor);
public final DexType annotationType = createType(annotationDescriptor);
public final DexType throwableType = createType(throwableDescriptor);
+ public final DexType exceptionInInitializerErrorType =
+ createType(exceptionInInitializerErrorDescriptor);
public final DexType classType = createType(classDescriptor);
public final DexType autoCloseableType = createType(autoCloseableDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
new file mode 100644
index 0000000..a0fbb59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -0,0 +1,232 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.ir.code.DominatorTree.Inclusive;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.Streams;
+import java.util.Iterator;
+
+/**
+ * Analysis that given an instruction determines if a given type is guaranteed to be class
+ * initialized prior the given instruction.
+ */
+public class ClassInitializationAnalysis {
+
+ public enum AnalysisAssumption {
+ INSTRUCTION_DOES_NOT_THROW,
+ NONE
+ }
+
+ public enum Query {
+ DIRECTLY,
+ DIRECTLY_OR_INDIRECTLY
+ }
+
+ private static final ClassInitializationAnalysis TRIVIAL =
+ new ClassInitializationAnalysis() {
+
+ @Override
+ public boolean isClassDefinitelyLoadedBeforeInstruction(
+ DexType type, Instruction instruction) {
+ return false;
+ }
+ };
+
+ private final AppView<? extends AppInfoWithLiveness> appView;
+ private final IRCode code;
+ private final DexItemFactory dexItemFactory;
+
+ private DominatorTree dominatorTree = null;
+ private int markingColor = -1;
+
+ private ClassInitializationAnalysis() {
+ this.appView = null;
+ this.code = null;
+ this.dexItemFactory = null;
+ }
+
+ public ClassInitializationAnalysis(AppView<? extends AppInfoWithLiveness> appView, IRCode code) {
+ assert appView != null;
+ assert code != null;
+ this.appView = appView;
+ this.code = code;
+ this.dexItemFactory = appView.dexItemFactory();
+ }
+
+ // Returns a trivial, conservative analysis that always returns false.
+ public static ClassInitializationAnalysis trivial() {
+ return TRIVIAL;
+ }
+
+ public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
+ BasicBlock block = instruction.getBlock();
+
+ // Visit the instructions in `block` prior to `instruction`.
+ for (Instruction previous : block.getInstructions()) {
+ if (previous == instruction) {
+ break;
+ }
+ if (previous.definitelyTriggersClassInitialization(
+ type,
+ appView,
+ Query.DIRECTLY_OR_INDIRECTLY,
+ // The given instruction is only reached if none of the instructions in the same
+ // basic block throws, so we can safely assume that they will not.
+ AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
+ return true;
+ }
+ }
+
+ if (dominatorTree == null) {
+ dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
+ }
+
+ // Visit all the instructions in all the blocks that dominate `block`.
+ for (BasicBlock dominator : dominatorTree.dominatorBlocks(block, Inclusive.NO)) {
+ AnalysisAssumption assumption = getAssumptionForDominator(dominator, block);
+ Iterator<Instruction> instructionIterator = dominator.iterator();
+ while (instructionIterator.hasNext()) {
+ Instruction previous = instructionIterator.next();
+ if (previous.definitelyTriggersClassInitialization(
+ type, appView, Query.DIRECTLY_OR_INDIRECTLY, assumption)) {
+ return true;
+ }
+ if (dominator.hasCatchHandlers() && previous.instructionTypeCanThrow()) {
+ // All of the instructions that follow the first instruction that may throw are
+ // guaranteed to be non-throwing. Hence they cannot cause any class initializations.
+ assert Streams.stream(instructionIterator)
+ .noneMatch(Instruction::instructionTypeCanThrow);
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the analysis assumption to use when analyzing the instructions in the given dominator
+ * block.
+ *
+ * <p>If the given block has no catch handlers, then we can safely assume that the instruction
+ * does not throw, because execution would otherwise exit the method.
+ *
+ * <p>As a simple example, consider the method below. In order for the execution to get from the
+ * call to A.foo() to the call to A.bar() the call A.foo() must not throw.
+ *
+ * <pre>
+ * public static void method() {
+ * A.foo();
+ * A.bar();
+ * }
+ * </pre>
+ *
+ * This assumption cannot be made in the presence of intraprocedural exceptional control flow.
+ * Consider the following example.
+ *
+ * <pre>
+ * public static void method(A instance) {
+ * try {
+ * instance.field = 42;
+ * } catch (Exception e) {
+ * A.foo();
+ * return;
+ * }
+ * A.bar();
+ * }
+ * </pre>
+ *
+ * <p>At the call to A.foo() it is not guaranteed that the class A has been initialized, since
+ * `instance` could always be null.
+ *
+ * <p>At the call to A.bar() it is guaranteed, since the instance field assignment succeeded.
+ */
+ private AnalysisAssumption getAssumptionForDominator(BasicBlock dominator, BasicBlock block) {
+ if (!dominator.hasCatchHandlers()) {
+ return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+ }
+
+ Instruction exceptionalExit = dominator.exceptionalExit();
+ if (exceptionalExit == null) {
+ // The block cannot throw after all.
+ return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+ }
+
+ if (markingColor < 0) {
+ markingColor = code.reserveMarkingColor();
+ code.markTransitivePredecessors(block, markingColor);
+ }
+
+ for (CatchHandler<BasicBlock> catchHandler : dominator.getCatchHandlers()) {
+ if (!catchHandler.target.isMarked(markingColor)) {
+ // There is no path from this catch handler to the instruction of interest, so we can
+ // ignore it.
+ continue;
+ }
+
+ DexType guard = catchHandler.guard;
+ if (guard == DexItemFactory.catchAllType) {
+ return AnalysisAssumption.NONE;
+ }
+
+ if (exceptionalExit.isInstanceGet()
+ || exceptionalExit.isInstancePut()
+ || exceptionalExit.isInvokeMethodWithReceiver()) {
+ // If an instance-get, instance-put, or instance-invoke instruction does not fail with a
+ // NullPointerException, then the receiver class must have been initialized.
+ if (!dexItemFactory.npeType.isSubtypeOf(guard, appView.appInfo())) {
+ continue;
+ }
+ }
+ if (exceptionalExit.isStaticGet()
+ || exceptionalExit.isStaticPut()
+ || exceptionalExit.isInvokeStatic()) {
+ // If a static-get, static-put, or invoke-static does not fail with an ExceptionIn-
+ // InitializerError, then the holder class must have been initialized.
+ if (!dexItemFactory.exceptionInInitializerErrorType.isSubtypeOf(guard, appView.appInfo())) {
+ continue;
+ }
+ }
+
+ return AnalysisAssumption.NONE;
+ }
+
+ // There are no paths from any of the catch handlers to the instruction of interest, so we can
+ // assume that no instructions in the given block will throw (otherwise, the instruction of
+ // interest will not be reached).
+ return AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW;
+ }
+
+ /**
+ * The analysis reuses the dominator tree and basic block markings. If the underlying structure of
+ * the IR changes, then this method must be called to reset the dominator tree, return the current
+ * marking color, and clear all marks.
+ */
+ public void notifyCodeHasChanged() {
+ dominatorTree = null;
+ returnMarkingColor();
+ }
+
+ /** Returns the marking color, if any, and clears all marks. */
+ public void finish() {
+ returnMarkingColor();
+ }
+
+ private void returnMarkingColor() {
+ if (markingColor >= 0) {
+ code.returnMarkingColor(markingColor);
+ markingColor = -1;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 3f6a2ef..34926fa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -5,14 +5,16 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
-public class CatchHandlers<T> {
+public class CatchHandlers<T> implements Iterable<CatchHandler<T>> {
public static class CatchHandler<T> {
@@ -94,6 +96,27 @@
}
@Override
+ public Iterator<CatchHandler<T>> iterator() {
+ return new Iterator<CatchHandler<T>>() {
+
+ private int nextIndex = 0;
+
+ @Override
+ public boolean hasNext() {
+ return nextIndex < size();
+ }
+
+ @Override
+ public CatchHandler<T> next() {
+ DexType guard = guards.get(nextIndex);
+ T target = targets.get(nextIndex);
+ ++nextIndex;
+ return new CatchHandler<>(guard, target);
+ }
+ };
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index b8192be..960ea92 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -20,6 +20,11 @@
MAY_HAVE_UNREACHABLE_BLOCKS
}
+ public enum Inclusive {
+ YES,
+ NO
+ }
+
private final BasicBlock[] sorted;
private BasicBlock[] doms;
private final BasicBlock normalExitBlock = new BasicBlock();
@@ -165,30 +170,42 @@
* iteration starts by returning <code>dominated</code>.
*/
public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+ return dominatorBlocks(dominated, Inclusive.YES);
+ }
+
+ public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated, Inclusive inclusive) {
assert !obsolete;
- return () -> new Iterator<BasicBlock>() {
- private BasicBlock current = dominated;
+ return () -> {
+ Iterator<BasicBlock> iterator =
+ new Iterator<BasicBlock>() {
+ private BasicBlock current = dominated;
- @Override
- public boolean hasNext() {
- return current != null;
- }
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
- @Override
- public BasicBlock next() {
- if (!hasNext()) {
- return null;
- } else {
- BasicBlock result = current;
- if (current.getNumber() == 0) {
- current = null;
- } else {
- current = immediateDominator(current);
- assert current != result;
- }
- return result;
- }
+ @Override
+ public BasicBlock next() {
+ if (!hasNext()) {
+ return null;
+ } else {
+ BasicBlock result = current;
+ if (current.getNumber() == 0) {
+ current = null;
+ } else {
+ current = immediateDominator(current);
+ assert current != result;
+ }
+ return result;
+ }
+ }
+ };
+ if (inclusive == Inclusive.NO) {
+ BasicBlock block = iterator.next();
+ assert block == dominated;
}
+ return iterator;
};
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index a5070ca..870ae6f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -387,11 +387,9 @@
}
}
- private boolean verifyNoBlocksMarked(int color) {
+ public boolean verifyNoBlocksMarked(int color) {
for (BasicBlock block : blocks) {
- if (block.isMarked(color)) {
- return false;
- }
+ assert !block.isMarked(color);
}
return true;
}
@@ -667,15 +665,8 @@
}
// After the throwing instruction only debug instructions and the final jump
// instruction is allowed.
- // TODO(mkroghj) Temporarily allow stack-operations to be after throwing instructions.
if (seenThrowing) {
- assert instruction.isDebugInstruction()
- || instruction.isJumpInstruction()
- || instruction.isStore()
- || instruction.isPop()
- || instruction.isDup()
- || instruction.isDup2()
- || instruction.isSwap();
+ assert instruction.isDebugInstruction() || instruction.isGoto();
}
}
}
@@ -919,7 +910,7 @@
public Set<BasicBlock> getUnreachableBlocks() {
Set<BasicBlock> unreachableBlocks = Sets.newIdentityHashSet();
int color = reserveMarkingColor();
- markReachableBlocks(color);
+ markTransitiveSuccessors(blocks.getFirst(), color);
for (BasicBlock block : blocks) {
if (!block.isMarked(color)) {
unreachableBlocks.add(block);
@@ -932,7 +923,7 @@
public Set<Value> removeUnreachableBlocks() {
ImmutableSet.Builder<Value> affectedValueBuilder = ImmutableSet.builder();
int color = reserveMarkingColor();
- markReachableBlocks(color);
+ markTransitiveSuccessors(blocks.getFirst(), color);
ListIterator<BasicBlock> blockIterator = listIterator();
while (blockIterator.hasNext()) {
BasicBlock current = blockIterator.next();
@@ -946,10 +937,10 @@
}
// Note: It is the responsibility of the caller to return the marking color.
- private void markReachableBlocks(int color) {
+ private void markTransitiveSuccessors(BasicBlock subject, int color) {
assert isMarkingColorInUse(color) && !anyBlocksMarkedWithColor(color);
Queue<BasicBlock> worklist = new ArrayDeque<>();
- worklist.add(blocks.getFirst());
+ worklist.add(subject);
while (!worklist.isEmpty()) {
BasicBlock block = worklist.poll();
if (block.isMarked(color)) {
@@ -963,4 +954,27 @@
}
}
}
+
+ /**
+ * Marks the transitive predecessors of the given block, including the block itself.
+ *
+ * <p>Note: It is the responsibility of the caller to return the marking color.
+ */
+ public void markTransitivePredecessors(BasicBlock subject, int color) {
+ assert isMarkingColorInUse(color) && !anyBlocksMarkedWithColor(color);
+ Queue<BasicBlock> worklist = new ArrayDeque<>();
+ worklist.add(subject);
+ while (!worklist.isEmpty()) {
+ BasicBlock block = worklist.poll();
+ if (block.isMarked(color)) {
+ continue;
+ }
+ block.mark(color);
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ if (!predecessor.isMarked(color)) {
+ worklist.add(predecessor);
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index d292aca..acc122b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -17,9 +17,13 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -158,4 +162,24 @@
public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
return object() == value;
}
+
+ @Override
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ if (object().getTypeLattice().isNullable()) {
+ // If the receiver is null we cannot be sure that the holder has been initialized.
+ return false;
+ }
+ }
+ DexType holder = getField().clazz;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 2d70317..48bc0c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -15,9 +15,13 @@
import com.android.tools.r8.code.IputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -141,4 +145,24 @@
public boolean throwsNpeIfValueIsNull(Value value, DexItemFactory dexItemFactory) {
return object() == value;
}
+
+ @Override
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ if (object().getTypeLattice().isNullable()) {
+ // If the receiver is null we cannot be sure that the holder has been initialized.
+ return false;
+ }
+ }
+ DexType holder = getField().clazz;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 6d21c5a..d8500e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -8,10 +8,14 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.constant.Bottom;
import com.android.tools.r8.ir.analysis.constant.ConstRangeLatticeElement;
import com.android.tools.r8.ir.analysis.constant.LatticeElement;
@@ -1250,11 +1254,14 @@
* Indicates whether the instruction triggers the class initialization (i.e. the <clinit> method)
* of the given class at runtime execution.
*
- * @param klass a class of the program
* @return true if the instruction triggers intialization of the class at runtime, false
- * otherwise.
+ * otherwise.
*/
- public boolean triggersInitializationOfClass(DexType klass) {
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 10a80e2..ca1180f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -7,9 +7,12 @@
import com.android.tools.r8.code.InvokeDirectRange;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -120,4 +123,24 @@
public void buildCf(CfBuilder builder) {
builder.add(new CfInvoke(Opcodes.INVOKESPECIAL, getInvokedMethod(), itf));
}
+
+ @Override
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ if (getReceiver().getTypeLattice().isNullable()) {
+ // If the receiver is null we cannot be sure that the holder has been initialized.
+ return false;
+ }
+ }
+ DexType holder = getInvokedMethod().holder;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 060f1d8..d203184 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -61,7 +62,10 @@
public abstract Collection<DexEncodedMethod> lookupTargets(
AppInfoWithSubtyping appInfo, DexType invocationContext);
- public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
+ public abstract InlineAction computeInlining(
+ InliningOracle decider,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis);
@Override
public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 44ec246..f994058 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -34,7 +35,10 @@
}
@Override
- public final InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ public final InlineAction computeInlining(
+ InliningOracle decider,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis) {
return decider.computeForInvokeWithReceiver(this, invocationContext);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index fa760ff..4b9ff7d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -126,7 +127,10 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ public InlineAction computeInlining(
+ InliningOracle decider,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis) {
return decider.computeForInvokePolymorphic(this, invocationContext);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index d5f86fa..e203cc6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -6,9 +6,13 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokeStaticRange;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -105,8 +109,11 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
- return decider.computeForInvokeStatic(this, invocationContext);
+ public InlineAction computeInlining(
+ InliningOracle decider,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis) {
+ return decider.computeForInvokeStatic(this, invocationContext, classInitializationAnalysis);
}
@Override
@@ -115,7 +122,20 @@
}
@Override
- public boolean triggersInitializationOfClass(DexType klass) {
- return getInvokedMethod().holder == klass;
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ // Class initialization may fail with ExceptionInInitializerError.
+ return false;
+ }
+ DexType holder = getInvokedMethod().holder;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index cbb645f..b02ca94 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -5,10 +5,15 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -107,4 +112,39 @@
InliningConstraints inliningConstraints, DexType invocationContext) {
return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
}
+
+ @Override
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ if (getReceiver().getTypeLattice().isNullable()) {
+ // If the receiver is null we cannot be sure that the holder has been initialized.
+ return false;
+ }
+ }
+ if (mode == Query.DIRECTLY) {
+ // We cannot ensure exactly which class is being loaded because it depends on the runtime
+ // type of the receiver.
+ // TODO(christofferqa): We can do better if there is a unique target.
+ return false;
+ }
+ DexMethod method = getInvokedMethod();
+ DexClass enclosingClass = appView.appInfo().definitionFor(method.holder);
+ if (enclosingClass == null) {
+ return false;
+ }
+ DexType superType = enclosingClass.superType;
+ if (superType == null) {
+ return false;
+ }
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(superType, method);
+ if (!resolutionResult.hasSingleTarget()) {
+ return false;
+ }
+ DexType holder = resolutionResult.asSingleTarget().method.holder;
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 7ef8979..4c2327f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -5,10 +5,14 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -98,4 +102,31 @@
public void buildCf(CfBuilder builder) {
builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, getInvokedMethod(), false));
}
+
+ @Override
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ if (getReceiver().getTypeLattice().isNullable()) {
+ // If the receiver is null we cannot be sure that the holder has been initialized.
+ return false;
+ }
+ }
+ if (mode == Query.DIRECTLY) {
+ // We cannot ensure exactly which class is being loaded because it depends on the runtime
+ // type of the receiver.
+ // TODO(christofferqa): We can do better if there is a unique target.
+ return false;
+ }
+ DexMethod method = getInvokedMethod();
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+ if (!resolutionResult.hasSingleTarget()) {
+ return false;
+ }
+ DexType holder = resolutionResult.asSingleTarget().method.holder;
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index fca7b7e..93b1042 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -8,7 +8,11 @@
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -107,8 +111,17 @@
}
@Override
- public boolean triggersInitializationOfClass(DexType klass) {
- return clazz == klass;
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ DexType holder = this.clazz;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
public void markNoSpilling() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index af9c5c6..db3838c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -16,8 +16,12 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -147,7 +151,20 @@
}
@Override
- public boolean triggersInitializationOfClass(DexType klass) {
- return getField().clazz == klass;
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ // Class initialization may fail with ExceptionInInitializerError.
+ return false;
+ }
+ DexType holder = getField().clazz;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index ba33cbf..4f63a1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -14,8 +14,12 @@
import com.android.tools.r8.code.SputWide;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -130,7 +134,20 @@
}
@Override
- public boolean triggersInitializationOfClass(DexType klass) {
- return getField().clazz == klass;
+ public boolean definitelyTriggersClassInitialization(
+ DexType clazz,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ Query mode,
+ AnalysisAssumption assumption) {
+ if (assumption == AnalysisAssumption.NONE) {
+ // Class initialization may fail with ExceptionInInitializerError.
+ return false;
+ }
+ DexType holder = getField().clazz;
+ if (mode == Query.DIRECTLY) {
+ return holder == clazz;
+ } else {
+ return holder.isSubtypeOf(clazz, appView.appInfo());
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 21cb146..202bac6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -75,7 +75,7 @@
@Override
public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
- // Nothing to do. This is only hit because loads and stores are insert for phis.
+ throw new Unreachable("This IR must not be inserted before load and store insertion.");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 6b5f6c4..8a46e1e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -149,9 +149,6 @@
computeInitializers();
typeVerificationHelper = new TypeVerificationHelper(code, factory, appInfo);
typeVerificationHelper.computeVerificationTypes();
- if (!options.testing.noSplittingExceptionalEdges) {
- splitExceptionalBlocks();
- }
rewriter.converter.deadCodeRemover.run(code);
rewriteNots();
LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, typeVerificationHelper, appInfo);
@@ -223,38 +220,6 @@
return initializers;
}
- // Split all blocks with throwing instructions and exceptional edges such that any non-throwing
- // instructions that might define values prior to the throwing exception are excluded from the
- // try-catch range. Failure to do so will result in code that does not verify on the JVM.
- private void splitExceptionalBlocks() {
- ListIterator<BasicBlock> it = code.listIterator();
- while (it.hasNext()) {
- BasicBlock block = it.next();
- if (!block.hasCatchHandlers()) {
- continue;
- }
- int size = block.getInstructions().size();
- boolean isThrow = block.exit().isThrow();
- if ((isThrow && size == 1) || (!isThrow && size == 2)) {
- // Fast-path to avoid processing blocks with just a single throwing instruction.
- continue;
- }
- InstructionListIterator instructions = block.listIterator();
- boolean hasOutValues = false;
- while (instructions.hasNext()) {
- Instruction instruction = instructions.next();
- if (instruction.instructionTypeCanThrow()) {
- break;
- }
- hasOutValues |= instruction.outValue() != null;
- }
- if (hasOutValues) {
- instructions.previous();
- instructions.split(code, it);
- }
- }
- }
-
private void rewriteNots() {
for (BasicBlock block : code.blocks) {
InstructionListIterator it = block.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 5bf9154..0b5e17e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1125,12 +1125,14 @@
codeRewriter.identifyReturnsArgument(method, code, feedback);
if (options.enableInlining && inliner != null) {
- codeRewriter.identifyInvokeSemanticsForInlining(method, code, graphLense(), feedback);
+ codeRewriter.identifyInvokeSemanticsForInlining(method, code, appView, feedback);
}
// Track usage of parameters and compute their nullability and possibility of NPE.
- if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
- computeNonNullParamHints(feedback, method, code);
+ if (enableWholeProgramOptimizations) {
+ if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
+ computeNonNullParamHints(feedback, method, code);
+ }
}
// Insert code to log arguments if requested.
@@ -1189,8 +1191,7 @@
// invoke-static throwParameterIsNullException(msg)
//
// or some other variants, e.g., throw null or NPE after the direct null check.
- if (argument.isUsed()
- && checksNullBeforeSideEffect(code, appInfo, graphLense(), argument)) {
+ if (argument.isUsed() && checksNullBeforeSideEffect(code, argument, appView)) {
paramsCheckedForNull.set(index);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 0af7c9e..359223d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query.DIRECTLY;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo.ClassNameComputationOption.CANONICAL_NAME;
@@ -15,6 +16,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
@@ -42,10 +44,10 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
@@ -1024,17 +1026,19 @@
}
public void identifyInvokeSemanticsForInlining(
- DexEncodedMethod method, IRCode code, GraphLense graphLense, OptimizationFeedback feedback) {
+ DexEncodedMethod method,
+ IRCode code,
+ AppView<? extends AppInfoWithSubtyping> appView,
+ OptimizationFeedback feedback) {
if (method.isStatic()) {
// Identifies if the method preserves class initialization after inlining.
- feedback.markTriggerClassInitBeforeAnySideEffect(method,
- triggersClassInitializationBeforeSideEffect(code, method.method.getHolder()));
+ feedback.markTriggerClassInitBeforeAnySideEffect(
+ method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
} else {
// Identifies if the method preserves null check of the receiver after inlining.
final Value receiver = code.getThis();
- feedback.markCheckNullReceiverBeforeAnySideEffect(method,
- receiver.isUsed()
- && checksNullBeforeSideEffect(code, appInfo, graphLense, receiver));
+ feedback.markCheckNullReceiverBeforeAnySideEffect(
+ method, receiver.isUsed() && checksNullBeforeSideEffect(code, receiver, appView));
}
}
@@ -1366,10 +1370,10 @@
* Returns true if the given code unconditionally throws if value is null before any other side
* effect instruction.
*
- * Note: we do not track phis so we may return false negative. This is a conservative approach.
+ * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
*/
public static boolean checksNullBeforeSideEffect(
- IRCode code, AppInfo appInfo, GraphLense graphLense, Value value) {
+ IRCode code, Value value, AppView<? extends AppInfoWithSubtyping> appView) {
return alwaysTriggerExpectedEffectBeforeAnythingElse(
code,
(instr, it) -> {
@@ -1379,13 +1383,13 @@
if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
return InstructionEffect.CONDITIONAL_EFFECT;
}
- if (isKotlinNullCheck(appInfo, graphLense, instr, value)) {
+ if (isKotlinNullCheck(instr, value, appView)) {
DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
// Kotlin specific way of throwing NPE: throwParameterIsNullException.
// Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
// the value.
if (invokedMethod.name
- == appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException.name) {
+ == appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException.name) {
// We found a NPE (or similar exception) throwing code.
// Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
for (BasicBlock predecessor : currentBlock.getPredecessors()) {
@@ -1401,14 +1405,14 @@
} else {
// Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
assert invokedMethod.name
- == appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull.name;
+ == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull.name;
return InstructionEffect.DESIRED_EFFECT;
}
}
- if (isInstantiationOfNullPointerException(instr, it, appInfo.dexItemFactory)) {
+ if (isInstantiationOfNullPointerException(instr, it, appView.dexItemFactory())) {
it.next(); // Skip call to NullPointerException.<init>.
return InstructionEffect.NO_EFFECT;
- } else if (instr.throwsNpeIfValueIsNull(value, appInfo.dexItemFactory)) {
+ } else if (instr.throwsNpeIfValueIsNull(value, appView.dexItemFactory())) {
// In order to preserve NPE semantic, the exception must not be caught by any handler.
// Therefore, we must ignore this instruction if it is covered by a catch handler.
// Note: this is a conservative approach where we consider that any catch handler could
@@ -1434,7 +1438,7 @@
// declare a method called checkParameterIsNotNull(parameter, message) or
// throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
private static boolean isKotlinNullCheck(
- AppInfo appInfo, GraphLense graphLense, Instruction instr, Value value) {
+ Instruction instr, Value value, AppView<? extends AppInfoWithSubtyping> appView) {
if (!instr.isInvokeStatic()) {
return false;
}
@@ -1442,11 +1446,11 @@
// e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
Wrapper<DexMethod> checkParameterIsNotNull =
- wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull);
+ wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull);
Wrapper<DexMethod> throwParamIsNullException =
- wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+ wrapper.wrap(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
DexMethod invokedMethod =
- graphLense.getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
+ appView.graphLense().getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
if (methodWrap.equals(throwParamIsNullException)
|| (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
@@ -1492,13 +1496,15 @@
* Returns true if the given code unconditionally triggers class initialization before any other
* side effecting instruction.
*
- * Note: we do not track phis so we may return false negative. This is a conservative approach.
+ * <p>Note: we do not track phis so we may return false negative. This is a conservative approach.
*/
- private static boolean triggersClassInitializationBeforeSideEffect(IRCode code, DexType klass) {
+ private static boolean triggersClassInitializationBeforeSideEffect(
+ DexType clazz, IRCode code, AppView<? extends AppInfoWithSubtyping> appView) {
return alwaysTriggerExpectedEffectBeforeAnythingElse(
code,
(instruction, it) -> {
- if (instruction.triggersInitializationOfClass(klass)) {
+ if (instruction.definitelyTriggersClassInitialization(
+ clazz, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
// In order to preserve class initialization semantic, the exception must not be caught
// by any handler. Therefore, we must ignore this instruction if it is covered by a
// catch handler.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 357e949..d7faa6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -115,14 +116,20 @@
return Reason.SIMPLE;
}
- private boolean canInlineStaticInvoke(DexEncodedMethod method, DexEncodedMethod target) {
+ private boolean canInlineStaticInvoke(
+ InvokeStatic invoke,
+ DexEncodedMethod method,
+ DexEncodedMethod target,
+ ClassInitializationAnalysis classInitializationAnalysis) {
// Only proceed with inlining a static invoke if:
- // - the holder for the target equals the holder for the method, or
+ // - the holder for the target is a subtype of the holder for the method,
// - the target method always triggers class initialization of its holder before any other side
- // effect (hence preserving class initialization semantics).
+ // effect (hence preserving class initialization semantics),
+ // - the current method has already triggered the holder for the target method to be
+ // initialized, or
// - there is no non-trivial class initializer.
- DexType targetHolder = target.method.getHolder();
- if (method.method.getHolder() == targetHolder) {
+ DexType targetHolder = target.method.holder;
+ if (method.method.holder.isSubtypeOf(targetHolder, appView.appInfo())) {
return true;
}
DexClass clazz = inliner.appView.appInfo().definitionFor(targetHolder);
@@ -130,6 +137,10 @@
if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
return true;
}
+ if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
+ target.method.holder, invoke)) {
+ return true;
+ }
// Check for class initializer side effects when loading this class, as inlining might remove
// the load operation.
//
@@ -316,7 +327,10 @@
}
@Override
- public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+ public InlineAction computeForInvokeStatic(
+ InvokeStatic invoke,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis) {
DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
if (candidate == null || inliner.isBlackListed(candidate.method)) {
return null;
@@ -333,7 +347,7 @@
}
// Abort inlining attempt if we can not guarantee class for static target has been initialized.
- if (!canInlineStaticInvoke(method, candidate)) {
+ if (!canInlineStaticInvoke(invoke, method, candidate, classInitializationAnalysis)) {
if (info != null) {
info.exclude(invoke, "target is static but we cannot guarantee class has been initialized");
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 6890dd8..5ffa2cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -39,7 +40,10 @@
}
@Override
- public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+ public InlineAction computeForInvokeStatic(
+ InvokeStatic invoke,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis) {
return computeForInvoke(invoke);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 27ea742..953562f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -593,6 +594,8 @@
InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod context, IRCode code) {
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
+ ClassInitializationAnalysis classInitializationAnalysis =
+ new ClassInitializationAnalysis(appView, code);
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (blocksToRemove.contains(block)) {
@@ -603,7 +606,8 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- InlineAction result = invoke.computeInlining(oracle, context.method.holder);
+ InlineAction result =
+ invoke.computeInlining(oracle, context.method.holder, classInitializationAnalysis);
if (result != null) {
if (!(strategy.stillHasBudget() || result.reason.mustBeInlined())) {
continue;
@@ -641,6 +645,8 @@
strategy.markInlined(inlinee);
iterator.inlineInvoke(
appView.appInfo(), code, inlinee.code, blockIterator, blocksToRemove, downcast);
+
+ classInitializationAnalysis.notifyCodeHasChanged();
strategy.updateTypeInformationIfNeeded(inlinee.code, blockIterator, block);
// If we inlined the invoke from a bridge method, it is no longer a bridge method.
@@ -656,6 +662,7 @@
}
}
}
+ classInitializationAnalysis.finish();
oracle.finish();
code.removeBlocks(blocksToRemove);
code.removeAllTrivialPhis();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 08f8045..15a6329 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokePolymorphic;
import com.android.tools.r8.ir.code.InvokeStatic;
@@ -21,7 +22,9 @@
InvokeMethodWithReceiver invoke, DexType invocationContext);
InlineAction computeForInvokeStatic(
- InvokeStatic invoke, DexType invocationContext);
+ InvokeStatic invoke,
+ DexType invocationContext,
+ ClassInitializationAnalysis classInitializationAnalysis);
InlineAction computeForInvokePolymorphic(
InvokePolymorphic invoke, DexType invocationContext);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index cdf37c4..1f036b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.OptimizationInfo;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
+import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -822,7 +823,9 @@
}
// Check if the method is inline-able by standard inliner.
- InlineAction inlineAction = invoke.computeInlining(defaultOracle.get(), method.method.holder);
+ InlineAction inlineAction =
+ invoke.computeInlining(
+ defaultOracle.get(), method.method.holder, ClassInitializationAnalysis.trivial());
if (inlineAction == null) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3997982..7f4fbe1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -539,7 +539,6 @@
public boolean forceNameReflectionOptimization = false;
public boolean disallowLoadStoreOptimization = false;
public Consumer<IRCode> irModifier = null;
- public boolean noSplittingExceptionalEdges = false;
}
private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
index e4c7098..926a22d 100644
--- a/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/TryRangeTestRunner.java
@@ -8,9 +8,19 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
import java.util.ListIterator;
import org.junit.Test;
@@ -18,8 +28,7 @@
* This tests that we produce valid code when having normal-flow with exceptional edges in blocks.
* We might perform optimizations that add operations (dup, swap, etc.) before and after
* instructions that lie on the boundary of the exception table that is generated for a basic block.
- * If live-ranges are minimized this could produce VerifyErrors. TODO(b/119771771) Will fail if
- * shorten live ranges without shorten exception table range.
+ * If live-ranges are minimized this could produce VerifyErrors.
*/
public class TryRangeTestRunner extends TestBase {
@@ -37,27 +46,45 @@
o.testing.disallowLoadStoreOptimization = true;
})
.run(TryRangeTest.class)
- .assertSuccess();
+ .assertSuccessWithOutput(StringUtils.lines("10", "7.0"));
}
@Test
public void testRegisterAllocationLimitLeadingRange() throws Exception {
- testForR8(Backend.CF)
- .addProgramClasses(TryRangeTestLimitRange.class)
- .addKeepMainRule(TryRangeTestLimitRange.class)
- .setMode(CompilationMode.RELEASE)
- .minification(false)
- .noTreeShaking()
- .enableInliningAnnotations()
- .addOptionsModification(
- o -> {
- o.testing.disallowLoadStoreOptimization = true;
- o.testing.irModifier = this::processIR;
- // TODO(mkroghj) Remove this option entirely when splittingExceptionalEdges is moved.
- o.testing.noSplittingExceptionalEdges = true;
- })
- .run(TryRangeTestLimitRange.class)
- .assertFailure();
+ CodeInspector inspector =
+ testForR8(Backend.CF)
+ .addProgramClasses(TryRangeTestLimitRange.class)
+ .addKeepMainRule(TryRangeTestLimitRange.class)
+ .setMode(CompilationMode.RELEASE)
+ .minification(false)
+ .noTreeShaking()
+ .enableInliningAnnotations()
+ .addOptionsModification(
+ o -> {
+ o.testing.disallowLoadStoreOptimization = true;
+ o.testing.irModifier = this::processIR;
+ })
+ .run(TryRangeTestLimitRange.class)
+ .assertSuccessWithOutput("")
+ .inspector();
+ // Assert that we do not have any register-modifying instructions in the throwing block:
+ // L0: ; locals:
+ // iload 1;
+ // invokestatic com.android.tools.r8.cf.TryRangeTestLimitRange.doSomething(I)F
+ // L1: ; locals:
+ // 11: pop
+ ClassSubject clazz = inspector.clazz("com.android.tools.r8.cf.TryRangeTestLimitRange");
+ CfCode cfCode = clazz.uniqueMethodWithName("main").getMethod().getCode().asCfCode();
+ List<CfInstruction> instructions = cfCode.getInstructions();
+ CfLabel startLabel = cfCode.getTryCatchRanges().get(0).start;
+ int index = 0;
+ while (instructions.get(index) != startLabel) {
+ index++;
+ }
+ assert instructions.get(index + 1) instanceof CfLoad;
+ assert instructions.get(index + 2) instanceof CfInvoke;
+ assert instructions.get(index + 3) == cfCode.getTryCatchRanges().get(0).end;
+ assert instructions.get(index + 4) instanceof CfStackInstruction;
}
private void processIR(IRCode code) {
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index da0bc9e..62c6bc8 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -17,8 +17,6 @@
private String proguardMap1 = null;
private String proguardMap2 = null;
- @Rule public final ExpectedException exception = ExpectedException.none();
-
@Test
public void buildAndTreeShakeFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
new file mode 100644
index 0000000..1144491
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningAfterClassInitializationTest.java
@@ -0,0 +1,373 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.KeepConstantArguments;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class InliningAfterClassInitializationTest extends TestBase {
+
+ @Test
+ public void testClass1() throws Exception {
+ Class<TestClass1> mainClass = TestClass1.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines("In A.<clinit>()", "In A.notInlineable()", "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+
+ MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+ assertThat(notInlineableMethod, isPresent());
+
+ MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+ assertThat(testMethod, isPresent());
+ assertThat(testMethod, invokesMethod(notInlineableMethod));
+ }
+
+ @Test
+ public void testClass2() throws Exception {
+ Class<TestClass2> mainClass = TestClass2.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ @Test
+ public void testClass3() throws Exception {
+ Class<TestClass3> mainClass = TestClass3.class;
+ CodeInspector inspector =
+ buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ @Test
+ public void testClass4() throws Exception {
+ Class<TestClass4> mainClass = TestClass4.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines("In A.<clinit>()", "Field A.instanceField", "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ @Test
+ public void testClass5() throws Exception {
+ Class<TestClass5> mainClass = TestClass5.class;
+ CodeInspector inspector =
+ buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ @Test
+ public void testClass6() throws Exception {
+ Class<TestClass6> mainClass = TestClass6.class;
+ CodeInspector inspector =
+ buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.notInlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+ assertThat(notInlineableMethod, isPresent());
+
+ MethodSubject testMethod = inspector.clazz(mainClass).uniqueMethodWithName("test");
+ assertThat(testMethod, isPresent());
+ assertThat(testMethod, invokesMethod(notInlineableMethod));
+ }
+
+ @Test
+ public void testClass7() throws Exception {
+ Class<TestClass7> mainClass = TestClass7.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines(
+ "Caught NullPointerException",
+ "In A.<clinit>()",
+ "Field A.instanceField",
+ "In A.inlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
+ assertThat(inlineableMethod, not(isPresent()));
+ }
+
+ @Test
+ public void testClass8() throws Exception {
+ Class<TestClass8> mainClass = TestClass8.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines(
+ "In A.<clinit>()", "In A.notInlineable()", "In A.alsoNotInlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+ assertThat(notInlineableMethod, isPresent());
+
+ MethodSubject alsoNotInlineableMethod = classA.uniqueMethodWithName("alsoNotInlineable");
+ assertThat(alsoNotInlineableMethod, isPresent());
+
+ MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+ assertThat(testMethod, isPresent());
+ assertThat(testMethod, invokesMethod(notInlineableMethod));
+ assertThat(testMethod, invokesMethod(alsoNotInlineableMethod));
+ }
+
+ @Test
+ public void testClass9() throws Exception {
+ Class<TestClass9> mainClass = TestClass9.class;
+ CodeInspector inspector =
+ buildAndRun(
+ mainClass,
+ StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.notInlineable()"));
+
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+
+ MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
+ assertThat(notInlineableMethod, isPresent());
+
+ MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
+ assertThat(testMethod, isPresent());
+ assertThat(testMethod, invokesMethod(notInlineableMethod));
+ }
+
+ private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
+ testForJvm().addTestClasspath().run(mainClass).assertSuccessWithOutput(expectedOutput);
+
+ return testForR8(Backend.DEX)
+ .addInnerClasses(InliningAfterClassInitializationTest.class)
+ .addKeepMainRule(mainClass)
+ .enableConstantArgumentAnnotations()
+ .enableInliningAnnotations()
+ .enableMergeAnnotations()
+ .run(mainClass)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+ }
+
+ static class TestClass1 {
+
+ public static void main(String[] args) {
+ A.notInlineable();
+
+ // Since the previous call will cause the initializer of A to run, we can safely inline this
+ // call.
+ A.inlineable();
+ }
+ }
+
+ static class TestClass2 {
+
+ public static void main(String[] args) {
+ System.out.println(A.staticField);
+
+ // Since the previous instruction will cause the initializer of A to run, we can safely inline
+ // this call.
+ A.inlineable();
+ }
+ }
+
+ static class TestClass3 {
+
+ public static void main(String[] args) {
+ A.staticField = "Hello world!";
+
+ // Since the previous instruction will cause the initializer of A to run, we can safely inline
+ // this call.
+ A.inlineable();
+
+ // Make sure the field is read to prevent the static-put instruction from being removed
+ // (b/123553485).
+ spuriousFieldRead();
+ }
+
+ private static void spuriousFieldRead() {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(A.staticField);
+ }
+ }
+ }
+
+ static class TestClass4 {
+
+ public static void main(String[] args) {
+ test(new A());
+ }
+
+ @NeverInline
+ private static void test(A obj) {
+ System.out.println(obj.instanceField);
+
+ // Since the previous instruction will cause the initializer of A to run, we can safely inline
+ // this call.
+ A.inlineable();
+ }
+ }
+
+ static class TestClass5 {
+
+ public static void main(String[] args) {
+ test(new A());
+ }
+
+ @NeverInline
+ private static void test(A obj) {
+ obj.instanceField = "Hello world!";
+
+ // Since the previous instruction will cause the initializer of A to run, we can safely inline
+ // this call.
+ A.inlineable();
+
+ // Make sure the field is read to prevent the instance-put instruction from being removed.
+ spuriousFieldRead(obj);
+ }
+
+ private static void spuriousFieldRead(A obj) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(obj.instanceField);
+ }
+ }
+ }
+
+ static class TestClass6 {
+
+ public static void main(String[] args) {
+ test(null);
+ }
+
+ @KeepConstantArguments
+ @NeverInline
+ private static void test(A obj) {
+ try {
+ String value = obj.instanceField;
+ System.out.println(value);
+ } catch (NullPointerException e) {
+ // Ignore.
+ }
+
+ // This call cannot be inlined because `obj.field` may throw a NullPointerException, in which
+ // case A is not guaranteed to be initialized.
+ A.notInlineable();
+ }
+ }
+
+ static class TestClass7 {
+
+ public static void main(String[] args) {
+ test(null);
+ test(new A());
+ }
+
+ @NeverInline
+ private static void test(A obj) {
+ try {
+ String value = obj.instanceField;
+ System.out.println(value);
+ } catch (NullPointerException e) {
+ // Ignore.
+ System.out.println("Caught NullPointerException");
+ return;
+ }
+
+ // Due to the `return` in the catch handler above, A is guaranteed to be initialized if we
+ // reach this line.
+ A.inlineable();
+ }
+ }
+
+ static class TestClass8 {
+
+ public static void main(String[] args) {
+ try {
+ A.notInlineable();
+ } catch (ExceptionInInitializerError e) {
+ System.out.println("Caught ExceptionInInitializerError");
+ }
+
+ A.alsoNotInlineable();
+ }
+ }
+
+ static class TestClass9 {
+
+ public static void main(String[] args) {
+ try {
+ String value = A.staticField;
+ System.out.println(value);
+ } catch (ExceptionInInitializerError e) {
+ System.out.println("Caught ExceptionInInitializerError");
+ }
+
+ A.notInlineable();
+ }
+ }
+
+ static class A {
+
+ public String instanceField = "Field A.instanceField";
+ public static String staticField =
+ System.currentTimeMillis() >= 0 ? "Field A.staticField" : null;
+
+ static {
+ System.out.println("In A.<clinit>()");
+ }
+
+ static void notInlineable() {
+ System.out.println("In A.notInlineable()");
+ }
+
+ static void alsoNotInlineable() {
+ System.out.println("In A.alsoNotInlineable()");
+ }
+
+ static void inlineable() {
+ System.out.println("In A.inlineable()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
index 71c5fbe..d581291 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningFromCurrentClassTest.java
@@ -16,12 +16,10 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Ignore;
import org.junit.Test;
public class InliningFromCurrentClassTest extends TestBase {
- @Ignore("b/123327416")
@Test
public void test() throws Exception {
String expectedOutput =
@@ -65,8 +63,6 @@
MethodSubject testMethod = classB.uniqueMethodWithName("test");
assertThat(testMethod, isPresent());
- assertThat(testMethod, not(invokesMethod(inlineable1Method)));
- assertThat(testMethod, not(invokesMethod(inlineable2Method)));
assertThat(testMethod, invokesMethod(notInlineableMethod));
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
index 9691149..a264d15 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningWithClassInitializerTest.java
@@ -16,16 +16,13 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Ignore;
import org.junit.Test;
public class InliningWithClassInitializerTest extends TestBase {
- @Ignore("b/123327413")
@Test
public void test() throws Exception {
- String expectedOutput =
- StringUtils.lines("In A.<clinit>()", "In B.inlineable()", "In B.other()");
+ String expectedOutput = StringUtils.lines("In A.<clinit>()", "In B.<clinit>()", "In B.other()");
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
@@ -74,8 +71,11 @@
static class B extends A {
+ static {
+ System.out.println("In B.<clinit>()");
+ }
+
static void inlineable() {
- System.out.println("In B.inlineable()");
other();
}
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 10fb29f..5f224a6 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -126,6 +126,9 @@
for line in lines:
if '-printconfiguration' not in line:
f.write(line)
+ # Check that there is a line-break at the end of the file or insert one.
+ if lines[-1].strip():
+ f.write('\n')
f.write('-printconfiguration {}\n'.format(destination))
def FindProguardConfigurationFile(app, config, checkout_dir):